From 52c9b5fb081a88f85f881abd03adbe60ebd5ec2d Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Tue, 10 Sep 2019 11:41:13 +0300 Subject: [PATCH 01/49] Changed version number (0, 5, 'final', 0). --- cvat/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cvat/__init__.py b/cvat/__init__.py index 35efa70d25c..b962ba74c71 100644 --- a/cvat/__init__.py +++ b/cvat/__init__.py @@ -5,6 +5,6 @@ from cvat.utils.version import get_version -VERSION = (0, 5, 0, 'alpha', 0) +VERSION = (0, 5, 0, 'final', 0) __version__ = get_version(VERSION) From 0967a74b9cccab899f122a695b330f00ef4dbf2a Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Tue, 10 Sep 2019 12:02:32 +0300 Subject: [PATCH 02/49] Updated changelog file. --- CHANGELOG.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 198db453cb5..de92930e755 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [0.5.0] - 2019-10-12 ### Added - A converter to YOLO format - Installation guide @@ -20,13 +20,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added in a command line model manager tester - Ability to dump/load annotations in several formats from UI (CVAT, Pascal VOC, YOLO, MS COCO, png mask, TFRecord) - Auth for REST API (api/v1/auth/): login, logout, register, ... +- Preview for the new CVAT UI (dashboard only) is available: http://localhost:9080/ ### Changed - Outside and keyframe buttons in the side panel for all interpolation shapes (they were only for boxes before) -- Improved error messages on client side (#511) - -### Deprecated -- +- Improved error messages on the client side (#511) ### Removed - "Flip images" has been removed. UI now contains rotation features. @@ -49,7 +47,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Creating a video task with 0 overlap ### Security -- +- Upgraded Django, djangorestframework, and other packages ## [0.4.2] - 2019-06-03 ### Fixed From fda7c1a2227ba376c630d2aec2e3e969c1c16508 Mon Sep 17 00:00:00 2001 From: Andrey Zhavoronkov <41117609+azhavoro@users.noreply.github.com> Date: Tue, 10 Sep 2019 15:50:19 +0300 Subject: [PATCH 03/49] fixed default attribute values for tracked shapes (#703) --- cvat/apps/engine/annotation.py | 47 +++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/cvat/apps/engine/annotation.py b/cvat/apps/engine/annotation.py index fb152076b87..acb9a6d5e34 100644 --- a/cvat/apps/engine/annotation.py +++ b/cvat/apps/engine/annotation.py @@ -21,6 +21,14 @@ from .log import slogger from . import serializers +"""dot.notation access to dictionary attributes""" +class dotdict(OrderedDict): + __getattr__ = OrderedDict.get + __setattr__ = OrderedDict.__setitem__ + __delattr__ = OrderedDict.__delitem__ + __eq__ = lambda self, other: self.id == other.id + __hash__ = lambda self: self.id + class PatchAction(str, Enum): CREATE = "create" UPDATE = "update" @@ -142,15 +150,6 @@ def bulk_create(db_model, objects, flt_param): return [] def _merge_table_rows(rows, keys_for_merge, field_id): - """dot.notation access to dictionary attributes""" - from collections import OrderedDict - class dotdict(OrderedDict): - __getattr__ = OrderedDict.get - __setattr__ = OrderedDict.__setitem__ - __delattr__ = OrderedDict.__delitem__ - __eq__ = lambda self, other: self.id == other.id - __hash__ = lambda self: self.id - # It is necessary to keep a stable order of original rows # (e.g. for tracked boxes). Otherwise prev_box.frame can be bigger # than next_box.frame. @@ -202,12 +201,16 @@ def __init__(self, pk, user): "all": OrderedDict(), } for db_attr in db_label.attributespec_set.all(): + default_value = dotdict([ + ('spec_id', db_attr.id), + ('value', db_attr.default_value), + ]) if db_attr.mutable: - self.db_attributes[db_label.id]["mutable"][db_attr.id] = db_attr + self.db_attributes[db_label.id]["mutable"][db_attr.id] = default_value else: - self.db_attributes[db_label.id]["immutable"][db_attr.id] = db_attr + self.db_attributes[db_label.id]["immutable"][db_attr.id] = default_value - self.db_attributes[db_label.id]["all"][db_attr.id] = db_attr + self.db_attributes[db_label.id]["all"][db_attr.id] = default_value def reset(self): self.ir_data.reset() @@ -458,13 +461,13 @@ def delete(self, data=None): self._commit() @staticmethod - def _extend_attributes(attributeval_set, attribute_specs): + def _extend_attributes(attributeval_set, default_attribute_values): shape_attribute_specs_set = set(attr.spec_id for attr in attributeval_set) - for db_attr_spec in attribute_specs: - if db_attr_spec.id not in shape_attribute_specs_set: - attributeval_set.append(OrderedDict([ - ('spec_id', db_attr_spec.id), - ('value', db_attr_spec.default_value), + for db_attr in default_attribute_values: + if db_attr.spec_id not in shape_attribute_specs_set: + attributeval_set.append(dotdict([ + ('spec_id', db_attr.spec_id), + ('value', db_attr.value), ])) def _init_tags_from_db(self): @@ -600,12 +603,16 @@ def _init_tracks_from_db(self): self._extend_attributes(db_track.labeledtrackattributeval_set, self.db_attributes[db_track.label_id]["immutable"].values()) + default_attribute_values = self.db_attributes[db_track.label_id]["mutable"].values() for db_shape in db_track["trackedshape_set"]: db_shape["trackedshapeattributeval_set"] = list( set(db_shape["trackedshapeattributeval_set"]) ) - self._extend_attributes(db_shape["trackedshapeattributeval_set"], - self.db_attributes[db_track.label_id]["mutable"].values()) + # in case of trackedshapes need to interpolate attriute values and extend it + # by previous shape attribute values (not default values) + self._extend_attributes(db_shape["trackedshapeattributeval_set"], default_attribute_values) + default_attribute_values = db_shape["trackedshapeattributeval_set"] + serializer = serializers.LabeledTrackSerializer(db_tracks, many=True) self.ir_data.tracks = serializer.data From eacfab792039ea105603ac7636e966392794d517 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Thu, 17 Oct 2019 12:59:55 +0300 Subject: [PATCH 04/49] Updated CHANGELOG with information about Zenodo --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de92930e755..fb1264eeee0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.5.0] - 2019-10-12 +## [0.5.1] - 2019-10-17 +### Added +- Integration with Zenodo.org (DOI) + +## [0.5.0] - 2019-09-12 ### Added - A converter to YOLO format - Installation guide From 69bc32b07fb9de919bfdb0d64b7d4c3e4c8b1c26 Mon Sep 17 00:00:00 2001 From: Nikita Manovich <40690625+nmanovic@users.noreply.github.com> Date: Thu, 17 Oct 2019 13:01:44 +0300 Subject: [PATCH 05/49] Updated CHANGELOG with information about Zenodo (#777) --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de92930e755..fb1264eeee0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.5.0] - 2019-10-12 +## [0.5.1] - 2019-10-17 +### Added +- Integration with Zenodo.org (DOI) + +## [0.5.0] - 2019-09-12 ### Added - A converter to YOLO format - Installation guide From 87a74a7d0d6db226cca253013ba35d6022db751c Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Thu, 17 Oct 2019 13:03:28 +0300 Subject: [PATCH 06/49] Updated version of the project. --- cvat/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cvat/__init__.py b/cvat/__init__.py index b962ba74c71..104e7859336 100644 --- a/cvat/__init__.py +++ b/cvat/__init__.py @@ -5,6 +5,6 @@ from cvat.utils.version import get_version -VERSION = (0, 5, 0, 'final', 0) +VERSION = (0, 5, 1, 'final', 0) __version__ = get_version(VERSION) From 91245843d75f67817647cfe01c14fcde4d14d0b1 Mon Sep 17 00:00:00 2001 From: Boris Sekachev <40690378+bsekachev@users.noreply.github.com> Date: Mon, 16 Dec 2019 18:34:12 +0300 Subject: [PATCH 07/49] Hotfix: fixed skikit-image version (#965) * Fixed skikit-image version * Updated changelog --- CHANGELOG.md | 4 ++++ cvat/requirements/base.txt | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb1264eeee0..1b1f9e7b129 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.5.2] - 2019-12-15 +### Fixed +- Frozen version of scikit-image==0.15 in requirements.txt because next releases don't support Python 3.5 + ## [0.5.1] - 2019-10-17 ### Added - Integration with Zenodo.org (DOI) diff --git a/cvat/requirements/base.txt b/cvat/requirements/base.txt index f5ec239e53b..2f8ecbcd7f6 100644 --- a/cvat/requirements/base.txt +++ b/cvat/requirements/base.txt @@ -38,7 +38,7 @@ pascal_voc_writer==0.1.4 django-rest-auth[with_social]==0.9.5 cython==0.29.13 matplotlib==3.0.3 -scikit-image>=0.14.0 +scikit-image==0.15.0 tensorflow==1.12.3 django-cors-headers==3.0.2 furl==2.0.0 From 42aad8b56bcce454b99a852af290d4a9208d37d8 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Mon, 16 Dec 2019 18:40:15 +0300 Subject: [PATCH 08/49] Increased CVAT version (0.5.2) --- cvat/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cvat/__init__.py b/cvat/__init__.py index 104e7859336..9b671a9bf29 100644 --- a/cvat/__init__.py +++ b/cvat/__init__.py @@ -5,6 +5,6 @@ from cvat.utils.version import get_version -VERSION = (0, 5, 1, 'final', 0) +VERSION = (0, 5, 2, 'final', 0) __version__ = get_version(VERSION) From 965ff77898d54f40c35d4019dd646493dd66b01e Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Thu, 12 Mar 2020 13:50:08 +0300 Subject: [PATCH 09/49] wip --- cvat-canvas/src/typescript/canvasView.ts | 23 +++++++++++++++++++ .../standard-workspace/canvas-wrapper.tsx | 4 ++++ 2 files changed, 27 insertions(+) diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index d026c5246a7..1ea3bdd6de3 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -453,6 +453,27 @@ export class CanvasViewImpl implements CanvasView, Listener { e.preventDefault(); } + function contextmenuHandler(e: MouseEvent): void { + const pointID = Array.prototype.indexOf + .call(((e.target as HTMLElement).parentElement as HTMLElement).children, e.target); + if (self.activeElement.clientID !== null) { + const [state] = self.controller.objects + .filter((_state: any): boolean => ( + _state.clientID === self.activeElement.clientID + )); + self.canvas.dispatchEvent(new CustomEvent('point.contextmenu', { + bubbles: false, + cancelable: true, + detail: { + mouseEvent: e, + objectState: state, + pointID, + }, + })); + } + e.preventDefault(); + } + if (value) { (shape as any).selectize(value, { deepSelect: true, @@ -475,6 +496,7 @@ export class CanvasViewImpl implements CanvasView, Listener { }); circle.on('dblclick', dblClickHandler); + circle.on('contextmenu', contextmenuHandler); circle.addClass('cvat_canvas_selected_point'); }); @@ -484,6 +506,7 @@ export class CanvasViewImpl implements CanvasView, Listener { }); circle.off('dblclick', dblClickHandler); + circle.off('contextmenu', contextmenuHandler); circle.removeClass('cvat_canvas_selected_point'); }); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx index 1b5d750820d..aaa6645dc08 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -516,6 +516,10 @@ export default class CanvasWrapperComponent extends React.PureComponent { canvasInstance.html().addEventListener('canvas.merged', this.onObjectsMerged.bind(this)); canvasInstance.html().addEventListener('canvas.groupped', this.onObjectsGroupped.bind(this)); canvasInstance.html().addEventListener('canvas.splitted', this.onTrackSplitted.bind(this)); + + canvasInstance.html().addEventListener('point.contextmenu', (event: any) => { + console.log(event); + }); } public render(): JSX.Element { From 8bf647b360cae637f5bd9282c8a2cb9252afacf3 Mon Sep 17 00:00:00 2001 From: Nikita Manovich <40690625+nmanovic@users.noreply.github.com> Date: Sun, 15 Mar 2020 15:51:36 +0300 Subject: [PATCH 10/49] Release 0.6.0 (#1238) * Release 0.5 (#705) * Changed version number (0, 5, 'final', 0). * Updated changelog file. * fixed default attribute values for tracked shapes (#703) * typo ? Should not this be cvat_redis -> redis ? * Fixed labels regex for non-latin characters (#708) * Update README.md * Update README.md * Don't save shapes with keyframe==False * Selecting non images leads to 400 error (#734) * Fix HTTP 400 error if together with vision data the user submit non-vision data (e.g. text files) * Ignore SVG images because Pillow doesn't work with them. * Fix the problem with duplicated frames in case of "share" (#735) * Fix the problem with duplicated frames in case of "share". * Fix a case when the code works incorrectly /a/b/c /a/b/c0 Previously only /a/b/c will be in output but should be both. * added method docs to Auto Annotation inference.py (#725) * remove deprecated method call `from_ir` (#726) * New command line tool for working with tasks (#732) * Adding new command line tool for performing common task related operations (create, list, delete, etc.) * Replaced @exception decorator with try/except in main() * Replaced optional --name with positional name and removed default * Added license text to files * Added django units to cover future API changes * Refactored into submodules to better support tests * Fix an issue with permissions (observer can change annotations) (#745) * Fixed a problem with observer (check_object_permissions method was not called) * Added a test case to cover issue #712. * COCO Annotation IDs should begin with 1 (#748) Currently the annotation ID begins with 0 which is interpreted by cocoapi as a false detection. The array dtm saves the matches via the ground truth annotation ID. The variable dtm is initialized as an array of zeros. https://github.com/cocodataset/cocoapi/blob/636becdc73d54283b3aac6d4ec363cffbb6f9b20/PythonAPI/pycocotools/cocoeval.py#L269 https://github.com/cocodataset/cocoapi/blob/636becdc73d54283b3aac6d4ec363cffbb6f9b20/PythonAPI/pycocotools/cocoeval.py#L295 https://github.com/cocodataset/cocoapi/blob/636becdc73d54283b3aac6d4ec363cffbb6f9b20/PythonAPI/pycocotools/cocoeval.py#L375 * Slightly enhance command line interface feature (#746) * Slightly enhance command line interface feature. Added README.md, run tests using travis, run CLI tests from VS code. * Removed formatted string due to a limitation on our python version inside the container. * Add information about command line interface to the main page. * Projects (server only, REST API) (#754) * Initial version of projects * Added tests for Projects REST API. * Added information about projects into CHANGELOG * Updating string format for case missed in PR #746. (#757) * add robust JSON handeling for auto annotation runner (#758) * Basic user information (#761) * Fix https://github.com/opencv/cvat/issues/750 * Updated CHANGELOG * Added more tests for /api/v1/users* REST API. * Disable fix_segments_intersections for now (#751) * Disable fix_segments_intersections for now When the bounding boxes had intersections and were exported with the COCO JSON format they were often cut off. I commented out the line with the function fix_segments_intersections and replaced it with lines of that function. This helped with the bounding boxes and keeps the masks as they are created with CVAT. It is probably inconvenient for the user to get something fixed in the export without an active agreement of the user. Secondly letting a function automatically fix segments could result in a bad fix. * Use fix_segments_intersections only with z-order The fix_segments_intersections will only be used when the z-order flag is set. This is useful for bounding boxes or masks which don't need to be fixed. This fix was created according to Andrey Zhavoronkov's (@azhavoro) advice. * Added information about a fixed issue. (#765) * Add more information into questions section (#766) * User interface with react and antd (#755) * Login page, router * Registration * Tasks view * add in serializing check in auto annotation model runner (#770) * allow security segmentation models to be used in auto annotation (#759) * Integration with Zenodo (#779) * Updated CHANGELOG with information about Zenodo * Updated version of the project. * Fixed a case when a task's owner can be undefined. (#782) * Added `restart` tag to docker-compose for `cvat_ui` (#789) * User interface with React and antd (#785) * Dump & refactoring * Upload annotations, cvat-core from sources * Added download icon * Added icon * Update documentation to point to OpenVino component documentation (#752) * Change the version of OpenVINO compatibility (#797) * Change the version of OpenVINO compatibility * added mask RCNN script (#780) * added in yolo auto annotation sciprt (#794) * Annotation formats documentation (#719) * added handling of truncated and difficult attributes for pascal voc loader/dumper added descriptions of supported annotation formats * added YOLO example * made match_frame as Annotations method changed 'image/source_id' field TF feature from int64 to string (according to TF OD API dataset utlis) * updated README improved match_frame function * added unit tests for dump/load * added in semantic segmentation instructions to README (#804) * fix off by one error in mask rcnn (#801) * Fix Yolo: swap width, height; Change box coord order; parsing fix (#802) * Auto segmentation using Mask_RCNN (#767) * Update CHANGELOG.md * Bump pillow from 5.1.0 to 6.2.0 in /cvat/requirements (#808) Bumps [pillow](https://github.com/python-pillow/Pillow) from 5.1.0 to 6.2.0. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst) - [Commits](https://github.com/python-pillow/Pillow/compare/5.1.0...6.2.0) Signed-off-by: dependabot[bot] * Bump pillow from 5.3.0 to 6.2.0 in /utils/cli (#807) Bumps [pillow](https://github.com/python-pillow/Pillow) from 5.3.0 to 6.2.0. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst) - [Commits](https://github.com/python-pillow/Pillow/compare/5.3.0...6.2.0) Signed-off-by: dependabot[bot] * Bump eslint-utils from 1.4.0 to 1.4.3 in /cvat-canvas (#809) Bumps [eslint-utils](https://github.com/mysticatea/eslint-utils) from 1.4.0 to 1.4.3. - [Release notes](https://github.com/mysticatea/eslint-utils/releases) - [Commits](https://github.com/mysticatea/eslint-utils/compare/v1.4.0...v1.4.3) Signed-off-by: dependabot[bot] * fix serialize bug when using AutoAnnotation runner (#810) * User interface with React and antd (#811) * Fixed links for analytics and help * Delete task functionality * Added navigation for create and open task * Added icon for help * Added easy plugin checker * Header dependes on installed plugins * Menu depends on installed plugins * Shared actions menu component, base layout for task page * Task page based (relations with redux, base layout) * Added attribute form * Finished label creator * Added jobs table * Added job assignee * Save updated labels on server * Added imports plugin, updated webpack * Editable bug tracker * Clean task update * Change assignee * Fix login problem (unathorized user cannot login). (#812) * Fix upload anno for COCO (#788) * COCO: load bbox as rectangle if segmentation field is empty * added unit test for coco format (case: object segment field is empty) * Add support for ip git repo urls (#827) * Add support for ip v4 git repo urls * Add tests for git urls * React & Antd UI: Create task (#840) * Separated component user selector * Change job assignee * Basic create task window * Bug fixes and refactoring * Create task connected with a server * Loading status for a button * Reset loading on error response * UI improvements * Github/feedback/share window * added in new interp files for pixel link v0004 (#852) * Add LabelMe format support (#844) * Add labelme export * Add LabelMe import * Add labelme format to readme * Updated CHANGELOG.md * Adding dump and load support for MOT CSV format. (#830) * Adding dump and load support for MOT CSV format. * Updated test cases to use correct track annotations for MOT format. * Removed behaviour of MOT loader which would duplicate the last track shape prior to setting outside=True. * Add dataset export facility (#813) * Add datumaro django application * Add cvat task datumaro bindings * Add REST api for task export * Add scheduler service * Updated CHANGELOG.md * Mit license for pixellink and changelog (#862) * React & Antd UI: Model manager (#856) * Supported git to create and sync * Updated antd * Updated icons * Improved header * Top bar for models & empty models list * Removed one extra reducer and actions * Removed one extra reducer and actions * Crossplatform css * Models reducers, some models actions, base for model list, imrovements * Models list, ability to delete models * Added ability to upload models * Improved form, reinit models after create * Removed some importants in css * Model running dialog window, a lot of fixes * Add a dataset export button for tasks (#834) * Add dataset export button for tasks in dashboard * Fix downloading, shrink list of export formats * Add strict export format check * Add strict export format check * Change REST api paths * Move formats declarations to server, * Coco converter updates (#864) * [Datumaro] Fix coco images export (#875) * Update test * Fix export * Support several image paths in coco extractor * [Datumaro] Disable lazy image caching by default (#876) * Disable lazy image caching by default * Deterministic cache test * Add displacing image cache * React & Antd UI: Export dataset, refactoring & fixes (#872) * Automatic label matching (by the same name) in model running window * Improved create task window * Improved upload model window * Fixed: error window showed twice * Updated CONTRIBUTING.md * Removed token before login, fixed dump submenu (adjustment), fixed case when empty models list displayed * Export as dataset, better error showing system * Removed extra requests, improved UI * Fixed a name of a format * Show inference progress * Fixed model loading after a model was uploaded * Fix redirect (#878) * Add cvat cli to datumaro project export (#870) * Configurable REST for UI, minor improvements (#880) * [Datumaro] Pip installation (#881) * Add version file * Remove unnecessary dependencies * Add lxml use motivation * Add pip setup script * Reduce opencv dependency * Fix cli command * Codacy * page_size parameter for all REST API methods (#884) * Added page_size parameter for all REST API methods which returns list of objects. Also it is possible to specify page_size=all to return all elements. * Updated changelog.md * VOC converter: Use depth from CVAT XML if available (#885) * Token auth for non-REST API apps (#889) * Token authorization for non REST API apps (e.g. git, tf annotation, tf segmentation) * set CORS_REPLACE_HTTPS_REFERER option to True (#895) * Fix some spelling (#897) * React & Antd: Dashboard migration (#892) * Removed old dashboard * Getting all users * Updated changelog * Reimplemented login decorator * Implicit host, scheme in docker-compose * Fixed issue with pagination * Implicit page size parameter for tasks * Fixed linkedin icon, added links to tasks in notifications * Configurable method for check plugin * Bump django from 2.2.4 to 2.2.8 in /cvat/requirements (#902) Bumps [django](https://github.com/django/django) from 2.2.4 to 2.2.8. - [Release notes](https://github.com/django/django/releases) - [Commits](https://github.com/django/django/compare/2.2.4...2.2.8) Signed-off-by: dependabot[bot] * Az/fix meta requests (#903) * fixed processing of meta requests * Fixed some issues with dump (#904) * Changed method for downloading annotations * Initial commit * Initial commit * Updated download method for dataset * fixed eslint error * Restore session id (#905) * Restore session id when we use token authorization. * UI eslint fixes (#908) * Installed airbnb fullsettings * Fixed actions menu * Create model/task page * File manager, header * Labels editor * Login, register * Models page & model runner * Tasks page * Feedback and base app * Tasks page * Containers * Reducers * Fixed additional issues * Small pagination fix * implemented adas semantic segmentation * Copy JOB info to clibpard * Yolov3 interpretration script fix for 'Annotation failed' and changes to mapping.json (#896) (#912) * [Datumaro] Add YOLO converter (#906) * Add YOLO converter * Added yolo extractor * Added YOLO format test * Add YOLO export in UI * Added padding * Remove deprecated html attributes (#924) * Updated message * Improved some hints * Added 3rdparty library to clipboard * Updated doc * Added ability to copy labels without IDs * Removed extra lines * Updated contributing * Updated contributing * Task name displayed better * Improved tasks routing * Ability to show hidden task * Destroy messages before getting new tasks * Fixed eslint * Names of selected files when creating a new task * [Datumaro] Added tf detection api tfrecord import and export (#894) * Added tf detection api tfrecord import and export * Added export button in dashboard * Add tf to requirements * Extend test * Add tf dependency * Require images in tfrecord export * Add video task case handling * Maintain image order in CVAT export * Fix Task image id-path conversions * Update tfrecord tests * Extend image utilities * Update tfrecord format * Fix image loading bug * Add some logs * Add 'copy' option to project import command * Reduce default cache size * Improve UX with creating new shape by shortkey (#941) * Fixed command in CONTRIBUTING.md (#947) * Fixed command in CONTRIBUTING.md * Removed daemon, updated command * [Datumaro] COCO 'merge instance polygons' option (#938) * Add polygon merging option to coco converter * Add test, refactor coco, add support for cli args * Drop colormap application in datumaro format * Add cli support in voc converter * Add cli support in yolo converter * Add converter cli options in project cli * Add image data type conversion in image saving * [Datumaro] Fix voc colormap (#945) * Add polygon merging option to coco converter * Add test, refactor coco, add support for cli args * Drop colormap application in datumaro format * Add cli support in voc converter * Add cli support in yolo converter * Add converter cli options in project cli * Add image data type conversion in image saving * Add image data type conversion in image saving * Update mask support in voc * Replace null with quotes in coco export * Improve cli * Enable Datumaro intellisense in vs cde * Adjust fields in voc detection export * Return pylint to config (#951) * Update docker base images (#950) Don't fix minor/patch version to get security updates and bug fixes. * Fixed git plugin (#961) * Add upload annotation function to cli (#958) * add upload annotation function to cli * Update core.py Removing whitespace * React, Antd, Redux: Left sidebar and top for annotation page (#963) * Rebased from develop * Improved getting icons method * Added more icons * Left menu * Initial commit * Setup SVGO, added some buttons to top * Top bar progress * Top bar for annotation page * Updated styles * added in label visualization to auto annotation runner (#931) * Bump tensorflow from 1.13.1 to 1.15.0 in /utils/tfrecords (#967) Bumps [tensorflow](https://github.com/tensorflow/tensorflow) from 1.13.1 to 1.15.0. - [Release notes](https://github.com/tensorflow/tensorflow/releases) - [Changelog](https://github.com/tensorflow/tensorflow/blob/master/RELEASE.md) - [Commits](https://github.com/tensorflow/tensorflow/compare/v1.13.1...v1.15.0) Signed-off-by: dependabot[bot] * Fixed number attribute (#972) * CSS Enhancement (#971) * Removed vendor/specific rules * Sass for CVAT, less for Antd, added autoprefixer and css polyfills * Removed extra line * Changed update state * [Datumaro] VOC labelmap support (#957) * Add import result checks and options to skip * Add label-specific attributes * Overwrite option for export * Add labelmap file support in voc * Add labelmap tests * Little refactoring * Bump tensorflow from 1.12.3 to 1.15.0 in /cvat/requirements (#968) * Bump tensorflow from 1.12.3 to 1.15.0 in /cvat/requirements Bumps [tensorflow](https://github.com/tensorflow/tensorflow) from 1.12.3 to 1.15.0. - [Release notes](https://github.com/tensorflow/tensorflow/releases) - [Changelog](https://github.com/tensorflow/tensorflow/blob/master/RELEASE.md) - [Commits](https://github.com/tensorflow/tensorflow/compare/v1.12.3...v1.15.0) Signed-off-by: dependabot[bot] * Update pip because tensorflow 1.15 cannot not be found. * Fix a typo (pip -> pip3) * Replaced pip3 by python3 -m pip. * Change-submit-button-style (#976) * UI/UX improvement. Changed buttons type for create task / upload model * Added documentation for swagger page (#936) * Styles refactoring (#977) * Add polygon point count checks (#975) * User Guide update (#953) * Swagger documentation (#978) * Fix swagger problems (exceptions, /api/swagger.json, /api/docs/) * [Datumaro] CVAT format import (#974) * Add label-specific attributes * Add CVAT format import * Register CVAT format * Add little more logs * Little refactoring for tests * Cvat format checks * Add missing check * Refactor datumaro format * Little refactoring * Regularize dataset importer logic * Fix project import issue * Refactor coco extractor * Refactor tests * Codacy * Fix label for mask rcnn (#980) * UI Enhancements (#985) * Single import of basic styles * A little bit redesigned header * Specified min resolution 1280x768 * Getting a job instance * Improved handling when task doesn't exist * Adding dump for VOC instance mask. (#859) * Add mask instance dumper * Fix bug * Merge mask instance into mask * Merge the change into mask * Create MaskColorizer * Add dump method * Updating the Model Manager section of the CVAT User Guide (#991) * Added Code Climate, CodeBeat badges. (#995) * [Datumaro] Fix TFrecord converter constructor (#993) * Resolved performance bottleneck in merge function (#999) * Fixed issue: Unknown shape type found (#998) * Automatic bordering feature during drawing/editing (#997) * Change Modal submit button okType (#1001) * Fixed comparison of shapes (#1000) * Add test code for cli upload function (#986) * pass in model name and task id to run auto annotation script (#934) * fix dockerfile for PDF (#939) * Updating the Auto Annotation section of the CVAT User Guide (#996) * Updating the Task synchronization with a repository section of the CVAT User Guide (#1006) * Fix timezone bug (#1010) * [Datumaro] Fix project loading (#1013) * Fix occasional infinite loop in project loading * Fix project import source options saving * Fix project import .git dir placement * Make code aware of grayscale images * Added root folder for share functionality (#1005) * Improved feature: common borders (#1016) * Auto borders -> common borders, invisible when do not edit or draw, don't reset state * Reset sticker after clicking outside * Update AWS-Deployment-Guide.md (#1019) Fixed documentation typo for file extension * Correct link to #automatic-annotation in README (#1029) * AWS deployment guide updated #1009 (#1031) * Add info about auto segmentation to advanced topics of the installation guide (#1033) * correct path to eula.cfg (#1037) * Update README.md (#1040) * Removed patool package with GPL license (it is not used) (#1045) * Removed VIM package (it isn't necessary) (#1046) * Trim possible attribute values like attribute values setup by a user (#1044) * React UI: Player in annotation view & settings page (#1018) * Active player controls * Setup packages * Playing * Fold/unfold sidebar, minor issues * Improved cvat-canvas integration * Resolved some issues * Added cvat-canvas to Dockerfile.ui * Fit canvas method * Added annotation reducer * Added annotation actions * Added containers * Added components * cvat-canvas removed from dockerignore * Added settings page * Minor improvements * Container for canvas wrapper * Configurable grid * Rotation * fitCanvas added to readme * Aligned table * Changed CharField(64) -> CharField(4096) for attribute value (#1048) * [Datumaro] Add cvat format export (#1034) * Add cvat format export * Remove wrong items in test * [Datumaro] Instance polygon-mask conversions in COCO format (#1008) * Microoptimizations * Mask conversion functions * Add mask-polygon conversions * Add mask-polygon conversions in coco * Add mask-polygon conversions in coco * Update requirements * Option to disable crop * Fix cli parameter passing * Fix test * Fixes in COCO * [Datumaro] Dataset annotations filter (#1053) * Fix deprecation message * Update launcher interface * Add dataset entity, anno filter, remove filter from project config, update transform * Update project and source cli * Fix help message * Refactor tests * Added ability to match many model labels to one task labels (#1051) * Added ability to match many model labels to one task labels * Fixed grammar * React UI: Player updates (#1058) * Move, zoom integration * Moving, zooming, additional canvas handler * Activating & changing for objects * Improved colors * Saving annotations on the server * Fixed size * Refactoring * Added couple of notifications * Basic shape drawing * Cancel previous drawing * Refactoring * Minor draw improvings * Merge, group, split * Improved colors * Fixed: Uncaught TypeError: Cannot read property 'nodeValue' of undefined (#1068) * Add about CVAT (#1024) * Fix typos in xml_format.md (#1069) typo fixes * Update CONTRIBUTING.md (#1072) * align serializer max length of attribute value with the model (#1074) * Cleanup Dockerfiles for CVAT (#1060) * Replaced wget by curl * Moved CI stuff into Dockerfile.ci * Use docker-compose to run commnands inside docker (need environment variables) * Added patool again (to support different archive formats) * Roll back tensorflow version: 1.15 -> 1.13.1 Fixed https://github.com/opencv/cvat/issues/982 Fixed https://github.com/opencv/cvat/issues/1017 * datumaro install tensorflow 2.x now. It breaks automatic annotation using TF. * Follow redirects in curl (auto_segmentation) * Update method call (#1085) * React UI: Sidebar with objects and optimizations for annotation view (#1089) * Basic layout for objects panel * Objects header * A little name refactoring * Side panel base layout * Firefox specific exceptions * Some minor fixes * React & canvas optimizations * Icons refactoring * Little style refactoring * Some style fixes * Improved side panel with objects * Actual attribute values * Actual icons * Hidden > visible * hidden -> __internal * Fixed hidden in ui * Fixed some issues in canvas * Fixed list height * Color picker for labels * A bit fixed design * Actual header icons * Changing attributes and switchable buttons * Removed react memo (will reoptimize better) * Sorting methods, removed cache from cvat-core (a lot of bugs related with it) * Label switchers * Fixed bug with update timestamp for shapes * Annotation state refactoring * Removed old resetCache calls * Optimized top & left panels. Number of renders significantly decreased * Optimized some extra renders * Accelerated performance * Fixed two minor issues * Canvas improvements * Minor fixes * Removed extra code * resolving import error caused by pip 20.0 (#1094) * [Datumaro] CLI updates + better documentation (#1057) * Optimize mask conversions (#1097) * Update base.py (#1099) Modification necessary for using CVAT from remote machines when accessing with FQDNs See https://github.com/opencv/cvat/issues/1011#issue-542817055 and https://github.com/opencv/cvat/pull/1098 "I believe the reason for this is that sometimes if the port number is :80 and the URL is not in the LAN (:port), but instead it is a Fully Qualified Domain Name (:port), the port 80 is redundant (mydomain.com:80) and the errors arise." * fixed dump of interpolation points object && statistics calculation (#1108) * Add extreme clicking feature to draw box by 4 points (#1111) * Add extreme clicking feature to draw box by 4 points * Add documentation for extreme clicking * React UI: Annotation view enhancements (#1106) * Keyframes navigation * Synchronized objects on canvas and in side panel * Fixed minor bug with collapse * Fixed css property 'pointer-events' * Drawn appearance block * Removed extra force reflow * Finished appearance block, fixed couple bugs * Improved save() in cvat-core, changed approach to highlight shapes * Fixed exception in edit function, fixed filling for polylines and points, fixed wrong image navigation, remove and copy * Added lock * Some fixes with points * Minor appearance fixes * Fixed insert for points * Fixed unit tests * Fixed control * Fixed list size * Added propagate * Minor fix with attr saving * Some div changed to buttons * Locked some buttons for unimplemented functionalities * Statistics modal, changing a job status * Minor fix with shapes counting * Couple of fixes to improve visibility * Added fullscreen * SVG Canvas -> HTML Canvas frame (#1113) * SVG Frame -> HTML Canvas frame * React UI: Added annotation menus, added shape context menu, added some confirmations before dangerous actions (#1123) * Annotation menu, modified tasks menu * Removed extra styles * Context menu using side panel * Mousewheel on draw * Added more cursor icons * Do not check .svg & .scss by eslint * [Datumaro] Plugins and transforms (#1126) * Fix model run command * Rename annotation types, update class interfaces * Fix random cvat format test fails * Mask operations and dataset format fixes * Update tests, extract format testing functions * Add transform interface * Implement plugin system * Update tests with plugins * Fix logging * Add transfroms * Update cvat integration * Fix tensorflow installation (#1129) * Make tf dependency optional * Reduce opencv dependency * Import tf eagerly as it is a plugin * Do not install TF with Datumaro * Add plugin system documentation (#1131) * React UI: Improved mouse behaviour during draw/merge/edit/group/split (#1130) * Moving image with mouse during drawing, paste, group, split, merge * Babel plugin to dev deps * Move mouse during editing * Minor issues * [Datumaro] fixes (#1137) * Fix import command * Fix project name for spawned projects * Fix voc and coco converter parameters * Fix voc colormap color interpretation * Change order of image search for cvat extractor * fix CVAT image search paths * Bump django from 2.2.8 to 2.2.10 in /cvat/requirements (#1139) Bumps [django](https://github.com/django/django) from 2.2.8 to 2.2.10. - [Release notes](https://github.com/django/django/releases) - [Commits](https://github.com/django/django/compare/2.2.8...2.2.10) Signed-off-by: dependabot[bot] * Add extreme clicking method in cvat-canvas and cvat-ui (#1127) * Add extreme clicking method in cvat-canvas and cvat-ui * Fix bugs and issues, update readme * Fix error after rebasing develop * updated CUDA to version 10 (#1138) * updated CUDA to version 10 * updated tensorflow * added comment about NVIDIA_REQUIRE_CUDA env varOF * React UI: Undo/redo (#1135) * Typed reducers (#1136) * Added typed actions/reducers * Added commands to check types / eslint issues * Added redux dev tools * Bump gitpython version (#1146) * Fix postgres startup. * React UI: Objects filtering & search (#1155) * Initial filter function * Updated method for filtering * Updated documentation * Added annotations filter file * Updated some comments * Added filter to UI * Implemented search alorithm * Removed extra code * Fixed typos * Added frame URL * Object URL * Removed extra encoding/decoding * Fixed dump for cases when special URL characters in task name (#1162) * Add offline subset remapping and bbox conversion (#1147) * Avoid tf deprecation warning (#1148) * [Datumaro] Pretty output folder names (#1149) * Generate output dir name from operation parameters * Fix failing command * Update changelog (#1165) * [Datumaro] Introduce image info (#1140) * Employ transforms and item wrapper * Add image class and tests * Add image info support to formats * Fix cli * Fix merge and voc converte * Update remote images extractor * Codacy * Remove item name, require path in Image * Merge images of dataset items * Update tests * Add image dir converter * Update Datumaro format * Update COCO format with image info * Update CVAT format with image info * Update TFrecord format with image info * Update VOC formar with image info * Update YOLO format with image info * Update dataset manager bindings with image info * Add image name to id transform * Fix coco export * More types in actions and reducers (#1166) * [Datumaro] Add masks to tfrecord format (#1156) * Employ transforms and item wrapper * Add image class and tests * Add image info support to formats * Fix cli * Fix merge and voc converte * Update remote images extractor * Codacy * Remove item name, require path in Image * Merge images of dataset items * Update tests * Add image dir converter * Update Datumaro format * Update COCO format with image info * Update CVAT format with image info * Update TFrecord format with image info * Update VOC formar with image info * Update YOLO format with image info * Update dataset manager bindings with image info * Add image name to id transform * Fix coco export * Add masks support for tfrecord * Refactor coco * Fix comparison * Remove dead code * Extract common code for instances * Replace YOLO format support in CVAT with Datumaro (#1151) * Employ transforms and item wrapper * Add image class and tests * Add image info support to formats * Fix cli * Fix merge and voc converte * Update remote images extractor * Codacy * Remove item name, require path in Image * Merge images of dataset items * Update tests * Add image dir converter * Update Datumaro format * Update COCO format with image info * Update CVAT format with image info * Update TFrecord format with image info * Update VOC formar with image info * Update YOLO format with image info * Update dataset manager bindings with image info * Add image name to id transform * Replace YOLO export and import in CVAT with Datumaro * Add editorconfig (#1142) * Add editorconfig * Update indent value * Cuboid annotation (#678) * Cuboid feature * migration files * Refactored cuboidShape Fixed a bug where coloring by label would not update cuboids properly Fixed a bug where the select points would not scale properly on initialization * Removed math.js dependency Implemented custom line intersection function * new cvat formatting with labelled points * Added MIT License to js files that were missing it * Added simple constraints to the cuboids * reverted commit for settings for vscode to hide local path * fixed locking for cuboids * fixed cuboid View when locked * fixed occlusion view for cuboids * Allow cuboid points to be outside the frame dimensions. Signed-off-by: Tritin Truong * Added stricter constraints on cuboid edges. * Slightly stricter restrictions for edge case * Cleaned up unused imports * removed dashed lines on cuboids * Moved projection lines to settings tab * Fixed Cuboid shape buffer \ * Fix migrations (two 022 migrations after merge with the develop branch). * Fix compatibility issues with auto segmentation. * Grab points and update control scheme * Greatly improved control scheme, fixed shape merging Fixed Cuboid upload * Fixed slight visual bug when dragging faces * Some optimizations * Hiding the grab point on creation Small refactoring * Fixed some cases where cuboid breaks * Fixed upload for videos * Removed perspective effects * Made left back edge editable * left back edge resizable * fix statistics bug * added toggles for the back edges * Constraints for the back edges * Fix creation bug * Tightened creation constraints * Fixing the code style * updated message for invalid cuboids * Code style * More style fixes * Codacy fixes * added shift control for edges * More Codacy fixes * More Codacy fixes * Double arrows for cursor * Fix Drag bug * More Codacy fixes * Fix double quotes * Fix camel case * More camelcase fixes * Generic object sink fixes * Various codacy fixes * Codacy * Double quotes * Fix migrations * Updated shape creation Fix jittering * Adjusted constraints * Codacy fixes * Codacy fixes again * Drawing cuboids from the top and bottom * Codacy * Resetting perspective on cuboids * Choosing orientation of cuboids. * Codacy fix * Merge cleanup * revert vs-code settings * Update settings.json Co-authored-by: timbowl <54648082+timbowl@users.noreply.github.com> Co-authored-by: Nikita Manovich <40690625+nmanovic@users.noreply.github.com> * Update yolo format description (#1173) * Replace tfrecord format support in CVAT with Datumaro (#1157) * Replace mask format support with Datumaro (#1163) * Add box to mask transform * Fix 'source' labelmap mode in voc converter * Import groups * Replace mask format support * Update mask format documentation * codacy * Fix tests * Fix dataset * Fix segments grouping * Merge instances in mask export * Update Onepanel demo information and link (#1189) * Added displayed versions of core, canvas, and ui in about (#1191) * Added displayed versions of core, canvas, and ui in about * Removed extra method * React UI: ZOrder implementation (#1176) * Drawn z-order switcher * Z layer was added to state * Added ZLayer API method cvat-canvas * Added sorting by Z * Displaying points in top * Removed old code * Improved sort function * Drawn a couple of icons * Send to foreground / send to background * Updated unit tests * Added unit tests for filter parser * Removed extra code * Updated README.md * Replace VOC format support in CVAT with Datumaro (#1167) * Add image meta reading to voc * Replace voc support in cvat * Bump format version * Materialize lazy transforms in voc export * Store voc instance id as group id * Add flat format import * Add documentation * Fix format name in doc * [Datumaro] Remote project export fixes (#1193) * Export project with trask name * Do not expose server paths * Fix tfrecord mask reading in tf>1.14 * Setuptools compatibility * Replace COCO implementation (#1195) * Fixed lags (#1197) * React UI: Changing color for a shape (#1194) * Minimized size of an element in side panel * To background / to foreground like in legacy UI * Added color changer for a shape * Adjusted color updating * React-UI: settings (#1164) * Image filters: brightness, contrast, saturation * Auto saving * Frame auto fit * Player speed * Leave confirmation for unsaved changes * React UI: Changing color for a group (#1205) * Added license headers (#1208) * Added licenser * Added license headers for cvat-canvas and cvat-ui * Move project dir to .datumaro (#1207) * Updated svg.js version (#1212) * React UI: Batch of fixes (#1211) * Disabled tracks for polyshapes in UI * RectDrawingMethod enum pushed to cvat-canvas, fixed some code issues * Optional arguments * Draw a text for locked shapes, some fixes with not keyframe shapes * Fixed zooming & batch grouping * Reset zoom for tasks with images * Fixed putting shapes out of canvas * Fixed grid opacity, little refactoring of componentDidUpdate in canvas-wrapper component * Fixed corner cases for drawing * Fixed putting shapes out of canvas * Improved drawing * Removed extra event handler * Auto-generate labelmap for voc from task (#1214) * Add random split transform (#1213) * React UI: Improved rotation feature (#1206) Co-authored-by: Boris Sekachev <40690378+bsekachev@users.noreply.github.com> * Az/cvat proxy (#1177) * added nginx proxy * removed unnecessary port configuration & build arg * updated installation guide * Add tags to cvat xml (#1200) * Extend cvat format test * Add tags to cvat for images * Add tags to cvat format in dm * Add import of tags from datumaro * React UI: Pinned option was added (#1202) * Fix remainder logic for subset splitting (#1222) * Add tags support for VOC (#1201) * Extend voc format test with tags * Add import and export of voc labels * Fix voc and yolo format version numbers * React UI: batch of fixes (#1227) * Fix: keyframes navigation * Fix: handled removing of the latest keyframe * Fix: activating a shape when another shape is being changed * Fix: up points in the side bar on points click * Fix: editable shape isn't transformed when change zoom * Updated message * React UI: Filters history (#1225) * Added filters history * Fixed unclosed dropdown * Added saving filters to localStrorage * Added button to cancel started automatic annotation (#1198) * [WIP] Cuboid feature user guide (#1218) * Initial cuboid description * Added Gifs * Added gifs to descriptions * Formatting fixes * Codacy Fixes * Az/fix annotation dump upload (#1229) * fixed upload annotation in case of frame step != 1 * fixed upload annotation in case of attribute value is empty * React UI: Added shortcuts (#1230) * [Datumaro] Label remapping transform (#1233) * Add label remapping transform * Apply transforms before project saving * Refactor voc converter * [Datumaro] Optimize mask operations (#1232) * Optimize mask to rle * Optimize mask operations * Fix dm format cmdline * Use RLE masks in datumaro format * Fixed date in CHANGELOG.md * sort frame shapes by z_order (#1258) Co-authored-by: vfdev Co-authored-by: Boris Sekachev <40690378+bsekachev@users.noreply.github.com> Co-authored-by: Ben Hoff Co-authored-by: Boris Sekachev Co-authored-by: Ben Hoff Co-authored-by: telenachos <54951461+telenachos@users.noreply.github.com> Co-authored-by: Johannes222 Co-authored-by: RS Nikhil Krishna Co-authored-by: Andrey Zhavoronkov <41117609+azhavoro@users.noreply.github.com> Co-authored-by: Reza Malek Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zhiltsov-max Co-authored-by: a-andre Co-authored-by: Maksim Markelov Co-authored-by: himalayanZephyr <42401082+himalayanZephyr@users.noreply.github.com> Co-authored-by: Seungwon Jeong Co-authored-by: Maya <49038720+Marishka17@users.noreply.github.com> Co-authored-by: TOsmanov <54434686+TOsmanov@users.noreply.github.com> Co-authored-by: vugia truong Co-authored-by: roho Co-authored-by: Christian Co-authored-by: provider161 Co-authored-by: Radhika <43014570+radhika1601@users.noreply.github.com> Co-authored-by: Tanvi Anand Co-authored-by: Lisa <38404726+LiSa20120@users.noreply.github.com> Co-authored-by: Josh Bradley Co-authored-by: Priya4607 <59498234+Priya4607@users.noreply.github.com> Co-authored-by: LukeAI <43993778+LukeAI@users.noreply.github.com> Co-authored-by: Jijoong Kim Co-authored-by: Nikita Glazov Co-authored-by: Tritin Truong Co-authored-by: timbowl <54648082+timbowl@users.noreply.github.com> Co-authored-by: Rush Tehrani Co-authored-by: Dmitry Kalinin Co-authored-by: Tritin Truong --- .codacy.yml | 1 + .dockerignore | 3 +- .editorconfig | 17 + .gitignore | 2 +- .travis.yml | 9 +- .vscode/launch.json | 20 +- .vscode/python.env | 1 + .vscode/settings.json | 18 +- CHANGELOG.md | 33 + CONTRIBUTING.md | 119 +- Dockerfile | 76 +- Dockerfile.ci | 32 + Dockerfile.ui | 43 + README.md | 43 +- components/auto_segmentation/README.md | 38 + .../docker-compose.auto_segmentation.yml | 13 + components/auto_segmentation/install.sh | 13 + components/cuda/docker-compose.cuda.yml | 7 +- components/cuda/install.sh | 27 +- components/openvino/README.md | 28 +- components/tf_annotation/install.sh | 2 +- cvat-canvas/README.md | 105 +- cvat-canvas/package-lock.json | 10395 +++++++++++++ cvat-canvas/package.json | 6 +- cvat-canvas/postcss.config.js | 13 + .../src/{css/canvas.css => scss/canvas.scss} | 74 +- cvat-canvas/src/typescript/canvas.ts | 59 +- .../src/typescript/canvasController.ts | 12 +- cvat-canvas/src/typescript/canvasModel.ts | 187 +- cvat-canvas/src/typescript/canvasView.ts | 1014 +- cvat-canvas/src/typescript/consts.ts | 11 +- cvat-canvas/src/typescript/drawHandler.ts | 480 +- cvat-canvas/src/typescript/editHandler.ts | 132 +- cvat-canvas/src/typescript/groupHandler.ts | 183 +- cvat-canvas/src/typescript/master.ts | 7 +- cvat-canvas/src/typescript/mergeHandler.ts | 43 +- cvat-canvas/src/typescript/shared.ts | 31 +- cvat-canvas/src/typescript/splitHandler.ts | 15 +- cvat-canvas/src/typescript/svg.patch.ts | 12 +- cvat-canvas/src/typescript/zoomHandler.ts | 145 + cvat-canvas/tsconfig.json | 2 + cvat-canvas/webpack.config.js | 19 +- cvat-core/.dockerignore | 5 + cvat-core/.eslintrc.js | 1 + cvat-core/package.json | 4 +- cvat-core/src/annotation-format.js | 4 +- cvat-core/src/annotations-collection.js | 275 +- cvat-core/src/annotations-filter.js | 240 + cvat-core/src/annotations-history.js | 71 + cvat-core/src/annotations-objects.js | 1034 +- cvat-core/src/annotations-saver.js | 129 +- cvat-core/src/annotations.js | 111 +- cvat-core/src/api-implementation.js | 51 +- cvat-core/src/api.js | 38 +- cvat-core/src/common.js | 4 +- cvat-core/src/enums.js | 76 +- cvat-core/src/frames.js | 42 +- cvat-core/src/labels.js | 28 +- cvat-core/src/object-state.js | 183 +- cvat-core/src/plugins.js | 4 +- cvat-core/src/server-proxy.js | 186 +- cvat-core/src/session.js | 365 +- cvat-core/tests/api/annotations.js | 97 +- cvat-core/tests/api/frames.js | 14 + cvat-core/tests/api/object-state.js | 42 - cvat-core/tests/api/tasks.js | 2 +- cvat-core/tests/internal/filter.js | 124 + cvat-core/tests/mocks/server-proxy.mock.js | 5 + cvat-core/webpack.config.js | 17 +- cvat-ui/.dockerignore | 3 +- cvat-ui/.env | 11 +- cvat-ui/.env.production | 9 - cvat-ui/.eslintrc.js | 49 + cvat-ui/.gitignore | 20 +- cvat-ui/Dockerfile | 36 - cvat-ui/README.md | 44 - cvat-ui/config-overrides.js | 14 - cvat-ui/dist/favicon.ico | Bin 0 -> 102581 bytes cvat-ui/index.d.ts | 5 + cvat-ui/package-lock.json | 12305 +++++++--------- cvat-ui/package.json | 112 +- cvat-ui/postcss.config.js | 13 + cvat-ui/public/cvat-core.min.js | 15 - cvat-ui/public/cvat-core.min.js.map | 1 - cvat-ui/public/favicon.ico | Bin 3870 -> 0 bytes cvat-ui/public/images/cvat-logo.svg | 1 - cvat-ui/public/images/empty-tasks-icon.svg | 1 - cvat-ui/public/index.html | 40 - cvat-ui/public/manifest.json | 15 - cvat-ui/src/actions/about-actions.ts | 35 + cvat-ui/src/actions/annotation-actions.ts | 1220 ++ cvat-ui/src/actions/annotations.actions.ts | 75 - cvat-ui/src/actions/auth-actions.ts | 99 + cvat-ui/src/actions/auth.actions.ts | 172 - cvat-ui/src/actions/formats-actions.ts | 48 + cvat-ui/src/actions/models-actions.ts | 547 + cvat-ui/src/actions/notification-actions.ts | 28 + cvat-ui/src/actions/plugins-actions.ts | 55 + cvat-ui/src/actions/server.actions.ts | 109 - cvat-ui/src/actions/settings-actions.ts | 202 + cvat-ui/src/actions/share-actions.ts | 49 + cvat-ui/src/actions/shortcuts-actions.ts | 11 + cvat-ui/src/actions/tasks-actions.ts | 537 + cvat-ui/src/actions/tasks-filter.actions.ts | 9 - cvat-ui/src/actions/tasks.actions.ts | 175 - cvat-ui/src/actions/users-actions.ts | 37 + cvat-ui/src/actions/users.actions.ts | 40 - cvat-ui/src/assets/account-icon.svg | 1 + cvat-ui/src/assets/back-jump-icon.svg | 5 + cvat-ui/src/assets/background-icon.svg | 11 + cvat-ui/src/assets/cursor-icon.svg | 1 + cvat-ui/src/assets/cvat-logo.svg | 1 + cvat-ui/src/assets/empty-tasks-icon.svg | 11 + cvat-ui/src/assets/exit-fullscreen-icon.svg | 1 + cvat-ui/src/assets/first-icon.svg | 5 + cvat-ui/src/assets/fit-to-window-icon.svg | 1 + cvat-ui/src/assets/foreground-icon.svg | 11 + cvat-ui/src/assets/forward-jump-icon.svg | 5 + cvat-ui/src/assets/fullscreen-icon.svg | 1 + cvat-ui/src/assets/group-icon.svg | 1 + cvat-ui/src/assets/info-icon.svg | 1 + cvat-ui/src/assets/last-icon.svg | 5 + cvat-ui/src/assets/main-menu-icon.svg | 1 + cvat-ui/src/assets/menu-icon.svg | 1 + cvat-ui/src/assets/merge-icon.svg | 1 + cvat-ui/src/assets/move-icon.svg | 1 + cvat-ui/src/assets/next-icon.svg | 5 + cvat-ui/src/assets/object-filter-icon.svg | 1 + cvat-ui/src/assets/object-hide-icon.svg | 1 + cvat-ui/src/assets/object-inside-icon.svg | 7 + cvat-ui/src/assets/object-occlude-icon.svg | 1 + cvat-ui/src/assets/object-outside-icon.svg | 8 + cvat-ui/src/assets/pause-icon.svg | 8 + cvat-ui/src/assets/play-icon.svg | 5 + cvat-ui/src/assets/plus-icon.svg | 1 + cvat-ui/src/assets/point-icon.svg | 1 + cvat-ui/src/assets/polygon-icon.svg | 1 + cvat-ui/src/assets/polyline-icon.svg | 1 + cvat-ui/src/assets/previous-icon.svg | 5 + cvat-ui/src/assets/rectangle-icon.svg | 1 + cvat-ui/src/assets/redo-icon.svg | 1 + cvat-ui/src/assets/rotate-icon.svg | 1 + cvat-ui/src/assets/save-icon.svg | 1 + cvat-ui/src/assets/settings-icon.svg | 1 + cvat-ui/src/assets/show-sidebar-icon.svg | 1 + cvat-ui/src/assets/side-icon-object-lock.svg | 1 + cvat-ui/src/assets/split-icon.svg | 1 + cvat-ui/src/assets/tag-icon.svg | 1 + cvat-ui/src/assets/undo-icon.svg | 1 + cvat-ui/src/assets/zoom-icon.svg | 1 + cvat-ui/src/base.scss | 27 + .../components/actions-menu/actions-menu.tsx | 162 + .../components/actions-menu/dump-submenu.tsx | 55 + .../actions-menu/export-submenu.tsx | 47 + .../components/actions-menu/load-submenu.tsx | 65 + .../src/components/actions-menu/styles.scss | 50 + .../annotation-page/annotation-page.tsx | 59 + .../canvas-context-menu.tsx | 35 + .../standard-workspace/canvas-wrapper.tsx | 715 + .../controls-side-bar/controls-side-bar.tsx | 247 + .../controls-side-bar/cursor-control.tsx | 51 + .../controls-side-bar/draw-points-control.tsx | 58 + .../draw-polygon-control.tsx | 58 + .../draw-polyline-control.tsx | 58 + .../draw-rectangle-control.tsx | 58 + .../controls-side-bar/draw-shape-popover.tsx | 151 + .../controls-side-bar/fit-control.tsx | 36 + .../controls-side-bar/group-control.tsx | 55 + .../controls-side-bar/merge-control.tsx | 55 + .../controls-side-bar/move-control.tsx | 54 + .../controls-side-bar/resize-control.tsx | 54 + .../controls-side-bar/rotate-control.tsx | 59 + .../controls-side-bar/split-control.tsx | 55 + .../objects-side-bar/appearance-block.tsx | 94 + .../objects-side-bar/color-changer.tsx | 60 + .../objects-side-bar/label-item.tsx | 86 + .../objects-side-bar/labels-list.tsx | 29 + .../objects-side-bar/object-item.tsx | 780 + .../objects-side-bar/objects-list-header.tsx | 143 + .../objects-side-bar/objects-list.tsx | 79 + .../objects-side-bar/objects-side-bar.tsx | 114 + .../objects-side-bar/styles.scss | 281 + .../standard-workspace/propagate-confirm.tsx | 59 + .../standard-workspace/standard-workspace.tsx | 28 + .../standard-workspace/styles.scss | 176 + .../components/annotation-page/styles.scss | 215 + .../top-bar/annotation-menu.tsx | 129 + .../annotation-page/top-bar/left-group.tsx | 107 + .../top-bar/player-buttons.tsx | 90 + .../top-bar/player-navigation.tsx | 81 + .../annotation-page/top-bar/right-group.tsx | 58 + .../top-bar/statistics-modal.tsx | 207 + .../annotation-page/top-bar/top-bar.tsx | 113 + cvat-ui/src/components/app/app.scss | 3 - cvat-ui/src/components/app/app.test.tsx | 11 - cvat-ui/src/components/app/app.tsx | 65 - .../create-model-content.tsx | 164 + .../create-model-page/create-model-form.tsx | 86 + .../create-model-page/create-model-page.tsx | 42 + .../components/create-model-page/styles.scss | 43 + .../advanced-configuration-form.tsx | 339 + .../basic-configuration-form.tsx | 68 + .../create-task-page/create-task-content.tsx | 258 + .../create-task-page/create-task-page.tsx | 42 + .../components/create-task-page/styles.scss | 36 + cvat-ui/src/components/cvat-app.tsx | 285 + cvat-ui/src/components/feedback/feedback.tsx | 118 + cvat-ui/src/components/feedback/styles.scss | 16 + .../components/file-manager/file-manager.tsx | 243 + .../src/components/file-manager/styles.scss | 12 + .../header-layout/header-layout.scss | 66 - .../header-layout/header-layout.test.tsx | 11 - .../header-layout/header-layout.tsx | 87 - cvat-ui/src/components/header/header.tsx | 242 + cvat-ui/src/components/header/styles.scss | 59 + .../src/components/labels-editor/common.ts | 33 + .../labels-editor/constructor-creator.tsx | 21 + .../labels-editor/constructor-updater.tsx | 26 + .../labels-editor/constructor-viewer-item.tsx | 59 + .../labels-editor/constructor-viewer.tsx | 65 + .../components/labels-editor/label-form.tsx | 525 + .../labels-editor/labels-editor.tsx | 335 + .../components/labels-editor/raw-viewer.tsx | 112 + .../src/components/labels-editor/styles.scss | 90 + .../src/components/login-page/login-form.tsx | 107 + .../src/components/login-page/login-page.scss | 10 - .../components/login-page/login-page.test.tsx | 11 - .../src/components/login-page/login-page.tsx | 140 +- .../modals/task-create/task-create.scss | 8 - .../modals/task-create/task-create.tsx | 352 - .../modals/task-update/task-update.tsx | 51 - .../model-runner-modal/model-runner-modal.tsx | 411 + .../components/model-runner-modal/styles.scss | 13 + .../models-page/built-model-item.tsx | 54 + .../models-page/built-models-list.tsx | 52 + .../src/components/models-page/empty-list.tsx | 44 + .../components/models-page/models-page.tsx | 76 + .../src/components/models-page/styles.scss | 86 + .../src/components/models-page/top-bar.tsx | 56 + .../models-page/uploaded-model-item.tsx | 94 + .../models-page/uploaded-models-list.tsx | 75 + .../page-not-found/page-not-found.scss | 3 - .../page-not-found/page-not-found.test.tsx | 11 - .../page-not-found/page-not-found.tsx | 22 - .../register-page/register-form.tsx | 248 + .../register-page/register-page.scss | 10 - .../register-page/register-page.test.tsx | 11 - .../register-page/register-page.tsx | 263 +- .../settings-page/player-settings.tsx | 268 + .../settings-page/settings-page.tsx | 82 + .../src/components/settings-page/styles.scss | 90 + .../settings-page/workspace-settings.tsx | 105 + .../shortcuts-dialog/shortcuts-dialog.tsx | 97 + cvat-ui/src/components/task-page/details.tsx | 421 + cvat-ui/src/components/task-page/job-list.tsx | 200 + cvat-ui/src/components/task-page/styles.scss | 124 + .../src/components/task-page/task-page.tsx | 88 + cvat-ui/src/components/task-page/top-bar.tsx | 50 + .../components/task-page/user-selector.tsx | 40 + .../src/components/tasks-page/empty-list.tsx | 42 + cvat-ui/src/components/tasks-page/styles.scss | 164 + .../src/components/tasks-page/task-item.tsx | 236 + .../src/components/tasks-page/task-list.tsx | 56 + .../tasks-content/tasks-content.scss | 45 - .../tasks-content/tasks-content.test.tsx | 11 - .../tasks-content/tasks-content.tsx | 361 - .../tasks-page/tasks-footer/tasks-footer.scss | 9 - .../tasks-footer/tasks-footer.test.tsx | 11 - .../tasks-page/tasks-footer/tasks-footer.tsx | 46 - .../tasks-page/tasks-header/tasks-header.scss | 41 - .../tasks-header/tasks-header.test.tsx | 11 - .../tasks-page/tasks-header/tasks-header.tsx | 120 - .../src/components/tasks-page/tasks-page.scss | 3 - .../components/tasks-page/tasks-page.test.tsx | 11 - .../src/components/tasks-page/tasks-page.tsx | 264 +- cvat-ui/src/components/tasks-page/top-bar.tsx | 69 + .../containers/actions-menu/actions-menu.tsx | 195 + .../annotation-page/annotation-page.tsx | 88 + .../canvas-context-menu.tsx | 193 + .../standard-workspace/canvas-wrapper.tsx | 272 + .../controls-side-bar/controls-side-bar.tsx | 92 + .../controls-side-bar/draw-shape-popover.tsx | 198 + .../objects-side-bar/label-item.tsx | 226 + .../objects-side-bar/labels-list.tsx | 33 + .../objects-side-bar/object-item.tsx | 525 + .../objects-side-bar/objects-list.tsx | 526 + .../objects-side-bar/objects-side-bar.tsx | 214 + .../standard-workspace/propagate-confirm.tsx | 138 + .../top-bar/annotation-menu.tsx | 170 + .../top-bar/statistics-modal.tsx | 112 + .../annotation-page/top-bar/top-bar.tsx | 627 + .../create-model-page/create-model-page.tsx | 50 + .../create-task-page/create-task-page.tsx | 45 + .../containers/file-manager/file-manager.tsx | 96 + cvat-ui/src/containers/header/header.tsx | 79 + .../src/containers/login-page/login-page.tsx | 31 + .../model-runner-dialog.tsx | 88 + .../containers/models-page/models-page.tsx | 78 + .../register-page/register-page.tsx | 42 + .../settings-page/player-settings.tsx | 112 + .../settings-page/settings-page.tsx | 83 + .../settings-page/workspace-settings.tsx | 78 + cvat-ui/src/containers/task-page/details.tsx | 67 + cvat-ui/src/containers/task-page/job-list.tsx | 58 + .../src/containers/task-page/task-page.tsx | 86 + .../src/containers/tasks-page/task-item.tsx | 65 + .../src/containers/tasks-page/tasks-list.tsx | 67 + .../src/containers/tasks-page/tasks-page.tsx | 69 + cvat-ui/src/cvat-canvas.ts | 17 + cvat-ui/src/cvat-core.ts | 14 + cvat-ui/src/cvat-store.ts | 50 + cvat-ui/src/icons.tsx | 147 + cvat-ui/src/index.html | 20 + cvat-ui/src/index.scss | 57 - cvat-ui/src/index.tsx | 118 +- cvat-ui/src/react-app-env.d.ts | 1 - cvat-ui/src/reducers/about-reducer.ts | 57 + cvat-ui/src/reducers/annotation-reducer.ts | 1062 ++ cvat-ui/src/reducers/annotations.reducer.ts | 29 - cvat-ui/src/reducers/auth-reducer.ts | 74 + cvat-ui/src/reducers/auth.reducer.ts | 45 - cvat-ui/src/reducers/formats-reducer.ts | 51 + cvat-ui/src/reducers/interfaces.ts | 446 + cvat-ui/src/reducers/models-reducer.ts | 130 + cvat-ui/src/reducers/notifications-reducer.ts | 794 + cvat-ui/src/reducers/plugins-reducer.ts | 52 + cvat-ui/src/reducers/root-reducer.ts | 34 + cvat-ui/src/reducers/root.reducer.ts | 33 - cvat-ui/src/reducers/server.reducer.ts | 38 - cvat-ui/src/reducers/settings-reducer.ts | 232 + cvat-ui/src/reducers/share-reducer.ts | 59 + cvat-ui/src/reducers/shortcuts-reducer.ts | 20 + cvat-ui/src/reducers/tasks-filter.reducer.ts | 17 - cvat-ui/src/reducers/tasks-reducer.ts | 331 + cvat-ui/src/reducers/tasks.reducer.ts | 44 - cvat-ui/src/reducers/users-reducer.ts | 48 + cvat-ui/src/reducers/users.reducer.ts | 23 - cvat-ui/src/serviceWorker.ts | 143 - cvat-ui/src/store.ts | 32 - cvat-ui/src/styles.scss | 52 + cvat-ui/src/utils/enviroment.ts | 7 + cvat-ui/src/utils/git-utils.ts | 195 + cvat-ui/src/utils/plugin-checker.ts | 47 + cvat-ui/src/utils/redux.ts | 24 + cvat-ui/src/utils/tasks-dto.ts | 152 - cvat-ui/src/utils/tasks-filter.ts | 13 - cvat-ui/src/utils/validation-patterns.ts | 71 + cvat-ui/tsconfig.json | 9 +- cvat-ui/webpack.config.js | 97 + cvat/__init__.py | 5 +- cvat/apps/annotation/README.md | 417 +- cvat/apps/annotation/annotation.py | 39 +- cvat/apps/annotation/coco.py | 388 +- cvat/apps/annotation/cvat.py | 130 +- cvat/apps/annotation/labelme.py | 307 + cvat/apps/annotation/mask.py | 101 +- cvat/apps/annotation/mot.py | 109 + cvat/apps/annotation/pascal_voc.py | 122 +- cvat/apps/annotation/settings.py | 2 + cvat/apps/annotation/tfrecord.py | 164 +- cvat/apps/annotation/yolo.py | 143 +- cvat/apps/authentication/auth.py | 87 +- cvat/apps/authentication/decorators.py | 31 +- cvat/apps/authentication/views.py | 22 + cvat/apps/auto_annotation/README.md | 207 + cvat/apps/auto_annotation/__init__.py | 8 +- cvat/apps/auto_annotation/inference.py | 17 +- cvat/apps/auto_annotation/inference_engine.py | 2 +- cvat/apps/auto_annotation/model_loader.py | 14 +- cvat/apps/auto_annotation/model_manager.py | 6 +- .../auto_annotation/js/dashboardPlugin.js | 794 - .../static/auto_annotation/stylesheet.css | 83 - cvat/apps/auto_annotation/views.py | 5 +- cvat/apps/auto_segmentation/__init__.py | 4 + cvat/apps/auto_segmentation/admin.py | 8 + cvat/apps/auto_segmentation/apps.py | 11 + .../migrations/__init__.py | 0 cvat/apps/auto_segmentation/models.py | 8 + cvat/apps/auto_segmentation/tests.py | 8 + cvat/apps/auto_segmentation/urls.py | 14 + cvat/apps/auto_segmentation/views.py | 325 + cvat/apps/dashboard/__init__.py | 8 - cvat/apps/dashboard/apps.py | 13 - .../js/3rdparty/jstree/jstree.min.js | 6 - .../3rdparty/jstree/themes/default/32px.png | Bin 5667 -> 0 bytes .../3rdparty/jstree/themes/default/40px.png | Bin 2218 -> 0 bytes .../jstree/themes/default/style.min.css | 1 - .../jstree/themes/default/throbber.gif | Bin 1464 -> 0 bytes .../js/3rdparty/pagination/bootstrap.css | 107 - .../pagination/jquery.twbsPagination.js | 364 - .../static/dashboard/js/dashboard.js | 754 - .../static/dashboard/js/enginePlugin.js | 12 - .../dashboard/static/dashboard/stylesheet.css | 122 - .../templates/dashboard/dashboard.html | 223 - cvat/apps/dashboard/urls.py | 13 - cvat/apps/dashboard/views.py | 30 - .../apps/dataset_manager/__init__.py | 0 cvat/apps/dataset_manager/bindings.py | 249 + .../export_templates/README.md | 20 + .../plugins/cvat_rest_api_task_images.py | 135 + cvat/apps/dataset_manager/task.py | 348 + cvat/apps/dataset_manager/util.py | 20 + .../dextr_segmentation/js/enginePlugin.js | 3 +- .../documentation/AWS-Deployment-Guide.md | 25 +- cvat/apps/documentation/__init__.py | 6 +- cvat/apps/documentation/installation.md | 16 +- .../documentation/images/CuboidDrawing1.gif | Bin 0 -> 716427 bytes .../documentation/images/CuboidDrawing2.gif | Bin 0 -> 720088 bytes .../documentation/images/CuboidDrawing3.gif | Bin 0 -> 1398788 bytes .../documentation/images/CuboidEditing1.gif | Bin 0 -> 1763895 bytes .../documentation/images/CuboidEditing2.gif | Bin 0 -> 1943905 bytes .../images/EditingPerspective.gif | Bin 0 -> 1486047 bytes .../documentation/images/ResetPerspective.gif | Bin 0 -> 954704 bytes .../static/documentation/images/gif016.gif | Bin 0 -> 3161470 bytes .../static/documentation/images/image001.jpg | Bin 12719 -> 16406 bytes .../static/documentation/images/image002.jpg | Bin 6996 -> 11555 bytes .../static/documentation/images/image003.jpg | Bin 31181 -> 25063 bytes .../static/documentation/images/image004.jpg | Bin 9367 -> 13514 bytes .../static/documentation/images/image005.jpg | Bin 105424 -> 18152 bytes .../documentation/images/image006_DETRAC.jpg | Bin 67689 -> 13965 bytes .../static/documentation/images/image074.jpg | Bin 29606 -> 20437 bytes .../static/documentation/images/image099.jpg | Bin 9371 -> 68395 bytes .../static/documentation/images/image100.jpg | Bin 9501 -> 0 bytes .../documentation/images/image100_DETRAC.jpg | Bin 0 -> 32709 bytes .../static/documentation/images/image104.jpg | Bin 30584 -> 36674 bytes .../static/documentation/images/image106.jpg | Bin 72029 -> 44398 bytes .../static/documentation/images/image107.jpg | Bin 26138 -> 0 bytes .../static/documentation/images/image109.jpg | Bin 68146 -> 99258 bytes .../static/documentation/images/image110.jpg | Bin 22989 -> 100783 bytes .../static/documentation/images/image111.jpg | Bin 71082 -> 0 bytes .../static/documentation/images/image113.jpg | Bin 9296 -> 0 bytes .../documentation/images/image113_DETRAC.jpg | Bin 0 -> 4857 bytes .../static/documentation/images/image119.jpg | Bin 69171 -> 0 bytes .../documentation/images/image119_DETRAC.jpg | Bin 0 -> 56633 bytes .../static/documentation/images/image120.jpg | Bin 26875 -> 29252 bytes .../static/documentation/images/image121.jpg | Bin 47398 -> 0 bytes .../documentation/images/image121_DETRAC.jpg | Bin 0 -> 38076 bytes .../static/documentation/images/image123.jpg | Bin 0 -> 10771 bytes .../static/documentation/images/image124.jpg | Bin 0 -> 15567 bytes .../static/documentation/images/image125.jpg | Bin 0 -> 30966 bytes .../static/documentation/images/image126.jpg | Bin 0 -> 39226 bytes .../static/documentation/images/image127.jpg | Bin 0 -> 18378 bytes .../static/documentation/images/image128.jpg | Bin 0 -> 34272 bytes .../static/documentation/images/image130.jpg | Bin 0 -> 11726 bytes .../static/documentation/images/image131.jpg | Bin 0 -> 64287 bytes .../static/documentation/images/image133.JPG | Bin 0 -> 20437 bytes .../static/documentation/images/image134.jpg | Bin 0 -> 21966 bytes .../documentation/js/dashboardPlugin.js | 11 - cvat/apps/documentation/user_guide.md | 408 +- cvat/apps/documentation/xml_format.md | 12 +- cvat/apps/engine/data_manager.py | 3 +- cvat/apps/engine/media_extractors.py | 4 +- .../migrations/0022_auto_20191004_0817.py | 38 + .../migrations/0023_auto_20200113_1323.py | 34 + cvat/apps/engine/models.py | 34 +- cvat/apps/engine/pagination.py | 22 + cvat/apps/engine/serializers.py | 58 +- .../engine/static/engine/js/3rdparty.patch | 8 +- .../engine/static/engine/js/3rdparty/svg.js | 237 +- .../static/engine/js/annotationParser.js | 27 +- .../static/engine/js/annotationSaver.js | 36 +- .../engine/static/engine/js/annotationUI.js | 19 +- .../engine/static/engine/js/borderSticker.js | 213 + .../engine/static/engine/js/cuboidShape.js | 1370 ++ .../engine/static/engine/js/cvat-core.min.js | 6 +- .../engine/static/engine/js/labelsInfo.js | 2 +- .../static/engine/js/polyshapeEditor.js | 114 +- .../engine/static/engine/js/shapeBuffer.js | 30 +- .../static/engine/js/shapeCollection.js | 47 +- .../engine/static/engine/js/shapeCreator.js | 545 +- .../engine/static/engine/js/shapeMerger.js | 3 +- cvat/apps/engine/static/engine/js/shapes.js | 92 +- .../engine/static/engine/js/userConfig.js | 8 +- cvat/apps/engine/static/engine/js/utils.js | 56 + cvat/apps/engine/static/engine/stylesheet.css | 35 +- cvat/apps/engine/task.py | 30 +- .../engine/templates/engine/annotation.html | 37 +- cvat/apps/engine/tests/test_model.py | 25 + cvat/apps/engine/tests/test_rest_api.py | 1068 +- cvat/apps/engine/urls.py | 13 +- cvat/apps/engine/views.py | 384 +- cvat/apps/git/__init__.py | 6 +- cvat/apps/git/git.py | 20 +- .../apps/git/static/git/js/dashboardPlugin.js | 289 - cvat/apps/git/tests.py | 52 + cvat/apps/log_viewer/__init__.py | 6 +- .../static/log_viewer/js/dashboardPlugin.js | 11 - cvat/apps/tf_annotation/__init__.py | 7 +- .../tf_annotation/js/dashboardPlugin.js | 112 - cvat/apps/tf_annotation/views.py | 16 +- cvat/requirements/base.txt | 23 +- cvat/requirements/testing.txt | 5 +- cvat/settings/base.py | 30 +- cvat/urls.py | 11 +- cvat_proxy/conf.d/cvat.conf.template | 37 + cvat_proxy/nginx.conf | 16 + datumaro/CONTRIBUTING.md | 195 + datumaro/README.md | 176 + datumaro/datum.py | 8 + datumaro/datumaro/__init__.py | 4 + datumaro/datumaro/__main__.py | 12 + datumaro/datumaro/cli/__init__.py | 4 + datumaro/datumaro/cli/__main__.py | 166 + datumaro/datumaro/cli/commands/__init__.py | 6 + datumaro/datumaro/cli/commands/add.py | 8 + datumaro/datumaro/cli/commands/create.py | 8 + datumaro/datumaro/cli/commands/explain.py | 190 + datumaro/datumaro/cli/commands/export.py | 8 + datumaro/datumaro/cli/commands/remove.py | 8 + datumaro/datumaro/cli/contexts/__init__.py | 6 + .../datumaro/cli/contexts/item/__init__.py | 36 + .../datumaro/cli/contexts/model/__init__.py | 152 + .../datumaro/cli/contexts/project/__init__.py | 691 + .../datumaro/cli/contexts/project/diff.py | 281 + .../datumaro/cli/contexts/source/__init__.py | 247 + datumaro/datumaro/cli/util/__init__.py | 52 + datumaro/datumaro/cli/util/project.py | 34 + datumaro/datumaro/components/__init__.py | 5 + .../components/algorithms/__init__.py | 5 + .../datumaro/components/algorithms/rise.py | 220 + datumaro/datumaro/components/cli_plugin.py | 56 + datumaro/datumaro/components/comparator.py | 113 + datumaro/datumaro/components/config.py | 237 + datumaro/datumaro/components/config_model.py | 64 + datumaro/datumaro/components/converter.py | 19 + .../datumaro/components/dataset_filter.py | 247 + datumaro/datumaro/components/extractor.py | 767 + datumaro/datumaro/components/launcher.py | 65 + datumaro/datumaro/components/project.py | 832 ++ .../datumaro/plugins/__init__.py | 0 .../datumaro/plugins/coco_format/__init__.py | 0 .../datumaro/plugins/coco_format/converter.py | 624 + .../datumaro/plugins/coco_format/extractor.py | 264 + .../datumaro/plugins/coco_format/format.py | 23 + .../datumaro/plugins/coco_format/importer.py | 73 + .../datumaro/plugins/cvat_format/__init__.py | 0 .../datumaro/plugins/cvat_format/converter.py | 387 + .../datumaro/plugins/cvat_format/extractor.py | 356 + .../datumaro/plugins/cvat_format/format.py | 10 + .../datumaro/plugins/cvat_format/importer.py | 48 + .../plugins/datumaro_format/__init__.py | 0 .../plugins/datumaro_format/converter.py | 295 + .../plugins/datumaro_format/extractor.py | 165 + .../plugins/datumaro_format/format.py | 12 + .../plugins/datumaro_format/importer.py | 48 + datumaro/datumaro/plugins/image_dir.py | 91 + .../datumaro/plugins/openvino_launcher.py | 189 + .../tf_detection_api_format/__init__.py | 0 .../tf_detection_api_format/converter.py | 208 + .../tf_detection_api_format/extractor.py | 209 + .../plugins/tf_detection_api_format/format.py | 13 + .../tf_detection_api_format/importer.py | 44 + datumaro/datumaro/plugins/transforms.py | 483 + .../datumaro/plugins/voc_format/__init__.py | 0 .../datumaro/plugins/voc_format/converter.py | 588 + .../datumaro/plugins/voc_format/extractor.py | 740 + .../datumaro/plugins/voc_format/format.py | 205 + .../datumaro/plugins/voc_format/importer.py | 81 + .../datumaro/plugins/yolo_format/__init__.py | 0 .../datumaro/plugins/yolo_format/converter.py | 125 + .../datumaro/plugins/yolo_format/extractor.py | 180 + .../datumaro/plugins/yolo_format/format.py | 11 + .../datumaro/plugins/yolo_format/importer.py | 38 + datumaro/datumaro/util/__init__.py | 20 + datumaro/datumaro/util/annotation_tools.py | 29 + datumaro/datumaro/util/command_targets.py | 113 + datumaro/datumaro/util/image.py | 221 + datumaro/datumaro/util/image_cache.py | 38 + datumaro/datumaro/util/mask_tools.py | 284 + datumaro/datumaro/util/test_utils.py | 100 + datumaro/datumaro/util/tf_util.py | 41 + datumaro/datumaro/version.py | 1 + datumaro/docs/cli_design.mm | 65 + datumaro/docs/design.md | 183 + datumaro/docs/images/cli_design.png | Bin 0 -> 35845 bytes datumaro/docs/images/mvvm.png | Bin 0 -> 30318 bytes datumaro/docs/user_manual.md | 563 + datumaro/requirements.txt | 10 + datumaro/setup.py | 71 + datumaro/tests/__init__.py | 0 datumaro/tests/test_RISE.py | 231 + datumaro/tests/test_coco_format.py | 648 + datumaro/tests/test_command_targets.py | 128 + datumaro/tests/test_cvat_format.py | 253 + datumaro/tests/test_datumaro_format.py | 101 + datumaro/tests/test_diff.py | 142 + datumaro/tests/test_image.py | 52 + datumaro/tests/test_image_dir_format.py | 28 + datumaro/tests/test_images.py | 79 + datumaro/tests/test_masks.py | 186 + datumaro/tests/test_project.py | 551 + datumaro/tests/test_tfrecord_format.py | 172 + datumaro/tests/test_transforms.py | 457 + datumaro/tests/test_voc_format.py | 777 + datumaro/tests/test_yolo_format.py | 116 + docker-compose.ci.yml | 10 +- docker-compose.yml | 37 +- package.json | 2 +- supervisord.conf | 8 +- utils/auto_annotation/README.md | 89 +- utils/auto_annotation/run_model.py | 149 +- utils/cli/README.md | 44 + utils/cli/cli.py | 43 + utils/cli/core/__init__.py | 3 + utils/cli/core/core.py | 161 + utils/cli/core/definition.py | 236 + utils/cli/requirements.txt | 2 + utils/cli/tests/__init__.py | 0 utils/cli/tests/test_cli.py | 131 + .../0004/mappings.json | 5 + .../0004/pixel_link_mobilenet_v2.py | 197 + .../{0001 => }/README.md | 0 .../semantic-segmentation-adas/interp.py | 31 + .../semantic-segmentation-adas/mapping.json | 25 + .../README.md | 32 + .../interp.py | 64 + .../mapping.json | 84 + utils/open_model_zoo/yolov3/README.md | 22 + utils/open_model_zoo/yolov3/interp.py | 160 + utils/open_model_zoo/yolov3/mapping.json | 84 + utils/tfrecords/converter.md | 14 +- utils/tfrecords/requirements.txt | 2 +- utils/voc/converter.py | 3 +- 623 files changed, 69372 insertions(+), 16705 deletions(-) create mode 100644 .editorconfig create mode 100644 .vscode/python.env create mode 100644 Dockerfile.ci create mode 100644 Dockerfile.ui create mode 100644 components/auto_segmentation/README.md create mode 100644 components/auto_segmentation/docker-compose.auto_segmentation.yml create mode 100755 components/auto_segmentation/install.sh create mode 100644 cvat-canvas/package-lock.json create mode 100644 cvat-canvas/postcss.config.js rename cvat-canvas/src/{css/canvas.css => scss/canvas.scss} (67%) create mode 100644 cvat-canvas/src/typescript/zoomHandler.ts create mode 100644 cvat-core/.dockerignore create mode 100644 cvat-core/src/annotations-filter.js create mode 100644 cvat-core/src/annotations-history.js create mode 100644 cvat-core/tests/internal/filter.js delete mode 100644 cvat-ui/.env.production create mode 100644 cvat-ui/.eslintrc.js delete mode 100644 cvat-ui/Dockerfile delete mode 100644 cvat-ui/config-overrides.js create mode 100644 cvat-ui/dist/favicon.ico create mode 100644 cvat-ui/index.d.ts create mode 100644 cvat-ui/postcss.config.js delete mode 100644 cvat-ui/public/cvat-core.min.js delete mode 100644 cvat-ui/public/cvat-core.min.js.map delete mode 100644 cvat-ui/public/favicon.ico delete mode 100644 cvat-ui/public/images/cvat-logo.svg delete mode 100644 cvat-ui/public/images/empty-tasks-icon.svg delete mode 100644 cvat-ui/public/index.html delete mode 100644 cvat-ui/public/manifest.json create mode 100644 cvat-ui/src/actions/about-actions.ts create mode 100644 cvat-ui/src/actions/annotation-actions.ts delete mode 100644 cvat-ui/src/actions/annotations.actions.ts create mode 100644 cvat-ui/src/actions/auth-actions.ts delete mode 100644 cvat-ui/src/actions/auth.actions.ts create mode 100644 cvat-ui/src/actions/formats-actions.ts create mode 100644 cvat-ui/src/actions/models-actions.ts create mode 100644 cvat-ui/src/actions/notification-actions.ts create mode 100644 cvat-ui/src/actions/plugins-actions.ts delete mode 100644 cvat-ui/src/actions/server.actions.ts create mode 100644 cvat-ui/src/actions/settings-actions.ts create mode 100644 cvat-ui/src/actions/share-actions.ts create mode 100644 cvat-ui/src/actions/shortcuts-actions.ts create mode 100644 cvat-ui/src/actions/tasks-actions.ts delete mode 100644 cvat-ui/src/actions/tasks-filter.actions.ts delete mode 100644 cvat-ui/src/actions/tasks.actions.ts create mode 100644 cvat-ui/src/actions/users-actions.ts delete mode 100644 cvat-ui/src/actions/users.actions.ts create mode 100644 cvat-ui/src/assets/account-icon.svg create mode 100644 cvat-ui/src/assets/back-jump-icon.svg create mode 100644 cvat-ui/src/assets/background-icon.svg create mode 100644 cvat-ui/src/assets/cursor-icon.svg create mode 100644 cvat-ui/src/assets/cvat-logo.svg create mode 100644 cvat-ui/src/assets/empty-tasks-icon.svg create mode 100644 cvat-ui/src/assets/exit-fullscreen-icon.svg create mode 100644 cvat-ui/src/assets/first-icon.svg create mode 100644 cvat-ui/src/assets/fit-to-window-icon.svg create mode 100644 cvat-ui/src/assets/foreground-icon.svg create mode 100644 cvat-ui/src/assets/forward-jump-icon.svg create mode 100644 cvat-ui/src/assets/fullscreen-icon.svg create mode 100644 cvat-ui/src/assets/group-icon.svg create mode 100644 cvat-ui/src/assets/info-icon.svg create mode 100644 cvat-ui/src/assets/last-icon.svg create mode 100644 cvat-ui/src/assets/main-menu-icon.svg create mode 100644 cvat-ui/src/assets/menu-icon.svg create mode 100644 cvat-ui/src/assets/merge-icon.svg create mode 100644 cvat-ui/src/assets/move-icon.svg create mode 100644 cvat-ui/src/assets/next-icon.svg create mode 100644 cvat-ui/src/assets/object-filter-icon.svg create mode 100644 cvat-ui/src/assets/object-hide-icon.svg create mode 100644 cvat-ui/src/assets/object-inside-icon.svg create mode 100644 cvat-ui/src/assets/object-occlude-icon.svg create mode 100644 cvat-ui/src/assets/object-outside-icon.svg create mode 100644 cvat-ui/src/assets/pause-icon.svg create mode 100644 cvat-ui/src/assets/play-icon.svg create mode 100644 cvat-ui/src/assets/plus-icon.svg create mode 100644 cvat-ui/src/assets/point-icon.svg create mode 100644 cvat-ui/src/assets/polygon-icon.svg create mode 100644 cvat-ui/src/assets/polyline-icon.svg create mode 100644 cvat-ui/src/assets/previous-icon.svg create mode 100644 cvat-ui/src/assets/rectangle-icon.svg create mode 100644 cvat-ui/src/assets/redo-icon.svg create mode 100644 cvat-ui/src/assets/rotate-icon.svg create mode 100644 cvat-ui/src/assets/save-icon.svg create mode 100644 cvat-ui/src/assets/settings-icon.svg create mode 100644 cvat-ui/src/assets/show-sidebar-icon.svg create mode 100644 cvat-ui/src/assets/side-icon-object-lock.svg create mode 100644 cvat-ui/src/assets/split-icon.svg create mode 100644 cvat-ui/src/assets/tag-icon.svg create mode 100644 cvat-ui/src/assets/undo-icon.svg create mode 100644 cvat-ui/src/assets/zoom-icon.svg create mode 100644 cvat-ui/src/base.scss create mode 100644 cvat-ui/src/components/actions-menu/actions-menu.tsx create mode 100644 cvat-ui/src/components/actions-menu/dump-submenu.tsx create mode 100644 cvat-ui/src/components/actions-menu/export-submenu.tsx create mode 100644 cvat-ui/src/components/actions-menu/load-submenu.tsx create mode 100644 cvat-ui/src/components/actions-menu/styles.scss create mode 100644 cvat-ui/src/components/annotation-page/annotation-page.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/canvas-context-menu.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/cursor-control.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-points-control.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-polygon-control.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-polyline-control.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-rectangle-control.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/fit-control.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/group-control.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/merge-control.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/move-control.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/resize-control.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/split-control.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/appearance-block.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/color-changer.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/label-item.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/labels-list.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-list-header.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/propagate-confirm.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/styles.scss create mode 100644 cvat-ui/src/components/annotation-page/styles.scss create mode 100644 cvat-ui/src/components/annotation-page/top-bar/annotation-menu.tsx create mode 100644 cvat-ui/src/components/annotation-page/top-bar/left-group.tsx create mode 100644 cvat-ui/src/components/annotation-page/top-bar/player-buttons.tsx create mode 100644 cvat-ui/src/components/annotation-page/top-bar/player-navigation.tsx create mode 100644 cvat-ui/src/components/annotation-page/top-bar/right-group.tsx create mode 100644 cvat-ui/src/components/annotation-page/top-bar/statistics-modal.tsx create mode 100644 cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx delete mode 100644 cvat-ui/src/components/app/app.scss delete mode 100644 cvat-ui/src/components/app/app.test.tsx delete mode 100644 cvat-ui/src/components/app/app.tsx create mode 100644 cvat-ui/src/components/create-model-page/create-model-content.tsx create mode 100644 cvat-ui/src/components/create-model-page/create-model-form.tsx create mode 100644 cvat-ui/src/components/create-model-page/create-model-page.tsx create mode 100644 cvat-ui/src/components/create-model-page/styles.scss create mode 100644 cvat-ui/src/components/create-task-page/advanced-configuration-form.tsx create mode 100644 cvat-ui/src/components/create-task-page/basic-configuration-form.tsx create mode 100644 cvat-ui/src/components/create-task-page/create-task-content.tsx create mode 100644 cvat-ui/src/components/create-task-page/create-task-page.tsx create mode 100644 cvat-ui/src/components/create-task-page/styles.scss create mode 100644 cvat-ui/src/components/cvat-app.tsx create mode 100644 cvat-ui/src/components/feedback/feedback.tsx create mode 100644 cvat-ui/src/components/feedback/styles.scss create mode 100644 cvat-ui/src/components/file-manager/file-manager.tsx create mode 100644 cvat-ui/src/components/file-manager/styles.scss delete mode 100644 cvat-ui/src/components/header-layout/header-layout.scss delete mode 100644 cvat-ui/src/components/header-layout/header-layout.test.tsx delete mode 100644 cvat-ui/src/components/header-layout/header-layout.tsx create mode 100644 cvat-ui/src/components/header/header.tsx create mode 100644 cvat-ui/src/components/header/styles.scss create mode 100644 cvat-ui/src/components/labels-editor/common.ts create mode 100644 cvat-ui/src/components/labels-editor/constructor-creator.tsx create mode 100644 cvat-ui/src/components/labels-editor/constructor-updater.tsx create mode 100644 cvat-ui/src/components/labels-editor/constructor-viewer-item.tsx create mode 100644 cvat-ui/src/components/labels-editor/constructor-viewer.tsx create mode 100644 cvat-ui/src/components/labels-editor/label-form.tsx create mode 100644 cvat-ui/src/components/labels-editor/labels-editor.tsx create mode 100644 cvat-ui/src/components/labels-editor/raw-viewer.tsx create mode 100644 cvat-ui/src/components/labels-editor/styles.scss create mode 100644 cvat-ui/src/components/login-page/login-form.tsx delete mode 100644 cvat-ui/src/components/login-page/login-page.scss delete mode 100644 cvat-ui/src/components/login-page/login-page.test.tsx delete mode 100644 cvat-ui/src/components/modals/task-create/task-create.scss delete mode 100644 cvat-ui/src/components/modals/task-create/task-create.tsx delete mode 100644 cvat-ui/src/components/modals/task-update/task-update.tsx create mode 100644 cvat-ui/src/components/model-runner-modal/model-runner-modal.tsx create mode 100644 cvat-ui/src/components/model-runner-modal/styles.scss create mode 100644 cvat-ui/src/components/models-page/built-model-item.tsx create mode 100644 cvat-ui/src/components/models-page/built-models-list.tsx create mode 100644 cvat-ui/src/components/models-page/empty-list.tsx create mode 100644 cvat-ui/src/components/models-page/models-page.tsx create mode 100644 cvat-ui/src/components/models-page/styles.scss create mode 100644 cvat-ui/src/components/models-page/top-bar.tsx create mode 100644 cvat-ui/src/components/models-page/uploaded-model-item.tsx create mode 100644 cvat-ui/src/components/models-page/uploaded-models-list.tsx delete mode 100644 cvat-ui/src/components/page-not-found/page-not-found.scss delete mode 100644 cvat-ui/src/components/page-not-found/page-not-found.test.tsx delete mode 100644 cvat-ui/src/components/page-not-found/page-not-found.tsx create mode 100644 cvat-ui/src/components/register-page/register-form.tsx delete mode 100644 cvat-ui/src/components/register-page/register-page.scss delete mode 100644 cvat-ui/src/components/register-page/register-page.test.tsx create mode 100644 cvat-ui/src/components/settings-page/player-settings.tsx create mode 100644 cvat-ui/src/components/settings-page/settings-page.tsx create mode 100644 cvat-ui/src/components/settings-page/styles.scss create mode 100644 cvat-ui/src/components/settings-page/workspace-settings.tsx create mode 100644 cvat-ui/src/components/shortcuts-dialog/shortcuts-dialog.tsx create mode 100644 cvat-ui/src/components/task-page/details.tsx create mode 100644 cvat-ui/src/components/task-page/job-list.tsx create mode 100644 cvat-ui/src/components/task-page/styles.scss create mode 100644 cvat-ui/src/components/task-page/task-page.tsx create mode 100644 cvat-ui/src/components/task-page/top-bar.tsx create mode 100644 cvat-ui/src/components/task-page/user-selector.tsx create mode 100644 cvat-ui/src/components/tasks-page/empty-list.tsx create mode 100644 cvat-ui/src/components/tasks-page/styles.scss create mode 100644 cvat-ui/src/components/tasks-page/task-item.tsx create mode 100644 cvat-ui/src/components/tasks-page/task-list.tsx delete mode 100644 cvat-ui/src/components/tasks-page/tasks-content/tasks-content.scss delete mode 100644 cvat-ui/src/components/tasks-page/tasks-content/tasks-content.test.tsx delete mode 100644 cvat-ui/src/components/tasks-page/tasks-content/tasks-content.tsx delete mode 100644 cvat-ui/src/components/tasks-page/tasks-footer/tasks-footer.scss delete mode 100644 cvat-ui/src/components/tasks-page/tasks-footer/tasks-footer.test.tsx delete mode 100644 cvat-ui/src/components/tasks-page/tasks-footer/tasks-footer.tsx delete mode 100644 cvat-ui/src/components/tasks-page/tasks-header/tasks-header.scss delete mode 100644 cvat-ui/src/components/tasks-page/tasks-header/tasks-header.test.tsx delete mode 100644 cvat-ui/src/components/tasks-page/tasks-header/tasks-header.tsx delete mode 100644 cvat-ui/src/components/tasks-page/tasks-page.scss delete mode 100644 cvat-ui/src/components/tasks-page/tasks-page.test.tsx create mode 100644 cvat-ui/src/components/tasks-page/top-bar.tsx create mode 100644 cvat-ui/src/containers/actions-menu/actions-menu.tsx create mode 100644 cvat-ui/src/containers/annotation-page/annotation-page.tsx create mode 100644 cvat-ui/src/containers/annotation-page/standard-workspace/canvas-context-menu.tsx create mode 100644 cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx create mode 100644 cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx create mode 100644 cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx create mode 100644 cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/label-item.tsx create mode 100644 cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/labels-list.tsx create mode 100644 cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx create mode 100644 cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx create mode 100644 cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx create mode 100644 cvat-ui/src/containers/annotation-page/standard-workspace/propagate-confirm.tsx create mode 100644 cvat-ui/src/containers/annotation-page/top-bar/annotation-menu.tsx create mode 100644 cvat-ui/src/containers/annotation-page/top-bar/statistics-modal.tsx create mode 100644 cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx create mode 100644 cvat-ui/src/containers/create-model-page/create-model-page.tsx create mode 100644 cvat-ui/src/containers/create-task-page/create-task-page.tsx create mode 100644 cvat-ui/src/containers/file-manager/file-manager.tsx create mode 100644 cvat-ui/src/containers/header/header.tsx create mode 100644 cvat-ui/src/containers/login-page/login-page.tsx create mode 100644 cvat-ui/src/containers/model-runner-dialog/model-runner-dialog.tsx create mode 100644 cvat-ui/src/containers/models-page/models-page.tsx create mode 100644 cvat-ui/src/containers/register-page/register-page.tsx create mode 100644 cvat-ui/src/containers/settings-page/player-settings.tsx create mode 100644 cvat-ui/src/containers/settings-page/settings-page.tsx create mode 100644 cvat-ui/src/containers/settings-page/workspace-settings.tsx create mode 100644 cvat-ui/src/containers/task-page/details.tsx create mode 100644 cvat-ui/src/containers/task-page/job-list.tsx create mode 100644 cvat-ui/src/containers/task-page/task-page.tsx create mode 100644 cvat-ui/src/containers/tasks-page/task-item.tsx create mode 100644 cvat-ui/src/containers/tasks-page/tasks-list.tsx create mode 100644 cvat-ui/src/containers/tasks-page/tasks-page.tsx create mode 100644 cvat-ui/src/cvat-canvas.ts create mode 100644 cvat-ui/src/cvat-core.ts create mode 100644 cvat-ui/src/cvat-store.ts create mode 100644 cvat-ui/src/icons.tsx create mode 100644 cvat-ui/src/index.html delete mode 100644 cvat-ui/src/index.scss delete mode 100644 cvat-ui/src/react-app-env.d.ts create mode 100644 cvat-ui/src/reducers/about-reducer.ts create mode 100644 cvat-ui/src/reducers/annotation-reducer.ts delete mode 100644 cvat-ui/src/reducers/annotations.reducer.ts create mode 100644 cvat-ui/src/reducers/auth-reducer.ts delete mode 100644 cvat-ui/src/reducers/auth.reducer.ts create mode 100644 cvat-ui/src/reducers/formats-reducer.ts create mode 100644 cvat-ui/src/reducers/interfaces.ts create mode 100644 cvat-ui/src/reducers/models-reducer.ts create mode 100644 cvat-ui/src/reducers/notifications-reducer.ts create mode 100644 cvat-ui/src/reducers/plugins-reducer.ts create mode 100644 cvat-ui/src/reducers/root-reducer.ts delete mode 100644 cvat-ui/src/reducers/root.reducer.ts delete mode 100644 cvat-ui/src/reducers/server.reducer.ts create mode 100644 cvat-ui/src/reducers/settings-reducer.ts create mode 100644 cvat-ui/src/reducers/share-reducer.ts create mode 100644 cvat-ui/src/reducers/shortcuts-reducer.ts delete mode 100644 cvat-ui/src/reducers/tasks-filter.reducer.ts create mode 100644 cvat-ui/src/reducers/tasks-reducer.ts delete mode 100644 cvat-ui/src/reducers/tasks.reducer.ts create mode 100644 cvat-ui/src/reducers/users-reducer.ts delete mode 100644 cvat-ui/src/reducers/users.reducer.ts delete mode 100644 cvat-ui/src/serviceWorker.ts delete mode 100644 cvat-ui/src/store.ts create mode 100644 cvat-ui/src/styles.scss create mode 100644 cvat-ui/src/utils/enviroment.ts create mode 100644 cvat-ui/src/utils/git-utils.ts create mode 100644 cvat-ui/src/utils/plugin-checker.ts create mode 100644 cvat-ui/src/utils/redux.ts delete mode 100644 cvat-ui/src/utils/tasks-dto.ts delete mode 100644 cvat-ui/src/utils/tasks-filter.ts create mode 100644 cvat-ui/src/utils/validation-patterns.ts create mode 100644 cvat-ui/webpack.config.js create mode 100644 cvat/apps/annotation/labelme.py create mode 100644 cvat/apps/annotation/mot.py delete mode 100644 cvat/apps/auto_annotation/static/auto_annotation/js/dashboardPlugin.js delete mode 100644 cvat/apps/auto_annotation/static/auto_annotation/stylesheet.css create mode 100644 cvat/apps/auto_segmentation/__init__.py create mode 100644 cvat/apps/auto_segmentation/admin.py create mode 100644 cvat/apps/auto_segmentation/apps.py rename cvat/apps/{dashboard => auto_segmentation}/migrations/__init__.py (100%) create mode 100644 cvat/apps/auto_segmentation/models.py create mode 100644 cvat/apps/auto_segmentation/tests.py create mode 100644 cvat/apps/auto_segmentation/urls.py create mode 100644 cvat/apps/auto_segmentation/views.py delete mode 100644 cvat/apps/dashboard/__init__.py delete mode 100644 cvat/apps/dashboard/apps.py delete mode 100644 cvat/apps/dashboard/static/dashboard/js/3rdparty/jstree/jstree.min.js delete mode 100644 cvat/apps/dashboard/static/dashboard/js/3rdparty/jstree/themes/default/32px.png delete mode 100644 cvat/apps/dashboard/static/dashboard/js/3rdparty/jstree/themes/default/40px.png delete mode 100644 cvat/apps/dashboard/static/dashboard/js/3rdparty/jstree/themes/default/style.min.css delete mode 100644 cvat/apps/dashboard/static/dashboard/js/3rdparty/jstree/themes/default/throbber.gif delete mode 100644 cvat/apps/dashboard/static/dashboard/js/3rdparty/pagination/bootstrap.css delete mode 100644 cvat/apps/dashboard/static/dashboard/js/3rdparty/pagination/jquery.twbsPagination.js delete mode 100644 cvat/apps/dashboard/static/dashboard/js/dashboard.js delete mode 100644 cvat/apps/dashboard/static/dashboard/js/enginePlugin.js delete mode 100644 cvat/apps/dashboard/static/dashboard/stylesheet.css delete mode 100644 cvat/apps/dashboard/templates/dashboard/dashboard.html delete mode 100644 cvat/apps/dashboard/urls.py delete mode 100644 cvat/apps/dashboard/views.py rename cvat-ui/src/components/modals/task-create/task-create.test.tsx => cvat/apps/dataset_manager/__init__.py (100%) create mode 100644 cvat/apps/dataset_manager/bindings.py create mode 100644 cvat/apps/dataset_manager/export_templates/README.md create mode 100644 cvat/apps/dataset_manager/export_templates/plugins/cvat_rest_api_task_images.py create mode 100644 cvat/apps/dataset_manager/task.py create mode 100644 cvat/apps/dataset_manager/util.py create mode 100644 cvat/apps/documentation/static/documentation/images/CuboidDrawing1.gif create mode 100644 cvat/apps/documentation/static/documentation/images/CuboidDrawing2.gif create mode 100644 cvat/apps/documentation/static/documentation/images/CuboidDrawing3.gif create mode 100644 cvat/apps/documentation/static/documentation/images/CuboidEditing1.gif create mode 100644 cvat/apps/documentation/static/documentation/images/CuboidEditing2.gif create mode 100644 cvat/apps/documentation/static/documentation/images/EditingPerspective.gif create mode 100644 cvat/apps/documentation/static/documentation/images/ResetPerspective.gif create mode 100644 cvat/apps/documentation/static/documentation/images/gif016.gif delete mode 100644 cvat/apps/documentation/static/documentation/images/image100.jpg create mode 100644 cvat/apps/documentation/static/documentation/images/image100_DETRAC.jpg delete mode 100644 cvat/apps/documentation/static/documentation/images/image107.jpg delete mode 100644 cvat/apps/documentation/static/documentation/images/image111.jpg delete mode 100644 cvat/apps/documentation/static/documentation/images/image113.jpg create mode 100644 cvat/apps/documentation/static/documentation/images/image113_DETRAC.jpg delete mode 100644 cvat/apps/documentation/static/documentation/images/image119.jpg create mode 100644 cvat/apps/documentation/static/documentation/images/image119_DETRAC.jpg delete mode 100644 cvat/apps/documentation/static/documentation/images/image121.jpg create mode 100644 cvat/apps/documentation/static/documentation/images/image121_DETRAC.jpg create mode 100644 cvat/apps/documentation/static/documentation/images/image123.jpg create mode 100644 cvat/apps/documentation/static/documentation/images/image124.jpg create mode 100644 cvat/apps/documentation/static/documentation/images/image125.jpg create mode 100644 cvat/apps/documentation/static/documentation/images/image126.jpg create mode 100644 cvat/apps/documentation/static/documentation/images/image127.jpg create mode 100644 cvat/apps/documentation/static/documentation/images/image128.jpg create mode 100644 cvat/apps/documentation/static/documentation/images/image130.jpg create mode 100644 cvat/apps/documentation/static/documentation/images/image131.jpg create mode 100644 cvat/apps/documentation/static/documentation/images/image133.JPG create mode 100644 cvat/apps/documentation/static/documentation/images/image134.jpg delete mode 100644 cvat/apps/documentation/static/documentation/js/dashboardPlugin.js create mode 100644 cvat/apps/engine/migrations/0022_auto_20191004_0817.py create mode 100644 cvat/apps/engine/migrations/0023_auto_20200113_1323.py create mode 100644 cvat/apps/engine/pagination.py create mode 100644 cvat/apps/engine/static/engine/js/borderSticker.js create mode 100644 cvat/apps/engine/static/engine/js/cuboidShape.js create mode 100644 cvat/apps/engine/static/engine/js/utils.js create mode 100644 cvat/apps/engine/tests/test_model.py delete mode 100644 cvat/apps/git/static/git/js/dashboardPlugin.js delete mode 100644 cvat/apps/log_viewer/static/log_viewer/js/dashboardPlugin.js delete mode 100644 cvat/apps/tf_annotation/static/tf_annotation/js/dashboardPlugin.js create mode 100644 cvat_proxy/conf.d/cvat.conf.template create mode 100644 cvat_proxy/nginx.conf create mode 100644 datumaro/CONTRIBUTING.md create mode 100644 datumaro/README.md create mode 100755 datumaro/datum.py create mode 100644 datumaro/datumaro/__init__.py create mode 100644 datumaro/datumaro/__main__.py create mode 100644 datumaro/datumaro/cli/__init__.py create mode 100644 datumaro/datumaro/cli/__main__.py create mode 100644 datumaro/datumaro/cli/commands/__init__.py create mode 100644 datumaro/datumaro/cli/commands/add.py create mode 100644 datumaro/datumaro/cli/commands/create.py create mode 100644 datumaro/datumaro/cli/commands/explain.py create mode 100644 datumaro/datumaro/cli/commands/export.py create mode 100644 datumaro/datumaro/cli/commands/remove.py create mode 100644 datumaro/datumaro/cli/contexts/__init__.py create mode 100644 datumaro/datumaro/cli/contexts/item/__init__.py create mode 100644 datumaro/datumaro/cli/contexts/model/__init__.py create mode 100644 datumaro/datumaro/cli/contexts/project/__init__.py create mode 100644 datumaro/datumaro/cli/contexts/project/diff.py create mode 100644 datumaro/datumaro/cli/contexts/source/__init__.py create mode 100644 datumaro/datumaro/cli/util/__init__.py create mode 100644 datumaro/datumaro/cli/util/project.py create mode 100644 datumaro/datumaro/components/__init__.py create mode 100644 datumaro/datumaro/components/algorithms/__init__.py create mode 100644 datumaro/datumaro/components/algorithms/rise.py create mode 100644 datumaro/datumaro/components/cli_plugin.py create mode 100644 datumaro/datumaro/components/comparator.py create mode 100644 datumaro/datumaro/components/config.py create mode 100644 datumaro/datumaro/components/config_model.py create mode 100644 datumaro/datumaro/components/converter.py create mode 100644 datumaro/datumaro/components/dataset_filter.py create mode 100644 datumaro/datumaro/components/extractor.py create mode 100644 datumaro/datumaro/components/launcher.py create mode 100644 datumaro/datumaro/components/project.py rename cvat-ui/src/components/modals/task-update/task-update.scss => datumaro/datumaro/plugins/__init__.py (100%) rename cvat-ui/src/components/modals/task-update/task-update.test.tsx => datumaro/datumaro/plugins/coco_format/__init__.py (100%) create mode 100644 datumaro/datumaro/plugins/coco_format/converter.py create mode 100644 datumaro/datumaro/plugins/coco_format/extractor.py create mode 100644 datumaro/datumaro/plugins/coco_format/format.py create mode 100644 datumaro/datumaro/plugins/coco_format/importer.py create mode 100644 datumaro/datumaro/plugins/cvat_format/__init__.py create mode 100644 datumaro/datumaro/plugins/cvat_format/converter.py create mode 100644 datumaro/datumaro/plugins/cvat_format/extractor.py create mode 100644 datumaro/datumaro/plugins/cvat_format/format.py create mode 100644 datumaro/datumaro/plugins/cvat_format/importer.py create mode 100644 datumaro/datumaro/plugins/datumaro_format/__init__.py create mode 100644 datumaro/datumaro/plugins/datumaro_format/converter.py create mode 100644 datumaro/datumaro/plugins/datumaro_format/extractor.py create mode 100644 datumaro/datumaro/plugins/datumaro_format/format.py create mode 100644 datumaro/datumaro/plugins/datumaro_format/importer.py create mode 100644 datumaro/datumaro/plugins/image_dir.py create mode 100644 datumaro/datumaro/plugins/openvino_launcher.py create mode 100644 datumaro/datumaro/plugins/tf_detection_api_format/__init__.py create mode 100644 datumaro/datumaro/plugins/tf_detection_api_format/converter.py create mode 100644 datumaro/datumaro/plugins/tf_detection_api_format/extractor.py create mode 100644 datumaro/datumaro/plugins/tf_detection_api_format/format.py create mode 100644 datumaro/datumaro/plugins/tf_detection_api_format/importer.py create mode 100644 datumaro/datumaro/plugins/transforms.py create mode 100644 datumaro/datumaro/plugins/voc_format/__init__.py create mode 100644 datumaro/datumaro/plugins/voc_format/converter.py create mode 100644 datumaro/datumaro/plugins/voc_format/extractor.py create mode 100644 datumaro/datumaro/plugins/voc_format/format.py create mode 100644 datumaro/datumaro/plugins/voc_format/importer.py create mode 100644 datumaro/datumaro/plugins/yolo_format/__init__.py create mode 100644 datumaro/datumaro/plugins/yolo_format/converter.py create mode 100644 datumaro/datumaro/plugins/yolo_format/extractor.py create mode 100644 datumaro/datumaro/plugins/yolo_format/format.py create mode 100644 datumaro/datumaro/plugins/yolo_format/importer.py create mode 100644 datumaro/datumaro/util/__init__.py create mode 100644 datumaro/datumaro/util/annotation_tools.py create mode 100644 datumaro/datumaro/util/command_targets.py create mode 100644 datumaro/datumaro/util/image.py create mode 100644 datumaro/datumaro/util/image_cache.py create mode 100644 datumaro/datumaro/util/mask_tools.py create mode 100644 datumaro/datumaro/util/test_utils.py create mode 100644 datumaro/datumaro/util/tf_util.py create mode 100644 datumaro/datumaro/version.py create mode 100644 datumaro/docs/cli_design.mm create mode 100644 datumaro/docs/design.md create mode 100644 datumaro/docs/images/cli_design.png create mode 100644 datumaro/docs/images/mvvm.png create mode 100644 datumaro/docs/user_manual.md create mode 100644 datumaro/requirements.txt create mode 100644 datumaro/setup.py create mode 100644 datumaro/tests/__init__.py create mode 100644 datumaro/tests/test_RISE.py create mode 100644 datumaro/tests/test_coco_format.py create mode 100644 datumaro/tests/test_command_targets.py create mode 100644 datumaro/tests/test_cvat_format.py create mode 100644 datumaro/tests/test_datumaro_format.py create mode 100644 datumaro/tests/test_diff.py create mode 100644 datumaro/tests/test_image.py create mode 100644 datumaro/tests/test_image_dir_format.py create mode 100644 datumaro/tests/test_images.py create mode 100644 datumaro/tests/test_masks.py create mode 100644 datumaro/tests/test_project.py create mode 100644 datumaro/tests/test_tfrecord_format.py create mode 100644 datumaro/tests/test_transforms.py create mode 100644 datumaro/tests/test_voc_format.py create mode 100644 datumaro/tests/test_yolo_format.py create mode 100644 utils/cli/README.md create mode 100755 utils/cli/cli.py create mode 100644 utils/cli/core/__init__.py create mode 100644 utils/cli/core/core.py create mode 100644 utils/cli/core/definition.py create mode 100644 utils/cli/requirements.txt create mode 100644 utils/cli/tests/__init__.py create mode 100644 utils/cli/tests/test_cli.py create mode 100644 utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0004/mappings.json create mode 100644 utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0004/pixel_link_mobilenet_v2.py rename utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/{0001 => }/README.md (100%) create mode 100644 utils/open_model_zoo/Transportation/semantic-segmentation-adas/interp.py create mode 100644 utils/open_model_zoo/Transportation/semantic-segmentation-adas/mapping.json create mode 100644 utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/README.md create mode 100644 utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/interp.py create mode 100644 utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/mapping.json create mode 100644 utils/open_model_zoo/yolov3/README.md create mode 100644 utils/open_model_zoo/yolov3/interp.py create mode 100644 utils/open_model_zoo/yolov3/mapping.json diff --git a/.codacy.yml b/.codacy.yml index 0549c8f5e92..55fc0679684 100644 --- a/.codacy.yml +++ b/.codacy.yml @@ -1,3 +1,4 @@ exclude_paths: - '**/3rdparty/**' - '**/engine/js/cvat-core.min.js' + - CHANGELOG.md diff --git a/.dockerignore b/.dockerignore index 44c9d2c9763..afe7b64b36d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,5 +7,4 @@ /db.sqlite3 /keys **/node_modules -cvat-ui -cvat-canvas + diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000000..4c754efc979 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + +[*] + +# Change these settings to your own preference +indent_style = space +indent_size = 4 + +# We recommend you to keep these unchanged +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.gitignore b/.gitignore index c30ade19756..00e9b3f9ddd 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,7 @@ /ssh/* !/ssh/README.md node_modules - +/Mask_RCNN/ # Ignore temporary files docker-compose.override.yml diff --git a/.travis.yml b/.travis.yml index f4218109b96..8cbae296ccc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,10 +7,11 @@ python: services: - docker - + before_script: - - docker-compose -f docker-compose.yml -f docker-compose.ci.yml up --build -d + - docker-compose -f docker-compose.yml -f docker-compose.ci.yml build script: - - docker exec -it cvat /bin/bash -c 'python3 manage.py test cvat/apps/engine' - - docker exec -it cvat /bin/bash -c 'cd cvat-core && npm install && npm run test && npm run coveralls' + - docker-compose -f docker-compose.yml -f docker-compose.ci.yml run cvat_ci /bin/bash -c 'python3 manage.py test cvat/apps utils/cli' + - docker-compose -f docker-compose.yml -f docker-compose.ci.yml run cvat_ci /bin/bash -c 'python3 manage.py test datumaro/' + - docker-compose -f docker-compose.yml -f docker-compose.ci.yml run cvat_ci /bin/bash -c 'cd cvat-core && npm install && npm run test && npm run coveralls' diff --git a/.vscode/launch.json b/.vscode/launch.json index ee8bb4fb3d2..1fefab7fee5 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -71,6 +71,22 @@ "env": {}, "console": "internalConsole" }, + { + "name": "server: RQ - scheduler", + "type": "python", + "request": "launch", + "stopOnEntry": false, + "justMyCode": false, + "pythonPath": "${config:python.pythonPath}", + "program": "${workspaceRoot}/manage.py", + "args": [ + "rqscheduler", + ], + "django": true, + "cwd": "${workspaceFolder}", + "env": {}, + "console": "internalConsole" + }, { "name": "server: RQ - low", "type": "python", @@ -134,7 +150,8 @@ "test", "--settings", "cvat.settings.testing", - "cvat/apps/engine", + "cvat/apps", + "utils/cli" ], "django": true, "cwd": "${workspaceFolder}", @@ -176,6 +193,7 @@ "server: django", "server: RQ - default", "server: RQ - low", + "server: RQ - scheduler", "server: git", ] } diff --git a/.vscode/python.env b/.vscode/python.env new file mode 100644 index 00000000000..a624ab1397b --- /dev/null +++ b/.vscode/python.env @@ -0,0 +1 @@ +PYTHONPATH="datumaro/:$PYTHONPATH" \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 4cb0fb70d92..d7f4a5984ff 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,7 +3,8 @@ "eslint.enable": true, "eslint.validate": [ "javascript", - "typescript" + "typescript", + "typescriptreact", ], "eslint.workingDirectories": [ { @@ -14,10 +15,23 @@ "directory": "./cvat-canvas", "changeProcessCWD": true }, + { + "directory": "./cvat-ui", + "changeProcessCWD": true + }, { "directory": ".", "changeProcessCWD": true } ], - "python.linting.pylintEnabled": true + "python.linting.pylintEnabled": true, + "python.envFile": "${workspaceFolder}/.vscode/python.env", + "python.testing.unittestEnabled": true, + "python.testing.unittestArgs": [ + "-v", + "-s", + "./datumaro", + ], + "licenser.license": "Custom", + "licenser.customHeader": "Copyright (C) @YEAR@ Intel Corporation\n\nSPDX-License-Identifier: MIT" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b1f9e7b129..7bbc45348ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,38 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.6.0] - 2020-03-15 +### Added +- Server only support for projects. Extend REST API v1 (/api/v1/projects*) +- Ability to get basic information about users without admin permissions ([#750](https://github.com/opencv/cvat/issues/750)) +- Changed REST API: removed PUT and added DELETE methods for /api/v1/users/ID +- Mask-RCNN Auto Annotation Script in OpenVINO format +- Yolo Auto Annotation Script +- Auto segmentation using Mask_RCNN component (Keras+Tensorflow Mask R-CNN Segmentation) +- REST API to export an annotation task (images + annotations) +- [Datumaro](https://github.com/opencv/cvat/tree/develop/datumaro) - a framework to build, analyze, debug and visualize datasets +- Text Detection Auto Annotation Script in OpenVINO format for version 4 +- Added in OpenVINO Semantic Segmentation for roads +- Ability to visualize labels when using Auto Annotation runner +- MOT CSV format support ([#830](https://github.com/opencv/cvat/pull/830)) +- LabelMe format support ([#844](https://github.com/opencv/cvat/pull/844)) +- Segmentation MASK format import (as polygons) ([#1163](https://github.com/opencv/cvat/pull/1163)) +- Git repositories can be specified with IPv4 address ([#827](https://github.com/opencv/cvat/pull/827)) + +### Changed +- page_size parameter for all REST API methods +- React & Redux & Antd based dashboard +- Yolov3 interpretation script fix and changes to mapping.json +- YOLO format support ([#1151](https://github.com/opencv/cvat/pull/1151)) + +### Fixed +- Exception in Git plugin [#826](https://github.com/opencv/cvat/issues/826) +- Label ids in TFrecord format now start from 1 [#866](https://github.com/opencv/cvat/issues/866) +- Mask problem in COCO JSON style [#718](https://github.com/opencv/cvat/issues/718) +- Datasets (or tasks) can be joined and split to subsets with Datumaro [#791](https://github.com/opencv/cvat/issues/791) +- Output labels for VOC format can be specified with Datumaro [#942](https://github.com/opencv/cvat/issues/942) +- Annotations can be filtered before dumping with Datumaro [#994](https://github.com/opencv/cvat/issues/994) + ## [0.5.2] - 2019-12-15 ### Fixed - Frozen version of scikit-image==0.15 in requirements.txt because next releases don't support Python 3.5 @@ -29,6 +61,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Ability to dump/load annotations in several formats from UI (CVAT, Pascal VOC, YOLO, MS COCO, png mask, TFRecord) - Auth for REST API (api/v1/auth/): login, logout, register, ... - Preview for the new CVAT UI (dashboard only) is available: http://localhost:9080/ +- Added command line tool for performing common task operations (/utils/cli/) ### Changed - Outside and keyframe buttons in the side panel for all interpolation shapes (they were only for boxes before) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e2c6e88d7d2..a0ff41aca79 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,22 +15,24 @@ Next steps should work on clear Ubuntu 18.04. - Install necessary dependencies: ```sh -$ sudo apt-get install -y curl redis-server python3-dev python3-pip python3-venv libldap2-dev libsasl2-dev +$ sudo apt update && apt install -y nodejs npm curl redis-server python3-dev python3-pip python3-venv libldap2-dev libsasl2-dev ``` -- Install [Visual Studio Code](https://code.visualstudio.com/docs/setup/linux#_debian-and-ubuntu-based-distributions) for development +- Install [Visual Studio Code](https://code.visualstudio.com/docs/setup/linux#_debian-and-ubuntu-based-distributions) +for development -- Install CVAT on your local host: +- Install CVAT on your local host: ```sh -$ git clone https://github.com/opencv/cvat -$ cd cvat && mkdir logs keys -$ python3 -m venv .env -$ . .env/bin/activate -$ pip install -U pip wheel -$ pip install -r cvat/requirements/development.txt -$ python manage.py migrate -$ python manage.py collectstatic +git clone https://github.com/opencv/cvat +cd cvat && mkdir logs keys +python3 -m venv .env +. .env/bin/activate +pip install -U pip wheel +pip install -r cvat/requirements/development.txt +pip install -r datumaro/requirements.txt +python manage.py migrate +python manage.py collectstatic ``` - Create a super user for CVAT: @@ -43,21 +45,102 @@ Password: *** Password (again): *** ``` -- Run Visual Studio Code from the virtual environment - +- Install npm packages for UI and start UI debug server (run the following command from CVAT root directory): +```sh +npm install && \ +cd cvat-core && npm install && \ +cd ../cvat-canvas && npm install && \ +cd ../cvat-ui && npm install && npm start ``` -$ code . + +- Open new terminal (Ctrl + Shift + T), run Visual Studio Code from the virtual environment + +```sh + cd .. && source .env/bin/activate && code ``` -- Inside Visual Studio Code install [Debugger for Chrome](https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome) and [Python](https://marketplace.visualstudio.com/items?itemName=ms-python.python) extensions +- Install followig vscode extensions: + - [Debugger for Chrome](https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome) + - [Python](https://marketplace.visualstudio.com/items?itemName=ms-python.python) + - [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + - [vscode-remark-lint](https://marketplace.visualstudio.com/items?itemName=drewbourne.vscode-remark-lint) + - [licenser](https://marketplace.visualstudio.com/items?itemName=ymotongpoo.licenser) -- Reload Visual Studio Code +- Reload Visual Studio Code from virtual environment -- Select `server: debug` configuration and start debugging (F5) +- Select `server: debug` configuration and start it (F5) to run REST server and its workers You have done! Now it is possible to insert breakpoints and debug server and client of the tool. -## JavaScript coding style +## How to setup additional components in development environment + +### Automatic annotation +- Install OpenVINO on your host machine according to instructions from +[OpenVINO website](https://docs.openvinotoolkit.org/latest/index.html) +- Add some environment variables (copy code below to the end of ``.env/bin/activate`` file): +```sh + source /opt/intel/openvino/bin/setupvars.sh + + export OPENVINO_TOOLKIT="yes" + export IE_PLUGINS_PATH="/opt/intel/openvino/deployment_tools/inference_engine/lib/intel64" + export OpenCV_DIR="/usr/local/lib/cmake/opencv4" + export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:/opt/intel/openvino/inference_engine/lib/intel64" +``` + +Notice 1: be sure that these paths actually exist. Some of them can differ in different OpenVINO versions. + +Notice 2: you need to deactivate, activate again and restart vs code +to changes in ``.env/bin/activate`` file are active. + +### ReID algorithm +- Perform all steps in the automatic annotation section +- Download ReID model and save it somewhere: +```sh + curl https://download.01.org/openvinotoolkit/2018_R5/open_model_zoo/person-reidentification-retail-0079/FP32/person-reidentification-retail-0079.xml -o reid.xml + curl https://download.01.org/openvinotoolkit/2018_R5/open_model_zoo/person-reidentification-retail-0079/FP32/person-reidentification-retail-0079.bin -o reid.bin +``` +- Add next line to ``.env/bin/activate``: +```sh + export REID_MODEL_DIR="/path/to/dir" # dir must contain .xml and .bin files +``` + +### Deep Extreme Cut +- Perform all steps in the automatic annotation section +- Download Deep Extreme Cut model, unpack it, and save somewhere: +```sh +curl https://download.01.org/openvinotoolkit/models_contrib/cvat/dextr_model_v1.zip -o dextr.zip +unzip dextr.zip +``` +- Add next lines to ``.env/bin/activate``: +```sh + export WITH_DEXTR="yes" + export DEXTR_MODEL_DIR="/path/to/dir" # dir must contain .xml and .bin files +``` + +### Tensorflow RCNN +- Download RCNN model, unpack it, and save it somewhere: +```sh +curl http://download.tensorflow.org/models/object_detection/faster_rcnn_inception_resnet_v2_atrous_coco_2018_01_28.tar.gz -o model.tar.gz && \ +tar -xzf model.tar.gz +``` +- Add next lines to ``.env/bin/activate``: +```sh + export TF_ANNOTATION="yes" + export TF_ANNOTATION_MODEL_PATH="/path/to/the/model/graph" # truncate .pb extension +``` + +### Tensorflow Mask RCNN +- Download Mask RCNN model, and save it somewhere: +```sh +curl https://github.com/matterport/Mask_RCNN/releases/download/v2.0/mask_rcnn_coco.h5 -o mask_rcnn_coco.h5 +``` +- Add next lines to ``.env/bin/activate``: +```sh + export AUTO_SEGMENTATION="yes" + export AUTO_SEGMENTATION_PATH="/path/to/dir" # dir must contain mask_rcnn_coco.h5 file +``` + +## JavaScript/Typescript coding style We use the [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript) for JavaScript code with a litle exception - we prefere 4 spaces for indentation of nested blocks and statements. diff --git a/Dockerfile b/Dockerfile index a4c0e655bcf..57a9bc74c8d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,9 +22,7 @@ ENV DJANGO_CONFIGURATION=${DJANGO_CONFIGURATION} # Install necessary apt packages RUN apt-get update && \ apt-get install -yq \ - python-software-properties \ - software-properties-common \ - wget && \ + software-properties-common && \ add-apt-repository ppa:mc3man/xerus-media -y && \ add-apt-repository ppa:mc3man/gstffmpeg-keep -y && \ apt-get update && \ @@ -40,11 +38,19 @@ RUN apt-get update && \ python3-dev \ python3-pip \ tzdata \ - unzip \ - unrar \ p7zip-full \ - vim && \ - pip3 install -U setuptools && \ + git \ + ssh \ + poppler-utils \ + curl && \ + curl https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash && \ + apt-get install -y git-lfs && git lfs install && \ + if [ -z ${socks_proxy} ]; then \ + echo export "GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o ConnectTimeout=30\"" >> ${HOME}/.bashrc; \ + else \ + echo export "GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o ConnectTimeout=30 -o ProxyCommand='nc -X 5 -x ${socks_proxy} %h %p'\"" >> ${HOME}/.bashrc; \ + fi && \ + python3 -m pip install --no-cache-dir -U pip==20.0.1 setuptools && \ ln -fs /usr/share/zoneinfo/${TZ} /etc/localtime && \ dpkg-reconfigure -f noninteractive tzdata && \ add-apt-repository --remove ppa:mc3man/gstffmpeg-keep -y && \ @@ -66,8 +72,8 @@ ENV REID_MODEL_DIR=${HOME}/reid RUN if [ "$OPENVINO_TOOLKIT" = "yes" ]; then \ /tmp/components/openvino/install.sh && \ mkdir ${REID_MODEL_DIR} && \ - wget https://download.01.org/openvinotoolkit/2018_R5/open_model_zoo/person-reidentification-retail-0079/FP32/person-reidentification-retail-0079.xml -O reid/reid.xml && \ - wget https://download.01.org/openvinotoolkit/2018_R5/open_model_zoo/person-reidentification-retail-0079/FP32/person-reidentification-retail-0079.bin -O reid/reid.bin; \ + curl https://download.01.org/openvinotoolkit/2018_R5/open_model_zoo/person-reidentification-retail-0079/FP32/person-reidentification-retail-0079.xml -o reid/reid.xml && \ + curl https://download.01.org/openvinotoolkit/2018_R5/open_model_zoo/person-reidentification-retail-0079/FP32/person-reidentification-retail-0079.bin -o reid/reid.bin; \ fi # Tensorflow annotation support @@ -78,48 +84,21 @@ RUN if [ "$TF_ANNOTATION" = "yes" ]; then \ bash -i /tmp/components/tf_annotation/install.sh; \ fi -ARG WITH_TESTS -RUN if [ "$WITH_TESTS" = "yes" ]; then \ - wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \ - echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' | tee /etc/apt/sources.list.d/google-chrome.list && \ - wget -qO- https://deb.nodesource.com/setup_9.x | bash - && \ - apt-get update && \ - DEBIAN_FRONTEND=noninteractive apt-get install -yq \ - google-chrome-stable \ - nodejs && \ - rm -rf /var/lib/apt/lists/*; \ - mkdir tests && cd tests && npm install \ - eslint \ - eslint-detailed-reporter \ - karma \ - karma-chrome-launcher \ - karma-coveralls \ - karma-coverage \ - karma-junit-reporter \ - karma-qunit \ - qunit; \ - echo "export PATH=~/tests/node_modules/.bin:${PATH}" >> ~/.bashrc; \ +# Auto segmentation support. by Mohammad +ARG AUTO_SEGMENTATION +ENV AUTO_SEGMENTATION=${AUTO_SEGMENTATION} +ENV AUTO_SEGMENTATION_PATH=${HOME}/Mask_RCNN +RUN if [ "$AUTO_SEGMENTATION" = "yes" ]; then \ + bash -i /tmp/components/auto_segmentation/install.sh; \ fi # Install and initialize CVAT, copy all necessary files COPY cvat/requirements/ /tmp/requirements/ COPY supervisord.conf mod_wsgi.conf wait-for-it.sh manage.py ${HOME}/ -RUN pip3 install --no-cache-dir -r /tmp/requirements/${DJANGO_CONFIGURATION}.txt +RUN python3 -m pip install --no-cache-dir -r /tmp/requirements/${DJANGO_CONFIGURATION}.txt # pycocotools package is impossible to install with its dependencies by one pip install command -RUN pip3 install --no-cache-dir pycocotools==2.0.0 +RUN python3 -m pip install --no-cache-dir pycocotools==2.0.0 -# Install git application dependencies -RUN apt-get update && \ - apt-get install -y ssh netcat-openbsd git curl zip && \ - wget -qO /dev/stdout https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash && \ - apt-get install -y git-lfs && \ - git lfs install && \ - rm -rf /var/lib/apt/lists/* && \ - if [ -z ${socks_proxy} ]; then \ - echo export "GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o ConnectTimeout=30\"" >> ${HOME}/.bashrc; \ - else \ - echo export "GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o ConnectTimeout=30 -o ProxyCommand='nc -X 5 -x ${socks_proxy} %h %p'\"" >> ${HOME}/.bashrc; \ - fi # CUDA support ARG CUDA_SUPPORT @@ -134,14 +113,19 @@ ENV WITH_DEXTR=${WITH_DEXTR} ENV DEXTR_MODEL_DIR=${HOME}/dextr RUN if [ "$WITH_DEXTR" = "yes" ]; then \ mkdir ${DEXTR_MODEL_DIR} -p && \ - wget https://download.01.org/openvinotoolkit/models_contrib/cvat/dextr_model_v1.zip -O ${DEXTR_MODEL_DIR}/dextr.zip && \ - unzip ${DEXTR_MODEL_DIR}/dextr.zip -d ${DEXTR_MODEL_DIR} && rm ${DEXTR_MODEL_DIR}/dextr.zip; \ + curl https://download.01.org/openvinotoolkit/models_contrib/cvat/dextr_model_v1.zip -o ${DEXTR_MODEL_DIR}/dextr.zip && \ + 7z e ${DEXTR_MODEL_DIR}/dextr.zip -o${DEXTR_MODEL_DIR} && rm ${DEXTR_MODEL_DIR}/dextr.zip; \ fi COPY ssh ${HOME}/.ssh +COPY utils ${HOME}/utils COPY cvat/ ${HOME}/cvat COPY cvat-core/ ${HOME}/cvat-core COPY tests ${HOME}/tests +COPY datumaro/ ${HOME}/datumaro + +RUN python3 -m pip install --no-cache-dir -r ${HOME}/datumaro/requirements.txt + # Binary option is necessary to correctly apply the patch on Windows platform. # https://unix.stackexchange.com/questions/239364/how-to-fix-hunk-1-failed-at-1-different-line-endings-message RUN patch --binary -p1 < ${HOME}/cvat/apps/engine/static/engine/js/3rdparty.patch diff --git a/Dockerfile.ci b/Dockerfile.ci new file mode 100644 index 00000000000..e3671196ee3 --- /dev/null +++ b/Dockerfile.ci @@ -0,0 +1,32 @@ +FROM cvat + +ENV DJANGO_CONFIGURATION=testing +USER root + +RUN curl https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \ + echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' | tee /etc/apt/sources.list.d/google-chrome.list && \ + curl https://deb.nodesource.com/setup_9.x | bash - && \ + apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -yq \ + google-chrome-stable \ + nodejs && \ + rm -rf /var/lib/apt/lists/*; + +RUN python3 -m pip install --no-cache-dir -r /tmp/requirements/${DJANGO_CONFIGURATION}.txt + +# RUN all commands below as 'django' user +USER ${USER} + +RUN mkdir -p tests && cd tests && npm install \ + eslint \ + eslint-detailed-reporter \ + karma \ + karma-chrome-launcher \ + karma-coveralls \ + karma-coverage \ + karma-junit-reporter \ + karma-qunit \ + qunit; \ + echo "export PATH=~/tests/node_modules/.bin:${PATH}" >> ~/.bashrc; + +ENTRYPOINT [] \ No newline at end of file diff --git a/Dockerfile.ui b/Dockerfile.ui new file mode 100644 index 00000000000..b0bfc23c18c --- /dev/null +++ b/Dockerfile.ui @@ -0,0 +1,43 @@ +FROM node:lts-alpine AS cvat-ui + +ARG http_proxy +ARG https_proxy +ARG no_proxy +ARG socks_proxy + +ENV TERM=xterm \ + http_proxy=${http_proxy} \ + https_proxy=${https_proxy} \ + no_proxy=${no_proxy} \ + socks_proxy=${socks_proxy} + +ENV LANG='C.UTF-8' \ + LC_ALL='C.UTF-8' + +# Install dependencies +COPY cvat-core/package*.json /tmp/cvat-core/ +COPY cvat-canvas/package*.json /tmp/cvat-canvas/ +COPY cvat-ui/package*.json /tmp/cvat-ui/ + +# Install cvat-core dependencies +WORKDIR /tmp/cvat-core/ +RUN npm install + +# Install cvat-canvas dependencies +WORKDIR /tmp/cvat-canvas/ +RUN npm install + +# Install cvat-ui dependencies +WORKDIR /tmp/cvat-ui/ +RUN npm install + +# Build source code +COPY cvat-core/ /tmp/cvat-core/ +COPY cvat-canvas/ /tmp/cvat-canvas/ +COPY cvat-ui/ /tmp/cvat-ui/ +RUN npm run build + +FROM nginx:stable-alpine +# Replace default.conf configuration to remove unnecessary rules +COPY cvat-ui/react_nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=cvat-ui /tmp/cvat-ui/dist /usr/share/nginx/html/ diff --git a/README.md b/README.md index 945d9f101bb..5942c9d7c18 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ [![Codacy Badge](https://api.codacy.com/project/badge/Grade/840351da141e4eaeac6476fd19ec0a33)](https://app.codacy.com/app/cvat/cvat?utm_source=github.com&utm_medium=referral&utm_content=opencv/cvat&utm_campaign=Badge_Grade_Dashboard) [![Gitter chat](https://badges.gitter.im/opencv-cvat/gitter.png)](https://gitter.im/opencv-cvat) [![Coverage Status](https://coveralls.io/repos/github/opencv/cvat/badge.svg?branch=)](https://coveralls.io/github/opencv/cvat?branch=develop) +[![codebeat badge](https://codebeat.co/badges/53cd0d16-fddc-46f8-903c-f43ed9abb6dd)](https://codebeat.co/projects/github-com-opencv-cvat-develop) +[![DOI](https://zenodo.org/badge/139156354.svg)](https://zenodo.org/badge/latestdoi/139156354) CVAT is free, online, interactive video and image annotation tool for computer vision. It is being used by our team to annotate million of objects with different properties. Many UI and UX decisions are based on feedbacks from professional data annotation team. @@ -14,6 +16,8 @@ CVAT is free, online, interactive video and image annotation tool for computer v - [Installation guide](cvat/apps/documentation/installation.md) - [User's guide](cvat/apps/documentation/user_guide.md) - [Django REST API documentation](#rest-api) +- [Datumaro dataset framework](datumaro/README.md) +- [Command line interface](utils/cli/) - [XML annotation format](cvat/apps/documentation/xml_format.md) - [AWS Deployment Guide](cvat/apps/documentation/AWS-Deployment-Guide.md) - [Questions](#questions) @@ -31,16 +35,20 @@ CVAT is free, online, interactive video and image annotation tool for computer v ## Supported annotation formats Format selection is possible after clicking on the Upload annotation / Dump annotation button. - -| Annotation format | Dumper | Loader | -| ---------------------------------------------------------------------------------- | ------ | ------ | -| [CVAT XML v1.1 for images](cvat/apps/documentation/xml_format.md#annotation) | X | X | -| [CVAT XML v1.1 for a video](cvat/apps/documentation/xml_format.md#interpolation) | X | X | -| [Pascal VOC](http://host.robots.ox.ac.uk/pascal/VOC/) | X | X | -| [YOLO](https://pjreddie.com/darknet/yolo/) | X | X | -| [MS COCO Object Detection](http://cocodataset.org/#format-data) | X | X | -| PNG mask | X | | -| [TFrecord](https://www.tensorflow.org/tutorials/load_data/tf_records) | X | X | +[Datumaro](datumaro/README.md) dataset framework allows additional dataset transformations +via its command line tool. + +| Annotation format | Dumper | Loader | +| ------------------------------------------------------------------------------------------ | ------ | ------ | +| [CVAT XML v1.1 for images](cvat/apps/documentation/xml_format.md#annotation) | X | X | +| [CVAT XML v1.1 for a video](cvat/apps/documentation/xml_format.md#interpolation) | X | X | +| [Pascal VOC](http://host.robots.ox.ac.uk/pascal/VOC/) | X | X | +| [YOLO](https://pjreddie.com/darknet/yolo/) | X | X | +| [MS COCO Object Detection](http://cocodataset.org/#format-data) | X | X | +| PNG class mask + instance mask as in [Pascal VOC](http://host.robots.ox.ac.uk/pascal/VOC/) | X | X | +| [TFrecord](https://www.tensorflow.org/tutorials/load_data/tf_records) | X | X | +| [MOT](https://motchallenge.net/) | X | X | +| [LabelMe](http://labelme.csail.mit.edu/Release3.0) | X | X | ## Links - [Intel AI blog: New Computer Vision Tool Accelerates Annotation of Digital Images and Video](https://www.intel.ai/introducing-cvat) @@ -49,12 +57,7 @@ Format selection is possible after clicking on the Upload annotation / Dump anno ## Online Demo -[Onepanel](https://www.onepanel.io/) has added CVAT as an environment into their platform and a running demo of CVAT can be accessed at [CVAT Public Demo](https://c.onepanel.io/onepanel-demo/projects/cvat-public-demo/workspaces). - -After you click the link above: - -- Click on "GO TO WORKSPACE" and the CVAT environment will load up -- The environment is backed by a K80 GPU +[Onepanel](https://www.onepanel.io/) has added CVAT as an environment into their platform and a running demo of CVAT can be accessed at [CVAT Public Demo](https://c.onepanel.io/onepanel-demo/projects/cvat-public-demo/workspaces?utm_source=cvat). If you have any questions, please contact Onepanel directly at support@onepanel.io. If you are in the Onepanel application, you can also use the chat icon in the bottom right corner. @@ -75,7 +78,11 @@ contributors and other users. However, if you have a feature request or a bug report that can reproduced, feel free to open an issue (with steps to reproduce the bug if it's a bug -report). +report) on [GitHub* issues](https://github.com/opencv/cvat/issues). If you are not sure or just want to browse other users common questions, -[Gitter chat](https://gitter.im/opencv-cvat) is the way to go. \ No newline at end of file +[Gitter chat](https://gitter.im/opencv-cvat) is the way to go. + +Other ways to ask questions and get our support: +* [\#cvat](https://stackoverflow.com/search?q=%23cvat) tag on StackOverflow* +* [Forum on Intel Developer Zone](https://software.intel.com/en-us/forums/computer-vision) diff --git a/components/auto_segmentation/README.md b/components/auto_segmentation/README.md new file mode 100644 index 00000000000..b456857ec4c --- /dev/null +++ b/components/auto_segmentation/README.md @@ -0,0 +1,38 @@ +## [Keras+Tensorflow Mask R-CNN Segmentation](https://github.com/matterport/Mask_RCNN) + +### What is it? +- This application allows you automatically to segment many various objects on images. +- It's based on Feature Pyramid Network (FPN) and a ResNet101 backbone. + +- It uses a pre-trained model on MS COCO dataset +- It supports next classes (use them in "labels" row): +```python +'BG', 'person', 'bicycle', 'car', 'motorcycle', 'airplane', +'bus', 'train', 'truck', 'boat', 'traffic light', +'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', +'cat', 'dog', 'horse', 'sheep', 'cow', 'elephant', 'bear', +'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', +'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball', +'kite', 'baseball bat', 'baseball glove', 'skateboard', +'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup', +'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple', +'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', +'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed', +'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', +'keyboard', 'cell phone', 'microwave', 'oven', 'toaster', +'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors', +'teddy bear', 'hair drier', 'toothbrush'. +``` +- Component adds "Run Auto Segmentation" button into dashboard. + +### Build docker image +```bash +# From project root directory +docker-compose -f docker-compose.yml -f components/auto_segmentation/docker-compose.auto_segmentation.yml build +``` + +### Run docker container +```bash +# From project root directory +docker-compose -f docker-compose.yml -f components/auto_segmentation/docker-compose.auto_segmentation.yml up -d +``` diff --git a/components/auto_segmentation/docker-compose.auto_segmentation.yml b/components/auto_segmentation/docker-compose.auto_segmentation.yml new file mode 100644 index 00000000000..1d9763cdf6c --- /dev/null +++ b/components/auto_segmentation/docker-compose.auto_segmentation.yml @@ -0,0 +1,13 @@ +# +# Copyright (C) 2018 Intel Corporation +# +# SPDX-License-Identifier: MIT +# +version: "2.3" + +services: + cvat: + build: + context: . + args: + AUTO_SEGMENTATION: "yes" diff --git a/components/auto_segmentation/install.sh b/components/auto_segmentation/install.sh new file mode 100755 index 00000000000..d6881a68150 --- /dev/null +++ b/components/auto_segmentation/install.sh @@ -0,0 +1,13 @@ +#!/bin/bash +# + +set -e + +MASK_RCNN_URL=https://github.com/matterport/Mask_RCNN + +cd ${HOME} && \ +git clone ${MASK_RCNN_URL}.git && \ +curl -L ${MASK_RCNN_URL}/releases/download/v2.0/mask_rcnn_coco.h5 -o Mask_RCNN/mask_rcnn_coco.h5 + +# TODO remove useless files +# tensorflow and Keras are installed globally diff --git a/components/cuda/docker-compose.cuda.yml b/components/cuda/docker-compose.cuda.yml index 66445f12437..41d325f3f2b 100644 --- a/components/cuda/docker-compose.cuda.yml +++ b/components/cuda/docker-compose.cuda.yml @@ -15,4 +15,9 @@ services: environment: NVIDIA_VISIBLE_DEVICES: all NVIDIA_DRIVER_CAPABILITIES: compute,utility - NVIDIA_REQUIRE_CUDA: "cuda>=9.0" + # That environment variable is used by the Nvidia Container Runtime. + # The Nvidia Container Runtime parses this as: + # :space:: logical OR + # ,: Logical AND + # https://gitlab.com/nvidia/container-images/cuda/issues/31#note_149432780 + NVIDIA_REQUIRE_CUDA: "cuda>=10.0 brand=tesla,driver>=384,driver<385 brand=tesla,driver>=410,driver<411" diff --git a/components/cuda/install.sh b/components/cuda/install.sh index 2cda99fcb76..58f99acff50 100755 --- a/components/cuda/install.sh +++ b/components/cuda/install.sh @@ -14,24 +14,25 @@ echo "$NVIDIA_GPGKEY_SUM cudasign.pub" | sha256sum -c --strict - && rm cudasign echo "deb http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1604/x86_64 /" > /etc/apt/sources.list.d/cuda.list && \ echo "deb http://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu1604/x86_64 /" > /etc/apt/sources.list.d/nvidia-ml.list -CUDA_VERSION=9.0.176 -NCCL_VERSION=2.1.15 -CUDNN_VERSION=7.6.2.24 -CUDA_PKG_VERSION="9-0=${CUDA_VERSION}-1" +CUDA_VERSION=10.0.130 +NCCL_VERSION=2.5.6 +CUDNN_VERSION=7.6.5.32 +CUDA_PKG_VERSION="10-0=$CUDA_VERSION-1" echo 'export PATH=/usr/local/nvidia/bin:/usr/local/cuda/bin:${PATH}' >> ${HOME}/.bashrc echo 'export LD_LIBRARY_PATH=/usr/local/nvidia/lib:/usr/local/nvidia/lib64:${LD_LIBRARY_PATH}' >> ${HOME}/.bashrc apt-get update && apt-get install -y --no-install-recommends --allow-unauthenticated \ - libprotobuf-dev \ - libprotoc-dev \ - protobuf-compiler \ cuda-cudart-$CUDA_PKG_VERSION \ + cuda-compat-10-0 \ cuda-libraries-$CUDA_PKG_VERSION \ - libnccl2=$NCCL_VERSION-1+cuda9.0 \ - libcudnn7=$CUDNN_VERSION-1+cuda9.0 && \ - ln -s cuda-9.0 /usr/local/cuda && \ -rm -rf /var/lib/apt/lists/* \ + cuda-nvtx-$CUDA_PKG_VERSION \ + libnccl2=$NCCL_VERSION-1+cuda10.0 \ + libcudnn7=$CUDNN_VERSION-1+cuda10.0 && \ + ln -s cuda-10.0 /usr/local/cuda && \ + apt-mark hold libnccl2 libcudnn7 && \ + rm -rf /var/lib/apt/lists/* \ /etc/apt/sources.list.d/nvidia-ml.list /etc/apt/sources.list.d/cuda.list -pip3 uninstall -y tensorflow -pip3 install --no-cache-dir tensorflow-gpu==1.12.3 +python3 -m pip uninstall -y tensorflow +python3 -m pip install --no-cache-dir tensorflow-gpu==1.15.2 + diff --git a/components/openvino/README.md b/components/openvino/README.md index e800aef31bb..3b3a123b304 100644 --- a/components/openvino/README.md +++ b/components/openvino/README.md @@ -6,9 +6,12 @@ ### Preparation -* Download [OpenVINO toolkit 2018R5](https://software.intel.com/en-us/openvino-toolkit) .tgz installer (offline or online) for Ubuntu platforms. -* Put downloaded file into ```components/openvino```. -* Accept EULA in the eula.cfg file. +- Download the latest [OpenVINO toolkit](https://software.intel.com/en-us/openvino-toolkit) .tgz installer +(offline or online) for Ubuntu platforms. Note that OpenVINO does not maintain forward compatability between +Intermediate Representations (IRs), so the version of OpenVINO in CVAT and the version used to translate the +models needs to be the same. +- Put downloaded file into ```cvat/components/openvino```. +- Accept EULA in the `cvat/components/openvino/eula.cfg` file. ### Build docker image ```bash @@ -21,3 +24,22 @@ docker-compose -f docker-compose.yml -f components/openvino/docker-compose.openv # From project root directory docker-compose -f docker-compose.yml -f components/openvino/docker-compose.openvino.yml up -d ``` + +You should be able to login and see the web interface for CVAT now, complete with the new "Model Manager" button. + +### OpenVINO Models + +Clone the [Open Model Zoo](https://github.com/opencv/open_model_zoo). `$ git clone https://github.com/opencv/open_model_zoo.git` + +Install the appropriate libraries. Currently that command would be `$ pip install -r open_model_zoo/tools/downloader/requirements.in` + +Download the models using `downloader.py` file in `open_model_zoo/tools/downloader/`. +The `--name` command can be used to specify specific models. +The `--print_all` command can print all the available models. +Specific models that are already integrated into Cvat can be found [here](https://github.com/opencv/cvat/tree/develop/utils/open_model_zoo). + +From the web user interface in CVAT, upload the models using the model manager. +You'll need to include the xml and bin file from the model downloader. +You'll need to include the python and JSON files from scratch or by using the ones in the CVAT libary. +See [here](https://github.com/opencv/cvat/tree/develop/cvat/apps/auto_annotation) for instructions for creating custom +python and JSON files. diff --git a/components/tf_annotation/install.sh b/components/tf_annotation/install.sh index fc5ed6b6309..8dc034832a2 100755 --- a/components/tf_annotation/install.sh +++ b/components/tf_annotation/install.sh @@ -7,7 +7,7 @@ set -e cd ${HOME} && \ -wget -O model.tar.gz http://download.tensorflow.org/models/object_detection/faster_rcnn_inception_resnet_v2_atrous_coco_2018_01_28.tar.gz && \ +curl http://download.tensorflow.org/models/object_detection/faster_rcnn_inception_resnet_v2_atrous_coco_2018_01_28.tar.gz -o model.tar.gz && \ tar -xzf model.tar.gz && rm model.tar.gz && \ mv faster_rcnn_inception_resnet_v2_atrous_coco_2018_01_28 ${HOME}/rcnn && cd ${HOME} && \ mv rcnn/frozen_inference_graph.pb rcnn/inference_graph.pb diff --git a/cvat-canvas/README.md b/cvat-canvas/README.md index e35f47d397b..2b9f9201ba7 100644 --- a/cvat-canvas/README.md +++ b/cvat-canvas/README.md @@ -32,14 +32,15 @@ Canvas itself handles: ### API Methods ```ts - enum Rotation { - ANTICLOCKWISE90, - CLOCKWISE90, + enum RectDrawingMethod { + CLASSIC = 'By 2 points', + EXTREME_POINTS = 'By 4 points' } interface DrawData { enabled: boolean; shapeType?: string; + rectDrawingMethod?: RectDrawingMethod; numberOfPoints?: number; initialState?: any; crosshair?: boolean; @@ -70,9 +71,10 @@ Canvas itself handles: interface Canvas { html(): HTMLDivElement; + setZLayer(zLayer: number | null): void; setup(frameData: any, objectStates: any[]): void; activate(clientID: number, attributeID?: number): void; - rotate(rotation: Rotation, remember?: boolean): void; + rotate(frameAngle: number): void; focus(clientID: number, padding?: number): void; fit(): void; grid(stepX: number, stepY: number): void; @@ -83,6 +85,10 @@ Canvas itself handles: merge(mergeData: MergeData): void; select(objectState: any): void; + fitCanvas(): void; + dragCanvas(enable: boolean): void; + zoomCanvas(enable: boolean): void; + cancel(): void; } ``` @@ -99,7 +105,7 @@ Canvas itself handles: - Drawn texts have the class ```cvat_canvas_text``` - Tags have the class ```cvat_canvas_tag``` - Canvas image has ID ```cvat_canvas_image``` -- Grid on the canvas has ID ```cvat_canvas_grid_pattern``` +- Grid on the canvas has ID ```cvat_canvas_grid``` and ```cvat_canvas_grid_pattern``` - Crosshair during a draw has class ```cvat_canvas_crosshair``` ### Events @@ -107,16 +113,21 @@ Canvas itself handles: Standard JS events are used. ```js - canvas.setup - - canvas.activated => ObjectState - - canvas.deactivated + - canvas.activated => {state: ObjectState} + - canvas.clicked => {state: ObjectState} - canvas.moved => {states: ObjectState[], x: number, y: number} - canvas.find => {states: ObjectState[], x: number, y: number} - canvas.drawn => {state: DrawnData} + - canvas.editstart - canvas.edited => {state: ObjectState, points: number[]} - canvas.splitted => {state: ObjectState} - canvas.groupped => {states: ObjectState[]} - canvas.merged => {states: ObjectState[]} - canvas.canceled + - canvas.dragstart + - canvas.dragstop + - canvas.zoomstart + - canvas.zoomstop ``` ### WEB @@ -124,75 +135,39 @@ Standard JS events are used. // Create an instance of a canvas const canvas = new window.canvas.Canvas(); - // Put canvas to a html container - htmlContainer.appendChild(canvas.html()); - - // Next you can use its API methods. For example: - canvas.rotate(window.Canvas.Rotation.CLOCKWISE90); - canvas.draw({ - enabled: true, - shapeType: 'rectangle', - crosshair: true, - }); -``` - -### TypeScript -- Add to ```tsconfig.json```: -```json - "compilerOptions": { - "paths": { - "cvat-canvas.node": ["3rdparty/cvat-canvas.node"] - } - } -``` - -- ```3rdparty``` directory contains both ```cvat-canvas.node.js``` and ```cvat-canvas.node.d.ts```. -- Add alias to ```webpack.config.js```: -```js -module.exports = { - resolve: { - alias: { - 'cvat-canvas.node': path.resolve(__dirname, '3rdparty/cvat-canvas.node.js'), - } - } -} -``` - -Than you can use it in TypeScript: -```ts - import * as CANVAS from 'cvat-canvas.node'; - // Create an instance of a canvas - const canvas = new CANVAS.Canvas(); + console.log('Version', window.canvas.CanvasVersion); // Put canvas to a html container htmlContainer.appendChild(canvas.html()); + canvas.fitCanvas(); // Next you can use its API methods. For example: - canvas.rotate(CANVAS.Rotation.CLOCKWISE90); + canvas.rotate(270); canvas.draw({ enabled: true, shapeType: 'rectangle', crosshair: true, + rectDrawingMethod: window.Canvas.RectDrawingMethod.CLASSIC, }); ``` -## States - - ![](images/states.svg) - ## API Reaction -| | IDLE | GROUPING | SPLITTING | DRAWING | MERGING | EDITING | -|------------|------|----------|-----------|---------|---------|---------| -| html() | + | + | + | + | + | + | -| setup() | + | + | + | + | + | - | -| activate() | + | - | - | - | - | - | -| rotate() | + | + | + | + | + | + | -| focus() | + | + | + | + | + | + | -| fit() | + | + | + | + | + | + | -| grid() | + | + | + | + | + | + | -| draw() | + | - | - | - | - | - | -| split() | + | - | + | - | - | - | -| group | + | + | - | - | - | - | -| merge() | + | - | - | - | + | - | -| cancel() | - | + | + | + | + | + | +| | IDLE | GROUPING | SPLITTING | DRAWING | MERGING | EDITING | DRAG | ZOOM | +|--------------|------|----------|-----------|---------|---------|---------|------|------| +| html() | + | + | + | + | + | + | + | + | +| setup() | + | + | + | + | + | - | + | + | +| activate() | + | - | - | - | - | - | - | - | +| rotate() | + | + | + | + | + | + | + | + | +| focus() | + | + | + | + | + | + | + | + | +| fit() | + | + | + | + | + | + | + | + | +| grid() | + | + | + | + | + | + | + | + | +| draw() | + | - | - | - | - | - | - | - | +| split() | + | - | + | - | - | - | - | - | +| group() | + | + | - | - | - | - | - | - | +| merge() | + | - | - | - | + | - | - | - | +| fitCanvas() | + | + | + | + | + | + | + | + | +| dragCanvas() | + | - | - | - | - | - | + | - | +| zoomCanvas() | + | - | - | - | - | - | - | + | +| cancel() | - | + | + | + | + | + | + | + | +| setZLayer() | + | + | + | + | + | + | + | + | diff --git a/cvat-canvas/package-lock.json b/cvat-canvas/package-lock.json new file mode 100644 index 00000000000..46c12b74665 --- /dev/null +++ b/cvat-canvas/package-lock.json @@ -0,0 +1,10395 @@ +{ + "name": "cvat-canvas", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/cli": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.5.5.tgz", + "integrity": "sha512-UHI+7pHv/tk9g6WXQKYz+kmXTI77YtuY3vqC59KIqcoWEjsJJSG6rAxKaLsgj3LDyadsPrCB929gVOKM6Hui0w==", + "dev": true, + "requires": { + "chokidar": "^2.0.4", + "commander": "^2.8.1", + "convert-source-map": "^1.1.0", + "fs-readdir-recursive": "^1.1.0", + "glob": "^7.0.0", + "lodash": "^4.17.13", + "mkdirp": "^0.5.1", + "output-file-sync": "^2.0.0", + "slash": "^2.0.0", + "source-map": "^0.5.0" + } + }, + "@babel/code-frame": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", + "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/core": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.5.5.tgz", + "integrity": "sha512-i4qoSr2KTtce0DmkuuQBV4AuQgGPUcPXMr9L5MyYAtk06z068lQ10a4O009fe5OB/DfNV+h+qqT7ddNV8UnRjg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.5.5", + "@babel/helpers": "^7.5.5", + "@babel/parser": "^7.5.5", + "@babel/template": "^7.4.4", + "@babel/traverse": "^7.5.5", + "@babel/types": "^7.5.5", + "convert-source-map": "^1.1.0", + "debug": "^4.1.0", + "json5": "^2.1.0", + "lodash": "^4.17.13", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "json5": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", + "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "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 + } + } + }, + "@babel/generator": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.5.5.tgz", + "integrity": "sha512-ETI/4vyTSxTzGnU2c49XHv2zhExkv9JHLTwDAFz85kmcwuShvYG2H08FwgIguQf4JC75CBnXAUM5PqeF4fj0nQ==", + "dev": true, + "requires": { + "@babel/types": "^7.5.5", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz", + "integrity": "sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz", + "integrity": "sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w==", + "dev": true, + "requires": { + "@babel/helper-explode-assignable-expression": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-call-delegate": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.4.4.tgz", + "integrity": "sha512-l79boDFJ8S1c5hvQvG+rc+wHw6IuH7YldmRKsYtpbawsxURu/paVy57FZMomGK22/JckepaikOkY0MoAmdyOlQ==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.4.4", + "@babel/traverse": "^7.4.4", + "@babel/types": "^7.4.4" + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.5.5.tgz", + "integrity": "sha512-ZsxkyYiRA7Bg+ZTRpPvB6AbOFKTFFK4LrvTet8lInm0V468MWCaSYJE+I7v2z2r8KNLtYiV+K5kTCnR7dvyZjg==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-member-expression-to-functions": "^7.5.5", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.5.5", + "@babel/helper-split-export-declaration": "^7.4.4" + } + }, + "@babel/helper-define-map": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.5.5.tgz", + "integrity": "sha512-fTfxx7i0B5NJqvUOBBGREnrqbTxRh7zinBANpZXAVDlsZxYdclDp467G1sQ8VZYMnAURY3RpBUAgOYT9GfzHBg==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/types": "^7.5.5", + "lodash": "^4.17.13" + } + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz", + "integrity": "sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA==", + "dev": true, + "requires": { + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.4.4.tgz", + "integrity": "sha512-VYk2/H/BnYbZDDg39hr3t2kKyifAm1W6zHRfhx8jGjIHpQEBv9dry7oQ2f3+J703TLu69nYdxsovl0XYfcnK4w==", + "dev": true, + "requires": { + "@babel/types": "^7.4.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.5.5.tgz", + "integrity": "sha512-5qZ3D1uMclSNqYcXqiHoA0meVdv+xUEex9em2fqMnrk/scphGlGgg66zjMrPJESPwrFJ6sbfFQYUSa0Mz7FabA==", + "dev": true, + "requires": { + "@babel/types": "^7.5.5" + } + }, + "@babel/helper-module-imports": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz", + "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-module-transforms": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.5.5.tgz", + "integrity": "sha512-jBeCvETKuJqeiaCdyaheF40aXnnU1+wkSiUs/IQg3tB85up1LyL8x77ClY8qJpuRJUcXQo+ZtdNESmZl4j56Pw==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-simple-access": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/template": "^7.4.4", + "@babel/types": "^7.5.5", + "lodash": "^4.17.13" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz", + "integrity": "sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", + "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==", + "dev": true + }, + "@babel/helper-regex": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.5.5.tgz", + "integrity": "sha512-CkCYQLkfkiugbRDO8eZn6lRuR8kzZoGXCg3149iTk5se7g6qykSpy3+hELSwquhu+TgHn8nkLiBwHvNX8Hofcw==", + "dev": true, + "requires": { + "lodash": "^4.17.13" + } + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz", + "integrity": "sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-wrap-function": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-replace-supers": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.5.5.tgz", + "integrity": "sha512-XvRFWrNnlsow2u7jXDuH4jDDctkxbS7gXssrP4q2nUD606ukXHRvydj346wmNg+zAgpFx4MWf4+usfC93bElJg==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.5.5", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/traverse": "^7.5.5", + "@babel/types": "^7.5.5" + } + }, + "@babel/helper-simple-access": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz", + "integrity": "sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w==", + "dev": true, + "requires": { + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz", + "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==", + "dev": true, + "requires": { + "@babel/types": "^7.4.4" + } + }, + "@babel/helper-wrap-function": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz", + "integrity": "sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.2.0" + } + }, + "@babel/helpers": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.5.5.tgz", + "integrity": "sha512-nRq2BUhxZFnfEn/ciJuhklHvFOqjJUD5wpx+1bxUF2axL9C+v4DE/dmp5sT2dKnpOs4orZWzpAZqlCy8QqE/7g==", + "dev": true, + "requires": { + "@babel/template": "^7.4.4", + "@babel/traverse": "^7.5.5", + "@babel/types": "^7.5.5" + } + }, + "@babel/highlight": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", + "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.5.5.tgz", + "integrity": "sha512-E5BN68cqR7dhKan1SfqgPGhQ178bkVKpXTPEXnFJBrEt8/DKRZlybmy+IgYLTeN7tp1R5Ccmbm2rBk17sHYU3g==", + "dev": true + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz", + "integrity": "sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.1.0", + "@babel/plugin-syntax-async-generators": "^7.2.0" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.5.0.tgz", + "integrity": "sha512-x/iMjggsKTFHYC6g11PL7Qy58IK8H5zqfm9e6hu4z1iH2IRyAp9u9dL80zA6R76yFovETFLKz2VJIC2iIPBuFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-dynamic-import": "^7.2.0" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz", + "integrity": "sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-json-strings": "^7.2.0" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.5.5.tgz", + "integrity": "sha512-F2DxJJSQ7f64FyTVl5cw/9MWn6naXGdk3Q3UhDbFEEHv+EilCPoeRD3Zh/Utx1CJz4uyKlQ4uH+bJPbEhMV7Zw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-object-rest-spread": "^7.2.0" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz", + "integrity": "sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.2.0" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.4.4.tgz", + "integrity": "sha512-j1NwnOqMG9mFUOH58JTFsA/+ZYzQLUZ/drqWUqxCYLGeu2JFZL8YrNC9hBxKmWtAuOCHPcRpgv7fhap09Fb4kA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.4.4", + "regexpu-core": "^4.5.4" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz", + "integrity": "sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.2.0.tgz", + "integrity": "sha512-mVxuJ0YroI/h/tbFTPGZR8cv6ai+STMKNBq0f8hFxsxWjl94qqhsb+wXbpNMDPU3cfR1TIsVFzU3nXyZMqyK4w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz", + "integrity": "sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz", + "integrity": "sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz", + "integrity": "sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.3.3.tgz", + "integrity": "sha512-dGwbSMA1YhVS8+31CnPR7LB4pcbrzcV99wQzby4uAfrkZPYZlQ7ImwdpzLqi6Z6IL02b8IAL379CaMwo0x5Lag==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz", + "integrity": "sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.5.0.tgz", + "integrity": "sha512-mqvkzwIGkq0bEF1zLRRiTdjfomZJDV33AH3oQzHVGkI2VzEmXLpKKOBvEVaFZBJdN0XTyH38s9j/Kiqr68dggg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.1.0" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz", + "integrity": "sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.5.5.tgz", + "integrity": "sha512-82A3CLRRdYubkG85lKwhZB0WZoHxLGsJdux/cOVaJCJpvYFl1LVzAIFyRsa7CvXqW8rBM4Zf3Bfn8PHt5DP0Sg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "lodash": "^4.17.13" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.5.5.tgz", + "integrity": "sha512-U2htCNK/6e9K7jGyJ++1p5XRU+LJjrwtoiVn9SzRlDT2KubcZ11OOwy3s24TjHxPgxNwonCYP7U2K51uVYCMDg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-define-map": "^7.5.5", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.5.5", + "@babel/helper-split-export-declaration": "^7.4.4", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz", + "integrity": "sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.5.0.tgz", + "integrity": "sha512-YbYgbd3TryYYLGyC7ZR+Tq8H/+bCmwoaxHfJHupom5ECstzbRLTch6gOQbhEY9Z4hiCNHEURgq06ykFv9JZ/QQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.4.4.tgz", + "integrity": "sha512-P05YEhRc2h53lZDjRPk/OektxCVevFzZs2Gfjd545Wde3k+yFDbXORgl2e0xpbq8mLcKJ7Idss4fAg0zORN/zg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.4.4", + "regexpu-core": "^4.5.4" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.5.0.tgz", + "integrity": "sha512-igcziksHizyQPlX9gfSjHkE2wmoCH3evvD2qR5w29/Dk0SMKE/eOI7f1HhBdNhR/zxJDqrgpoDTq5YSLH/XMsQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz", + "integrity": "sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.4.tgz", + "integrity": "sha512-9T/5Dlr14Z9TIEXLXkt8T1DU7F24cbhwhMNUziN3hB1AXoZcdzPcTiKGRn/6iOymDqtTKWnr/BtRKN9JwbKtdQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.4.4.tgz", + "integrity": "sha512-iU9pv7U+2jC9ANQkKeNF6DrPy4GBa4NWQtl6dHB4Pb3izX2JOEvDTFarlNsBj/63ZEzNNIAMs3Qw4fNCcSOXJA==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz", + "integrity": "sha512-2ThDhm4lI4oV7fVQ6pNNK+sx+c/GM5/SaML0w/r4ZB7sAneD/piDJtwdKlNckXeyGK7wlwg2E2w33C/Hh+VFCg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.2.0.tgz", + "integrity": "sha512-HiU3zKkSU6scTidmnFJ0bMX8hz5ixC93b4MHMiYebmk2lUVNGOboPsqQvx5LzooihijUoLR/v7Nc1rbBtnc7FA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.5.0.tgz", + "integrity": "sha512-n20UsQMKnWrltocZZm24cRURxQnWIvsABPJlw/fvoy9c6AgHZzoelAIzajDHAQrDpuKFFPPcFGd7ChsYuIUMpg==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0", + "babel-plugin-dynamic-import-node": "^2.3.0" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.5.0.tgz", + "integrity": "sha512-xmHq0B+ytyrWJvQTc5OWAC4ii6Dhr0s22STOoydokG51JjWhyYo5mRPXoi+ZmtHQhZZwuXNN+GG5jy5UZZJxIQ==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.4.4", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-simple-access": "^7.1.0", + "babel-plugin-dynamic-import-node": "^2.3.0" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.5.0.tgz", + "integrity": "sha512-Q2m56tyoQWmuNGxEtUyeEkm6qJYFqs4c+XyXH5RAuYxObRNz9Zgj/1g2GMnjYp2EUyEy7YTrxliGCXzecl/vJg==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.4.4", + "@babel/helper-plugin-utils": "^7.0.0", + "babel-plugin-dynamic-import-node": "^2.3.0" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.2.0.tgz", + "integrity": "sha512-BV3bw6MyUH1iIsGhXlOK6sXhmSarZjtJ/vMiD9dNmpY8QXFFQTj+6v92pcfy1iqa8DeAfJFwoxcrS/TUZda6sw==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.4.5.tgz", + "integrity": "sha512-z7+2IsWafTBbjNsOxU/Iv5CvTJlr5w4+HGu1HovKYTtgJ362f7kBcQglkfmlspKKZ3bgrbSGvLfNx++ZJgCWsg==", + "dev": true, + "requires": { + "regexp-tree": "^0.1.6" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.4.4.tgz", + "integrity": "sha512-r1z3T2DNGQwwe2vPGZMBNjioT2scgWzK9BCnDEh+46z8EEwXBq24uRzd65I7pjtugzPSj921aM15RpESgzsSuA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.5.5.tgz", + "integrity": "sha512-un1zJQAhSosGFBduPgN/YFNvWVpRuHKU7IHBglLoLZsGmruJPOo6pbInneflUdmq7YvSVqhpPs5zdBvLnteltQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.5.5" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.4.4.tgz", + "integrity": "sha512-oMh5DUO1V63nZcu/ZVLQFqiihBGo4OpxJxR1otF50GMeCLiRx5nUdtokd+u9SuVJrvvuIh9OosRFPP4pIPnwmw==", + "dev": true, + "requires": { + "@babel/helper-call-delegate": "^7.4.4", + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.2.0.tgz", + "integrity": "sha512-9q7Dbk4RhgcLp8ebduOpCbtjh7C0itoLYHXd9ueASKAG/is5PQtMR5VJGka9NKqGhYEGn5ITahd4h9QeBMylWQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.5.tgz", + "integrity": "sha512-gBKRh5qAaCWntnd09S8QC7r3auLCqq5DI6O0DlfoyDjslSBVqBibrMdsqO+Uhmx3+BlOmE/Kw1HFxmGbv0N9dA==", + "dev": true, + "requires": { + "regenerator-transform": "^0.14.0" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.2.0.tgz", + "integrity": "sha512-fz43fqW8E1tAB3DKF19/vxbpib1fuyCwSPE418ge5ZxILnBhWyhtPgz8eh1RCGGJlwvksHkyxMxh0eenFi+kFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz", + "integrity": "sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.2.tgz", + "integrity": "sha512-KWfky/58vubwtS0hLqEnrWJjsMGaOeSBn90Ezn5Jeg9Z8KKHmELbP1yGylMlm5N6TPKeY9A2+UaSYLdxahg01w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz", + "integrity": "sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.4.4.tgz", + "integrity": "sha512-mQrEC4TWkhLN0z8ygIvEL9ZEToPhG5K7KDW3pzGqOfIGZ28Jb0POUkeWcoz8HnHvhFy6dwAT1j8OzqN8s804+g==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz", + "integrity": "sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-typescript": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.5.5.tgz", + "integrity": "sha512-pehKf4m640myZu5B2ZviLaiBlxMCjSZ1qTEO459AXKX5GnPueyulJeCqZFs1nz/Ya2dDzXQ1NxZ/kKNWyD4h6w==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.5.5", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-typescript": "^7.2.0" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.4.4.tgz", + "integrity": "sha512-il+/XdNw01i93+M9J9u4T7/e/Ue/vWfNZE4IRUQjplu2Mqb/AFTDimkw2tdEdSH50wuQXZAbXSql0UphQke+vA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.4.4", + "regexpu-core": "^4.5.4" + } + }, + "@babel/preset-env": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.5.5.tgz", + "integrity": "sha512-GMZQka/+INwsMz1A5UEql8tG015h5j/qjptpKY2gJ7giy8ohzU710YciJB5rcKsWGWHiW3RUnHib0E5/m3Tp3A==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-async-generator-functions": "^7.2.0", + "@babel/plugin-proposal-dynamic-import": "^7.5.0", + "@babel/plugin-proposal-json-strings": "^7.2.0", + "@babel/plugin-proposal-object-rest-spread": "^7.5.5", + "@babel/plugin-proposal-optional-catch-binding": "^7.2.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-syntax-async-generators": "^7.2.0", + "@babel/plugin-syntax-dynamic-import": "^7.2.0", + "@babel/plugin-syntax-json-strings": "^7.2.0", + "@babel/plugin-syntax-object-rest-spread": "^7.2.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.2.0", + "@babel/plugin-transform-arrow-functions": "^7.2.0", + "@babel/plugin-transform-async-to-generator": "^7.5.0", + "@babel/plugin-transform-block-scoped-functions": "^7.2.0", + "@babel/plugin-transform-block-scoping": "^7.5.5", + "@babel/plugin-transform-classes": "^7.5.5", + "@babel/plugin-transform-computed-properties": "^7.2.0", + "@babel/plugin-transform-destructuring": "^7.5.0", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/plugin-transform-duplicate-keys": "^7.5.0", + "@babel/plugin-transform-exponentiation-operator": "^7.2.0", + "@babel/plugin-transform-for-of": "^7.4.4", + "@babel/plugin-transform-function-name": "^7.4.4", + "@babel/plugin-transform-literals": "^7.2.0", + "@babel/plugin-transform-member-expression-literals": "^7.2.0", + "@babel/plugin-transform-modules-amd": "^7.5.0", + "@babel/plugin-transform-modules-commonjs": "^7.5.0", + "@babel/plugin-transform-modules-systemjs": "^7.5.0", + "@babel/plugin-transform-modules-umd": "^7.2.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.4.5", + "@babel/plugin-transform-new-target": "^7.4.4", + "@babel/plugin-transform-object-super": "^7.5.5", + "@babel/plugin-transform-parameters": "^7.4.4", + "@babel/plugin-transform-property-literals": "^7.2.0", + "@babel/plugin-transform-regenerator": "^7.4.5", + "@babel/plugin-transform-reserved-words": "^7.2.0", + "@babel/plugin-transform-shorthand-properties": "^7.2.0", + "@babel/plugin-transform-spread": "^7.2.0", + "@babel/plugin-transform-sticky-regex": "^7.2.0", + "@babel/plugin-transform-template-literals": "^7.4.4", + "@babel/plugin-transform-typeof-symbol": "^7.2.0", + "@babel/plugin-transform-unicode-regex": "^7.4.4", + "@babel/types": "^7.5.5", + "browserslist": "^4.6.0", + "core-js-compat": "^3.1.1", + "invariant": "^2.2.2", + "js-levenshtein": "^1.1.3", + "semver": "^5.5.0" + } + }, + "@babel/preset-typescript": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.3.3.tgz", + "integrity": "sha512-mzMVuIP4lqtn4du2ynEfdO0+RYcslwrZiJHXu4MGaC1ctJiW2fyaeDrtjJGs7R/KebZ1sgowcIoWf4uRpEfKEg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-transform-typescript": "^7.3.2" + } + }, + "@babel/template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.4.tgz", + "integrity": "sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.4.4", + "@babel/types": "^7.4.4" + } + }, + "@babel/traverse": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.5.5.tgz", + "integrity": "sha512-MqB0782whsfffYfSjH4TM+LMjrJnhCNEDMDIjeTpl+ASaUvxcjoiVCo/sM1GhS1pHOXYfWVCYneLjMckuUxDaQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.5.5", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/parser": "^7.5.5", + "@babel/types": "^7.5.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "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 + } + } + }, + "@babel/types": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.5.5.tgz", + "integrity": "sha512-s63F9nJioLqOlW3UkyMd+BYhXt44YuaFm/VV0VwuteqjYwRrObkU7ra9pY4wAJR3oXi8hJrMcrcJdO/HH33vtw==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "@csstools/convert-colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz", + "integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==", + "dev": true + }, + "@types/detect-indent": { + "version": "0.1.30", + "resolved": "https://registry.npmjs.org/@types/detect-indent/-/detect-indent-0.1.30.tgz", + "integrity": "sha1-3GgrtBK05lugmOcO2tc7SDP7kQ0=", + "dev": true + }, + "@types/eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", + "dev": true + }, + "@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", + "dev": true + }, + "@types/glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", + "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "dev": true, + "requires": { + "@types/events": "*", + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/json-schema": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.3.tgz", + "integrity": "sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==", + "dev": true + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, + "@types/mkdirp": { + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@types/mkdirp/-/mkdirp-0.3.29.tgz", + "integrity": "sha1-fyrX7FX5FEgvybHsS7GuYCjUYGY=", + "dev": true + }, + "@types/node": { + "version": "12.6.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.6.8.tgz", + "integrity": "sha512-aX+gFgA5GHcDi89KG5keey2zf0WfZk/HAQotEamsK2kbey+8yGKcson0hbK8E+v0NArlCJQCqMP161YhV6ZXLg==", + "dev": true + }, + "@typescript-eslint/eslint-plugin": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-1.13.0.tgz", + "integrity": "sha512-WQHCozMnuNADiqMtsNzp96FNox5sOVpU8Xt4meaT4em8lOG1SrOv92/mUbEHQVh90sldKSfcOc/I0FOb/14G1g==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "1.13.0", + "eslint-utils": "^1.3.1", + "functional-red-black-tree": "^1.0.1", + "regexpp": "^2.0.1", + "tsutils": "^3.7.0" + } + }, + "@typescript-eslint/experimental-utils": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz", + "integrity": "sha512-zmpS6SyqG4ZF64ffaJ6uah6tWWWgZ8m+c54XXgwFtUv0jNz8aJAVx8chMCvnk7yl6xwn8d+d96+tWp7fXzTuDg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/typescript-estree": "1.13.0", + "eslint-scope": "^4.0.0" + } + }, + "@typescript-eslint/parser": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-1.13.0.tgz", + "integrity": "sha512-ITMBs52PCPgLb2nGPoeT4iU3HdQZHcPaZVw+7CsFagRJHUhyeTgorEwHXhFf3e7Evzi8oujKNpHc8TONth8AdQ==", + "dev": true, + "requires": { + "@types/eslint-visitor-keys": "^1.0.0", + "@typescript-eslint/experimental-utils": "1.13.0", + "@typescript-eslint/typescript-estree": "1.13.0", + "eslint-visitor-keys": "^1.0.0" + } + }, + "@typescript-eslint/typescript-estree": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz", + "integrity": "sha512-b5rCmd2e6DCC6tCTN9GSUAuxdYwCM/k/2wdjHGrIRGPSJotWMCe/dGpi66u42bhuh8q3QBzqM4TMA1GUUCJvdw==", + "dev": true, + "requires": { + "lodash.unescape": "4.0.1", + "semver": "5.5.0" + }, + "dependencies": { + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "dev": true + } + } + }, + "@webassemblyjs/ast": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", + "integrity": "sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ==", + "dev": true, + "requires": { + "@webassemblyjs/helper-module-context": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/wast-parser": "1.8.5" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz", + "integrity": "sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz", + "integrity": "sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz", + "integrity": "sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q==", + "dev": true + }, + "@webassemblyjs/helper-code-frame": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz", + "integrity": "sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ==", + "dev": true, + "requires": { + "@webassemblyjs/wast-printer": "1.8.5" + } + }, + "@webassemblyjs/helper-fsm": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz", + "integrity": "sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow==", + "dev": true + }, + "@webassemblyjs/helper-module-context": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz", + "integrity": "sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "mamacro": "^0.0.3" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz", + "integrity": "sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz", + "integrity": "sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-buffer": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/wasm-gen": "1.8.5" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz", + "integrity": "sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.8.5.tgz", + "integrity": "sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.8.5.tgz", + "integrity": "sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz", + "integrity": "sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-buffer": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/helper-wasm-section": "1.8.5", + "@webassemblyjs/wasm-gen": "1.8.5", + "@webassemblyjs/wasm-opt": "1.8.5", + "@webassemblyjs/wasm-parser": "1.8.5", + "@webassemblyjs/wast-printer": "1.8.5" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz", + "integrity": "sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/ieee754": "1.8.5", + "@webassemblyjs/leb128": "1.8.5", + "@webassemblyjs/utf8": "1.8.5" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz", + "integrity": "sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-buffer": "1.8.5", + "@webassemblyjs/wasm-gen": "1.8.5", + "@webassemblyjs/wasm-parser": "1.8.5" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz", + "integrity": "sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-api-error": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/ieee754": "1.8.5", + "@webassemblyjs/leb128": "1.8.5", + "@webassemblyjs/utf8": "1.8.5" + } + }, + "@webassemblyjs/wast-parser": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz", + "integrity": "sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/floating-point-hex-parser": "1.8.5", + "@webassemblyjs/helper-api-error": "1.8.5", + "@webassemblyjs/helper-code-frame": "1.8.5", + "@webassemblyjs/helper-fsm": "1.8.5", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz", + "integrity": "sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/wast-parser": "1.8.5", + "@xtuc/long": "4.2.2" + } + }, + "@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 + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dev": true, + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "acorn": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.2.1.tgz", + "integrity": "sha512-JD0xT5FCRDNyjDda3Lrg/IxFscp9q4tiYtxE1/nOzlKCk7hIRuYjhq1kCNkbPjMRMZuFq20HNQn1I9k8Oj0E+Q==", + "dev": true + }, + "acorn-jsx": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", + "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", + "dev": true + }, + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-errors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", + "dev": true + }, + "ajv-keywords": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", + "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", + "dev": true + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true + }, + "ansi-align": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", + "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", + "dev": true, + "requires": { + "string-width": "^2.0.0" + } + }, + "ansi-colors": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", + "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", + "dev": true + }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, + "ansi-html": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", + "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "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": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "dev": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true + }, + "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 + }, + "array-includes": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", + "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.7.0" + } + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", + "dev": true, + "requires": { + "object-assign": "^4.1.1", + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true + }, + "async-foreach": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", + "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "autoprefixer": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.7.3.tgz", + "integrity": "sha512-8T5Y1C5Iyj6PgkPSFd0ODvK9DIleuPKUPYniNxybS47g2k2wFgLZ46lGQHlBuGKIAEV8fbCDfKCCRS1tvOgc3Q==", + "dev": true, + "requires": { + "browserslist": "^4.8.0", + "caniuse-lite": "^1.0.30001012", + "chalk": "^2.4.2", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^7.0.23", + "postcss-value-parser": "^4.0.2" + }, + "dependencies": { + "browserslist": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.2.tgz", + "integrity": "sha512-+M4oeaTplPm/f1pXDw84YohEv7B1i/2Aisei8s4s6k3QsoSHa7i5sz8u/cGQkkatCPxMASKxPualR4wwYgVboA==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001015", + "electron-to-chromium": "^1.3.322", + "node-releases": "^1.1.42" + } + }, + "caniuse-lite": { + "version": "1.0.30001016", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001016.tgz", + "integrity": "sha512-yYQ2QfotceRiH4U+h1Us86WJXtVHDmy3nEKIdYPsZCYnOV5/tMgGbmoIlrMzmh2VXlproqYtVaKeGDBkMZifFA==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.322", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.322.tgz", + "integrity": "sha512-Tc8JQEfGQ1MzfSzI/bTlSr7btJv/FFO7Yh6tanqVmIWOuNCu6/D1MilIEgLtmWqIrsv+o4IjpLAhgMBr/ncNAA==", + "dev": true + }, + "node-releases": { + "version": "1.1.44", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.44.tgz", + "integrity": "sha512-NwbdvJyR7nrcGrXvKAvzc5raj/NkoJudkarh2yIpJ4t0NH4aqjUDz/486P+ynIW5eokKOfzGNRdYoLfBlomruw==", + "dev": true, + "requires": { + "semver": "^6.3.0" + } + }, + "postcss": { + "version": "7.0.25", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.25.tgz", + "integrity": "sha512-NXXVvWq9icrm/TgQC0O6YVFi4StfJz46M1iNd/h6B26Nvh/HKI+q4YZtFN/EjcInZliEscO/WL10BXnc1E5nwg==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "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 + } + } + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.0.tgz", + "integrity": "sha512-Uvq6hVe90D0B2WEnUqtdgY1bATGz3mw33nH9Y+dmA+w5DHvUmBgkr5rM/KCHpCsiFNRUfokW/szpPPgMK2hm4A==", + "dev": true + }, + "babel-loader": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.6.tgz", + "integrity": "sha512-4BmWKtBOBm13uoUwd08UwjZlaw3O9GWf456R9j+5YykFZ6LUIjIKLc0zEZf+hauxPOJs96C8k6FvYD09vWzhYw==", + "dev": true, + "requires": { + "find-cache-dir": "^2.0.0", + "loader-utils": "^1.0.2", + "mkdirp": "^0.5.1", + "pify": "^4.0.1" + } + }, + "babel-plugin-dynamic-import-node": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz", + "integrity": "sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==", + "dev": true, + "requires": { + "object.assign": "^4.1.0" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "base64-js": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", + "dev": true + }, + "batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "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": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true + }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "dev": true, + "requires": { + "inherits": "~2.0.0" + } + }, + "bluebird": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", + "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==", + "dev": true + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "dev": true + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "dev": true, + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "dependencies": { + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true + } + } + }, + "bonjour": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", + "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", + "dev": true, + "requires": { + "array-flatten": "^2.1.0", + "deep-equal": "^1.0.1", + "dns-equal": "^1.0.0", + "dns-txt": "^2.0.2", + "multicast-dns": "^6.0.1", + "multicast-dns-service-types": "^1.1.0" + } + }, + "boxen": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", + "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", + "dev": true, + "requires": { + "ansi-align": "^2.0.0", + "camelcase": "^4.0.0", + "chalk": "^2.0.1", + "cli-boxes": "^1.0.0", + "string-width": "^2.0.0", + "term-size": "^1.2.0", + "widest-line": "^2.0.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + } + } + }, + "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": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" + } + }, + "browserify-sign": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "dev": true, + "requires": { + "bn.js": "^4.1.1", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.2", + "elliptic": "^6.0.0", + "inherits": "^2.0.1", + "parse-asn1": "^5.0.0" + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "requires": { + "pako": "~1.0.5" + } + }, + "browserslist": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.6.6.tgz", + "integrity": "sha512-D2Nk3W9JL9Fp/gIcWei8LrERCS+eXu9AM5cfXA8WEZ84lFks+ARnZ0q/R69m2SV3Wjma83QDDPxsNKXUwdIsyA==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30000984", + "electron-to-chromium": "^1.3.191", + "node-releases": "^1.1.25" + } + }, + "buffer": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "buffer-indexof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", + "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", + "dev": true + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "dev": true + }, + "cacache": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.3.tgz", + "integrity": "sha512-p8WcneCytvzPxhDvYp31PD039vi77I12W+/KfR9S8AZbaiARFBCpsPJS+9uhWfeBfeAtW7o/4vt3MUqLkbY6nA==", + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "dev": true, + "requires": { + "callsites": "^2.0.0" + }, + "dependencies": { + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", + "dev": true + } + } + }, + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "dev": true, + "requires": { + "caller-callsite": "^2.0.0" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true + } + } + }, + "caniuse-lite": { + "version": "1.0.30000985", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000985.tgz", + "integrity": "sha512-1ngiwkgqAYPG0JSSUp3PUDGPKKY59EK7NrGGX+VOxaKCNzRbNc7uXMny+c3VJfZxtoK3wSImTvG9T9sXiTw2+w==", + "dev": true + }, + "capture-stack-trace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", + "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "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" + }, + "dependencies": { + "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" + } + } + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "chokidar": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.6.tgz", + "integrity": "sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "chownr": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz", + "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==", + "dev": true + }, + "chrome-trace-event": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", + "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "ci-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", + "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", + "dev": true + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "cli-boxes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", + "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", + "dev": true + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "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" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.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": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "compressible": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.17.tgz", + "integrity": "sha512-BGHeLCK1GV7j1bSmQQAi26X+GgWcTjLr/0tzSvMCl3LH1w1IJ4PFSPoV5316b30cneTziC+B1a+3OjoSUcQYmw==", + "dev": true, + "requires": { + "mime-db": ">= 1.40.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" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "configstore": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", + "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", + "dev": true, + "requires": { + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" + }, + "dependencies": { + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, + "confusing-browser-globals": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.7.tgz", + "integrity": "sha512-cgHI1azax5ATrZ8rJ+ODDML9Fvu67PimB6aNxBrc/QwSaDaM9eTfIEUHx3bBLJJ82ioSb+/5zfsMCCEJax3ByQ==", + "dev": true + }, + "connect-history-api-fallback": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", + "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", + "dev": true + }, + "console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "dev": true, + "requires": { + "date-now": "^0.1.4" + } + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "dev": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true + }, + "copy-concurrently": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "core-js-compat": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.1.4.tgz", + "integrity": "sha512-Z5zbO9f1d0YrJdoaQhphVAnKPimX92D6z8lCGphH89MNRxlL1prI9ExJPqVwP0/kgkQCv8c4GJGT8X16yUncOg==", + "dev": true, + "requires": { + "browserslist": "^4.6.2", + "core-js-pure": "3.1.4", + "semver": "^6.1.1" + }, + "dependencies": { + "semver": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.2.0.tgz", + "integrity": "sha512-jdFC1VdUGT/2Scgbimf7FSx9iJLXoqfglSF+gJeuNWVpiE37OIbc1jywR/GJyFdz3mnkz2/id0L0J/cr0izR5A==", + "dev": true + } + } + }, + "core-js-pure": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.1.4.tgz", + "integrity": "sha512-uJ4Z7iPNwiu1foygbcZYJsJs1jiXrTTCvxfLDXNhI/I+NHbSIEyr548y4fcsCEyWY0XgfAG/qqaunJ1SThHenA==", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + }, + "dependencies": { + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "dev": true, + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + } + } + }, + "create-ecdh": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", + "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.0.0" + } + }, + "create-error-class": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", + "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", + "dev": true, + "requires": { + "capture-stack-trace": "^1.0.0" + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "crypto-random-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", + "dev": true + }, + "css-blank-pseudo": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz", + "integrity": "sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w==", + "dev": true, + "requires": { + "postcss": "^7.0.5" + } + }, + "css-has-pseudo": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz", + "integrity": "sha512-Z8hnfsZu4o/kt+AuFzeGpLVhFOGO9mluyHBaA2bA8aCGTwah5sT3WV/fTHH8UNZUytOIImuGPrl/prlb4oX4qQ==", + "dev": true, + "requires": { + "postcss": "^7.0.6", + "postcss-selector-parser": "^5.0.0-rc.4" + }, + "dependencies": { + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", + "dev": true + }, + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "dev": true, + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "css-loader": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.2.0.tgz", + "integrity": "sha512-QTF3Ud5H7DaZotgdcJjGMvyDj5F3Pn1j/sC6VBEOVp94cbwqyIBdcs/quzj4MC1BKQSrTpQznegH/5giYbhnCQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "cssesc": "^3.0.0", + "icss-utils": "^4.1.1", + "loader-utils": "^1.2.3", + "normalize-path": "^3.0.0", + "postcss": "^7.0.17", + "postcss-modules-extract-imports": "^2.0.0", + "postcss-modules-local-by-default": "^3.0.2", + "postcss-modules-scope": "^2.1.0", + "postcss-modules-values": "^3.0.0", + "postcss-value-parser": "^4.0.0", + "schema-utils": "^2.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.2.0.tgz", + "integrity": "sha512-5EwsCNhfFTZvUreQhx/4vVQpJ/lnCAkgoIHLhSpp4ZirE+4hzFvdJi0FMub6hxbFVBJYSpeVVmon+2e7uEGRrA==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1" + } + } + } + }, + "css-prefers-color-scheme": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz", + "integrity": "sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg==", + "dev": true, + "requires": { + "postcss": "^7.0.5" + } + }, + "cssdb": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-4.4.0.tgz", + "integrity": "sha512-LsTAR1JPEM9TpGhl/0p3nQecC2LJ0kD8X5YARu1hk/9I1gril5vDtMZyNxcEpxxDj34YNck/ucjuoUd66K03oQ==", + "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 + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "requires": { + "array-find-index": "^1.0.1" + } + }, + "cyclist": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", + "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=", + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", + "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" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "dev": true + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "default-gateway": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", + "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "ip-regex": "^2.1.0" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "del": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", + "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", + "dev": true, + "requires": { + "@types/glob": "^7.1.1", + "globby": "^6.1.0", + "is-path-cwd": "^2.0.0", + "is-path-in-cwd": "^2.0.0", + "p-map": "^2.0.0", + "pify": "^4.0.1", + "rimraf": "^2.6.3" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "des.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", + "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true + }, + "detect-indent": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-0.2.0.tgz", + "integrity": "sha1-BCkUSYl5rC2fPHPk/z5od9O8krY=", + "dev": true, + "requires": { + "get-stdin": "^0.1.0", + "minimist": "^0.1.0" + }, + "dependencies": { + "minimist": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.1.0.tgz", + "integrity": "sha1-md9lelJXTCHJBXSX33QnkLK0wN4=", + "dev": true + } + } + }, + "detect-node": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", + "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==", + "dev": true + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", + "dev": true + }, + "dns-packet": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", + "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", + "dev": true, + "requires": { + "ip": "^1.1.0", + "safe-buffer": "^5.0.1" + } + }, + "dns-txt": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", + "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", + "dev": true, + "requires": { + "buffer-indexof": "^1.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true + }, + "dot-prop": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", + "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", + "dev": true, + "requires": { + "is-obj": "^1.0.0" + } + }, + "dts-bundle": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/dts-bundle/-/dts-bundle-0.7.3.tgz", + "integrity": "sha1-Nyt7tpyCB4LmOC9ABzmmnc7T1Zo=", + "dev": true, + "requires": { + "@types/detect-indent": "0.1.30", + "@types/glob": "5.0.30", + "@types/mkdirp": "0.3.29", + "@types/node": "8.0.0", + "commander": "^2.9.0", + "detect-indent": "^0.2.0", + "glob": "^6.0.4", + "mkdirp": "^0.5.0" + }, + "dependencies": { + "@types/glob": { + "version": "5.0.30", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-5.0.30.tgz", + "integrity": "sha1-ECZAnFYlqGiQdGAoCNCCsoZ7ilE=", + "dev": true, + "requires": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/node": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.0.tgz", + "integrity": "sha512-j2tekvJCO7j22cs+LO6i0kRPhmQ9MXaPZ55TzOc1lzkN5b6BWqq4AFjl04s1oRRQ1v5rSe+KEvnLUSTonuls/A==", + "dev": true + }, + "glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "dts-bundle-webpack": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/dts-bundle-webpack/-/dts-bundle-webpack-1.0.2.tgz", + "integrity": "sha512-/gBQBu5spW8BsGKyYwZeDb+gzDsipisf4Hg0ERPrrS0661cYajVUHARwvts/vfvG5wuv+p295byoNl2da+Re6w==", + "dev": true, + "requires": { + "dts-bundle": "^0.7.3" + } + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.199", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.199.tgz", + "integrity": "sha512-gachlDdHSK47s0N2e58GH9HMC6Z4ip0SfmYUa5iEbE50AKaOUXysaJnXMfKj0xB245jWbYcyFSH+th3rqsF8hA==", + "dev": true + }, + "elliptic": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.0.tgz", + "integrity": "sha512-eFOJTMyCYb7xtE/caJ6JJu+bhi67WCYNbkGSknu20pmM8Ke/bqOfdnZWxyoGN26JgfxTbXrsCkEw4KheCT/KGg==", + "dev": true, + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "enhanced-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz", + "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.4.0", + "tapable": "^1.0.0" + } + }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "dev": true, + "requires": { + "prr": "~1.0.1" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", + "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-keys": "^1.0.12" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "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": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.1.0.tgz", + "integrity": "sha512-QhrbdRD7ofuV09IuE2ySWBz0FyXCq0rriLTZXZqaWSI79CVtHVRdkFuFTViiqzZhkCgfOh9USpriuGN2gIpZDQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^6.0.0", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^6.4.1", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "eslint-scope": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", + "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "glob-parent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.0.0.tgz", + "integrity": "sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "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 + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "strip-json-comments": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", + "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "dev": true + } + } + }, + "eslint-config-airbnb": { + "version": "17.1.1", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-17.1.1.tgz", + "integrity": "sha512-xCu//8a/aWqagKljt+1/qAM62BYZeNq04HmdevG5yUGWpja0I/xhqd6GdLRch5oetEGFiJAnvtGuTEAese53Qg==", + "dev": true, + "requires": { + "eslint-config-airbnb-base": "^13.2.0", + "object.assign": "^4.1.0", + "object.entries": "^1.1.0" + } + }, + "eslint-config-airbnb-base": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-13.2.0.tgz", + "integrity": "sha512-1mg/7eoB4AUeB0X1c/ho4vb2gYkNH8Trr/EgCT/aGmKhhG+F6vF5s8+iRBlWAzFIAphxIdp3YfEKgEl0f9Xg+w==", + "dev": true, + "requires": { + "confusing-browser-globals": "^1.0.5", + "object.assign": "^4.1.0", + "object.entries": "^1.1.0" + } + }, + "eslint-config-airbnb-typescript": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-4.0.1.tgz", + "integrity": "sha512-4LHD0O0X1e08k+e8AngAsKPYNc7nL+5PzK7OEl9qZ6d7C+wo8BN2fMxBhhiUmRggJxArrldp7Dgb1s2f1/Robg==", + "dev": true, + "requires": { + "@typescript-eslint/parser": "^1.11.0", + "eslint-config-airbnb": "^17.1.0", + "eslint-config-airbnb-base": "^13.1.0" + } + }, + "eslint-config-typescript-recommended": { + "version": "1.4.17", + "resolved": "https://registry.npmjs.org/eslint-config-typescript-recommended/-/eslint-config-typescript-recommended-1.4.17.tgz", + "integrity": "sha512-mFtQb5Fp+F5CMKmFV75gyEF47nXz7QWvoV525dVUUmpAl6YAMDbGbH7eCw/qZS4rYuwE7m3SFwhNHd6RbAa/Sg==", + "dev": true + }, + "eslint-import-resolver-node": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", + "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", + "dev": true, + "requires": { + "debug": "^2.6.9", + "resolve": "^1.5.0" + } + }, + "eslint-module-utils": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.4.1.tgz", + "integrity": "sha512-H6DOj+ejw7Tesdgbfs4jeS4YMFrT8uI8xwd1gtQqXssaR0EQ26L+2O/w6wkYFy2MymON0fTwHmXBvvfLNZVZEw==", + "dev": true, + "requires": { + "debug": "^2.6.8", + "pkg-dir": "^2.0.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + } + } + }, + "eslint-plugin-import": { + "version": "2.18.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.18.2.tgz", + "integrity": "sha512-5ohpsHAiUBRNaBWAF08izwUGlbrJoJJ+W9/TBwsGoR1MnlgfwMIKrFeSjWbt6moabiXW9xNvtFz+97KHRfI4HQ==", + "dev": true, + "requires": { + "array-includes": "^3.0.3", + "contains-path": "^0.1.0", + "debug": "^2.6.9", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.2", + "eslint-module-utils": "^2.4.0", + "has": "^1.0.3", + "minimatch": "^3.0.4", + "object.values": "^1.1.0", + "read-pkg-up": "^2.0.0", + "resolve": "^1.11.0" + }, + "dependencies": { + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + } + } + } + }, + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", + "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "dev": true + }, + "espree": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.0.0.tgz", + "integrity": "sha512-lJvCS6YbCn3ImT3yKkPe0+tJ+mH6ljhGNjHQH9mRtiO6gjhVAOhVXW1yjnwqGwTkK3bGbye+hb00nFNmu0l/1Q==", + "dev": true, + "requires": { + "acorn": "^6.0.7", + "acorn-jsx": "^5.0.0", + "eslint-visitor-keys": "^1.0.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "dev": true, + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true + }, + "eventemitter3": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", + "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", + "dev": true + }, + "events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", + "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==", + "dev": true + }, + "eventsource": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz", + "integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==", + "dev": true, + "requires": { + "original": "^1.0.0" + } + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "dev": true, + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "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": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "dev": true + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "figgy-pudding": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", + "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==", + "dev": true + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "findup-sync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", + "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "dev": true, + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + } + }, + "flatted": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", + "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", + "dev": true + }, + "flatten": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.3.tgz", + "integrity": "sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==", + "dev": true + }, + "flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } + }, + "follow-redirects": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.7.0.tgz", + "integrity": "sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==", + "dev": true, + "requires": { + "debug": "^3.2.6" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "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 + } + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "dev": true + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "fs-readdir-recursive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", + "dev": true + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", + "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.12.1", + "node-pre-gyp": "^0.12.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true, + "optional": true + }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.3.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^4.1.0", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.12.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": 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" + } + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.7.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "gaze": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", + "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", + "dev": true, + "requires": { + "globule": "^1.0.0" + } + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "get-stdin": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-0.1.0.tgz", + "integrity": "sha1-WZivJKr8gC0VyCxoVlfuuLENSpE=", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", + "dev": true, + "requires": { + "ini": "^1.3.4" + } + }, + "global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dev": true, + "requires": { + "global-prefix": "^3.0.0" + }, + "dependencies": { + "global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dev": true, + "requires": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + } + } + } + }, + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + } + }, + "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 + }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "globule": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.0.tgz", + "integrity": "sha512-YlD4kdMqRCQHrhVdonet4TdRtv1/sZKepvoxNT4Nrhrp5HI8XFfc8kFlGlBn2myBo80aGp8Eft259mbcUJhgSg==", + "dev": true, + "requires": { + "glob": "~7.1.1", + "lodash": "~4.17.10", + "minimatch": "~3.0.2" + } + }, + "got": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", + "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", + "dev": true, + "requires": { + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" + }, + "dependencies": { + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + } + } + }, + "graceful-fs": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.0.tgz", + "integrity": "sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==", + "dev": true + }, + "handle-thing": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.0.tgz", + "integrity": "sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "requires": { + "parse-passwd": "^1.0.0" + } + }, + "hosted-git-info": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.2.tgz", + "integrity": "sha512-CyjlXII6LMsPMyUzxpTt8fzh5QwzGqPmQXgY/Jyf4Zfp27t/FvfhwoE/8laaMUcMy816CkWF20I7NeQhwwY88w==", + "dev": true, + "requires": { + "lru-cache": "^5.1.1" + } + }, + "hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "html-entities": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", + "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=", + "dev": true + }, + "http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", + "dev": true + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + } + } + }, + "http-parser-js": { + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz", + "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=", + "dev": true + }, + "http-proxy": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", + "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", + "dev": true, + "requires": { + "eventemitter3": "^3.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-proxy-middleware": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", + "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", + "dev": true, + "requires": { + "http-proxy": "^1.17.0", + "is-glob": "^4.0.0", + "lodash": "^4.17.11", + "micromatch": "^3.1.10" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "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" + } + }, + "icss-utils": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz", + "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==", + "dev": true, + "requires": { + "postcss": "^7.0.14" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "dev": true + }, + "iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", + "dev": true + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", + "dev": true + }, + "import-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", + "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", + "dev": true, + "requires": { + "import-from": "^2.1.0" + } + }, + "import-fresh": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.1.0.tgz", + "integrity": "sha512-PpuksHKGt8rXfWEr9m9EHIpgyyaltBy8+eF6GJM0QCAxMgxCfucMF3mjecK2QsJr0amJW7gTqh5/wht0z2UhEQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + } + } + }, + "import-from": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", + "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + } + }, + "import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", + "dev": true + }, + "import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "dev": true, + "requires": { + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "in-publish": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz", + "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=", + "dev": true + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "indexes-of": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", + "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "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==", + "dev": true + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, + "inquirer": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.0.tgz", + "integrity": "sha512-scfHejeG/lVZSpvCXpsB4j/wQNPM5JC8kiElOI0OUTwmc1RTpXr4H32/HOlQHcZiYl2z2VElwuCVDRG8vFmbnA==", + "dev": true, + "requires": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.12", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "internal-ip": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", + "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==", + "dev": true, + "requires": { + "default-gateway": "^4.2.0", + "ipaddr.js": "^1.9.0" + } + }, + "interpret": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", + "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", + "dev": true + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "dev": true + }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true + }, + "ipaddr.js": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", + "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==", + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, + "is-ci": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", + "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", + "dev": true, + "requires": { + "ci-info": "^1.5.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", + "dev": true + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-installed-globally": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", + "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", + "dev": true, + "requires": { + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" + }, + "dependencies": { + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "^1.0.1" + } + } + } + }, + "is-npm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", + "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "dev": true + }, + "is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true + }, + "is-path-in-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", + "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", + "dev": true, + "requires": { + "is-path-inside": "^2.1.0" + } + }, + "is-path-inside": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", + "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", + "dev": true, + "requires": { + "path-is-inside": "^1.0.2" + } + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "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-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", + "dev": true + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-retry-allowed": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", + "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=", + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "js-base64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz", + "integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==", + "dev": true + }, + "js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "dev": true + }, + "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 + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "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-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "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 + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "json3": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz", + "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==", + "dev": true + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "killable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", + "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "latest-version": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", + "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", + "dev": true, + "requires": { + "package-json": "^4.0.0" + } + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "loader-runner": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", + "dev": true + }, + "loader-utils": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", + "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^2.0.0", + "json5": "^1.0.1" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", + "dev": true + }, + "lodash.template": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", + "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", + "dev": true, + "requires": { + "lodash._reinterpolate": "^3.0.0", + "lodash.templatesettings": "^4.0.0" + } + }, + "lodash.templatesettings": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", + "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", + "dev": true, + "requires": { + "lodash._reinterpolate": "^3.0.0" + } + }, + "lodash.unescape": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz", + "integrity": "sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=", + "dev": true + }, + "loglevel": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.3.tgz", + "integrity": "sha512-LoEDv5pgpvWgPF4kNYuIp0qqSJVWak/dML0RY74xlzMZiT9w77teNAwKYKWBTYjlokMirg+o3jBwp+vlLrcfAA==", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "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": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "mamacro": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz", + "integrity": "sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==", + "dev": true + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + } + }, + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + } + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.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.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", + "dev": true + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "dev": true, + "requires": { + "mime-db": "1.40.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 + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "move-concurrently": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "multicast-dns": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", + "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", + "dev": true, + "requires": { + "dns-packet": "^1.3.1", + "thunky": "^1.0.2" + } + }, + "multicast-dns-service-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", + "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "dev": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "dev": true + }, + "neo-async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node-forge": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz", + "integrity": "sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ==", + "dev": true + }, + "node-gyp": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", + "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==", + "dev": true, + "requires": { + "fstream": "^1.0.0", + "glob": "^7.0.3", + "graceful-fs": "^4.1.2", + "mkdirp": "^0.5.0", + "nopt": "2 || 3", + "npmlog": "0 || 1 || 2 || 3 || 4", + "osenv": "0", + "request": "^2.87.0", + "rimraf": "2", + "semver": "~5.3.0", + "tar": "^2.0.0", + "which": "1" + }, + "dependencies": { + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "dev": true + } + } + }, + "node-libs-browser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", + "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", + "dev": true, + "requires": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.1", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.11.0", + "vm-browserify": "^1.0.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "node-releases": { + "version": "1.1.25", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.25.tgz", + "integrity": "sha512-fI5BXuk83lKEoZDdH3gRhtsNgh05/wZacuXkgbiYkceE7+QIMXOg98n9ZV7mz27B+kFHnqHcUpscZZlGRSmTpQ==", + "dev": true, + "requires": { + "semver": "^5.3.0" + } + }, + "node-sass": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.13.0.tgz", + "integrity": "sha512-W1XBrvoJ1dy7VsvTAS5q1V45lREbTlZQqFbiHb3R3OTTCma0XBtuG6xZ6Z4506nR4lmHPTqVRwxT6KgtWC97CA==", + "dev": true, + "requires": { + "async-foreach": "^0.1.3", + "chalk": "^1.1.1", + "cross-spawn": "^3.0.0", + "gaze": "^1.0.0", + "get-stdin": "^4.0.1", + "glob": "^7.0.3", + "in-publish": "^2.0.0", + "lodash": "^4.17.15", + "meow": "^3.7.0", + "mkdirp": "^0.5.1", + "nan": "^2.13.2", + "node-gyp": "^3.8.0", + "npmlog": "^4.0.0", + "request": "^2.88.0", + "sass-graph": "^2.2.4", + "stdout-stream": "^1.4.0", + "true-case-path": "^1.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "cross-spawn": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", + "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + } + } + }, + "nodemon": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.19.1.tgz", + "integrity": "sha512-/DXLzd/GhiaDXXbGId5BzxP1GlsqtMGM9zTmkWrgXtSqjKmGSbLicM/oAy4FR0YWm14jCHRwnR31AHS2dYFHrg==", + "dev": true, + "requires": { + "chokidar": "^2.1.5", + "debug": "^3.1.0", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.0.4", + "pstree.remy": "^1.1.6", + "semver": "^5.5.0", + "supports-color": "^5.2.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.2", + "update-notifier": "^2.5.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "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 + }, + "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" + } + } + } + }, + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "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 + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", + "dev": true + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "num2fraction": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", + "dev": true + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.entries": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz", + "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "object.values": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz", + "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "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.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "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": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + }, + "dependencies": { + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + } + } + }, + "opn": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", + "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", + "dev": true, + "requires": { + "is-wsl": "^1.1.0" + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + } + }, + "original": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", + "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", + "dev": true, + "requires": { + "url-parse": "^1.4.3" + } + }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", + "dev": true + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "output-file-sync": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-2.0.1.tgz", + "integrity": "sha512-mDho4qm7WgIXIGf4eYU1RHN2UU5tPfVYVSRwDJw0uTmj35DQUt/eNp19N7v6T3SrR0ESTEf2up2CGO73qI35zQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "is-plain-obj": "^1.1.0", + "mkdirp": "^0.5.1" + } + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + }, + "p-retry": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz", + "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==", + "dev": true, + "requires": { + "retry": "^0.12.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "package-json": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", + "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", + "dev": true, + "requires": { + "got": "^6.7.1", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" + } + }, + "pako": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", + "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==", + "dev": true + }, + "parallel-transform": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz", + "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", + "dev": true, + "requires": { + "cyclist": "~0.2.2", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-asn1": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.4.tgz", + "integrity": "sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw==", + "dev": true, + "requires": { + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "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 + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "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": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "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": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "pbkdf2": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", + "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "dev": true, + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "portfinder": { + "version": "1.0.21", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.21.tgz", + "integrity": "sha512-ESabpDCzmBS3ekHbmpAIiESq3udRsCBGiBZLsC+HgBKv2ezb0R4oG+7RnYEVZ/ZCfhel5Tx3UzdNWA0Lox2QCA==", + "dev": true, + "requires": { + "async": "^1.5.2", + "debug": "^2.2.0", + "mkdirp": "0.5.x" + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + }, + "dependencies": { + "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 + } + } + }, + "postcss-attribute-case-insensitive": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.1.tgz", + "integrity": "sha512-L2YKB3vF4PetdTIthQVeT+7YiSzMoNMLLYxPXXppOOP7NoazEAy45sh2LvJ8leCQjfBcfkYQs8TtCcQjeZTp8A==", + "dev": true, + "requires": { + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0" + }, + "dependencies": { + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", + "dev": true + }, + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "dev": true, + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-color-functional-notation": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz", + "integrity": "sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g==", + "dev": true, + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-color-gray": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-gray/-/postcss-color-gray-5.0.0.tgz", + "integrity": "sha512-q6BuRnAGKM/ZRpfDascZlIZPjvwsRye7UDNalqVz3s7GDxMtqPY6+Q871liNxsonUw8oC61OG+PSaysYpl1bnw==", + "dev": true, + "requires": { + "@csstools/convert-colors": "^1.4.0", + "postcss": "^7.0.5", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-color-hex-alpha": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.3.tgz", + "integrity": "sha512-PF4GDel8q3kkreVXKLAGNpHKilXsZ6xuu+mOQMHWHLPNyjiUBOr75sp5ZKJfmv1MCus5/DWUGcK9hm6qHEnXYw==", + "dev": true, + "requires": { + "postcss": "^7.0.14", + "postcss-values-parser": "^2.0.1" + } + }, + "postcss-color-mod-function": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/postcss-color-mod-function/-/postcss-color-mod-function-3.0.3.tgz", + "integrity": "sha512-YP4VG+xufxaVtzV6ZmhEtc+/aTXH3d0JLpnYfxqTvwZPbJhWqp8bSY3nfNzNRFLgB4XSaBA82OE4VjOOKpCdVQ==", + "dev": true, + "requires": { + "@csstools/convert-colors": "^1.4.0", + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-color-rebeccapurple": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz", + "integrity": "sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g==", + "dev": true, + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-custom-media": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-7.0.8.tgz", + "integrity": "sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg==", + "dev": true, + "requires": { + "postcss": "^7.0.14" + } + }, + "postcss-custom-properties": { + "version": "8.0.11", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-8.0.11.tgz", + "integrity": "sha512-nm+o0eLdYqdnJ5abAJeXp4CEU1c1k+eB2yMCvhgzsds/e0umabFrN6HoTy/8Q4K5ilxERdl/JD1LO5ANoYBeMA==", + "dev": true, + "requires": { + "postcss": "^7.0.17", + "postcss-values-parser": "^2.0.1" + } + }, + "postcss-custom-selectors": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz", + "integrity": "sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w==", + "dev": true, + "requires": { + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0-rc.3" + }, + "dependencies": { + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", + "dev": true + }, + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "dev": true, + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-dir-pseudo-class": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz", + "integrity": "sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw==", + "dev": true, + "requires": { + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0-rc.3" + }, + "dependencies": { + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", + "dev": true + }, + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "dev": true, + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-double-position-gradients": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-1.0.0.tgz", + "integrity": "sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA==", + "dev": true, + "requires": { + "postcss": "^7.0.5", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-env-function": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-2.0.2.tgz", + "integrity": "sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw==", + "dev": true, + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-focus-visible": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz", + "integrity": "sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g==", + "dev": true, + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-focus-within": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz", + "integrity": "sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w==", + "dev": true, + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-font-variant": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-4.0.0.tgz", + "integrity": "sha512-M8BFYKOvCrI2aITzDad7kWuXXTm0YhGdP9Q8HanmN4EF1Hmcgs1KK5rSHylt/lUJe8yLxiSwWAHdScoEiIxztg==", + "dev": true, + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-gap-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz", + "integrity": "sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg==", + "dev": true, + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-image-set-function": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz", + "integrity": "sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw==", + "dev": true, + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-initial": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-3.0.2.tgz", + "integrity": "sha512-ugA2wKonC0xeNHgirR4D3VWHs2JcU08WAi1KFLVcnb7IN89phID6Qtg2RIctWbnvp1TM2BOmDtX8GGLCKdR8YA==", + "dev": true, + "requires": { + "lodash.template": "^4.5.0", + "postcss": "^7.0.2" + } + }, + "postcss-lab-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz", + "integrity": "sha512-whLy1IeZKY+3fYdqQFuDBf8Auw+qFuVnChWjmxm/UhHWqNHZx+B99EwxTvGYmUBqe3Fjxs4L1BoZTJmPu6usVg==", + "dev": true, + "requires": { + "@csstools/convert-colors": "^1.4.0", + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-load-config": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.0.tgz", + "integrity": "sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==", + "dev": true, + "requires": { + "cosmiconfig": "^5.0.0", + "import-cwd": "^2.0.0" + } + }, + "postcss-loader": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz", + "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "postcss": "^7.0.0", + "postcss-load-config": "^2.0.0", + "schema-utils": "^1.0.0" + } + }, + "postcss-logical": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-3.0.0.tgz", + "integrity": "sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA==", + "dev": true, + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-media-minmax": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz", + "integrity": "sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw==", + "dev": true, + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-modules-extract-imports": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", + "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", + "dev": true, + "requires": { + "postcss": "^7.0.5" + } + }, + "postcss-modules-local-by-default": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.2.tgz", + "integrity": "sha512-jM/V8eqM4oJ/22j0gx4jrp63GSvDH6v86OqyTHHUvk4/k1vceipZsaymiZ5PvocqZOl5SFHiFJqjs3la0wnfIQ==", + "dev": true, + "requires": { + "icss-utils": "^4.1.1", + "postcss": "^7.0.16", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.0.0" + } + }, + "postcss-modules-scope": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.1.0.tgz", + "integrity": "sha512-91Rjps0JnmtUB0cujlc8KIKCsJXWjzuxGeT/+Q2i2HXKZ7nBUeF9YQTZZTNvHVoNYj1AthsjnGLtqDUE0Op79A==", + "dev": true, + "requires": { + "postcss": "^7.0.6", + "postcss-selector-parser": "^6.0.0" + } + }, + "postcss-modules-values": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz", + "integrity": "sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==", + "dev": true, + "requires": { + "icss-utils": "^4.0.0", + "postcss": "^7.0.6" + } + }, + "postcss-nesting": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-7.0.1.tgz", + "integrity": "sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg==", + "dev": true, + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-overflow-shorthand": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-2.0.0.tgz", + "integrity": "sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g==", + "dev": true, + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-page-break": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-2.0.0.tgz", + "integrity": "sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ==", + "dev": true, + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-place": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-4.0.1.tgz", + "integrity": "sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg==", + "dev": true, + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-preset-env": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-6.7.0.tgz", + "integrity": "sha512-eU4/K5xzSFwUFJ8hTdTQzo2RBLbDVt83QZrAvI07TULOkmyQlnYlpwep+2yIK+K+0KlZO4BvFcleOCCcUtwchg==", + "dev": true, + "requires": { + "autoprefixer": "^9.6.1", + "browserslist": "^4.6.4", + "caniuse-lite": "^1.0.30000981", + "css-blank-pseudo": "^0.1.4", + "css-has-pseudo": "^0.10.0", + "css-prefers-color-scheme": "^3.1.1", + "cssdb": "^4.4.0", + "postcss": "^7.0.17", + "postcss-attribute-case-insensitive": "^4.0.1", + "postcss-color-functional-notation": "^2.0.1", + "postcss-color-gray": "^5.0.0", + "postcss-color-hex-alpha": "^5.0.3", + "postcss-color-mod-function": "^3.0.3", + "postcss-color-rebeccapurple": "^4.0.1", + "postcss-custom-media": "^7.0.8", + "postcss-custom-properties": "^8.0.11", + "postcss-custom-selectors": "^5.1.2", + "postcss-dir-pseudo-class": "^5.0.0", + "postcss-double-position-gradients": "^1.0.0", + "postcss-env-function": "^2.0.2", + "postcss-focus-visible": "^4.0.0", + "postcss-focus-within": "^3.0.0", + "postcss-font-variant": "^4.0.0", + "postcss-gap-properties": "^2.0.0", + "postcss-image-set-function": "^3.0.1", + "postcss-initial": "^3.0.0", + "postcss-lab-function": "^2.0.1", + "postcss-logical": "^3.0.0", + "postcss-media-minmax": "^4.0.0", + "postcss-nesting": "^7.0.0", + "postcss-overflow-shorthand": "^2.0.0", + "postcss-page-break": "^2.0.0", + "postcss-place": "^4.0.1", + "postcss-pseudo-class-any-link": "^6.0.0", + "postcss-replace-overflow-wrap": "^3.0.0", + "postcss-selector-matches": "^4.0.0", + "postcss-selector-not": "^4.0.0" + } + }, + "postcss-pseudo-class-any-link": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz", + "integrity": "sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew==", + "dev": true, + "requires": { + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0-rc.3" + }, + "dependencies": { + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", + "dev": true + }, + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "dev": true, + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-replace-overflow-wrap": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz", + "integrity": "sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw==", + "dev": true, + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-selector-matches": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz", + "integrity": "sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "postcss": "^7.0.2" + } + }, + "postcss-selector-not": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-4.0.0.tgz", + "integrity": "sha512-W+bkBZRhqJaYN8XAnbbZPLWMvZD1wKTu0UxtFKdhtGjWYmxhkUneoeOhRJKdAE5V7ZTlnbHfCR+6bNwK9e1dTQ==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "postcss": "^7.0.2" + } + }, + "postcss-selector-parser": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz", + "integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "postcss-value-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz", + "integrity": "sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ==", + "dev": true + }, + "postcss-values-parser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz", + "integrity": "sha512-2tLuBsA6P4rYTNKCXYG/71C7j1pU6pK503suYOmn4xYrQIzW+opD+7FAFNuGSdZC/3Qfy334QbeMu7MEb8gOxg==", + "dev": true, + "requires": { + "flatten": "^1.0.2", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "dev": true + }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "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 + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "dev": true + }, + "proxy-addr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", + "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", + "dev": true, + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.0" + } + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "psl": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.6.0.tgz", + "integrity": "sha512-SYKKmVel98NCOYXpkwUqZqh0ahZeeKfmisiLIcEZdsb+WbLv02g/dI5BUmZnIyOe7RzZtLax81nnb2HbvC2tzA==", + "dev": true + }, + "pstree.remy": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.7.tgz", + "integrity": "sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A==", + "dev": true + }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "dev": true + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "querystringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", + "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==", + "dev": true + }, + "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" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "requires": { + "randombytes": "^2.0.5", + "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.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "dev": true, + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "dependencies": { + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true + } + } + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "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" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "requires": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + } + }, + "regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz", + "integrity": "sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA==", + "dev": true, + "requires": { + "regenerate": "^1.4.0" + } + }, + "regenerator-transform": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.1.tgz", + "integrity": "sha512-flVuee02C3FKRISbxhXl9mGzdbWUVHubl1SMaknjxkFB1/iqpJhArQUvRxOOPEc/9tAiX0BaQ28FJH10E4isSQ==", + "dev": true, + "requires": { + "private": "^0.1.6" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexp-tree": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.11.tgz", + "integrity": "sha512-7/l/DgapVVDzZobwMCCgMlqiqyLFJ0cduo/j+3BcDJIB+yJdsYCfKuI3l/04NV+H/rfNRdPIDbXNZHM9XvQatg==", + "dev": true + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, + "regexpu-core": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.5.4.tgz", + "integrity": "sha512-BtizvGtFQKGPUcTy56o3nk1bGRp4SZOTYrDtGNlqCQufptV5IkkLN6Emw+yunAJjzf+C9FQFtvq7IoA3+oMYHQ==", + "dev": true, + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.0.2", + "regjsgen": "^0.5.0", + "regjsparser": "^0.6.0", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.1.0" + } + }, + "registry-auth-token": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", + "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", + "dev": true, + "requires": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", + "dev": true, + "requires": { + "rc": "^1.0.1" + } + }, + "regjsgen": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz", + "integrity": "sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA==", + "dev": true + }, + "regjsparser": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz", + "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==", + "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": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "^1.0.0" + } + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + } + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "resolve": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz", + "integrity": "sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + } + }, + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "dependencies": { + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "requires": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + } + } + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "dev": true + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "^2.1.0" + } + }, + "run-queue": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", + "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", + "dev": true, + "requires": { + "aproba": "^1.1.1" + } + }, + "rxjs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz", + "integrity": "sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "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 + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sass-graph": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz", + "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=", + "dev": true, + "requires": { + "glob": "^7.0.0", + "lodash": "^4.0.0", + "scss-tokenizer": "^0.2.3", + "yargs": "^7.0.0" + }, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "requires": { + "invert-kv": "^1.0.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dev": true, + "requires": { + "lcid": "^1.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + }, + "yargs": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", + "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", + "dev": true, + "requires": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^5.0.0" + } + }, + "yargs-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", + "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", + "dev": true, + "requires": { + "camelcase": "^3.0.0" + } + } + } + }, + "sass-loader": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-8.0.0.tgz", + "integrity": "sha512-+qeMu563PN7rPdit2+n5uuYVR0SSVwm0JsOUsaJXzgYcClWSlmX0iHDnmeOobPkf5kUglVot3QS6SyLyaQoJ4w==", + "dev": true, + "requires": { + "clone-deep": "^4.0.1", + "loader-utils": "^1.2.3", + "neo-async": "^2.6.1", + "schema-utils": "^2.1.0", + "semver": "^6.3.0" + }, + "dependencies": { + "schema-utils": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.1.tgz", + "integrity": "sha512-0WXHDs1VDJyo+Zqs9TKLKyD/h7yDpHUhEFsM2CzkICFdoX1av+GBq/J2xRTFfsQO5kBfhZzANf2VcIm84jqDbg==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + }, + "scss-tokenizer": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz", + "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=", + "dev": true, + "requires": { + "js-base64": "^2.1.8", + "source-map": "^0.4.2" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", + "dev": true + }, + "selfsigned": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.4.tgz", + "integrity": "sha512-9AukTiDmHXGXWtWjembZ5NDmVvP2695EtpgbCsxCa68w3c88B+alqbmZ4O3hZ4VWGXeGWzEVdvqgAJD8DQPCDw==", + "dev": true, + "requires": { + "node-forge": "0.7.5" + } + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + }, + "semver-diff": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", + "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", + "dev": true, + "requires": { + "semver": "^5.0.3" + } + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "serialize-javascript": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.7.0.tgz", + "integrity": "sha512-ke8UG8ulpFOxO8f8gRYabHQe/ZntKlcig2Mp+8+URDP1D8vJZ0KUt7LYo07q25Z/+JVSgpr/cui9PIp5H6/+nA==", + "dev": true + }, + "serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", + "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": { + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "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": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "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 + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "dev": true + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "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": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + } + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "sockjs": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", + "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", + "dev": true, + "requires": { + "faye-websocket": "^0.10.0", + "uuid": "^3.0.1" + } + }, + "sockjs-client": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.3.0.tgz", + "integrity": "sha512-R9jxEzhnnrdxLCNln0xg5uGHqMnkhPSTzUZH2eXcR03S/On9Yvoq2wyUZILRUhZCNVu2PmwWVoyuiPz8th8zbg==", + "dev": true, + "requires": { + "debug": "^3.2.5", + "eventsource": "^1.0.7", + "faye-websocket": "~0.11.1", + "inherits": "^2.0.3", + "json3": "^3.3.2", + "url-parse": "^1.4.3" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "faye-websocket": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", + "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "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 + } + } + }, + "source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "dev": true, + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", + "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "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-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true + }, + "spdy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.0.tgz", + "integrity": "sha512-ot0oEGT/PGUpzf/6uk4AWLqkq+irlqHXkrdbk51oWONh3bxQmBuljxPNl66zlRRcIJStWq0QkLUCPOPjgjvU0Q==", + "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" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "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 + } + } + }, + "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" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "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 + }, + "readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + }, + "stdout-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", + "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==", + "dev": true, + "requires": { + "readable-stream": "^2.0.1" + } + }, + "stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "stream-each": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", + "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "dev": true, + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + } + }, + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "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" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1" + }, + "dependencies": { + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true + } + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "style-loader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.0.0.tgz", + "integrity": "sha512-B0dOCFwv7/eY31a5PCieNwMgMhVGFe9w+rh7s/Bx8kfFkrth9zfTZquoYvdw8URgiqxObQKcpW51Ugz1HjfdZw==", + "dev": true, + "requires": { + "loader-utils": "^1.2.3", + "schema-utils": "^2.0.1" + }, + "dependencies": { + "schema-utils": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.2.0.tgz", + "integrity": "sha512-5EwsCNhfFTZvUreQhx/4vVQpJ/lnCAkgoIHLhSpp4ZirE+4hzFvdJi0FMub6hxbFVBJYSpeVVmon+2e7uEGRrA==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1" + } + } + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "svg.draggable.js": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz", + "integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==", + "requires": { + "svg.js": "^2.0.1" + } + }, + "svg.draw.js": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/svg.draw.js/-/svg.draw.js-2.0.3.tgz", + "integrity": "sha512-GGK7vBzwdEgr5fJUupqDqYPhQBXxrtBd4CcrgqAt7S4/5REmF6JTSuITeAgfVlfXftOO90t5OqLOtJ4IEEUFVw==", + "requires": { + "svg.js": "2.x.x" + } + }, + "svg.js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz", + "integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==" + }, + "svg.resize.js": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz", + "integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==", + "requires": { + "svg.js": "^2.6.5", + "svg.select.js": "^2.1.2" + }, + "dependencies": { + "svg.select.js": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz", + "integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==", + "requires": { + "svg.js": "^2.2.5" + } + } + } + }, + "svg.select.js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz", + "integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==", + "requires": { + "svg.js": "^2.6.5" + } + }, + "table": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.5.tgz", + "integrity": "sha512-oGa2Hl7CQjfoaogtrOHEJroOcYILTx7BZWLGsJIlzoWmB2zmguhNfPJZsWPKYek/MgCxfco54gEi31d1uN2hFA==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "dev": true + }, + "tar": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz", + "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==", + "dev": true, + "requires": { + "block-stream": "*", + "fstream": "^1.0.12", + "inherits": "2" + } + }, + "term-size": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", + "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", + "dev": true, + "requires": { + "execa": "^0.7.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + } + } + }, + "terser": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.1.2.tgz", + "integrity": "sha512-jvNoEQSPXJdssFwqPSgWjsOrb+ELoE+ILpHPKXC83tIxOlh2U75F1KuB2luLD/3a6/7K3Vw5pDn+hvu0C4AzSw==", + "dev": true, + "requires": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + }, + "dependencies": { + "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 + } + } + }, + "terser-webpack-plugin": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.3.0.tgz", + "integrity": "sha512-W2YWmxPjjkUcOWa4pBEv4OP4er1aeQJlSo2UhtCFQCuRXEHjOFscO8VyWHj9JLlA0RzQb8Y2/Ta78XZvT54uGg==", + "dev": true, + "requires": { + "cacache": "^11.3.2", + "find-cache-dir": "^2.0.0", + "is-wsl": "^1.1.0", + "loader-utils": "^1.2.3", + "schema-utils": "^1.0.0", + "serialize-javascript": "^1.7.0", + "source-map": "^0.6.1", + "terser": "^4.0.0", + "webpack-sources": "^1.3.0", + "worker-farm": "^1.7.0" + }, + "dependencies": { + "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 + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "thunky": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.0.3.tgz", + "integrity": "sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow==", + "dev": true + }, + "timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", + "dev": true + }, + "timers-browserify": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", + "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==", + "dev": true, + "requires": { + "setimmediate": "^1.0.4" + } + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "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": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "dev": true + }, + "touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "requires": { + "nopt": "~1.0.10" + } + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "true-case-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", + "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==", + "dev": true, + "requires": { + "glob": "^7.1.2" + } + }, + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", + "dev": true + }, + "tsutils": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", + "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "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" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "typescript": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz", + "integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==", + "dev": true + }, + "undefsafe": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.2.tgz", + "integrity": "sha1-Il9rngM3Zj4Njnz9aG/Cg2zKznY=", + "dev": true, + "requires": { + "debug": "^2.2.0" + } + }, + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "dev": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz", + "integrity": "sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz", + "integrity": "sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==", + "dev": true + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", + "dev": true + }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "unique-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", + "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", + "dev": true, + "requires": { + "crypto-random-string": "^1.0.0" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "unzip-response": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", + "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=", + "dev": true + }, + "upath": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", + "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==", + "dev": true + }, + "update-notifier": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz", + "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==", + "dev": true, + "requires": { + "boxen": "^1.2.1", + "chalk": "^2.0.1", + "configstore": "^3.0.0", + "import-lazy": "^2.1.0", + "is-ci": "^1.0.10", + "is-installed-globally": "^0.1.0", + "is-npm": "^1.0.0", + "latest-version": "^3.0.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "url-parse": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", + "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", + "dev": true, + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "dev": true, + "requires": { + "prepend-http": "^1.0.1" + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "util": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", + "dev": true, + "requires": { + "inherits": "2.0.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + } + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + }, + "v8-compile-cache": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz", + "integrity": "sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "vm-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.0.tgz", + "integrity": "sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==", + "dev": true + }, + "watchpack": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", + "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==", + "dev": true, + "requires": { + "chokidar": "^2.0.2", + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0" + } + }, + "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" + } + }, + "webpack": { + "version": "4.36.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.36.1.tgz", + "integrity": "sha512-Ej01/N9W8DVyhEpeQnbUdGvOECw0L46FxS12cCOs8gSK7bhUlrbHRnWkjiXckGlHjUrmL89kDpTRIkUk6Y+fKg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-module-context": "1.8.5", + "@webassemblyjs/wasm-edit": "1.8.5", + "@webassemblyjs/wasm-parser": "1.8.5", + "acorn": "^6.2.0", + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0", + "chrome-trace-event": "^1.0.0", + "enhanced-resolve": "^4.1.0", + "eslint-scope": "^4.0.0", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^2.3.0", + "loader-utils": "^1.1.0", + "memory-fs": "~0.4.1", + "micromatch": "^3.1.8", + "mkdirp": "~0.5.0", + "neo-async": "^2.5.0", + "node-libs-browser": "^2.0.0", + "schema-utils": "^1.0.0", + "tapable": "^1.1.0", + "terser-webpack-plugin": "^1.1.0", + "watchpack": "^1.5.0", + "webpack-sources": "^1.3.0" + } + }, + "webpack-cli": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.6.tgz", + "integrity": "sha512-0vEa83M7kJtxK/jUhlpZ27WHIOndz5mghWL2O53kiDoA9DIxSKnfqB92LoqEn77cT4f3H2cZm1BMEat/6AZz3A==", + "dev": true, + "requires": { + "chalk": "2.4.2", + "cross-spawn": "6.0.5", + "enhanced-resolve": "4.1.0", + "findup-sync": "3.0.0", + "global-modules": "2.0.0", + "import-local": "2.0.0", + "interpret": "1.2.0", + "loader-utils": "1.2.3", + "supports-color": "6.1.0", + "v8-compile-cache": "2.0.3", + "yargs": "13.2.4" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "yargs": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.4.tgz", + "integrity": "sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "os-locale": "^3.1.0", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.0" + } + }, + "yargs-parser": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", + "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "webpack-dev-middleware": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.0.tgz", + "integrity": "sha512-qvDesR1QZRIAZHOE3iQ4CXLZZSQ1lAUsSpnQmlB1PBfoN/xdRjmge3Dok0W4IdaVLJOGJy3sGI4sZHwjRU0PCA==", + "dev": true, + "requires": { + "memory-fs": "^0.4.1", + "mime": "^2.4.2", + "range-parser": "^1.2.1", + "webpack-log": "^2.0.0" + }, + "dependencies": { + "mime": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", + "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", + "dev": true + } + } + }, + "webpack-dev-server": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.7.2.tgz", + "integrity": "sha512-mjWtrKJW2T9SsjJ4/dxDC2fkFVUw8jlpemDERqV0ZJIkjjjamR2AbQlr3oz+j4JLhYCHImHnXZK5H06P2wvUew==", + "dev": true, + "requires": { + "ansi-html": "0.0.7", + "bonjour": "^3.5.0", + "chokidar": "^2.1.6", + "compression": "^1.7.4", + "connect-history-api-fallback": "^1.6.0", + "debug": "^4.1.1", + "del": "^4.1.1", + "express": "^4.17.1", + "html-entities": "^1.2.1", + "http-proxy-middleware": "^0.19.1", + "import-local": "^2.0.0", + "internal-ip": "^4.3.0", + "ip": "^1.1.5", + "killable": "^1.0.1", + "loglevel": "^1.6.3", + "opn": "^5.5.0", + "p-retry": "^3.0.1", + "portfinder": "^1.0.20", + "schema-utils": "^1.0.0", + "selfsigned": "^1.10.4", + "semver": "^6.1.1", + "serve-index": "^1.9.1", + "sockjs": "0.3.19", + "sockjs-client": "1.3.0", + "spdy": "^4.0.0", + "strip-ansi": "^3.0.1", + "supports-color": "^6.1.0", + "url": "^0.11.0", + "webpack-dev-middleware": "^3.7.0", + "webpack-log": "^2.0.0", + "yargs": "12.0.5" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "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 + }, + "semver": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.2.0.tgz", + "integrity": "sha512-jdFC1VdUGT/2Scgbimf7FSx9iJLXoqfglSF+gJeuNWVpiE37OIbc1jywR/GJyFdz3mnkz2/id0L0J/cr0izR5A==", + "dev": true + } + } + }, + "webpack-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", + "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", + "dev": true, + "requires": { + "ansi-colors": "^3.0.0", + "uuid": "^3.3.2" + } + }, + "webpack-sources": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz", + "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "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 + } + } + }, + "websocket-driver": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.3.tgz", + "integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==", + "dev": true, + "requires": { + "http-parser-js": ">=0.4.0 <0.4.11", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", + "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "widest-line": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", + "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", + "dev": true, + "requires": { + "string-width": "^2.1.1" + } + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "worker-farm": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", + "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", + "dev": true, + "requires": { + "errno": "~0.1.7" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "xdg-basedir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", + "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", + "dev": true + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } +} diff --git a/cvat-canvas/package.json b/cvat-canvas/package.json index a9b19472d7e..06ea6c5c196 100644 --- a/cvat-canvas/package.json +++ b/cvat-canvas/package.json @@ -1,6 +1,6 @@ { "name": "cvat-canvas", - "version": "0.1.0", + "version": "0.5.2", "description": "Part of Computer Vision Annotation Tool which presents its canvas library", "main": "src/canvas.ts", "scripts": { @@ -31,7 +31,11 @@ "eslint-config-airbnb-typescript": "^4.0.1", "eslint-config-typescript-recommended": "^1.4.17", "eslint-plugin-import": "^2.18.2", + "node-sass": "^4.13.0", "nodemon": "^1.19.1", + "postcss-loader": "^3.0.0", + "postcss-preset-env": "^6.7.0", + "sass-loader": "^8.0.0", "style-loader": "^1.0.0", "typescript": "^3.5.3", "webpack": "^4.36.1", diff --git a/cvat-canvas/postcss.config.js b/cvat-canvas/postcss.config.js new file mode 100644 index 00000000000..07d9a7dcbd7 --- /dev/null +++ b/cvat-canvas/postcss.config.js @@ -0,0 +1,13 @@ +// Copyright (C) 2019-2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +/* eslint-disable */ +module.exports = { + parser: false, + plugins: { + 'postcss-preset-env': { + browsers: '> 2.5%', // https://github.com/browserslist/browserslist + }, + }, +}; diff --git a/cvat-canvas/src/css/canvas.css b/cvat-canvas/src/scss/canvas.scss similarity index 67% rename from cvat-canvas/src/css/canvas.css rename to cvat-canvas/src/scss/canvas.scss index 4d931ed0a54..d46edb0e228 100644 --- a/cvat-canvas/src/css/canvas.css +++ b/cvat-canvas/src/scss/canvas.scss @@ -1,17 +1,41 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + .cvat_canvas_hidden { display: none; } .cvat_canvas_shape { - fill-opacity: 0.05; stroke-opacity: 1; } polyline.cvat_canvas_shape { fill-opacity: 0; +} + +.cvat_shape_action_opacity { + fill-opacity: 0.5; + stroke-opacity: 1; +} + +polyline.cvat_shape_action_opacity { + fill-opacity: 0; +} + +.cvat_shape_drawing_opacity { + fill-opacity: 0.2; stroke-opacity: 1; } +polyline.cvat_shape_drawing_opacity { + fill-opacity: 0; +} + +.cvat_shape_action_dasharray { + stroke-dasharray: 4 1 2 3; +} + .cvat_canvas_text { font-weight: bold; font-size: 1.2em; @@ -27,47 +51,54 @@ polyline.cvat_canvas_shape { stroke: red; } -.cvat_canvas_shape_activated { - fill-opacity: 0.3; -} - .cvat_canvas_shape_grouping { + @extend .cvat_shape_action_dasharray; + @extend .cvat_shape_action_opacity; fill: darkmagenta; - fill-opacity: 0.5; } polyline.cvat_canvas_shape_grouping { + @extend .cvat_shape_action_dasharray; + @extend .cvat_shape_action_opacity; stroke: darkmagenta; - stroke-opacity: 1; } .cvat_canvas_shape_merging { + @extend .cvat_shape_action_dasharray; + @extend .cvat_shape_action_opacity; fill: blue; - fill-opacity: 0.5; +} + +polyline.cvat_canvas_shape_merging { + @extend .cvat_shape_action_dasharray; + @extend .cvat_shape_action_opacity; + stroke: blue; } polyline.cvat_canvas_shape_splitting { + @extend .cvat_shape_action_dasharray; + @extend .cvat_shape_action_opacity; stroke: dodgerblue; - stroke-opacity: 1; } .cvat_canvas_shape_splitting { + @extend .cvat_shape_action_dasharray; + @extend .cvat_shape_action_opacity; fill: dodgerblue; - fill-opacity: 0.5; -} - -polyline.cvat_canvas_shape_merging { - stroke: blue; - stroke-opacity: 1; } .cvat_canvas_shape_drawing { - fill-opacity: 0.1; - stroke-opacity: 1; + @extend .cvat_shape_drawing_opacity; fill: white; stroke: black; } +.cvat_canvas_zoom_selection { + @extend .cvat_shape_action_dasharray; + stroke: #096dd9; + fill-opacity: 0; +} + .cvat_canvas_shape_occluded { stroke-dasharray: 5; } @@ -78,8 +109,9 @@ polyline.cvat_canvas_shape_merging { } #cvat_canvas_wrapper { - width: 100%; - height: 93%; + width: calc(100% - 10px); + height: calc(100% - 10px); + margin: 5px; border-radius: 5px; background-color: white; overflow: hidden; @@ -102,6 +134,7 @@ polyline.cvat_canvas_shape_merging { } #cvat_canvas_text_content { + text-rendering: optimizeSpeed; position: absolute; z-index: 3; pointer-events: none; @@ -134,6 +167,7 @@ polyline.cvat_canvas_shape_merging { } #cvat_canvas_content { + filter: contrast(120%) saturate(150%); position: absolute; z-index: 2; outline: 10px solid black; @@ -145,4 +179,4 @@ polyline.cvat_canvas_shape_merging { 0% {stroke-dashoffset: 1; stroke: #09c;} 50% {stroke-dashoffset: 100; stroke: #f44;} 100% {stroke-dashoffset: 300; stroke: #09c;} -} \ No newline at end of file +} diff --git a/cvat-canvas/src/typescript/canvas.ts b/cvat-canvas/src/typescript/canvas.ts index e934b41b6c7..eef1de40ef3 100644 --- a/cvat-canvas/src/typescript/canvas.ts +++ b/cvat-canvas/src/typescript/canvas.ts @@ -1,16 +1,16 @@ -/* -* Copyright (C) 2019 Intel Corporation -* SPDX-License-Identifier: MIT -*/ +// Copyright (C) 2019-2020 Intel Corporation +// +// SPDX-License-Identifier: MIT import { - Rotation, + Mode, DrawData, MergeData, SplitData, GroupData, CanvasModel, CanvasModelImpl, + RectDrawingMethod, } from './canvasModel'; import { @@ -27,15 +27,17 @@ import { CanvasViewImpl, } from './canvasView'; +import '../scss/canvas.scss'; +import pjson from '../../package.json'; -import '../css/canvas.css'; - +const CanvasVersion = pjson.version; interface Canvas { html(): HTMLDivElement; + setZLayer(zLayer: number | null): void; setup(frameData: any, objectStates: any[]): void; - activate(clientID: number, attributeID?: number): void; - rotate(rotation: Rotation, remember?: boolean): void; + activate(clientID: number | null, attributeID?: number): void; + rotate(rotationAngle: number): void; focus(clientID: number, padding?: number): void; fit(): void; grid(stepX: number, stepY: number): void; @@ -46,6 +48,11 @@ interface Canvas { merge(mergeData: MergeData): void; select(objectState: any): void; + fitCanvas(): void; + dragCanvas(enable: boolean): void; + zoomCanvas(enable: boolean): void; + + mode(): void; cancel(): void; } @@ -64,16 +71,35 @@ class CanvasImpl implements Canvas { return this.view.html(); } + public setZLayer(zLayer: number | null): void { + this.model.setZLayer(zLayer); + } + public setup(frameData: any, objectStates: any[]): void { this.model.setup(frameData, objectStates); } - public activate(clientID: number, attributeID: number = null): void { + public fitCanvas(): void { + this.model.fitCanvas( + this.view.html().clientWidth, + this.view.html().clientHeight, + ); + } + + public dragCanvas(enable: boolean): void { + this.model.dragCanvas(enable); + } + + public zoomCanvas(enable: boolean): void { + this.model.zoomCanvas(enable); + } + + public activate(clientID: number | null, attributeID: number | null = null): void { this.model.activate(clientID, attributeID); } - public rotate(rotation: Rotation, remember: boolean = false): void { - this.model.rotate(rotation, remember); + public rotate(rotationAngle: number): void { + this.model.rotate(rotationAngle); } public focus(clientID: number, padding: number = 0): void { @@ -108,13 +134,18 @@ class CanvasImpl implements Canvas { this.model.select(objectState); } + public mode(): Mode { + return this.model.mode; + } + public cancel(): void { this.model.cancel(); } } - export { CanvasImpl as Canvas, - Rotation, + CanvasVersion, + RectDrawingMethod, + Mode as CanvasMode, }; diff --git a/cvat-canvas/src/typescript/canvasController.ts b/cvat-canvas/src/typescript/canvasController.ts index 30914a568b7..7fc555c6469 100644 --- a/cvat-canvas/src/typescript/canvasController.ts +++ b/cvat-canvas/src/typescript/canvasController.ts @@ -1,7 +1,6 @@ -/* -* Copyright (C) 2019 Intel Corporation -* SPDX-License-Identifier: MIT -*/ +// Copyright (C) 2019-2020 Intel Corporation +// +// SPDX-License-Identifier: MIT import { CanvasModel, @@ -18,6 +17,7 @@ import { export interface CanvasController { readonly objects: any[]; + readonly zLayer: number | null; readonly focusData: FocusData; readonly activeElement: ActiveElement; readonly drawData: DrawData; @@ -105,6 +105,10 @@ export class CanvasControllerImpl implements CanvasController { this.model.geometry = geometry; } + public get zLayer(): number | null { + return this.model.zLayer; + } + public get objects(): any[] { return this.model.objects; } diff --git a/cvat-canvas/src/typescript/canvasModel.ts b/cvat-canvas/src/typescript/canvasModel.ts index 5fbb2ab15ea..f72bc632470 100644 --- a/cvat-canvas/src/typescript/canvasModel.ts +++ b/cvat-canvas/src/typescript/canvasModel.ts @@ -1,14 +1,9 @@ -/* -* Copyright (C) 2019 Intel Corporation -* SPDX-License-Identifier: MIT -*/ - -// Disable till full implementation -/* eslint class-methods-use-this: "off" */ +// Copyright (C) 2019-2020 Intel Corporation +// +// SPDX-License-Identifier: MIT import { MasterImpl } from './master'; - export interface Size { width: number; height: number; @@ -36,13 +31,19 @@ export interface FocusData { } export interface ActiveElement { - clientID: number; - attributeID: number; + clientID: number | null; + attributeID: number | null; +} + +export enum RectDrawingMethod { + CLASSIC = 'By 2 points', + EXTREME_POINTS = 'By 4 points' } export interface DrawData { enabled: boolean; shapeType?: string; + rectDrawingMethod?: RectDrawingMethod; numberOfPoints?: number; initialState?: any; crosshair?: boolean; @@ -71,26 +72,28 @@ export enum FrameZoom { MAX = 10, } -export enum Rotation { - ANTICLOCKWISE90, - CLOCKWISE90, -} - export enum UpdateReasons { - IMAGE = 'image', - OBJECTS = 'objects', - ZOOM = 'zoom', - FIT = 'fit', - MOVE = 'move', - GRID = 'grid', - FOCUS = 'focus', - ACTIVATE = 'activate', + IMAGE_CHANGED = 'image_changed', + IMAGE_ZOOMED = 'image_zoomed', + IMAGE_FITTED = 'image_fitted', + IMAGE_MOVED = 'image_moved', + GRID_UPDATED = 'grid_updated', + SET_Z_LAYER = 'set_z_layer', + + OBJECTS_UPDATED = 'objects_updated', + SHAPE_ACTIVATED = 'shape_activated', + SHAPE_FOCUSED = 'shape_focused', + + FITTED_CANVAS = 'fitted_canvas', + DRAW = 'draw', MERGE = 'merge', SPLIT = 'split', GROUP = 'group', SELECT = 'select', CANCEL = 'cancel', + DRAG_CANVAS = 'drag_canvas', + ZOOM_CANVAS = 'ZOOM_CANVAS', } export enum Mode { @@ -102,11 +105,14 @@ export enum Mode { MERGE = 'merge', SPLIT = 'split', GROUP = 'group', + DRAG_CANVAS = 'drag_canvas', + ZOOM_CANVAS = 'zoom_canvas', } export interface CanvasModel { - readonly image: string; + readonly image: HTMLImageElement | null; readonly objects: any[]; + readonly zLayer: number | null; readonly gridSize: Size; readonly focusData: FocusData; readonly activeElement: ActiveElement; @@ -118,12 +124,13 @@ export interface CanvasModel { geometry: Geometry; mode: Mode; + setZLayer(zLayer: number | null): void; zoom(x: number, y: number, direction: number): void; move(topOffset: number, leftOffset: number): void; setup(frameData: any, objectStates: any[]): void; - activate(clientID: number, attributeID: number): void; - rotate(rotation: Rotation, remember: boolean): void; + activate(clientID: number | null, attributeID: number | null): void; + rotate(rotationAngle: number): void; focus(clientID: number, padding: number): void; fit(): void; grid(stepX: number, stepY: number): void; @@ -134,6 +141,10 @@ export interface CanvasModel { merge(mergeData: MergeData): void; select(objectState: any): void; + fitCanvas(width: number, height: number): void; + dragCanvas(enable: boolean): void; + zoomCanvas(enable: boolean): void; + cancel(): void; } @@ -142,16 +153,17 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { activeElement: ActiveElement; angle: number; canvasSize: Size; - image: string; + image: HTMLImageElement | null; + imageID: number | null; imageOffset: number; imageSize: Size; focusData: FocusData; gridSize: Size; left: number; objects: any[]; - rememberAngle: boolean; scale: number; top: number; + zLayer: number | null; drawData: DrawData; mergeData: MergeData; groupData: GroupData; @@ -173,7 +185,8 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { height: 0, width: 0, }, - image: '', + image: null, + imageID: null, imageOffset: 0, imageSize: { height: 0, @@ -189,13 +202,11 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { }, left: 0, objects: [], - rememberAngle: false, scale: 1, top: 0, + zLayer: null, drawData: { enabled: false, - shapeType: null, - numberOfPoints: null, initialState: null, }, mergeData: { @@ -208,10 +219,15 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { enabled: false, }, selected: null, - mode: null, + mode: Mode.IDLE, }; } + public setZLayer(zLayer: number | null): void { + this.data.zLayer = zLayer; + this.notify(UpdateReasons.SET_Z_LAYER); + } + public zoom(x: number, y: number, direction: number): void { const oldScale: number = this.data.scale; const newScale: number = direction > 0 ? oldScale * 6 / 5 : oldScale * 5 / 6; @@ -233,42 +249,89 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { * (oldScale / this.data.scale - 1)) * this.data.scale; } - this.notify(UpdateReasons.ZOOM); + this.notify(UpdateReasons.IMAGE_ZOOMED); } public move(topOffset: number, leftOffset: number): void { this.data.top += topOffset; this.data.left += leftOffset; - this.notify(UpdateReasons.MOVE); + this.notify(UpdateReasons.IMAGE_MOVED); + } + + public fitCanvas(width: number, height: number): void { + this.data.canvasSize.height = height; + this.data.canvasSize.width = width; + + this.data.imageOffset = Math.floor(Math.max( + this.data.canvasSize.height / FrameZoom.MIN, + this.data.canvasSize.width / FrameZoom.MIN, + )); + + this.notify(UpdateReasons.FITTED_CANVAS); + this.notify(UpdateReasons.OBJECTS_UPDATED); + } + + public dragCanvas(enable: boolean): void { + if (enable && this.data.mode !== Mode.IDLE) { + throw Error(`Canvas is busy. Action: ${this.data.mode}`); + } + + if (!enable && this.data.mode !== Mode.DRAG_CANVAS) { + throw Error(`Canvas is not in the drag mode. Action: ${this.data.mode}`); + } + + this.data.mode = enable ? Mode.DRAG_CANVAS : Mode.IDLE; + this.notify(UpdateReasons.DRAG_CANVAS); + } + + public zoomCanvas(enable: boolean): void { + if (enable && this.data.mode !== Mode.IDLE) { + throw Error(`Canvas is busy. Action: ${this.data.mode}`); + } + + if (!enable && this.data.mode !== Mode.ZOOM_CANVAS) { + throw Error(`Canvas is not in the zoom mode. Action: ${this.data.mode}`); + } + + this.data.mode = enable ? Mode.ZOOM_CANVAS : Mode.IDLE; + this.notify(UpdateReasons.ZOOM_CANVAS); } public setup(frameData: any, objectStates: any[]): void { + if (frameData.number === this.data.imageID) { + this.data.objects = objectStates; + this.notify(UpdateReasons.OBJECTS_UPDATED); + return; + } + + this.data.imageID = frameData.number; frameData.data( (): void => { - this.data.image = ''; - this.notify(UpdateReasons.IMAGE); + this.data.image = null; + this.notify(UpdateReasons.IMAGE_CHANGED); }, - ).then((data: string): void => { + ).then((data: HTMLImageElement): void => { + if (frameData.number !== this.data.imageID) { + // already another image + return; + } + this.data.imageSize = { height: (frameData.height as number), width: (frameData.width as number), }; - if (!this.data.rememberAngle) { - this.data.angle = 0; - } - this.data.image = data; - this.notify(UpdateReasons.IMAGE); + this.notify(UpdateReasons.IMAGE_CHANGED); this.data.objects = objectStates; - this.notify(UpdateReasons.OBJECTS); + this.notify(UpdateReasons.OBJECTS_UPDATED); }).catch((exception: any): void => { throw exception; }); } - public activate(clientID: number, attributeID: number): void { - if (this.data.mode !== Mode.IDLE) { + public activate(clientID: number | null, attributeID: number | null): void { + if (this.data.mode !== Mode.IDLE && clientID !== null) { // Exception or just return? throw Error(`Canvas is busy. Action: ${this.data.mode}`); } @@ -278,19 +341,14 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { attributeID, }; - this.notify(UpdateReasons.ACTIVATE); + this.notify(UpdateReasons.SHAPE_ACTIVATED); } - public rotate(rotation: Rotation, remember: boolean = false): void { - if (rotation === Rotation.CLOCKWISE90) { - this.data.angle += 90; - } else { - this.data.angle -= 90; + public rotate(rotationAngle: number): void { + if (this.data.angle !== rotationAngle) { + this.data.angle = (360 + Math.floor((rotationAngle) / 90) * 90) % 360; + this.fit(); } - - this.data.angle %= 360; - this.data.rememberAngle = remember; - this.fit(); } public focus(clientID: number, padding: number): void { @@ -299,7 +357,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { padding, }; - this.notify(UpdateReasons.FOCUS); + this.notify(UpdateReasons.SHAPE_FOCUSED); } public fit(): void { @@ -326,7 +384,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { this.data.top = (this.data.canvasSize.height / 2 - this.data.imageSize.height / 2); this.data.left = (this.data.canvasSize.width / 2 - this.data.imageSize.width / 2); - this.notify(UpdateReasons.FIT); + this.notify(UpdateReasons.IMAGE_FITTED); } public grid(stepX: number, stepY: number): void { @@ -335,7 +393,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { width: stepX, }; - this.notify(UpdateReasons.GRID); + this.notify(UpdateReasons.GRID_UPDATED); } public draw(drawData: DrawData): void { @@ -454,11 +512,20 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { )); } - public get image(): string { + public get zLayer(): number | null { + return this.data.zLayer; + } + + public get image(): HTMLImageElement | null { return this.data.image; } public get objects(): any[] { + if (this.data.zLayer !== null) { + return this.data.objects + .filter((object: any): boolean => object.zOrder <= this.data.zLayer); + } + return this.data.objects; } diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index d9c57233e16..d026c5246a7 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -1,7 +1,6 @@ -/* -* Copyright (C) 2019 Intel Corporation -* SPDX-License-Identifier: MIT -*/ +// Copyright (C) 2019-2020 Intel Corporation +// +// SPDX-License-Identifier: MIT import * as SVG from 'svg.js'; @@ -16,11 +15,11 @@ import { EditHandler, EditHandlerImpl } from './editHandler'; import { MergeHandler, MergeHandlerImpl } from './mergeHandler'; import { SplitHandler, SplitHandlerImpl } from './splitHandler'; import { GroupHandler, GroupHandlerImpl } from './groupHandler'; +import { ZoomHandler, ZoomHandlerImpl } from './zoomHandler'; import consts from './consts'; import { translateToSVG, translateFromSVG, - translateBetweenSVG, pointsToArray, displayShapeSize, ShapeSizeElement, @@ -29,7 +28,6 @@ import { CanvasModel, Geometry, UpdateReasons, - FocusData, FrameZoom, ActiveElement, DrawData, @@ -44,33 +42,11 @@ export interface CanvasView { html(): HTMLDivElement; } -interface ShapeDict { - [index: number]: SVG.Shape; -} - -interface TextDict { - [index: number]: SVG.Text; -} - -function darker(color: string, percentage: number): string { - const R = Math.round(parseInt(color.slice(1, 3), 16) * (1 - percentage / 100)); - const G = Math.round(parseInt(color.slice(3, 5), 16) * (1 - percentage / 100)); - const B = Math.round(parseInt(color.slice(5, 7), 16) * (1 - percentage / 100)); - - const rHex = Math.max(0, R).toString(16); - const gHex = Math.max(0, G).toString(16); - const bHex = Math.max(0, B).toString(16); - - return `#${rHex.length === 1 ? `0${rHex}` : rHex}` - + `${gHex.length === 1 ? `0${gHex}` : gHex}` - + `${bHex.length === 1 ? `0${bHex}` : bHex}`; -} - export class CanvasViewImpl implements CanvasView, Listener { private loadingAnimation: SVGSVGElement; private text: SVGSVGElement; private adoptedText: SVG.Container; - private background: SVGSVGElement; + private background: HTMLCanvasElement; private grid: SVGSVGElement; private content: SVGSVGElement; private adoptedContent: SVG.Container; @@ -78,18 +54,17 @@ export class CanvasViewImpl implements CanvasView, Listener { private gridPath: SVGPathElement; private gridPattern: SVGPatternElement; private controller: CanvasController; - private svgShapes: ShapeDict; - private svgTexts: TextDict; + private svgShapes: Record; + private svgTexts: Record; + private drawnStates: Record; private geometry: Geometry; private drawHandler: DrawHandler; private editHandler: EditHandler; private mergeHandler: MergeHandler; private splitHandler: SplitHandler; private groupHandler: GroupHandler; - private activeElement: { - state: any; - attributeID: number; - }; + private zoomHandler: ZoomHandler; + private activeElement: ActiveElement; private set mode(value: Mode) { this.controller.mode = value; @@ -99,14 +74,19 @@ export class CanvasViewImpl implements CanvasView, Listener { return this.controller.mode; } - private onDrawDone(data: object): void { + private onDrawDone(data: object, continueDraw?: boolean): void { if (data) { + const { zLayer } = this.controller; const event: CustomEvent = new CustomEvent('canvas.drawn', { bubbles: false, cancelable: true, detail: { // eslint-disable-next-line new-cap - state: data, + state: { + ...data, + zOrder: zLayer || 0, + }, + continue: continueDraw, }, }); @@ -120,11 +100,17 @@ export class CanvasViewImpl implements CanvasView, Listener { this.canvas.dispatchEvent(event); } - this.controller.draw({ - enabled: false, - }); - - this.mode = Mode.IDLE; + if (continueDraw) { + this.drawHandler.draw( + this.controller.drawData, + this.geometry, + ); + } else { + this.mode = Mode.IDLE; + this.controller.draw({ + enabled: false, + }); + } } private onEditDone(state: any, points: number[]): void { @@ -206,7 +192,7 @@ export class CanvasViewImpl implements CanvasView, Listener { this.mode = Mode.IDLE; } - private onGroupDone(objects: any[]): void { + private onGroupDone(objects?: any[]): void { if (objects) { const event: CustomEvent = new CustomEvent('canvas.groupped', { bubbles: false, @@ -235,13 +221,14 @@ export class CanvasViewImpl implements CanvasView, Listener { private onFindObject(e: MouseEvent): void { if (e.which === 1 || e.which === 0) { - const [x, y] = translateToSVG(this.background, [e.clientX, e.clientY]); + const { offset } = this.controller.geometry; + const [x, y] = translateToSVG(this.content, [e.clientX, e.clientY]); const event: CustomEvent = new CustomEvent('canvas.find', { bubbles: false, cancelable: true, detail: { - x, - y, + x: x - offset, + y: y - offset, states: this.controller.objects, }, }); @@ -252,23 +239,208 @@ export class CanvasViewImpl implements CanvasView, Listener { } } + private onFocusRegion(x: number, y: number, width: number, height: number): void { + // First of all, compute and apply scale + let scale = null; + + if ((this.geometry.angle / 90) % 2) { + // 90, 270, .. + scale = Math.min(Math.max(Math.min( + this.geometry.canvas.width / height, + this.geometry.canvas.height / width, + ), FrameZoom.MIN), FrameZoom.MAX); + } else { + scale = Math.min(Math.max(Math.min( + this.geometry.canvas.width / width, + this.geometry.canvas.height / height, + ), FrameZoom.MIN), FrameZoom.MAX); + } + + this.geometry = { ...this.geometry, scale }; + this.transformCanvas(); + + const [canvasX, canvasY] = translateFromSVG(this.content, [ + x + width / 2, + y + height / 2, + ]); + + const [cx, cy] = [ + this.canvas.clientWidth / 2 + this.canvas.offsetLeft, + this.canvas.clientHeight / 2 + this.canvas.offsetTop, + ]; + + const dragged = { + ...this.geometry, + top: this.geometry.top + cy - canvasY, + left: this.geometry.left + cx - canvasX, + scale, + }; + + this.controller.geometry = dragged; + this.geometry = dragged; + this.moveCanvas(); + } + + private moveCanvas(): void { + for (const obj of [this.background, this.grid, this.loadingAnimation]) { + obj.style.top = `${this.geometry.top}px`; + obj.style.left = `${this.geometry.left}px`; + } + + for (const obj of [this.content, this.text]) { + obj.style.top = `${this.geometry.top - this.geometry.offset}px`; + obj.style.left = `${this.geometry.left - this.geometry.offset}px`; + } + + // Transform handlers + this.drawHandler.transform(this.geometry); + this.editHandler.transform(this.geometry); + this.zoomHandler.transform(this.geometry); + } + + private transformCanvas(): void { + // Transform canvas + for (const obj of [this.background, this.grid, this.loadingAnimation, this.content]) { + obj.style.transform = `scale(${this.geometry.scale}) rotate(${this.geometry.angle}deg)`; + } + + // Transform grid + this.gridPath.setAttribute('stroke-width', `${consts.BASE_GRID_WIDTH / (this.geometry.scale)}px`); + + // Transform all shape points + for (const element of window.document.getElementsByClassName('svg_select_points')) { + element.setAttribute( + 'stroke-width', + `${consts.POINTS_STROKE_WIDTH / this.geometry.scale}`, + ); + element.setAttribute( + 'r', + `${consts.BASE_POINT_SIZE / this.geometry.scale}`, + ); + } + + for (const element of + window.document.getElementsByClassName('cvat_canvas_selected_point')) { + const previousWidth = element.getAttribute('stroke-width') as string; + element.setAttribute( + 'stroke-width', + `${+previousWidth * 2}`, + ); + } + + // Transform all drawn shapes + for (const key in this.svgShapes) { + if (Object.prototype.hasOwnProperty.call(this.svgShapes, key)) { + const object = this.svgShapes[key]; + object.attr({ + 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, + }); + } + } + + // Transform all text + for (const key in this.svgShapes) { + if (Object.prototype.hasOwnProperty.call(this.svgShapes, key) + && Object.prototype.hasOwnProperty.call(this.svgTexts, key)) { + this.updateTextPosition( + this.svgTexts[key], + this.svgShapes[key], + ); + } + } + + // Transform handlers + this.drawHandler.transform(this.geometry); + this.editHandler.transform(this.geometry); + } + + private resizeCanvas(): void { + for (const obj of [this.background, this.grid, this.loadingAnimation]) { + obj.style.width = `${this.geometry.image.width}px`; + obj.style.height = `${this.geometry.image.height}px`; + } + + for (const obj of [this.content, this.text]) { + obj.style.width = `${this.geometry.image.width + this.geometry.offset * 2}px`; + obj.style.height = `${this.geometry.image.height + this.geometry.offset * 2}px`; + } + } + + + private setupObjects(states: any[]): void { + const { offset } = this.controller.geometry; + const translate = (points: number[]): number[] => points + .map((coord: number): number => coord + offset); + + const created = []; + const updated = []; + for (const state of states) { + if (!(state.clientID in this.drawnStates)) { + created.push(state); + } else { + const drawnState = this.drawnStates[state.clientID]; + if (drawnState.updated !== state.updated || drawnState.frame !== state.frame) { + updated.push(state); + } + } + } + const newIDs = states.map((state: any): number => state.clientID); + const deleted = Object.keys(this.drawnStates).map((clientID: string): number => +clientID) + .filter((id: number): boolean => !newIDs.includes(id)) + .map((id: number): any => this.drawnStates[id]); + + + if (this.activeElement.clientID !== null) { + this.deactivate(); + } + + for (const state of deleted) { + if (state.clientID in this.svgTexts) { + this.svgTexts[state.clientID].remove(); + } + + this.svgShapes[state.clientID].off('click.canvas'); + this.svgShapes[state.clientID].remove(); + delete this.drawnStates[state.clientID]; + } + + this.addObjects(created, translate); + this.updateObjects(updated, translate); + this.sortObjects(); + + if (this.controller.activeElement.clientID !== null) { + const { clientID } = this.controller.activeElement; + if (states.map((state: any): number => state.clientID).includes(clientID)) { + this.activate(this.controller.activeElement); + } + } + } + private selectize(value: boolean, shape: SVG.Element): void { const self = this; function dblClickHandler(e: MouseEvent): void { const pointID = Array.prototype.indexOf - .call((e.target as HTMLElement).parentElement.children, e.target); + .call(((e.target as HTMLElement).parentElement as HTMLElement).children, e.target); - if (self.activeElement) { + if (self.activeElement.clientID !== null) { + const [state] = self.controller.objects + .filter((_state: any): boolean => ( + _state.clientID === self.activeElement.clientID + )); if (e.ctrlKey) { - const { points } = self.activeElement.state; + const { points } = state; self.onEditDone( - self.activeElement.state, + state, points.slice(0, pointID * 2).concat(points.slice(pointID * 2 + 2)), ); } else if (e.shiftKey) { + self.canvas.dispatchEvent(new CustomEvent('canvas.editstart', { + bubbles: false, + cancelable: true, + })); + self.mode = Mode.EDIT; - const { state } = self.activeElement; self.deactivate(); self.editHandler.edit({ enabled: true, @@ -277,6 +449,8 @@ export class CanvasViewImpl implements CanvasView, Listener { }); } } + + e.preventDefault(); } if (value) { @@ -288,27 +462,28 @@ export class CanvasViewImpl implements CanvasView, Listener { const circle: SVG.Circle = this.nested .circle(this.options.pointSize) .stroke('black') - .fill(shape.node.getAttribute('fill') || 'inherit') + .fill('inherit') .center(cx, cy) .attr({ + 'fill-opacity': 1, 'stroke-width': consts.POINTS_STROKE_WIDTH / self.geometry.scale, }); - circle.node.addEventListener('mouseenter', (): void => { + circle.on('mouseenter', (): void => { circle.attr({ 'stroke-width': consts.POINTS_SELECTED_STROKE_WIDTH / self.geometry.scale, }); - circle.node.addEventListener('dblclick', dblClickHandler); + circle.on('dblclick', dblClickHandler); circle.addClass('cvat_canvas_selected_point'); }); - circle.node.addEventListener('mouseleave', (): void => { + circle.on('mouseleave', (): void => { circle.attr({ 'stroke-width': consts.POINTS_STROKE_WIDTH / self.geometry.scale, }); - circle.node.removeEventListener('dblclick', dblClickHandler); + circle.off('dblclick', dblClickHandler); circle.removeClass('cvat_canvas_selected_point'); }); @@ -320,13 +495,23 @@ export class CanvasViewImpl implements CanvasView, Listener { deepSelect: true, }); } + + const handler = shape.remember('_selectHandler'); + if (handler && handler.nested) { + handler.nested.fill(shape.attr('fill')); + } } public constructor(model: CanvasModel & Master, controller: CanvasController) { this.controller = controller; + this.geometry = controller.geometry; this.svgShapes = {}; this.svgTexts = {}; - this.activeElement = null; + this.drawnStates = {}; + this.activeElement = { + clientID: null, + attributeID: null, + }; this.mode = Mode.IDLE; // Create HTML elements @@ -334,7 +519,8 @@ export class CanvasViewImpl implements CanvasView, Listener { .createElementNS('http://www.w3.org/2000/svg', 'svg'); this.text = window.document.createElementNS('http://www.w3.org/2000/svg', 'svg'); this.adoptedText = (SVG.adopt((this.text as any as HTMLElement)) as SVG.Container); - this.background = window.document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + this.background = window.document.createElement('canvas'); + // window.document.createElementNS('http://www.w3.org/2000/svg', 'svg'); this.grid = window.document.createElementNS('http://www.w3.org/2000/svg', 'svg'); this.gridPath = window.document.createElementNS('http://www.w3.org/2000/svg', 'path'); @@ -364,7 +550,8 @@ export class CanvasViewImpl implements CanvasView, Listener { this.grid.setAttribute('version', '2'); this.gridPath.setAttribute('d', 'M 1000 0 L 0 0 0 1000'); this.gridPath.setAttribute('fill', 'none'); - this.gridPath.setAttribute('stroke-width', '1.5'); + this.gridPath.setAttribute('stroke-width', `${consts.BASE_GRID_WIDTH}`); + this.gridPath.setAttribute('opacity', 'inherit'); this.gridPattern.setAttribute('id', 'cvat_canvas_grid_pattern'); this.gridPattern.setAttribute('width', '100'); this.gridPattern.setAttribute('height', '100'); @@ -396,36 +583,17 @@ export class CanvasViewImpl implements CanvasView, Listener { this.canvas.appendChild(this.content); - // A little hack to get size after first mounting - // http://www.backalleycoder.com/2012/04/25/i-want-a-damnodeinserted/ const self = this; - const canvasFirstMounted = (event: AnimationEvent): void => { - if (event.animationName === 'loadingAnimation') { - const { geometry } = this.controller; - geometry.canvas = { - height: self.canvas.clientHeight, - width: self.canvas.clientWidth, - }; - - this.controller.geometry = geometry; - this.geometry = geometry; - self.canvas.removeEventListener('animationstart', canvasFirstMounted); - } - }; - - this.canvas.addEventListener('animationstart', canvasFirstMounted); // Setup API handlers this.drawHandler = new DrawHandlerImpl( this.onDrawDone.bind(this), this.adoptedContent, this.adoptedText, - this.background, ); this.editHandler = new EditHandlerImpl( this.onEditDone.bind(this), this.adoptedContent, - this.background, ); this.mergeHandler = new MergeHandlerImpl( this.onMergeDone.bind(this), @@ -443,7 +611,11 @@ export class CanvasViewImpl implements CanvasView, Listener { this.onFindObject.bind(this), this.adoptedContent, ); - + this.zoomHandler = new ZoomHandlerImpl( + this.onFocusRegion.bind(this), + this.adoptedContent, + this.geometry, + ); // Setup event handlers this.content.addEventListener('dblclick', (e: MouseEvent): void => { @@ -453,10 +625,10 @@ export class CanvasViewImpl implements CanvasView, Listener { }); this.content.addEventListener('mousedown', (event): void => { - if ((event.which === 1 && this.mode === Mode.IDLE) || (event.which === 2)) { - self.controller.enableDrag(event.clientX, event.clientY); - - event.preventDefault(); + if ([1, 2].includes(event.which)) { + if (![Mode.ZOOM_CANVAS, Mode.GROUP].includes(this.mode) || event.which === 2) { + self.controller.enableDrag(event.clientX, event.clientY); + } } }); @@ -467,8 +639,13 @@ export class CanvasViewImpl implements CanvasView, Listener { }); this.content.addEventListener('wheel', (event): void => { - const point = translateToSVG(self.background, [event.clientX, event.clientY]); - self.controller.zoom(point[0], point[1], event.deltaY > 0 ? -1 : 1); + const { offset } = this.controller.geometry; + const point = translateToSVG(this.content, [event.clientX, event.clientY]); + self.controller.zoom(point[0] - offset, point[1] - offset, event.deltaY > 0 ? -1 : 1); + this.canvas.dispatchEvent(new CustomEvent('canvas.zoom', { + bubbles: false, + cancelable: true, + })); event.preventDefault(); }); @@ -478,13 +655,14 @@ export class CanvasViewImpl implements CanvasView, Listener { if (this.mode !== Mode.IDLE) return; if (e.ctrlKey || e.shiftKey) return; - const [x, y] = translateToSVG(this.background, [e.clientX, e.clientY]); + const { offset } = this.controller.geometry; + const [x, y] = translateToSVG(this.content, [e.clientX, e.clientY]); const event: CustomEvent = new CustomEvent('canvas.moved', { bubbles: false, cancelable: true, detail: { - x, - y, + x: x - offset, + y: y - offset, states: this.controller.objects, }, }); @@ -497,219 +675,127 @@ export class CanvasViewImpl implements CanvasView, Listener { } public notify(model: CanvasModel & Master, reason: UpdateReasons): void { - function transform(): void { - // Transform canvas - for (const obj of [this.background, this.grid, this.loadingAnimation, this.content]) { - obj.style.transform = `scale(${this.geometry.scale}) rotate(${this.geometry.angle}deg)`; - } - - // Transform grid - this.gridPath.setAttribute('stroke-width', `${consts.BASE_GRID_WIDTH / (this.geometry.scale)}px`); - - // Transform all shape points - for (const element of window.document.getElementsByClassName('svg_select_points')) { - element.setAttribute( - 'stroke-width', - `${consts.POINTS_STROKE_WIDTH / this.geometry.scale}`, - ); - element.setAttribute( - 'r', - `${consts.BASE_POINT_SIZE / this.geometry.scale}`, - ); - } - - for (const element of - window.document.getElementsByClassName('cvat_canvas_selected_point')) { - element.setAttribute( - 'stroke-width', - `${+element.getAttribute('stroke-width') * 2}`, - ); - } - - // Transform all drawn shapes - for (const key in this.svgShapes) { - if (Object.prototype.hasOwnProperty.call(this.svgShapes, key)) { - const object = this.svgShapes[key]; - if (object.attr('stroke-width')) { - object.attr({ - 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, - }); - } - } - } - - // Transform all text - for (const key in this.svgShapes) { - if (Object.prototype.hasOwnProperty.call(this.svgShapes, key) - && Object.prototype.hasOwnProperty.call(this.svgTexts, key)) { - this.updateTextPosition( - this.svgTexts[key], - this.svgShapes[key], - ); - } - } - - // Transform handlers - this.drawHandler.transform(this.geometry); - this.editHandler.transform(this.geometry); - } - - function resize(): void { - for (const obj of [this.background, this.grid, this.loadingAnimation]) { - obj.style.width = `${this.geometry.image.width}px`; - obj.style.height = `${this.geometry.image.height}px`; - } - - for (const obj of [this.content, this.text]) { - obj.style.width = `${this.geometry.image.width + this.geometry.offset * 2}px`; - obj.style.height = `${this.geometry.image.height + this.geometry.offset * 2}px`; - } - } - - function move(): void { - for (const obj of [this.background, this.grid, this.loadingAnimation]) { - obj.style.top = `${this.geometry.top}px`; - obj.style.left = `${this.geometry.left}px`; - } - - for (const obj of [this.content, this.text]) { - obj.style.top = `${this.geometry.top - this.geometry.offset}px`; - obj.style.left = `${this.geometry.left - this.geometry.offset}px`; - } - - // Transform handlers - this.drawHandler.transform(this.geometry); - this.editHandler.transform(this.geometry); - } - - function computeFocus(focusData: FocusData): void { - // This computation cann't be done in the model because of lack of data - const object = this.svgShapes[focusData.clientID]; - if (!object) { - return; - } - - // First of all, compute and apply scale - - let scale = null; - const bbox: SVG.BBox = object.bbox(); - if ((this.geometry.angle / 90) % 2) { - // 90, 270, .. - scale = Math.min(Math.max(Math.min( - this.geometry.canvas.width / bbox.height, - this.geometry.canvas.height / bbox.width, - ), FrameZoom.MIN), FrameZoom.MAX); - } else { - scale = Math.min(Math.max(Math.min( - this.geometry.canvas.width / bbox.width, - this.geometry.canvas.height / bbox.height, - ), FrameZoom.MIN), FrameZoom.MAX); - } - - this.geometry = { ...this.geometry, scale }; - transform.call(this); - - const [x, y] = translateFromSVG(this.content, [ - bbox.x + bbox.width / 2, - bbox.y + bbox.height / 2, - ]); - - const [cx, cy] = [ - this.canvas.clientWidth / 2 + this.canvas.offsetLeft, - this.canvas.clientHeight / 2 + this.canvas.offsetTop, - ]; - - const dragged = { - ...this.geometry, - top: this.geometry.top + cy - y, - left: this.geometry.left + cx - x, - scale, - }; - - this.controller.geometry = dragged; - this.geometry = dragged; - move.call(this); - } - - function setupObjects(objects: any[]): void { - const ctm = this.content.getScreenCTM() - .inverse().multiply(this.background.getScreenCTM()); - - this.deactivate(); - - // TODO: Compute difference - - // Instead of simple clearing let's remove all objects properly - for (const id of Object.keys(this.svgShapes)) { - if (id in this.svgTexts) { - this.svgTexts[id].remove(); - } - - this.svgShapes[id].remove(); - } - - this.svgTexts = {}; - this.svgShapes = {}; - - this.addObjects(ctm, objects); - // TODO: Update objects - // TODO: Delete objects - } - this.geometry = this.controller.geometry; - if (reason === UpdateReasons.IMAGE) { - if (!model.image.length) { + if (reason === UpdateReasons.IMAGE_CHANGED) { + const { image } = model; + if (!image) { this.loadingAnimation.classList.remove('cvat_canvas_hidden'); } else { this.loadingAnimation.classList.add('cvat_canvas_hidden'); - this.background.style.backgroundImage = `url("${model.image}")`; - move.call(this); - resize.call(this); - transform.call(this); + const ctx = this.background.getContext('2d'); + this.background.setAttribute('width', `${image.width}px`); + this.background.setAttribute('height', `${image.height}px`); + if (ctx) { + ctx.drawImage(image, 0, 0); + } + this.moveCanvas(); + this.resizeCanvas(); + this.transformCanvas(); + } + } else if (reason === UpdateReasons.FITTED_CANVAS) { + // Canvas geometry is going to be changed. Old object positions aren't valid any more + this.setupObjects([]); + this.moveCanvas(); + this.resizeCanvas(); + } else if ([UpdateReasons.IMAGE_ZOOMED, UpdateReasons.IMAGE_FITTED].includes(reason)) { + this.moveCanvas(); + this.transformCanvas(); + } else if (reason === UpdateReasons.IMAGE_MOVED) { + this.moveCanvas(); + } else if ([UpdateReasons.OBJECTS_UPDATED, UpdateReasons.SET_Z_LAYER].includes(reason)) { + if (this.mode === Mode.GROUP) { + this.groupHandler.resetSelectedObjects(); + } + this.setupObjects(this.controller.objects); + if (this.mode === Mode.MERGE) { + this.mergeHandler.repeatSelection(); } - } else if (reason === UpdateReasons.ZOOM || reason === UpdateReasons.FIT) { - move.call(this); - transform.call(this); - } else if (reason === UpdateReasons.MOVE) { - move.call(this); - } else if (reason === UpdateReasons.OBJECTS) { - setupObjects.call(this, this.controller.objects); const event: CustomEvent = new CustomEvent('canvas.setup'); this.canvas.dispatchEvent(event); - } else if (reason === UpdateReasons.GRID) { + } else if (reason === UpdateReasons.GRID_UPDATED) { const size: Size = this.geometry.grid; this.gridPattern.setAttribute('width', `${size.width}`); this.gridPattern.setAttribute('height', `${size.height}`); - } else if (reason === UpdateReasons.FOCUS) { - computeFocus.call(this, this.controller.focusData); - } else if (reason === UpdateReasons.ACTIVATE) { + } else if (reason === UpdateReasons.SHAPE_FOCUSED) { + const { + padding, + clientID, + } = this.controller.focusData; + const object = this.svgShapes[clientID]; + if (object) { + const bbox: SVG.BBox = object.bbox(); + this.onFocusRegion(bbox.x - padding, bbox.y - padding, + bbox.width + padding, bbox.height + padding); + } + } else if (reason === UpdateReasons.SHAPE_ACTIVATED) { this.activate(this.controller.activeElement); + } else if (reason === UpdateReasons.DRAG_CANVAS) { + if (this.mode === Mode.DRAG_CANVAS) { + this.canvas.dispatchEvent(new CustomEvent('canvas.dragstart', { + bubbles: false, + cancelable: true, + })); + this.canvas.style.cursor = 'move'; + } else { + this.canvas.dispatchEvent(new CustomEvent('canvas.dragstop', { + bubbles: false, + cancelable: true, + })); + this.canvas.style.cursor = ''; + } + } else if (reason === UpdateReasons.ZOOM_CANVAS) { + if (this.mode === Mode.ZOOM_CANVAS) { + this.canvas.dispatchEvent(new CustomEvent('canvas.zoomstart', { + bubbles: false, + cancelable: true, + })); + this.canvas.style.cursor = 'zoom-in'; + this.zoomHandler.zoom(); + } else { + this.canvas.dispatchEvent(new CustomEvent('canvas.zoomstop', { + bubbles: false, + cancelable: true, + })); + this.canvas.style.cursor = ''; + this.zoomHandler.cancel(); + } } else if (reason === UpdateReasons.DRAW) { const data: DrawData = this.controller.drawData; - if (data.enabled) { + if (data.enabled && this.mode === Mode.IDLE) { + this.canvas.style.cursor = 'crosshair'; this.mode = Mode.DRAW; - this.deactivate(); + this.drawHandler.draw(data, this.geometry); + } else { + this.canvas.style.cursor = ''; + if (this.mode !== Mode.IDLE) { + this.drawHandler.draw(data, this.geometry); + } } - this.drawHandler.draw(data, this.geometry); } else if (reason === UpdateReasons.MERGE) { const data: MergeData = this.controller.mergeData; if (data.enabled) { + this.canvas.style.cursor = 'copy'; this.mode = Mode.MERGE; - this.deactivate(); + } else { + this.canvas.style.cursor = ''; } this.mergeHandler.merge(data); } else if (reason === UpdateReasons.SPLIT) { const data: SplitData = this.controller.splitData; if (data.enabled) { + this.canvas.style.cursor = 'copy'; this.mode = Mode.SPLIT; - this.deactivate(); + } else { + this.canvas.style.cursor = ''; } this.splitHandler.split(data); } else if (reason === UpdateReasons.GROUP) { const data: GroupData = this.controller.groupData; if (data.enabled) { + this.canvas.style.cursor = 'copy'; this.mode = Mode.GROUP; - this.deactivate(); + } else { + this.canvas.style.cursor = ''; } this.groupHandler.group(data); } else if (reason === UpdateReasons.SELECT) { @@ -731,7 +817,20 @@ export class CanvasViewImpl implements CanvasView, Listener { this.groupHandler.cancel(); } else if (this.mode === Mode.EDIT) { this.editHandler.cancel(); + } else if (this.mode === Mode.DRAG_CANVAS) { + this.canvas.dispatchEvent(new CustomEvent('canvas.dragstop', { + bubbles: false, + cancelable: true, + })); + } else if (this.mode === Mode.ZOOM_CANVAS) { + this.zoomHandler.cancel(); + this.canvas.dispatchEvent(new CustomEvent('canvas.zoomstop', { + bubbles: false, + cancelable: true, + })); } + this.mode = Mode.IDLE; + this.canvas.style.cursor = ''; } } @@ -739,20 +838,118 @@ export class CanvasViewImpl implements CanvasView, Listener { return this.canvas; } - private addObjects(ctm: SVGMatrix, states: any[]): void { + private saveState(state: any): void { + this.drawnStates[state.clientID] = { + clientID: state.clientID, + outside: state.outside, + occluded: state.occluded, + hidden: state.hidden, + lock: state.lock, + shapeType: state.shapeType, + points: [...state.points], + attributes: { ...state.attributes }, + zOrder: state.zOrder, + pinned: state.pinned, + }; + } + + private updateObjects(states: any[], translate: (points: number[]) => number[]): void { + for (const state of states) { + const { clientID } = state; + const drawnState = this.drawnStates[clientID]; + + if (drawnState.hidden !== state.hidden || drawnState.outside !== state.outside) { + const none = state.hidden || state.outside; + if (state.shapeType === 'points') { + this.svgShapes[clientID].remember('_selectHandler').nested + .style('display', none ? 'none' : ''); + } else { + this.svgShapes[clientID].style('display', none ? 'none' : ''); + } + } + + if (drawnState.zOrder !== state.zOrder) { + if (state.shapeType === 'points') { + this.svgShapes[clientID].remember('_selectHandler').nested + .attr('data-z-order', state.zOrder); + } else { + this.svgShapes[clientID].attr('data-z-order', state.zOrder); + } + } + + if (drawnState.occluded !== state.occluded) { + if (state.occluded) { + this.svgShapes[clientID].addClass('cvat_canvas_shape_occluded'); + } else { + this.svgShapes[clientID].removeClass('cvat_canvas_shape_occluded'); + } + } + + if (drawnState.pinned !== state.pinned && this.activeElement.clientID !== null) { + const activeElement = { ...this.activeElement }; + this.deactivate(); + this.activate(activeElement); + } + + if (state.points + .some((p: number, id: number): boolean => p !== drawnState.points[id]) + ) { + const translatedPoints: number[] = translate(state.points); + + if (state.shapeType === 'rectangle') { + const [xtl, ytl, xbr, ybr] = translatedPoints; + + this.svgShapes[clientID].attr({ + x: xtl, + y: ytl, + width: xbr - xtl, + height: ybr - ytl, + }); + } else { + const stringified = translatedPoints.reduce( + (acc: string, val: number, idx: number): string => { + if (idx % 2) { + return `${acc}${val} `; + } + + return `${acc}${val},`; + }, '', + ); + (this.svgShapes[clientID] as any).clear(); + this.svgShapes[clientID].attr('points', stringified); + + if (state.shapeType === 'points') { + this.selectize(false, this.svgShapes[clientID]); + this.setupPoints(this.svgShapes[clientID] as SVG.PolyLine, state); + } + } + } + + for (const attrID of Object.keys(state.attributes)) { + if (state.attributes[attrID] !== drawnState.attributes[attrID]) { + const text = this.svgTexts[state.clientID]; + if (text) { + const [span] = this.svgTexts[state.clientID].node + .querySelectorAll(`[attrID="${attrID}"]`) as any as SVGTSpanElement[]; + if (span && span.textContent) { + const prefix = span.textContent.split(':').slice(0, -1).join(':'); + span.textContent = `${prefix}: ${state.attributes[attrID]}`; + } + } + } + } + + this.saveState(state); + } + } + + private addObjects(states: any[], translate: (points: number[]) => number[]): void { for (const state of states) { if (state.objectType === 'tag') { this.addTag(state); } else { const points: number[] = (state.points as number[]); - const translatedPoints: number[] = []; - for (let i = 0; i <= points.length - 1; i += 2) { - let point: SVGPoint = this.background.createSVGPoint(); - point.x = points[i]; - point.y = points[i + 1]; - point = point.matrixTransform(ctm); - translatedPoints.push(point.x, point.y); - } + const translatedPoints: number[] = translate(points); // TODO: Use enums after typification cvat-core if (state.shapeType === 'rectangle') { @@ -781,47 +978,86 @@ export class CanvasViewImpl implements CanvasView, Listener { } } - // TODO: Use enums after typification cvat-core - if (state.visibility === 'all') { - this.svgTexts[state.clientID] = this.addText(state); - this.updateTextPosition( - this.svgTexts[state.clientID], - this.svgShapes[state.clientID], - ); - } + this.svgShapes[state.clientID].on('click.canvas', (): void => { + this.canvas.dispatchEvent(new CustomEvent('canvas.clicked', { + bubbles: false, + cancelable: true, + detail: { + state, + }, + })); + }); } + + this.saveState(state); + } + } + + private sortObjects(): void { + // TODO: Can be significantly optimized + const states = Array.from( + this.content.getElementsByClassName('cvat_canvas_shape'), + ).map((state: SVGElement): [SVGElement, number] => ( + [state, +state.getAttribute('data-z-order')] + )); + + const needSort = states.some((pair): boolean => pair[1] !== states[0][1]); + if (!states.length || !needSort) { + return; } + + const sorted = states.sort((a, b): number => a[1] - b[1]); + sorted.forEach((pair): void => { + this.content.appendChild(pair[0]); + }); + + this.content.prepend(...sorted.map((pair): SVGElement => pair[0])); } private deactivate(): void { - if (this.activeElement) { - const { state } = this.activeElement; - const shape = this.svgShapes[this.activeElement.state.clientID]; + if (this.activeElement.clientID !== null) { + const { clientID } = this.activeElement; + const drawnState = this.drawnStates[clientID]; + const shape = this.svgShapes[clientID]; + shape.removeClass('cvat_canvas_shape_activated'); - (shape as any).draggable(false); + if (!drawnState.pinned) { + (shape as any).off('dragstart'); + (shape as any).off('dragend'); + (shape as any).draggable(false); + } - if (state.shapeType !== 'points') { + if (drawnState.shapeType !== 'points') { this.selectize(false, shape); } + (shape as any).off('resizestart'); + (shape as any).off('resizing'); + (shape as any).off('resizedone'); (shape as any).resize(false); - // Hide text only if it is hidden by settings - const text = this.svgTexts[state.clientID]; - if (text && state.visibility === 'shape') { + // TODO: Hide text only if it is hidden by settings + const text = this.svgTexts[clientID]; + if (text) { text.remove(); - delete this.svgTexts[state.clientID]; + delete this.svgTexts[clientID]; } - this.activeElement = null; + + this.sortObjects(); + + this.activeElement = { + clientID: null, + attributeID: null, + }; } } private activate(activeElement: ActiveElement): void { // Check if other element have been already activated - if (this.activeElement) { + if (this.activeElement.clientID !== null) { // Check if it is the same element - if (this.activeElement.state.clientID === activeElement.clientID) { + if (this.activeElement.clientID === activeElement.clientID) { return; } @@ -829,18 +1065,27 @@ export class CanvasViewImpl implements CanvasView, Listener { this.deactivate(); } - const state = this.controller.objects - .filter((el): boolean => el.clientID === activeElement.clientID)[0]; - this.activeElement = { - attributeID: activeElement.attributeID, - state, - }; + const { clientID } = activeElement; + if (clientID === null) { + return; + } - const shape = this.svgShapes[activeElement.clientID]; - shape.addClass('cvat_canvas_shape_activated'); - let text = this.svgTexts[activeElement.clientID]; - // Draw text if it's hidden by default - if (!text && state.visibility === 'shape') { + const [state] = this.controller.objects + .filter((_state: any): boolean => _state.clientID === clientID); + + if (state && state.shapeType === 'points') { + this.svgShapes[clientID].remember('_selectHandler').nested + .style('pointer-events', state.lock ? 'none' : ''); + } + + if (!state || state.hidden || state.outside) { + return; + } + + this.activeElement = { ...activeElement }; + const shape = this.svgShapes[clientID]; + let text = this.svgTexts[clientID]; + if (!text) { text = this.addText(state); this.svgTexts[state.clientID] = text; this.updateTextPosition( @@ -849,43 +1094,56 @@ export class CanvasViewImpl implements CanvasView, Listener { ); } - const self = this; - this.content.append(shape.node); - (shape as any).draggable().on('dragstart', (): void => { - this.mode = Mode.DRAG; - if (text) { - text.addClass('cvat_canvas_hidden'); - } - }).on('dragend', (e: CustomEvent): void => { - if (text) { - text.removeClass('cvat_canvas_hidden'); - self.updateTextPosition( - text, - shape, - ); - } + if (state.lock) { + return; + } - this.mode = Mode.IDLE; + shape.addClass('cvat_canvas_shape_activated'); + if (state.shapeType === 'points') { + this.content.append(this.svgShapes[clientID] + .remember('_selectHandler').nested.node); + } else { + this.content.append(shape.node); + } - const p1 = e.detail.handler.startPoints.point; - const p2 = e.detail.p; - const delta = 1; - if (Math.sqrt(((p1.x - p2.x) ** 2) + ((p1.y - p2.y) ** 2)) >= delta) { - const points = pointsToArray( - shape.attr('points') || `${shape.attr('x')},${shape.attr('y')} ` - + `${shape.attr('x') + shape.attr('width')},` - + `${shape.attr('y') + shape.attr('height')}`, - ); + if (!state.pinned) { + (shape as any).draggable().on('dragstart', (): void => { + this.mode = Mode.DRAG; + if (text) { + text.addClass('cvat_canvas_hidden'); + } + }).on('dragend', (e: CustomEvent): void => { + if (text) { + text.removeClass('cvat_canvas_hidden'); + this.updateTextPosition( + text, + shape, + ); + } - this.onEditDone(state, translateBetweenSVG(this.content, this.background, points)); - } - }); + this.mode = Mode.IDLE; + const p1 = e.detail.handler.startPoints.point; + const p2 = e.detail.p; + const delta = 1; + const { offset } = this.controller.geometry; + if (Math.sqrt(((p1.x - p2.x) ** 2) + ((p1.y - p2.y) ** 2)) >= delta) { + const points = pointsToArray( + shape.attr('points') || `${shape.attr('x')},${shape.attr('y')} ` + + `${shape.attr('x') + shape.attr('width')},` + + `${shape.attr('y') + shape.attr('height')}`, + ).map((x: number): number => x - offset); + + this.drawnStates[state.clientID].points = points; + this.onEditDone(state, points); + } + }); + } if (state.shapeType !== 'points') { this.selectize(true, shape); } - let shapeSizeElement: ShapeSizeElement = null; + let shapeSizeElement: ShapeSizeElement | null = null; let resized = false; (shape as any).resize().on('resizestart', (): void => { this.mode = Mode.RESIZE; @@ -908,7 +1166,7 @@ export class CanvasViewImpl implements CanvasView, Listener { if (text) { text.removeClass('cvat_canvas_hidden'); - self.updateTextPosition( + this.updateTextPosition( text, shape, ); @@ -917,15 +1175,26 @@ export class CanvasViewImpl implements CanvasView, Listener { this.mode = Mode.IDLE; if (resized) { + const { offset } = this.controller.geometry; + const points = pointsToArray( shape.attr('points') || `${shape.attr('x')},${shape.attr('y')} ` + `${shape.attr('x') + shape.attr('width')},` + `${shape.attr('y') + shape.attr('height')}`, - ); + ).map((x: number): number => x - offset); - this.onEditDone(state, translateBetweenSVG(this.content, this.background, points)); + this.drawnStates[state.clientID].points = points; + this.onEditDone(state, points); } }); + + this.canvas.dispatchEvent(new CustomEvent('canvas.activated', { + bubbles: false, + cancelable: true, + detail: { + state, + }, + })); } // Update text position after corresponding box has been moved, resized, etc. @@ -994,9 +1263,9 @@ export class CanvasViewImpl implements CanvasView, Listener { id: `cvat_canvas_shape_${state.clientID}`, fill: state.color, 'shape-rendering': 'geometricprecision', - stroke: darker(state.color, 50), + stroke: state.color, 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, - zOrder: state.zOrder, + 'data-z-order': state.zOrder, }).move(xtl, ytl) .addClass('cvat_canvas_shape'); @@ -1004,6 +1273,10 @@ export class CanvasViewImpl implements CanvasView, Listener { rect.addClass('cvat_canvas_shape_occluded'); } + if (state.hidden || state.outside) { + rect.style('display', 'none'); + } + return rect; } @@ -1014,15 +1287,19 @@ export class CanvasViewImpl implements CanvasView, Listener { id: `cvat_canvas_shape_${state.clientID}`, fill: state.color, 'shape-rendering': 'geometricprecision', - stroke: darker(state.color, 50), + stroke: state.color, 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, - zOrder: state.zOrder, + 'data-z-order': state.zOrder, }).addClass('cvat_canvas_shape'); if (state.occluded) { polygon.addClass('cvat_canvas_shape_occluded'); } + if (state.hidden || state.outside) { + polygon.style('display', 'none'); + } + return polygon; } @@ -1033,18 +1310,43 @@ export class CanvasViewImpl implements CanvasView, Listener { id: `cvat_canvas_shape_${state.clientID}`, fill: state.color, 'shape-rendering': 'geometricprecision', - stroke: darker(state.color, 50), + stroke: state.color, 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, - zOrder: state.zOrder, + 'data-z-order': state.zOrder, }).addClass('cvat_canvas_shape'); if (state.occluded) { polyline.addClass('cvat_canvas_shape_occluded'); } + if (state.hidden || state.outside) { + polyline.style('display', 'none'); + } + return polyline; } + private setupPoints(basicPolyline: SVG.PolyLine, state: any): any { + this.selectize(true, basicPolyline); + + const group: SVG.G = basicPolyline.remember('_selectHandler').nested + .addClass('cvat_canvas_shape').attr({ + clientID: state.clientID, + id: `cvat_canvas_shape_${state.clientID}`, + 'data-z-order': state.zOrder, + }); + + group.on('click.canvas', (event: MouseEvent): void => { + // Need to redispatch the event on another element + basicPolyline.fire(new MouseEvent('click', event)); + }); + + group.bbox = basicPolyline.bbox.bind(basicPolyline); + group.clone = basicPolyline.clone.bind(basicPolyline); + + return group; + } + private addPoints(points: string, state: any): SVG.PolyLine { const shape = this.adoptedContent.polyline(points).attr({ 'color-rendering': 'optimizeQuality', @@ -1056,19 +1358,11 @@ export class CanvasViewImpl implements CanvasView, Listener { opacity: 0, }); - this.selectize(true, shape); + const group = this.setupPoints(shape, state); - const group = shape.remember('_selectHandler').nested - .addClass('cvat_canvas_shape').attr({ - clientID: state.clientID, - zOrder: state.zOrder, - id: `cvat_canvas_shape_${state.clientID}`, - fill: state.color, - }).style({ - 'fill-opacity': 1, - }); - group.bbox = shape.bbox.bind(shape); - group.clone = shape.clone.bind(shape); + if (state.hidden || state.outside) { + group.style('display', 'none'); + } shape.remove = (): SVG.PolyLine => { this.selectize(false, shape); diff --git a/cvat-canvas/src/typescript/consts.ts b/cvat-canvas/src/typescript/consts.ts index 69a926d15ea..1c36351dddf 100644 --- a/cvat-canvas/src/typescript/consts.ts +++ b/cvat-canvas/src/typescript/consts.ts @@ -1,10 +1,9 @@ -/* -* Copyright (C) 2019 Intel Corporation -* SPDX-License-Identifier: MIT -*/ +// Copyright (C) 2019-2020 Intel Corporation +// +// SPDX-License-Identifier: MIT -const BASE_STROKE_WIDTH = 2; -const BASE_GRID_WIDTH = 1; +const BASE_STROKE_WIDTH = 1.75; +const BASE_GRID_WIDTH = 2; const BASE_POINT_SIZE = 5; const TEXT_MARGIN = 10; const AREA_THRESHOLD = 9; diff --git a/cvat-canvas/src/typescript/drawHandler.ts b/cvat-canvas/src/typescript/drawHandler.ts index 0c27618b1bb..60745f9f3ec 100644 --- a/cvat-canvas/src/typescript/drawHandler.ts +++ b/cvat-canvas/src/typescript/drawHandler.ts @@ -1,7 +1,6 @@ -/* -* Copyright (C) 2019 Intel Corporation -* SPDX-License-Identifier: MIT -*/ +// Copyright (C) 2019-2020 Intel Corporation +// +// SPDX-License-Identifier: MIT import * as SVG from 'svg.js'; import consts from './consts'; @@ -11,11 +10,11 @@ import './svg.patch'; import { DrawData, Geometry, + RectDrawingMethod, } from './canvasModel'; import { translateToSVG, - translateBetweenSVG, displayShapeSize, ShapeSizeElement, pointsToString, @@ -26,15 +25,19 @@ import { export interface DrawHandler { draw(drawData: DrawData, geometry: Geometry): void; + transform(geometry: Geometry): void; cancel(): void; } export class DrawHandlerImpl implements DrawHandler { // callback is used to notify about creating new shape - private onDrawDone: (data: object) => void; + private onDrawDone: (data: object, continueDraw?: boolean) => void; private canvas: SVG.Container; private text: SVG.Container; - private background: SVGSVGElement; + private cursorPosition: { + x: number; + y: number; + }; private crosshair: { x: SVG.Line; y: SVG.Line; @@ -45,17 +48,17 @@ export class DrawHandlerImpl implements DrawHandler { // we should use any instead of SVG.Shape because svg plugins cannot change declared interface // so, methods like draw() just undefined for SVG.Shape, but nevertheless they exist private drawInstance: any; + private initialized: boolean; + private pointsGroup: SVG.G | null; private shapeSizeElement: ShapeSizeElement; private getFinalRectCoordinates(bbox: BBox): number[] { const frameWidth = this.geometry.image.width; const frameHeight = this.geometry.image.height; + const { offset } = this.geometry; - let [xtl, ytl, xbr, ybr] = translateBetweenSVG( - this.canvas.node as any as SVGSVGElement, - this.background, - [bbox.x, bbox.y, bbox.x + bbox.width, bbox.y + bbox.height], - ); + let [xtl, ytl, xbr, ybr] = [bbox.x, bbox.y, bbox.x + bbox.width, bbox.y + bbox.height] + .map((coord: number): number => coord - offset); xtl = Math.min(Math.max(xtl, 0), frameWidth); xbr = Math.min(Math.max(xbr, 0), frameWidth); @@ -69,12 +72,8 @@ export class DrawHandlerImpl implements DrawHandler { points: number[]; box: Box; } { - const points = translateBetweenSVG( - this.canvas.node as any as SVGSVGElement, - this.background, - targetPoints, - ); - + const { offset } = this.geometry; + const points = targetPoints.map((coord: number): number => coord - offset); const box = { xtl: Number.MAX_SAFE_INTEGER, ytl: Number.MAX_SAFE_INTEGER, @@ -101,12 +100,13 @@ export class DrawHandlerImpl implements DrawHandler { } private addCrosshair(): void { + const { x, y } = this.cursorPosition; this.crosshair = { - x: this.canvas.line(0, 0, this.canvas.node.clientWidth, 0).attr({ + x: this.canvas.line(0, y, this.canvas.node.clientWidth, y).attr({ 'stroke-width': consts.BASE_STROKE_WIDTH / (2 * this.geometry.scale), zOrder: Number.MAX_SAFE_INTEGER, }).addClass('cvat_canvas_crosshair'), - y: this.canvas.line(0, 0, 0, this.canvas.node.clientHeight).attr({ + y: this.canvas.line(x, 0, x, this.canvas.node.clientHeight).attr({ 'stroke-width': consts.BASE_STROKE_WIDTH / (2 * this.geometry.scale), zOrder: Number.MAX_SAFE_INTEGER, }).addClass('cvat_canvas_crosshair'), @@ -120,22 +120,37 @@ export class DrawHandlerImpl implements DrawHandler { } private release(): void { + if (!this.initialized) { + // prevents recursive calls + return; + } + + this.initialized = false; this.canvas.off('mousedown.draw'); + this.canvas.off('mouseup.draw'); this.canvas.off('mousemove.draw'); this.canvas.off('click.draw'); - if (this.drawInstance) { - // Draw plugin isn't activated when draw from initialState - // So, we don't need to use any draw events - if (!this.drawData.initialState) { - this.drawInstance.off('drawdone'); - this.drawInstance.off('drawstop'); - this.drawInstance.draw('stop'); - } + if (this.pointsGroup) { + this.pointsGroup.remove(); + this.pointsGroup = null; + } - this.drawInstance.remove(); - this.drawInstance = null; + // Draw plugin in some cases isn't activated + // For example when draw from initialState + // Or when no drawn points, but we call cancel() drawing + // We check if it is activated with remember function + if (this.drawInstance.remember('_paintHandler')) { + if (this.drawData.shapeType !== 'rectangle') { + // Check for unsaved drawn shapes + this.drawInstance.draw('done'); + } + // Clear drawing + this.drawInstance.draw('stop'); } + this.drawInstance.off(); + this.drawInstance.remove(); + this.drawInstance = null; if (this.shapeSizeElement) { this.shapeSizeElement.rm(); @@ -153,60 +168,64 @@ export class DrawHandlerImpl implements DrawHandler { } } - private closeDrawing(): void { - if (this.drawInstance) { - // Draw plugin isn't activated when draw from initialState - // So, we don't need to use any draw events - if (!this.drawData.initialState) { - const { drawInstance } = this; - this.drawInstance = null; - if (this.drawData.shapeType === 'rectangle') { - drawInstance.draw('cancel'); - } else { - drawInstance.draw('done'); - } - this.drawInstance = drawInstance; - this.release(); - } else { - this.release(); - this.onDrawDone(null); - } - - // here is a cycle - // onDrawDone => controller => model => view => closeDrawing - // one call of closeDrawing is unuseful, but it's okey - } - } - private drawBox(): void { this.drawInstance = this.canvas.rect(); - this.drawInstance.draw({ - snapToGrid: 0.1, - }).addClass('cvat_canvas_shape_drawing').attr({ - 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, - z_order: Number.MAX_SAFE_INTEGER, - }).on('drawupdate', (): void => { - this.shapeSizeElement.update(this.drawInstance); - }).on('drawstop', (e: Event): void => { + this.drawInstance.on('drawstop', (e: Event): void => { const bbox = (e.target as SVGRectElement).getBBox(); const [xtl, ytl, xbr, ybr] = this.getFinalRectCoordinates(bbox); + const { shapeType } = this.drawData; + this.cancel(); if ((xbr - xtl) * (ybr - ytl) >= consts.AREA_THRESHOLD) { this.onDrawDone({ - shapeType: this.drawData.shapeType, + shapeType, points: [xtl, ytl, xbr, ybr], }); - } else { - this.onDrawDone(null); } + }).on('drawupdate', (): void => { + this.shapeSizeElement.update(this.drawInstance); + }).addClass('cvat_canvas_shape_drawing').attr({ + 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, }); } - private drawPolyshape(): void { - this.drawInstance.attr({ - z_order: Number.MAX_SAFE_INTEGER, - }); + private drawBoxBy4Points(): void { + let numberOfPoints = 0; + this.drawInstance = (this.canvas as any).polygon() + .addClass('cvat_canvas_shape_drawing').attr({ + 'stroke-width': 0, + opacity: 0, + }).on('drawstart', (): void => { + // init numberOfPoints as one on drawstart + numberOfPoints = 1; + }).on('drawpoint', (e: CustomEvent): void => { + // increase numberOfPoints by one on drawpoint + numberOfPoints += 1; + + // finish if numberOfPoints are exactly four + if (numberOfPoints === 4) { + const bbox = (e.target as SVGPolylineElement).getBBox(); + const [xtl, ytl, xbr, ybr] = this.getFinalRectCoordinates(bbox); + const { shapeType } = this.drawData; + this.cancel(); + + if ((xbr - xtl) * (ybr - ytl) >= consts.AREA_THRESHOLD) { + this.onDrawDone({ + shapeType, + points: [xtl, ytl, xbr, ybr], + }); + } + } + }).on('undopoint', (): void => { + if (numberOfPoints > 0) { + numberOfPoints -= 1; + } + }); + this.drawPolyshape(); + } + + private drawPolyshape(): void { let size = this.drawData.numberOfPoints; const sizeDecrement = function sizeDecrement(): void { if (!--size) { @@ -214,25 +233,20 @@ export class DrawHandlerImpl implements DrawHandler { } }.bind(this); - const sizeIncrement = function sizeIncrement(): void { - size++; - }; - if (this.drawData.numberOfPoints) { this.drawInstance.on('drawstart', sizeDecrement); this.drawInstance.on('drawpoint', sizeDecrement); - this.drawInstance.on('undopoint', sizeIncrement); + this.drawInstance.on('undopoint', (): number => size++); } // Add ability to cancel the latest drawn point - const handleUndo = function handleUndo(e: MouseEvent): void { + this.canvas.on('mousedown.draw', (e: MouseEvent): void => { if (e.which === 3) { e.stopPropagation(); e.preventDefault(); this.drawInstance.draw('undo'); } - }.bind(this); - this.canvas.on('mousedown.draw', handleUndo); + }); // Add ability to draw shapes by sliding // We need to remember last drawn point @@ -245,7 +259,7 @@ export class DrawHandlerImpl implements DrawHandler { y: null, }; - const handleSlide = function handleSlide(e: MouseEvent): void { + this.canvas.on('mousemove.draw', (e: MouseEvent): void => { // TODO: Use enumeration after typification cvat-core if (e.shiftKey && ['polygon', 'polyline'].includes(this.drawData.shapeType)) { if (lastDrawnPoint.x === null || lastDrawnPoint.y === null) { @@ -260,14 +274,15 @@ export class DrawHandlerImpl implements DrawHandler { this.drawInstance.draw('point', e); } } + + e.stopPropagation(); + e.preventDefault(); } - }.bind(this); - this.canvas.on('mousemove.draw', handleSlide); + }); // We need scale just drawn points - const self = this; this.drawInstance.on('drawstart drawpoint', (e: CustomEvent): void => { - self.transform(self.geometry); + this.transform(this.geometry); lastDrawnPoint.x = e.detail.event.clientX; lastDrawnPoint.y = e.detail.event.clientY; }); @@ -275,134 +290,129 @@ export class DrawHandlerImpl implements DrawHandler { this.drawInstance.on('drawdone', (e: CustomEvent): void => { const targetPoints = pointsToArray((e.target as SVGElement).getAttribute('points')); - const { - points, - box, - } = this.getFinalPolyshapeCoordinates(targetPoints); + const { points, box } = this.getFinalPolyshapeCoordinates(targetPoints); + const { shapeType } = this.drawData; + this.cancel(); - if (this.drawData.shapeType === 'polygon' + if (shapeType === 'polygon' && ((box.xbr - box.xtl) * (box.ybr - box.ytl) >= consts.AREA_THRESHOLD) && points.length >= 3 * 2) { this.onDrawDone({ - shapeType: this.drawData.shapeType, + shapeType, points, }); - } else if (this.drawData.shapeType === 'polyline' + } else if (shapeType === 'polyline' && ((box.xbr - box.xtl) >= consts.SIZE_THRESHOLD || (box.ybr - box.ytl) >= consts.SIZE_THRESHOLD) && points.length >= 2 * 2) { this.onDrawDone({ - shapeType: this.drawData.shapeType, + shapeType, points, }); - } else if (this.drawData.shapeType === 'points' + } else if (shapeType === 'points' && (e.target as any).getAttribute('points') !== '0,0') { this.onDrawDone({ - shapeType: this.drawData.shapeType, + shapeType, points, }); - } else { - this.onDrawDone(null); } }); } private drawPolygon(): void { - this.drawInstance = (this.canvas as any).polygon().draw({ - snapToGrid: 0.1, - }).addClass('cvat_canvas_shape_drawing').style({ - 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, - }); + this.drawInstance = (this.canvas as any).polygon() + .addClass('cvat_canvas_shape_drawing').attr({ + 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, + }); this.drawPolyshape(); } private drawPolyline(): void { - this.drawInstance = (this.canvas as any).polyline().draw({ - snapToGrid: 0.1, - }).addClass('cvat_canvas_shape_drawing').style({ - 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, - 'fill-opacity': 0, - }); + this.drawInstance = (this.canvas as any).polyline() + .addClass('cvat_canvas_shape_drawing').attr({ + 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, + 'fill-opacity': 0, + }); this.drawPolyshape(); } private drawPoints(): void { - this.drawInstance = (this.canvas as any).polygon().draw({ - snapToGrid: 0.1, - }).addClass('cvat_canvas_shape_drawing').style({ - 'stroke-width': 0, - opacity: 0, - }); + this.drawInstance = (this.canvas as any).polygon() + .addClass('cvat_canvas_shape_drawing').attr({ + 'stroke-width': 0, + opacity: 0, + }); this.drawPolyshape(); } private pastePolyshape(): void { - this.canvas.on('click.draw', (e: MouseEvent): void => { - const targetPoints = (e.target as SVGElement) - .getAttribute('points') + this.drawInstance.on('done', (e: CustomEvent): void => { + const targetPoints = this.drawInstance + .attr('points') .split(/[,\s]/g) - .map((coord): number => +coord); + .map((coord: string): number => +coord); const { points } = this.getFinalPolyshapeCoordinates(targetPoints); this.release(); this.onDrawDone({ - shapeType: this.drawData.shapeType, + shapeType: this.drawData.initialState.shapeType, + objectType: this.drawData.initialState.objectType, points, occluded: this.drawData.initialState.occluded, attributes: { ...this.drawData.initialState.attributes }, label: this.drawData.initialState.label, color: this.drawData.initialState.color, - }); + }, e.detail.originalEvent.ctrlKey); }); } // Common settings for rectangle and polyshapes private pasteShape(): void { - this.drawInstance.attr({ - z_order: Number.MAX_SAFE_INTEGER, - }); + function moveShape(shape: SVG.Shape, x: number, y: number): void { + const bbox = shape.bbox(); + shape.move(x - bbox.width / 2, y - bbox.height / 2); + } - this.canvas.on('mousemove.draw', (e: MouseEvent): void => { - const [x, y] = translateToSVG( - this.canvas.node as any as SVGSVGElement, - [e.clientX, e.clientY], - ); + const { x: initialX, y: initialY } = this.cursorPosition; + moveShape(this.drawInstance, initialX, initialY); - const bbox = this.drawInstance.bbox(); - this.drawInstance.move(x - bbox.width / 2, y - bbox.height / 2); + this.canvas.on('mousemove.draw', (): void => { + const { x, y } = this.cursorPosition; // was computer in another callback + moveShape(this.drawInstance, x, y); }); } private pasteBox(box: BBox): void { this.drawInstance = (this.canvas as any).rect(box.width, box.height) .move(box.x, box.y) - .addClass('cvat_canvas_shape_drawing').style({ + .addClass('cvat_canvas_shape_drawing').attr({ 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, }); this.pasteShape(); - this.canvas.on('click.draw', (e: MouseEvent): void => { - const bbox = (e.target as SVGRectElement).getBBox(); + this.drawInstance.on('done', (e: CustomEvent): void => { + const bbox = this.drawInstance.node.getBBox(); const [xtl, ytl, xbr, ybr] = this.getFinalRectCoordinates(bbox); this.release(); this.onDrawDone({ - shapeType: this.drawData.shapeType, + shapeType: this.drawData.initialState.shapeType, + objectType: this.drawData.initialState.objectType, points: [xtl, ytl, xbr, ybr], occluded: this.drawData.initialState.occluded, attributes: { ...this.drawData.initialState.attributes }, label: this.drawData.initialState.label, color: this.drawData.initialState.color, - }); + }, e.detail.originalEvent.ctrlKey); }); } private pastePolygon(points: string): void { this.drawInstance = (this.canvas as any).polygon(points) - .addClass('cvat_canvas_shape_drawing').style({ + .addClass('cvat_canvas_shape_drawing').attr({ 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, }); this.pasteShape(); @@ -411,31 +421,126 @@ export class DrawHandlerImpl implements DrawHandler { private pastePolyline(points: string): void { this.drawInstance = (this.canvas as any).polyline(points) - .addClass('cvat_canvas_shape_drawing').style({ + .addClass('cvat_canvas_shape_drawing').attr({ 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, }); this.pasteShape(); this.pastePolyshape(); } - private pastePoints(points: string): void { - this.drawInstance = (this.canvas as any).polyline(points) + private pastePoints(initialPoints: string): void { + function moveShape( + shape: SVG.PolyLine, + group: SVG.G, + x: number, + y: number, + scale: number, + ): void { + const bbox = shape.bbox(); + shape.move(x - bbox.width / 2, y - bbox.height / 2); + + const points = shape.attr('points').split(' '); + const radius = consts.BASE_POINT_SIZE / scale; + + group.children().forEach((child: SVG.Element, idx: number): void => { + const [px, py] = points[idx].split(','); + child.move(px - radius / 2, py - radius / 2); + }); + } + + const { x: initialX, y: initialY } = this.cursorPosition; + this.pointsGroup = this.canvas.group(); + this.drawInstance = (this.canvas as any).polyline(initialPoints) .addClass('cvat_canvas_shape_drawing').style({ 'stroke-width': 0, }); - this.pasteShape(); + + let numOfPoints = initialPoints.split(' ').length; + while (numOfPoints) { + numOfPoints--; + const radius = consts.BASE_POINT_SIZE / this.geometry.scale; + const stroke = consts.POINTS_STROKE_WIDTH / this.geometry.scale; + this.pointsGroup.circle().fill('white').stroke('black').attr({ + r: radius, + 'stroke-width': stroke, + }); + } + + moveShape( + this.drawInstance, this.pointsGroup, initialX, initialY, this.geometry.scale, + ); + + this.canvas.on('mousemove.draw', (): void => { + const { x, y } = this.cursorPosition; // was computer in another callback + moveShape( + this.drawInstance, this.pointsGroup, x, y, this.geometry.scale, + ); + }); + this.pastePolyshape(); } + private setupPasteEvents(): void { + let mouseX: number | null = null; + let mouseY: number | null = null; + + this.canvas.on('mousedown.draw', (e: MouseEvent): void => { + if (e.which === 1) { + mouseX = e.clientX; + mouseY = e.clientY; + } + }); + + this.canvas.on('mouseup.draw', (e: MouseEvent): void => { + const threshold = 10; // px + if (e.which === 1) { + if (Math.sqrt( // l2 distance < threshold + ((mouseX - e.clientX) ** 2) + + ((mouseY - e.clientY) ** 2), + ) < threshold) { + this.drawInstance.fire('done', { originalEvent: e }); + } + } + }); + } + + private setupDrawEvents(): void { + let initialized = false; + let mouseX: number | null = null; + let mouseY: number | null = null; + + this.canvas.on('mousedown.draw', (e: MouseEvent): void => { + if (e.which === 1) { + mouseX = e.clientX; + mouseY = e.clientY; + } + }); + + this.canvas.on('mouseup.draw', (e: MouseEvent): void => { + const threshold = 10; // px + if (e.which === 1) { + if (Math.sqrt( // l2 distance < threshold + ((mouseX - e.clientX) ** 2) + + ((mouseY - e.clientY) ** 2), + ) < threshold) { + if (!initialized) { + this.drawInstance.draw(e, { snapToGrid: 0.1 }); + initialized = true; + } else { + this.drawInstance.draw(e); + } + } + } + }); + } + private startDraw(): void { // TODO: Use enums after typification cvat-core if (this.drawData.initialState) { + const { offset } = this.geometry; if (this.drawData.shapeType === 'rectangle') { - const [xtl, ytl, xbr, ybr] = translateBetweenSVG( - this.background, - this.canvas.node as any as SVGSVGElement, - this.drawData.initialState.points, - ); + const [xtl, ytl, xbr, ybr] = this.drawData.initialState.points + .map((coord: number): number => coord + offset); this.pasteBox({ x: xtl, @@ -444,12 +549,8 @@ export class DrawHandlerImpl implements DrawHandler { height: ybr - ytl, }); } else { - const points = translateBetweenSVG( - this.background, - this.canvas.node as any as SVGSVGElement, - this.drawData.initialState.points, - ); - + const points = this.drawData.initialState.points + .map((coord: number): number => coord + offset); const stringifiedPoints = pointsToString(points); if (this.drawData.shapeType === 'polygon') { @@ -460,50 +561,59 @@ export class DrawHandlerImpl implements DrawHandler { this.pastePoints(stringifiedPoints); } } - } else if (this.drawData.shapeType === 'rectangle') { - this.drawBox(); - // Draw instance was initialized after drawBox(); - this.shapeSizeElement = displayShapeSize(this.canvas, this.text); - } else if (this.drawData.shapeType === 'polygon') { - this.drawPolygon(); - } else if (this.drawData.shapeType === 'polyline') { - this.drawPolyline(); - } else if (this.drawData.shapeType === 'points') { - this.drawPoints(); + this.setupPasteEvents(); + } else { + if (this.drawData.shapeType === 'rectangle') { + if (this.drawData.rectDrawingMethod === RectDrawingMethod.EXTREME_POINTS) { + // draw box by extreme clicking + this.drawBoxBy4Points(); + } else { + // default box drawing + this.drawBox(); + // Draw instance was initialized after drawBox(); + this.shapeSizeElement = displayShapeSize(this.canvas, this.text); + } + } else if (this.drawData.shapeType === 'polygon') { + this.drawPolygon(); + } else if (this.drawData.shapeType === 'polyline') { + this.drawPolyline(); + } else if (this.drawData.shapeType === 'points') { + this.drawPoints(); + } + this.setupDrawEvents(); } + + this.initialized = true; } public constructor( - onDrawDone: (data: object) => void, + onDrawDone: (data: object, continueDraw?: boolean) => void, canvas: SVG.Container, text: SVG.Container, - background: SVGSVGElement, ) { this.onDrawDone = onDrawDone; this.canvas = canvas; this.text = text; - this.background = background; + this.initialized = false; this.drawData = null; this.geometry = null; this.crosshair = null; this.drawInstance = null; + this.pointsGroup = null; + this.cursorPosition = { + x: 0, + y: 0, + }; this.canvas.on('mousemove.crosshair', (e: MouseEvent): void => { + const [x, y] = translateToSVG( + this.canvas.node as any as SVGSVGElement, + [e.clientX, e.clientY], + ); + this.cursorPosition = { x, y }; if (this.crosshair) { - const [x, y] = translateToSVG( - this.canvas.node as any as SVGSVGElement, - [e.clientX, e.clientY], - ); - - this.crosshair.x.attr({ - y1: y, - y2: y, - }); - - this.crosshair.y.attr({ - x1: x, - x2: x, - }); + this.crosshair.x.attr({ y1: y, y2: y }); + this.crosshair.y.attr({ x1: x, x2: x }); } }); } @@ -524,16 +634,25 @@ export class DrawHandlerImpl implements DrawHandler { }); } + if (this.pointsGroup) { + for (const point of this.pointsGroup.children()) { + point.attr({ + 'stroke-width': consts.POINTS_STROKE_WIDTH / geometry.scale, + r: consts.BASE_POINT_SIZE / geometry.scale, + }); + } + } + if (this.drawInstance) { this.drawInstance.draw('transform'); - this.drawInstance.style({ + this.drawInstance.attr({ 'stroke-width': consts.BASE_STROKE_WIDTH / geometry.scale, }); const paintHandler = this.drawInstance.remember('_paintHandler'); for (const point of (paintHandler as any).set.members) { - point.style( + point.attr( 'stroke-width', `${consts.POINTS_STROKE_WIDTH / geometry.scale}`, ); @@ -553,7 +672,7 @@ export class DrawHandlerImpl implements DrawHandler { this.initDrawing(); this.startDraw(); } else { - this.closeDrawing(); + this.cancel(); this.drawData = drawData; } } @@ -561,8 +680,5 @@ export class DrawHandlerImpl implements DrawHandler { public cancel(): void { this.release(); this.onDrawDone(null); - // here is a cycle - // onDrawDone => controller => model => view => closeDrawing - // one call of closeDrawing is unuseful, but it's okey } } diff --git a/cvat-canvas/src/typescript/editHandler.ts b/cvat-canvas/src/typescript/editHandler.ts index bc0dc82309a..defdb69e0b2 100644 --- a/cvat-canvas/src/typescript/editHandler.ts +++ b/cvat-canvas/src/typescript/editHandler.ts @@ -1,7 +1,6 @@ -/* -* Copyright (C) 2019 Intel Corporation -* SPDX-License-Identifier: MIT -*/ +// Copyright (C) 2019-2020 Intel Corporation +// +// SPDX-License-Identifier: MIT import * as SVG from 'svg.js'; import 'svg.select.js'; @@ -9,7 +8,6 @@ import 'svg.select.js'; import consts from './consts'; import { translateFromSVG, - translateBetweenSVG, pointsToArray, } from './shared'; import { @@ -27,7 +25,6 @@ export class EditHandlerImpl implements EditHandler { private onEditDone: (state: any, points: number[]) => void; private geometry: Geometry; private canvas: SVG.Container; - private background: SVGSVGElement; private editData: EditData; private editedShape: SVG.Shape; private editLine: SVG.PolyLine; @@ -40,6 +37,14 @@ export class EditHandlerImpl implements EditHandler { this.editedShape.attr('points').split(' ')[this.editData.pointID].split(','), ); + // generate mouse event + const dummyEvent = new MouseEvent('mousedown', { + bubbles: true, + cancelable: true, + clientX, + clientY, + }); + // Add ability to edit shapes by sliding // We need to remember last drawn point // to implementation of slide drawing @@ -51,10 +56,10 @@ export class EditHandlerImpl implements EditHandler { y: null, }; - const handleSlide = function handleSlide(e: MouseEvent): void { - if (e.shiftKey) { + this.canvas.on('mousemove.edit', (e: MouseEvent): void => { + if (e.shiftKey && ['polygon', 'polyline'].includes(this.editData.state.shapeType)) { if (lastDrawnPoint.x === null || lastDrawnPoint.y === null) { - this.editLine.draw('point', e); + (this.editLine as any).draw('point', e); } else { const deltaTreshold = 15; const delta = Math.sqrt( @@ -62,53 +67,67 @@ export class EditHandlerImpl implements EditHandler { + ((e.clientY - lastDrawnPoint.y) ** 2), ); if (delta > deltaTreshold) { - this.editLine.draw('point', e); + (this.editLine as any).draw('point', e); } } } - }.bind(this); - this.canvas.on('mousemove.draw', handleSlide); + }); - this.editLine = (this.canvas as any).polyline().draw({ - snapToGrid: 0.1, - }).addClass('cvat_canvas_shape_drawing').style({ + this.editLine = (this.canvas as any).polyline(); + (this.editLine as any).addClass('cvat_canvas_shape_drawing').style({ 'pointer-events': 'none', 'fill-opacity': 0, }).on('drawstart drawpoint', (e: CustomEvent): void => { this.transform(this.geometry); lastDrawnPoint.x = e.detail.event.clientX; lastDrawnPoint.y = e.detail.event.clientY; - }); + }).draw(dummyEvent, { snapToGrid: 0.1 }); if (this.editData.state.shapeType === 'points') { - this.editLine.style('stroke-width', 0); - } else { - // generate mouse event - const dummyEvent = new MouseEvent('mousedown', { - bubbles: true, - cancelable: true, - clientX, - clientY, - }); - (this.editLine as any).draw('point', dummyEvent); + this.editLine.attr('stroke-width', 0); + (this.editLine as any).draw('undo'); } + + this.setupEditEvents(); } - private stopEdit(e: MouseEvent): void { - function selectPolygon(shape: SVG.Polygon): void { - const points = translateBetweenSVG( - this.canvas.node as any as SVGSVGElement, - this.background, - pointsToArray(shape.attr('points')), - ); - - const { state } = this.editData; - this.edit({ - enabled: false, - }); - this.onEditDone(state, points); - } + private setupEditEvents(): void { + let mouseX: number | null = null; + let mouseY: number | null = null; + + this.canvas.on('mousedown.edit', (e: MouseEvent): void => { + if (e.which === 1) { + mouseX = e.clientX; + mouseY = e.clientY; + } + }); + + this.canvas.on('mouseup.edit', (e: MouseEvent): void => { + const threshold = 10; // px + if (e.which === 1) { + if (Math.sqrt( // l2 distance < threshold + ((mouseX - e.clientX) ** 2) + + ((mouseY - e.clientY) ** 2), + ) < threshold) { + (this.editLine as any).draw('point', e); + } + } + }); + } + private selectPolygon(shape: SVG.Polygon): void { + const { offset } = this.geometry; + const points = pointsToArray(shape.attr('points')) + .map((coord: number): number => coord - offset); + + const { state } = this.editData; + this.edit({ + enabled: false, + }); + this.onEditDone(state, points); + } + + private stopEdit(e: MouseEvent): void { if (!this.editLine) { return; } @@ -149,12 +168,12 @@ export class EditHandlerImpl implements EditHandler { for (const points of [firstPart, secondPart]) { this.clones.push(this.canvas.polygon(points.join(' ')) .attr('fill', this.editedShape.attr('fill')) - .style('fill-opacity', '0.5') + .attr('fill-opacity', '0.5') .addClass('cvat_canvas_shape')); } for (const clone of this.clones) { - clone.on('click', selectPolygon.bind(this, clone)); + clone.on('click', (): void => this.selectPolygon(clone)); clone.on('mouseenter', (): void => { clone.addClass('cvat_canvas_shape_splitting'); }).on('mouseleave', (): void => { @@ -162,6 +181,11 @@ export class EditHandlerImpl implements EditHandler { }); } + // We do not need these events any more + this.canvas.off('mousedown.edit'); + this.canvas.off('mouseup.edit'); + this.canvas.off('mousemove.edit'); + (this.editLine as any).draw('stop'); this.editLine.remove(); this.editLine = null; @@ -170,6 +194,7 @@ export class EditHandlerImpl implements EditHandler { } let points = null; + const { offset } = this.geometry; if (this.editData.state.shapeType === 'polyline') { if (start !== this.editData.pointID) { linePoints.reverse(); @@ -181,11 +206,8 @@ export class EditHandlerImpl implements EditHandler { points = oldPoints.concat(linePoints.slice(0, -1)); } - points = translateBetweenSVG( - this.canvas.node as any as SVGSVGElement, - this.background, - pointsToArray(points.join(' ')), - ); + points = pointsToArray(points.join(' ')) + .map((coord: number): number => coord - offset); const { state } = this.editData; this.edit({ @@ -242,7 +264,9 @@ export class EditHandlerImpl implements EditHandler { } private release(): void { - this.canvas.off('mousemove.draw'); + this.canvas.off('mousedown.edit'); + this.canvas.off('mouseup.edit'); + this.canvas.off('mousemove.edit'); if (this.editedShape) { this.setupPoints(false); @@ -284,11 +308,9 @@ export class EditHandlerImpl implements EditHandler { public constructor( onEditDone: (state: any, points: number[]) => void, canvas: SVG.Container, - background: SVGSVGElement, ) { this.onEditDone = onEditDone; this.canvas = canvas; - this.background = background; this.editData = null; this.editedShape = null; this.editLine = null; @@ -318,10 +340,16 @@ export class EditHandlerImpl implements EditHandler { public transform(geometry: Geometry): void { this.geometry = geometry; + if (this.editedShape) { + this.editedShape.attr({ + 'stroke-width': consts.BASE_STROKE_WIDTH / geometry.scale, + }); + } + if (this.editLine) { (this.editLine as any).draw('transform'); if (this.editData.state.shapeType !== 'points') { - this.editLine.style({ + this.editLine.attr({ 'stroke-width': consts.BASE_STROKE_WIDTH / geometry.scale, }); } @@ -329,7 +357,7 @@ export class EditHandlerImpl implements EditHandler { const paintHandler = this.editLine.remember('_paintHandler'); for (const point of (paintHandler as any).set.members) { - point.style( + point.attr( 'stroke-width', `${consts.POINTS_STROKE_WIDTH / geometry.scale}`, ); diff --git a/cvat-canvas/src/typescript/groupHandler.ts b/cvat-canvas/src/typescript/groupHandler.ts index 9b6f1ec38b3..0c2475ef420 100644 --- a/cvat-canvas/src/typescript/groupHandler.ts +++ b/cvat-canvas/src/typescript/groupHandler.ts @@ -1,3 +1,7 @@ +// Copyright (C) 2019-2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + import * as SVG from 'svg.js'; import { GroupData } from './canvasModel'; @@ -10,16 +14,17 @@ export interface GroupHandler { group(groupData: GroupData): void; select(state: any): void; cancel(): void; + resetSelectedObjects(): void; } export class GroupHandlerImpl implements GroupHandler { // callback is used to notify about grouping end - private onGroupDone: (objects: any[]) => void; + private onGroupDone: (objects?: any[]) => void; private getStates: () => any[]; private onFindObject: (event: MouseEvent) => void; - private onSelectStart: (event: MouseEvent) => void; - private onSelectUpdate: (event: MouseEvent) => void; - private onSelectStop: (event: MouseEvent) => void; + private bindedOnSelectStart: (event: MouseEvent) => void; + private bindedOnSelectUpdate: (event: MouseEvent) => void; + private bindedOnSelectStop: (event: MouseEvent) => void; private selectionRect: SVG.Rect; private startSelectionPoint: { x: number; @@ -27,7 +32,7 @@ export class GroupHandlerImpl implements GroupHandler { }; private canvas: SVG.Container; private initialized: boolean; - private states: any[]; + private statesToBeGroupped: any[]; private highlightedShapes: Record; private getSelectionBox(event: MouseEvent): { @@ -53,19 +58,72 @@ export class GroupHandlerImpl implements GroupHandler { }; } + private onSelectStart(event: MouseEvent): void { + if (!this.selectionRect) { + const point = translateToSVG( + this.canvas.node as any as SVGSVGElement, + [event.clientX, event.clientY], + ); + this.startSelectionPoint = { + x: point[0], + y: point[1], + }; + + this.selectionRect = this.canvas.rect().addClass('cvat_canvas_shape_grouping'); + this.selectionRect.attr({ ...this.startSelectionPoint }); + } + } + + private onSelectUpdate(event: MouseEvent): void { + // called on mousemove + if (this.selectionRect) { + const box = this.getSelectionBox(event); + + this.selectionRect.attr({ + x: box.xtl, + y: box.ytl, + width: box.xbr - box.xtl, + height: box.ybr - box.ytl, + }); + } + } + + private onSelectStop(event: MouseEvent): void { + // called on mouseup, mouseleave + if (this.selectionRect) { + this.selectionRect.remove(); + this.selectionRect = null; + + const box = this.getSelectionBox(event); + const shapes = (this.canvas.select('.cvat_canvas_shape') as any).members; + for (const shape of shapes) { + // TODO: Doesn't work properly for groups + const bbox = shape.bbox(); + const clientID = shape.attr('clientID'); + if (bbox.x > box.xtl && bbox.y > box.ytl + && bbox.x + bbox.width < box.xbr + && bbox.y + bbox.height < box.ybr + && !(clientID in this.highlightedShapes)) { + const objectState = this.getStates() + .filter((state: any): boolean => state.clientID === clientID)[0]; + + if (objectState) { + this.statesToBeGroupped.push(objectState); + this.highlightedShapes[clientID] = shape; + (shape as any).addClass('cvat_canvas_shape_grouping'); + } + } + } + } + } + private release(): void { this.canvas.node.removeEventListener('click', this.onFindObject); - this.canvas.node.removeEventListener('mousedown', this.onSelectStart); - this.canvas.node.removeEventListener('mousemove', this.onSelectUpdate); - this.canvas.node.removeEventListener('mouseup', this.onSelectStop); - this.canvas.node.removeEventListener('mouseleave', this.onSelectStop); + this.canvas.node.removeEventListener('mousedown', this.bindedOnSelectStart); + this.canvas.node.removeEventListener('mousemove', this.bindedOnSelectUpdate); + this.canvas.node.removeEventListener('mouseup', this.bindedOnSelectStop); - for (const state of this.states) { - const shape = this.highlightedShapes[state.clientID]; - shape.removeClass('cvat_canvas_shape_grouping'); - } - this.states = []; - this.highlightedShapes = {}; + this.resetSelectedObjects(); this.initialized = false; this.selectionRect = null; this.startSelectionPoint = { @@ -76,29 +134,28 @@ export class GroupHandlerImpl implements GroupHandler { private initGrouping(): void { this.canvas.node.addEventListener('click', this.onFindObject); - this.canvas.node.addEventListener('mousedown', this.onSelectStart); - this.canvas.node.addEventListener('mousemove', this.onSelectUpdate); - this.canvas.node.addEventListener('mouseup', this.onSelectStop); - this.canvas.node.addEventListener('mouseleave', this.onSelectStop); + this.canvas.node.addEventListener('mousedown', this.bindedOnSelectStart); + this.canvas.node.addEventListener('mousemove', this.bindedOnSelectUpdate); + this.canvas.node.addEventListener('mouseup', this.bindedOnSelectStop); this.initialized = true; } private closeGrouping(): void { if (this.initialized) { - const { states } = this; + const { statesToBeGroupped } = this; this.release(); - if (states.length) { - this.onGroupDone(states); + if (statesToBeGroupped.length) { + this.onGroupDone(statesToBeGroupped); } else { - this.onGroupDone(null); + this.onGroupDone(); } } } public constructor( - onGroupDone: (objects: any[]) => void, + onGroupDone: (objects?: any[]) => void, getStates: () => any[], onFindObject: (event: MouseEvent) => void, canvas: SVG.Container, @@ -107,69 +164,18 @@ export class GroupHandlerImpl implements GroupHandler { this.getStates = getStates; this.onFindObject = onFindObject; this.canvas = canvas; - this.states = []; + this.statesToBeGroupped = []; this.highlightedShapes = {}; this.selectionRect = null; + this.initialized = false; this.startSelectionPoint = { x: null, y: null, }; - this.onSelectStart = function (event: MouseEvent): void { - if (!this.selectionRect) { - const point = translateToSVG(this.canvas.node, [event.clientX, event.clientY]); - this.startSelectionPoint = { - x: point[0], - y: point[1], - }; - - this.selectionRect = this.canvas.rect().addClass('cvat_canvas_shape_grouping'); - this.selectionRect.attr({ ...this.startSelectionPoint }); - } - }.bind(this); - - this.onSelectUpdate = function (event: MouseEvent): void { - // called on mousemove - if (this.selectionRect) { - const box = this.getSelectionBox(event); - - this.selectionRect.attr({ - x: box.xtl, - y: box.ytl, - width: box.xbr - box.xtl, - height: box.ybr - box.ytl, - }); - } - }.bind(this); - - this.onSelectStop = function (event: MouseEvent): void { - // called on mouseup, mouseleave - if (this.selectionRect) { - this.selectionRect.remove(); - this.selectionRect = null; - - const box = this.getSelectionBox(event); - const shapes = (this.canvas.select('.cvat_canvas_shape') as any).members; - for (const shape of shapes) { - // TODO: Doesn't work properly for groups - const bbox = shape.bbox(); - const clientID = shape.attr('clientID'); - if (bbox.x > box.xtl && bbox.y > box.ytl - && bbox.x + bbox.width < box.xbr - && bbox.y + bbox.height < box.ybr - && !(clientID in this.highlightedShapes)) { - const objectState = this.getStates() - .filter((state: any): boolean => state.clientID === clientID)[0]; - - if (objectState) { - this.states.push(objectState); - this.highlightedShapes[clientID] = shape; - (shape as any).addClass('cvat_canvas_shape_grouping'); - } - } - } - } - }.bind(this); + this.bindedOnSelectStart = this.onSelectStart.bind(this); + this.bindedOnSelectUpdate = this.onSelectUpdate.bind(this); + this.bindedOnSelectStop = this.onSelectStop.bind(this); } /* eslint-disable-next-line */ @@ -182,11 +188,11 @@ export class GroupHandlerImpl implements GroupHandler { } public select(objectState: any): void { - const stateIndexes = this.states.map((state): number => state.clientID); + const stateIndexes = this.statesToBeGroupped.map((state): number => state.clientID); const includes = stateIndexes.indexOf(objectState.clientID); if (includes !== -1) { const shape = this.highlightedShapes[objectState.clientID]; - this.states.splice(includes, 1); + this.statesToBeGroupped.splice(includes, 1); if (shape) { delete this.highlightedShapes[objectState.clientID]; shape.removeClass('cvat_canvas_shape_grouping'); @@ -194,15 +200,24 @@ export class GroupHandlerImpl implements GroupHandler { } else { const shape = this.canvas.select(`#cvat_canvas_shape_${objectState.clientID}`).first(); if (shape) { - this.states.push(objectState); + this.statesToBeGroupped.push(objectState); this.highlightedShapes[objectState.clientID] = shape; shape.addClass('cvat_canvas_shape_grouping'); } } } + public resetSelectedObjects(): void { + for (const state of this.statesToBeGroupped) { + const shape = this.highlightedShapes[state.clientID]; + shape.removeClass('cvat_canvas_shape_grouping'); + } + this.statesToBeGroupped = []; + this.highlightedShapes = {}; + } + public cancel(): void { this.release(); - this.onGroupDone(null); + this.onGroupDone(); } } diff --git a/cvat-canvas/src/typescript/master.ts b/cvat-canvas/src/typescript/master.ts index 42e9a5e0718..9380e041be2 100644 --- a/cvat-canvas/src/typescript/master.ts +++ b/cvat-canvas/src/typescript/master.ts @@ -1,7 +1,6 @@ -/* -* Copyright (C) 2019 Intel Corporation -* SPDX-License-Identifier: MIT -*/ +// Copyright (C) 2019-2020 Intel Corporation +// +// SPDX-License-Identifier: MIT export interface Master { subscribe(listener: Listener): void; diff --git a/cvat-canvas/src/typescript/mergeHandler.ts b/cvat-canvas/src/typescript/mergeHandler.ts index a19961d8764..efaa4ac09d7 100644 --- a/cvat-canvas/src/typescript/mergeHandler.ts +++ b/cvat-canvas/src/typescript/mergeHandler.ts @@ -1,3 +1,7 @@ +// Copyright (C) 2019-2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + import * as SVG from 'svg.js'; import { MergeData } from './canvasModel'; @@ -5,6 +9,7 @@ export interface MergeHandler { merge(mergeData: MergeData): void; select(state: any): void; cancel(): void; + repeatSelection(): void; } @@ -14,7 +19,7 @@ export class MergeHandlerImpl implements MergeHandler { private onFindObject: (event: MouseEvent) => void; private canvas: SVG.Container; private initialized: boolean; - private states: any[]; // are being merged + private statesToBeMerged: any[]; // are being merged private highlightedShapes: Record; private constraints: { labelID: number; @@ -22,7 +27,7 @@ export class MergeHandlerImpl implements MergeHandler { }; private addConstraints(): void { - const shape = this.states[0]; + const shape = this.statesToBeMerged[0]; this.constraints = { labelID: shape.label.id, shapeType: shape.shapeType, @@ -41,11 +46,11 @@ export class MergeHandlerImpl implements MergeHandler { private release(): void { this.removeConstraints(); this.canvas.node.removeEventListener('click', this.onFindObject); - for (const state of this.states) { + for (const state of this.statesToBeMerged) { const shape = this.highlightedShapes[state.clientID]; shape.removeClass('cvat_canvas_shape_merging'); } - this.states = []; + this.statesToBeMerged = []; this.highlightedShapes = {}; this.initialized = false; } @@ -57,11 +62,11 @@ export class MergeHandlerImpl implements MergeHandler { private closeMerging(): void { if (this.initialized) { - const { states } = this; + const { statesToBeMerged } = this; this.release(); - if (states.length > 1) { - this.onMergeDone(states); + if (statesToBeMerged.length > 1) { + this.onMergeDone(statesToBeMerged); } else { this.onMergeDone(null); // here is a cycle @@ -79,7 +84,7 @@ export class MergeHandlerImpl implements MergeHandler { this.onMergeDone = onMergeDone; this.onFindObject = onFindObject; this.canvas = canvas; - this.states = []; + this.statesToBeMerged = []; this.highlightedShapes = {}; this.constraints = null; this.initialized = false; @@ -94,35 +99,45 @@ export class MergeHandlerImpl implements MergeHandler { } public select(objectState: any): void { - const stateIndexes = this.states.map((state): number => state.clientID); - const stateFrames = this.states.map((state): number => state.frame); + const stateIndexes = this.statesToBeMerged.map((state): number => state.clientID); + const stateFrames = this.statesToBeMerged.map((state): number => state.frame); const includes = stateIndexes.indexOf(objectState.clientID); if (includes !== -1) { const shape = this.highlightedShapes[objectState.clientID]; - this.states.splice(includes, 1); + this.statesToBeMerged.splice(includes, 1); if (shape) { delete this.highlightedShapes[objectState.clientID]; shape.removeClass('cvat_canvas_shape_merging'); } - if (!this.states.length) { + if (!this.statesToBeMerged.length) { this.removeConstraints(); } } else { const shape = this.canvas.select(`#cvat_canvas_shape_${objectState.clientID}`).first(); if (shape && this.checkConstraints(objectState) && !stateFrames.includes(objectState.frame)) { - this.states.push(objectState); + this.statesToBeMerged.push(objectState); this.highlightedShapes[objectState.clientID] = shape; shape.addClass('cvat_canvas_shape_merging'); - if (this.states.length === 1) { + if (this.statesToBeMerged.length === 1) { this.addConstraints(); } } } } + public repeatSelection(): void { + for (const objectState of this.statesToBeMerged) { + const shape = this.canvas.select(`#cvat_canvas_shape_${objectState.clientID}`).first(); + if (shape) { + this.highlightedShapes[objectState.clientID] = shape; + shape.addClass('cvat_canvas_shape_merging'); + } + } + } + public cancel(): void { this.release(); this.onMergeDone(null); diff --git a/cvat-canvas/src/typescript/shared.ts b/cvat-canvas/src/typescript/shared.ts index 37cecd2b6e3..2a1136ca4aa 100644 --- a/cvat-canvas/src/typescript/shared.ts +++ b/cvat-canvas/src/typescript/shared.ts @@ -1,7 +1,6 @@ -/* -* Copyright (C) 2019 Intel Corporation -* SPDX-License-Identifier: MIT -*/ +// Copyright (C) 2019-2020 Intel Corporation +// +// SPDX-License-Identifier: MIT import * as SVG from 'svg.js'; import consts from './consts'; @@ -26,11 +25,11 @@ export interface BBox { y: number; } -// Translate point array from the client coordinate system -// to a coordinate system of a canvas +// Translate point array from the canvas coordinate system +// to the coordinate system of a client export function translateFromSVG(svg: SVGSVGElement, points: number[]): number[] { const output = []; - const transformationMatrix = svg.getScreenCTM(); + const transformationMatrix = svg.getScreenCTM() as DOMMatrix; let pt = svg.createSVGPoint(); for (let i = 0; i < points.length - 1; i += 2) { pt.x = points[i]; @@ -42,11 +41,11 @@ export function translateFromSVG(svg: SVGSVGElement, points: number[]): number[] return output; } -// Translate point array from a coordinate system of a canvas -// to the client coordinate system +// Translate point array from the coordinate system of a client +// to the canvas coordinate system export function translateToSVG(svg: SVGSVGElement, points: number[]): number[] { const output = []; - const transformationMatrix = svg.getScreenCTM().inverse(); + const transformationMatrix = (svg.getScreenCTM() as DOMMatrix).inverse(); let pt = svg.createSVGPoint(); for (let i = 0; i < points.length; i += 2) { pt.x = points[i]; @@ -58,23 +57,13 @@ export function translateToSVG(svg: SVGSVGElement, points: number[]): number[] { return output; } -// Translate point array from the first canvas coordinate system -// to another -export function translateBetweenSVG( - from: SVGSVGElement, - to: SVGSVGElement, - points: number[], -): number[] { - return translateToSVG(to, translateFromSVG(from, points)); -} - export function pointsToString(points: number[]): string { return points.reduce((acc, val, idx): string => { if (idx % 2) { return `${acc},${val}`; } - return `${acc} ${val}`; + return `${acc} ${val}`.trim(); }, ''); } diff --git a/cvat-canvas/src/typescript/splitHandler.ts b/cvat-canvas/src/typescript/splitHandler.ts index 46e5b646fa4..c0756526cc5 100644 --- a/cvat-canvas/src/typescript/splitHandler.ts +++ b/cvat-canvas/src/typescript/splitHandler.ts @@ -1,3 +1,7 @@ +// Copyright (C) 2019-2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + import * as SVG from 'svg.js'; import { SplitData } from './canvasModel'; @@ -27,13 +31,13 @@ export class SplitHandlerImpl implements SplitHandler { private release(): void { if (this.initialized) { this.resetShape(); - this.canvas.node.removeEventListener('mousemove', this.onFindObject); + this.canvas.node.removeEventListener('mousemove', this.findObject); this.initialized = false; } } private initSplitting(): void { - this.canvas.node.addEventListener('mousemove', this.onFindObject); + this.canvas.node.addEventListener('mousemove', this.findObject); this.initialized = true; this.splitDone = false; } @@ -47,6 +51,11 @@ export class SplitHandlerImpl implements SplitHandler { this.release(); } + private findObject = (e: MouseEvent): void => { + this.resetShape(); + this.onFindObject(e); + }; + public constructor( onSplitDone: (object: any) => void, onFindObject: (event: MouseEvent) => void, @@ -83,8 +92,6 @@ export class SplitHandlerImpl implements SplitHandler { once: true, }); } - } else { - this.resetShape(); } } diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index 98cacbf3a51..5d6c6aae42f 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -1,7 +1,9 @@ -import * as SVG from 'svg.js'; +// Copyright (C) 2019-2020 Intel Corporation +// +// SPDX-License-Identifier: MIT /* eslint-disable */ - +import * as SVG from 'svg.js'; import 'svg.draggable.js'; import 'svg.resize.js'; import 'svg.select.js'; @@ -14,7 +16,9 @@ SVG.Element.prototype.draw = function constructor(...args: any): any { if (!handler) { originalDraw.call(this, ...args); handler = this.remember('_paintHandler'); - handler.set = new SVG.Set(); + if (!handler.set) { + handler.set = new SVG.Set(); + } } else { originalDraw.call(this, ...args); } @@ -27,7 +31,7 @@ for (const key of Object.keys(originalDraw)) { // Create undo for polygones and polylines function undo(): void { - if (this.set.length()) { + if (this.set && this.set.length()) { this.set.members.splice(-1, 1)[0].remove(); this.el.array().value.splice(-2, 1); this.el.plot(this.el.array()); diff --git a/cvat-canvas/src/typescript/zoomHandler.ts b/cvat-canvas/src/typescript/zoomHandler.ts new file mode 100644 index 00000000000..1df36185343 --- /dev/null +++ b/cvat-canvas/src/typescript/zoomHandler.ts @@ -0,0 +1,145 @@ +// Copyright (C) 2019-2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import * as SVG from 'svg.js'; +import consts from './consts'; + +import { + translateToSVG, +} from './shared'; + +import { + Geometry, +} from './canvasModel'; + + +export interface ZoomHandler { + zoom(): void; + cancel(): void; + transform(geometry: Geometry): void; +} + +export class ZoomHandlerImpl implements ZoomHandler { + private onZoomRegion: (x: number, y: number, width: number, height: number) => void; + private bindedOnSelectStart: (event: MouseEvent) => void; + private bindedOnSelectUpdate: (event: MouseEvent) => void; + private bindedOnSelectStop: (event: MouseEvent) => void; + private geometry: Geometry; + private canvas: SVG.Container; + private selectionRect: SVG.Rect | null; + private startSelectionPoint: { + x: number; + y: number; + }; + + private onSelectStart(event: MouseEvent): void { + if (!this.selectionRect && event.which === 1) { + const point = translateToSVG( + (this.canvas.node as any as SVGSVGElement), + [event.clientX, event.clientY], + ); + this.startSelectionPoint = { + x: point[0], + y: point[1], + }; + + this.selectionRect = this.canvas.rect().addClass('cvat_canvas_zoom_selection'); + this.selectionRect.attr({ + 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, + ...this.startSelectionPoint, + }); + } + } + + private getSelectionBox(event: MouseEvent): { + x: number; + y: number; + width: number; + height: number; + } { + const point = translateToSVG( + (this.canvas.node as any as SVGSVGElement), + [event.clientX, event.clientY], + ); + const stopSelectionPoint = { + x: point[0], + y: point[1], + }; + + const xtl = Math.min(this.startSelectionPoint.x, stopSelectionPoint.x); + const ytl = Math.min(this.startSelectionPoint.y, stopSelectionPoint.y); + const xbr = Math.max(this.startSelectionPoint.x, stopSelectionPoint.x); + const ybr = Math.max(this.startSelectionPoint.y, stopSelectionPoint.y); + + return { + x: xtl, + y: ytl, + width: xbr - xtl, + height: ybr - ytl, + }; + } + + private onSelectUpdate(event: MouseEvent): void { + if (this.selectionRect) { + this.selectionRect.attr({ + ...this.getSelectionBox(event), + }); + } + } + + private onSelectStop(event: MouseEvent): void { + if (this.selectionRect) { + const box = this.getSelectionBox(event); + this.selectionRect.remove(); + this.selectionRect = null; + this.startSelectionPoint = { + x: 0, + y: 0, + }; + const threshold = 5; + if (box.width > threshold && box.height > threshold) { + this.onZoomRegion(box.x, box.y, box.width, box.height); + } + } + } + + public constructor( + onZoomRegion: (x: number, y: number, width: number, height: number) => void, + canvas: SVG.Container, + geometry: Geometry, + ) { + this.onZoomRegion = onZoomRegion; + this.canvas = canvas; + this.geometry = geometry; + this.selectionRect = null; + this.startSelectionPoint = { + x: 0, + y: 0, + }; + this.bindedOnSelectStart = this.onSelectStart.bind(this); + this.bindedOnSelectUpdate = this.onSelectUpdate.bind(this); + this.bindedOnSelectStop = this.onSelectStop.bind(this); + } + + public zoom(): void { + this.canvas.node.addEventListener('mousedown', this.bindedOnSelectStart); + this.canvas.node.addEventListener('mousemove', this.bindedOnSelectUpdate); + this.canvas.node.addEventListener('mouseup', this.bindedOnSelectStop); + } + + public cancel(): void { + this.canvas.node.removeEventListener('mousedown', this.bindedOnSelectStart); + this.canvas.node.removeEventListener('mousemove', this.bindedOnSelectUpdate); + this.canvas.node.removeEventListener('mouseup ', this.bindedOnSelectStop); + } + + public transform(geometry: Geometry): void { + this.geometry = geometry; + if (this.selectionRect) { + this.selectionRect.style({ + 'stroke-width': consts.BASE_STROKE_WIDTH / geometry.scale, + }); + } + } +} diff --git a/cvat-canvas/tsconfig.json b/cvat-canvas/tsconfig.json index f919ec6cadd..b83cdaf98f3 100644 --- a/cvat-canvas/tsconfig.json +++ b/cvat-canvas/tsconfig.json @@ -7,6 +7,8 @@ "noImplicitAny": true, "preserveConstEnums": true, "declaration": true, + "resolveJsonModule": true, + "esModuleInterop": true, "moduleResolution": "node", "declarationDir": "dist/declaration", "paths": { diff --git a/cvat-canvas/webpack.config.js b/cvat-canvas/webpack.config.js index aa68cf6169c..69d97b125ae 100644 --- a/cvat-canvas/webpack.config.js +++ b/cvat-canvas/webpack.config.js @@ -1,3 +1,8 @@ +/* + * Copyright (C) 2019 Intel Corporation + * SPDX-License-Identifier: MIT +*/ + /* eslint-disable */ const path = require('path'); const DtsBundleWebpack = require('dts-bundle-webpack') @@ -70,15 +75,23 @@ const webConfig = { loader: 'babel-loader', options: { presets: [ - ['@babel/preset-env'], + ['@babel/preset-env', { + targets: '> 2.5%', // https://github.com/browserslist/browserslist + }], ['@babel/typescript'], ], sourceType: 'unambiguous', }, }, }, { - test: /\.css$/, - use: ['style-loader', 'css-loader'] + test: /\.scss$/, + exclude: /node_modules/, + use: ['style-loader', { + loader: 'css-loader', + options: { + importLoaders: 2, + }, + }, 'postcss-loader', 'sass-loader'] }], }, plugins: [ diff --git a/cvat-core/.dockerignore b/cvat-core/.dockerignore new file mode 100644 index 00000000000..1ea915a5600 --- /dev/null +++ b/cvat-core/.dockerignore @@ -0,0 +1,5 @@ +/dist +/docs +/node_modules +/reports + diff --git a/cvat-core/.eslintrc.js b/cvat-core/.eslintrc.js index ff0d33983c1..f4a4e53dd17 100644 --- a/cvat-core/.eslintrc.js +++ b/cvat-core/.eslintrc.js @@ -50,5 +50,6 @@ "func-names": [0], "valid-typeof": [0], "no-console": [0], // this rule deprecates console.log, console.warn etc. because "it is not good in production code" + "max-classes-per-file": [0], }, }; diff --git a/cvat-core/package.json b/cvat-core/package.json index deab3a7ff3d..d07e81aa12a 100644 --- a/cvat-core/package.json +++ b/cvat-core/package.json @@ -1,6 +1,6 @@ { "name": "cvat-core.js", - "version": "0.1.0", + "version": "0.5.2", "description": "Part of Computer Vision Tool which presents an interface for client-side integration", "main": "babel.config.js", "scripts": { @@ -18,7 +18,6 @@ "airbnb": "0.0.2", "babel-eslint": "^10.0.1", "babel-loader": "^8.0.6", - "core-js": "^3.0.1", "coveralls": "^3.0.5", "eslint": "6.1.0", "eslint-config-airbnb-base": "14.0.0", @@ -39,6 +38,7 @@ "form-data": "^2.5.0", "jest-config": "^24.8.0", "js-cookie": "^2.2.0", + "jsonpath": "^1.0.2", "platform": "^1.3.5", "store": "^2.0.12" } diff --git a/cvat-core/src/annotation-format.js b/cvat-core/src/annotation-format.js index 0bf8cfdfba9..5505ecf1a32 100644 --- a/cvat-core/src/annotation-format.js +++ b/cvat-core/src/annotation-format.js @@ -138,8 +138,8 @@ handler_file: initialData.handler_file, }; - data.dumpers = initialData.dumpers.map(el => new Dumper(el)); - data.loaders = initialData.loaders.map(el => new Loader(el)); + data.dumpers = initialData.dumpers.map((el) => new Dumper(el)); + data.loaders = initialData.loaders.map((el) => new Loader(el)); // Now all fields are readonly Object.defineProperties(this, { diff --git a/cvat-core/src/annotations-collection.js b/cvat-core/src/annotations-collection.js index 5e14974b4b1..a3e49f1505c 100644 --- a/cvat-core/src/annotations-collection.js +++ b/cvat-core/src/annotations-collection.js @@ -1,5 +1,5 @@ /* -* Copyright (C) 2019 Intel Corporation +* Copyright (C) 2019-2020 Intel Corporation * SPDX-License-Identifier: MIT */ @@ -22,6 +22,7 @@ Tag, objectStateFactory, } = require('./annotations-objects'); + const AnnotationsFilter = require('./annotations-filter'); const { checkObjectType } = require('./common'); const Statistics = require('./statistics'); const { Label } = require('./labels'); @@ -32,29 +33,13 @@ } = require('./exceptions'); const { + HistoryActions, ObjectShape, ObjectType, + colors, } = require('./enums'); const ObjectState = require('./object-state'); - const colors = [ - '#0066FF', '#AF593E', '#01A368', '#FF861F', '#ED0A3F', '#FF3F34', '#76D7EA', - '#8359A3', '#FBE870', '#C5E17A', '#03BB85', '#FFDF00', '#8B8680', '#0A6B0D', - '#8FD8D8', '#A36F40', '#F653A6', '#CA3435', '#FFCBA4', '#FF99CC', '#FA9D5A', - '#FFAE42', '#A78B00', '#788193', '#514E49', '#1164B4', '#F4FA9F', '#FED8B1', - '#C32148', '#01796F', '#E90067', '#FF91A4', '#404E5A', '#6CDAE7', '#FFC1CC', - '#006A93', '#867200', '#E2B631', '#6EEB6E', '#FFC800', '#CC99BA', '#FF007C', - '#BC6CAC', '#DCCCD7', '#EBE1C2', '#A6AAAE', '#B99685', '#0086A7', '#5E4330', - '#C8A2C8', '#708EB3', '#BC8777', '#B2592D', '#497E48', '#6A2963', '#E6335F', - '#00755E', '#B5A895', '#0048ba', '#EED9C4', '#C88A65', '#FF6E4A', '#87421F', - '#B2BEB5', '#926F5B', '#00B9FB', '#6456B7', '#DB5079', '#C62D42', '#FA9C44', - '#DA8A67', '#FD7C6E', '#93CCEA', '#FCF686', '#503E32', '#FF5470', '#9DE093', - '#FF7A00', '#4F69C6', '#A50B5E', '#F0E68C', '#FDFF00', '#F091A9', '#FFFF66', - '#6F9940', '#FC74FD', '#652DC1', '#D6AEDD', '#EE34D2', '#BB3385', '#6B3FA0', - '#33CC99', '#FFDB00', '#87FF2A', '#6EEB6E', '#FFC800', '#CC99BA', '#7A89B8', - '#006A93', '#867200', '#E2B631', '#D9D6CF', - ]; - function shapeFactory(shapeData, clientID, injection) { const { type } = shapeData; const color = colors[clientID % colors.length]; @@ -126,31 +111,42 @@ return labelAccumulator; }, {}); + this.annotationsFilter = new AnnotationsFilter(); + this.history = data.history; this.shapes = {}; // key is a frame this.tags = {}; // key is a frame this.tracks = []; this.objects = {}; // key is a client id this.count = 0; this.flush = false; - this.collectionZ = {}; // key is a frame, {max, min} are values this.groups = { max: 0, }; // it is an object to we can pass it as an argument by a reference this.injection = { labels: this.labels, - collectionZ: this.collectionZ, groups: this.groups, frameMeta: this.frameMeta, + history: this.history, + groupColors: {}, }; } import(data) { + const result = { + tags: [], + shapes: [], + tracks: [], + }; + for (const tag of data.tags) { const clientID = ++this.count; - const tagModel = new Tag(tag, clientID, this.injection); + const color = colors[clientID % colors.length]; + const tagModel = new Tag(tag, clientID, color, this.injection); this.tags[tagModel.frame] = this.tags[tagModel.frame] || []; this.tags[tagModel.frame].push(tagModel); this.objects[clientID] = tagModel; + + result.tags.push(tagModel); } for (const shape of data.shapes) { @@ -159,6 +155,8 @@ this.shapes[shapeModel.frame] = this.shapes[shapeModel.frame] || []; this.shapes[shapeModel.frame].push(shapeModel); this.objects[clientID] = shapeModel; + + result.shapes.push(shapeModel); } for (const track of data.tracks) { @@ -169,51 +167,74 @@ if (trackModel) { this.tracks.push(trackModel); this.objects[clientID] = trackModel; + + result.tracks.push(trackModel); } } - return this; + return result; } export() { const data = { - tracks: this.tracks.filter(track => !track.removed) - .map(track => track.toJSON()), + tracks: this.tracks.filter((track) => !track.removed) + .map((track) => track.toJSON()), shapes: Object.values(this.shapes) .reduce((accumulator, value) => { accumulator.push(...value); return accumulator; - }, []).filter(shape => !shape.removed) - .map(shape => shape.toJSON()), + }, []).filter((shape) => !shape.removed) + .map((shape) => shape.toJSON()), tags: Object.values(this.tags).reduce((accumulator, value) => { accumulator.push(...value); return accumulator; - }, []).filter(tag => !tag.removed) - .map(tag => tag.toJSON()), + }, []).filter((tag) => !tag.removed) + .map((tag) => tag.toJSON()), }; return data; } - get(frame) { + get(frame, allTracks, filters) { const { tracks } = this; const shapes = this.shapes[frame] || []; const tags = this.tags[frame] || []; - const objects = tracks.concat(shapes).concat(tags).filter(object => !object.removed); - // filtering here + const objects = [].concat(tracks, shapes, tags); + const visible = { + models: [], + data: [], + }; - const objectStates = []; for (const object of objects) { + if (object.removed) { + continue; + } + const stateData = object.get(frame); - if (stateData.outside && !stateData.keyframe) { + if (!allTracks && stateData.outside && !stateData.keyframe) { continue; } - const objectState = objectStateFactory.call(object, frame, stateData); - objectStates.push(objectState); + visible.models.push(object); + visible.data.push(stateData); + } + + const [, query] = this.annotationsFilter.toJSONQuery(filters); + let filtered = []; + if (filters.length) { + filtered = this.annotationsFilter.filter(visible.data, query); } + const objectStates = []; + visible.data.forEach((stateData, idx) => { + if (!filters.length || filtered.includes(stateData.clientID)) { + const model = visible.models[idx]; + const objectState = objectStateFactory.call(model, frame, stateData); + objectStates.push(objectState); + } + }); + return objectStates; } @@ -370,7 +391,7 @@ const clientID = ++this.count; const track = { - frame: Math.min.apply(null, Object.keys(keyframes).map(frame => +frame)), + frame: Math.min.apply(null, Object.keys(keyframes).map((frame) => +frame)), shapes: Object.values(keyframes), group: 0, label_id: label.id, @@ -394,10 +415,19 @@ // Remove other shapes for (const object of objectsForMerge) { object.removed = true; - if (typeof (object.resetCache) === 'function') { - object.resetCache(); - } } + + this.history.do(HistoryActions.MERGED_OBJECTS, () => { + trackModel.removed = true; + for (const object of objectsForMerge) { + object.removed = false; + } + }, () => { + trackModel.removed = false; + for (const object of objectsForMerge) { + object.removed = true; + } + }, [...objectsForMerge.map((object) => object.clientID), trackModel.clientID]); } split(objectState, frame) { @@ -416,7 +446,7 @@ } const keyframes = Object.keys(object.shapes).sort((a, b) => +a - +b); - if (frame <= +keyframes[0] || frame > keyframes[keyframes.length - 1]) { + if (frame <= +keyframes[0]) { return; } @@ -431,7 +461,7 @@ points: [...objectState.points], occluded: objectState.occluded, outside: objectState.outside, - zOrder: 0, + zOrder: objectState.zOrder, attributes: Object.keys(objectState.attributes) .reduce((accumulator, attrID) => { if (!labelAttributes[attrID].mutable) { @@ -483,7 +513,16 @@ // Remove source object object.removed = true; - object.resetCache(); + + this.history.do(HistoryActions.SPLITTED_TRACK, () => { + object.removed = false; + prevTrack.removed = true; + nextTrack.removed = true; + }, () => { + object.removed = true; + prevTrack.removed = false; + nextTrack.removed = false; + }, [object.clientID, prevTrack.clientID, nextTrack.clientID]); } group(objectStates, reset) { @@ -501,12 +540,21 @@ }); const groupIdx = reset ? 0 : ++this.groups.max; + const undoGroups = objectsForGroup.map((object) => object.group); for (const object of objectsForGroup) { object.group = groupIdx; - if (typeof (object.resetCache) === 'function') { - object.resetCache(); - } } + const redoGroups = objectsForGroup.map((object) => object.group); + + this.history.do(HistoryActions.GROUPED_OBJECTS, () => { + objectsForGroup.forEach((object, idx) => { + object.group = undoGroups[idx]; + }); + }, () => { + objectsForGroup.forEach((object, idx) => { + object.group = redoGroups[idx]; + }); + }, objectsForGroup.map((object) => object.clientID)); return groupIdx; } @@ -553,6 +601,10 @@ } for (const object of Object.values(this.objects)) { + if (object.removed) { + continue; + } + let objectType = null; if (object instanceof Shape) { objectType = 'shape'; @@ -577,7 +629,7 @@ if (objectType === 'track') { const keyframes = Object.keys(object.shapes) - .sort((a, b) => +a - +b).map(el => +el); + .sort((a, b) => +a - +b).map((el) => +el); let prevKeyframe = keyframes[0]; let visible = false; @@ -673,6 +725,7 @@ } else { checkObjectType('state occluded', state.occluded, 'boolean', null); checkObjectType('state points', state.points, null, Array); + checkObjectType('state zOrder', state.zOrder, 'integer', null); for (const coord of state.points) { checkObjectType('point coordinate', coord, 'number', null); @@ -694,24 +747,24 @@ occluded: state.occluded || false, points: [...state.points], type: state.shapeType, - z_order: 0, + z_order: state.zOrder, }); } else if (state.objectType === 'track') { constructed.tracks.push({ attributes: attributes - .filter(attr => !labelAttributes[attr.spec_id].mutable), + .filter((attr) => !labelAttributes[attr.spec_id].mutable), frame: state.frame, group: 0, label_id: state.label.id, shapes: [{ attributes: attributes - .filter(attr => labelAttributes[attr.spec_id].mutable), + .filter((attr) => labelAttributes[attr.spec_id].mutable), frame: state.frame, occluded: state.occluded || false, outside: false, points: [...state.points], type: state.shapeType, - z_order: 0, + z_order: state.zOrder, }], }); } else { @@ -724,7 +777,20 @@ } // Add constructed objects to a collection - this.import(constructed); + const imported = this.import(constructed); + const importedArray = imported.tags + .concat(imported.tracks) + .concat(imported.shapes); + + this.history.do(HistoryActions.CREATED_OBJECTS, () => { + importedArray.forEach((object) => { + object.removed = true; + }); + }, () => { + importedArray.forEach((object) => { + object.removed = false; + }); + }, importedArray.map((object) => object.clientID)); } select(objectStates, x, y) { @@ -736,7 +802,7 @@ let minimumState = null; for (const state of objectStates) { checkObjectType('object state', state, null, ObjectState); - if (state.outside) continue; + if (state.outside || state.hidden) continue; const object = this.objects[state.clientID]; if (typeof (object) === 'undefined') { @@ -757,6 +823,109 @@ distance: minimumDistance, }; } + + search(filters, frameFrom, frameTo) { + const [groups, query] = this.annotationsFilter.toJSONQuery(filters); + const sign = Math.sign(frameTo - frameFrom); + + const flattenedQuery = groups.flat(Number.MAX_SAFE_INTEGER); + const containsDifficultProperties = flattenedQuery + .some((fragment) => fragment + .match(/^width/) || fragment.match(/^height/)); + + const deepSearch = (deepSearchFrom, deepSearchTo) => { + // deepSearchFrom is expected to be a frame that doesn't satisfy a filter + // deepSearchTo is expected to be a frame that satifies a filter + + let [prev, next] = [deepSearchFrom, deepSearchTo]; + // half division method instead of linear search + while (!(Math.abs(prev - next) === 1)) { + const middle = next + Math.floor((prev - next) / 2); + const shapesData = this.tracks.map((track) => track.get(middle)); + const filtered = this.annotationsFilter.filter(shapesData, query); + if (filtered.length) { + next = middle; + } else { + prev = middle; + } + } + + return next; + }; + + const keyframesMemory = {}; + const predicate = sign > 0 + ? (frame) => frame <= frameTo + : (frame) => frame >= frameTo; + const update = sign > 0 + ? (frame) => frame + 1 + : (frame) => frame - 1; + for (let frame = frameFrom; predicate(frame); frame = update(frame)) { + // First prepare all data for the frame + // Consider all shapes, tags, and not outside tracks that have keyframe here + // In particular consider first and last frame as keyframes for all frames + const statesData = [].concat( + (frame in this.shapes ? this.shapes[frame] : []) + .map((shape) => shape.get(frame)), + (frame in this.tags ? this.tags[frame] : []) + .map((tag) => tag.get(frame)), + ); + const tracks = Object.values(this.tracks) + .filter((track) => ( + frame in track.shapes + || frame === frameFrom + || frame === frameTo + )); + statesData.push( + ...tracks.map((track) => track.get(frame)) + .filter((state) => !state.outside), + ); + + // Nothing to filtering, go to the next iteration + if (!statesData.length) { + continue; + } + + // Filtering + const filtered = this.annotationsFilter.filter(statesData, query); + + // Now we are checking whether we need deep search or not + // Deep search is needed in some difficult cases + // For example when filter contains fields which + // can be changed between keyframes (like: height and width of a shape) + // It's expected, that a track doesn't satisfy a filter on the previous keyframe + // At the same time it sutisfies the filter on the next keyframe + let withDeepSearch = false; + if (containsDifficultProperties) { + for (const track of tracks) { + const trackIsSatisfy = filtered.includes(track.clientID); + if (!trackIsSatisfy) { + keyframesMemory[track.clientID] = [ + filtered.includes(track.clientID), + frame, + ]; + } else if (keyframesMemory[track.clientID] + && keyframesMemory[track.clientID][0] === false) { + withDeepSearch = true; + } + } + } + + if (withDeepSearch) { + const reducer = sign > 0 ? Math.min : Math.max; + const deepSearchFrom = reducer( + ...Object.values(keyframesMemory).map((value) => value[1]), + ); + return deepSearch(deepSearchFrom, frame); + } + + if (filtered.length) { + return frame; + } + } + + return null; + } } module.exports = Collection; diff --git a/cvat-core/src/annotations-filter.js b/cvat-core/src/annotations-filter.js new file mode 100644 index 00000000000..62530167557 --- /dev/null +++ b/cvat-core/src/annotations-filter.js @@ -0,0 +1,240 @@ +/* +* Copyright (C) 2020 Intel Corporation +* SPDX-License-Identifier: MIT +*/ + +/* global + require:false +*/ + +const jsonpath = require('jsonpath'); +const { AttributeType } = require('./enums'); +const { ArgumentError } = require('./exceptions'); + + +class AnnotationsFilter { + constructor() { + // eslint-disable-next-line security/detect-unsafe-regex + this.operatorRegex = /(==|!=|<=|>=|>|<|~=)(?=(?:[^"]*(["])[^"]*\2)*[^"]*$)/g; + } + + // Method splits expression by operators that are outside of any brackets + _splitWithOperator(container, expression) { + const operators = ['|', '&']; + const splitted = []; + let nestedCounter = 0; + let isQuotes = false; + let start = -1; + + for (let i = 0; i < expression.length; i++) { + if (expression[i] === '"') { + // all quotes inside other quotes must + // be escaped by a user and changed to ` above + isQuotes = !isQuotes; + } + + // We don't split with operator inside brackets + // It will be done later in recursive call + if (!isQuotes && expression[i] === '(') { + nestedCounter++; + } + if (!isQuotes && expression[i] === ')') { + nestedCounter--; + } + + if (operators.includes(expression[i])) { + if (!nestedCounter) { + const subexpression = expression + .substr(start + 1, i - start - 1).trim(); + splitted.push(subexpression); + splitted.push(expression[i]); + start = i; + } + } + } + + const subexpression = expression + .substr(start + 1).trim(); + splitted.push(subexpression); + + splitted.forEach((internalExpression) => { + if (internalExpression === '|' || internalExpression === '&') { + container.push(internalExpression); + } else { + this._groupByBrackets( + container, + internalExpression, + ); + } + }); + } + + // Method groups bracket containings to nested arrays of container + _groupByBrackets(container, expression) { + if (!(expression.startsWith('(') && expression.endsWith(')'))) { + container.push(expression); + } + + let nestedCounter = 0; + let startBracket = null; + let endBracket = null; + let isQuotes = false; + + for (let i = 0; i < expression.length; i++) { + if (expression[i] === '"') { + // all quotes inside other quotes must + // be escaped by a user and changed to ` above + isQuotes = !isQuotes; + } + + if (!isQuotes && expression[i] === '(') { + nestedCounter++; + if (startBracket === null) { + startBracket = i; + } + } + + if (!isQuotes && expression[i] === ')') { + nestedCounter--; + if (!nestedCounter) { + endBracket = i; + + const subcontainer = []; + const subexpression = expression + .substr(startBracket + 1, endBracket - 1 - startBracket); + this._splitWithOperator( + subcontainer, + subexpression, + ); + + container.push(subcontainer); + + startBracket = null; + endBracket = null; + } + } + } + + if (startBracket !== null) { + throw Error('Extra opening bracket found'); + } + if (endBracket !== null) { + throw Error('Extra closing bracket found'); + } + } + + _parse(expression) { + const groups = []; + this._splitWithOperator(groups, expression); + } + + _join(groups) { + let expression = ''; + for (const group of groups) { + if (Array.isArray(group)) { + expression += `(${this._join(group)})`; + } else if (typeof (group) === 'string') { + // it can be operator or expression + if (group === '|' || group === '&') { + expression += group; + } else { + let [field, operator, , value] = group.split(this.operatorRegex); + field = `@.${field.trim()}`; + operator = operator.trim(); + value = value.trim(); + if (value === 'width' || value === 'height' || value.startsWith('attr')) { + value = `@.${value}`; + } + expression += [field, operator, value].join(''); + } + } + } + + return expression; + } + + _convertObjects(statesData) { + const objects = statesData.map((state) => { + const labelAttributes = state.label.attributes + .reduce((acc, attr) => { + acc[attr.id] = attr; + return acc; + }, {}); + + let xtl = Number.MAX_SAFE_INTEGER; + let xbr = Number.MIN_SAFE_INTEGER; + let ytl = Number.MAX_SAFE_INTEGER; + let ybr = Number.MIN_SAFE_INTEGER; + + state.points.forEach((coord, idx) => { + if (idx % 2) { // y + ytl = Math.min(ytl, coord); + ybr = Math.max(ybr, coord); + } else { // x + xtl = Math.min(xtl, coord); + xbr = Math.max(xbr, coord); + } + }); + + const [width, height] = [xbr - xtl, ybr - ytl]; + const attributes = {}; + Object.keys(state.attributes).reduce((acc, key) => { + const attr = labelAttributes[key]; + let value = state.attributes[key].replace(/\\"/g, '`'); + if (attr.inputType === AttributeType.NUMBER) { + value = +value; + } else if (attr.inputType === AttributeType.CHECKBOX) { + value = value === 'true'; + } + acc[attr.name] = value; + return acc; + }, attributes); + + return { + width, + height, + attr: attributes, + label: state.label.name.replace(/\\"/g, '`'), + serverID: state.serverID, + clientID: state.clientID, + type: state.objectType, + shape: state.objectShape, + occluded: state.occluded, + }; + }); + + return { + objects, + }; + } + + toJSONQuery(filters) { + try { + if (!Array.isArray(filters) || filters.some((value) => typeof (value) !== 'string')) { + throw Error('Argument must be an array of strings'); + } + + if (!filters.length) { + return [[], '$.objects[*].clientID']; + } + + const groups = []; + const expression = filters.map((filter) => `(${filter})`).join('|').replace(/\\"/g, '`'); + this._splitWithOperator(groups, expression); + return [groups, `$.objects[?(${this._join(groups)})].clientID`]; + } catch (error) { + throw new ArgumentError(`Wrong filter expression. ${error.toString()}`); + } + } + + filter(statesData, query) { + try { + const objects = this._convertObjects(statesData); + return jsonpath.query(objects, query); + } catch (error) { + throw new ArgumentError(`Could not apply the filter. ${error.toString()}`); + } + } +} + +module.exports = AnnotationsFilter; diff --git a/cvat-core/src/annotations-history.js b/cvat-core/src/annotations-history.js new file mode 100644 index 00000000000..d98973e5f86 --- /dev/null +++ b/cvat-core/src/annotations-history.js @@ -0,0 +1,71 @@ +/* +* Copyright (C) 2019-2020 Intel Corporation +* SPDX-License-Identifier: MIT +*/ + +const MAX_HISTORY_LENGTH = 128; + +class AnnotationHistory { + constructor() { + this.clear(); + } + + get() { + return { + undo: this._undo.map((undo) => undo.action), + redo: this._redo.map((redo) => redo.action), + }; + } + + do(action, undo, redo, clientIDs) { + const actionItem = { + clientIDs, + action, + undo, + redo, + }; + + this._undo = this._undo.slice(-MAX_HISTORY_LENGTH + 1); + this._undo.push(actionItem); + this._redo = []; + } + + undo(count) { + const affectedObjects = []; + for (let i = 0; i < count; i++) { + const action = this._undo.pop(); + if (action) { + action.undo(); + this._redo.push(action); + affectedObjects.push(...action.clientIDs); + } else { + break; + } + } + + return affectedObjects; + } + + redo(count) { + const affectedObjects = []; + for (let i = 0; i < count; i++) { + const action = this._redo.pop(); + if (action) { + action.redo(); + this._undo.push(action); + affectedObjects.push(...action.clientIDs); + } else { + break; + } + } + + return affectedObjects; + } + + clear() { + this._undo = []; + this._redo = []; + } +} + +module.exports = AnnotationHistory; diff --git a/cvat-core/src/annotations-objects.js b/cvat-core/src/annotations-objects.js index a9206a44107..65a30b5663a 100644 --- a/cvat-core/src/annotations-objects.js +++ b/cvat-core/src/annotations-objects.js @@ -1,5 +1,5 @@ /* -* Copyright (C) 2019 Intel Corporation +* Copyright (C) 2019-2020 Intel Corporation * SPDX-License-Identifier: MIT */ @@ -7,17 +7,18 @@ require:false */ + (() => { const ObjectState = require('./object-state'); const { checkObjectType, - isEnum, } = require('./common'); const { + colors, ObjectShape, ObjectType, AttributeType, - VisibleState, + HistoryActions, } = require('./enums'); const { @@ -28,15 +29,16 @@ const { Label } = require('./labels'); + const defaultGroupColor = '#E0E0E0'; + // Called with the Annotation context function objectStateFactory(frame, data) { const objectState = new ObjectState(data); - objectState.hidden = { + // eslint-disable-next-line no-underscore-dangle + objectState.__internal = { save: this.save.bind(this, frame, objectState), delete: this.delete.bind(this), - up: this.up.bind(this, frame, objectState), - down: this.down.bind(this, frame, objectState), }; return objectState; @@ -127,12 +129,18 @@ return ['true', 'false'].includes(value.toLowerCase()); } + if (type === AttributeType.TEXT) { + return true; + } + return values.includes(value); } class Annotation { - constructor(data, clientID, injection) { + constructor(data, clientID, color, injection) { this.taskLabels = injection.labels; + this.history = injection.history; + this.groupColors = injection.groupColors; this.clientID = clientID; this.serverID = data.id; this.group = data.group; @@ -140,15 +148,207 @@ this.frame = data.frame; this.removed = false; this.lock = false; + this.color = color; + this.updated = Date.now(); this.attributes = data.attributes.reduce((attributeAccumulator, attr) => { attributeAccumulator[attr.spec_id] = attr.value; return attributeAccumulator; }, {}); + this.groupObject = Object.defineProperties({}, { + color: { + get: () => { + if (this.group) { + return this.groupColors[this.group] + || colors[this.group % colors.length]; + } + return defaultGroupColor; + }, + set: (newColor) => { + if (this.group && typeof (newColor) === 'string' && /^#[0-9A-F]{6}$/i.test(newColor)) { + this.groupColors[this.group] = newColor; + } + }, + }, + id: { + get: () => this.group, + }, + }); this.appendDefaultAttributes(this.label); injection.groups.max = Math.max(injection.groups.max, this.group); } + _saveLock(lock) { + const undoLock = this.lock; + const redoLock = lock; + + this.history.do(HistoryActions.CHANGED_LOCK, () => { + this.lock = undoLock; + }, () => { + this.lock = redoLock; + }, [this.clientID]); + + this.lock = lock; + } + + _saveColor(color) { + const undoColor = this.color; + const redoColor = color; + + this.history.do(HistoryActions.CHANGED_COLOR, () => { + this.color = undoColor; + }, () => { + this.color = redoColor; + }, [this.clientID]); + + this.color = color; + } + + _saveHidden(hidden) { + const undoHidden = this.hidden; + const redoHidden = hidden; + + this.history.do(HistoryActions.CHANGED_HIDDEN, () => { + this.hidden = undoHidden; + }, () => { + this.hidden = redoHidden; + }, [this.clientID]); + + this.hidden = hidden; + } + + _saveLabel(label) { + const undoLabel = this.label; + const redoLabel = label; + const undoAttributes = { ...this.attributes }; + this.label = label; + this.attributes = {}; + this.appendDefaultAttributes(label); + const redoAttributes = { ...this.attributes }; + + this.history.do(HistoryActions.CHANGED_LABEL, () => { + this.label = undoLabel; + this.attributes = undoAttributes; + }, () => { + this.label = redoLabel; + this.attributes = redoAttributes; + }, [this.clientID]); + } + + _saveAttributes(attributes) { + const undoAttributes = { ...this.attributes }; + + for (const attrID of Object.keys(attributes)) { + this.attributes[attrID] = attributes[attrID]; + } + + const redoAttributes = { ...this.attributes }; + + this.history.do(HistoryActions.CHANGED_ATTRIBUTES, () => { + this.attributes = undoAttributes; + }, () => { + this.attributes = redoAttributes; + }, [this.clientID]); + } + + _validateStateBeforeSave(frame, data, updated) { + let fittedPoints = []; + + if (updated.label) { + checkObjectType('label', data.label, null, Label); + } + + const labelAttributes = data.label.attributes + .reduce((accumulator, value) => { + accumulator[value.id] = value; + return accumulator; + }, {}); + + if (updated.attributes) { + for (const attrID of Object.keys(data.attributes)) { + const value = data.attributes[attrID]; + if (attrID in labelAttributes) { + if (!validateAttributeValue(value, labelAttributes[attrID])) { + throw new ArgumentError( + `Trying to save an attribute attribute with id ${attrID} and invalid value ${value}`, + ); + } + } else { + throw new ArgumentError( + `The label of the shape doesn't have the attribute with id ${attrID} and value ${value}`, + ); + } + } + } + + if (updated.points) { + checkObjectType('points', data.points, null, Array); + checkNumberOfPoints(this.shapeType, data.points); + // cut points + const { width, height } = this.frameMeta[frame]; + for (let i = 0; i < data.points.length - 1; i += 2) { + const x = data.points[i]; + const y = data.points[i + 1]; + + checkObjectType('coordinate', x, 'number', null); + checkObjectType('coordinate', y, 'number', null); + + fittedPoints.push( + Math.clamp(x, 0, width), + Math.clamp(y, 0, height), + ); + } + + if (!checkShapeArea(this.shapeType, fittedPoints)) { + fittedPoints = []; + } + } + + if (updated.occluded) { + checkObjectType('occluded', data.occluded, 'boolean', null); + } + + if (updated.outside) { + checkObjectType('outside', data.outside, 'boolean', null); + } + + if (updated.zOrder) { + checkObjectType('zOrder', data.zOrder, 'integer', null); + } + + if (updated.lock) { + checkObjectType('lock', data.lock, 'boolean', null); + } + + if (updated.pinned) { + checkObjectType('pinned', data.pinned, 'boolean', null); + } + + if (updated.color) { + checkObjectType('color', data.color, 'string', null); + if (!/^#[0-9A-F]{6}$/i.test(data.color)) { + throw new ArgumentError( + `Got invalid color value: "${data.color}"`, + ); + } + } + + if (updated.hidden) { + checkObjectType('hidden', data.hidden, 'boolean', null); + } + + if (updated.keyframe) { + checkObjectType('keyframe', data.keyframe, 'boolean', null); + if (!this.shapes || (Object.keys(this.shapes).length === 1 && !data.keyframe)) { + throw new ArgumentError( + 'Can not remove the latest keyframe of an object. Consider removing the object instead', + ); + } + } + + return fittedPoints; + } + appendDefaultAttributes(label) { const labelAttributes = label.attributes; for (const attribute of labelAttributes) { @@ -158,34 +358,51 @@ } } + updateTimestamp(updated) { + const anyChanges = updated.label || updated.attributes || updated.points + || updated.outside || updated.occluded || updated.keyframe + || updated.zOrder; + + if (anyChanges) { + this.updated = Date.now(); + } + } + delete(force) { if (!this.lock || force) { this.removed = true; + + this.history.do(HistoryActions.REMOVED_OBJECT, () => { + this.removed = false; + }, () => { + this.removed = true; + }, [this.clientID]); } - return true; + return this.removed; } } class Drawn extends Annotation { constructor(data, clientID, color, injection) { - super(data, clientID, injection); - + super(data, clientID, color, injection); this.frameMeta = injection.frameMeta; - this.collectionZ = injection.collectionZ; - this.visibility = VisibleState.SHAPE; - - this.color = color; + this.hidden = false; + this.pinned = true; this.shapeType = null; } - _getZ(frame) { - this.collectionZ[frame] = this.collectionZ[frame] || { - max: 0, - min: 0, - }; + _savePinned(pinned) { + const undoPinned = this.pinned; + const redoPinned = pinned; + + this.history.do(HistoryActions.CHANGED_PINNED, () => { + this.pinned = undoPinned; + }, () => { + this.pinned = redoPinned; + }, [this.clientID]); - return this.collectionZ[frame]; + this.pinned = pinned; } save() { @@ -205,20 +422,6 @@ 'Is not implemented', ); } - - // Increase ZOrder within frame - up(frame, objectState) { - const z = this._getZ(frame); - z.max++; - objectState.zOrder = z.max; - } - - // Decrease ZOrder within frame - down(frame, objectState) { - const z = this._getZ(frame); - z.min--; - objectState.zOrder = z.min; - } } class Shape extends Drawn { @@ -227,10 +430,6 @@ this.points = data.points; this.occluded = data.occluded; this.zOrder = data.z_order; - - const z = this._getZ(this.frame); - z.max = Math.max(z.max, this.zOrder || 0); - z.min = Math.min(z.min, this.zOrder || 0); } // Method is used to export data to the server @@ -273,14 +472,56 @@ lock: this.lock, zOrder: this.zOrder, points: [...this.points], - attributes: Object.assign({}, this.attributes), + attributes: { ...this.attributes }, label: this.label, - group: this.group, + group: this.groupObject, color: this.color, - visibility: this.visibility, + hidden: this.hidden, + updated: this.updated, + pinned: this.pinned, + frame, }; } + _savePoints(points) { + const undoPoints = this.points; + const redoPoints = points; + + this.history.do(HistoryActions.CHANGED_POINTS, () => { + this.points = undoPoints; + }, () => { + this.points = redoPoints; + }, [this.clientID]); + + this.points = points; + } + + _saveOccluded(occluded) { + const undoOccluded = this.occluded; + const redoOccluded = occluded; + + this.history.do(HistoryActions.CHANGED_OCCLUDED, () => { + this.occluded = undoOccluded; + }, () => { + this.occluded = redoOccluded; + }, [this.clientID]); + + this.occluded = occluded; + } + + _saveZOrder(zOrder) { + const undoZOrder = this.zOrder; + const redoZOrder = zOrder; + + this.history.do(HistoryActions.CHANGED_ZORDER, () => { + this.zOrder = undoZOrder; + }, () => { + this.zOrder = redoZOrder; + }, [this.clientID]); + + this.zOrder = zOrder; + } + save(frame, data) { if (frame !== this.frame) { throw new ScriptingError( @@ -292,110 +533,48 @@ return objectStateFactory.call(this, frame, this.get(frame)); } - // All changes are done in this temporary object - const copy = this.get(frame); const updated = data.updateFlags; + const fittedPoints = this._validateStateBeforeSave(frame, data, updated); + // Now when all fields are validated, we can apply them if (updated.label) { - checkObjectType('label', data.label, null, Label); - copy.label = data.label; - copy.attributes = {}; - this.appendDefaultAttributes.call(copy, copy.label); + this._saveLabel(data.label); } if (updated.attributes) { - const labelAttributes = copy.label.attributes - .reduce((accumulator, value) => { - accumulator[value.id] = value; - return accumulator; - }, {}); - - for (const attrID of Object.keys(data.attributes)) { - const value = data.attributes[attrID]; - if (attrID in labelAttributes - && validateAttributeValue(value, labelAttributes[attrID])) { - copy.attributes[attrID] = value; - } else { - throw new ArgumentError( - `Trying to save unknown attribute with id ${attrID} and value ${value}`, - ); - } - } + this._saveAttributes(data.attributes); } - if (updated.points) { - checkObjectType('points', data.points, null, Array); - checkNumberOfPoints(this.shapeType, data.points); - - // cut points - const { width, height } = this.frameMeta[frame]; - const cutPoints = []; - for (let i = 0; i < data.points.length - 1; i += 2) { - const x = data.points[i]; - const y = data.points[i + 1]; - - checkObjectType('coordinate', x, 'number', null); - checkObjectType('coordinate', y, 'number', null); - - cutPoints.push( - Math.clamp(x, 0, width), - Math.clamp(y, 0, height), - ); - } - - if (checkShapeArea(this.shapeType, cutPoints)) { - copy.points = cutPoints; - } + if (updated.points && fittedPoints.length) { + this._savePoints(fittedPoints); } if (updated.occluded) { - checkObjectType('occluded', data.occluded, 'boolean', null); - copy.occluded = data.occluded; - } - - if (updated.group) { - checkObjectType('group', data.group, 'integer', null); - copy.group = data.group; + this._saveOccluded(data.occluded); } if (updated.zOrder) { - checkObjectType('zOrder', data.zOrder, 'integer', null); - copy.zOrder = data.zOrder; + this._saveZOrder(data.zOrder); } if (updated.lock) { - checkObjectType('lock', data.lock, 'boolean', null); - copy.lock = data.lock; + this._saveLock(data.lock); } - if (updated.color) { - checkObjectType('color', data.color, 'string', null); - if (/^#[0-9A-F]{6}$/i.test(data.color)) { - throw new ArgumentError( - `Got invalid color value: "${data.color}"`, - ); - } - - copy.color = data.color; + if (updated.pinned) { + this._savePinned(data.pinned); } - if (updated.visibility) { - if (!isEnum.call(VisibleState, data.visibility)) { - throw new ArgumentError( - `Got invalid visibility value: "${data.visibility}"`, - ); - } + if (updated.color) { + this._saveColor(data.color); + } - copy.visibility = data.visibility; + if (updated.hidden) { + this._saveHidden(data.hidden); } - // Reset flags and commit all changes + this.updateTimestamp(updated); updated.reset(); - for (const prop of Object.keys(copy)) { - if (prop in this) { - this[prop] = copy[prop]; - } - } return objectStateFactory.call(this, frame, this.get(frame)); } @@ -417,14 +596,8 @@ }, {}), }; - const z = this._getZ(value.frame); - z.max = Math.max(z.max, value.z_order); - z.min = Math.min(z.min, value.z_order); - return shapeAccumulator; }, {}); - - this.cache = {}; } // Method is used to export data to the server @@ -479,50 +652,69 @@ // Method is used to construct ObjectState objects get(frame) { - if (!(frame in this.cache)) { - const interpolation = Object.assign( - {}, this.getPosition(frame), - { - attributes: this.getAttributes(frame), - group: this.group, - objectType: ObjectType.TRACK, - shapeType: this.shapeType, - clientID: this.clientID, - serverID: this.serverID, - lock: this.lock, - color: this.color, - visibility: this.visibility, - }, - ); - - this.cache[frame] = interpolation; - } + const { + prev, + next, + first, + last, + } = this.boundedKeyframes(frame); - const result = JSON.parse(JSON.stringify(this.cache[frame])); - result.label = this.label; - return result; + return { + ...this.getPosition(frame, prev, next), + attributes: this.getAttributes(frame), + group: this.groupObject, + objectType: ObjectType.TRACK, + shapeType: this.shapeType, + clientID: this.clientID, + serverID: this.serverID, + lock: this.lock, + color: this.color, + hidden: this.hidden, + updated: this.updated, + label: this.label, + pinned: this.pinned, + keyframes: { + prev, + next, + first, + last, + }, + frame, + }; } - neighborsFrames(targetFrame) { - const frames = Object.keys(this.shapes).map(frame => +frame); + boundedKeyframes(targetFrame) { + const frames = Object.keys(this.shapes).map((frame) => +frame); let lDiff = Number.MAX_SAFE_INTEGER; let rDiff = Number.MAX_SAFE_INTEGER; + let first = Number.MAX_SAFE_INTEGER; + let last = Number.MIN_SAFE_INTEGER; for (const frame of frames) { + if (frame < first) { + first = frame; + } + if (frame > last) { + last = frame; + } + const diff = Math.abs(targetFrame - frame); - if (frame <= targetFrame && diff < lDiff) { + + if (frame < targetFrame && diff < lDiff) { lDiff = diff; - } else if (diff < rDiff) { + } else if (frame > targetFrame && diff < rDiff) { rDiff = diff; } } - const leftFrame = lDiff === Number.MAX_SAFE_INTEGER ? null : targetFrame - lDiff; - const rightFrame = rDiff === Number.MAX_SAFE_INTEGER ? null : targetFrame + rDiff; + const prev = lDiff === Number.MAX_SAFE_INTEGER ? null : targetFrame - lDiff; + const next = rDiff === Number.MAX_SAFE_INTEGER ? null : targetFrame + rDiff; return { - leftFrame, - rightFrame, + prev, + next, + first, + last, }; } @@ -553,277 +745,363 @@ return result; } - save(frame, data) { - if (this.lock && data.lock) { - return objectStateFactory.call(this, frame, this.get(frame)); - } - - // All changes are done in this temporary object - const copy = Object.assign(this.get(frame)); - copy.attributes = Object.assign(copy.attributes); - copy.points = [...copy.points]; + _saveLabel(label) { + const undoLabel = this.label; + const redoLabel = label; + const undoAttributes = { + unmutable: { ...this.attributes }, + mutable: Object.keys(this.shapes).map((key) => ({ + frame: +key, + attributes: { ...this.shapes[key].attributes }, + })), + }; - const updated = data.updateFlags; - let positionUpdated = false; + this.label = label; + this.attributes = {}; + for (const shape of Object.values(this.shapes)) { + shape.attributes = {}; + } + this.appendDefaultAttributes(label); - if (updated.label) { - checkObjectType('label', data.label, null, Label); - copy.label = data.label; - copy.attributes = {}; + const redoAttributes = { + unmutable: { ...this.attributes }, + mutable: Object.keys(this.shapes).map((key) => ({ + frame: +key, + attributes: { ...this.shapes[key].attributes }, + })), + }; - // Shape attributes will be removed later after all checks - this.appendDefaultAttributes.call(copy, copy.label); - } + this.history.do(HistoryActions.CHANGED_LABEL, () => { + this.label = undoLabel; + this.attributes = undoAttributes.unmutable; + for (const mutable of undoAttributes.mutable) { + this.shapes[mutable.frame].attributes = mutable.attributes; + } + }, () => { + this.label = redoLabel; + this.attributes = redoAttributes.unmutable; + for (const mutable of redoAttributes.mutable) { + this.shapes[mutable.frame].attributes = mutable.attributes; + } + }, [this.clientID]); + } - const labelAttributes = copy.label.attributes + _saveAttributes(frame, attributes) { + const current = this.get(frame); + const labelAttributes = this.label.attributes .reduce((accumulator, value) => { accumulator[value.id] = value; return accumulator; }, {}); - if (updated.attributes) { - for (const attrID of Object.keys(data.attributes)) { - const value = data.attributes[attrID]; - if (attrID in labelAttributes - && validateAttributeValue(value, labelAttributes[attrID])) { - copy.attributes[attrID] = value; - } else { - throw new ArgumentError( - `Trying to save unknown attribute with id ${attrID} and value ${value}`, - ); - } + const wasKeyframe = frame in this.shapes; + const undoAttributes = this.attributes; + const undoShape = wasKeyframe ? this.shapes[frame] : undefined; + + let mutableAttributesUpdated = false; + const redoAttributes = { ...this.attributes }; + for (const attrID of Object.keys(attributes)) { + if (!labelAttributes[attrID].mutable) { + redoAttributes[attrID] = attributes[attrID]; + } else if (attributes[attrID] !== current.attributes[attrID]) { + mutableAttributesUpdated = mutableAttributesUpdated + // not keyframe yet + || !(frame in this.shapes) + // keyframe, but without this attrID + || !(attrID in this.shapes[frame].attributes) + // keyframe with attrID, but with another value + || (this.shapes[frame].attributes[attrID] !== attributes[attrID]); + } + } + let redoShape; + if (mutableAttributesUpdated) { + if (wasKeyframe) { + redoShape = { + ...this.shapes[frame], + attributes: { + ...this.shapes[frame].attributes, + }, + }; + } else { + redoShape = { + frame, + zOrder: current.zOrder, + points: current.points, + outside: current.outside, + occluded: current.occluded, + attributes: {}, + }; } } - if (updated.points) { - checkObjectType('points', data.points, null, Array); - checkNumberOfPoints(this.shapeType, data.points); - - // cut points - const { width, height } = this.frameMeta[frame]; - const cutPoints = []; - for (let i = 0; i < data.points.length - 1; i += 2) { - const x = data.points[i]; - const y = data.points[i + 1]; + for (const attrID of Object.keys(attributes)) { + if (labelAttributes[attrID].mutable + && attributes[attrID] !== current.attributes[attrID]) { + redoShape.attributes[attrID] = attributes[attrID]; + } + } - checkObjectType('coordinate', x, 'number', null); - checkObjectType('coordinate', y, 'number', null); + this.attributes = redoAttributes; + if (redoShape) { + this.shapes[frame] = redoShape; + } - cutPoints.push( - Math.clamp(x, 0, width), - Math.clamp(y, 0, height), - ); + this.history.do(HistoryActions.CHANGED_ATTRIBUTES, () => { + this.attributes = undoAttributes; + if (undoShape) { + this.shapes[frame] = undoShape; + } else if (redoShape) { + delete this.shapes[frame]; } + }, () => { + this.attributes = redoAttributes; + if (redoShape) { + this.shapes[frame] = redoShape; + } + }, [this.clientID]); + } - if (checkShapeArea(this.shapeType, cutPoints)) { - copy.points = cutPoints; - positionUpdated = true; + _appendShapeActionToHistory(actionType, frame, undoShape, redoShape) { + this.history.do(actionType, () => { + if (!undoShape) { + delete this.shapes[frame]; + } else { + this.shapes[frame] = undoShape; } - } + }, () => { + if (!redoShape) { + delete this.shapes[frame]; + } else { + this.shapes[frame] = redoShape; + } + }, [this.clientID]); + } - if (updated.occluded) { - checkObjectType('occluded', data.occluded, 'boolean', null); - copy.occluded = data.occluded; - positionUpdated = true; - } + _savePoints(frame, points) { + const current = this.get(frame); + const wasKeyframe = frame in this.shapes; + const undoShape = wasKeyframe ? this.shapes[frame] : undefined; + const redoShape = wasKeyframe ? { ...this.shapes[frame], points } : { + frame, + points, + zOrder: current.zOrder, + outside: current.outside, + occluded: current.occluded, + attributes: {}, + }; - if (updated.outside) { - checkObjectType('outside', data.outside, 'boolean', null); - copy.outside = data.outside; - positionUpdated = true; - } + this.shapes[frame] = redoShape; + this._appendShapeActionToHistory( + HistoryActions.CHANGED_POINTS, + frame, + undoShape, + redoShape, + ); + } - if (updated.group) { - checkObjectType('group', data.group, 'integer', null); - copy.group = data.group; - } + _saveOutside(frame, outside) { + const current = this.get(frame); + const wasKeyframe = frame in this.shapes; + const undoShape = wasKeyframe ? this.shapes[frame] : undefined; + const redoShape = wasKeyframe ? { ...this.shapes[frame], outside } : { + frame, + outside, + zOrder: current.zOrder, + points: current.points, + occluded: current.occluded, + attributes: {}, + }; - if (updated.zOrder) { - checkObjectType('zOrder', data.zOrder, 'integer', null); - copy.zOrder = data.zOrder; - positionUpdated = true; - } + this.shapes[frame] = redoShape; + this._appendShapeActionToHistory( + HistoryActions.CHANGED_OUTSIDE, + frame, + undoShape, + redoShape, + ); + } - if (updated.lock) { - checkObjectType('lock', data.lock, 'boolean', null); - copy.lock = data.lock; - } + _saveOccluded(frame, occluded) { + const current = this.get(frame); + const wasKeyframe = frame in this.shapes; + const undoShape = wasKeyframe ? this.shapes[frame] : undefined; + const redoShape = wasKeyframe ? { ...this.shapes[frame], occluded } : { + frame, + occluded, + zOrder: current.zOrder, + points: current.points, + outside: current.outside, + attributes: {}, + }; - if (updated.color) { - checkObjectType('color', data.color, 'string', null); - if (/^#[0-9A-F]{6}$/i.test(data.color)) { - throw new ArgumentError( - `Got invalid color value: "${data.color}"`, - ); - } + this.shapes[frame] = redoShape; + this._appendShapeActionToHistory( + HistoryActions.CHANGED_OCCLUDED, + frame, + undoShape, + redoShape, + ); + } + + _saveZOrder(frame, zOrder) { + const current = this.get(frame); + const wasKeyframe = frame in this.shapes; + const undoShape = wasKeyframe ? this.shapes[frame] : undefined; + const redoShape = wasKeyframe ? { ...this.shapes[frame], zOrder } : { + frame, + zOrder, + occluded: current.occluded, + points: current.points, + outside: current.outside, + attributes: {}, + }; + + this.shapes[frame] = redoShape; + this._appendShapeActionToHistory( + HistoryActions.CHANGED_ZORDER, + frame, + undoShape, + redoShape, + ); + } + + _saveKeyframe(frame, keyframe) { + const current = this.get(frame); + const wasKeyframe = frame in this.shapes; - copy.color = data.color; + if ((keyframe && wasKeyframe) + || (!keyframe && !wasKeyframe)) { + return; } - if (updated.visibility) { - if (!isEnum.call(VisibleState, data.visibility)) { - throw new ArgumentError( - `Got invalid visibility value: "${data.visibility}"`, - ); - } + const undoShape = wasKeyframe ? this.shapes[frame] : undefined; + const redoShape = keyframe ? { + frame, + zOrder: current.zOrder, + points: current.points, + outside: current.outside, + occluded: current.occluded, + attributes: {}, + } : undefined; - copy.visibility = data.visibility; + if (redoShape) { + this.shapes[frame] = redoShape; + } else { + delete this.shapes[frame]; } - if (updated.keyframe) { - // Just check here - checkObjectType('keyframe', data.keyframe, 'boolean', null); + this._appendShapeActionToHistory( + HistoryActions.CHANGED_KEYFRAME, + frame, + undoShape, + redoShape, + ); + } + + save(frame, data) { + if (this.lock && data.lock) { + return objectStateFactory.call(this, frame, this.get(frame)); } - // Commit all changes - for (const prop of Object.keys(copy)) { - if (prop in this) { - this[prop] = copy[prop]; - } + const updated = data.updateFlags; + const fittedPoints = this._validateStateBeforeSave(frame, data, updated); - this.cache[frame][prop] = copy[prop]; + if (updated.label) { + this._saveLabel(data.label); } - if (updated.attributes) { - // Mutable attributes will be updated below - for (const attrID of Object.keys(copy.attributes)) { - if (!labelAttributes[attrID].mutable) { - this.shapes[frame].attributes[attrID] = data.attributes[attrID]; - this.shapes[frame].attributes[attrID] = data.attributes[attrID]; - } - } + if (updated.lock) { + this._saveLock(data.lock); } - if (updated.label) { - for (const shape of Object.values(this.shapes)) { - shape.attributes = {}; - } + if (updated.pinned) { + this._savePinned(data.pinned); } - // Remove keyframe - if (updated.keyframe && !data.keyframe) { - // Remove all cache after this keyframe because it have just become outdated - for (const cacheFrame in this.cache) { - if (+cacheFrame > frame) { - delete this.cache[cacheFrame]; - } - } + if (updated.color) { + this._saveColor(data.color); + } - this.cache[frame].keyframe = false; - delete this.shapes[frame]; - updated.reset(); + if (updated.hidden) { + this._saveHidden(data.hidden); + } - return objectStateFactory.call(this, frame, this.get(frame)); + if (updated.points && fittedPoints.length) { + this._savePoints(frame, fittedPoints); } - // Add/update keyframe - if (positionUpdated || (updated.keyframe && data.keyframe)) { - // Remove all cache after this keyframe because it have just become outdated - for (const cacheFrame in this.cache) { - if (+cacheFrame > frame) { - delete this.cache[cacheFrame]; - } - } + if (updated.outside) { + this._saveOutside(frame, data.outside); + } + + if (updated.occluded) { + this._saveOccluded(frame, data.occluded); + } - this.cache[frame].keyframe = true; - data.keyframe = true; + if (updated.zOrder) { + this._saveZOrder(frame, data.zOrder); + } - this.shapes[frame] = { - frame, - zOrder: copy.zOrder, - points: copy.points, - outside: copy.outside, - occluded: copy.occluded, - attributes: {}, - }; + if (updated.attributes) { + this._saveAttributes(frame, data.attributes); + } - if (updated.attributes) { - // Unmutable attributes were updated above - for (const attrID of Object.keys(copy.attributes)) { - if (labelAttributes[attrID].mutable) { - this.shapes[frame].attributes[attrID] = data.attributes[attrID]; - this.shapes[frame].attributes[attrID] = data.attributes[attrID]; - } - } - } + if (updated.keyframe) { + this._saveKeyframe(frame, data.keyframe); } + this.updateTimestamp(updated); updated.reset(); return objectStateFactory.call(this, frame, this.get(frame)); } - getPosition(targetFrame) { - const { - leftFrame, - rightFrame, - } = this.neighborsFrames(targetFrame); - + getPosition(targetFrame, leftKeyframe, rightFrame) { + const leftFrame = targetFrame in this.shapes ? targetFrame : leftKeyframe; const rightPosition = Number.isInteger(rightFrame) ? this.shapes[rightFrame] : null; const leftPosition = Number.isInteger(leftFrame) ? this.shapes[leftFrame] : null; - if (leftPosition && leftFrame === targetFrame) { + if (leftPosition && rightPosition) { + return { + ...this.interpolatePosition( + leftPosition, + rightPosition, + (targetFrame - leftFrame) / (rightFrame - leftFrame), + ), + keyframe: targetFrame in this.shapes, + }; + } + + if (leftPosition) { return { points: [...leftPosition.points], occluded: leftPosition.occluded, outside: leftPosition.outside, zOrder: leftPosition.zOrder, - keyframe: true, + keyframe: targetFrame in this.shapes, }; } - if (rightPosition && leftPosition) { - return Object.assign({}, this.interpolatePosition( - leftPosition, - rightPosition, - (targetFrame - leftFrame) / (rightFrame - leftFrame), - ), { - keyframe: false, - }); - } - if (rightPosition) { return { points: [...rightPosition.points], occluded: rightPosition.occluded, outside: true, - zOrder: 0, - keyframe: false, - }; - } - - if (leftPosition) { - return { - points: [...leftPosition.points], - occluded: leftPosition.occluded, - outside: leftPosition.outside, - zOrder: 0, - keyframe: false, + zOrder: rightPosition.zOrder, + keyframe: targetFrame in this.shapes, }; } - throw new ScriptingError( - `No one neightbour frame found for the track with client ID: "${this.id}"`, + throw new DataError( + 'No one left position or right position was found. ' + + `Interpolation impossible. Client ID: ${this.clientID}`, ); } - - delete(force) { - if (!this.lock || force) { - this.removed = true; - this.resetCache(); - } - - return true; - } - - resetCache() { - this.cache = {}; - } } class Tag extends Annotation { - constructor(data, clientID, injection) { - super(data, clientID, injection); + constructor(data, clientID, color, injection) { + super(data, clientID, color, injection); } // Method is used to export data to the server @@ -858,16 +1136,18 @@ clientID: this.clientID, serverID: this.serverID, lock: this.lock, - attributes: Object.assign({}, this.attributes), + attributes: { ...this.attributes }, label: this.label, - group: this.group, + group: this.groupObject, + updated: this.updated, + frame, }; } save(frame, data) { if (frame !== this.frame) { throw new ScriptingError( - 'Got frame is not equal to the frame of the shape', + 'Got frame is not equal to the frame of the tag', ); } @@ -875,45 +1155,24 @@ return objectStateFactory.call(this, frame, this.get(frame)); } - // All changes are done in this temporary object - const copy = this.get(frame); const updated = data.updateFlags; + this._validateStateBeforeSave(frame, data, updated); + // Now when all fields are validated, we can apply them if (updated.label) { - checkObjectType('label', data.label, null, Label); - copy.label = data.label; - copy.attributes = {}; - this.appendDefaultAttributes.call(copy, copy.label); + this._saveLabel(data.label); } if (updated.attributes) { - const labelAttributes = copy.label - .attributes.map(attr => `${attr.id}`); - - for (const attrID of Object.keys(data.attributes)) { - if (labelAttributes.includes(attrID)) { - copy.attributes[attrID] = data.attributes[attrID]; - } - } - } - - if (updated.group) { - checkObjectType('group', data.group, 'integer', null); - copy.group = data.group; + this._saveAttributes(data.attributes); } if (updated.lock) { - checkObjectType('lock', data.lock, 'boolean', null); - copy.lock = data.lock; + this._saveLock(data.lock); } - // Reset flags and commit all changes + this.updateTimestamp(updated); updated.reset(); - for (const prop of Object.keys(copy)) { - if (prop in this) { - this[prop] = copy[prop]; - } - } return objectStateFactory.call(this, frame, this.get(frame)); } @@ -923,6 +1182,7 @@ constructor(data, clientID, color, injection) { super(data, clientID, color, injection); this.shapeType = ObjectShape.RECTANGLE; + this.pinned = false; checkNumberOfPoints(this.shapeType, this.points); } @@ -1090,6 +1350,7 @@ constructor(data, clientID, color, injection) { super(data, clientID, color, injection); this.shapeType = ObjectShape.RECTANGLE; + this.pinned = false; for (const shape of Object.values(this.shapes)) { checkNumberOfPoints(this.shapeType, shape.points); } @@ -1338,6 +1599,15 @@ return [processedSource, processedTarget]; } + if (offset === 0) { + return { + points: [...leftPosition.points], + occluded: leftPosition.occluded, + outside: leftPosition.outside, + zOrder: leftPosition.zOrder, + }; + } + let leftBox = findBox(leftPosition.points); let rightBox = findBox(rightPosition.points); @@ -1399,8 +1669,8 @@ // some points from source and target can absent in mapping // source, target - arrays of points. Target array size >= sourse array size appendMapping(mapping, source, target) { - const targetMatched = Object.values(mapping).map(x => +x); - const sourceMatched = Object.keys(mapping).map(x => +x); + const targetMatched = Object.values(mapping).map((x) => +x); + const sourceMatched = Object.keys(mapping).map((x) => +x); const orderForReceive = []; function findNeighbors(point) { diff --git a/cvat-core/src/annotations-saver.js b/cvat-core/src/annotations-saver.js index 2a27e2d548d..42a07ecb39d 100644 --- a/cvat-core/src/annotations-saver.js +++ b/cvat-core/src/annotations-saver.js @@ -102,12 +102,18 @@ }, }; + const keys = ['id', 'label_id', 'group', 'frame', + 'occluded', 'z_order', 'points', 'type', 'shapes', + 'attributes', 'value', 'spec_id', 'outside']; + // Find created and updated objects for (const type of Object.keys(exported)) { for (const object of exported[type]) { if (object.id in this.initialObjects[type]) { - const exportedHash = JSON.stringify(object); - const initialHash = JSON.stringify(this.initialObjects[type][object.id]); + const exportedHash = JSON.stringify(object, keys); + const initialHash = JSON.stringify( + this.initialObjects[type][object.id], keys, + ); if (exportedHash !== initialHash) { splitted.updated[type].push(object); } @@ -161,10 +167,6 @@ for (let i = 0; i < indexes[type].length; i++) { const clientID = indexes[type][i]; this.collection.objects[clientID].serverID = saved[type][i].id; - if (type === 'tracks') { - // We have to reset cache because of old value of serverID was saved there - this.collection.objects[clientID].resetCache(); - } } } } @@ -194,80 +196,67 @@ }; } - try { - const exported = this.collection.export(); - const { flush } = this.collection; - if (flush) { - onUpdate('New objects are being saved..'); - const indexes = this._receiveIndexes(exported); - const savedData = await this._put({ ...exported, version: this.version }); - this.version = savedData.version; - this.collection.flush = false; - - onUpdate('Saved objects are being updated in the client'); - this._updateCreatedObjects(savedData, indexes); - - onUpdate('Initial state is being updated'); - - this._resetState(); - for (const type of Object.keys(this.initialObjects)) { - for (const object of savedData[type]) { - this.initialObjects[type][object.id] = object; - } + const exported = this.collection.export(); + const { flush } = this.collection; + if (flush) { + onUpdate('Created objects are being saved on the server'); + const indexes = this._receiveIndexes(exported); + const savedData = await this._put({ ...exported, version: this.version }); + this.version = savedData.version; + this.collection.flush = false; + + this._updateCreatedObjects(savedData, indexes); + + this._resetState(); + for (const type of Object.keys(this.initialObjects)) { + for (const object of savedData[type]) { + this.initialObjects[type][object.id] = object; } - } else { - const { - created, - updated, - deleted, - } = this._split(exported); - - onUpdate('New objects are being saved..'); - const indexes = this._receiveIndexes(created); - const createdData = await this._create({ ...created, version: this.version }); - this.version = createdData.version; - - onUpdate('Saved objects are being updated in the client'); - this._updateCreatedObjects(createdData, indexes); - - onUpdate('Initial state is being updated'); - for (const type of Object.keys(this.initialObjects)) { - for (const object of createdData[type]) { - this.initialObjects[type][object.id] = object; - } + } + } else { + const { + created, + updated, + deleted, + } = this._split(exported); + + onUpdate('Created objects are being saved on the server'); + const indexes = this._receiveIndexes(created); + const createdData = await this._create({ ...created, version: this.version }); + this.version = createdData.version; + + this._updateCreatedObjects(createdData, indexes); + + for (const type of Object.keys(this.initialObjects)) { + for (const object of createdData[type]) { + this.initialObjects[type][object.id] = object; } + } - onUpdate('Changed objects are being saved..'); - this._receiveIndexes(updated); - const updatedData = await this._update({ ...updated, version: this.version }); - this.version = updatedData.version; + onUpdate('Updated objects are being saved on the server'); + this._receiveIndexes(updated); + const updatedData = await this._update({ ...updated, version: this.version }); + this.version = updatedData.version; - onUpdate('Initial state is being updated'); - for (const type of Object.keys(this.initialObjects)) { - for (const object of updatedData[type]) { - this.initialObjects[type][object.id] = object; - } + for (const type of Object.keys(this.initialObjects)) { + for (const object of updatedData[type]) { + this.initialObjects[type][object.id] = object; } + } - onUpdate('Changed objects are being saved..'); - this._receiveIndexes(deleted); - const deletedData = await this._delete({ ...deleted, version: this.version }); - this._version = deletedData.version; + onUpdate('Deleted objects are being deleted from the server'); + this._receiveIndexes(deleted); + const deletedData = await this._delete({ ...deleted, version: this.version }); + this._version = deletedData.version; - onUpdate('Initial state is being updated'); - for (const type of Object.keys(this.initialObjects)) { - for (const object of deletedData[type]) { - delete this.initialObjects[type][object.id]; - } + for (const type of Object.keys(this.initialObjects)) { + for (const object of deletedData[type]) { + delete this.initialObjects[type][object.id]; } } - - this.hash = this._getHash(); - onUpdate('Saving is done'); - } catch (error) { - onUpdate(`Can not save annotations: ${error.message}`); - throw error; } + + this.hash = this._getHash(); } hasUnsavedChanges() { diff --git a/cvat-core/src/annotations.js b/cvat-core/src/annotations.js index 8c9014caf52..3ee70d3cb48 100644 --- a/cvat-core/src/annotations.js +++ b/cvat-core/src/annotations.js @@ -1,5 +1,5 @@ /* -* Copyright (C) 2019 Intel Corporation +* Copyright (C) 2019-2020 Intel Corporation * SPDX-License-Identifier: MIT */ @@ -11,6 +11,7 @@ const serverProxy = require('./server-proxy'); const Collection = require('./annotations-collection'); const AnnotationsSaver = require('./annotations-saver'); + const AnnotationsHistory = require('./annotations-history'); const { checkObjectType } = require('./common'); const { Task } = require('./session'); const { @@ -56,28 +57,36 @@ frameMeta[i] = await session.frames.get(i); } + const history = new AnnotationsHistory(); const collection = new Collection({ labels: session.labels || session.task.labels, + history, startFrame, stopFrame, frameMeta, - }).import(rawAnnotations); + }); + collection.import(rawAnnotations); const saver = new AnnotationsSaver(rawAnnotations.version, collection, session); cache.set(session, { collection, saver, - + history, }); } } - async function getAnnotations(session, frame, filter) { - await getAnnotationsFromServer(session); + async function getAnnotations(session, frame, allTracks, filters) { const sessionType = session instanceof Task ? 'task' : 'job'; const cache = getCache(sessionType); - return cache.get(session).collection.get(frame, filter); + + if (cache.has(session)) { + return cache.get(session).collection.get(frame, allTracks, filters); + } + + await getAnnotationsFromServer(session); + return cache.get(session).collection.get(frame, allTracks, filters); } async function saveAnnotations(session, onUpdate) { @@ -91,6 +100,19 @@ // If a collection wasn't uploaded, than it wasn't changed, finally we shouldn't save it } + function searchAnnotations(session, filters, frameFrom, frameTo) { + const sessionType = session instanceof Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (cache.has(session)) { + return cache.get(session).collection.search(filters, frameFrom, frameTo); + } + + throw new DataError( + 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', + ); + } + function mergeAnnotations(session, objectStates) { const sessionType = session instanceof Task ? 'task' : 'job'; const cache = getCache(sessionType); @@ -225,12 +247,84 @@ return result; } + async function exportDataset(session, format) { + if (!(format instanceof String || typeof format === 'string')) { + throw new ArgumentError( + 'Format must be a string', + ); + } + if (!(session instanceof Task)) { + throw new ArgumentError( + 'A dataset can only be created from a task', + ); + } + + let result = null; + result = await serverProxy.tasks + .exportDataset(session.id, format); + + return result; + } + + function undoActions(session, count) { + const sessionType = session instanceof Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (cache.has(session)) { + return cache.get(session).history.undo(count); + } + + throw new DataError( + 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', + ); + } + + function redoActions(session, count) { + const sessionType = session instanceof Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (cache.has(session)) { + return cache.get(session).history.redo(count); + } + + throw new DataError( + 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', + ); + } + + function clearActions(session) { + const sessionType = session instanceof Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (cache.has(session)) { + return cache.get(session).history.clear(); + } + + throw new DataError( + 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', + ); + } + + function getActions(session) { + const sessionType = session instanceof Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (cache.has(session)) { + return cache.get(session).history.get(); + } + + throw new DataError( + 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', + ); + } + module.exports = { getAnnotations, putAnnotations, saveAnnotations, hasUnsavedChanges, mergeAnnotations, + searchAnnotations, splitAnnotations, groupAnnotations, clearAnnotations, @@ -238,5 +332,10 @@ selectObject, uploadAnnotations, dumpAnnotations, + exportDataset, + undoActions, + redoActions, + clearActions, + getActions, }; })(); diff --git a/cvat-core/src/api-implementation.js b/cvat-core/src/api-implementation.js index 8f4b8841cd1..4b7e2872719 100644 --- a/cvat-core/src/api-implementation.js +++ b/cvat-core/src/api-implementation.js @@ -9,7 +9,6 @@ require:false */ - (() => { const PluginRegistry = require('./plugins'); const serverProxy = require('./server-proxy'); @@ -31,6 +30,26 @@ const { ArgumentError } = require('./exceptions'); const { Task } = require('./session'); + function attachUsers(task, users) { + if (task.assignee !== null) { + [task.assignee] = users.filter((user) => user.id === task.assignee); + } + + for (const segment of task.segments) { + for (const job of segment.jobs) { + if (job.assignee !== null) { + [job.assignee] = users.filter((user) => user.id === job.assignee); + } + } + } + + if (task.owner !== null) { + [task.owner] = users.filter((user) => user.id === task.owner); + } + + return task; + } + function implementAPI(cvat) { cvat.plugins.list.implementation = PluginRegistry.list; cvat.plugins.register.implementation = PluginRegistry.register.bind(cvat); @@ -47,7 +66,12 @@ cvat.server.formats.implementation = async () => { const result = await serverProxy.server.formats(); - return result.map(el => new AnnotationFormat(el)); + return result.map((el) => new AnnotationFormat(el)); + }; + + cvat.server.datasetFormats.implementation = async () => { + const result = await serverProxy.server.datasetFormats(); + return result; }; cvat.server.register.implementation = async (username, firstName, lastName, @@ -69,6 +93,11 @@ return result; }; + cvat.server.request.implementation = async (url, data) => { + const result = await serverProxy.server.request(url, data); + return result; + }; + cvat.users.get.implementation = async (filter) => { checkFilter(filter, { self: isBoolean, @@ -82,7 +111,7 @@ users = await serverProxy.users.getUsers(); } - users = users.map(user => new User(user)); + users = users.map((user) => new User(user)); return users; }; @@ -116,8 +145,12 @@ // If task was found by its id, then create task instance and get Job instance from it if (tasks !== null && tasks.length) { - const task = new Task(tasks[0]); - return filter.jobID ? task.jobs.filter(job => job.id === filter.jobID) : task.jobs; + const users = (await serverProxy.users.getUsers()) + .map((userData) => new User(userData)); + const task = new Task(attachUsers(tasks[0], users)); + + return filter.jobID ? task.jobs + .filter((job) => job.id === filter.jobID) : task.jobs; } return []; @@ -158,8 +191,14 @@ } } + const users = (await serverProxy.users.getUsers()) + .map((userData) => new User(userData)); const tasksData = await serverProxy.tasks.getTasks(searchParams.toString()); - const tasks = tasksData.map(task => new Task(task)); + const tasks = tasksData + .map((task) => attachUsers(task, users)) + .map((task) => new Task(task)); + + tasks.count = tasksData.count; return tasks; diff --git a/cvat-core/src/api.js b/cvat-core/src/api.js index 21c2299a249..7f9ad9c7448 100644 --- a/cvat-core/src/api.js +++ b/cvat-core/src/api.js @@ -1,5 +1,5 @@ /* -* Copyright (C) 2019 Intel Corporation +* Copyright (C) 2019-2020 Intel Corporation * SPDX-License-Identifier: MIT */ @@ -27,8 +27,9 @@ function build() { AttributeType, ObjectType, ObjectShape, - VisibleState, LogType, + HistoryActions, + colors, } = require('./enums'); const { @@ -115,6 +116,20 @@ function build() { .apiWrapper(cvat.server.formats); return result; }, + /** + * Method returns available dataset export formats + * @method exportFormats + * @async + * @memberof module:API.cvat.server + * @returns {module:String[]} + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ServerError} + */ + async datasetFormats() { + const result = await PluginRegistry + .apiWrapper(cvat.server.datasetFormats); + return result; + }, /** * Method allows to register on a server * @method register @@ -177,6 +192,22 @@ function build() { .apiWrapper(cvat.server.authorized); return result; }, + /** + * Method allows to do requests via cvat-core with authorization headers + * @method request + * @async + * @memberof module:API.cvat.server + * @param {string} url + * @param {Object} data request parameters: method, headers, data, etc. + * @returns {Object | undefined} response data if exist + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ServerError} + */ + async request(url, data) { + const result = await PluginRegistry + .apiWrapper(cvat.server.request, url, data); + return result; + }, }, /** * Namespace is used for getting tasks @@ -467,8 +498,9 @@ function build() { AttributeType, ObjectType, ObjectShape, - VisibleState, LogType, + HistoryActions, + colors, }, /** * Namespace is used for access to exceptions diff --git a/cvat-core/src/common.js b/cvat-core/src/common.js index 51819bffae1..93165db9d0d 100644 --- a/cvat-core/src/common.js +++ b/cvat-core/src/common.js @@ -56,7 +56,7 @@ if (typeof (value) !== type) { // specific case for integers which aren't native type in JS if (type === 'integer' && Number.isInteger(value)) { - return; + return true; } throw new ArgumentError( @@ -77,6 +77,8 @@ ); } } + + return true; } module.exports = { diff --git a/cvat-core/src/enums.js b/cvat-core/src/enums.js index 241cca71a0c..2c34290876b 100644 --- a/cvat-core/src/enums.js +++ b/cvat-core/src/enums.js @@ -1,5 +1,5 @@ /* -* Copyright (C) 2019 Intel Corporation +* Copyright (C) 2019-2020 Intel Corporation * SPDX-License-Identifier: MIT */ @@ -102,22 +102,6 @@ POINTS: 'points', }); - /** - * Object visibility states - * @enum {string} - * @name ObjectShape - * @memberof module:API.cvat.enums - * @property {string} ALL 'all' - * @property {string} SHAPE 'shape' - * @property {string} NONE 'none' - * @readonly - */ - const VisibleState = Object.freeze({ - ALL: 'all', - SHAPE: 'shape', - NONE: 'none', - }); - /** * Event types * @enum {number} @@ -182,6 +166,61 @@ rotateImage: 26, }; + /** + * Types of actions with annotations + * @enum {string} + * @name HistoryActions + * @memberof module:API.cvat.enums + * @property {string} CHANGED_LABEL Changed label + * @property {string} CHANGED_ATTRIBUTES Changed attributes + * @property {string} CHANGED_POINTS Changed points + * @property {string} CHANGED_OUTSIDE Changed outside + * @property {string} CHANGED_OCCLUDED Changed occluded + * @property {string} CHANGED_ZORDER Changed z-order + * @property {string} CHANGED_LOCK Changed lock + * @property {string} CHANGED_COLOR Changed color + * @property {string} CHANGED_HIDDEN Changed hidden + * @property {string} MERGED_OBJECTS Merged objects + * @property {string} SPLITTED_TRACK Splitted track + * @property {string} GROUPED_OBJECTS Grouped objects + * @property {string} CREATED_OBJECTS Created objects + * @property {string} REMOVED_OBJECT Removed object + * @readonly + */ + const HistoryActions = Object.freeze({ + CHANGED_LABEL: 'Changed label', + CHANGED_ATTRIBUTES: 'Changed attributes', + CHANGED_POINTS: 'Changed points', + CHANGED_OUTSIDE: 'Changed outside', + CHANGED_OCCLUDED: 'Changed occluded', + CHANGED_ZORDER: 'Changed z-order', + CHANGED_KEYFRAME: 'Changed keyframe', + CHANGED_LOCK: 'Changed lock', + CHANGED_PINNED: 'Changed pinned', + CHANGED_COLOR: 'Changed color', + CHANGED_HIDDEN: 'Changed hidden', + MERGED_OBJECTS: 'Merged objects', + SPLITTED_TRACK: 'Splitted track', + GROUPED_OBJECTS: 'Grouped objects', + CREATED_OBJECTS: 'Created objects', + REMOVED_OBJECT: 'Removed object', + }); + + /** + * Array of hex colors + * @type {module:API.cvat.classes.Loader[]} values + * @name colors + * @memberof module:API.cvat.enums + * @type {string[]} + * @readonly + */ + const colors = [ + '#FF355E', '#E936A7', '#FD5B78', '#FF007C', '#FF00CC', '#66FF66', + '#50BFE6', '#CCFF00', '#FFFF66', '#FF9966', '#FF6037', '#FFCC33', + '#AAF0D1', '#FF3855', '#FFF700', '#A7F432', '#FF5470', '#FAFA37', + '#FF7A00', '#FF9933', '#AFE313', '#00CC99', '#FF5050', '#733380', + ]; + module.exports = { ShareFileType, TaskStatus, @@ -189,7 +228,8 @@ AttributeType, ObjectType, ObjectShape, - VisibleState, LogType, + HistoryActions, + colors, }; })(); diff --git a/cvat-core/src/frames.js b/cvat-core/src/frames.js index 06342bd001b..4141de07bfc 100644 --- a/cvat-core/src/frames.js +++ b/cvat-core/src/frames.js @@ -52,6 +52,13 @@ value: tid, writable: false, }, + /** + * @name number + * @type {integer} + * @memberof module:API.cvat.classes.FrameData + * @readonly + * @instance + */ number: { value: number, writable: false, @@ -93,9 +100,14 @@ } else if (isBrowser) { const reader = new FileReader(); reader.onload = () => { - frameCache[this.tid][this.number] = reader.result; - resolve(frameCache[this.tid][this.number]); + const image = new Image(frame.width, frame.height); + image.onload = () => { + frameCache[this.tid][this.number] = image; + resolve(frameCache[this.tid][this.number]); + }; + image.src = reader.result; }; + reader.readAsDataURL(frame); } } @@ -105,10 +117,31 @@ }); }; + async function getPreview(taskID) { + return new Promise(async (resolve, reject) => { + try { + // Just go to server and get preview (no any cache) + const result = await serverProxy.frames.getPreview(taskID); + if (isNode) { + resolve(global.Buffer.from(result, 'binary').toString('base64')); + } else if (isBrowser) { + const reader = new FileReader(); + reader.onload = () => { + resolve(reader.result); + }; + reader.readAsDataURL(result); + } + } catch (error) { + reject(error); + } + }); + } + async function getFrame(taskID, mode, frame) { if (!(taskID in frameDataCache)) { - frameDataCache[taskID] = {}; - frameDataCache[taskID].meta = await serverProxy.frames.getMeta(taskID); + frameDataCache[taskID] = { + meta: await serverProxy.frames.getMeta(taskID), + }; frameCache[taskID] = {}; } @@ -140,5 +173,6 @@ module.exports = { FrameData, getFrame, + getPreview, }; })(); diff --git a/cvat-core/src/labels.js b/cvat-core/src/labels.js index dc0235ffd9b..fa92a61196e 100644 --- a/cvat-core/src/labels.js +++ b/cvat-core/src/labels.js @@ -8,7 +8,10 @@ */ (() => { - const { AttributeType } = require('./enums'); + const { + AttributeType, + colors, + } = require('./enums'); const { ArgumentError } = require('./exceptions'); /** @@ -136,6 +139,7 @@ const data = { id: undefined, name: undefined, + color: undefined, }; for (const key in data) { @@ -146,6 +150,9 @@ } } + if (typeof (data.id) !== 'undefined') { + data.color = colors[data.id % colors.length]; + } data.attributes = []; if (Object.prototype.hasOwnProperty.call(initialData, 'attributes') @@ -176,6 +183,23 @@ name: { get: () => data.name, }, + /** + * @name color + * @type {string} + * @memberof module:API.cvat.classes.Label + * @readonly + * @instance + */ + color: { + get: () => data.color, + set: (color) => { + if (colors.includes(color)) { + data.color = color; + } else { + throw new ArgumentError('Trying to set unknown color'); + } + }, + }, /** * @name attributes * @type {module:API.cvat.classes.Attribute[]} @@ -192,7 +216,7 @@ toJSON() { const object = { name: this.name, - attributes: [...this.attributes.map(el => el.toJSON())], + attributes: [...this.attributes.map((el) => el.toJSON())], }; if (typeof (this.id) !== 'undefined') { diff --git a/cvat-core/src/object-state.js b/cvat-core/src/object-state.js index a3ad09520e2..e6a42f188af 100644 --- a/cvat-core/src/object-state.js +++ b/cvat-core/src/object-state.js @@ -19,13 +19,10 @@ /** * @param {Object} serialized - is an dictionary which contains * initial information about an ObjectState; - * Necessary fields: objectType, shapeType - * (don't have setters) - * Necessary fields for objects which haven't been added to collection yet: frame - * (doesn't have setters) - * Optional fields: points, group, zOrder, outside, occluded, - * attributes, lock, label, mode, color, keyframe, clientID, serverID - * These fields can be set later via setters + *
Necessary fields: objectType, shapeType, frame, updated, group + *
Optional fields: keyframes, clientID, serverID + *
Optional fields which can be set later: points, zOrder, outside, + * occluded, hidden, attributes, lock, label, color, keyframe */ constructor(serialized) { const data = { @@ -37,11 +34,14 @@ occluded: null, keyframe: null, - group: null, zOrder: null, lock: null, color: null, - visibility: null, + hidden: null, + pinned: null, + keyframes: serialized.keyframes, + group: serialized.group, + updated: serialized.updated, clientID: serialized.clientID, serverID: serialized.serverID, @@ -63,11 +63,13 @@ this.occluded = false; this.keyframe = false; - this.group = false; this.zOrder = false; + this.pinned = false; this.lock = false; this.color = false; - this.visibility = false; + this.hidden = false; + + return reset; }, writable: false, }); @@ -153,17 +155,17 @@ data.color = color; }, }, - visibility: { + hidden: { /** - * @name visibility - * @type {module:API.cvat.enums.VisibleState} + * @name hidden + * @type {boolean} * @memberof module:API.cvat.classes.ObjectState * @instance */ - get: () => data.visibility, - set: (visibility) => { - data.updateFlags.visibility = true; - data.visibility = visibility; + get: () => data.hidden, + set: (hidden) => { + data.updateFlags.hidden = true; + data.hidden = hidden; }, }, points: { @@ -190,21 +192,19 @@ }, group: { /** + * Object with short group info { color, id } * @name group - * @type {integer} + * @type {object} * @memberof module:API.cvat.classes.ObjectState * @instance + * @readonly */ get: () => data.group, - set: (group) => { - data.updateFlags.group = true; - data.group = group; - }, }, zOrder: { /** * @name zOrder - * @type {integer} + * @type {integer | null} * @memberof module:API.cvat.classes.ObjectState * @instance */ @@ -240,6 +240,23 @@ data.keyframe = keyframe; }, }, + keyframes: { + /** + * Object of keyframes { first, prev, next, last } + * @name keyframes + * @type {object | null} + * @memberof module:API.cvat.classes.ObjectState + * @readonly + * @instance + */ + get: () => { + if (typeof (data.keyframes) === 'object') { + return { ...data.keyframes }; + } + + return null; + }, + }, occluded: { /** * @name occluded @@ -266,6 +283,36 @@ data.lock = lock; }, }, + pinned: { + /** + * @name pinned + * @type {boolean | null} + * @memberof module:API.cvat.classes.ObjectState + * @instance + */ + get: () => { + if (typeof (data.pinned) === 'boolean') { + return data.pinned; + } + + return null; + }, + set: (pinned) => { + data.updateFlags.pinned = true; + data.pinned = pinned; + }, + }, + updated: { + /** + * Timestamp of the latest updated of the object + * @name updated + * @type {number} + * @memberof module:API.cvat.classes.ObjectState + * @instance + * @readonly + */ + get: () => data.updated, + }, attributes: { /** * Object is id:value pairs where "id" is an integer @@ -295,20 +342,33 @@ })); this.label = serialized.label; - this.group = serialized.group; - this.zOrder = serialized.zOrder; - this.outside = serialized.outside; - this.keyframe = serialized.keyframe; - this.occluded = serialized.occluded; - this.color = serialized.color; this.lock = serialized.lock; - this.visibility = serialized.visibility; - // It can be undefined in a constructor and it can be defined later - if (typeof (serialized.points) !== 'undefined') { + if (typeof (serialized.zOrder) === 'number') { + this.zOrder = serialized.zOrder; + } + if (typeof (serialized.occluded) === 'boolean') { + this.occluded = serialized.occluded; + } + if (typeof (serialized.outside) === 'boolean') { + this.outside = serialized.outside; + } + if (typeof (serialized.keyframe) === 'boolean') { + this.keyframe = serialized.keyframe; + } + if (typeof (serialized.pinned) === 'boolean') { + this.pinned = serialized.pinned; + } + if (typeof (serialized.hidden) === 'boolean') { + this.hidden = serialized.hidden; + } + if (typeof (serialized.color) === 'string') { + this.color = serialized.color; + } + if (Array.isArray(serialized.points)) { this.points = serialized.points; } - if (typeof (serialized.attributes) !== 'undefined') { + if (typeof (serialized.attributes) === 'object') { this.attributes = serialized.attributes; } @@ -348,42 +408,12 @@ .apiWrapper.call(this, ObjectState.prototype.delete, force); return result; } - - /** - * Set the highest ZOrder within a frame - * @method up - * @memberof module:API.cvat.classes.ObjectState - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.PluginError} - */ - async up() { - const result = await PluginRegistry - .apiWrapper.call(this, ObjectState.prototype.up); - return result; - } - - /** - * Set the lowest ZOrder within a frame - * @method down - * @memberof module:API.cvat.classes.ObjectState - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.PluginError} - */ - async down() { - const result = await PluginRegistry - .apiWrapper.call(this, ObjectState.prototype.down); - return result; - } } // Updates element in collection which contains it ObjectState.prototype.save.implementation = async function () { - if (this.hidden && this.hidden.save) { - return this.hidden.save(); + if (this.__internal && this.__internal.save) { + return this.__internal.save(); } return this; @@ -391,29 +421,12 @@ // Delete element from a collection which contains it ObjectState.prototype.delete.implementation = async function (force) { - if (this.hidden && this.hidden.delete) { - return this.hidden.delete(force); + if (this.__internal && this.__internal.delete) { + return this.__internal.delete(force); } return false; }; - ObjectState.prototype.up.implementation = async function () { - if (this.hidden && this.hidden.up) { - return this.hidden.up(); - } - - return false; - }; - - ObjectState.prototype.down.implementation = async function () { - if (this.hidden && this.hidden.down) { - return this.hidden.down(); - } - - return false; - }; - - module.exports = ObjectState; })(); diff --git a/cvat-core/src/plugins.js b/cvat-core/src/plugins.js index 595e6cbc41e..eea06ee4f98 100644 --- a/cvat-core/src/plugins.js +++ b/cvat-core/src/plugins.js @@ -17,7 +17,7 @@ const pluginList = await PluginRegistry.list(); for (const plugin of pluginList) { const pluginDecorators = plugin.functions - .filter(obj => obj.callback === wrappedFunc)[0]; + .filter((obj) => obj.callback === wrappedFunc)[0]; if (pluginDecorators && pluginDecorators.enter) { try { await pluginDecorators.enter.call(this, plugin, ...args); @@ -35,7 +35,7 @@ for (const plugin of pluginList) { const pluginDecorators = plugin.functions - .filter(obj => obj.callback === wrappedFunc)[0]; + .filter((obj) => obj.callback === wrappedFunc)[0]; if (pluginDecorators && pluginDecorators.leave) { try { result = await pluginDecorators.leave.call(this, plugin, result, ...args); diff --git a/cvat-core/src/server-proxy.js b/cvat-core/src/server-proxy.js index 9b5eef3f8d8..25f101822ca 100644 --- a/cvat-core/src/server-proxy.js +++ b/cvat-core/src/server-proxy.js @@ -15,16 +15,14 @@ const store = require('store'); const config = require('./config'); - function generateError(errorData, baseMessage) { + function generateError(errorData) { if (errorData.response) { - const message = `${baseMessage}. ` - + `${errorData.message}. ${JSON.stringify(errorData.response.data) || ''}.`; + const message = `${errorData.message}. ${JSON.stringify(errorData.response.data) || ''}.`; return new ServerError(message, errorData.response.status); } // Server is unavailable (no any response) - const message = `${baseMessage}. ` - + `${errorData.message}.`; // usually is "Error Network" + const message = `${errorData.message}.`; // usually is "Error Network" return new ServerError(message, 0); } @@ -49,7 +47,7 @@ proxy: config.proxy, }); } catch (errorData) { - throw generateError(errorData, 'Could not get "about" information from the server'); + throw generateError(errorData); } return response.data; @@ -65,7 +63,7 @@ proxy: config.proxy, }); } catch (errorData) { - throw generateError(errorData, 'Could not get "share" information from the server'); + throw generateError(errorData); } return response.data; @@ -82,7 +80,7 @@ }, }); } catch (errorData) { - throw generateError(errorData, 'Could not send an exception to the server'); + throw generateError(errorData); } } @@ -95,12 +93,28 @@ proxy: config.proxy, }); } catch (errorData) { - throw generateError(errorData, 'Could not get annotation formats from the server'); + throw generateError(errorData); } return response.data; } + async function datasetFormats() { + const { backendAPI } = config; + + let response = null; + try { + response = await Axios.get(`${backendAPI}/server/dataset/formats`, { + proxy: config.proxy, + }); + response = JSON.parse(response.data); + } catch (errorData) { + throw generateError(errorData); + } + + return response; + } + async function register(username, firstName, lastName, email, password1, password2) { let response = null; try { @@ -119,7 +133,7 @@ }, }); } catch (errorData) { - throw generateError(errorData, `Could not register '${username}' user on the server`); + throw generateError(errorData); } return response.data; @@ -131,6 +145,7 @@ `${encodeURIComponent('password')}=${encodeURIComponent(password)}`, ]).join('&').replace(/%20/g, '+'); + Axios.defaults.headers.common.Authorization = ''; let authenticationResponse = null; try { authenticationResponse = await Axios.post( @@ -140,7 +155,7 @@ }, ); } catch (errorData) { - throw generateError(errorData, 'Could not login on a server'); + throw generateError(errorData); } if (authenticationResponse.headers['set-cookie']) { @@ -161,7 +176,7 @@ proxy: config.proxy, }); } catch (errorData) { - throw generateError(errorData, 'Could not logout from the server'); + throw generateError(errorData); } store.remove('token'); @@ -182,16 +197,27 @@ return true; } + async function serverRequest(url, data) { + try { + return (await Axios({ + url, + ...data, + })).data; + } catch (errorData) { + throw generateError(errorData); + } + } + async function getTasks(filter = '') { const { backendAPI } = config; let response = null; try { - response = await Axios.get(`${backendAPI}/tasks?${filter}`, { + response = await Axios.get(`${backendAPI}/tasks?page_size=10&${filter}`, { proxy: config.proxy, }); } catch (errorData) { - throw generateError(errorData, 'Could not get tasks from a server'); + throw generateError(errorData); } response.data.results.count = response.data.count; @@ -209,7 +235,7 @@ }, }); } catch (errorData) { - throw generateError(errorData, 'Could not save the task on the server'); + throw generateError(errorData); } } @@ -219,10 +245,36 @@ try { await Axios.delete(`${backendAPI}/tasks/${id}`); } catch (errorData) { - throw generateError(errorData, 'Could not delete the task from the server'); + throw generateError(errorData); } } + async function exportDataset(id, format) { + const { backendAPI } = config; + let url = `${backendAPI}/tasks/${id}/dataset?format=${format}`; + + return new Promise((resolve, reject) => { + async function request() { + try { + const response = await Axios + .get(`${url}`, { + proxy: config.proxy, + }); + if (response.status === 202) { + setTimeout(request, 3000); + } else { + url = `${url}&action=download`; + resolve(url); + } + } catch (errorData) { + reject(generateError(errorData)); + } + } + + setTimeout(request); + }); + } + async function createTask(taskData, files, onUpdate) { const { backendAPI } = config; @@ -254,7 +306,7 @@ } } catch (errorData) { reject( - generateError(errorData, 'Could not put task to the server'), + generateError(errorData), ); } } @@ -283,7 +335,7 @@ }, }); } catch (errorData) { - throw generateError(errorData, 'Could not put task to the server'); + throw generateError(errorData); } onUpdate('The data is being uploaded to the server..'); @@ -298,7 +350,7 @@ // ignore } - throw generateError(errorData, 'Could not put data to the server'); + throw generateError(errorData); } try { @@ -321,7 +373,7 @@ proxy: config.proxy, }); } catch (errorData) { - throw generateError(errorData, 'Could not get jobs from a server'); + throw generateError(errorData); } return response.data; @@ -338,20 +390,26 @@ }, }); } catch (errorData) { - throw generateError(errorData, 'Could not save the job on the server'); + throw generateError(errorData); } } - async function getUsers() { + async function getUsers(id = null) { const { backendAPI } = config; let response = null; try { - response = await Axios.get(`${backendAPI}/users`, { - proxy: config.proxy, - }); + if (id === null) { + response = await Axios.get(`${backendAPI}/users?page_size=all`, { + proxy: config.proxy, + }); + } else { + response = await Axios.get(`${backendAPI}/users/${id}`, { + proxy: config.proxy, + }); + } } catch (errorData) { - throw generateError(errorData, 'Could not get users from the server'); + throw generateError(errorData); } return response.data.results; @@ -366,7 +424,28 @@ proxy: config.proxy, }); } catch (errorData) { - throw generateError(errorData, 'Could not get user data from the server'); + throw generateError(errorData); + } + + return response.data; + } + + async function getPreview(tid) { + const { backendAPI } = config; + + let response = null; + try { + // TODO: change 0 frame to preview + response = await Axios.get(`${backendAPI}/tasks/${tid}/frames/0`, { + proxy: config.proxy, + responseType: 'blob', + }); + } catch (errorData) { + const code = errorData.response ? errorData.response.status : errorData.code; + throw new ServerError( + `Could not get preview frame for the task ${tid} from the server`, + code, + ); } return response.data; @@ -382,10 +461,7 @@ responseType: 'blob', }); } catch (errorData) { - throw generateError( - errorData, - `Could not get frame ${frame} for the task ${tid} from the server`, - ); + throw generateError(errorData); } return response.data; @@ -400,10 +476,7 @@ proxy: config.proxy, }); } catch (errorData) { - throw generateError( - errorData, - `Could not get frame meta info for the task ${tid} from the server`, - ); + throw generateError(errorData); } return response.data; @@ -419,10 +492,7 @@ proxy: config.proxy, }); } catch (errorData) { - throw generateError( - errorData, - `Could not get annotations for the ${session} ${id} from the server`, - ); + throw generateError(errorData); } return response.data; @@ -450,10 +520,7 @@ }, }); } catch (errorData) { - throw generateError( - errorData, - `Could not ${action} annotations for the ${session} ${id} on the server`, - ); + throw generateError(errorData); } return response.data; @@ -480,10 +547,7 @@ resolve(); } } catch (errorData) { - reject(generateError( - errorData, - `Could not upload annotations for the ${session} ${id}`, - )); + reject(generateError(errorData)); } } @@ -495,27 +559,25 @@ async function dumpAnnotations(id, name, format) { const { backendAPI } = config; const filename = name.replace(/\//g, '_'); - let url = `${backendAPI}/tasks/${id}/annotations/${filename}?format=${format}`; + const baseURL = `${backendAPI}/tasks/${id}/annotations/${encodeURIComponent(filename)}`; + let query = `format=${encodeURIComponent(format)}`; + let url = `${baseURL}?${query}`; return new Promise((resolve, reject) => { async function request() { - try { - const response = await Axios - .get(`${url}`, { - proxy: config.proxy, - }); + Axios.get(`${url}`, { + proxy: config.proxy, + }).then((response) => { if (response.status === 202) { setTimeout(request, 3000); } else { - url = `${url}&action=download`; + query = `${query}&action=download`; + url = `${baseURL}?${query}`; resolve(url); } - } catch (errorData) { - reject(generateError( - errorData, - `Could not dump annotations for the task ${id} from the server`, - )); - } + }).catch((errorData) => { + reject(generateError(errorData)); + }); } setTimeout(request); @@ -528,11 +590,13 @@ about, share, formats, + datasetFormats, exception, login, logout, authorized, register, + request: serverRequest, }), writable: false, }, @@ -543,6 +607,7 @@ saveTask, createTask, deleteTask, + exportDataset, }), writable: false, }, @@ -567,6 +632,7 @@ value: Object.freeze({ getData, getMeta, + getPreview, }), writable: false, }, diff --git a/cvat-core/src/session.js b/cvat-core/src/session.js index e985b5e45cc..32643dd200b 100644 --- a/cvat-core/src/session.js +++ b/cvat-core/src/session.js @@ -1,5 +1,5 @@ /* -* Copyright (C) 2019 Intel Corporation +* Copyright (C) 2019-2020 Intel Corporation * SPDX-License-Identifier: MIT */ @@ -10,10 +10,11 @@ (() => { const PluginRegistry = require('./plugins'); const serverProxy = require('./server-proxy'); - const { getFrame } = require('./frames'); + const { getFrame, getPreview } = require('./frames'); const { ArgumentError } = require('./exceptions'); const { TaskStatus } = require('./enums'); const { Label } = require('./labels'); + const User = require('./user'); function buildDublicatedAPI(prototype) { Object.defineProperties(prototype, { @@ -25,9 +26,9 @@ return result; }, - async save() { + async save(onUpdate) { const result = await PluginRegistry - .apiWrapper.call(this, prototype.annotations.save); + .apiWrapper.call(this, prototype.annotations.save, onUpdate); return result; }, @@ -55,16 +56,17 @@ return result; }, - async get(frame, filter = {}) { + async get(frame, allTracks = false, filters = []) { const result = await PluginRegistry - .apiWrapper.call(this, prototype.annotations.get, frame, filter); + .apiWrapper.call(this, prototype.annotations.get, + frame, allTracks, filters); return result; }, - async search(filter, frameFrom, frameTo) { + async search(filters, frameFrom, frameTo) { const result = await PluginRegistry .apiWrapper.call(this, prototype.annotations.search, - filter, frameFrom, frameTo); + filters, frameFrom, frameTo); return result; }, @@ -75,12 +77,6 @@ return result; }, - async hasUnsavedChanges() { - const result = await PluginRegistry - .apiWrapper.call(this, prototype.annotations.hasUnsavedChanges); - return result; - }, - async merge(objectStates) { const result = await PluginRegistry .apiWrapper.call(this, prototype.annotations.merge, objectStates); @@ -99,6 +95,18 @@ objectStates, reset); return result; }, + + async exportDataset(format) { + const result = await PluginRegistry + .apiWrapper.call(this, prototype.annotations.exportDataset, format); + return result; + }, + + hasUnsavedChanges() { + const result = prototype.annotations + .hasUnsavedChanges.implementation.call(this); + return result; + }, }, writable: true, }), @@ -109,6 +117,11 @@ .apiWrapper.call(this, prototype.frames.get, frame); return result; }, + async preview() { + const result = await PluginRegistry + .apiWrapper.call(this, prototype.frames.preview); + return result; + }, }, writable: true, }), @@ -129,12 +142,12 @@ }), actions: Object.freeze({ value: { - async undo(count) { + async undo(count = 1) { const result = await PluginRegistry .apiWrapper.call(this, prototype.actions.undo, count); return result; }, - async redo(count) { + async redo(count = 1) { const result = await PluginRegistry .apiWrapper.call(this, prototype.actions.redo, count); return result; @@ -144,6 +157,11 @@ .apiWrapper.call(this, prototype.actions.clear); return result; }, + async get() { + const result = await PluginRegistry + .apiWrapper.call(this, prototype.actions.get); + return result; + }, }, writable: true, }), @@ -182,7 +200,7 @@ * You need upload annotations from a server again after successful executing * @method upload * @memberof Session.annotations - * @param {File} annotations - a text file with annotations + * @param {File} annotations - a file with annotations * @param {module:API.cvat.classes.Loader} loader - a loader * which will be used to upload * @instance @@ -256,24 +274,34 @@ * @instance * @async */ - /** - * @typedef {Object} ObjectFilter - * @property {string} [label] a name of a label - * @property {module:API.cvat.enums.ObjectType} [type] - * @property {module:API.cvat.enums.ObjectShape} [shape] - * @property {boolean} [occluded] a value of occluded property - * @property {boolean} [lock] a value of lock property - * @property {number} [width] a width of a shape - * @property {number} [height] a height of a shape - * @property {Object[]} [attributes] dictionary with "name: value" pairs - * @global - */ /** * Get annotations for a specific frame + *
Filter supports following operators: + * ==, !=, >, >=, <, <=, ~= and (), |, & for grouping. + *
Filter supports properties: + * width, height, label, serverID, clientID, type, shape, occluded + *
All prop values are case-sensitive. CVAT uses json queries for search. + *
Examples: + *
    + *
  • label=="car" | label==["road sign"]
  • + *
  • width >= height
  • + *
  • attr["Attribute 1"] == attr["Attribute 2"]
  • + *
  • type=="track" & shape="rectangle"
  • + *
  • clientID == 50
  • + *
  • (label=="car" & attr["parked"]==true) + * | (label=="pedestrian" & width > 150)
  • + *
  • (( label==["car \"mazda\""]) & + * (attr["sunglass ( help ) es"]==true | + * (width > 150 | height > 150 & (clientID == serverID)))))
  • + *
+ * If you have double quotes in your query string, + * please escape them using back slash: \" * @method get * @param {integer} frame get objects from the frame - * @param {ObjectFilter[]} [filter = []] - * get only objects are satisfied to specific filter + * @param {boolean} allTracks show all tracks + * even if they are outside and not keyframe + * @param {string[]} [filters = []] + * get only objects that satisfied to specific filters * @returns {module:API.cvat.classes.ObjectState[]} * @memberof Session.annotations * @throws {module:API.cvat.exceptions.PluginError} @@ -282,13 +310,14 @@ * @async */ /** - * Find frame which contains at least one object satisfied to a filter + * Find a frame in the range [from, to] + * that contains at least one object satisfied to a filter * @method search * @memberof Session.annotations * @param {ObjectFilter} [filter = []] filter * @param {integer} from lower bound of a search * @param {integer} to upper bound of a search - * @returns {integer} the nearest frame which contains filtered objects + * @returns {integer|null} a frame that contains objects according to the filter * @throws {module:API.cvat.exceptions.PluginError} * @throws {module:API.cvat.exceptions.ArgumentError} * @instance @@ -352,13 +381,26 @@ * @async */ /** - * Indicate if there are any changes in + * Method indicates if there are any changes in * annotations which haven't been saved on a server + *
This function cannot be wrapped with a plugin * @method hasUnsavedChanges * @memberof Session.annotations * @returns {boolean} * @throws {module:API.cvat.exceptions.PluginError} * @instance + */ + /** + * Export as a dataset. + * Method builds a dataset in the specified format. + * @method exportDataset + * @memberof Session.annotations + * @param {module:String} format - a format + * @returns {string} An URL to the dataset file + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ServerError} + * @throws {module:API.cvat.exceptions.ArgumentError} + * @instance * @async */ @@ -380,6 +422,17 @@ * @throws {module:API.cvat.exceptions.ServerError} * @throws {module:API.cvat.exceptions.ArgumentError} */ + /** + * Get the first frame of a task for preview + * @method preview + * @memberof Session.frames + * @returns {string} - jpeg encoded image + * @instance + * @async + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ServerError} + * @throws {module:API.cvat.exceptions.ArgumentError} + */ /** * Namespace is used for an interaction with logs @@ -418,28 +471,48 @@ */ /** - * Is a dictionary of pairs "id:action" where "id" is an identifier of an object - * which has been affected by undo/redo and "action" is what exactly has been - * done with the object. Action can be: "created", "deleted", "updated". - * Size of an output array equal the param "count". - * @typedef {Object} HistoryAction + * @typedef {Object} HistoryActions + * @property {string[]} [undo] - array of possible actions to undo + * @property {string[]} [redo] - array of possible actions to redo * @global */ /** - * Undo actions + * Make undo * @method undo * @memberof Session.actions - * @returns {HistoryAction} + * @param {number} [count=1] number of actions to undo + * @returns {number[]} Array of affected objects * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} * @instance * @async */ /** - * Redo actions + * Make redo * @method redo * @memberof Session.actions - * @returns {HistoryAction} + * @param {number} [count=1] number of actions to redo + * @returns {number[]} Array of affected objects * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} + * @instance + * @async + */ + /** + * Remove all actions from history + * @method clear + * @memberof Session.actions + * @throws {module:API.cvat.exceptions.PluginError} + * @instance + * @async + */ + /** + * Get actions + * @method get + * @memberof Session.actions + * @returns {HistoryActions} + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} * @instance * @async */ @@ -520,19 +593,19 @@ get: () => data.id, }, /** - * Identifier of a user who is responsible for the job + * Instance of a user who is responsible for the job * @name assignee - * @type {integer} + * @type {module:API.cvat.classes.User} * @memberof module:API.cvat.classes.Job * @instance * @throws {module:API.cvat.exceptions.ArgumentError} */ assignee: { get: () => data.assignee, - set: () => (assignee) => { - if (!Number.isInteger(assignee) || assignee < 0) { + set: (assignee) => { + if (assignee !== null && !(assignee instanceof User)) { throw new ArgumentError( - 'Value must be a non negative integer', + 'Value must be a user instance', ); } data.assignee = assignee; @@ -610,6 +683,7 @@ split: Object.getPrototypeOf(this).annotations.split.bind(this), group: Object.getPrototypeOf(this).annotations.group.bind(this), clear: Object.getPrototypeOf(this).annotations.clear.bind(this), + search: Object.getPrototypeOf(this).annotations.search.bind(this), upload: Object.getPrototypeOf(this).annotations.upload.bind(this), select: Object.getPrototypeOf(this).annotations.select.bind(this), statistics: Object.getPrototypeOf(this).annotations.statistics.bind(this), @@ -617,8 +691,16 @@ .annotations.hasUnsavedChanges.bind(this), }; + this.actions = { + undo: Object.getPrototypeOf(this).actions.undo.bind(this), + redo: Object.getPrototypeOf(this).actions.redo.bind(this), + clear: Object.getPrototypeOf(this).actions.clear.bind(this), + get: Object.getPrototypeOf(this).actions.get.bind(this), + }; + this.frames = { get: Object.getPrototypeOf(this).frames.get.bind(this), + preview: Object.getPrototypeOf(this).frames.preview.bind(this), }; } @@ -780,9 +862,9 @@ get: () => data.mode, }, /** - * Identificator of a user who has created the task + * Instance of a user who has created the task * @name owner - * @type {integer} + * @type {module:API.cvat.classes.User} * @memberof module:API.cvat.classes.Task * @readonly * @instance @@ -791,19 +873,19 @@ get: () => data.owner, }, /** - * Identificator of a user who is responsible for the task + * Instance of a user who is responsible for the task * @name assignee - * @type {integer} + * @type {module:API.cvat.classes.User} * @memberof module:API.cvat.classes.Task * @instance * @throws {module:API.cvat.exceptions.ArgumentError} */ assignee: { get: () => data.assignee, - set: () => (assignee) => { - if (!Number.isInteger(assignee) || assignee < 0) { + set: (assignee) => { + if (assignee !== null && !(assignee instanceof User)) { throw new ArgumentError( - 'Value must be a non negative integer', + 'Value must be a user instance', ); } data.assignee = assignee; @@ -940,11 +1022,7 @@ } } - if (typeof (data.id) === 'undefined') { - data.labels = [...labels]; - } else { - data.labels = data.labels.concat([...labels]); - } + data.labels = [...labels]; }, }, /** @@ -1113,15 +1191,26 @@ split: Object.getPrototypeOf(this).annotations.split.bind(this), group: Object.getPrototypeOf(this).annotations.group.bind(this), clear: Object.getPrototypeOf(this).annotations.clear.bind(this), + search: Object.getPrototypeOf(this).annotations.search.bind(this), upload: Object.getPrototypeOf(this).annotations.upload.bind(this), select: Object.getPrototypeOf(this).annotations.select.bind(this), statistics: Object.getPrototypeOf(this).annotations.statistics.bind(this), hasUnsavedChanges: Object.getPrototypeOf(this) .annotations.hasUnsavedChanges.bind(this), + exportDataset: Object.getPrototypeOf(this) + .annotations.exportDataset.bind(this), + }; + + this.actions = { + undo: Object.getPrototypeOf(this).actions.undo.bind(this), + redo: Object.getPrototypeOf(this).actions.redo.bind(this), + clear: Object.getPrototypeOf(this).actions.clear.bind(this), + get: Object.getPrototypeOf(this).actions.get.bind(this), }; this.frames = { get: Object.getPrototypeOf(this).frames.get.bind(this), + preview: Object.getPrototypeOf(this).frames.preview.bind(this), }; } @@ -1172,6 +1261,7 @@ putAnnotations, saveAnnotations, hasUnsavedChanges, + searchAnnotations, mergeAnnotations, splitAnnotations, groupAnnotations, @@ -1180,6 +1270,11 @@ annotationsStatistics, uploadAnnotations, dumpAnnotations, + exportDataset, + undoActions, + redoActions, + clearActions, + getActions, } = require('./annotations'); buildDublicatedAPI(Job.prototype); @@ -1190,6 +1285,7 @@ if (this.id) { const jobData = { status: this.status, + assignee: this.assignee ? this.assignee.id : null, }; await serverProxy.jobs.saveJob(this.id, jobData); @@ -1218,18 +1314,64 @@ return frameData; }; + Job.prototype.frames.preview.implementation = async function () { + const frameData = await getPreview(this.task.id); + return frameData; + }; + // TODO: Check filter for annotations - Job.prototype.annotations.get.implementation = async function (frame, filter) { + Job.prototype.annotations.get.implementation = async function (frame, allTracks, filters) { + if (!Array.isArray(filters) || filters.some((filter) => typeof (filter) !== 'string')) { + throw new ArgumentError( + 'The filters argument must be an array of strings', + ); + } + + if (!Number.isInteger(frame)) { + throw new ArgumentError( + 'The frame argument must be an integer', + ); + } + if (frame < this.startFrame || frame > this.stopFrame) { throw new ArgumentError( `Frame ${frame} does not exist in the job`, ); } - const annotationsData = await getAnnotations(this, frame, filter); + const annotationsData = await getAnnotations(this, frame, allTracks, filters); return annotationsData; }; + Job.prototype.annotations.search.implementation = function (filters, frameFrom, frameTo) { + if (!Array.isArray(filters) || filters.some((filter) => typeof (filter) !== 'string')) { + throw new ArgumentError( + 'The filters argument must be an array of strings', + ); + } + + if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) { + throw new ArgumentError( + 'The start and end frames both must be an integer', + ); + } + + if (frameFrom < this.startFrame || frameFrom > this.stopFrame) { + throw new ArgumentError( + 'The start frame is out of the job', + ); + } + + if (frameTo < this.startFrame || frameTo > this.stopFrame) { + throw new ArgumentError( + 'The stop frame is out of the job', + ); + } + + const result = searchAnnotations(this, filters, frameFrom, frameTo); + return result; + }; + Job.prototype.annotations.save.implementation = async function (onUpdate) { const result = await saveAnnotations(this, onUpdate); return result; @@ -1285,15 +1427,41 @@ return result; }; + Job.prototype.annotations.exportDataset.implementation = async function (format) { + const result = await exportDataset(this.task, format); + return result; + }; + + Job.prototype.actions.undo.implementation = function (count) { + const result = undoActions(this, count); + return result; + }; + + Job.prototype.actions.redo.implementation = function (count) { + const result = redoActions(this, count); + return result; + }; + + Job.prototype.actions.clear.implementation = function () { + const result = clearActions(this); + return result; + }; + + Job.prototype.actions.get.implementation = function () { + const result = getActions(this); + return result; + }; + Task.prototype.save.implementation = async function saveTaskImplementation(onUpdate) { // TODO: Add ability to change an owner and an assignee if (typeof (this.id) !== 'undefined') { // If the task has been already created, we update it const taskData = { + assignee: this.assignee ? this.assignee.id : null, name: this.name, bug_tracker: this.bugTracker, z_order: this.zOrder, - labels: [...this.labels.map(el => el.toJSON())], + labels: [...this.labels.map((el) => el.toJSON())], }; await serverProxy.tasks.saveTask(this.id, taskData); @@ -1302,7 +1470,7 @@ const taskData = { name: this.name, - labels: this.labels.map(el => el.toJSON()), + labels: this.labels.map((el) => el.toJSON()), image_quality: this.imageQuality, z_order: Boolean(this.zOrder), }; @@ -1358,8 +1526,19 @@ return result; }; + Task.prototype.frames.preview.implementation = async function () { + const frameData = await getPreview(this.id); + return frameData; + }; + // TODO: Check filter for annotations - Task.prototype.annotations.get.implementation = async function (frame, filter) { + Task.prototype.annotations.get.implementation = async function (frame, allTracks, filters) { + if (!Array.isArray(filters) || filters.some((filter) => typeof (filter) !== 'string')) { + throw new ArgumentError( + 'The filters argument must be an array of strings', + ); + } + if (!Number.isInteger(frame) || frame < 0) { throw new ArgumentError( `Frame must be a positive integer. Got: "${frame}"`, @@ -1372,7 +1551,36 @@ ); } - const result = await getAnnotations(this, frame, filter); + const result = await getAnnotations(this, frame, allTracks, filters); + return result; + }; + + Task.prototype.annotations.search.implementation = function (filters, frameFrom, frameTo) { + if (!Array.isArray(filters) || filters.some((filter) => typeof (filter) !== 'string')) { + throw new ArgumentError( + 'The filters argument must be an array of strings', + ); + } + + if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) { + throw new ArgumentError( + 'The start and end frames both must be an integer', + ); + } + + if (frameFrom < 0 || frameFrom >= this.size) { + throw new ArgumentError( + 'The start frame is out of the task', + ); + } + + if (frameTo < 0 || frameTo >= this.size) { + throw new ArgumentError( + 'The stop frame is out of the task', + ); + } + + const result = searchAnnotations(this, filters, frameFrom, frameTo); return result; }; @@ -1430,4 +1638,29 @@ const result = await dumpAnnotations(this, name, dumper); return result; }; + + Task.prototype.annotations.exportDataset.implementation = async function (format) { + const result = await exportDataset(this, format); + return result; + }; + + Task.prototype.actions.undo.implementation = function (count) { + const result = undoActions(this, count); + return result; + }; + + Task.prototype.actions.redo.implementation = function (count) { + const result = redoActions(this, count); + return result; + }; + + Task.prototype.actions.clear.implementation = function () { + const result = clearActions(this); + return result; + }; + + Task.prototype.actions.get.implementation = function () { + const result = getActions(this); + return result; + }; })(); diff --git a/cvat-core/tests/api/annotations.js b/cvat-core/tests/api/annotations.js index 71a70f46844..57fe808eca9 100644 --- a/cvat-core/tests/api/annotations.js +++ b/cvat-core/tests/api/annotations.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 Intel Corporation + * Copyright (C) 2018-2020 Intel Corporation * SPDX-License-Identifier: MIT */ @@ -85,6 +85,7 @@ describe('Feature: put annotations', () => { points: [0, 0, 100, 0, 100, 50], occluded: true, label: task.labels[0], + zOrder: 0, }); await task.annotations.put([state]); @@ -104,6 +105,7 @@ describe('Feature: put annotations', () => { points: [0, 0, 100, 100], occluded: false, label: job.task.labels[0], + zOrder: 0, }); await job.annotations.put([state]); @@ -123,6 +125,7 @@ describe('Feature: put annotations', () => { points: [0, 0, 100, 0, 100, 50], occluded: true, label: task.labels[0], + zOrder: 0, }); await task.annotations.put([state]); @@ -142,6 +145,7 @@ describe('Feature: put annotations', () => { points: [0, 0, 100, 100], occluded: false, label: job.task.labels[0], + zOrder: 0, }); await job.annotations.put([state]); @@ -158,6 +162,7 @@ describe('Feature: put annotations', () => { points: [0, 0, 100, 0, 100, 50], occluded: true, label: task.labels[0], + zOrder: 0, }); expect(task.annotations.put([state])) @@ -175,12 +180,45 @@ describe('Feature: put annotations', () => { attributes: { 'bad key': 55 }, occluded: true, label: task.labels[0], + zOrder: 0, }); expect(task.annotations.put([state])) .rejects.toThrow(window.cvat.exceptions.ArgumentError); }); + test('put shape with bad zOrder to a task', async () => { + const task = (await window.cvat.tasks.get({ id: 101 }))[0]; + await task.annotations.clear(true); + const state = new window.cvat.classes.ObjectState({ + frame: 1, + objectType: window.cvat.enums.ObjectType.SHAPE, + shapeType: window.cvat.enums.ObjectShape.POLYGON, + points: [0, 0, 100, 0, 100, 50], + attributes: { 'bad key': 55 }, + occluded: true, + label: task.labels[0], + zOrder: 'bad value', + }); + + expect(task.annotations.put([state])) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + + const state1 = new window.cvat.classes.ObjectState({ + frame: 1, + objectType: window.cvat.enums.ObjectType.SHAPE, + shapeType: window.cvat.enums.ObjectShape.POLYGON, + points: [0, 0, 100, 0, 100, 50], + attributes: { 'bad key': 55 }, + occluded: true, + label: task.labels[0], + zOrder: NaN, + }); + + expect(task.annotations.put([state1])) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + }); + test('put shape without points and with invalud points to a task', async () => { const task = (await window.cvat.tasks.get({ id: 101 }))[0]; await task.annotations.clear(true); @@ -191,6 +229,7 @@ describe('Feature: put annotations', () => { occluded: true, points: [], label: task.labels[0], + zOrder: 0, }); await expect(task.annotations.put([state])) @@ -214,6 +253,7 @@ describe('Feature: put annotations', () => { points: [0, 0, 100, 0, 100, 50], occluded: true, label: task.labels[0], + zOrder: 0, }); expect(task.annotations.put([state])) @@ -229,6 +269,7 @@ describe('Feature: put annotations', () => { shapeType: window.cvat.enums.ObjectShape.POLYGON, points: [0, 0, 100, 0, 100, 50], occluded: true, + zOrder: 0, }); await expect(task.annotations.put([state])) @@ -253,6 +294,7 @@ describe('Feature: put annotations', () => { points: [0, 0, 100, 0, 100, 50], occluded: true, label: task.labels[0], + zOrder: 0, }); expect(task.annotations.put([state])) @@ -266,7 +308,7 @@ describe('Feature: check unsaved changes', () => { expect(await task.annotations.hasUnsavedChanges()).toBe(false); const annotations = await task.annotations.get(0); - annotations[0].keyframe = true; + annotations[0].keyframe = false; await annotations[0].save(); expect(await task.annotations.hasUnsavedChanges()).toBe(true); @@ -296,13 +338,14 @@ describe('Feature: save annotations', () => { points: [0, 0, 100, 0, 100, 50], occluded: true, label: task.labels[0], + zOrder: 0, }); - expect(await task.annotations.hasUnsavedChanges()).toBe(false); + expect(task.annotations.hasUnsavedChanges()).toBe(false); await task.annotations.put([state]); - expect(await task.annotations.hasUnsavedChanges()).toBe(true); + expect(task.annotations.hasUnsavedChanges()).toBe(true); await task.annotations.save(); - expect(await task.annotations.hasUnsavedChanges()).toBe(false); + expect(task.annotations.hasUnsavedChanges()).toBe(false); annotations = await task.annotations.get(0); expect(annotations).toHaveLength(length + 1); }); @@ -311,23 +354,23 @@ describe('Feature: save annotations', () => { const task = (await window.cvat.tasks.get({ id: 101 }))[0]; const annotations = await task.annotations.get(0); - expect(await task.annotations.hasUnsavedChanges()).toBe(false); + expect(task.annotations.hasUnsavedChanges()).toBe(false); annotations[0].occluded = true; await annotations[0].save(); - expect(await task.annotations.hasUnsavedChanges()).toBe(true); + expect(task.annotations.hasUnsavedChanges()).toBe(true); await task.annotations.save(); - expect(await task.annotations.hasUnsavedChanges()).toBe(false); + expect(task.annotations.hasUnsavedChanges()).toBe(false); }); test('delete & save annotations for a task', async () => { const task = (await window.cvat.tasks.get({ id: 101 }))[0]; const annotations = await task.annotations.get(0); - expect(await task.annotations.hasUnsavedChanges()).toBe(false); + expect(task.annotations.hasUnsavedChanges()).toBe(false); await annotations[0].delete(); - expect(await task.annotations.hasUnsavedChanges()).toBe(true); + expect(task.annotations.hasUnsavedChanges()).toBe(true); await task.annotations.save(); - expect(await task.annotations.hasUnsavedChanges()).toBe(false); + expect(task.annotations.hasUnsavedChanges()).toBe(false); }); test('create & save annotations for a job', async () => { @@ -341,13 +384,14 @@ describe('Feature: save annotations', () => { points: [0, 0, 100, 0, 100, 50], occluded: true, label: job.task.labels[0], + zOrder: 0, }); - expect(await job.annotations.hasUnsavedChanges()).toBe(false); + expect(job.annotations.hasUnsavedChanges()).toBe(false); await job.annotations.put([state]); - expect(await job.annotations.hasUnsavedChanges()).toBe(true); + expect(job.annotations.hasUnsavedChanges()).toBe(true); await job.annotations.save(); - expect(await job.annotations.hasUnsavedChanges()).toBe(false); + expect(job.annotations.hasUnsavedChanges()).toBe(false); annotations = await job.annotations.get(0); expect(annotations).toHaveLength(length + 1); }); @@ -356,23 +400,23 @@ describe('Feature: save annotations', () => { const job = (await window.cvat.jobs.get({ jobID: 100 }))[0]; const annotations = await job.annotations.get(0); - expect(await job.annotations.hasUnsavedChanges()).toBe(false); + expect(job.annotations.hasUnsavedChanges()).toBe(false); annotations[0].points = [0, 100, 200, 300]; await annotations[0].save(); - expect(await job.annotations.hasUnsavedChanges()).toBe(true); + expect(job.annotations.hasUnsavedChanges()).toBe(true); await job.annotations.save(); - expect(await job.annotations.hasUnsavedChanges()).toBe(false); + expect(job.annotations.hasUnsavedChanges()).toBe(false); }); test('delete & save annotations for a job', async () => { const job = (await window.cvat.jobs.get({ jobID: 100 }))[0]; const annotations = await job.annotations.get(0); - expect(await job.annotations.hasUnsavedChanges()).toBe(false); + expect(job.annotations.hasUnsavedChanges()).toBe(false); await annotations[0].delete(); - expect(await job.annotations.hasUnsavedChanges()).toBe(true); + expect(job.annotations.hasUnsavedChanges()).toBe(true); await job.annotations.save(); - expect(await job.annotations.hasUnsavedChanges()).toBe(false); + expect(job.annotations.hasUnsavedChanges()).toBe(false); }); test('delete & save annotations for a job when there are a track and a shape with the same id', async () => { @@ -548,7 +592,7 @@ describe('Feature: group annotations', () => { expect(typeof (groupID)).toBe('number'); annotations = await task.annotations.get(0); for (const state of annotations) { - expect(state.group).toBe(groupID); + expect(state.group.id).toBe(groupID); } }); @@ -559,7 +603,7 @@ describe('Feature: group annotations', () => { expect(typeof (groupID)).toBe('number'); annotations = await job.annotations.get(0); for (const state of annotations) { - expect(state.group).toBe(groupID); + expect(state.group.id).toBe(groupID); } }); @@ -574,6 +618,7 @@ describe('Feature: group annotations', () => { points: [0, 0, 100, 0, 100, 50], occluded: true, label: task.labels[0], + zOrder: 0, }); expect(task.annotations.group([state])) @@ -613,11 +658,11 @@ describe('Feature: clear annotations', () => { expect(annotations.length).not.toBe(0); annotations[0].occluded = true; await annotations[0].save(); - expect(await task.annotations.hasUnsavedChanges()).toBe(true); + expect(task.annotations.hasUnsavedChanges()).toBe(true); await task.annotations.clear(true); annotations = await task.annotations.get(0); expect(annotations.length).not.toBe(0); - expect(await task.annotations.hasUnsavedChanges()).toBe(false); + expect(task.annotations.hasUnsavedChanges()).toBe(false); }); test('clear annotations with reload in a job', async () => { @@ -626,11 +671,11 @@ describe('Feature: clear annotations', () => { expect(annotations.length).not.toBe(0); annotations[0].occluded = true; await annotations[0].save(); - expect(await job.annotations.hasUnsavedChanges()).toBe(true); + expect(job.annotations.hasUnsavedChanges()).toBe(true); await job.annotations.clear(true); annotations = await job.annotations.get(0); expect(annotations.length).not.toBe(0); - expect(await job.annotations.hasUnsavedChanges()).toBe(false); + expect(job.annotations.hasUnsavedChanges()).toBe(false); }); test('clear annotations with bad reload parameter', async () => { diff --git a/cvat-core/tests/api/frames.js b/cvat-core/tests/api/frames.js index 2ecb51c966f..6e778b2293f 100644 --- a/cvat-core/tests/api/frames.js +++ b/cvat-core/tests/api/frames.js @@ -69,3 +69,17 @@ describe('Feature: get frame data', () => { expect(typeof (frameData)).toBe('string'); }); }); + +describe('Feature: get frame preview', () => { + test('get frame preview for a task', async () => { + const task = (await window.cvat.tasks.get({ id: 100 }))[0]; + const frame = await task.frames.preview(); + expect(typeof (frame)).toBe('string'); + }); + + test('get frame preview for a job', async () => { + const job = (await window.cvat.jobs.get({ jobID: 100 }))[0]; + const frame = await job.frames.preview(); + expect(typeof (frame)).toBe('string'); + }); +}); diff --git a/cvat-core/tests/api/object-state.js b/cvat-core/tests/api/object-state.js index 96988b3e0ca..1cf592ac4d4 100644 --- a/cvat-core/tests/api/object-state.js +++ b/cvat-core/tests/api/object-state.js @@ -303,45 +303,3 @@ describe('Feature: delete object', () => { expect(annotationsAfter).toHaveLength(length - 1); }); }); - -describe('Feature: change z order of an object', () => { - test('up z order for a shape', async () => { - const task = (await window.cvat.tasks.get({ id: 100 }))[0]; - const annotations = await task.annotations.get(0); - const state = annotations[0]; - - const { zOrder } = state; - await state.up(); - expect(state.zOrder).toBeGreaterThan(zOrder); - }); - - test('up z order for a track', async () => { - const task = (await window.cvat.tasks.get({ id: 101 }))[0]; - const annotations = await task.annotations.get(0); - const state = annotations[0]; - - const { zOrder } = state; - await state.up(); - expect(state.zOrder).toBeGreaterThan(zOrder); - }); - - test('down z order for a shape', async () => { - const task = (await window.cvat.tasks.get({ id: 100 }))[0]; - const annotations = await task.annotations.get(0); - const state = annotations[0]; - - const { zOrder } = state; - await state.down(); - expect(state.zOrder).toBeLessThan(zOrder); - }); - - test('down z order for a track', async () => { - const task = (await window.cvat.tasks.get({ id: 101 }))[0]; - const annotations = await task.annotations.get(0); - const state = annotations[0]; - - const { zOrder } = state; - await state.down(); - expect(state.zOrder).toBeLessThan(zOrder); - }); -}); diff --git a/cvat-core/tests/api/tasks.js b/cvat-core/tests/api/tasks.js index 232bec63a9b..627bf7e85e8 100644 --- a/cvat-core/tests/api/tasks.js +++ b/cvat-core/tests/api/tasks.js @@ -129,7 +129,7 @@ describe('Feature: save a task', () => { }], }); - result[0].labels = [newLabel]; + result[0].labels = [...result[0].labels, newLabel]; result[0].save(); result = await window.cvat.tasks.get({ diff --git a/cvat-core/tests/internal/filter.js b/cvat-core/tests/internal/filter.js new file mode 100644 index 00000000000..97336c43b0c --- /dev/null +++ b/cvat-core/tests/internal/filter.js @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2018-2020 Intel Corporation + * SPDX-License-Identifier: MIT +*/ + +/* global + require:false + jest:false + describe:false +*/ + +// Setup mock for a server +jest.mock('../../src/server-proxy', () => { + const mock = require('../mocks/server-proxy.mock'); + return mock; +}); + +const AnnotationsFilter = require('../../src/annotations-filter'); +// Initialize api +window.cvat = require('../../src/api'); + +// Test cases +describe('Feature: toJSONQuery', () => { + test('convert filters to a json query', () => { + const annotationsFilter = new AnnotationsFilter(); + const [groups, query] = annotationsFilter.toJSONQuery([]); + expect(Array.isArray(groups)).toBeTruthy(); + expect(typeof (query)).toBe('string'); + }); + + test('convert empty fitlers to a json query', () => { + const annotationsFilter = new AnnotationsFilter(); + const [, query] = annotationsFilter.toJSONQuery([]); + expect(query).toBe('$.objects[*].clientID'); + }); + + test('convert wrong fitlers (empty string) to a json query', () => { + const annotationsFilter = new AnnotationsFilter(); + expect(() => { + annotationsFilter.toJSONQuery(['']); + }).toThrow(window.cvat.exceptions.ArgumentError); + }); + + test('convert wrong fitlers (wrong number argument) to a json query', () => { + const annotationsFilter = new AnnotationsFilter(); + expect(() => { + annotationsFilter.toJSONQuery(1); + }).toThrow(window.cvat.exceptions.ArgumentError); + }); + + test('convert wrong fitlers (wrong array argument) to a json query', () => { + const annotationsFilter = new AnnotationsFilter(); + expect(() => { + annotationsFilter.toJSONQuery(['clientID ==6', 1]); + }).toThrow(window.cvat.exceptions.ArgumentError); + }); + + test('convert wrong filters (wrong expression) to a json query', () => { + const annotationsFilter = new AnnotationsFilter(); + expect(() => { + annotationsFilter.toJSONQuery(['clientID=5']); + }).toThrow(window.cvat.exceptions.ArgumentError); + }); + + test('convert filters to a json query', () => { + const annotationsFilter = new AnnotationsFilter(); + const [groups, query] = annotationsFilter + .toJSONQuery(['clientID==5 & shape=="rectangle" & label==["car"]']); + expect(groups).toEqual([ + ['clientID==5', '&', 'shape=="rectangle"', '&', 'label==["car"]'], + ]); + expect(query).toBe('$.objects[?((@.clientID==5&@.shape=="rectangle"&@.label==["car"]))].clientID'); + }); + + test('convert filters to a json query', () => { + const annotationsFilter = new AnnotationsFilter(); + const [groups, query] = annotationsFilter + .toJSONQuery(['label=="car" | width >= height & type=="track"']); + expect(groups).toEqual([ + ['label=="car"', '|', 'width >= height', '&', 'type=="track"'], + ]); + expect(query).toBe('$.objects[?((@.label=="car"|@.width>=@.height&@.type=="track"))].clientID'); + }); + + test('convert filters to a json query', () => { + const annotationsFilter = new AnnotationsFilter(); + const [groups, query] = annotationsFilter + .toJSONQuery(['label=="person" & attr["Attribute 1"] ==attr["Attribute 2"]']); + expect(groups).toEqual([ + ['label=="person"', '&', 'attr["Attribute 1"] ==attr["Attribute 2"]'], + ]); + expect(query).toBe('$.objects[?((@.label=="person"&@.attr["Attribute 1"]==@.attr["Attribute 2"]))].clientID'); + }); + + test('convert filters to a json query', () => { + const annotationsFilter = new AnnotationsFilter(); + const [groups, query] = annotationsFilter + .toJSONQuery(['label=="car" & attr["parked"]==true', 'label=="pedestrian" & width > 150']); + expect(groups).toEqual([ + ['label=="car"', '&', 'attr["parked"]==true'], + '|', + ['label=="pedestrian"', '&', 'width > 150'], + ]); + expect(query).toBe('$.objects[?((@.label=="car"&@.attr["parked"]==true)|(@.label=="pedestrian"&@.width>150))].clientID'); + }); + + test('convert filters to a json query', () => { + const annotationsFilter = new AnnotationsFilter(); + const [groups, query] = annotationsFilter + .toJSONQuery(['(( label==["car \\"mazda\\""]) & (attr["sunglass ( help ) es"]==true | (width > 150 | height > 150 & (clientID == serverID))))) ']); + expect(groups).toEqual([[[ + ['label==["car `mazda`"]'], + '&', + ['attr["sunglass ( help ) es"]==true', '|', + ['width > 150', '|', 'height > 150', '&', + [ + 'clientID == serverID', + ], + ], + ], + ]]]); + expect(query).toBe('$.objects[?((((@.label==["car `mazda`"])&(@.attr["sunglass ( help ) es"]==true|(@.width>150|@.height>150&(@.clientID==serverID))))))].clientID'); + }); +}); diff --git a/cvat-core/tests/mocks/server-proxy.mock.js b/cvat-core/tests/mocks/server-proxy.mock.js index 2f7ed2f5aba..f189e25457f 100644 --- a/cvat-core/tests/mocks/server-proxy.mock.js +++ b/cvat-core/tests/mocks/server-proxy.mock.js @@ -192,6 +192,10 @@ class ServerProxy { return JSON.parse(JSON.stringify(usersDummyData)).results[0]; } + async function getPreview() { + return 'DUMMY_IMAGE'; + } + async function getData() { return 'DUMMY_IMAGE'; } @@ -282,6 +286,7 @@ class ServerProxy { value: Object.freeze({ getData, getMeta, + getPreview, }), writable: false, }, diff --git a/cvat-core/webpack.config.js b/cvat-core/webpack.config.js index 9b55ccde88d..be9464f50e2 100644 --- a/cvat-core/webpack.config.js +++ b/cvat-core/webpack.config.js @@ -7,13 +7,12 @@ const path = require('path'); const nodeConfig = { target: 'node', - mode: 'production', + mode: 'development', devtool: 'source-map', entry: './src/api.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'cvat-core.node.js', - library: 'cvat', libraryTarget: 'commonjs', }, module: { @@ -22,9 +21,6 @@ const nodeConfig = { exclude: /node_modules/, }], }, - externals: { - canvas: 'commonjs canvas', - }, stats: { warnings: false, }, @@ -50,16 +46,7 @@ const webConfig = { options: { presets: [ ['@babel/preset-env', { - targets: { - chrome: 58, - }, - useBuiltIns: 'usage', - corejs: 3, - loose: false, - spec: false, - debug: false, - include: [], - exclude: [], + targets: '> 2.5%', // https://github.com/browserslist/browserslist }], ], sourceType: 'unambiguous', diff --git a/cvat-ui/.dockerignore b/cvat-ui/.dockerignore index e3fbd98336e..07e6e472cc7 100644 --- a/cvat-ui/.dockerignore +++ b/cvat-ui/.dockerignore @@ -1,2 +1 @@ -build -node_modules +/node_modules diff --git a/cvat-ui/.env b/cvat-ui/.env index ac71a091100..424e2c3cc7e 100644 --- a/cvat-ui/.env +++ b/cvat-ui/.env @@ -1,9 +1,4 @@ -REACT_APP_VERSION=${npm_package_version} +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT -REACT_APP_API_PROTOCOL=http -REACT_APP_API_HOST=localhost -REACT_APP_API_PORT=7000 -REACT_APP_API_HOST_URL=${REACT_APP_API_PROTOCOL}://${REACT_APP_API_HOST}:${REACT_APP_API_PORT} -REACT_APP_API_FULL_URL=${REACT_APP_API_PROTOCOL}://${REACT_APP_API_HOST}:${REACT_APP_API_PORT}/api/v1 - -SKIP_PREFLIGHT_CHECK=true diff --git a/cvat-ui/.env.production b/cvat-ui/.env.production deleted file mode 100644 index d5af5e40249..00000000000 --- a/cvat-ui/.env.production +++ /dev/null @@ -1,9 +0,0 @@ -REACT_APP_VERSION=${npm_package_version} - -REACT_APP_API_PROTOCOL=http -REACT_APP_API_HOST=localhost -REACT_APP_API_PORT=8080 -REACT_APP_API_HOST_URL=${REACT_APP_API_PROTOCOL}://${REACT_APP_API_HOST}:${REACT_APP_API_PORT} -REACT_APP_API_FULL_URL=${REACT_APP_API_PROTOCOL}://${REACT_APP_API_HOST}:${REACT_APP_API_PORT}/api/v1 - -SKIP_PREFLIGHT_CHECK=true diff --git a/cvat-ui/.eslintrc.js b/cvat-ui/.eslintrc.js new file mode 100644 index 00000000000..8c0c5b35d23 --- /dev/null +++ b/cvat-ui/.eslintrc.js @@ -0,0 +1,49 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +module.exports = { + 'env': { + 'node': true, + 'browser': true, + 'es6': true, + }, + 'parserOptions': { + 'parser': '@typescript-eslint/parser', + 'ecmaVersion': 6, + 'project': './tsconfig.json', + }, + 'plugins': [ + '@typescript-eslint', + 'import', + ], + 'ignorePatterns': ['*.svg', '*.scss'], + 'extends': [ + 'plugin:@typescript-eslint/recommended', + 'airbnb-typescript', + 'plugin:import/errors', + 'plugin:import/warnings', + 'plugin:import/typescript', + ], + 'rules': { + '@typescript-eslint/indent': ['warn', 4], + 'react/jsx-indent': ['warn', 4], + 'react/jsx-indent-props': ['warn', 4], + 'react/jsx-props-no-spreading': 0, + 'jsx-quotes': ['error', 'prefer-single'], + 'arrow-parens': ['error', 'always'], + '@typescript-eslint/no-explicit-any': [0], + '@typescript-eslint/explicit-function-return-type': ['warn', { allowExpressions: true }], + 'no-restricted-syntax': [0, {'selector': 'ForOfStatement'}], + 'no-plusplus': [0], + 'lines-between-class-members': 0, + 'react/no-did-update-set-state': 0, // https://github.com/airbnb/javascript/issues/1875 + }, + 'settings': { + 'import/resolver': { + 'typescript': { + 'directory': './tsconfig.json' + } + }, + }, +}; diff --git a/cvat-ui/.gitignore b/cvat-ui/.gitignore index 4d29575de80..4952a5fa8bd 100644 --- a/cvat-ui/.gitignore +++ b/cvat-ui/.gitignore @@ -1,23 +1,7 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. -# dependencies /node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# production +/dist /build +/yarn.lock -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* diff --git a/cvat-ui/Dockerfile b/cvat-ui/Dockerfile deleted file mode 100644 index 1b5ce8eb2fe..00000000000 --- a/cvat-ui/Dockerfile +++ /dev/null @@ -1,36 +0,0 @@ -FROM ubuntu:18.04 AS cvat-ui - -ARG http_proxy -ARG https_proxy -ARG no_proxy -ARG socks_proxy - -ENV TERM=xterm \ - http_proxy=${http_proxy} \ - https_proxy=${https_proxy} \ - no_proxy=${no_proxy} \ - socks_proxy=${socks_proxy} - -ENV LANG='C.UTF-8' \ - LC_ALL='C.UTF-8' - -# Install necessary apt packages -RUN apt update && apt install -yq nodejs npm curl && \ - npm install -g n && n 10.16.3 - -# Create output directory -RUN mkdir /tmp/cvat-ui -WORKDIR /tmp/cvat-ui/ - -# Install dependencies -COPY package*.json /tmp/cvat-ui/ -RUN npm install - -# Build source code -COPY . /tmp/cvat-ui/ -RUN mv .env.production .env && npm run build - -FROM nginx -# Replace default.conf configuration to remove unnecessary rules -COPY react_nginx.conf /etc/nginx/conf.d/default.conf -COPY --from=cvat-ui /tmp/cvat-ui/build /usr/share/nginx/html/ diff --git a/cvat-ui/README.md b/cvat-ui/README.md index 897dc836601..e69de29bb2d 100644 --- a/cvat-ui/README.md +++ b/cvat-ui/README.md @@ -1,44 +0,0 @@ -This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). - -## Available Scripts - -In the project directory, you can run: - -### `npm start` - -Runs the app in the development mode.
-Open [http://localhost:3000](http://localhost:3000) to view it in the browser. - -The page will reload if you make edits.
-You will also see any lint errors in the console. - -### `npm test` - -Launches the test runner in the interactive watch mode.
-See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. - -### `npm run build` - -Builds the app for production to the `build` folder.
-It correctly bundles React in production mode and optimizes the build for the best performance. - -The build is minified and the filenames include the hashes.
-Your app is ready to be deployed! - -See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. - -### `npm run eject` - -**Note: this is a one-way operation. Once you `eject`, you can’t go back!** - -If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. - -Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. - -You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. - -## Learn More - -You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). - -To learn React, check out the [React documentation](https://reactjs.org/). diff --git a/cvat-ui/config-overrides.js b/cvat-ui/config-overrides.js deleted file mode 100644 index 55538a98412..00000000000 --- a/cvat-ui/config-overrides.js +++ /dev/null @@ -1,14 +0,0 @@ -const { override, fixBabelImports, addLessLoader } = require('customize-cra'); - -module.exports = override( - fixBabelImports('import', { - libraryName: 'antd', - libraryDirectory: 'es', - style: true, - }), - - addLessLoader({ - javascriptEnabled: true, - // modifyVars: { '@primary-color': '#1DA57A' }, - }), -); diff --git a/cvat-ui/dist/favicon.ico b/cvat-ui/dist/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..ddb856875691a87fec9b44b5cda4782a30e2fd47 GIT binary patch literal 102581 zcmeI52Yij!8^>?N-bFMbB6h6O*u<=u6%y1YwMWgOR)}4ilq#ZjMKxwMY70S)TD7aO zf*OexBc&Ao-!FL&$Nk^r?wcEZpU>y(JnwtYe4ex3bKe^pje{n=hT@=c*EGzc(e#8K z7?^nNkHvb zAP3E~7CsHLWp>Gotg|(4RNs%El8pFV^Y>`~yBbZF?2YT!Z8ak5?xLoHT-!KJ85i8X zLgJ zUvmCtntB-vP0E@4KUX7<&w8wJX_UU*$scc>ck|xtyU2OPh9UD_hKGDT+q=fbP2U_k z`Qz*s6<1B@AG-xLHJU4~i&wHrBTK_X)X?lhYte@VaTgd(v z6~2pl)yga5LCvvm92ORFtlBXBpksBvO5-`ScJ`nXi(a}<9)7oKy?_~~E< zhU95-eZt4CH)GO=ZTz?mvIrJM$nV7tg@YyK8QuQH^g@i_GZe**9mG(6c2<&cDBG>H13R?_@64 zxXZp_p>rP;kB`W=q5USuj~etS@3Z#(L8rr9tLLuPZ}d&?JI?Wb9i8U|IfT#NTre{9 z^s`1z)6VEAJu>j&v<_k{UG1YvKQQ6lpghDfXn8fsEm7-6t1;!*o74*UQKS_ruUpU z=fx{3j969o=KA3cN;-Je|6|#&<-#f-(&QU8KG)MUYyWuR?6&&BmIWgwK5pK7r zN&{n@Zmk=Z>Ar925))U21-Eo^dN#X~%SUcstQ^=YbKX@C{Vsd`aLKdPmn*(bKf<|J zkV|Bal^cJ$(R}{?N}=nnj6NL|*7R7OMlZhUyzkDwLVaIUOuxW$Mb&c?e*Ey$(^+di zIG!iE0DSq{4yZ!QPJ{M8xb7X~9G|t;z*hZQ3|;9Gy0(SqqmZXteJ2-BH}=7~57%7T ze){hUV=jbw^~nD1r?ZDFuhua<`1#1}$D6%B(&J$4A1`Q_U(G@SWo;qki5;pRNe2({gm?tjE0v z*7|&Hk7f>+UJns;vxQGRmVTAj>&C>gZNqzKUEQ|wsuqt1&-ygD!LHVi2FEtczjjad z%ol#mA6>C?#ZpttRp}A=YFW(+z53Pi@!awC=3tlA|5S|+4as`^K(O`*fYtVtAGFYO z%*_X7L+5XdIMS?C+F{2RJ03Y%ty7axRW%RuJkKz>+eeOZO)uS@R;KRxL zbob1uCDVB|E!SbilDbh#f7pKG+UXgE7yaIUz-g`gV7%PvhK-HF=&$+Ra+kUx6a3cn z>E)Uue|+zgdmYv{y7~F|{GoYPl&es3gmdc}WlOpS9Es}Q%;C+vM5AY{(s;(CO5Jn$ zPY7>QVN2Im)sFNm=F>E1^%>U&ht6u>rhmI-{dOD-e(!$KrrQq}-IwW5yBsI7tcf}A zS$xg*VOf6+YO?q9Ic2x)UvMF;f3H!?h*ULTJM8tI6y2XxFY_+NTh{^u_W{&!^vH!1YTU9TSd2Icdw&{AWJYFgLu{M(~ zfAcbPMB}JgE#|l%&i%`i8wD0FKYQQ*Xjr|8i@Oc=^Qt}Vr~GsF?pxmcM9o1yPil|& z(<8#QR2g}UVfrqyEZArKYG=>B9!@p7v>z2$CT5A>!ACBaKJ4ps>hzNr^R8{`78#b; ztJ&I{gU^K($uoV$gA$Kj$}V2Cd%ExL>}7m&zQ6AH`Os?1gE#rVy4ouy%bb|4`_c?( zd3o!+sST<@MhYamiyUF>^>wGu5`PV9*`QWKzCCgU*XzfQsFH~N>sEWs?uP#PZtQhIkzS8{N zA+uh7@MOGW_>Z&Pe41>jA3vwpwQbsloa9?)T6n*G?!hzv9Z>k{M2`%I>wM8DW=S$xud$o;%h3?PavsVdtyeY2Tn6qI6##D&;X?SG2Uw>-+D$wuI?dQ{8lzsl^ z`QejKd))|%S{K{sYUd_xi$>&Jc)z-KjW#Ut2>fo}lHl0vl^1-sy!{T}%)^T|bM$T> zTg}^Pe%^_d&Q0E*_4~2uw@mKPV3_~R_60)=pPq^BR*l)$#ueCAsc)u``5&g$GWf!0 z-VPsE8`0tJuC`VBe))Ha{>@%A2#R0kx@Ps*sk3@k7<%kpRBY?-JQ+9e>9}q!+-}mmi9iMIG<~>WcH62{4*T$yZn=1?1-!fdhdui zTx4#ePAfvHzG}8$Yo55~vD+J@>vH(a>=A3<4<3~>&z{y({)qXxMTQ$IhfSQjAY|t7 zEn`kqE_<%?5BERG_{p;s8tuCGoIHLor_ZWAvd?G{yTvi|^cS=D>~SmAYL0i6_@n#A zobMF%*~VN&H-vcfA02YeH*DmE<;|~*D3NaYZq0+CC%o?-D6ul$D=NsP z?k?ZERX+|G-L+PasS`G59?;_U_B@-8b&U!uJhpj2$i#p#Ex%rouy1+2()etcRd?ft zrqBHcob+tsKYizJuXJOpP0y6G!{IIUm-y}O-*-`{^Ofuy@1=b{v~P`OdN-W)GM?~w znPYa>sM_80zAAzZvZk@OPyKcE{3m!o59AHFliTY=anS6|u1P?-p$4i2R12sUP%WTZ zK(&Bs0o4Mk1yl>D7EmpqT0pgcY5~;(ss&UFs1{Hypjtq+fNBBN0;&a63#b;br3LuT zVMYbKV5li7Q4V?yiAK#a$^+i0955t;3lv5cxziSmZK^OI7$oCah zv780oLsoee+z|Rp1VCRZu?PBLh2scsOFV@BPZB(l;xSDWI3)1{^d$@Jcz{rz z@1NuPMKZ{7A1ir?ysqO~loJj8gv77Vw@OTcF6!S8dMSxq&{cWc0_kw+B{2Z{&l0iF z={vzI==Q|FsLwNrEzs91%t0vjjSTYvRo=FM>i@Ux@h)ERnJA3}pOg3#FOm~9lo$?u zqXhTkV(fFQ+0)o(U9`vl4yo=7pf`{x3SG3DA)gh5UEbyAM!a>bDJ=0d^j{RXw%eOe ztyP~>XluAcS?Gln-sQDH^?za?SN)$j52)imaUOVA$3NFJu62U?(1%HI&KJ*dXQ5kb zzO}b}`5Y-fJM#Qe*u@{ZI8$cGIWx5i_w?;0LZCmCu*ZE|O7xXSl1&alZ!5t*kXl9c zzhQHl>VL!RloDgp7G=Im{;!S6O3Ytfpr4WW3p(d~!At12u&=$BA?(4B=hderzJcCD zBJpqU;D((_Yt-utg>eY&-S+lgKb|9hDZw){dluFImhJQQ?*Fvtj9Dc7p-)tpj?mu6 zm%Z0-Cu}%NA{TUFZ+n;v`0mv>i74pyZqw9Uze%t!&kIO9kkX?1-?sf-^}lWHZEtf$ zO8cMRPnMAA2z`@8EOdLb*}GI9zGEc5GYx`XT*4M-#f~UX`%L#;vah|jg=Z#$-=T}M z7KZ!|+1jG|KV|1P;d_Rv|5Mf%DKS^1g#UA+ujN;8M|fP~3iOn)gIZ?dSuy7TK_}>Z ze@MW+nkB>;necl^QWEg{3ITsJqxvFcY{2&+wikRbMq9%i8ifd?UYa#peL=+@2e{q2h#y zwIudHSACWGY)}t=^HSh*yP=}`KlS-q*Z5TZ|8DqEmv0TZXR7}zVWQxgFNlV2NO>t0 zhi8!YB*LJFDDXFC9V9-6UNZ@3FF7usr{_srgMMFu=WZ$0UPH^J|K=<7L1?JZJ{u%% zLN~O`l!zCP`tiL3!MD))eiRu3TxU%J?vdoU?QlI-;yiTz4xO+cq(obdDU&`F ze?wy=bVEh;e_}uQFZtgQokQ>u^au%id#8urfj*MB0{yTA$80}^t_Vv@@Og;Ox4Pip zppfHot@uo0C-fr<{EdcOhiAApW^CFbE`7LNVFE(oUxwOe!~Bj<&=dM|3EJBh^)<8% z&iAV&xi9Rz4`S>y)HnXcIT1sypD!frVg8RsefT#b$U)Fnq5|{`654AUKC+0g49=T` z^Cb6&g2m9q8977R+*b2^QsvNx|0?W4SWO}ibXmAh63m01RGukye<$iB)_m5FK9<3y z%|$N1GfJ2D1ApT>rEI0E+@q*Z7l}g9Wf8t-sQO>FOwzjQf7|tOb^M!WYklK?F1op3 z8+2WBh%N8&O!uxtYv>*dMG@MYypIZ%k~jxloJl`~ZcE$gD(fQZFk6CqCRx76gi9R$e%f<@zVXlJYXQI8)J|{f>rJQwpZNs2p{FKkhRS}T5P;Cu_R}u!G3pQ{(Gv17C3|%|Dt*^OPDKEjjUuu%lsO*Od{s?VtKkf3Kq7JbVEuhOHe9utzzib)Pbk+an z^?7ytn`b{`$G`mD;3#x?&KClHkEorWwXeTJnYkp=LQhTd9xBT{3uz3jZ7+TK^Lt>P zsR~*_m!%jk1pT4&{9VAb`trB*=OHLlocA)c);nFu+A6|xpzI2K*0!{LjLnZ@Sd3?e z+*ivYe9utzzib)P^wj2mH#Aew8TvViN6?M!Z|25V$G>H5u5bLq#jnL3S|SCWF2QXb9a^tI_PsG zc0f0$UP-0hk9g zXbFD5l+>6u=Kcxf!+BFs6gtlU1yJou(x5h?ZVV@bxeDtL8q;=b#J!C=?3Snp-Bgk1 zgR;ov3yD9V^Q=K&YFpTkQrqu8#P@v+*?+};i*v83cCb{sE2zT~i7L=d71jU$Z#n+{ zL-l{+Z{Wq)XJ~5wRmZ>Sw$o*EOOAiJ_w)Uh3KCmL995c_~& zDs(M8gjJR)I?J-HspXT{&(dbGkIgykjN zpmQGA1^X)NBxnnLj0ER+QO;fHDdkr~%l!p)3|AP1(B4G#zjgbN>VJ(!9sh}QfIW`? zJeZJN6bd5rllTn!42iwaPbp}B$M_8Q#aSyu#yh02AK@B_AD}Nvf~B~>R$>SA!xC}O zMLEgsPYj)(tsRejpLQfoKxz{1+v-a!fG*AsBB1{(@dCQFZJ8SL{|I)esZa`GY7*7| z=J#pU|K{5;HI92x=hW=~G?=XTu9+Yc^vnu;HZLa86#58>pP}zkIDn9AwSen}u+O^| z@*IV27szKFwCB4bj%bLpL>=hd>j=2tvukdCAo>9N#RG}k&~HdwfPP+qXBRsp zcwRAAf_3F@3y43cCXpAqg9Oz7yr};FZ+8LJ|Nm|Ob3PQ$kPOB5y8LdJ@VjE3jghjT zhD2TH4JG(qtFHpjs@qDmhu%S=GxRPB)cLuf4fNI$%uD3!1HFL+&+n>Au%0CqiXe27 z;Paj;ss&UFs1{Hypjtq+fNBBN0;&a63#b-QEudOJwSZ~?)dH#oR12sUP%WTZK(&Bs z0o4Mk1yl?C$1U*czaR-d&hVzCMqVpjB!|dA&vl^kxu#%Z0wb^aT3DhuB&0X?I#MLZ z|DwPQ^jz}|B_WQ8ZtQiWNX)SITg4f2&6lO#h~q13Uq`k!rw>1Ep(;8 zgikUges1hF{1hjrH}X26Crjz|U5`Wli3KLs*N(5F*(|Ak#$G4%KPkOct|faX3{PXP zHNqVlOlpy6u(8)DFc8V`_j+Ev;{2lTIw7tSKOup*K})@vpb{;w?^@})30W|`(bun{ zMe&@lAo{LfPijhhqpuT-c^CinfGO*MK;WQp$G?y|8qMD9i6mUB|ELyFEudOJwSZ~? z)dH5X0N+Q>1?qv1!2mD{dk!ximVl9f?_2YIZt*)9sCFl=s8Izl8qmM| zz0Y}Y3&es4fWI+gJD&kQiSzM8hWDO>CxEtL8vbUCdC+Emf(3y6gJtnIPbn$vi;jT5 zM=cK6*S`fDz$3u6h~G+?8!|82Z4=;cr#L3c1D4G?*m_*>S-CK13-}vE`p4Wh8k?Sf zvn(9&_tAwwR$y(RojI?$0nTe9z+UhdFt+~Y#=na)cLR=xrhsz<>&D-MSWDQ~+5@)x zA>ddww;krD=N!tqd;{1YX#;Br$9Opq0=QnW?HrTl*5BOpV$R%#y6`uZ{GAQwM@!EM zJ(1^VU~YY_oqj3m_$9E^IH1qH!B2o=-P-jxH~&+p<8Po5s1D4PHppNc;Cy3l-E5hD zJnGvIm@0*kunyo@j{^2E){U_r>&*UI8xwpIlxF&c3LnR9$ z76cQ)Rq!{kmo^z&53UW-AQTh;IeP{bCb4k+|&1g zz+UQY>3S?h+c+2M8V9*?pJy&hfF;Z280230Iyelrfwf>6SggP@d5`0caV`O-;hBIX z>#`a7u@4jj+ENfVI8Ph}mMoL|2A+ZJ1OdPgcmkg1@R^lx&gDJs-Nypj;x?cSELj(x zq3|54CXj_=;zMu&@VvzmWzu)QgDv1Q@Cm34iU9V%On~E%@H~O{+yLYFfc{`5I1acD zS)xvNkq_tfmLMD8oJVm6wE@>UOZ7SWeKnX0Xy1&$P)U!7>=Oe3`vT`LOSGS3k@e~h z*#E>cQgi6E_e%wH%Q}dB==ZXK{$@GC)=>2d<1$SFz&w_MJ-}Swi1tlGULS*OfYKFk zY?|9{k)HF|_khpf#8eUKryzs1fW8xDm=n%LK3zagKv@Wwzd3m_EzfK@Z*eZm2P`G* ztLzI`zHU{R_lk*h!WFCMr6a;~P0oO=V^Y=&ELV#0S`mXyTO*xR-=6_WAwKzh{1X=;k z2hl)ZyT#{=5tn-uYwdrAAnz}YsE??Zc6bSS90g+k1NAN8{8b-J0>=RN+=i@+s}PU% zdkGUc03;Jw8JH-HwQ_fYE60!fkhz#!l zW8148KN5K?0r^34!uE5&(Hd+6>@)nno$H5op1ghxXoKdU3b2%vMMkX04e$`?D%+TQ zzaWobz;ho%h2w$eMz;amm|S_xe<=`s2&zdU(sLZH21(gHxw`23jAO_jt2{&Ry+nGB;XuH8aa(SsE;J)%UcmOy? zIOd-K&KqJ3&{kZ#gpYYm_&H%*rilm4hw~8g4uRQ#^Il2|*EY_194lOdIoA&Z zi@^%86R^IV3+Ok_1$!xnx=G06(o zT&_KQ_9zXSfEM60Fa%5hUx7J*Z3zWG0?sMy!{i6R`}}-9V0)*5@qqcT-OWHlz_#ZH z_Rw}zLlxBmss&UFs1{HyU>_DR{cj&&n^lmhMTqwB&oJOJ;qSwUFp}5!I}{=e*PG0!=~fFfj3chNQy8j}zAY+NcWGe-ZMpMDSV(QI~}4 zgg?1~`-X*qN<0xpE7xzr#C)H!t;iqkNhlBP7GXktU>6ax$`1SxlHqljXt;z*CEQQ6 z5G@~#YdOR%ss&UF{I@NT8O?VGRe&Gp4hDjeAW&hf za?LZa4xlOEb8arc=U!V2%jEf7K2Q;~1A&0gkcYt~a3Apefbi_$9ykRez%no#)CDDh zGvKo;&rob3e0J>weg=I0=d-^!12HCij&$e13Qz;Q2dpLRCxyUNunk0kyTH`Cvz^=` z&^NciNw64n1N5sUec_7y>VwgMXR_SE7-MHcaq!bRXQ zFtjeF#$%u2Sm_7K0CUE7PNe@FoCnV%YBu`t%iAKFv zfh@og$ih7V`Q=N?^*E<+%C{Oa+{uwfiM*y(OIExGx9=Y`3vxinX~F(u(~m`_4XKtSuNn z95e^qD;Xnc5P2pz1xzWI{lyDVg25SJOg_&MKMwc+_6<{{G7^0QOc@XCFKv`Ue?oW# z7*qfIh<^ZZubvi|BAJkg`xK5HW6EY+&hwuE$`Nn}7?a1Jh(88!E;dK1A@MX|OxcXf z=eBWx!ajTz7?TIb3+I`1z#O6f8Uka=W?atIvjBxY+zpJG3w9#DE8sXZM>uDd2gcSv z9%<$&1-l{q78v7$ZHUjZ9e_E)IlM41w*H*A7fJKJo zt}>FlcO7Y_>nNE0VFcI(I7X7n;{rb8^MpBlh-K9RT*HzoC#m~qktP7>5|J<4_X6n3 z;~(6Q0q#KTqo5ikY&)Nqj9q(JpDmy}NJ?_y1I}yX!5@IS+Mm$2Fq@SO++V7*bz34$GzA zTL9)|h_HO_6KJ=lfP43gK;+BzaIAAovp=;4r2*$;k{5gc1_ACBuLAZN*#;sneIf0) z8L)55Ho~P|p$%O?ZO{$O0Gvl-0o%a5Pk{)q8Z1&+hLFDd4g3Y30)6#N>T~XUJ_D}6 z9HB2bHYR~JfaBm2xC7`5j_;)EVCsF^=PaP@vjR&A`(X*d{=v4K0p{2^srGRmoCl2U zH%OAKuwQWv$O*U~E(~a+c3=eH`owna2W;Cl5C`ZdF%BLMNu2C%I|0sYt&v<8hqHBbn!&#IzYK()X- zWP!vti?G-vNHQT-d%p1W3Nv3pISk}mU$_sOIN%>H#JmvUQ(o8BXrdVwd=p0U8ncuL z19|P3I3dX)Zzs<9IilYV@p_d5KOFaOVu)AG60UWJfttkoB8*h7BbD^gufxQA|J5Xx zho&Ty|2mAsHR_r0x)wuzfOfnIVW5O-8kF~w4?U3F9n=A@-_y#ezNZCqq*lp@iWdRZ z0oPAIz_qlca?NL#Vt{*VOA4PqyulYB3>*VD!4tsY$nX~U9dLil^}h))cTd80uw3qw zP5_>5b5!VqdtB~E=K|Klls%~{(hUPg0H+##W$OBz^<}+j4|iaw(8n_YZEDK1kX#q0 z=l*dCC=c`r_o(v#CL5F5ESD|okCESUP!c31>}TB5a{nu*wUquh@}*t*tfwv9r?&+U zfu+jRl@Isxy+9@)gsV$&z;UH3jiv9ML^+i}!m5T4t4hM&-OzGq$18x(#H=&JXh6Ri z+Ft_^H#?xb4>(TrmCwAR!2-Z>O+V6(Oy2;^0T%%0Bz<|EL7WnRHsBn?IYVEXONjF+ zpuKbn@AKJ?_Rwbsme&fT1=E4PvT5_)zz|^_4C%+;B3>4-6X+{{E8-aXoW`-V-he#! zB2G>aX+Yebh(lW$DxV;tzBc`ZIJrSb@Jvtqs`y-t7pQuL>#)A^=~E~09+;&kemQ(@ zO!@is>%dbW>YP+~ z8*wTE`YJhLKkfxsk7&TTo%IO?qXFA65-bImfgy80>%{bVfuX|obpRZvyTJ{h&&El$ zo%L@H(g9;72NF~U?LYun40ZtaW#;<`u+PxHoL|^)dH*<|?-qbhL2h7<&~Nm60Zd3Li6fnaAGp0K)dQdB1n%JVIR#dF z8~vV)b66oz3X}lOfODEY;kq>t><4!N=N&!=Uji#YEs#_`oc9ia4CKo+h+ zoad67Tjcw!6ZgdF0flX4edRd%bmq|+P&nsk$7h>=0)xRM@F#$~--^e*b~dm<>pt7x z7s%@w?SDWk9`ob;a#`y>K9AA+(n_hyZ_qF@W}I0e%6rtDOECu651# zoQG?JuL1i7<8jSk`hNiX^=UvK$#ul#|4C-t;oM#vlm;Bu99Ox3>|b1(COq?eS`eE6 d)H)JY50q-;5C}&^zF|DBahb4*`xo(;_J6j=uYdpm literal 0 HcmV?d00001 diff --git a/cvat-ui/index.d.ts b/cvat-ui/index.d.ts new file mode 100644 index 00000000000..e78b0c987f2 --- /dev/null +++ b/cvat-ui/index.d.ts @@ -0,0 +1,5 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +declare module '*.svg'; diff --git a/cvat-ui/package-lock.json b/cvat-ui/package-lock.json index 488fa301cde..828118cfaaf 100644 --- a/cvat-ui/package-lock.json +++ b/cvat-ui/package-lock.json @@ -1,13 +1,13 @@ { "name": "cvat-ui", - "version": "0.1.0", + "version": "0.5.2", "lockfileVersion": 1, "requires": true, "dependencies": { "@ant-design/colors": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-3.1.0.tgz", - "integrity": "sha512-Td7g1P53sNFyT4Gya6836e70TrhoVZ+HjZs6mpWIHrxl4/VqsjjOyzj/8ktOuw0lCx+BfYu9UO1CiJ0MoYYfhg==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-3.2.2.tgz", + "integrity": "sha512-YKgNbG2dlzqMhA9NtI3/pbY16m3Yl/EeWBRa+lB1X1YaYxHrxNexiQYCLTWO/uDvAjLFMEDU+zR901waBtMtjQ==", "requires": { "tinycolor2": "^1.4.1" } @@ -22,9 +22,9 @@ } }, "@ant-design/icons": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-2.0.1.tgz", - "integrity": "sha512-SqiNhgoivKczEqIJc/9hntgtvmq4R3Ef73ehibqDPAT059IjsXXM7nze0S5P8F4HP76jgPiv5od+2JUhQl/nig==" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-2.1.1.tgz", + "integrity": "sha512-jCH+k2Vjlno4YWl6g535nHR09PwCEmTBKAG6VqF+rhkrSPRLfgpU2maagwbZPLjaHuU5Jd1DFQ2KJpQuI6uG8w==" }, "@ant-design/icons-react": { "version": "2.0.1", @@ -36,77 +36,53 @@ } }, "@babel/code-frame": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", + "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", + "dev": true, "requires": { "@babel/highlight": "^7.0.0" } }, "@babel/core": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.4.3.tgz", - "integrity": "sha512-oDpASqKFlbspQfzAE7yaeTmdljSH2ADIvBlb0RwbStltTuWa0+7CCI1fYVINNv9saHPa1W7oaKeuNuKj+RQCvA==", - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.4.0", - "@babel/helpers": "^7.4.3", - "@babel/parser": "^7.4.3", - "@babel/template": "^7.4.0", - "@babel/traverse": "^7.4.3", - "@babel/types": "^7.4.0", + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.6.4.tgz", + "integrity": "sha512-Rm0HGw101GY8FTzpWSyRbki/jzq+/PkNQJ+nSulrdY6gFGOsNseCqD6KHRYe2E+EdzuBdr2pxCp6s4Uk6eJ+XQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.6.4", + "@babel/helpers": "^7.6.2", + "@babel/parser": "^7.6.4", + "@babel/template": "^7.6.0", + "@babel/traverse": "^7.6.3", + "@babel/types": "^7.6.3", "convert-source-map": "^1.1.0", "debug": "^4.1.0", "json5": "^2.1.0", - "lodash": "^4.17.11", + "lodash": "^4.17.13", "resolve": "^1.3.2", "semver": "^5.4.1", "source-map": "^0.5.0" - }, - "dependencies": { - "json5": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", - "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", - "requires": { - "minimist": "^1.2.0" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - } } }, "@babel/generator": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.4.tgz", - "integrity": "sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ==", + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.4.tgz", + "integrity": "sha512-jsBuXkFoZxk0yWLyGI9llT9oiQ2FeTASmRFE32U+aaDTfoE92t78eroO7PTpU/OrYq38hlcDM6vbfLDaOLy+7w==", + "dev": true, "requires": { - "@babel/types": "^7.4.4", + "@babel/types": "^7.6.3", "jsesc": "^2.5.1", - "lodash": "^4.17.11", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - } + "lodash": "^4.17.13", + "source-map": "^0.5.0" } }, "@babel/helper-annotate-as-pure": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz", "integrity": "sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==", + "dev": true, "requires": { "@babel/types": "^7.0.0" } @@ -115,6 +91,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz", "integrity": "sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w==", + "dev": true, "requires": { "@babel/helper-explode-assignable-expression": "^7.1.0", "@babel/types": "^7.0.0" @@ -124,6 +101,7 @@ "version": "7.3.0", "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.3.0.tgz", "integrity": "sha512-MjA9KgwCuPEkQd9ncSXvSyJ5y+j2sICHyrI0M3L+6fnS4wMSNDc1ARXsbTfbb2cXHn17VisSnU/sHFTCxVxSMw==", + "dev": true, "requires": { "@babel/types": "^7.3.0", "esutils": "^2.0.0" @@ -133,6 +111,7 @@ "version": "7.4.4", "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.4.4.tgz", "integrity": "sha512-l79boDFJ8S1c5hvQvG+rc+wHw6IuH7YldmRKsYtpbawsxURu/paVy57FZMomGK22/JckepaikOkY0MoAmdyOlQ==", + "dev": true, "requires": { "@babel/helper-hoist-variables": "^7.4.4", "@babel/traverse": "^7.4.4", @@ -140,32 +119,35 @@ } }, "@babel/helper-create-class-features-plugin": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.4.4.tgz", - "integrity": "sha512-UbBHIa2qeAGgyiNR9RszVF7bUHEdgS4JAUNT8SiqrAN6YJVxlOxeLr5pBzb5kan302dejJ9nla4RyKcR1XT6XA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.6.0.tgz", + "integrity": "sha512-O1QWBko4fzGju6VoVvrZg0RROCVifcLxiApnGP3OWfWzvxRZFCoBD81K5ur5e3bVY2Vf/5rIJm8cqPKn8HUJng==", + "dev": true, "requires": { "@babel/helper-function-name": "^7.1.0", - "@babel/helper-member-expression-to-functions": "^7.0.0", + "@babel/helper-member-expression-to-functions": "^7.5.5", "@babel/helper-optimise-call-expression": "^7.0.0", "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-replace-supers": "^7.4.4", + "@babel/helper-replace-supers": "^7.5.5", "@babel/helper-split-export-declaration": "^7.4.4" } }, "@babel/helper-define-map": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.4.4.tgz", - "integrity": "sha512-IX3Ln8gLhZpSuqHJSnTNBWGDE9kdkTEWl21A/K7PQ00tseBwbqCHTvNLHSBd9M0R5rER4h5Rsvj9vw0R5SieBg==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.5.5.tgz", + "integrity": "sha512-fTfxx7i0B5NJqvUOBBGREnrqbTxRh7zinBANpZXAVDlsZxYdclDp467G1sQ8VZYMnAURY3RpBUAgOYT9GfzHBg==", + "dev": true, "requires": { "@babel/helper-function-name": "^7.1.0", - "@babel/types": "^7.4.4", - "lodash": "^4.17.11" + "@babel/types": "^7.5.5", + "lodash": "^4.17.13" } }, "@babel/helper-explode-assignable-expression": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz", "integrity": "sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA==", + "dev": true, "requires": { "@babel/traverse": "^7.1.0", "@babel/types": "^7.0.0" @@ -175,6 +157,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "dev": true, "requires": { "@babel/helper-get-function-arity": "^7.0.0", "@babel/template": "^7.1.0", @@ -185,6 +168,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "dev": true, "requires": { "@babel/types": "^7.0.0" } @@ -193,43 +177,48 @@ "version": "7.4.4", "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.4.4.tgz", "integrity": "sha512-VYk2/H/BnYbZDDg39hr3t2kKyifAm1W6zHRfhx8jGjIHpQEBv9dry7oQ2f3+J703TLu69nYdxsovl0XYfcnK4w==", + "dev": true, "requires": { "@babel/types": "^7.4.4" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0.tgz", - "integrity": "sha512-avo+lm/QmZlv27Zsi0xEor2fKcqWG56D5ae9dzklpIaY7cQMK5N8VSpaNVPPagiqmy7LrEjK1IWdGMOqPu5csg==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.5.5.tgz", + "integrity": "sha512-5qZ3D1uMclSNqYcXqiHoA0meVdv+xUEex9em2fqMnrk/scphGlGgg66zjMrPJESPwrFJ6sbfFQYUSa0Mz7FabA==", + "dev": true, "requires": { - "@babel/types": "^7.0.0" + "@babel/types": "^7.5.5" } }, "@babel/helper-module-imports": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz", "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==", + "dev": true, "requires": { "@babel/types": "^7.0.0" } }, "@babel/helper-module-transforms": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.4.4.tgz", - "integrity": "sha512-3Z1yp8TVQf+B4ynN7WoHPKS8EkdTbgAEy0nU0rs/1Kw4pDgmvYH3rz3aI11KgxKCba2cn7N+tqzV1mY2HMN96w==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.5.5.tgz", + "integrity": "sha512-jBeCvETKuJqeiaCdyaheF40aXnnU1+wkSiUs/IQg3tB85up1LyL8x77ClY8qJpuRJUcXQo+ZtdNESmZl4j56Pw==", + "dev": true, "requires": { "@babel/helper-module-imports": "^7.0.0", "@babel/helper-simple-access": "^7.1.0", "@babel/helper-split-export-declaration": "^7.4.4", "@babel/template": "^7.4.4", - "@babel/types": "^7.4.4", - "lodash": "^4.17.11" + "@babel/types": "^7.5.5", + "lodash": "^4.17.13" } }, "@babel/helper-optimise-call-expression": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz", "integrity": "sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g==", + "dev": true, "requires": { "@babel/types": "^7.0.0" } @@ -237,20 +226,23 @@ "@babel/helper-plugin-utils": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", - "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==" + "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==", + "dev": true }, "@babel/helper-regex": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.4.4.tgz", - "integrity": "sha512-Y5nuB/kESmR3tKjU8Nkn1wMGEx1tjJX076HBMeL3XLQCu6vA/YRzuTW0bbb+qRnXvQGn+d6Rx953yffl8vEy7Q==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.5.5.tgz", + "integrity": "sha512-CkCYQLkfkiugbRDO8eZn6lRuR8kzZoGXCg3149iTk5se7g6qykSpy3+hELSwquhu+TgHn8nkLiBwHvNX8Hofcw==", + "dev": true, "requires": { - "lodash": "^4.17.11" + "lodash": "^4.17.13" } }, "@babel/helper-remap-async-to-generator": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz", "integrity": "sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg==", + "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.0.0", "@babel/helper-wrap-function": "^7.1.0", @@ -260,20 +252,22 @@ } }, "@babel/helper-replace-supers": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.4.4.tgz", - "integrity": "sha512-04xGEnd+s01nY1l15EuMS1rfKktNF+1CkKmHoErDppjAAZL+IUBZpzT748x262HF7fibaQPhbvWUl5HeSt1EXg==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.5.5.tgz", + "integrity": "sha512-XvRFWrNnlsow2u7jXDuH4jDDctkxbS7gXssrP4q2nUD606ukXHRvydj346wmNg+zAgpFx4MWf4+usfC93bElJg==", + "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.0.0", + "@babel/helper-member-expression-to-functions": "^7.5.5", "@babel/helper-optimise-call-expression": "^7.0.0", - "@babel/traverse": "^7.4.4", - "@babel/types": "^7.4.4" + "@babel/traverse": "^7.5.5", + "@babel/types": "^7.5.5" } }, "@babel/helper-simple-access": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz", "integrity": "sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w==", + "dev": true, "requires": { "@babel/template": "^7.1.0", "@babel/types": "^7.0.0" @@ -283,6 +277,7 @@ "version": "7.4.4", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz", "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==", + "dev": true, "requires": { "@babel/types": "^7.4.4" } @@ -291,6 +286,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz", "integrity": "sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ==", + "dev": true, "requires": { "@babel/helper-function-name": "^7.1.0", "@babel/template": "^7.1.0", @@ -299,19 +295,21 @@ } }, "@babel/helpers": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.4.4.tgz", - "integrity": "sha512-igczbR/0SeuPR8RFfC7tGrbdTbFL3QTvH6D+Z6zNxnTe//GyqmtHmDkzrqDmyZ3eSwPqB/LhyKoU5DXsp+Vp2A==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.6.2.tgz", + "integrity": "sha512-3/bAUL8zZxYs1cdX2ilEE0WobqbCmKWr/889lf2SS0PpDcpEIY8pb1CCyz0pEcX3pEb+MCbks1jIokz2xLtGTA==", + "dev": true, "requires": { - "@babel/template": "^7.4.4", - "@babel/traverse": "^7.4.4", - "@babel/types": "^7.4.4" + "@babel/template": "^7.6.0", + "@babel/traverse": "^7.6.2", + "@babel/types": "^7.6.0" } }, "@babel/highlight": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", - "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", + "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", + "dev": true, "requires": { "chalk": "^2.0.0", "esutils": "^2.0.2", @@ -319,14 +317,16 @@ } }, "@babel/parser": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.5.tgz", - "integrity": "sha512-9mUqkL1FF5T7f0WDFfAoDdiMVPWsdD1gZYzSnaXsxUCUqzuch/8of9G3VUSNiZmMBoRxT3neyVsqeiL/ZPcjew==" + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.4.tgz", + "integrity": "sha512-D8RHPW5qd0Vbyo3qb+YjO5nvUVRTXFLQ/FsDxJU2Nqz4uB5EnUN0ZQSEYpvTIbRuttig1XbHWU5oMeQwQSAA+A==", + "dev": true }, "@babel/plugin-proposal-async-generator-functions": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz", "integrity": "sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/helper-remap-async-to-generator": "^7.1.0", @@ -334,37 +334,40 @@ } }, "@babel/plugin-proposal-class-properties": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.4.0.tgz", - "integrity": "sha512-t2ECPNOXsIeK1JxJNKmgbzQtoG27KIlVE61vTqX0DKR9E9sZlVVxWUtEW9D5FlZ8b8j7SBNCHY47GgPKCKlpPg==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.5.5.tgz", + "integrity": "sha512-AF79FsnWFxjlaosgdi421vmYG6/jg79bVD0dpD44QdgobzHKuLZ6S3vl8la9qIeSwGi8i1fS0O1mfuDAAdo1/A==", + "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.4.0", + "@babel/helper-create-class-features-plugin": "^7.5.5", "@babel/helper-plugin-utils": "^7.0.0" } }, - "@babel/plugin-proposal-decorators": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.4.0.tgz", - "integrity": "sha512-d08TLmXeK/XbgCo7ZeZ+JaeZDtDai/2ctapTRsWWkkmy7G/cqz8DQN/HlWG7RR4YmfXxmExsbU3SuCjlM7AtUg==", + "@babel/plugin-proposal-dynamic-import": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.5.0.tgz", + "integrity": "sha512-x/iMjggsKTFHYC6g11PL7Qy58IK8H5zqfm9e6hu4z1iH2IRyAp9u9dL80zA6R76yFovETFLKz2VJIC2iIPBuFw==", + "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.4.0", "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-decorators": "^7.2.0" + "@babel/plugin-syntax-dynamic-import": "^7.2.0" } }, "@babel/plugin-proposal-json-strings": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz", "integrity": "sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/plugin-syntax-json-strings": "^7.2.0" } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.4.4.tgz", - "integrity": "sha512-dMBG6cSPBbHeEBdFXeQ2QLc5gUpg4Vkaz8octD4aoW/ISO+jBOcsuxYL7bsb5WSu8RLP6boxrBIALEHgoHtO9g==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.6.2.tgz", + "integrity": "sha512-LDBXlmADCsMZV1Y9OQwMc0MyGZ8Ta/zlD9N67BfQT8uYwkRswiu2hU6nJKrjrt/58aH/vqfQlR/9yId/7A2gWw==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/plugin-syntax-object-rest-spread": "^7.2.0" @@ -374,33 +377,28 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz", "integrity": "sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/plugin-syntax-optional-catch-binding": "^7.2.0" } }, "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.4.4.tgz", - "integrity": "sha512-j1NwnOqMG9mFUOH58JTFsA/+ZYzQLUZ/drqWUqxCYLGeu2JFZL8YrNC9hBxKmWtAuOCHPcRpgv7fhap09Fb4kA==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.6.2.tgz", + "integrity": "sha512-NxHETdmpeSCtiatMRYWVJo7266rrvAC3DTeG5exQBIH/fMIUK7ejDNznBbn3HQl/o9peymRRg7Yqkx6PdUXmMw==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/helper-regex": "^7.4.4", - "regexpu-core": "^4.5.4" + "regexpu-core": "^4.6.0" } }, "@babel/plugin-syntax-async-generators": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz", "integrity": "sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg==", - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" - } - }, - "@babel/plugin-syntax-decorators": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.2.0.tgz", - "integrity": "sha512-38QdqVoXdHUQfTpZo3rQwqQdWtCn5tMv4uV6r2RMfTqNBuv4ZBhz79SfaQWKTVmxHjeFv/DnXVC/+agHCklYWA==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } @@ -409,14 +407,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.2.0.tgz", "integrity": "sha512-mVxuJ0YroI/h/tbFTPGZR8cv6ai+STMKNBq0f8hFxsxWjl94qqhsb+wXbpNMDPU3cfR1TIsVFzU3nXyZMqyK4w==", - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" - } - }, - "@babel/plugin-syntax-flow": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.2.0.tgz", - "integrity": "sha512-r6YMuZDWLtLlu0kqIim5o/3TNRAlWb073HwT3e2nKf9I8IIvOggPrnILYPsrrKilmn/mYEMCf/Z07w3yQJF6dg==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } @@ -425,6 +416,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz", "integrity": "sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } @@ -433,6 +425,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.2.0.tgz", "integrity": "sha512-VyN4QANJkRW6lDBmENzRszvZf3/4AXaj9YR7GwrWeeN9tEBPuXbmDYVU9bYBN0D70zCWVwUy0HWq2553VCb6Hw==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } @@ -441,6 +434,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz", "integrity": "sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } @@ -449,6 +443,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz", "integrity": "sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } @@ -457,6 +452,7 @@ "version": "7.3.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.3.3.tgz", "integrity": "sha512-dGwbSMA1YhVS8+31CnPR7LB4pcbrzcV99wQzby4uAfrkZPYZlQ7ImwdpzLqi6Z6IL02b8IAL379CaMwo0x5Lag==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } @@ -465,14 +461,16 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz", "integrity": "sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.4.4.tgz", - "integrity": "sha512-YiqW2Li8TXmzgbXw+STsSqPBPFnGviiaSp6CYOq55X8GQ2SGVLrXB6pNid8HkqkZAzOH6knbai3snhP7v0fNwA==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.5.0.tgz", + "integrity": "sha512-mqvkzwIGkq0bEF1zLRRiTdjfomZJDV33AH3oQzHVGkI2VzEmXLpKKOBvEVaFZBJdN0XTyH38s9j/Kiqr68dggg==", + "dev": true, "requires": { "@babel/helper-module-imports": "^7.0.0", "@babel/helper-plugin-utils": "^7.0.0", @@ -483,30 +481,33 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz", "integrity": "sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.4.4.tgz", - "integrity": "sha512-jkTUyWZcTrwxu5DD4rWz6rDB5Cjdmgz6z7M7RLXOJyCUkFBawssDGcGh8M/0FTSB87avyJI1HsTwUXp9nKA1PA==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.6.3.tgz", + "integrity": "sha512-7hvrg75dubcO3ZI2rjYTzUrEuh1E9IyDEhhB6qfcooxhDA33xx2MasuLVgdxzcP6R/lipAC6n9ub9maNW6RKdw==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", - "lodash": "^4.17.11" + "lodash": "^4.17.13" } }, "@babel/plugin-transform-classes": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.4.4.tgz", - "integrity": "sha512-/e44eFLImEGIpL9qPxSRat13I5QNRgBLu2hOQJCF7VLy/otSM/sypV1+XaIw5+502RX/+6YaSAPmldk+nhHDPw==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.5.5.tgz", + "integrity": "sha512-U2htCNK/6e9K7jGyJ++1p5XRU+LJjrwtoiVn9SzRlDT2KubcZ11OOwy3s24TjHxPgxNwonCYP7U2K51uVYCMDg==", + "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.0.0", - "@babel/helper-define-map": "^7.4.4", + "@babel/helper-define-map": "^7.5.5", "@babel/helper-function-name": "^7.1.0", "@babel/helper-optimise-call-expression": "^7.0.0", "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-replace-supers": "^7.4.4", + "@babel/helper-replace-supers": "^7.5.5", "@babel/helper-split-export-declaration": "^7.4.4", "globals": "^11.1.0" } @@ -515,32 +516,36 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz", "integrity": "sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-destructuring": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.4.4.tgz", - "integrity": "sha512-/aOx+nW0w8eHiEHm+BTERB2oJn5D127iye/SUQl7NjHy0lf+j7h4MKMMSOwdazGq9OxgiNADncE+SRJkCxjZpQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.6.0.tgz", + "integrity": "sha512-2bGIS5P1v4+sWTCnKNDZDxbGvEqi0ijeqM/YqHtVGrvG2y0ySgnEEhXErvE9dA0bnIzY9bIzdFK0jFA46ASIIQ==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.4.4.tgz", - "integrity": "sha512-P05YEhRc2h53lZDjRPk/OektxCVevFzZs2Gfjd545Wde3k+yFDbXORgl2e0xpbq8mLcKJ7Idss4fAg0zORN/zg==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.6.2.tgz", + "integrity": "sha512-KGKT9aqKV+9YMZSkowzYoYEiHqgaDhGmPNZlZxX6UeHC4z30nC1J9IrZuGqbYFB1jaIGdv91ujpze0exiVK8bA==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/helper-regex": "^7.4.4", - "regexpu-core": "^4.5.4" + "regexpu-core": "^4.6.0" } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.2.0.tgz", - "integrity": "sha512-q+yuxW4DsTjNceUiTzK0L+AfQ0zD9rWaTLiUqHA8p0gxx7lu1EylenfzjeIWNkPy6e/0VG/Wjw9uf9LueQwLOw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.5.0.tgz", + "integrity": "sha512-igcziksHizyQPlX9gfSjHkE2wmoCH3evvD2qR5w29/Dk0SMKE/eOI7f1HhBdNhR/zxJDqrgpoDTq5YSLH/XMsQ==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } @@ -549,24 +554,17 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz", "integrity": "sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A==", + "dev": true, "requires": { "@babel/helper-builder-binary-assignment-operator-visitor": "^7.1.0", "@babel/helper-plugin-utils": "^7.0.0" } }, - "@babel/plugin-transform-flow-strip-types": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.4.0.tgz", - "integrity": "sha512-C4ZVNejHnfB22vI2TYN4RUp2oCmq6cSEAg4RygSvYZUECRqUu9O4PMEMNJ4wsemaRGg27BbgYctG4BZh+AgIHw==", - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-flow": "^7.2.0" - } - }, "@babel/plugin-transform-for-of": { "version": "7.4.4", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.4.tgz", "integrity": "sha512-9T/5Dlr14Z9TIEXLXkt8T1DU7F24cbhwhMNUziN3hB1AXoZcdzPcTiKGRn/6iOymDqtTKWnr/BtRKN9JwbKtdQ==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } @@ -575,6 +573,7 @@ "version": "7.4.4", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.4.4.tgz", "integrity": "sha512-iU9pv7U+2jC9ANQkKeNF6DrPy4GBa4NWQtl6dHB4Pb3izX2JOEvDTFarlNsBj/63ZEzNNIAMs3Qw4fNCcSOXJA==", + "dev": true, "requires": { "@babel/helper-function-name": "^7.1.0", "@babel/helper-plugin-utils": "^7.0.0" @@ -584,6 +583,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz", "integrity": "sha512-2ThDhm4lI4oV7fVQ6pNNK+sx+c/GM5/SaML0w/r4ZB7sAneD/piDJtwdKlNckXeyGK7wlwg2E2w33C/Hh+VFCg==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } @@ -592,76 +592,88 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.2.0.tgz", "integrity": "sha512-HiU3zKkSU6scTidmnFJ0bMX8hz5ixC93b4MHMiYebmk2lUVNGOboPsqQvx5LzooihijUoLR/v7Nc1rbBtnc7FA==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-modules-amd": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.2.0.tgz", - "integrity": "sha512-mK2A8ucqz1qhrdqjS9VMIDfIvvT2thrEsIQzbaTdc5QFzhDjQv2CkJJ5f6BXIkgbmaoax3zBr2RyvV/8zeoUZw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.5.0.tgz", + "integrity": "sha512-n20UsQMKnWrltocZZm24cRURxQnWIvsABPJlw/fvoy9c6AgHZzoelAIzajDHAQrDpuKFFPPcFGd7ChsYuIUMpg==", + "dev": true, "requires": { "@babel/helper-module-transforms": "^7.1.0", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.0.0", + "babel-plugin-dynamic-import-node": "^2.3.0" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.4.4.tgz", - "integrity": "sha512-4sfBOJt58sEo9a2BQXnZq+Q3ZTSAUXyK3E30o36BOGnJ+tvJ6YSxF0PG6kERvbeISgProodWuI9UVG3/FMY6iw==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.6.0.tgz", + "integrity": "sha512-Ma93Ix95PNSEngqomy5LSBMAQvYKVe3dy+JlVJSHEXZR5ASL9lQBedMiCyVtmTLraIDVRE3ZjTZvmXXD2Ozw3g==", + "dev": true, "requires": { "@babel/helper-module-transforms": "^7.4.4", "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-simple-access": "^7.1.0" + "@babel/helper-simple-access": "^7.1.0", + "babel-plugin-dynamic-import-node": "^2.3.0" } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.4.4.tgz", - "integrity": "sha512-MSiModfILQc3/oqnG7NrP1jHaSPryO6tA2kOMmAQApz5dayPxWiHqmq4sWH2xF5LcQK56LlbKByCd8Aah/OIkQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.5.0.tgz", + "integrity": "sha512-Q2m56tyoQWmuNGxEtUyeEkm6qJYFqs4c+XyXH5RAuYxObRNz9Zgj/1g2GMnjYp2EUyEy7YTrxliGCXzecl/vJg==", + "dev": true, "requires": { "@babel/helper-hoist-variables": "^7.4.4", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.0.0", + "babel-plugin-dynamic-import-node": "^2.3.0" } }, "@babel/plugin-transform-modules-umd": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.2.0.tgz", "integrity": "sha512-BV3bw6MyUH1iIsGhXlOK6sXhmSarZjtJ/vMiD9dNmpY8QXFFQTj+6v92pcfy1iqa8DeAfJFwoxcrS/TUZda6sw==", + "dev": true, "requires": { "@babel/helper-module-transforms": "^7.1.0", "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.4.5.tgz", - "integrity": "sha512-z7+2IsWafTBbjNsOxU/Iv5CvTJlr5w4+HGu1HovKYTtgJ362f7kBcQglkfmlspKKZ3bgrbSGvLfNx++ZJgCWsg==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.6.3.tgz", + "integrity": "sha512-jTkk7/uE6H2s5w6VlMHeWuH+Pcy2lmdwFoeWCVnvIrDUnB5gQqTVI8WfmEAhF2CDEarGrknZcmSFg1+bkfCoSw==", + "dev": true, "requires": { - "regexp-tree": "^0.1.6" + "regexpu-core": "^4.6.0" } }, "@babel/plugin-transform-new-target": { "version": "7.4.4", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.4.4.tgz", "integrity": "sha512-r1z3T2DNGQwwe2vPGZMBNjioT2scgWzK9BCnDEh+46z8EEwXBq24uRzd65I7pjtugzPSj921aM15RpESgzsSuA==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-object-super": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.2.0.tgz", - "integrity": "sha512-VMyhPYZISFZAqAPVkiYb7dUe2AsVi2/wCT5+wZdsNO31FojQJa9ns40hzZ6U9f50Jlq4w6qwzdBB2uwqZ00ebg==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.5.5.tgz", + "integrity": "sha512-un1zJQAhSosGFBduPgN/YFNvWVpRuHKU7IHBglLoLZsGmruJPOo6pbInneflUdmq7YvSVqhpPs5zdBvLnteltQ==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-replace-supers": "^7.1.0" + "@babel/helper-replace-supers": "^7.5.5" } }, "@babel/plugin-transform-parameters": { "version": "7.4.4", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.4.4.tgz", "integrity": "sha512-oMh5DUO1V63nZcu/ZVLQFqiihBGo4OpxJxR1otF50GMeCLiRx5nUdtokd+u9SuVJrvvuIh9OosRFPP4pIPnwmw==", + "dev": true, "requires": { "@babel/helper-call-delegate": "^7.4.4", "@babel/helper-get-function-arity": "^7.0.0", @@ -672,23 +684,16 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.2.0.tgz", "integrity": "sha512-9q7Dbk4RhgcLp8ebduOpCbtjh7C0itoLYHXd9ueASKAG/is5PQtMR5VJGka9NKqGhYEGn5ITahd4h9QeBMylWQ==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, - "@babel/plugin-transform-react-constant-elements": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.2.0.tgz", - "integrity": "sha512-YYQFg6giRFMsZPKUM9v+VcHOdfSQdz9jHCx3akAi3UYgyjndmdYGSXylQ/V+HswQt4fL8IklchD9HTsaOCrWQQ==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0" - } - }, "@babel/plugin-transform-react-display-name": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.2.0.tgz", "integrity": "sha512-Htf/tPa5haZvRMiNSQSFifK12gtr/8vwfr+A9y69uF0QcU77AVu4K7MiHEkTxF7lQoHOL0F9ErqgfNEAKgXj7A==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } @@ -697,6 +702,7 @@ "version": "7.3.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.3.0.tgz", "integrity": "sha512-a/+aRb7R06WcKvQLOu4/TpjKOdvVEKRLWFpKcNuHhiREPgGRB4TQJxq07+EZLS8LFVYpfq1a5lDUnuMdcCpBKg==", + "dev": true, "requires": { "@babel/helper-builder-react-jsx": "^7.3.0", "@babel/helper-plugin-utils": "^7.0.0", @@ -707,15 +713,17 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.2.0.tgz", "integrity": "sha512-v6S5L/myicZEy+jr6ielB0OR8h+EH/1QFx/YJ7c7Ua+7lqsjj/vW6fD5FR9hB/6y7mGbfT4vAURn3xqBxsUcdg==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/plugin-syntax-jsx": "^7.2.0" } }, "@babel/plugin-transform-react-jsx-source": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.2.0.tgz", - "integrity": "sha512-A32OkKTp4i5U6aE88GwwcuV4HAprUgHcTq0sSafLxjr6AW0QahrCRCjxogkbbcdtpbXkuTOlgpjophCxb6sh5g==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.5.0.tgz", + "integrity": "sha512-58Q+Jsy4IDCZx7kqEZuSDdam/1oW8OdDX8f+Loo6xyxdfg1yF0GE2XNJQSTZCaMol93+FBzpWiPEwtbMloAcPg==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/plugin-syntax-jsx": "^7.2.0" @@ -725,6 +733,7 @@ "version": "7.4.5", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.5.tgz", "integrity": "sha512-gBKRh5qAaCWntnd09S8QC7r3auLCqq5DI6O0DlfoyDjslSBVqBibrMdsqO+Uhmx3+BlOmE/Kw1HFxmGbv0N9dA==", + "dev": true, "requires": { "regenerator-transform": "^0.14.0" } @@ -733,33 +742,25 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.2.0.tgz", "integrity": "sha512-fz43fqW8E1tAB3DKF19/vxbpib1fuyCwSPE418ge5ZxILnBhWyhtPgz8eh1RCGGJlwvksHkyxMxh0eenFi+kFw==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, - "@babel/plugin-transform-runtime": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.4.3.tgz", - "integrity": "sha512-7Q61bU+uEI7bCUFReT1NKn7/X6sDQsZ7wL1sJ9IYMAO7cI+eg6x9re1cEw2fCRMbbTVyoeUKWSV1M6azEfKCfg==", - "requires": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0", - "resolve": "^1.8.1", - "semver": "^5.5.1" - } - }, "@babel/plugin-transform-shorthand-properties": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz", "integrity": "sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-spread": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.2.tgz", - "integrity": "sha512-KWfky/58vubwtS0hLqEnrWJjsMGaOeSBn90Ezn5Jeg9Z8KKHmELbP1yGylMlm5N6TPKeY9A2+UaSYLdxahg01w==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.6.2.tgz", + "integrity": "sha512-DpSvPFryKdK1x+EDJYCy28nmAaIMdxmhot62jAXF/o99iA33Zj2Lmcp3vDmz+MUh0LNYVPvfj5iC3feb3/+PFg==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } @@ -768,6 +769,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz", "integrity": "sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/helper-regex": "^7.0.0" @@ -777,6 +779,7 @@ "version": "7.4.4", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.4.4.tgz", "integrity": "sha512-mQrEC4TWkhLN0z8ygIvEL9ZEToPhG5K7KDW3pzGqOfIGZ28Jb0POUkeWcoz8HnHvhFy6dwAT1j8OzqN8s804+g==", + "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.0.0", "@babel/helper-plugin-utils": "^7.0.0" @@ -786,77 +789,84 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz", "integrity": "sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-typescript": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.4.5.tgz", - "integrity": "sha512-RPB/YeGr4ZrFKNwfuQRlMf2lxoCUaU01MTw39/OFE/RiL8HDjtn68BwEPft1P7JN4akyEmjGWAMNldOV7o9V2g==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.6.3.tgz", + "integrity": "sha512-aiWINBrPMSC3xTXRNM/dfmyYuPNKY/aexYqBgh0HBI5Y+WO5oRAqW/oROYeYHrF4Zw12r9rK4fMk/ZlAmqx/FQ==", + "dev": true, "requires": { + "@babel/helper-create-class-features-plugin": "^7.6.0", "@babel/helper-plugin-utils": "^7.0.0", "@babel/plugin-syntax-typescript": "^7.2.0" } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.4.4.tgz", - "integrity": "sha512-il+/XdNw01i93+M9J9u4T7/e/Ue/vWfNZE4IRUQjplu2Mqb/AFTDimkw2tdEdSH50wuQXZAbXSql0UphQke+vA==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.6.2.tgz", + "integrity": "sha512-orZI6cWlR3nk2YmYdb0gImrgCUwb5cBUwjf6Ks6dvNVvXERkwtJWOQaEOjPiu0Gu1Tq6Yq/hruCZZOOi9F34Dw==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/helper-regex": "^7.4.4", - "regexpu-core": "^4.5.4" + "regexpu-core": "^4.6.0" } }, "@babel/preset-env": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.4.5.tgz", - "integrity": "sha512-f2yNVXM+FsR5V8UwcFeIHzHWgnhXg3NpRmy0ADvALpnhB0SLbCvrCRr4BLOUYbQNLS+Z0Yer46x9dJXpXewI7w==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.6.3.tgz", + "integrity": "sha512-CWQkn7EVnwzlOdR5NOm2+pfgSNEZmvGjOhlCHBDq0J8/EStr+G+FvPEiz9B56dR6MoiUFjXhfE4hjLoAKKJtIQ==", + "dev": true, "requires": { "@babel/helper-module-imports": "^7.0.0", "@babel/helper-plugin-utils": "^7.0.0", "@babel/plugin-proposal-async-generator-functions": "^7.2.0", + "@babel/plugin-proposal-dynamic-import": "^7.5.0", "@babel/plugin-proposal-json-strings": "^7.2.0", - "@babel/plugin-proposal-object-rest-spread": "^7.4.4", + "@babel/plugin-proposal-object-rest-spread": "^7.6.2", "@babel/plugin-proposal-optional-catch-binding": "^7.2.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-proposal-unicode-property-regex": "^7.6.2", "@babel/plugin-syntax-async-generators": "^7.2.0", + "@babel/plugin-syntax-dynamic-import": "^7.2.0", "@babel/plugin-syntax-json-strings": "^7.2.0", "@babel/plugin-syntax-object-rest-spread": "^7.2.0", "@babel/plugin-syntax-optional-catch-binding": "^7.2.0", "@babel/plugin-transform-arrow-functions": "^7.2.0", - "@babel/plugin-transform-async-to-generator": "^7.4.4", + "@babel/plugin-transform-async-to-generator": "^7.5.0", "@babel/plugin-transform-block-scoped-functions": "^7.2.0", - "@babel/plugin-transform-block-scoping": "^7.4.4", - "@babel/plugin-transform-classes": "^7.4.4", + "@babel/plugin-transform-block-scoping": "^7.6.3", + "@babel/plugin-transform-classes": "^7.5.5", "@babel/plugin-transform-computed-properties": "^7.2.0", - "@babel/plugin-transform-destructuring": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/plugin-transform-duplicate-keys": "^7.2.0", + "@babel/plugin-transform-destructuring": "^7.6.0", + "@babel/plugin-transform-dotall-regex": "^7.6.2", + "@babel/plugin-transform-duplicate-keys": "^7.5.0", "@babel/plugin-transform-exponentiation-operator": "^7.2.0", "@babel/plugin-transform-for-of": "^7.4.4", "@babel/plugin-transform-function-name": "^7.4.4", "@babel/plugin-transform-literals": "^7.2.0", "@babel/plugin-transform-member-expression-literals": "^7.2.0", - "@babel/plugin-transform-modules-amd": "^7.2.0", - "@babel/plugin-transform-modules-commonjs": "^7.4.4", - "@babel/plugin-transform-modules-systemjs": "^7.4.4", + "@babel/plugin-transform-modules-amd": "^7.5.0", + "@babel/plugin-transform-modules-commonjs": "^7.6.0", + "@babel/plugin-transform-modules-systemjs": "^7.5.0", "@babel/plugin-transform-modules-umd": "^7.2.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.4.5", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.6.3", "@babel/plugin-transform-new-target": "^7.4.4", - "@babel/plugin-transform-object-super": "^7.2.0", + "@babel/plugin-transform-object-super": "^7.5.5", "@babel/plugin-transform-parameters": "^7.4.4", "@babel/plugin-transform-property-literals": "^7.2.0", "@babel/plugin-transform-regenerator": "^7.4.5", "@babel/plugin-transform-reserved-words": "^7.2.0", "@babel/plugin-transform-shorthand-properties": "^7.2.0", - "@babel/plugin-transform-spread": "^7.2.0", + "@babel/plugin-transform-spread": "^7.6.2", "@babel/plugin-transform-sticky-regex": "^7.2.0", "@babel/plugin-transform-template-literals": "^7.4.4", "@babel/plugin-transform-typeof-symbol": "^7.2.0", - "@babel/plugin-transform-unicode-regex": "^7.4.4", - "@babel/types": "^7.4.4", + "@babel/plugin-transform-unicode-regex": "^7.6.2", + "@babel/types": "^7.6.3", "browserslist": "^4.6.0", "core-js-compat": "^3.1.1", "invariant": "^2.2.2", @@ -865,9 +875,10 @@ } }, "@babel/preset-react": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.0.0.tgz", - "integrity": "sha512-oayxyPS4Zj+hF6Et11BwuBkmpgT/zMxyuZgFrMeZID6Hdh3dGlk4sHCAhdBCpuCKW2ppBfl2uCCetlrUIJRY3w==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.6.3.tgz", + "integrity": "sha512-07yQhmkZmRAfwREYIQgW0HEwMY9GBJVuPY4Q12UC72AbfaawuupVWa8zQs2tlL+yun45Nv/1KreII/0PLfEsgA==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/plugin-transform-react-display-name": "^7.0.0", @@ -877,719 +888,368 @@ } }, "@babel/preset-typescript": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.3.3.tgz", - "integrity": "sha512-mzMVuIP4lqtn4du2ynEfdO0+RYcslwrZiJHXu4MGaC1ctJiW2fyaeDrtjJGs7R/KebZ1sgowcIoWf4uRpEfKEg==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.6.0.tgz", + "integrity": "sha512-4xKw3tTcCm0qApyT6PqM9qniseCE79xGHiUnNdKGdxNsGUc2X7WwZybqIpnTmoukg3nhPceI5KPNzNqLNeIJww==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-transform-typescript": "^7.3.2" + "@babel/plugin-transform-typescript": "^7.6.0" } }, "@babel/runtime": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.5.tgz", - "integrity": "sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.3.tgz", + "integrity": "sha512-kq6anf9JGjW8Nt5rYfEuGRaEAaH1mkv3Bbu6rYvLOpPh/RusSJXuKPEAoZ7L7gybZkchE8+NV5g9vKF4AGAtsA==", + "requires": { + "regenerator-runtime": "^0.13.2" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", + "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==" + } + } + }, + "@babel/runtime-corejs3": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.7.4.tgz", + "integrity": "sha512-BBIEhzk8McXDcB3IbOi8zQPzzINUp4zcLesVlBSOcyGhzPUU8Xezk5GAG7Sy5GVhGmAO0zGd2qRSeY2g4Obqxw==", + "dev": true, "requires": { + "core-js-pure": "^3.0.0", "regenerator-runtime": "^0.13.2" }, "dependencies": { "regenerator-runtime": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz", - "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==" + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", + "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==", + "dev": true } } }, "@babel/template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.4.tgz", - "integrity": "sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.6.0.tgz", + "integrity": "sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ==", + "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.4.4", - "@babel/types": "^7.4.4" + "@babel/parser": "^7.6.0", + "@babel/types": "^7.6.0" } }, "@babel/traverse": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.5.tgz", - "integrity": "sha512-Vc+qjynwkjRmIFGxy0KYoPj4FdVDxLej89kMHFsWScq999uX+pwcX4v9mWRjW0KcAYTPAuVQl2LKP1wEVLsp+A==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.3.tgz", + "integrity": "sha512-unn7P4LGsijIxaAJo/wpoU11zN+2IaClkQAxcJWBNCMS6cmVh802IyLHNkAjQ0iYnRS3nnxk5O3fuXW28IMxTw==", + "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.4.4", + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.6.3", "@babel/helper-function-name": "^7.1.0", "@babel/helper-split-export-declaration": "^7.4.4", - "@babel/parser": "^7.4.5", - "@babel/types": "^7.4.4", + "@babel/parser": "^7.6.3", + "@babel/types": "^7.6.3", "debug": "^4.1.0", "globals": "^11.1.0", - "lodash": "^4.17.11" + "lodash": "^4.17.13" } }, "@babel/types": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.4.tgz", - "integrity": "sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.3.tgz", + "integrity": "sha512-CqbcpTxMcpuQTMhjI37ZHVgjBkysg5icREQIEZ0eG1yCNwg3oy+5AaLiOKmjsCj6nqOsa6Hf0ObjRVwokb7srA==", + "dev": true, "requires": { "esutils": "^2.0.2", - "lodash": "^4.17.11", + "lodash": "^4.17.13", "to-fast-properties": "^2.0.0" } }, - "@cnakazawa/watch": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.3.tgz", - "integrity": "sha512-r5160ogAvGyHsal38Kux7YYtodEKOj89RGb28ht1jh3SJb08VwRwAKKJL0bGb04Zd/3r9FL3BFIc3bBidYffCA==", - "requires": { - "exec-sh": "^0.3.2", - "minimist": "^1.2.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - } - } - }, "@csstools/convert-colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz", - "integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==" + "integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==", + "dev": true }, - "@csstools/normalize.css": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-9.0.1.tgz", - "integrity": "sha512-6It2EVfGskxZCQhuykrfnALg7oVeiI6KclWSmGDqB0AiInVrTGB9Jp9i4/Ad21u9Jde/voVQz6eFX/eSg/UsPA==" + "@types/eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", + "dev": true }, - "@hapi/address": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.0.0.tgz", - "integrity": "sha512-mV6T0IYqb0xL1UALPFplXYQmR0twnXG0M6jUswpquqT2sD12BOiCiLy3EvMp/Fy7s3DZElC4/aPjEjo2jeZpvw==" + "@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", + "dev": true }, - "@hapi/hoek": { - "version": "6.2.4", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-6.2.4.tgz", - "integrity": "sha512-HOJ20Kc93DkDVvjwHyHawPwPkX44sIrbXazAUDiUXaY2R9JwQGo2PhFfnQtdrsIe4igjG2fPgMra7NYw7qhy0A==" + "@types/glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", + "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "dev": true, + "requires": { + "@types/events": "*", + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/history": { + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.3.tgz", + "integrity": "sha512-cS5owqtwzLN5kY+l+KgKdRJ/Cee8tlmQoGQuIE9tWnSmS3JMKzmxo2HIAk2wODMifGwO20d62xZQLYz+RLfXmw==" }, - "@hapi/joi": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-15.1.0.tgz", - "integrity": "sha512-n6kaRQO8S+kepUTbXL9O/UOL788Odqs38/VOfoCrATDtTvyfiO3fgjlSRaNkHabpTLgM7qru9ifqXlXbXk8SeQ==", + "@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", "requires": { - "@hapi/address": "2.x.x", - "@hapi/hoek": "6.x.x", - "@hapi/marker": "1.x.x", - "@hapi/topo": "3.x.x" + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" } }, - "@hapi/marker": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@hapi/marker/-/marker-1.0.0.tgz", - "integrity": "sha512-JOfdekTXnJexfE8PyhZFyHvHjt81rBFSAbTIRAhF2vv/2Y1JzoKsGqxH/GpZJoF7aEfYok8JVcAHmSz1gkBieA==" + "@types/json-schema": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.3.tgz", + "integrity": "sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==", + "dev": true }, - "@hapi/topo": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.0.tgz", - "integrity": "sha512-gZDI/eXOIk8kP2PkUKjWu9RW8GGVd2Hkgjxyr/S7Z+JF+0mr7bAlbw+DkTRxnD580o8Kqxlnba9wvqp5aOHBww==", + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, + "@types/node": { + "version": "12.12.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.3.tgz", + "integrity": "sha512-opgSsy+cEF9N8MgaVPnWVtdJ3o4mV2aMHvDq7thkQUFt0EuOHJon4rQpJfhjmNHB+ikl0Cd6WhWIErOyQ+f7tw==", + "dev": true + }, + "@types/prop-types": { + "version": "15.7.3", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", + "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" + }, + "@types/q": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", + "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==", + "dev": true + }, + "@types/react": { + "version": "16.9.11", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.11.tgz", + "integrity": "sha512-UBT4GZ3PokTXSWmdgC/GeCGEJXE5ofWyibCcecRLUVN2ZBpXQGVgQGtG2foS7CrTKFKlQVVswLvf7Js6XA/CVQ==", "requires": { - "@hapi/hoek": "6.x.x" + "@types/prop-types": "*", + "csstype": "^2.2.0" } }, - "@jest/console": { - "version": "24.7.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.7.1.tgz", - "integrity": "sha512-iNhtIy2M8bXlAOULWVTUxmnelTLFneTNEkHCgPmgd+zNwy9zVddJ6oS5rZ9iwoscNdT5mMwUd0C51v/fSlzItg==", + "@types/react-dom": { + "version": "16.9.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.3.tgz", + "integrity": "sha512-FUuZKXPr9qlzUT9lhuzrZgLjH63TvNn28Ch3MvKG4B+F52zQtO8DtE0Opbncy3xaucNZM2WIPfuNTgkbKx5Brg==", "requires": { - "@jest/source-map": "^24.3.0", - "chalk": "^2.0.1", - "slash": "^2.0.0" + "@types/react": "*" } }, - "@jest/core": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-24.8.0.tgz", - "integrity": "sha512-R9rhAJwCBQzaRnrRgAdVfnglUuATXdwTRsYqs6NMdVcAl5euG8LtWDe+fVkN27YfKVBW61IojVsXKaOmSnqd/A==", + "@types/react-redux": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.5.tgz", + "integrity": "sha512-ZoNGQMDxh5ENY7PzU7MVonxDzS1l/EWiy8nUhDqxFqUZn4ovboCyvk4Djf68x6COb7vhGTKjyjxHxtFdAA5sUA==", "requires": { - "@jest/console": "^24.7.1", - "@jest/reporters": "^24.8.0", - "@jest/test-result": "^24.8.0", - "@jest/transform": "^24.8.0", - "@jest/types": "^24.8.0", - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.1", - "exit": "^0.1.2", - "graceful-fs": "^4.1.15", - "jest-changed-files": "^24.8.0", - "jest-config": "^24.8.0", - "jest-haste-map": "^24.8.0", - "jest-message-util": "^24.8.0", - "jest-regex-util": "^24.3.0", - "jest-resolve-dependencies": "^24.8.0", - "jest-runner": "^24.8.0", - "jest-runtime": "^24.8.0", - "jest-snapshot": "^24.8.0", - "jest-util": "^24.8.0", - "jest-validate": "^24.8.0", - "jest-watcher": "^24.8.0", - "micromatch": "^3.1.10", - "p-each-series": "^1.0.0", - "pirates": "^4.0.1", - "realpath-native": "^1.1.0", - "rimraf": "^2.5.4", - "strip-ansi": "^5.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "requires": { - "ansi-regex": "^4.1.0" - } - } + "@types/hoist-non-react-statics": "^3.3.0", + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0", + "redux": "^4.0.0" } }, - "@jest/environment": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-24.8.0.tgz", - "integrity": "sha512-vlGt2HLg7qM+vtBrSkjDxk9K0YtRBi7HfRFaDxoRtyi+DyVChzhF20duvpdAnKVBV6W5tym8jm0U9EfXbDk1tw==", + "@types/react-router": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.2.tgz", + "integrity": "sha512-euC3SiwDg3NcjFdNmFL8uVuAFTpZJm0WMFUw+4eXMUnxa7M9RGFEG0szt0z+/Zgk4G2k9JBFhaEnY64RBiFmuw==", "requires": { - "@jest/fake-timers": "^24.8.0", - "@jest/transform": "^24.8.0", - "@jest/types": "^24.8.0", - "jest-mock": "^24.8.0" + "@types/history": "*", + "@types/react": "*" } }, - "@jest/fake-timers": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-24.8.0.tgz", - "integrity": "sha512-2M4d5MufVXwi6VzZhJ9f5S/wU4ud2ck0kxPof1Iz3zWx6Y+V2eJrES9jEktB6O3o/oEyk+il/uNu9PvASjWXQw==", + "@types/react-router-dom": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.1.1.tgz", + "integrity": "sha512-yXqWGaehta/cdmjvEQfCbHFX6l1c7QHuE5n2OfhcJ33ufbt55xhAKqQ0BmT24YM3s7OKwrrUUgY3FaSzO7be3Q==", "requires": { - "@jest/types": "^24.8.0", - "jest-message-util": "^24.8.0", - "jest-mock": "^24.8.0" + "@types/history": "*", + "@types/react": "*", + "@types/react-router": "*" } }, - "@jest/reporters": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-24.8.0.tgz", - "integrity": "sha512-eZ9TyUYpyIIXfYCrw0UHUWUvE35vx5I92HGMgS93Pv7du+GHIzl+/vh8Qj9MCWFK/4TqyttVBPakWMOfZRIfxw==", + "@types/react-share": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/react-share/-/react-share-3.0.1.tgz", + "integrity": "sha512-9SmC9TBOBKXvu7Otfl0Zl/1OqjPocmhEMwJnVspG3i1AW/wyKkemkL+Jshpp+tSxfcz76BbbzlBJVbsojYZHvA==", "requires": { - "@jest/environment": "^24.8.0", - "@jest/test-result": "^24.8.0", - "@jest/transform": "^24.8.0", - "@jest/types": "^24.8.0", - "chalk": "^2.0.1", - "exit": "^0.1.2", - "glob": "^7.1.2", - "istanbul-lib-coverage": "^2.0.2", - "istanbul-lib-instrument": "^3.0.1", - "istanbul-lib-report": "^2.0.4", - "istanbul-lib-source-maps": "^3.0.1", - "istanbul-reports": "^2.1.1", - "jest-haste-map": "^24.8.0", - "jest-resolve": "^24.8.0", - "jest-runtime": "^24.8.0", - "jest-util": "^24.8.0", - "jest-worker": "^24.6.0", - "node-notifier": "^5.2.1", - "slash": "^2.0.0", - "source-map": "^0.6.0", - "string-length": "^2.0.0" - }, - "dependencies": { - "jest-resolve": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-24.8.0.tgz", - "integrity": "sha512-+hjSzi1PoRvnuOICoYd5V/KpIQmkAsfjFO71458hQ2Whi/yf1GDeBOFj8Gxw4LrApHsVJvn5fmjcPdmoUHaVKw==", - "requires": { - "@jest/types": "^24.8.0", - "browser-resolve": "^1.11.3", - "chalk": "^2.0.1", - "jest-pnp-resolver": "^1.2.1", - "realpath-native": "^1.1.0" - } - } + "@types/react": "*" + } + }, + "@types/react-slick": { + "version": "0.23.4", + "resolved": "https://registry.npmjs.org/@types/react-slick/-/react-slick-0.23.4.tgz", + "integrity": "sha512-vXoIy4GUfB7/YgqubR4H7RALo+pRdMYCeLgWwV3MPwl5pggTlEkFBTF19R7u+LJc85uMqC7RfsbkqPLMQ4ab+A==", + "requires": { + "@types/react": "*" } }, - "@jest/source-map": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-24.3.0.tgz", - "integrity": "sha512-zALZt1t2ou8le/crCeeiRYzvdnTzaIlpOWaet45lNSqNJUnXbppUUFR4ZUAlzgDmKee4Q5P/tKXypI1RiHwgag==", + "@types/redux-logger": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/redux-logger/-/redux-logger-3.0.7.tgz", + "integrity": "sha512-oV9qiCuowhVR/ehqUobWWkXJjohontbDGLV88Be/7T4bqMQ3kjXwkFNL7doIIqlbg3X2PC5WPziZ8/j/QHNQ4A==", "requires": { - "callsites": "^3.0.0", - "graceful-fs": "^4.1.15", - "source-map": "^0.6.0" + "redux": "^3.6.0" }, "dependencies": { - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + "redux": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz", + "integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==", + "requires": { + "lodash": "^4.2.1", + "lodash-es": "^4.2.1", + "loose-envify": "^1.1.0", + "symbol-observable": "^1.0.3" + } } } }, - "@jest/test-result": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-24.8.0.tgz", - "integrity": "sha512-+YdLlxwizlfqkFDh7Mc7ONPQAhA4YylU1s529vVM1rsf67vGZH/2GGm5uO8QzPeVyaVMobCQ7FTxl38QrKRlng==", + "@typescript-eslint/eslint-plugin": { + "version": "2.19.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.19.2.tgz", + "integrity": "sha512-HX2qOq2GOV04HNrmKnTpSIpHjfl7iwdXe3u/Nvt+/cpmdvzYvY0NHSiTkYN257jHnq4OM/yo+OsFgati+7LqJA==", + "dev": true, "requires": { - "@jest/console": "^24.7.1", - "@jest/types": "^24.8.0", - "@types/istanbul-lib-coverage": "^2.0.0" + "@typescript-eslint/experimental-utils": "2.19.2", + "eslint-utils": "^1.4.3", + "functional-red-black-tree": "^1.0.1", + "regexpp": "^3.0.0", + "tsutils": "^3.17.1" } }, - "@jest/test-sequencer": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-24.8.0.tgz", - "integrity": "sha512-OzL/2yHyPdCHXEzhoBuq37CE99nkme15eHkAzXRVqthreWZamEMA0WoetwstsQBCXABhczpK03JNbc4L01vvLg==", + "@typescript-eslint/experimental-utils": { + "version": "2.19.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.19.2.tgz", + "integrity": "sha512-B88QuwT1wMJR750YvTJBNjMZwmiPpbmKYLm1yI7PCc3x0NariqPwqaPsoJRwU9DmUi0cd9dkhz1IqEnwfD+P1A==", + "dev": true, "requires": { - "@jest/test-result": "^24.8.0", - "jest-haste-map": "^24.8.0", - "jest-runner": "^24.8.0", - "jest-runtime": "^24.8.0" + "@types/json-schema": "^7.0.3", + "@typescript-eslint/typescript-estree": "2.19.2", + "eslint-scope": "^5.0.0" + }, + "dependencies": { + "eslint-scope": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", + "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + } } }, - "@jest/transform": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-24.8.0.tgz", - "integrity": "sha512-xBMfFUP7TortCs0O+Xtez2W7Zu1PLH9bvJgtraN1CDST6LBM/eTOZ9SfwS/lvV8yOfcDpFmwf9bq5cYbXvqsvA==", + "@typescript-eslint/parser": { + "version": "2.19.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.19.2.tgz", + "integrity": "sha512-8uwnYGKqX9wWHGPGdLB9sk9+12sjcdqEEYKGgbS8A0IvYX59h01o8os5qXUHMq2na8vpDRaV0suTLM7S8wraTA==", + "dev": true, "requires": { - "@babel/core": "^7.1.0", - "@jest/types": "^24.8.0", - "babel-plugin-istanbul": "^5.1.0", - "chalk": "^2.0.1", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.1.15", - "jest-haste-map": "^24.8.0", - "jest-regex-util": "^24.3.0", - "jest-util": "^24.8.0", - "micromatch": "^3.1.10", - "realpath-native": "^1.1.0", - "slash": "^2.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "2.4.1" + "@types/eslint-visitor-keys": "^1.0.0", + "@typescript-eslint/experimental-utils": "2.19.2", + "@typescript-eslint/typescript-estree": "2.19.2", + "eslint-visitor-keys": "^1.1.0" } }, - "@jest/types": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.8.0.tgz", - "integrity": "sha512-g17UxVr2YfBtaMUxn9u/4+siG1ptg9IGYAYwvpwn61nBg779RXnjE/m7CxYcIzEt0AbHZZAHSEZNhkE2WxURVg==", + "@typescript-eslint/typescript-estree": { + "version": "2.19.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.19.2.tgz", + "integrity": "sha512-Xu/qa0MDk6upQWqE4Qy2X16Xg8Vi32tQS2PR0AvnT/ZYS4YGDvtn2MStOh5y8Zy2mg4NuL06KUHlvCh95j9C6Q==", + "dev": true, "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^12.0.9" + "debug": "^4.1.1", + "eslint-visitor-keys": "^1.1.0", + "glob": "^7.1.6", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^6.3.0", + "tsutils": "^3.17.1" + }, + "dependencies": { + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, - "@mrmlnc/readdir-enhanced": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", - "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", + "@webassemblyjs/ast": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", + "integrity": "sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ==", + "dev": true, "requires": { - "call-me-maybe": "^1.0.1", - "glob-to-regexp": "^0.3.0" + "@webassemblyjs/helper-module-context": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/wast-parser": "1.8.5" } }, - "@nodelib/fs.stat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", - "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==" - }, - "@svgr/babel-plugin-add-jsx-attribute": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz", - "integrity": "sha512-j7KnilGyZzYr/jhcrSYS3FGWMZVaqyCG0vzMCwzvei0coIkczuYMcniK07nI0aHJINciujjH11T72ICW5eL5Ig==" - }, - "@svgr/babel-plugin-remove-jsx-attribute": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-4.2.0.tgz", - "integrity": "sha512-3XHLtJ+HbRCH4n28S7y/yZoEQnRpl0tvTZQsHqvaeNXPra+6vE5tbRliH3ox1yZYPCxrlqaJT/Mg+75GpDKlvQ==" - }, - "@svgr/babel-plugin-remove-jsx-empty-expression": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-4.2.0.tgz", - "integrity": "sha512-yTr2iLdf6oEuUE9MsRdvt0NmdpMBAkgK8Bjhl6epb+eQWk6abBaX3d65UZ3E3FWaOwePyUgNyNCMVG61gGCQ7w==" - }, - "@svgr/babel-plugin-replace-jsx-attribute-value": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-4.2.0.tgz", - "integrity": "sha512-U9m870Kqm0ko8beHawRXLGLvSi/ZMrl89gJ5BNcT452fAjtF2p4uRzXkdzvGJJJYBgx7BmqlDjBN/eCp5AAX2w==" - }, - "@svgr/babel-plugin-svg-dynamic-title": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-4.3.0.tgz", - "integrity": "sha512-3eI17Pb3jlg3oqV4Tie069n1SelYKBUpI90txDcnBWk4EGFW+YQGyQjy6iuJAReH0RnpUJ9jUExrt/xniGvhqw==" - }, - "@svgr/babel-plugin-svg-em-dimensions": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-4.2.0.tgz", - "integrity": "sha512-C0Uy+BHolCHGOZ8Dnr1zXy/KgpBOkEUYY9kI/HseHVPeMbluaX3CijJr7D4C5uR8zrc1T64nnq/k63ydQuGt4w==" - }, - "@svgr/babel-plugin-transform-react-native-svg": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-4.2.0.tgz", - "integrity": "sha512-7YvynOpZDpCOUoIVlaaOUU87J4Z6RdD6spYN4eUb5tfPoKGSF9OG2NuhgYnq4jSkAxcpMaXWPf1cePkzmqTPNw==" - }, - "@svgr/babel-plugin-transform-svg-component": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-4.2.0.tgz", - "integrity": "sha512-hYfYuZhQPCBVotABsXKSCfel2slf/yvJY8heTVX1PCTaq/IgASq1IyxPPKJ0chWREEKewIU/JMSsIGBtK1KKxw==" - }, - "@svgr/babel-preset": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-4.3.0.tgz", - "integrity": "sha512-Lgy1RJiZumGtv6yJroOxzFuL64kG/eIcivJQ7y9ljVWL+0QXvFz4ix1xMrmjMD+rpJWwj50ayCIcFelevG/XXg==", - "requires": { - "@svgr/babel-plugin-add-jsx-attribute": "^4.2.0", - "@svgr/babel-plugin-remove-jsx-attribute": "^4.2.0", - "@svgr/babel-plugin-remove-jsx-empty-expression": "^4.2.0", - "@svgr/babel-plugin-replace-jsx-attribute-value": "^4.2.0", - "@svgr/babel-plugin-svg-dynamic-title": "^4.3.0", - "@svgr/babel-plugin-svg-em-dimensions": "^4.2.0", - "@svgr/babel-plugin-transform-react-native-svg": "^4.2.0", - "@svgr/babel-plugin-transform-svg-component": "^4.2.0" - } - }, - "@svgr/core": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@svgr/core/-/core-4.3.0.tgz", - "integrity": "sha512-Ycu1qrF5opBgKXI0eQg3ROzupalCZnSDETKCK/3MKN4/9IEmt3jPX/bbBjftklnRW+qqsCEpO0y/X9BTRw2WBg==", - "requires": { - "@svgr/plugin-jsx": "^4.3.0", - "camelcase": "^5.3.1", - "cosmiconfig": "^5.2.0" - } - }, - "@svgr/hast-util-to-babel-ast": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-4.2.0.tgz", - "integrity": "sha512-IvAeb7gqrGB5TH9EGyBsPrMRH/QCzIuAkLySKvH2TLfLb2uqk98qtJamordRQTpHH3e6TORfBXoTo7L7Opo/Ow==", - "requires": { - "@babel/types": "^7.4.0" - } - }, - "@svgr/plugin-jsx": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-4.3.0.tgz", - "integrity": "sha512-0ab8zJdSOTqPfjZtl89cjq2IOmXXUYV3Fs7grLT9ur1Al3+x3DSp2+/obrYKUGbQUnLq96RMjSZ7Icd+13vwlQ==", - "requires": { - "@babel/core": "^7.4.3", - "@svgr/babel-preset": "^4.3.0", - "@svgr/hast-util-to-babel-ast": "^4.2.0", - "rehype-parse": "^6.0.0", - "unified": "^7.1.0", - "vfile": "^4.0.0" - } - }, - "@svgr/plugin-svgo": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-4.2.0.tgz", - "integrity": "sha512-zUEKgkT172YzHh3mb2B2q92xCnOAMVjRx+o0waZ1U50XqKLrVQ/8dDqTAtnmapdLsGurv8PSwenjLCUpj6hcvw==", - "requires": { - "cosmiconfig": "^5.2.0", - "merge-deep": "^3.0.2", - "svgo": "^1.2.1" - } - }, - "@svgr/webpack": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-4.1.0.tgz", - "integrity": "sha512-d09ehQWqLMywP/PT/5JvXwPskPK9QCXUjiSkAHehreB381qExXf5JFCBWhfEyNonRbkIneCeYM99w+Ud48YIQQ==", - "requires": { - "@babel/core": "^7.1.6", - "@babel/plugin-transform-react-constant-elements": "^7.0.0", - "@babel/preset-env": "^7.1.6", - "@babel/preset-react": "^7.0.0", - "@svgr/core": "^4.1.0", - "@svgr/plugin-jsx": "^4.1.0", - "@svgr/plugin-svgo": "^4.0.3", - "loader-utils": "^1.1.0" - } - }, - "@types/babel__core": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.2.tgz", - "integrity": "sha512-cfCCrFmiGY/yq0NuKNxIQvZFy9kY/1immpSpTngOnyIbD4+eJOG5mxphhHDv3CHL9GltO4GcKr54kGBg3RNdbg==", - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.0.2.tgz", - "integrity": "sha512-NHcOfab3Zw4q5sEE2COkpfXjoE7o+PmqD9DQW4koUT3roNxwziUdXGnRndMat/LJNUtePwn1TlP4do3uoe3KZQ==", - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.2.tgz", - "integrity": "sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==", - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.7.tgz", - "integrity": "sha512-CeBpmX1J8kWLcDEnI3Cl2Eo6RfbGvzUctA+CjZUhOKDFbLfcr7fc4usEqLNWetrlJd7RhAkyYe2czXop4fICpw==", - "requires": { - "@babel/types": "^7.3.0" - } - }, - "@types/history": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.2.tgz", - "integrity": "sha512-ui3WwXmjTaY73fOQ3/m3nnajU/Orhi6cEu5rzX+BrAAJxa3eITXZ5ch9suPqtM03OWhAHhPSyBGCN4UKoxO20Q==" - }, - "@types/hoist-non-react-statics": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", - "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", - "requires": { - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0" - } - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", - "integrity": "sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==" - }, - "@types/istanbul-lib-report": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz", - "integrity": "sha512-3BUTyMzbZa2DtDI2BkERNC6jJw2Mr2Y0oGI7mRxYNBPxppbtEK1F66u3bKwU2g+wxwWI7PAoRpJnOY1grJqzHg==", - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz", - "integrity": "sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA==", - "requires": { - "@types/istanbul-lib-coverage": "*", - "@types/istanbul-lib-report": "*" - } - }, - "@types/jest": { - "version": "24.0.13", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-24.0.13.tgz", - "integrity": "sha512-3m6RPnO35r7Dg+uMLj1+xfZaOgIHHHut61djNjzwExXN4/Pm9has9C6I1KMYSfz7mahDhWUOVg4HW/nZdv5Pww==", - "requires": { - "@types/jest-diff": "*" - } - }, - "@types/jest-diff": { - "version": "20.0.1", - "resolved": "https://registry.npmjs.org/@types/jest-diff/-/jest-diff-20.0.1.tgz", - "integrity": "sha512-yALhelO3i0hqZwhjtcr6dYyaLoCHbAMshwtj6cGxTvHZAKXHsYGdff6E8EPw3xLKY0ELUTQ69Q1rQiJENnccMA==" - }, - "@types/node": { - "version": "12.0.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.3.tgz", - "integrity": "sha512-zkOxCS/fA+3SsdA+9Yun0iANxzhQRiNwTvJSr6N95JhuJ/x27z9G2URx1Jpt3zYFfCGUXZGL5UDxt5eyLE7wgw==" - }, - "@types/prop-types": { - "version": "15.7.1", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.1.tgz", - "integrity": "sha512-CFzn9idOEpHrgdw8JsoTkaDDyRWk1jrzIV8djzcgpq0y9tG4B4lFT+Nxh52DVpDXV+n4+NPNv7M1Dj5uMp6XFg==" - }, - "@types/q": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", - "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==" - }, - "@types/react": { - "version": "16.8.19", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.8.19.tgz", - "integrity": "sha512-QzEzjrd1zFzY9cDlbIiFvdr+YUmefuuRYrPxmkwG0UQv5XF35gFIi7a95m1bNVcFU0VimxSZ5QVGSiBmlggQXQ==", - "requires": { - "@types/prop-types": "*", - "csstype": "^2.2.0" - } - }, - "@types/react-dom": { - "version": "16.8.4", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.8.4.tgz", - "integrity": "sha512-eIRpEW73DCzPIMaNBDP5pPIpK1KXyZwNgfxiVagb5iGiz6da+9A5hslSX6GAQKdO7SayVCS/Fr2kjqprgAvkfA==", - "requires": { - "@types/react": "*" - } - }, - "@types/react-redux": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.1.tgz", - "integrity": "sha512-owqNahzE8en/jR4NtrUJDJya3tKru7CIEGSRL/pVS84LtSCdSoT7qZTkrbBd3S4Lp11sAp+7LsvxIeONJVKMnw==", - "requires": { - "@types/hoist-non-react-statics": "^3.3.0", - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0", - "redux": "^4.0.0" - } - }, - "@types/react-router": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.0.2.tgz", - "integrity": "sha512-sdMN284GEOcqDEMS/hE/XD06Abw2fws30+xkZf3C9cSRcWopiv/HDTmunYI7DKLYKVRaWFkq1lkuJ6qeYu0E7A==", - "requires": { - "@types/history": "*", - "@types/react": "*" - } - }, - "@types/react-router-dom": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-4.3.4.tgz", - "integrity": "sha512-xrwaWHpnxKk/TTRe7pmoGy3E4SyF/ojFqNfFJacw7OLdfLXRvGfk4r/XePVaZNVfeJzL8fcnNilPN7xOdJ/vGw==", - "requires": { - "@types/history": "*", - "@types/react": "*", - "@types/react-router": "*" - } - }, - "@types/react-slick": { - "version": "0.23.4", - "resolved": "https://registry.npmjs.org/@types/react-slick/-/react-slick-0.23.4.tgz", - "integrity": "sha512-vXoIy4GUfB7/YgqubR4H7RALo+pRdMYCeLgWwV3MPwl5pggTlEkFBTF19R7u+LJc85uMqC7RfsbkqPLMQ4ab+A==", - "requires": { - "@types/react": "*" - } - }, - "@types/redux-logger": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/redux-logger/-/redux-logger-3.0.7.tgz", - "integrity": "sha512-oV9qiCuowhVR/ehqUobWWkXJjohontbDGLV88Be/7T4bqMQ3kjXwkFNL7doIIqlbg3X2PC5WPziZ8/j/QHNQ4A==", - "requires": { - "redux": "^3.6.0" - }, - "dependencies": { - "redux": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz", - "integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==", - "requires": { - "lodash": "^4.2.1", - "lodash-es": "^4.2.1", - "loose-envify": "^1.1.0", - "symbol-observable": "^1.0.3" - } - } - } - }, - "@types/stack-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", - "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==" - }, - "@types/unist": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz", - "integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==" - }, - "@types/vfile": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/vfile/-/vfile-3.0.2.tgz", - "integrity": "sha512-b3nLFGaGkJ9rzOcuXRfHkZMdjsawuDD0ENL9fzTophtBg8FJHSGbH7daXkEpcwy3v7Xol3pAvsmlYyFhR4pqJw==", - "requires": { - "@types/node": "*", - "@types/unist": "*", - "@types/vfile-message": "*" - } - }, - "@types/vfile-message": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/vfile-message/-/vfile-message-1.0.1.tgz", - "integrity": "sha512-mlGER3Aqmq7bqR1tTTIVHq8KSAFFRyGbrxuM8C/H82g6k7r2fS+IMEkIu3D7JHzG10NvPdR8DNx0jr0pwpp4dA==", - "requires": { - "@types/node": "*", - "@types/unist": "*" - } - }, - "@types/yargs": { - "version": "12.0.12", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-12.0.12.tgz", - "integrity": "sha512-SOhuU4wNBxhhTHxYaiG5NY4HBhDIDnJF60GU+2LqHAdKKer86//e4yg69aENCtQ04n0ovz+tq2YPME5t5yp4pw==" - }, - "@typescript-eslint/eslint-plugin": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-1.6.0.tgz", - "integrity": "sha512-U224c29E2lo861TQZs6GSmyC0OYeRNg6bE9UVIiFBxN2MlA0nq2dCrgIVyyRbC05UOcrgf2Wk/CF2gGOPQKUSQ==", - "requires": { - "@typescript-eslint/parser": "1.6.0", - "@typescript-eslint/typescript-estree": "1.6.0", - "requireindex": "^1.2.0", - "tsutils": "^3.7.0" - } - }, - "@typescript-eslint/parser": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-1.6.0.tgz", - "integrity": "sha512-VB9xmSbfafI+/kI4gUK3PfrkGmrJQfh0N4EScT1gZXSZyUxpsBirPL99EWZg9MmPG0pzq/gMtgkk7/rAHj4aQw==", - "requires": { - "@typescript-eslint/typescript-estree": "1.6.0", - "eslint-scope": "^4.0.0", - "eslint-visitor-keys": "^1.0.0" - } - }, - "@typescript-eslint/typescript-estree": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.6.0.tgz", - "integrity": "sha512-A4CanUwfaG4oXobD5y7EXbsOHjCwn8tj1RDd820etpPAjH+Icjc2K9e/DQM1Hac5zH2BSy+u6bjvvF2wwREvYA==", - "requires": { - "lodash.unescape": "4.0.1", - "semver": "5.5.0" - }, - "dependencies": { - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" - } - } - }, - "@webassemblyjs/ast": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", - "integrity": "sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ==", - "requires": { - "@webassemblyjs/helper-module-context": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/wast-parser": "1.8.5" - } - }, - "@webassemblyjs/floating-point-hex-parser": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz", - "integrity": "sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ==" + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz", + "integrity": "sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ==", + "dev": true }, "@webassemblyjs/helper-api-error": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz", - "integrity": "sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA==" + "integrity": "sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA==", + "dev": true }, "@webassemblyjs/helper-buffer": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz", - "integrity": "sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q==" + "integrity": "sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q==", + "dev": true }, "@webassemblyjs/helper-code-frame": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz", "integrity": "sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ==", + "dev": true, "requires": { "@webassemblyjs/wast-printer": "1.8.5" } @@ -1597,12 +1257,14 @@ "@webassemblyjs/helper-fsm": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz", - "integrity": "sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow==" + "integrity": "sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow==", + "dev": true }, "@webassemblyjs/helper-module-context": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz", "integrity": "sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g==", + "dev": true, "requires": { "@webassemblyjs/ast": "1.8.5", "mamacro": "^0.0.3" @@ -1611,12 +1273,14 @@ "@webassemblyjs/helper-wasm-bytecode": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz", - "integrity": "sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ==" + "integrity": "sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ==", + "dev": true }, "@webassemblyjs/helper-wasm-section": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz", "integrity": "sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA==", + "dev": true, "requires": { "@webassemblyjs/ast": "1.8.5", "@webassemblyjs/helper-buffer": "1.8.5", @@ -1628,6 +1292,7 @@ "version": "1.8.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz", "integrity": "sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g==", + "dev": true, "requires": { "@xtuc/ieee754": "^1.2.0" } @@ -1636,6 +1301,7 @@ "version": "1.8.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.8.5.tgz", "integrity": "sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A==", + "dev": true, "requires": { "@xtuc/long": "4.2.2" } @@ -1643,12 +1309,14 @@ "@webassemblyjs/utf8": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.8.5.tgz", - "integrity": "sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw==" + "integrity": "sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw==", + "dev": true }, "@webassemblyjs/wasm-edit": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz", "integrity": "sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q==", + "dev": true, "requires": { "@webassemblyjs/ast": "1.8.5", "@webassemblyjs/helper-buffer": "1.8.5", @@ -1664,6 +1332,7 @@ "version": "1.8.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz", "integrity": "sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg==", + "dev": true, "requires": { "@webassemblyjs/ast": "1.8.5", "@webassemblyjs/helper-wasm-bytecode": "1.8.5", @@ -1676,6 +1345,7 @@ "version": "1.8.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz", "integrity": "sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q==", + "dev": true, "requires": { "@webassemblyjs/ast": "1.8.5", "@webassemblyjs/helper-buffer": "1.8.5", @@ -1687,6 +1357,7 @@ "version": "1.8.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz", "integrity": "sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw==", + "dev": true, "requires": { "@webassemblyjs/ast": "1.8.5", "@webassemblyjs/helper-api-error": "1.8.5", @@ -1700,6 +1371,7 @@ "version": "1.8.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz", "integrity": "sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg==", + "dev": true, "requires": { "@webassemblyjs/ast": "1.8.5", "@webassemblyjs/floating-point-hex-parser": "1.8.5", @@ -1713,6 +1385,7 @@ "version": "1.8.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz", "integrity": "sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg==", + "dev": true, "requires": { "@webassemblyjs/ast": "1.8.5", "@webassemblyjs/wast-parser": "1.8.5", @@ -1722,60 +1395,42 @@ "@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" + "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==" - }, - "abab": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", - "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==" + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true }, "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dev": true, "requires": { "mime-types": "~2.1.24", "negotiator": "0.6.2" } }, "acorn": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", - "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==" - }, - "acorn-dynamic-import": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz", - "integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==" - }, - "acorn-globals": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.2.tgz", - "integrity": "sha512-BbzvZhVtZP+Bs1J1HcwrQe8ycfO0wStkSGxuul3He3GkHOIZ6eTqOkPuw9IP1X3+IkOo4wiJmwkobzXYz4wewQ==", - "requires": { - "acorn": "^6.0.1", - "acorn-walk": "^6.0.1" - } + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz", + "integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==", + "dev": true }, "acorn-jsx": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", - "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==" - }, - "acorn-walk": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.1.tgz", - "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==" + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.1.0.tgz", + "integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==", + "dev": true }, "add-dom-event-listener": { "version": "1.1.0", @@ -1785,15 +1440,11 @@ "object-assign": "4.x" } }, - "address": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/address/-/address-1.0.3.tgz", - "integrity": "sha512-z55ocwKBRLryBs394Sm3ushTtBeg6VAeuku7utSoSnsJKvKcnXFIyC6vh27n3rXyxSgkJBBCAvyOn7gSUcTYjg==" - }, "ajv": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", - "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "dev": true, "requires": { "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", @@ -1804,58 +1455,64 @@ "ajv-errors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", - "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==" + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", + "dev": true }, "ajv-keywords": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.0.tgz", - "integrity": "sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw==" - }, - "alphanum-sort": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", - "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=" + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", + "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", + "dev": true }, "amdefine": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true }, "ansi-colors": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", - "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==" + "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", + "dev": true }, "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==" + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.0.tgz", + "integrity": "sha512-EiYhwo0v255HUL6eDyuLrXEkTi7WwVCLAw+SeOQ7M7qdun1z1pum4DEm/nuqIVbPvi9RPPc9k9LbyBv6H0DwVg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } }, "ansi-html": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", - "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=" + "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", + "dev": true }, "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "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" } }, "antd": { - "version": "3.19.8", - "resolved": "https://registry.npmjs.org/antd/-/antd-3.19.8.tgz", - "integrity": "sha512-sGBJHuzr/7Vm3VJpzt5vjOWjtth8Ld38FneIQeX5OKHudWYH8j+Xt0j4q2sYwOiV9lYFuh4tlZv7/7NFyCO2vw==", + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/antd/-/antd-3.25.2.tgz", + "integrity": "sha512-+qF1bgU7rUkPIkggIIV0fmm+9pPacl50BBd6NNUR2+kKJOFYjwrnP39ZqJRsYNy5bX9VgR454fz9KEuW7HPjog==", "requires": { "@ant-design/create-react-context": "^0.2.4", - "@ant-design/icons": "~2.0.0", + "@ant-design/icons": "~2.1.1", "@ant-design/icons-react": "~2.0.1", "@types/react-slick": "^0.23.4", "array-tree-filter": "^2.1.0", @@ -1865,44 +1522,45 @@ "css-animation": "^1.5.0", "dom-closest": "^0.2.0", "enquire.js": "^2.1.6", - "lodash": "^4.17.11", + "lodash": "^4.17.13", "moment": "^2.24.0", "omit.js": "^1.0.2", "prop-types": "^15.7.2", "raf": "^3.4.1", - "rc-animate": "^2.8.3", - "rc-calendar": "~9.15.0", + "rc-animate": "^2.10.2", + "rc-calendar": "~9.15.5", "rc-cascader": "~0.17.4", "rc-checkbox": "~2.1.6", "rc-collapse": "~1.11.3", - "rc-dialog": "~7.4.0", - "rc-drawer": "~1.9.8", + "rc-dialog": "~7.5.2", + "rc-drawer": "~3.0.0", "rc-dropdown": "~2.4.1", "rc-editor-mention": "^1.1.13", - "rc-form": "^2.4.5", - "rc-input-number": "~4.4.5", - "rc-mentions": "~0.3.1", - "rc-menu": "~7.4.23", + "rc-form": "^2.4.10", + "rc-input-number": "~4.5.0", + "rc-mentions": "~0.4.0", + "rc-menu": "~7.5.1", "rc-notification": "~3.3.1", - "rc-pagination": "~1.20.1", - "rc-progress": "~2.3.0", + "rc-pagination": "~1.20.5", + "rc-progress": "~2.5.0", "rc-rate": "~2.5.0", - "rc-select": "~9.1.4", - "rc-slider": "~8.6.11", - "rc-steps": "~3.4.1", + "rc-resize-observer": "^0.1.0", + "rc-select": "~9.2.0", + "rc-slider": "~8.7.1", + "rc-steps": "~3.5.0", "rc-switch": "~1.9.0", - "rc-table": "~6.6.0", + "rc-table": "~6.9.4", "rc-tabs": "~9.6.4", - "rc-time-picker": "~3.6.6", + "rc-time-picker": "~3.7.1", "rc-tooltip": "~3.7.3", "rc-tree": "~2.1.0", "rc-tree-select": "~2.9.1", "rc-trigger": "^2.6.2", - "rc-upload": "~2.6.7", - "rc-util": "^4.6.0", + "rc-upload": "~2.9.1", + "rc-util": "^4.10.0", "react-lazy-load": "^3.0.13", "react-lifecycles-compat": "^3.0.4", - "react-slick": "~0.24.0", + "react-slick": "~0.25.2", "resize-observer-polyfill": "^1.5.1", "shallowequal": "^1.1.0", "warning": "~4.0.3" @@ -1912,29 +1570,76 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, "requires": { "micromatch": "^3.1.4", "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } } }, "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true }, "are-we-there-yet": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "dev": true, "requires": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "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" + } + }, + "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" + } + } } }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, "requires": { "sprintf-js": "~1.0.2" } @@ -1943,6 +1648,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=", + "dev": true, "requires": { "ast-types-flow": "0.0.7", "commander": "^2.11.0" @@ -1951,57 +1657,43 @@ "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true }, "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true }, "arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" - }, - "array-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", - "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=" - }, - "array-filter": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz", - "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=" + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true }, "array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=" + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true }, "array-flatten": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==" + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true }, "array-includes": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", + "dev": true, "requires": { "define-properties": "^1.1.2", "es-abstract": "^1.7.0" } }, - "array-map": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz", - "integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=" - }, - "array-reduce": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz", - "integrity": "sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=" - }, "array-tree-filter": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-tree-filter/-/array-tree-filter-2.1.0.tgz", @@ -2011,6 +1703,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, "requires": { "array-uniq": "^1.0.1" } @@ -2018,17 +1711,14 @@ "array-uniq": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true }, "array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true }, "asap": { "version": "2.0.6", @@ -2039,6 +1729,7 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, "requires": { "safer-buffer": "~2.1.0" } @@ -2047,6 +1738,7 @@ "version": "4.10.1", "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, "requires": { "bn.js": "^4.0.0", "inherits": "^2.0.1", @@ -2057,6 +1749,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", + "dev": true, "requires": { "object-assign": "^4.1.1", "util": "0.10.3" @@ -2065,12 +1758,14 @@ "inherits": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true }, "util": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, "requires": { "inherits": "2.0.1" } @@ -2080,406 +1775,227 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true }, "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true }, "ast-types-flow": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", - "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=" + "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", + "dev": true }, "astral-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==" + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true }, "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } }, "async-each": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==" + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true }, "async-foreach": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", - "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=" + "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=", + "dev": true }, "async-limiter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "dev": true }, "async-validator": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-1.8.5.tgz", - "integrity": "sha512-tXBM+1m056MAX0E8TL2iCjg8WvSyXu0Zc8LNtYqrVeyoL3+esHRZ4SieE9fKQyyU09uONjnMEjrNBMqT0mbvmA==", - "requires": { - "babel-runtime": "6.x" - } + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-1.11.5.tgz", + "integrity": "sha512-XNtCsMAeAH1pdLMEg1z8/Bb3a8cdCbui9QbJATRFHHHW5kT6+NPI3zSVQUXgikTFITzsg+kYY5NTWhM2Orwt9w==" }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true }, "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true }, "autoprefixer": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.6.0.tgz", - "integrity": "sha512-kuip9YilBqhirhHEGHaBTZKXL//xxGnzvsD0FtBQa6z+A69qZD6s/BAX9VzDF1i9VKDquTJDQaPLSEhOnL6FvQ==", + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.7.3.tgz", + "integrity": "sha512-8T5Y1C5Iyj6PgkPSFd0ODvK9DIleuPKUPYniNxybS47g2k2wFgLZ46lGQHlBuGKIAEV8fbCDfKCCRS1tvOgc3Q==", + "dev": true, "requires": { - "browserslist": "^4.6.1", - "caniuse-lite": "^1.0.30000971", + "browserslist": "^4.8.0", + "caniuse-lite": "^1.0.30001012", "chalk": "^2.4.2", "normalize-range": "^0.1.2", "num2fraction": "^1.2.2", - "postcss": "^7.0.16", - "postcss-value-parser": "^3.3.1" - } - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" - }, - "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" - }, - "axobject-query": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz", - "integrity": "sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==", - "requires": { - "ast-types-flow": "0.0.7" - } - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" + "postcss": "^7.0.23", + "postcss-value-parser": "^4.0.2" }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "browserslist": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.2.tgz", + "integrity": "sha512-+M4oeaTplPm/f1pXDw84YohEv7B1i/2Aisei8s4s6k3QsoSHa7i5sz8u/cGQkkatCPxMASKxPualR4wwYgVboA==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001015", + "electron-to-chromium": "^1.3.322", + "node-releases": "^1.1.42" + } }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + "caniuse-lite": { + "version": "1.0.30001016", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001016.tgz", + "integrity": "sha512-yYQ2QfotceRiH4U+h1Us86WJXtVHDmy3nEKIdYPsZCYnOV5/tMgGbmoIlrMzmh2VXlproqYtVaKeGDBkMZifFA==", + "dev": true }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "electron-to-chromium": { + "version": "1.3.322", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.322.tgz", + "integrity": "sha512-Tc8JQEfGQ1MzfSzI/bTlSr7btJv/FFO7Yh6tanqVmIWOuNCu6/D1MilIEgLtmWqIrsv+o4IjpLAhgMBr/ncNAA==", + "dev": true + }, + "node-releases": { + "version": "1.1.42", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.42.tgz", + "integrity": "sha512-OQ/ESmUqGawI2PRX+XIRao44qWYBBfN54ImQYdWVTQqUckuejOg76ysSqDBK8NG3zwySRVnX36JwDQ6x+9GxzA==", + "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" + "semver": "^6.3.0" } }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "postcss": { + "version": "7.0.25", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.25.tgz", + "integrity": "sha512-NXXVvWq9icrm/TgQC0O6YVFi4StfJz46M1iNd/h6B26Nvh/HKI+q4YZtFN/EjcInZliEscO/WL10BXnc1E5nwg==", + "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" } }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "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 + }, "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, - "babel-eslint": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.1.tgz", - "integrity": "sha512-z7OT1iNV+TjOwHNLLyJk+HN+YVWX+CLE6fPD2SymJZOZQBs+QIexFjhm4keGTm8MW9xr4EC9Q0PbaLB24V5GoQ==", + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.0.tgz", + "integrity": "sha512-Uvq6hVe90D0B2WEnUqtdgY1bATGz3mw33nH9Y+dmA+w5DHvUmBgkr5rM/KCHpCsiFNRUfokW/szpPPgMK2hm4A==", + "dev": true + }, + "axobject-query": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.1.1.tgz", + "integrity": "sha512-lF98xa/yvy6j3fBHAgQXIYl+J4eZadOSqsPojemUqClzNbBV38wWGpUbQbVEyf4eUF5yF7eHmGgGA2JiHyjeqw==", + "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.0.0", - "@babel/traverse": "^7.0.0", - "@babel/types": "^7.0.0", - "eslint-scope": "3.7.1", - "eslint-visitor-keys": "^1.0.0" + "@babel/runtime": "^7.7.4", + "@babel/runtime-corejs3": "^7.7.4" }, "dependencies": { - "eslint-scope": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", - "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", + "@babel/runtime": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.4.tgz", + "integrity": "sha512-r24eVUUr0QqNZa+qrImUk8fn5SPhHq+IfYvIoIMg0do3GdK9sMdiLKP3GYVVaxpPKORgm8KRKaNTEhAjgIpLMw==", + "dev": true, "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" + "regenerator-runtime": "^0.13.2" } + }, + "regenerator-runtime": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", + "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==", + "dev": true } } }, - "babel-extract-comments": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/babel-extract-comments/-/babel-extract-comments-1.0.0.tgz", - "integrity": "sha512-qWWzi4TlddohA91bFwgt6zO/J0X+io7Qp184Fw0m2JYRSTZnJbFR8+07KmzudHCZgOiKRCrjhylwv9Xd8gfhVQ==", - "requires": { - "babylon": "^6.18.0" - } - }, - "babel-jest": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.8.0.tgz", - "integrity": "sha512-+5/kaZt4I9efoXzPlZASyK/lN9qdRKmmUav9smVc0ruPQD7IsfucQ87gpOE8mn2jbDuS6M/YOW6n3v9ZoIfgnw==", - "requires": { - "@jest/transform": "^24.8.0", - "@jest/types": "^24.8.0", - "@types/babel__core": "^7.1.0", - "babel-plugin-istanbul": "^5.1.0", - "babel-preset-jest": "^24.6.0", - "chalk": "^2.4.2", - "slash": "^2.0.0" - } - }, "babel-loader": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.5.tgz", - "integrity": "sha512-NTnHnVRd2JnRqPC0vW+iOQWU5pchDbYXsG2E6DMXEpMfUcQKclF9gmf3G3ZMhzG7IG9ji4coL0cm+FxeWxDpnw==", + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.6.tgz", + "integrity": "sha512-4BmWKtBOBm13uoUwd08UwjZlaw3O9GWf456R9j+5YykFZ6LUIjIKLc0zEZf+hauxPOJs96C8k6FvYD09vWzhYw==", + "dev": true, "requires": { "find-cache-dir": "^2.0.0", "loader-utils": "^1.0.2", "mkdirp": "^0.5.1", - "util.promisify": "^1.0.0" + "pify": "^4.0.1" } }, "babel-plugin-dynamic-import-node": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.2.0.tgz", - "integrity": "sha512-fP899ELUnTaBcIzmrW7nniyqqdYWrWuJUyPWHxFa/c7r7hS6KC8FscNfLlBNIoPSc55kYMGEEKjPjJGCLbE1qA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz", + "integrity": "sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==", + "dev": true, "requires": { "object.assign": "^4.1.0" } }, "babel-plugin-import": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/babel-plugin-import/-/babel-plugin-import-1.12.0.tgz", - "integrity": "sha512-3Fo7sJ2Hm71y1VJS7eMA/E7J5+roKJmzwia5BxzUQREBs6CRylwtvQq8m39W8nplG4Y7rZwOCndh5MzRTSmHpA==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/babel-plugin-import/-/babel-plugin-import-1.12.2.tgz", + "integrity": "sha512-Vz9s+I6vAnsY8sYczU/cdtkKAHSorapa/2St6K+OzowplKizpWxul4HLi3kj1eRmHMFjhbROSMGXP+mFna2nUw==", + "dev": true, "requires": { "@babel/helper-module-imports": "^7.0.0", "@babel/runtime": "^7.0.0" } }, - "babel-plugin-istanbul": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.1.4.tgz", - "integrity": "sha512-dySz4VJMH+dpndj0wjJ8JPs/7i1TdSPb1nRrn56/92pKOF9VKC1FMFJmMXjzlGGusnCAqujP6PBCiKq0sVA+YQ==", - "requires": { - "find-up": "^3.0.0", - "istanbul-lib-instrument": "^3.3.0", - "test-exclude": "^5.2.3" - } - }, - "babel-plugin-jest-hoist": { - "version": "24.6.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.6.0.tgz", - "integrity": "sha512-3pKNH6hMt9SbOv0F3WVmy5CWQ4uogS3k0GY5XLyQHJ9EGpAT9XWkFd2ZiXXtkwFHdAHa5j7w7kfxSP5lAIwu7w==", - "requires": { - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-plugin-macros": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.5.1.tgz", - "integrity": "sha512-xN3KhAxPzsJ6OQTktCanNpIFnnMsCV+t8OloKxIL72D6+SUZYFn9qfklPgef5HyyDtzYZqqb+fs1S12+gQY82Q==", - "requires": { - "@babel/runtime": "^7.4.2", - "cosmiconfig": "^5.2.0", - "resolve": "^1.10.0" - } - }, - "babel-plugin-named-asset-import": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.2.tgz", - "integrity": "sha512-CxwvxrZ9OirpXQ201Ec57OmGhmI8/ui/GwTDy0hSp6CmRvgRC0pSair6Z04Ck+JStA0sMPZzSJ3uE4n17EXpPQ==" - }, - "babel-plugin-syntax-object-rest-spread": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", - "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=" - }, - "babel-plugin-transform-object-rest-spread": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz", - "integrity": "sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY=", - "requires": { - "babel-plugin-syntax-object-rest-spread": "^6.8.0", - "babel-runtime": "^6.26.0" - } - }, - "babel-plugin-transform-react-remove-prop-types": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", - "integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==" - }, - "babel-preset-jest": { - "version": "24.6.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.6.0.tgz", - "integrity": "sha512-pdZqLEdmy1ZK5kyRUfvBb2IfTPb2BUvIJczlPspS8fWmBQslNNDBqVfh7BW5leOVJMDZKzjD8XEyABTk6gQ5yw==", - "requires": { - "@babel/plugin-syntax-object-rest-spread": "^7.0.0", - "babel-plugin-jest-hoist": "^24.6.0" - } - }, - "babel-preset-react-app": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/babel-preset-react-app/-/babel-preset-react-app-9.0.0.tgz", - "integrity": "sha512-YVsDA8HpAKklhFLJtl9+AgaxrDaor8gGvDFlsg1ByOS0IPGUovumdv4/gJiAnLcDmZmKlH6+9sVOz4NVW7emAg==", - "requires": { - "@babel/core": "7.4.3", - "@babel/plugin-proposal-class-properties": "7.4.0", - "@babel/plugin-proposal-decorators": "7.4.0", - "@babel/plugin-proposal-object-rest-spread": "7.4.3", - "@babel/plugin-syntax-dynamic-import": "7.2.0", - "@babel/plugin-transform-classes": "7.4.3", - "@babel/plugin-transform-destructuring": "7.4.3", - "@babel/plugin-transform-flow-strip-types": "7.4.0", - "@babel/plugin-transform-react-constant-elements": "7.2.0", - "@babel/plugin-transform-react-display-name": "7.2.0", - "@babel/plugin-transform-runtime": "7.4.3", - "@babel/preset-env": "7.4.3", - "@babel/preset-react": "7.0.0", - "@babel/preset-typescript": "7.3.3", - "@babel/runtime": "7.4.3", - "babel-plugin-dynamic-import-node": "2.2.0", - "babel-plugin-macros": "2.5.1", - "babel-plugin-transform-react-remove-prop-types": "0.4.24" - }, - "dependencies": { - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.4.3.tgz", - "integrity": "sha512-xC//6DNSSHVjq8O2ge0dyYlhshsH4T7XdCVoxbi5HzLYWfsC5ooFlJjrXk8RcAT+hjHAK9UjBXdylzSoDK3t4g==", - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-object-rest-spread": "^7.2.0" - } - }, - "@babel/plugin-transform-classes": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.4.3.tgz", - "integrity": "sha512-PUaIKyFUDtG6jF5DUJOfkBdwAS/kFFV3XFk7Nn0a6vR7ZT8jYw5cGtIlat77wcnd0C6ViGqo/wyNf4ZHytF/nQ==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.0.0", - "@babel/helper-define-map": "^7.4.0", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-optimise-call-expression": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-replace-supers": "^7.4.0", - "@babel/helper-split-export-declaration": "^7.4.0", - "globals": "^11.1.0" - } - }, - "@babel/plugin-transform-destructuring": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.4.3.tgz", - "integrity": "sha512-rVTLLZpydDFDyN4qnXdzwoVpk1oaXHIvPEOkOLyr88o7oHxVc/LyrnDx+amuBWGOwUb7D1s/uLsKBNTx08htZg==", - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" - } - }, - "@babel/preset-env": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.4.3.tgz", - "integrity": "sha512-FYbZdV12yHdJU5Z70cEg0f6lvtpZ8jFSDakTm7WXeJbLXh4R0ztGEu/SW7G1nJ2ZvKwDhz8YrbA84eYyprmGqw==", - "requires": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-async-generator-functions": "^7.2.0", - "@babel/plugin-proposal-json-strings": "^7.2.0", - "@babel/plugin-proposal-object-rest-spread": "^7.4.3", - "@babel/plugin-proposal-optional-catch-binding": "^7.2.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.0", - "@babel/plugin-syntax-async-generators": "^7.2.0", - "@babel/plugin-syntax-json-strings": "^7.2.0", - "@babel/plugin-syntax-object-rest-spread": "^7.2.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.2.0", - "@babel/plugin-transform-arrow-functions": "^7.2.0", - "@babel/plugin-transform-async-to-generator": "^7.4.0", - "@babel/plugin-transform-block-scoped-functions": "^7.2.0", - "@babel/plugin-transform-block-scoping": "^7.4.0", - "@babel/plugin-transform-classes": "^7.4.3", - "@babel/plugin-transform-computed-properties": "^7.2.0", - "@babel/plugin-transform-destructuring": "^7.4.3", - "@babel/plugin-transform-dotall-regex": "^7.4.3", - "@babel/plugin-transform-duplicate-keys": "^7.2.0", - "@babel/plugin-transform-exponentiation-operator": "^7.2.0", - "@babel/plugin-transform-for-of": "^7.4.3", - "@babel/plugin-transform-function-name": "^7.4.3", - "@babel/plugin-transform-literals": "^7.2.0", - "@babel/plugin-transform-member-expression-literals": "^7.2.0", - "@babel/plugin-transform-modules-amd": "^7.2.0", - "@babel/plugin-transform-modules-commonjs": "^7.4.3", - "@babel/plugin-transform-modules-systemjs": "^7.4.0", - "@babel/plugin-transform-modules-umd": "^7.2.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.4.2", - "@babel/plugin-transform-new-target": "^7.4.0", - "@babel/plugin-transform-object-super": "^7.2.0", - "@babel/plugin-transform-parameters": "^7.4.3", - "@babel/plugin-transform-property-literals": "^7.2.0", - "@babel/plugin-transform-regenerator": "^7.4.3", - "@babel/plugin-transform-reserved-words": "^7.2.0", - "@babel/plugin-transform-shorthand-properties": "^7.2.0", - "@babel/plugin-transform-spread": "^7.2.0", - "@babel/plugin-transform-sticky-regex": "^7.2.0", - "@babel/plugin-transform-template-literals": "^7.2.0", - "@babel/plugin-transform-typeof-symbol": "^7.2.0", - "@babel/plugin-transform-unicode-regex": "^7.4.3", - "@babel/types": "^7.4.0", - "browserslist": "^4.5.2", - "core-js-compat": "^3.0.0", - "invariant": "^2.2.2", - "js-levenshtein": "^1.1.3", - "semver": "^5.5.0" - } - }, - "@babel/runtime": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.3.tgz", - "integrity": "sha512-9lsJwJLxDh/T3Q3SZszfWOTkk3pHbkmH+3KY+zwIDmsNlxsumuhS2TH3NIpktU4kNvfzy+k3eLT7aTJSPTo0OA==", - "requires": { - "regenerator-runtime": "^0.13.2" - } - }, - "regenerator-runtime": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz", - "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==" - } - } + "babel-plugin-react-svg": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/babel-plugin-react-svg/-/babel-plugin-react-svg-3.0.3.tgz", + "integrity": "sha512-Pst1RWjUIiV0Ykv1ODSeceCBsFOP2Y4dusjq7/XkjuzJdvS9CjpkPMUIoO4MLlvp5PiLCeMlsOC7faEUA0gm3Q==", + "dev": true }, "babel-runtime": { "version": "6.26.0", @@ -2490,25 +2006,17 @@ "regenerator-runtime": "^0.11.0" } }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==" - }, - "bail": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.4.tgz", - "integrity": "sha512-S8vuDB4w6YpRhICUDET3guPlQpaJl7od94tpZ0Fvnyp+MKW/HyDTcRDck+29C9g+d/qQHnddRH3+94kZdrW0Ww==" - }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true }, "base": { "version": "0.11.2", "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, "requires": { "cache-base": "^1.0.1", "class-utils": "^0.3.5", @@ -2523,6 +2031,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, "requires": { "is-descriptor": "^1.0.0" } @@ -2531,6 +2040,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -2539,6 +2049,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -2547,33 +2058,32 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", "kind-of": "^6.0.2" } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" } } }, "base64-js": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", - "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", + "dev": true }, "batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=" + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", + "dev": true }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, "requires": { "tweetnacl": "^0.14.3" } @@ -2581,35 +2091,41 @@ "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true }, "binary-extensions": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==" + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true }, "block-stream": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "dev": true, "requires": { "inherits": "~2.0.0" } }, "bluebird": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", - "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==" + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.1.tgz", + "integrity": "sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==", + "dev": true }, "bn.js": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "dev": true }, "body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "dev": true, "requires": { "bytes": "3.1.0", "content-type": "~1.0.4", @@ -2626,12 +2142,14 @@ "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "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" } @@ -2639,12 +2157,8 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, @@ -2652,6 +2166,7 @@ "version": "3.5.0", "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", + "dev": true, "requires": { "array-flatten": "^2.1.0", "deep-equal": "^1.0.1", @@ -2664,12 +2179,14 @@ "boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true }, "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" @@ -2679,6 +2196,7 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, "requires": { "arr-flatten": "^1.1.0", "array-unique": "^0.3.2", @@ -2696,6 +2214,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -2705,32 +2224,14 @@ "brorand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" - }, - "browser-process-hrtime": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", - "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==" - }, - "browser-resolve": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", - "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", - "requires": { - "resolve": "1.1.7" - }, - "dependencies": { - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=" - } - } + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true }, "browserify-aes": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, "requires": { "buffer-xor": "^1.0.3", "cipher-base": "^1.0.0", @@ -2744,6 +2245,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, "requires": { "browserify-aes": "^1.0.4", "browserify-des": "^1.0.0", @@ -2754,6 +2256,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, "requires": { "cipher-base": "^1.0.1", "des.js": "^1.0.0", @@ -2765,6 +2268,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dev": true, "requires": { "bn.js": "^4.1.0", "randombytes": "^2.0.1" @@ -2774,6 +2278,7 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "dev": true, "requires": { "bn.js": "^4.1.1", "browserify-rsa": "^4.0.0", @@ -2788,37 +2293,27 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, "requires": { "pako": "~1.0.5" } }, "browserslist": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.6.3.tgz", - "integrity": "sha512-CNBqTCq22RKM8wKJNowcqihHJ4SkI8CGeK7KOR9tPboXUuS5Zk5lQgzzTbs4oxD8x+6HUshZUa2OyNI9lR93bQ==", - "requires": { - "caniuse-lite": "^1.0.30000975", - "electron-to-chromium": "^1.3.164", - "node-releases": "^1.1.23" - } - }, - "bser": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.0.tgz", - "integrity": "sha512-8zsjWrQkkBoLK6uxASk1nJ2SKv97ltiGDo6A3wA0/yRPz+CwmEyDo0hUrhIuukG2JHpAl3bvFIixw2/3Hi0DOg==", + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.7.2.tgz", + "integrity": "sha512-uZavT/gZXJd2UTi9Ov7/Z340WOSQ3+m1iBVRUknf+okKxonL9P83S3ctiBDtuRmRu8PiCHjqyueqQ9HYlJhxiw==", + "dev": true, "requires": { - "node-int64": "^0.4.0" + "caniuse-lite": "^1.0.30001004", + "electron-to-chromium": "^1.3.295", + "node-releases": "^1.1.38" } }, - "btoa": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", - "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==" - }, "buffer": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "dev": true, "requires": { "base64-js": "^1.0.2", "ieee754": "^1.1.4", @@ -2828,45 +2323,53 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true } } }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true }, "buffer-indexof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", - "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==" + "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", + "dev": true }, "buffer-xor": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true }, "builtin-status-codes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=" + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true }, "bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "dev": true }, "cacache": { - "version": "11.3.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.3.tgz", - "integrity": "sha512-p8WcneCytvzPxhDvYp31PD039vi77I12W+/KfR9S8AZbaiARFBCpsPJS+9uhWfeBfeAtW7o/4vt3MUqLkbY6nA==", + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.3.tgz", + "integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==", + "dev": true, "requires": { "bluebird": "^3.5.5", "chownr": "^1.1.1", "figgy-pudding": "^3.5.1", "glob": "^7.1.4", "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", "lru-cache": "^5.1.1", "mississippi": "^3.0.0", "mkdirp": "^0.5.1", @@ -2876,12 +2379,30 @@ "ssri": "^6.0.1", "unique-filename": "^1.1.1", "y18n": "^4.0.0" + }, + "dependencies": { + "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" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } } }, "cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, "requires": { "collection-visit": "^1.0.0", "component-emitter": "^1.2.1", @@ -2894,15 +2415,11 @@ "unset-value": "^1.0.0" } }, - "call-me-maybe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", - "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=" - }, "caller-callsite": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "dev": true, "requires": { "callsites": "^2.0.0" } @@ -2911,6 +2428,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "dev": true, "requires": { "caller-callsite": "^2.0.0" } @@ -2918,12 +2436,14 @@ "callsites": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", + "dev": true }, "camel-case": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", + "dev": true, "requires": { "no-case": "^2.2.0", "upper-case": "^1.1.1" @@ -2932,12 +2452,14 @@ "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true }, "camelcase-keys": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, "requires": { "camelcase": "^2.0.0", "map-obj": "^1.0.0" @@ -2946,53 +2468,28 @@ "camelcase": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true } } }, - "caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", - "requires": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, "caniuse-lite": { - "version": "1.0.30000978", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000978.tgz", - "integrity": "sha512-H6gK6kxUzG6oAwg/Jal279z8pHw0BzrpZfwo/CA9FFm/vA0l8IhDfkZtepyJNE2Y4V6Dp3P3ubz6czby1/Mgsw==" - }, - "capture-exit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", - "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", - "requires": { - "rsvp": "^4.8.4" - } - }, - "case-sensitive-paths-webpack-plugin": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.2.0.tgz", - "integrity": "sha512-u5ElzokS8A1pm9vM3/iDgTcI3xqHxuCao94Oz8etI3cf0Tio0p8izkDYbTIn09uP3yUUr6+veaE6IkjnTYS46g==" + "version": "1.0.30001006", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001006.tgz", + "integrity": "sha512-MXnUVX27aGs/QINz+QG1sWSLDr3P1A3Hq5EUWoIt0T7K24DuvMxZEnh3Y5aHlJW6Bz2aApJdSewdYLd8zQnUuw==", + "dev": true }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, - "ccount": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.0.4.tgz", - "integrity": "sha512-fpZ81yYfzentuieinmGnphk0pLkOTMm6MZdVqwd77ROvhko6iujLNGrHH5E7utq3ygWklwfmwuG+A7P+NpqT6w==" + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "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", @@ -3002,12 +2499,14 @@ "chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true }, "chokidar": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.6.tgz", - "integrity": "sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "dev": true, "requires": { "anymatch": "^2.0.0", "async-each": "^1.0.1", @@ -3021,538 +2520,50 @@ "path-is-absolute": "^1.0.0", "readdirp": "^2.2.1", "upath": "^1.1.1" + } + }, + "chownr": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", + "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==", + "dev": true + }, + "chrome-trace-event": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", + "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" }, "dependencies": { - "fsevents": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", - "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", - "optional": true, - "requires": { - "nan": "^2.12.1", - "node-pre-gyp": "^0.12.0" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "optional": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.1", - "bundled": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "optional": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "debug": { - "version": "4.1.1", - "bundled": true, - "optional": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.5", - "bundled": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.3", - "bundled": true, - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.1", - "bundled": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true, - "optional": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "optional": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "optional": true - }, - "minipass": { - "version": "2.3.5", - "bundled": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.2.1", - "bundled": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "optional": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.1.1", - "bundled": true, - "optional": true - }, - "needle": { - "version": "2.3.0", - "bundled": true, - "optional": true, - "requires": { - "debug": "^4.1.0", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.12.0", - "bundled": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.0.6", - "bundled": true, - "optional": true - }, - "npm-packlist": { - "version": "1.4.1", - "bundled": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "optional": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.0", - "bundled": true, - "optional": true - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "optional": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "optional": 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" - } - }, - "rimraf": { - "version": "2.6.3", - "bundled": true, - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true, - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "optional": true - }, - "semver": { - "version": "5.7.0", - "bundled": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "optional": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "optional": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "tar": { - "version": "4.4.8", - "bundled": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.3.4", - "minizlib": "^1.1.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "optional": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "yallist": { - "version": "3.0.3", - "bundled": true, - "optional": true - } - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" - } - } - }, - "chownr": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", - "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==" - }, - "chrome-trace-event": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", - "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", - "requires": { - "tslib": "^1.9.0" - } - }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" - }, - "cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -3568,59 +2579,95 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", + "dev": true, "requires": { "source-map": "~0.6.0" + }, + "dependencies": { + "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 + } } }, "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, "requires": { - "restore-cursor": "^2.0.0" + "restore-cursor": "^3.1.0" } }, "cli-width": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=" + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true }, "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } } }, "clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=" + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true }, "clone-deep": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", - "integrity": "sha1-TnPdCen7lxzDhnDF3O2cGJZIHMY=", + "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": { - "for-own": "^0.1.3", - "is-plain-object": "^2.0.1", - "kind-of": "^3.0.2", - "lazy-cache": "^1.0.3", - "shallow-clone": "^0.1.2" + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" } }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" - }, "coa": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", + "dev": true, "requires": { "@types/q": "^1.5.1", "chalk": "^2.4.1", @@ -3630,30 +2677,24 @@ "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true }, "collection-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, "requires": { "map-visit": "^1.0.0", "object-visit": "^1.0.0" } }, - "color": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/color/-/color-3.1.2.tgz", - "integrity": "sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==", - "requires": { - "color-convert": "^1.9.1", - "color-string": "^1.5.2" - } - }, "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" } @@ -3661,44 +2702,29 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "color-string": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", - "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", - "requires": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, "requires": { "delayed-stream": "~1.0.0" } }, - "comma-separated-tokens": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.7.tgz", - "integrity": "sha512-Jrx3xsP4pPv4AwJUDWY9wOXGtwPXARej6Xd99h4TUGotmf8APuquKMpK+dnD3UgyxK7OEWaisjZz+3b5jtL6xQ==" - }, "commander": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", - "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==" - }, - "common-tags": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz", - "integrity": "sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw==" + "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": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true }, "component-classes": { "version": "1.2.6", @@ -3711,7 +2737,8 @@ "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true }, "component-indexof": { "version": "0.0.3", @@ -3722,6 +2749,7 @@ "version": "2.0.17", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.17.tgz", "integrity": "sha512-BGHeLCK1GV7j1bSmQQAi26X+GgWcTjLr/0tzSvMCl3LH1w1IJ4PFSPoV5316b30cneTziC+B1a+3OjoSUcQYmw==", + "dev": true, "requires": { "mime-db": ">= 1.40.0 < 2" } @@ -3730,6 +2758,7 @@ "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", @@ -3744,6 +2773,7 @@ "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" } @@ -3751,63 +2781,102 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true }, "concat-stream": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, "requires": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^2.2.2", "typedarray": "^0.0.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "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" + } + }, + "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" + } + } } }, "confusing-browser-globals": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.7.tgz", - "integrity": "sha512-cgHI1azax5ATrZ8rJ+ODDML9Fvu67PimB6aNxBrc/QwSaDaM9eTfIEUHx3bBLJJ82ioSb+/5zfsMCCEJax3ByQ==" + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz", + "integrity": "sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw==", + "dev": true }, "connect-history-api-fallback": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", - "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==" + "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", + "dev": true }, "console-browserify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", - "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", - "requires": { - "date-now": "^0.1.4" - } + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true }, "constants-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=" + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true }, "contains-path": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", - "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=" + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true }, "content-disposition": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dev": true, "requires": { "safe-buffer": "5.1.2" } @@ -3815,12 +2884,14 @@ "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true }, "convert-source-map": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, "requires": { "safe-buffer": "~5.1.1" } @@ -3828,17 +2899,20 @@ "cookie": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "dev": true }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true }, "copy-concurrently": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "dev": true, "requires": { "aproba": "^1.1.1", "fs-write-stream-atomic": "^1.0.8", @@ -3851,7 +2925,8 @@ "copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true }, "copy-to-clipboard": { "version": "3.2.0", @@ -3862,52 +2937,69 @@ } }, "core-js": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", - "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==" + "version": "2.6.10", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.10.tgz", + "integrity": "sha512-I39t74+4t+zau64EN1fE5v2W31Adtc/REhzWN+gWRRXg6WH5qAsZm62DHpQ1+Yhe4047T55jvzz7MUqF/dBBlA==" }, "core-js-compat": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.1.4.tgz", - "integrity": "sha512-Z5zbO9f1d0YrJdoaQhphVAnKPimX92D6z8lCGphH89MNRxlL1prI9ExJPqVwP0/kgkQCv8c4GJGT8X16yUncOg==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.3.5.tgz", + "integrity": "sha512-44ZORuapx0MUht0MUk0p9lcQPh7n/LDXehimTmjCs0CYblpKZcqVd5w0OQDUDq5OQjEbazWObHDQJWvvHYPNTg==", + "dev": true, "requires": { - "browserslist": "^4.6.2", - "core-js-pure": "3.1.4", - "semver": "^6.1.1" + "browserslist": "^4.7.2", + "semver": "^6.3.0" }, "dependencies": { "semver": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.2.tgz", - "integrity": "sha512-z4PqiCpomGtWj8633oeAdXm1Kn1W++3T8epkZYnwiVgIYIJ0QHszhInYSJTYxebByQH7KVCEAn8R9duzZW2PhQ==" + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true } } }, "core-js-pure": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.1.4.tgz", - "integrity": "sha512-uJ4Z7iPNwiu1foygbcZYJsJs1jiXrTTCvxfLDXNhI/I+NHbSIEyr548y4fcsCEyWY0XgfAG/qqaunJ1SThHenA==" + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.4.7.tgz", + "integrity": "sha512-Am3uRS8WCdTFA3lP7LtKR0PxgqYzjAMGKXaZKSNSC/8sqU0Wfq8R/YzoRs2rqtOVEunfgH+0q3O0BKOg0AvjPw==", + "dev": true }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true }, "cosmiconfig": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, "requires": { "import-fresh": "^2.0.0", "is-directory": "^0.3.1", "js-yaml": "^3.13.1", "parse-json": "^4.0.0" + }, + "dependencies": { + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + } } }, "create-ecdh": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "dev": true, "requires": { "bn.js": "^4.1.0", "elliptic": "^6.0.0" @@ -3917,6 +3009,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, "requires": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", @@ -3929,6 +3022,7 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, "requires": { "cipher-base": "^1.0.3", "create-hash": "^1.1.0", @@ -3948,24 +3042,13 @@ "object-assign": "^4.1.1" } }, - "create-react-context": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/create-react-context/-/create-react-context-0.2.2.tgz", - "integrity": "sha512-KkpaLARMhsTsgp0d2NA/R94F/eDLbhXERdIq3LvX2biCAXcDvHYoOqHfWCHf1+OLj+HKBotLG3KqaOOf+C1C+A==", + "cross-spawn": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", + "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", + "dev": true, "requires": { - "fbjs": "^0.8.0", - "gud": "^1.0.0" - } - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", + "lru-cache": "^4.0.1", "which": "^1.2.9" } }, @@ -3973,6 +3056,7 @@ "version": "3.12.0", "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, "requires": { "browserify-cipher": "^1.0.0", "browserify-sign": "^4.0.0", @@ -3988,9 +3072,9 @@ } }, "css-animation": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/css-animation/-/css-animation-1.5.0.tgz", - "integrity": "sha512-hWYoWiOZ7Vr20etzLh3kpWgtC454tW5vn4I6rLANDgpzNSkO7UfOqyCEeaoBSG9CYWQpRkFWTWbWW8o3uZrNLw==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/css-animation/-/css-animation-1.6.1.tgz", + "integrity": "sha512-/48+/BaEaHRY6kNQ2OIPzKf9A6g8WjZYjhiNDNuIVbsm5tXCGIAsHDjB4Xu1C4vXJtUWZo26O68OQkDpNBaPog==", "requires": { "babel-runtime": "6.x", "component-classes": "^1.2.5" @@ -4000,28 +3084,16 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz", "integrity": "sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w==", + "dev": true, "requires": { "postcss": "^7.0.5" } }, - "css-color-names": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", - "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=" - }, - "css-declaration-sorter": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz", - "integrity": "sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==", - "requires": { - "postcss": "^7.0.1", - "timsort": "^0.3.0" - } - }, "css-has-pseudo": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz", "integrity": "sha512-Z8hnfsZu4o/kt+AuFzeGpLVhFOGO9mluyHBaA2bA8aCGTwah5sT3WV/fTHH8UNZUytOIImuGPrl/prlb4oX4qQ==", + "dev": true, "requires": { "postcss": "^7.0.6", "postcss-selector-parser": "^5.0.0-rc.4" @@ -4030,12 +3102,14 @@ "cssesc": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", - "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==" + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", + "dev": true }, "postcss-selector-parser": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "dev": true, "requires": { "cssesc": "^2.0.0", "indexes-of": "^1.0.1", @@ -4045,273 +3119,137 @@ } }, "css-loader": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-2.1.1.tgz", - "integrity": "sha512-OcKJU/lt232vl1P9EEDamhoO9iKY3tIjY5GU+XDLblAykTdgs6Ux9P1hTHve8nFKy5KPpOXOsVI/hIwi3841+w==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.2.0.tgz", + "integrity": "sha512-QTF3Ud5H7DaZotgdcJjGMvyDj5F3Pn1j/sC6VBEOVp94cbwqyIBdcs/quzj4MC1BKQSrTpQznegH/5giYbhnCQ==", + "dev": true, "requires": { - "camelcase": "^5.2.0", - "icss-utils": "^4.1.0", + "camelcase": "^5.3.1", + "cssesc": "^3.0.0", + "icss-utils": "^4.1.1", "loader-utils": "^1.2.3", "normalize-path": "^3.0.0", - "postcss": "^7.0.14", + "postcss": "^7.0.17", "postcss-modules-extract-imports": "^2.0.0", - "postcss-modules-local-by-default": "^2.0.6", + "postcss-modules-local-by-default": "^3.0.2", "postcss-modules-scope": "^2.1.0", - "postcss-modules-values": "^2.0.0", - "postcss-value-parser": "^3.3.0", - "schema-utils": "^1.0.0" - }, - "dependencies": { - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" - } + "postcss-modules-values": "^3.0.0", + "postcss-value-parser": "^4.0.0", + "schema-utils": "^2.0.0" } }, "css-prefers-color-scheme": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz", "integrity": "sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg==", + "dev": true, "requires": { "postcss": "^7.0.5" } }, "css-select": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.0.2.tgz", - "integrity": "sha512-dSpYaDVoWaELjvZ3mS6IKZM/y2PMPa/XYoEfYNZePL4U/XgyxZNroHEHReDx/d+VgXh9VbCTtFqLkFbmeqeaRQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "dev": true, "requires": { - "boolbase": "^1.0.0", - "css-what": "^2.1.2", - "domutils": "^1.7.0", - "nth-check": "^1.0.2" + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" } }, "css-select-base-adapter": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", - "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" + "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", + "dev": true }, "css-tree": { - "version": "1.0.0-alpha.28", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.28.tgz", - "integrity": "sha512-joNNW1gCp3qFFzj4St6zk+Wh/NBv0vM5YbEreZk0SD4S23S+1xBKb6cLDg2uj4P4k/GUMlIm6cKIDqIG+vdt0w==", + "version": "1.0.0-alpha.37", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", + "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", + "dev": true, "requires": { - "mdn-data": "~1.1.0", - "source-map": "^0.5.3" + "mdn-data": "2.0.4", + "source-map": "^0.6.1" }, "dependencies": { "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true } } }, - "css-unit-converter": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.1.tgz", - "integrity": "sha1-2bkoGtz9jO2TW9urqDeGiX9k6ZY=" - }, - "css-url-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/css-url-regex/-/css-url-regex-1.1.0.tgz", - "integrity": "sha1-g4NCMMyfdMRX3lnuvRVD/uuDt+w=" - }, "css-what": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", - "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==" + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", + "dev": true }, "cssdb": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-4.4.0.tgz", - "integrity": "sha512-LsTAR1JPEM9TpGhl/0p3nQecC2LJ0kD8X5YARu1hk/9I1gril5vDtMZyNxcEpxxDj34YNck/ucjuoUd66K03oQ==" + "integrity": "sha512-LsTAR1JPEM9TpGhl/0p3nQecC2LJ0kD8X5YARu1hk/9I1gril5vDtMZyNxcEpxxDj34YNck/ucjuoUd66K03oQ==", + "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==" - }, - "cssnano": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz", - "integrity": "sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==", - "requires": { - "cosmiconfig": "^5.0.0", - "cssnano-preset-default": "^4.0.7", - "is-resolvable": "^1.0.0", - "postcss": "^7.0.0" - } - }, - "cssnano-preset-default": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz", - "integrity": "sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA==", - "requires": { - "css-declaration-sorter": "^4.0.1", - "cssnano-util-raw-cache": "^4.0.1", - "postcss": "^7.0.0", - "postcss-calc": "^7.0.1", - "postcss-colormin": "^4.0.3", - "postcss-convert-values": "^4.0.1", - "postcss-discard-comments": "^4.0.2", - "postcss-discard-duplicates": "^4.0.2", - "postcss-discard-empty": "^4.0.1", - "postcss-discard-overridden": "^4.0.1", - "postcss-merge-longhand": "^4.0.11", - "postcss-merge-rules": "^4.0.3", - "postcss-minify-font-values": "^4.0.2", - "postcss-minify-gradients": "^4.0.2", - "postcss-minify-params": "^4.0.2", - "postcss-minify-selectors": "^4.0.2", - "postcss-normalize-charset": "^4.0.1", - "postcss-normalize-display-values": "^4.0.2", - "postcss-normalize-positions": "^4.0.2", - "postcss-normalize-repeat-style": "^4.0.2", - "postcss-normalize-string": "^4.0.2", - "postcss-normalize-timing-functions": "^4.0.2", - "postcss-normalize-unicode": "^4.0.1", - "postcss-normalize-url": "^4.0.1", - "postcss-normalize-whitespace": "^4.0.2", - "postcss-ordered-values": "^4.1.2", - "postcss-reduce-initial": "^4.0.3", - "postcss-reduce-transforms": "^4.0.2", - "postcss-svgo": "^4.0.2", - "postcss-unique-selectors": "^4.0.1" - } - }, - "cssnano-util-get-arguments": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz", - "integrity": "sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=" - }, - "cssnano-util-get-match": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz", - "integrity": "sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=" - }, - "cssnano-util-raw-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", - "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", - "requires": { - "postcss": "^7.0.0" - } - }, - "cssnano-util-same-parent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz", - "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==" + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true }, "csso": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/csso/-/csso-3.5.1.tgz", - "integrity": "sha512-vrqULLffYU1Q2tLdJvaCYbONStnfkfimRxXNaGjxMldI0C7JPBC4rB1RyjhfdZ4m1frm8pM9uRPKH3d2knZ8gg==", - "requires": { - "css-tree": "1.0.0-alpha.29" - }, - "dependencies": { - "css-tree": { - "version": "1.0.0-alpha.29", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.29.tgz", - "integrity": "sha512-sRNb1XydwkW9IOci6iB2xmy8IGCj6r/fr+JWitvJ2JxQRPzN3T4AGGVWCMlVmVwM1gtgALJRmGIlWv5ppnGGkg==", - "requires": { - "mdn-data": "~1.1.0", - "source-map": "^0.5.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - } - } - }, - "cssom": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.6.tgz", - "integrity": "sha512-DtUeseGk9/GBW0hl0vVPpU22iHL6YB5BUX7ml1hB+GMpo0NX5G4voX3kdWiMSEguFtcW3Vh3djqNF4aIe6ne0A==" - }, - "cssstyle": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.2.2.tgz", - "integrity": "sha512-43wY3kl1CVQSvL7wUY1qXkxVGkStjpkDmVjiIKX8R97uhajy8Bybay78uOtqvh7Q5GK75dNPfW0geWjE6qQQow==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.0.2.tgz", + "integrity": "sha512-kS7/oeNVXkHWxby5tHVxlhjizRCSv8QdU7hB2FpdAibDU8FjTAolhNjKNTiLzXtUrKT6HwClE81yXwEk1309wg==", + "dev": true, "requires": { - "cssom": "0.3.x" + "css-tree": "1.0.0-alpha.37" } }, "csstype": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.5.tgz", - "integrity": "sha512-JsTaiksRsel5n7XwqPAfB0l3TFKdpjW/kgAELf9vrb5adGA7UCPLajKK5s3nFrcFm3Rkyp/Qkgl73ENc1UY3cA==" + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.7.tgz", + "integrity": "sha512-9Mcn9sFbGBAdmimWb2gLVDtFJzeKtDGIr76TUqmjZrw9LFXBMSU70lcs+C0/7fyCd6iBDqmksUcCOUIkisPHsQ==" }, "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, "requires": { "array-find-index": "^1.0.1" } }, - "customize-cra": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/customize-cra/-/customize-cra-0.2.14.tgz", - "integrity": "sha512-LtEMXNzkhnnqGPc1dP5fnPlF1ic1dj34hDbRVJIzfMQgOaGByHhx51fTR7fv7sTPEbCPrOBP777MkCo0GPV57g==", - "requires": { - "lodash.flow": "^3.5.0" - } - }, "cyclist": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", - "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", + "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", + "dev": true }, "damerau-levenshtein": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.5.tgz", - "integrity": "sha512-CBCRqFnpu715iPmw1KrdOrzRqbdFwQTwAWyyyYS42+iAgHCuXZ+/TdMgQkUENPomxEz9z1BEzuQU2Xw0kUuAgA==" + "integrity": "sha512-CBCRqFnpu715iPmw1KrdOrzRqbdFwQTwAWyyyYS42+iAgHCuXZ+/TdMgQkUENPomxEz9z1BEzuQU2Xw0kUuAgA==", + "dev": true }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, "requires": { "assert-plus": "^1.0.0" } }, - "data-urls": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", - "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", - "requires": { - "abab": "^2.0.0", - "whatwg-mimetype": "^2.2.0", - "whatwg-url": "^7.0.0" - }, - "dependencies": { - "whatwg-url": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", - "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - } - } - }, - "date-now": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", - "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=" - }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, "requires": { "ms": "^2.1.1" } @@ -4319,12 +3257,14 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true }, "deep-diff": { "version": "0.3.8", @@ -4332,28 +3272,79 @@ "integrity": "sha1-wB3mPvsO7JeYgB1Ax+Da4ltYLIQ=" }, "deep-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", - "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.0.tgz", + "integrity": "sha512-ZbfWJq/wN1Z273o7mUSjILYqehAktR2NVoSrOukDkU9kg2v/Uv89yU4Cvz8seJeAmtN5oqiefKq8FPuXOboqLw==", + "dev": true, + "requires": { + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.1", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.2.0" + } }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true }, "default-gateway": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==", + "dev": true, "requires": { "execa": "^1.0.0", "ip-regex": "^2.1.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + } } }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, "requires": { "object-keys": "^1.0.12" } @@ -4362,6 +3353,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, "requires": { "is-descriptor": "^1.0.2", "isobject": "^3.0.1" @@ -4371,6 +3363,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -4379,6 +3372,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -4387,77 +3381,53 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", "kind-of": "^6.0.2" } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" } } }, "del": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", - "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", + "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", + "dev": true, "requires": { + "@types/glob": "^7.1.1", "globby": "^6.1.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "p-map": "^1.1.1", - "pify": "^3.0.0", - "rimraf": "^2.2.8" - }, - "dependencies": { - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "requires": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - } - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" - } + "is-path-cwd": "^2.0.0", + "is-path-in-cwd": "^2.0.0", + "p-map": "^2.0.0", + "pify": "^4.0.1", + "rimraf": "^2.6.3" } }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true }, "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true }, "des.js": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "dev": true, "requires": { "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0" @@ -4466,75 +3436,43 @@ "destroy": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true }, - "detect-newline": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", - "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=" + "detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true }, "detect-node": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", - "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==" - }, - "detect-port-alt": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz", - "integrity": "sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==", - "requires": { - "address": "^1.0.1", - "debug": "^2.6.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "diff-sequences": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.3.0.tgz", - "integrity": "sha512-xLqpez+Zj9GKSnPWS0WZw1igGocZ+uua8+y+5dDNTT934N3QuY1sp2LkHzwiaYQGz60hMq0pjAshdeXm5VUOEw==" + "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==", + "dev": true }, "diffie-hellman": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, "requires": { "bn.js": "^4.1.0", "miller-rabin": "^4.0.0", "randombytes": "^2.0.0" } }, - "dir-glob": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", - "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", - "requires": { - "arrify": "^1.0.1", - "path-type": "^3.0.0" - } - }, "dns-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=" + "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", + "dev": true }, "dns-packet": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", + "dev": true, "requires": { "ip": "^1.1.0", "safe-buffer": "^5.0.1" @@ -4544,27 +3482,33 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", + "dev": true, "requires": { "buffer-indexof": "^1.0.0" } }, - "docopt": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/docopt/-/docopt-0.6.2.tgz", - "integrity": "sha1-so6eIiDaXsSffqW7JKR3h0Be6xE=" - }, "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, "requires": { - "esutils": "^2.0.2" + "esutils": "^2.0.2", + "isarray": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + } } }, "dom-align": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.8.3.tgz", - "integrity": "sha512-thE1qB8mvtRZgwN4+IGFz1rv7zVsr08c2/IEYtOJIeTzW4YDadIOd5nQ4BpiiAvUWg55xTeGq7zLTDxDYWDrnw==" + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.10.2.tgz", + "integrity": "sha512-AYZUzLepy05E9bCY4ExoqHrrIlM49PEak9oF93JEFoibqKL0F7w5DLM70/rosLOawerWZ3MlepQcl+EmHskOyw==" }, "dom-closest": { "version": "0.2.0", @@ -4578,6 +3522,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dev": true, "requires": { "utila": "~0.4" } @@ -4593,66 +3538,74 @@ "integrity": "sha1-6PNnMt0ImwIBqI14Fdw/iObWbH4=" }, "dom-serializer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", - "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.1.tgz", + "integrity": "sha512-sK3ujri04WyjwQXVoK4PU3y8ula1stq10GJZpqHIUgoGZdsGzAGu65BnU3d08aTVSvO7mGPZUc0wTEDL+qGE0Q==", + "dev": true, "requires": { - "domelementtype": "^1.3.0", - "entities": "^1.1.1" + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + }, + "dependencies": { + "domelementtype": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", + "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==", + "dev": true + } } }, "domain-browser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==" + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true }, "domelementtype": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" - }, - "domexception": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", - "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", - "requires": { - "webidl-conversions": "^4.0.2" - } + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true }, "domhandler": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dev": true, "requires": { "domelementtype": "1" } }, "domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dev": true, "requires": { "dom-serializer": "0", "domelementtype": "1" } }, - "dot-prop": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", - "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", - "requires": { - "is-obj": "^1.0.0" - } - }, "dotenv": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.2.0.tgz", "integrity": "sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==" }, - "dotenv-expand": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-4.2.0.tgz", - "integrity": "sha1-3vHxyl1gWdJKdm5YeULCEQbOEnU=" + "dotenv-defaults": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/dotenv-defaults/-/dotenv-defaults-1.0.2.tgz", + "integrity": "sha512-iXFvHtXl/hZPiFj++1hBg4lbKwGM+t/GlvELDnRtOFdjXyWP7mubkVr+eZGWG62kdsbulXAef6v/j6kiWc/xGA==", + "requires": { + "dotenv": "^6.2.0" + } + }, + "dotenv-webpack": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/dotenv-webpack/-/dotenv-webpack-1.7.0.tgz", + "integrity": "sha512-wwNtOBW/6gLQSkb8p43y0Wts970A3xtNiG/mpwj9MLUhtPCQG6i+/DSXXoNN7fbPCU/vQ7JjwGmgOeGZSSZnsw==", + "requires": { + "dotenv-defaults": "^1.0.2" + } }, "draft-js": { "version": "0.10.5", @@ -4664,26 +3617,55 @@ "object-assign": "^4.1.0" } }, - "duplexer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", - "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=" - }, "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, "requires": { "end-of-stream": "^1.0.0", "inherits": "^2.0.1", "readable-stream": "^2.0.0", "stream-shift": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "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" + } + }, + "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" + } + } } }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, "requires": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -4692,22 +3674,20 @@ "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "ejs": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.2.tgz", - "integrity": "sha512-PcW2a0tyTuPHz3tWyYqtK6r1fZ3gp+3Sop8Ph+ZYN81Ob5rwmbHEzaqs10N3BEsaGTkh/ooniXK+WwszGlc2+Q==" + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true }, "electron-to-chromium": { - "version": "1.3.174", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.174.tgz", - "integrity": "sha512-OEh3EARo2B07ZRtxB0u9GqWyWmTeNS+diMp5bjw4kqMjgpzqM0w1zUOyErDsyWxTdArbvZ79T/w5n3WsBVHLfA==" + "version": "1.3.296", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.296.tgz", + "integrity": "sha512-s5hv+TSJSVRsxH190De66YHb50pBGTweT9XGWYu/LMR20KX6TsjFzObo36CjVAzM+PUeeKSBRtm/mISlCzeojQ==", + "dev": true }, "elliptic": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.0.tgz", - "integrity": "sha512-eFOJTMyCYb7xtE/caJ6JJu+bhi67WCYNbkGSknu20pmM8Ke/bqOfdnZWxyoGN26JgfxTbXrsCkEw4KheCT/KGg==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.1.tgz", + "integrity": "sha512-xvJINNLbTeWQjrl6X+7eQCrIy/YPv5XCpKW6kB5mKvtnGILoLDcySuwomfdzt0BMdLNVnuRNTuzKNHj0bva1Cg==", + "dev": true, "requires": { "bn.js": "^4.4.0", "brorand": "^1.0.1", @@ -4721,17 +3701,20 @@ "emoji-regex": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true }, "emojis-list": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", - "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=" + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "dev": true }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true }, "encoding": { "version": "0.1.12", @@ -4742,21 +3725,65 @@ } }, "end-of-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, "requires": { "once": "^1.4.0" } }, "enhanced-resolve": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz", - "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz", + "integrity": "sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA==", + "dev": true, "requires": { "graceful-fs": "^4.1.2", - "memory-fs": "^0.4.0", + "memory-fs": "^0.5.0", "tapable": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "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" + } + }, + "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" + } + } } }, "enquire.js": { @@ -4765,14 +3792,16 @@ "integrity": "sha1-PoeAybi4NQhMP2DhZtvDwqPImBQ=" }, "entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", + "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==", + "dev": true }, "errno": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "dev": true, "requires": { "prr": "~1.0.1" } @@ -4781,27 +3810,34 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, "requires": { "is-arrayish": "^0.2.1" } }, "es-abstract": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", - "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.16.0.tgz", + "integrity": "sha512-xdQnfykZ9JMEiasTAJZJdMWCQ1Vm00NBw79/AWi7ELfZuuPCSOMDZbT9mkOfSctVtfhb+sAAzrm+j//GjjLHLg==", + "dev": true, "requires": { "es-to-primitive": "^1.2.0", "function-bind": "^1.1.1", "has": "^1.0.3", + "has-symbols": "^1.0.0", "is-callable": "^1.1.4", "is-regex": "^1.0.4", - "object-keys": "^1.0.12" + "object-inspect": "^1.6.0", + "object-keys": "^1.1.1", + "string.prototype.trimleft": "^2.1.0", + "string.prototype.trimright": "^2.1.0" } }, "es-to-primitive": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, "requires": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -4811,103 +3847,201 @@ "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "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": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "escodegen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.1.tgz", - "integrity": "sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw==", - "requires": { - "esprima": "^3.1.3", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=" - } - } + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true }, "eslint": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz", - "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "ajv": "^6.9.1", + "ajv": "^6.10.0", "chalk": "^2.1.0", "cross-spawn": "^6.0.5", "debug": "^4.0.1", "doctrine": "^3.0.0", - "eslint-scope": "^4.0.3", - "eslint-utils": "^1.3.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^5.0.1", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", "esquery": "^1.0.1", "esutils": "^2.0.2", "file-entry-cache": "^5.0.1", "functional-red-black-tree": "^1.0.1", - "glob": "^7.1.2", - "globals": "^11.7.0", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", "ignore": "^4.0.6", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", - "inquirer": "^6.2.2", - "js-yaml": "^3.13.0", + "inquirer": "^7.0.0", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.3.0", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "minimatch": "^3.0.4", "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.2", + "optionator": "^0.8.3", "progress": "^2.0.0", "regexpp": "^2.0.1", - "semver": "^5.5.1", - "strip-ansi": "^4.0.0", - "strip-json-comments": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", "table": "^5.2.3", - "text-table": "^0.2.0" + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" }, "dependencies": { - "import-fresh": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "doctrine": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz", - "integrity": "sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ==", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "esutils": "^2.0.2" } }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" - } - } - }, - "eslint-config-react-app": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-4.0.1.tgz", - "integrity": "sha512-ZsaoXUIGsK8FCi/x4lT2bZR5mMkL/Kgj+Lnw690rbvvUr/uiwgFiD8FcfAhkCycm7Xte6O5lYz4EqMx2vX7jgw==", + "eslint-scope": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", + "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "glob-parent": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", + "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.3.0.tgz", + "integrity": "sha512-wAfjdLgFsPZsklLJvOBUBmzYE8/CwhEqSBEMRXA3qxIiNtyqvjYurAtIfDh6chlEPUfmTY3MnZh5Hfh4q0UlIw==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "eslint-config-airbnb": { + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-18.0.1.tgz", + "integrity": "sha512-hLb/ccvW4grVhvd6CT83bECacc+s4Z3/AEyWQdIT2KeTsG9dR7nx1gs7Iw4tDmGKozCNHFn4yZmRm3Tgy+XxyQ==", + "dev": true, "requires": { - "confusing-browser-globals": "^1.0.7" + "eslint-config-airbnb-base": "^14.0.0", + "object.assign": "^4.1.0", + "object.entries": "^1.1.0" + } + }, + "eslint-config-airbnb-base": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.0.0.tgz", + "integrity": "sha512-2IDHobw97upExLmsebhtfoD3NAKhV4H0CJWP3Uprd/uk+cHuWYOczPVxQ8PxLFUAw7o3Th1RAU8u1DoUpr+cMA==", + "dev": true, + "requires": { + "confusing-browser-globals": "^1.0.7", + "object.assign": "^4.1.0", + "object.entries": "^1.1.0" + } + }, + "eslint-config-airbnb-typescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-7.0.0.tgz", + "integrity": "sha512-ki0JvJEdz2E0QWMeDfSgyr7tLwSmTYhMwaZP0XNnBhQfsjAAlLXwpQZHZBIpaoPrc2Fs6pFUTUU39xD3XPXKZQ==", + "dev": true, + "requires": { + "@typescript-eslint/parser": "^2.19.0", + "eslint-config-airbnb": "^18.0.1", + "eslint-config-airbnb-base": "^14.0.0" } }, "eslint-import-resolver-node": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", + "dev": true, "requires": { "debug": "^2.6.9", "resolve": "^1.5.0" @@ -4917,6 +4051,7 @@ "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" } @@ -4924,26 +4059,29 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, - "eslint-loader": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/eslint-loader/-/eslint-loader-2.1.2.tgz", - "integrity": "sha512-rA9XiXEOilLYPOIInvVH5S/hYfyTPyxag6DZhoQOduM+3TkghAEQ3VcFO8VnX4J4qg/UIBzp72aOf/xvYmpmsg==", + "eslint-import-resolver-typescript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.0.0.tgz", + "integrity": "sha512-bT5Frpl8UWoHBtY25vKUOMoVIMlJQOMefHLyQ4Tz3MQpIZ2N6yYKEEIHMo38bszBNUuMBW6M3+5JNYxeiGFH4w==", + "dev": true, "requires": { - "loader-fs-cache": "^1.0.0", - "loader-utils": "^1.0.2", - "object-assign": "^4.0.1", - "object-hash": "^1.1.4", - "rimraf": "^2.6.1" + "debug": "^4.1.1", + "is-glob": "^4.0.1", + "resolve": "^1.12.0", + "tiny-glob": "^0.2.6", + "tsconfig-paths": "^3.9.0" } }, "eslint-module-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.4.0.tgz", - "integrity": "sha512-14tltLm38Eu3zS+mt0KvILC3q8jyIAH518MlG+HO0p+yK885Lb1UHTY/UgR91eOyGdmxAPb+OLoW4znqIT6Ndw==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.4.1.tgz", + "integrity": "sha512-H6DOj+ejw7Tesdgbfs4jeS4YMFrT8uI8xwd1gtQqXssaR0EQ26L+2O/w6wkYFy2MymON0fTwHmXBvvfLNZVZEw==", + "dev": true, "requires": { "debug": "^2.6.8", "pkg-dir": "^2.0.0" @@ -4953,6 +4091,7 @@ "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" } @@ -4961,6 +4100,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, "requires": { "locate-path": "^2.0.0" } @@ -4969,6 +4109,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, "requires": { "p-locate": "^2.0.0", "path-exists": "^3.0.0" @@ -4977,12 +4118,14 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true }, "p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, "requires": { "p-try": "^1.0.0" } @@ -4991,6 +4134,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, "requires": { "p-limit": "^1.1.0" } @@ -4998,166 +4142,69 @@ "p-try": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true }, "pkg-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, "requires": { "find-up": "^2.1.0" } } } }, - "eslint-plugin-flowtype": { - "version": "2.50.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-2.50.1.tgz", - "integrity": "sha512-9kRxF9hfM/O6WGZcZPszOVPd2W0TLHBtceulLTsGfwMPtiCCLnCW0ssRiOOiXyqrCA20pm1iXdXm7gQeN306zQ==", - "requires": { - "lodash": "^4.17.10" - } + "eslint-plugin-eslint-plugin": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-eslint-plugin/-/eslint-plugin-eslint-plugin-2.1.0.tgz", + "integrity": "sha512-kT3A/ZJftt28gbl/Cv04qezb/NQ1dwYIbi8lyf806XMxkus7DvOVCLIfTXMrorp322Pnoez7+zabXH29tADIDg==", + "dev": true }, "eslint-plugin-import": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.16.0.tgz", - "integrity": "sha512-z6oqWlf1x5GkHIFgrSvtmudnqM6Q60KM4KvpWi5ubonMjycLjndvd5+8VAZIsTlHC03djdgJuyKG6XO577px6A==", + "version": "2.18.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.18.2.tgz", + "integrity": "sha512-5ohpsHAiUBRNaBWAF08izwUGlbrJoJJ+W9/TBwsGoR1MnlgfwMIKrFeSjWbt6moabiXW9xNvtFz+97KHRfI4HQ==", + "dev": true, "requires": { + "array-includes": "^3.0.3", "contains-path": "^0.1.0", "debug": "^2.6.9", "doctrine": "1.5.0", "eslint-import-resolver-node": "^0.3.2", - "eslint-module-utils": "^2.3.0", + "eslint-module-utils": "^2.4.0", "has": "^1.0.3", - "lodash": "^4.17.11", "minimatch": "^3.0.4", + "object.values": "^1.1.0", "read-pkg-up": "^2.0.0", - "resolve": "^1.9.0" + "resolve": "^1.11.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" } }, - "doctrine": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", - "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", - "requires": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "requires": { - "locate-path": "^2.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "requires": { - "error-ex": "^1.2.0" - } - }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "requires": { - "pify": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "requires": { - "load-json-file": "^2.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" - } - }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" - } + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, "eslint-plugin-jsx-a11y": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.1.tgz", - "integrity": "sha512-cjN2ObWrRz0TTw7vEcGQrx+YltMvZoOEx4hWU8eEERDnBIU00OTq7Vr+jA7DFKxiwLNv4tTh5Pq2GUNEa8b6+w==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.3.tgz", + "integrity": "sha512-CawzfGt9w83tyuVekn0GDPU9ytYtxyxyFZ3aSWROmnRRFQFT2BiPJd7jvRdzNDi6oLWaS2asMeYSNMjWTV4eNg==", + "dev": true, "requires": { + "@babel/runtime": "^7.4.5", "aria-query": "^3.0.0", "array-includes": "^3.0.3", "ast-types-flow": "^0.0.7", @@ -5165,76 +4212,108 @@ "damerau-levenshtein": "^1.0.4", "emoji-regex": "^7.0.2", "has": "^1.0.3", - "jsx-ast-utils": "^2.0.1" + "jsx-ast-utils": "^2.2.1" } }, "eslint-plugin-react": { - "version": "7.12.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.12.4.tgz", - "integrity": "sha512-1puHJkXJY+oS1t467MjbqjvX53uQ05HXwjqDgdbGBqf5j9eeydI54G3KwiJmWciQ0HTBacIKw2jgwSBSH3yfgQ==", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.17.0.tgz", + "integrity": "sha512-ODB7yg6lxhBVMeiH1c7E95FLD4E/TwmFjltiU+ethv7KPdCwgiFuOZg9zNRHyufStTDLl/dEFqI2Q1VPmCd78A==", + "dev": true, "requires": { "array-includes": "^3.0.3", "doctrine": "^2.1.0", + "eslint-plugin-eslint-plugin": "^2.1.0", "has": "^1.0.3", - "jsx-ast-utils": "^2.0.1", - "object.fromentries": "^2.0.0", - "prop-types": "^15.6.2", - "resolve": "^1.9.0" + "jsx-ast-utils": "^2.2.3", + "object.entries": "^1.1.0", + "object.fromentries": "^2.0.1", + "object.values": "^1.1.0", + "prop-types": "^15.7.2", + "resolve": "^1.13.1" }, "dependencies": { "doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, "requires": { "esutils": "^2.0.2" } + }, + "resolve": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.13.1.tgz", + "integrity": "sha512-CxqObCX8K8YtAhOBRg+lrcdn+LK+WYOS8tSjqSFbjtrI5PnS63QPhZl4+yKfrU9tdsbMu9Anr/amegT87M9Z6w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } } } }, "eslint-plugin-react-hooks": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.6.1.tgz", - "integrity": "sha512-wHhmGJyVuijnYIJXZJHDUF2WM+rJYTjulUTqF9k61d3BTk8etydz+M4dXUVH7M76ZRS85rqBTCx0Es/lLsrjnA==" + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.7.0.tgz", + "integrity": "sha512-iXTCFcOmlWvw4+TOE8CLWj6yX1GwzT0Y6cUfHHZqWnSk144VmVIRcVGtUAzrLES7C798lmvnt02C7rxaOX1HNA==", + "dev": true }, "eslint-scope": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, "requires": { "esrecurse": "^4.1.0", "estraverse": "^4.1.1" } }, "eslint-utils": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", - "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==" + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } }, "eslint-visitor-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", + "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", + "dev": true }, "espree": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", - "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.2.tgz", + "integrity": "sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA==", + "dev": true, "requires": { - "acorn": "^6.0.7", - "acorn-jsx": "^5.0.0", - "eslint-visitor-keys": "^1.0.0" + "acorn": "^7.1.0", + "acorn-jsx": "^5.1.0", + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "acorn": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz", + "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==", + "dev": true + } } }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true }, "esquery": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.1.0.tgz", + "integrity": "sha512-MxYW9xKmROWF672KqjO75sszsA8Mxhw06YFeS5VHlB98KDHbOSurm3ArsjO60Eaf3QmGMCP1yn+0JQkNLo/97Q==", + "dev": true, "requires": { "estraverse": "^4.0.0" } @@ -5243,29 +4322,34 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, "requires": { "estraverse": "^4.1.0" } }, "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=" + "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.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" + "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": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true }, "eventemitter3": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", - "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz", + "integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==", + "dev": true }, "eventlistener": { "version": "0.0.1", @@ -5275,12 +4359,14 @@ "events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", - "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==" + "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==", + "dev": true }, "eventsource": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz", "integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==", + "dev": true, "requires": { "original": "^1.0.0" } @@ -5289,39 +4375,17 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, "requires": { "md5.js": "^1.3.4", "safe-buffer": "^5.1.1" } }, - "exec-sh": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.2.tgz", - "integrity": "sha512-9sLAvzhI5nc8TpuQUh4ahMdCrWT00wPWz7j47/emR5+2qEfoZP5zzUXvx+vdx+H6ohhnsYC31iX04QLYJK8zTg==" - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=" - }, "expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, "requires": { "debug": "^2.3.3", "define-property": "^0.2.5", @@ -5336,6 +4400,7 @@ "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" } @@ -5344,6 +4409,7 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -5352,6 +4418,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -5359,27 +4426,25 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, - "expect": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-24.8.0.tgz", - "integrity": "sha512-/zYvP8iMDrzaaxHVa724eJBCKqSHmO0FA7EDkBiRHxg6OipmMn1fN+C8T9L9K8yr7UONkOifu6+LLH+z76CnaA==", + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "dev": true, "requires": { - "@jest/types": "^24.8.0", - "ansi-styles": "^3.2.0", - "jest-get-type": "^24.8.0", - "jest-matcher-utils": "^24.8.0", - "jest-message-util": "^24.8.0", - "jest-regex-util": "^24.3.0" + "homedir-polyfill": "^1.0.1" } }, "express": { "version": "4.17.1", "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "dev": true, "requires": { "accepts": "~1.3.7", "array-flatten": "1.1.1", @@ -5416,12 +4481,14 @@ "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "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" } @@ -5429,29 +4496,28 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "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": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" - }, - "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true } } }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true }, "extend-shallow": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, "requires": { "assign-symbols": "^1.0.0", "is-extendable": "^1.0.1" @@ -5461,6 +4527,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, "requires": { "is-plain-object": "^2.0.4" } @@ -5468,9 +4535,10 @@ } }, "external-editor": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", - "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, "requires": { "chardet": "^0.7.0", "iconv-lite": "^0.4.24", @@ -5481,6 +4549,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, "requires": { "array-unique": "^0.3.2", "define-property": "^1.0.0", @@ -5496,6 +4565,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, "requires": { "is-descriptor": "^1.0.0" } @@ -5504,6 +4574,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -5512,6 +4583,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -5520,6 +4592,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -5528,68 +4601,48 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", "kind-of": "^6.0.2" } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" } } }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true }, "fast-deep-equal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" - }, - "fast-glob": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz", - "integrity": "sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==", - "requires": { - "@mrmlnc/readdir-enhanced": "^2.2.1", - "@nodelib/fs.stat": "^1.1.2", - "glob-parent": "^3.1.0", - "is-glob": "^4.0.0", - "merge2": "^1.2.3", - "micromatch": "^3.1.10" - } + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true }, "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true }, "faye-websocket": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", - "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "dev": true, "requires": { "websocket-driver": ">=0.5.1" } }, - "fb-watchman": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz", - "integrity": "sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg=", - "requires": { - "bser": "^2.0.0" - } - }, "fbjs": { "version": "0.8.17", "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz", @@ -5614,12 +4667,14 @@ "figgy-pudding": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", - "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==" + "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==", + "dev": true }, "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.1.0.tgz", + "integrity": "sha512-ravh8VRXqHuMvZt/d8GblBeqDMkdJMBdv/2KntFH+ra5MXkO7nxNKpzQ3n6QD/2da1kH0aWmNISdvhM7gl2gVg==", + "dev": true, "requires": { "escape-string-regexp": "^1.0.5" } @@ -5628,28 +4683,16 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, "requires": { "flat-cache": "^2.0.1" } }, - "file-loader": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-3.0.1.tgz", - "integrity": "sha512-4sNIOXgtH/9WZq4NvlfU3Opn5ynUsqBwSLyM+I7UOwdGigTBYfVVQEwe/msZNX/j4pCJTIM14Fsw66Svo1oVrw==", - "requires": { - "loader-utils": "^1.0.2", - "schema-utils": "^1.0.0" - } - }, - "filesize": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz", - "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==" - }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, "requires": { "extend-shallow": "^2.0.1", "is-number": "^3.0.0", @@ -5661,6 +4704,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -5671,6 +4715,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, "requires": { "debug": "2.6.9", "encodeurl": "~1.0.2", @@ -5685,6 +4730,7 @@ "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" } @@ -5692,7 +4738,8 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, @@ -5700,6 +4747,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, "requires": { "commondir": "^1.0.1", "make-dir": "^2.0.0", @@ -5710,51 +4758,113 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, "requires": { "locate-path": "^3.0.0" } }, + "findup-sync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", + "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "dev": true, + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + } + }, "flat-cache": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, "requires": { "flatted": "^2.0.0", "rimraf": "2.6.3", "write": "1.0.3" + }, + "dependencies": { + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } } }, "flatted": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", - "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==" + "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", + "dev": true }, "flatten": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz", - "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.3.tgz", + "integrity": "sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==", + "dev": true }, "flush-write-stream": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "dev": true, "requires": { "inherits": "^2.0.3", "readable-stream": "^2.3.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "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" + } + }, + "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" + } + } } }, "follow-redirects": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.7.0.tgz", - "integrity": "sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.9.0.tgz", + "integrity": "sha512-CRcPzsSIbXyVDl0QI01muNDu69S8trU4jArW9LpOt2WtC6LyUJetcIrmfHsRBx7/Jb6GHJUiuqyYxPooFfNt6A==", + "dev": true, "requires": { - "debug": "^3.2.6" + "debug": "^3.0.0" }, "dependencies": { "debug": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, "requires": { "ms": "^2.1.1" } @@ -5764,40 +4874,20 @@ "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" - }, - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "requires": { - "for-in": "^1.0.1" - } + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - }, - "fork-ts-checker-webpack-plugin": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-1.1.1.tgz", - "integrity": "sha512-gqWAEMLlae/oeVnN6RWCAhesOJMswAN1MaKNqhhjXHV5O0/rTUjWI4UbgQHdlrVbCnb+xLotXmJbBlC66QmpFw==", - "requires": { - "babel-code-frame": "^6.22.0", - "chalk": "^2.4.1", - "chokidar": "^2.0.4", - "micromatch": "^3.1.10", - "minimatch": "^3.0.4", - "semver": "^5.6.0", - "tapable": "^1.0.0", - "worker-rpc": "^0.1.0" - } + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true }, "form-data": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -5807,12 +4897,14 @@ "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "dev": true }, "fragment-cache": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, "requires": { "map-cache": "^0.2.2" } @@ -5820,53 +4912,654 @@ "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true }, "from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "dev": true, "requires": { "inherits": "^2.0.1", "readable-stream": "^2.0.0" - } - }, - "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "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" + } + }, + "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" + } + } + } + }, "fs-write-stream-atomic": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "dev": true, "requires": { "graceful-fs": "^4.1.2", "iferr": "^0.1.5", "imurmurhash": "^0.1.4", "readable-stream": "1 || 2" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "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" + } + }, + "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" + } + } } }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true }, "fsevents": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.0.6.tgz", - "integrity": "sha512-vfmKZp3XPM36DNF0qhW+Cdxk7xm7gTEHY1clv1Xq1arwRQuKZgAhw+NZNWbJBtuaNxzNXwhfdPYRrvIbjfS33A==", - "optional": true + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", + "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.12.1", + "node-pre-gyp": "^0.12.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true, + "optional": true + }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.3.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^4.1.0", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.12.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": 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" + } + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.7.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true, + "dev": true, + "optional": true + } + } }, "fstream": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "dev": true, "requires": { "graceful-fs": "^4.1.2", "inherits": "~2.0.0", @@ -5877,17 +5570,20 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true }, "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, "requires": { "aproba": "^1.0.3", "console-control-strings": "^1.0.0", @@ -5899,15 +5595,11 @@ "wide-align": "^1.1.0" }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5916,19 +5608,12 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", "strip-ansi": "^3.0.0" } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } } } }, @@ -5936,50 +5621,43 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", + "dev": true, "requires": { "globule": "^1.0.0" } }, "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" - }, - "get-own-enumerable-property-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.0.tgz", - "integrity": "sha512-CIJYJC4GGF06TakLg8z4GQKvDsx9EMspVxOYih7LerEL/WosUnFIww45CGfxfeKHqlg3twgUrYRT1O3WQqjGCg==" + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true }, "get-stdin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=" - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "requires": { - "pump": "^3.0.0" - } + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true }, "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, "requires": { "assert-plus": "^1.0.0" } }, "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.5.tgz", + "integrity": "sha512-J9dlskqUXK1OeTOYBEn5s8aMukWMwWfs+rPTn/jn50Ux4MNXVhubL1wu/j2t+H4NVI+cXEcCaYellqaPVGXNqQ==", + "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -5993,6 +5671,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, "requires": { "is-glob": "^3.1.0", "path-dirname": "^1.0.0" @@ -6002,82 +5681,92 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, "requires": { "is-extglob": "^2.1.0" } } } }, - "glob-to-regexp": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", - "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=" - }, "global-modules": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dev": true, "requires": { "global-prefix": "^3.0.0" + }, + "dependencies": { + "global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dev": true, + "requires": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + } + } } }, "global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "dev": true, "requires": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - }, - "dependencies": { - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" - } + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" } }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "globalyzer": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.4.tgz", + "integrity": "sha512-LeguVWaxgHN0MNbWC6YljNMzHkrCny9fzjmEUdnF1kQ7wATFD1RHFRqA1qxaX2tgxGENlcxjOflopBwj3YZiXA==", + "dev": true }, "globby": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.2.tgz", - "integrity": "sha512-yTzMmKygLp8RUpG1Ymu2VXPSJQZjNAZPD4ywgYEaG7e4tBJeUQBO8OpXrf1RCNcEs5alsoJYPAMiIHP0cmeC7w==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, "requires": { "array-union": "^1.0.1", - "dir-glob": "2.0.0", - "fast-glob": "^2.0.2", - "glob": "^7.1.2", - "ignore": "^3.3.5", - "pify": "^3.0.0", - "slash": "^1.0.0" + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" }, "dependencies": { - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==" - }, "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" - }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true } } }, + "globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true + }, "globule": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==", + "dev": true, "requires": { "glob": "~7.1.1", "lodash": "~4.17.10", @@ -6085,36 +5774,16 @@ } }, "graceful-fs": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" - }, - "growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=" + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", + "dev": true }, "gud": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz", "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==" }, - "gzip-size": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.0.0.tgz", - "integrity": "sha512-5iI7omclyqrnWw4XbXAmGhPsABkSIDQonv2K0h61lybgofWa6iZyvrI3r2zsJH4P8Nb64fFVzlvfhs0g7BBxAA==", - "requires": { - "duplexer": "^0.1.1", - "pify": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" - } - } - }, "hammerjs": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", @@ -6123,42 +5792,30 @@ "handle-thing": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.0.tgz", - "integrity": "sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==" - }, - "handlebars": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz", - "integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==", - "requires": { - "neo-async": "^2.6.0", - "optimist": "^0.6.1", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4" - } + "integrity": "sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==", + "dev": true }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true }, "har-validator": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, "requires": { "ajv": "^6.5.5", "har-schema": "^2.0.0" } }, - "harmony-reflect": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.1.tgz", - "integrity": "sha512-WJTeyp0JzGtHcuMsi7rw2VwtkvLa+JyfEKJCFyfcS0+CDkjQ5lHPu7zEhFZP+PDSRrEgXa5Ah0l1MbgbE41XjA==" - }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -6167,36 +5824,34 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, "requires": { "ansi-regex": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - } } }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true }, "has-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=" + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true }, "has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true }, "has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, "requires": { "get-value": "^2.0.6", "has-values": "^1.0.0", @@ -6207,20 +5862,17 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, "requires": { "is-number": "^3.0.0", "kind-of": "^4.0.0" }, "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, "kind-of": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -6231,6 +5883,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "dev": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -6240,66 +5893,36 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, "requires": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" } }, - "hast-util-from-parse5": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-5.0.1.tgz", - "integrity": "sha512-UfPzdl6fbxGAxqGYNThRUhRlDYY7sXu6XU9nQeX4fFZtV+IHbyEJtd+DUuwOqNV4z3K05E/1rIkoVr/JHmeWWA==", - "requires": { - "ccount": "^1.0.3", - "hastscript": "^5.0.0", - "property-information": "^5.0.0", - "web-namespaces": "^1.1.2", - "xtend": "^4.0.1" - } - }, - "hast-util-parse-selector": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.2.tgz", - "integrity": "sha512-jIMtnzrLTjzqgVEQqPEmwEZV+ea4zHRFTP8Z2Utw0I5HuBOXHzUPPQWr6ouJdJqDKLbFU/OEiYwZ79LalZkmmw==" - }, - "hastscript": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-5.1.0.tgz", - "integrity": "sha512-7mOQX5VfVs/gmrOGlN8/EDfp1GqV6P3gTNVt+KnX4gbYhpASTM8bklFdFQCbFRAadURXAmw0R1QQdBdqp7jswQ==", - "requires": { - "comma-separated-tokens": "^1.0.0", - "hast-util-parse-selector": "^2.2.0", - "property-information": "^5.0.1", - "space-separated-tokens": "^1.0.0" - } - }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" - }, - "hex-color-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", - "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==" + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true }, "history": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/history/-/history-4.9.0.tgz", - "integrity": "sha512-H2DkjCjXf0Op9OAr6nJ56fcRkTSNrUiv41vNJ6IswJjif6wlpZK0BTfFbi7qK9dXLSYZxkq5lBsj3vUjlYBYZA==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", "requires": { "@babel/runtime": "^7.1.2", "loose-envify": "^1.2.0", - "resolve-pathname": "^2.2.0", + "resolve-pathname": "^3.0.0", "tiny-invariant": "^1.0.2", "tiny-warning": "^1.0.0", - "value-equal": "^0.4.0" + "value-equal": "^1.0.1" } }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, "requires": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", @@ -6314,54 +5937,76 @@ "react-is": "^16.7.0" } }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "requires": { + "parse-passwd": "^1.0.0" + } + }, "hosted-git-info": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==" + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", + "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==", + "dev": true }, "hpack.js": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", + "dev": true, "requires": { "inherits": "^2.0.1", "obuf": "^1.0.0", "readable-stream": "^2.0.1", "wbuf": "^1.1.0" - } - }, - "hsl-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz", - "integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=" - }, - "hsla-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", - "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=" - }, - "html-comment-regex": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", - "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==" - }, - "html-encoding-sniffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", - "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", - "requires": { - "whatwg-encoding": "^1.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "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" + } + }, + "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": "1.2.1", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", - "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=" + "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=", + "dev": true }, "html-minifier": { "version": "3.5.21", "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.21.tgz", "integrity": "sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA==", + "dev": true, "requires": { "camel-case": "3.0.x", "clean-css": "4.2.x", @@ -6375,27 +6020,57 @@ "commander": { "version": "2.17.1", "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==" + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true } } }, "html-webpack-plugin": { - "version": "4.0.0-beta.5", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.0.0-beta.5.tgz", - "integrity": "sha512-y5l4lGxOW3pz3xBTFdfB9rnnrWRPVxlAhX6nrBYIcW+2k2zC3mSp/3DxlWVCMBfnO6UAnoF8OcFn0IMy6kaKAQ==", - "requires": { - "html-minifier": "^3.5.20", - "loader-utils": "^1.1.0", - "lodash": "^4.17.11", - "pretty-error": "^2.1.1", - "tapable": "^1.1.0", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", + "integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=", + "dev": true, + "requires": { + "html-minifier": "^3.2.3", + "loader-utils": "^0.2.16", + "lodash": "^4.17.3", + "pretty-error": "^2.0.2", + "tapable": "^1.0.0", + "toposort": "^1.0.0", "util.promisify": "1.0.0" + }, + "dependencies": { + "big.js": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", + "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", + "dev": true + }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, + "loader-utils": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "dev": true, + "requires": { + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0", + "object-assign": "^4.0.1" + } + } } }, "htmlparser2": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "dev": true, "requires": { "domelementtype": "^1.3.1", "domhandler": "^2.3.0", @@ -6405,27 +6080,25 @@ "readable-stream": "^3.1.1" }, "dependencies": { - "readable-stream": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", - "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true } } }, "http-deceiver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=" + "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", + "dev": true }, "http-errors": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "dev": true, "requires": { "depd": "~1.1.2", "inherits": "2.0.3", @@ -6437,21 +6110,24 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true } } }, "http-parser-js": { "version": "0.4.10", "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz", - "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=" + "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=", + "dev": true }, "http-proxy": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", - "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.0.tgz", + "integrity": "sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==", + "dev": true, "requires": { - "eventemitter3": "^3.0.0", + "eventemitter3": "^4.0.0", "follow-redirects": "^1.0.0", "requires-port": "^1.0.0" } @@ -6460,6 +6136,7 @@ "version": "0.19.1", "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", + "dev": true, "requires": { "http-proxy": "^1.17.0", "is-glob": "^4.0.0", @@ -6471,6 +6148,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, "requires": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", @@ -6480,7 +6158,8 @@ "https-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=" + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true }, "iconv-lite": { "version": "0.4.24", @@ -6490,53 +6169,40 @@ "safer-buffer": ">= 2.1.2 < 3" } }, - "icss-replace-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", - "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=" - }, "icss-utils": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz", "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==", + "dev": true, "requires": { "postcss": "^7.0.14" } }, - "identity-obj-proxy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz", - "integrity": "sha1-lNK9qWCERT7zb7xarsN+D3nx/BQ=", - "requires": { - "harmony-reflect": "^1.4.6" - } - }, "ieee754": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "dev": true }, "iferr": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=" + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", + "dev": true }, "ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==" + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true }, "image-size": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", + "dev": true, "optional": true }, - "immer": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/immer/-/immer-1.10.0.tgz", - "integrity": "sha512-O3sR1/opvCDGLEVcvrGTMtLac8GJ5IwZC4puPrLuRj3l7ICKvkmA0vGuU9OW8mV9WIBRnaxp5GJh9IEAaNOoYg==" - }, "immutable": { "version": "3.7.6", "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.7.6.tgz", @@ -6546,6 +6212,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", + "dev": true, "requires": { "import-from": "^2.1.0" } @@ -6554,6 +6221,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "dev": true, "requires": { "caller-path": "^2.0.0", "resolve-from": "^3.0.0" @@ -6563,6 +6231,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", + "dev": true, "requires": { "resolve-from": "^3.0.0" } @@ -6571,6 +6240,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "dev": true, "requires": { "pkg-dir": "^3.0.0", "resolve-cwd": "^2.0.0" @@ -6579,17 +6249,20 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true }, "in-publish": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz", - "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=" + "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=", + "dev": true }, "indent-string": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, "requires": { "repeating": "^2.0.0" } @@ -6597,12 +6270,20 @@ "indexes-of": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", - "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=" + "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", + "dev": true + }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -6611,44 +6292,91 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "ini": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true }, "inquirer": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.4.1.tgz", - "integrity": "sha512-/Jw+qPZx4EDYsaT6uz7F4GJRNFMRdKNeUZw3ZnKV8lyuUgz/YWRCSUAJMZSVhSq4Ec0R2oYnyi6b3d4JXcL5Nw==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.4.tgz", + "integrity": "sha512-Bu5Td5+j11sCkqfqmUTiwv+tWisMtP0L7Q8WrqA2C/BbBhy1YTdFrvjjlrKq8oagA/tLQBski2Gcx/Sqyi2qSQ==", + "dev": true, "requires": { - "ansi-escapes": "^3.2.0", + "ansi-escapes": "^4.2.1", "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", + "cli-cursor": "^3.1.0", "cli-width": "^2.0.0", "external-editor": "^3.0.3", - "figures": "^2.0.0", - "lodash": "^4.17.11", - "mute-stream": "0.0.7", + "figures": "^3.0.0", + "lodash": "^4.17.15", + "mute-stream": "0.0.8", "run-async": "^2.2.0", - "rxjs": "^6.4.0", - "string-width": "^2.1.0", + "rxjs": "^6.5.3", + "string-width": "^4.1.0", "strip-ansi": "^5.1.0", "through": "^2.3.6" }, "dependencies": { "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } }, "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, "requires": { "ansi-regex": "^4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + } } } } @@ -6657,11 +6385,18 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==", + "dev": true, "requires": { "default-gateway": "^4.2.0", "ipaddr.js": "^1.9.0" } }, + "interpret": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", + "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", + "dev": true + }, "invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -6673,97 +6408,117 @@ "invert-kv": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==" + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true }, "ip": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "dev": true }, "ip-regex": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=" + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true }, "ipaddr.js": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", - "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" + "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==", + "dev": true }, "is-absolute-url": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", - "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", + "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", + "dev": true }, "is-accessor-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, "requires": { "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, + "is-arguments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", + "dev": true + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true }, "is-binary-path": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, "requires": { "binary-extensions": "^1.0.0" } }, "is-buffer": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", - "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==" + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true }, "is-callable": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==" - }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "requires": { - "ci-info": "^2.0.0" - } - }, - "is-color-stop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", - "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=", - "requires": { - "css-color-names": "^0.0.4", - "hex-color-regex": "^1.1.0", - "hsl-regex": "^1.0.0", - "hsla-regex": "^1.0.0", - "rgb-regex": "^1.0.1", - "rgba-regex": "^1.0.0" - } + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true }, "is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, "requires": { "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "is-date-object": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true }, "is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, "requires": { "is-accessor-descriptor": "^0.1.6", "is-data-descriptor": "^0.1.4", @@ -6773,29 +6528,34 @@ "kind-of": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true } } }, "is-directory": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=" + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", + "dev": true }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true }, "is-finite": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, "requires": { "number-is-nan": "^1.0.0" } @@ -6803,17 +6563,14 @@ "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" - }, - "is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==" + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true }, "is-glob": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, "requires": { "is-extglob": "^2.1.1" } @@ -6822,1260 +6579,153 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, "requires": { "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, - "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" - }, "is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true }, "is-path-in-cwd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", - "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", - "requires": { - "is-path-inside": "^1.0.0" - } - }, - "is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", + "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", + "dev": true, "requires": { - "path-is-inside": "^1.0.1" + "is-path-inside": "^2.1.0" + }, + "dependencies": { + "is-path-inside": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", + "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", + "dev": true, + "requires": { + "path-is-inside": "^1.0.2" + } + } } }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" - }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, "requires": { "isobject": "^3.0.1" } }, "is-promise": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" - }, - "is-regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", - "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", - "requires": { - "has": "^1.0.1" - } - }, - "is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=" - }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==" - }, - "is-root": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-root/-/is-root-2.0.0.tgz", - "integrity": "sha512-F/pJIk8QD6OX5DNhRB7hWamLsUilmkDGho48KbgZ6xg/lmAZXHxzXQ91jzB3yRSw5kdQGGGc4yz8HYhTYIMWPg==" - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" - }, - "is-svg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", - "integrity": "sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ==", - "requires": { - "html-comment-regex": "^1.1.0" - } - }, - "is-symbol": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", - "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", - "requires": { - "has-symbols": "^1.0.0" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" - }, - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "ismobilejs": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/ismobilejs/-/ismobilejs-0.5.2.tgz", - "integrity": "sha512-ta9UdV60xVZk/ZafFtSFslQaE76SvNkcs1r73d2PVR21zVzx9xuYv9tNe4MxA1NN7WoeCc2RjGot3Bz1eHDx3Q==" - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" - }, - "isomorphic-fetch": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", - "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", - "requires": { - "node-fetch": "^1.0.1", - "whatwg-fetch": ">=0.10.0" - } - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "istanbul-lib-coverage": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", - "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==" - }, - "istanbul-lib-instrument": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", - "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", - "requires": { - "@babel/generator": "^7.4.0", - "@babel/parser": "^7.4.3", - "@babel/template": "^7.4.0", - "@babel/traverse": "^7.4.3", - "@babel/types": "^7.4.0", - "istanbul-lib-coverage": "^2.0.5", - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.2.tgz", - "integrity": "sha512-z4PqiCpomGtWj8633oeAdXm1Kn1W++3T8epkZYnwiVgIYIJ0QHszhInYSJTYxebByQH7KVCEAn8R9duzZW2PhQ==" - } - } - }, - "istanbul-lib-report": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", - "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", - "requires": { - "istanbul-lib-coverage": "^2.0.5", - "make-dir": "^2.1.0", - "supports-color": "^6.1.0" - }, - "dependencies": { - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", - "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^2.0.5", - "make-dir": "^2.1.0", - "rimraf": "^2.6.3", - "source-map": "^0.6.1" - } - }, - "istanbul-reports": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.6.tgz", - "integrity": "sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA==", - "requires": { - "handlebars": "^4.1.2" - } - }, - "jest": { - "version": "24.7.1", - "resolved": "https://registry.npmjs.org/jest/-/jest-24.7.1.tgz", - "integrity": "sha512-AbvRar5r++izmqo5gdbAjTeA6uNRGoNRuj5vHB0OnDXo2DXWZJVuaObiGgtlvhKb+cWy2oYbQSfxv7Q7GjnAtA==", - "requires": { - "import-local": "^2.0.0", - "jest-cli": "^24.7.1" - }, - "dependencies": { - "jest-cli": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-24.8.0.tgz", - "integrity": "sha512-+p6J00jSMPQ116ZLlHJJvdf8wbjNbZdeSX9ptfHX06/MSNaXmKihQzx5vQcw0q2G6JsdVkUIdWbOWtSnaYs3yA==", - "requires": { - "@jest/core": "^24.8.0", - "@jest/test-result": "^24.8.0", - "@jest/types": "^24.8.0", - "chalk": "^2.0.1", - "exit": "^0.1.2", - "import-local": "^2.0.0", - "is-ci": "^2.0.0", - "jest-config": "^24.8.0", - "jest-util": "^24.8.0", - "jest-validate": "^24.8.0", - "prompts": "^2.0.1", - "realpath-native": "^1.1.0", - "yargs": "^12.0.2" - } - } - } - }, - "jest-changed-files": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-24.8.0.tgz", - "integrity": "sha512-qgANC1Yrivsq+UrLXsvJefBKVoCsKB0Hv+mBb6NMjjZ90wwxCDmU3hsCXBya30cH+LnPYjwgcU65i6yJ5Nfuug==", - "requires": { - "@jest/types": "^24.8.0", - "execa": "^1.0.0", - "throat": "^4.0.0" - } - }, - "jest-config": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-24.8.0.tgz", - "integrity": "sha512-Czl3Nn2uEzVGsOeaewGWoDPD8GStxCpAe0zOYs2x2l0fZAgPbCr3uwUkgNKV3LwE13VXythM946cd5rdGkkBZw==", - "requires": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^24.8.0", - "@jest/types": "^24.8.0", - "babel-jest": "^24.8.0", - "chalk": "^2.0.1", - "glob": "^7.1.1", - "jest-environment-jsdom": "^24.8.0", - "jest-environment-node": "^24.8.0", - "jest-get-type": "^24.8.0", - "jest-jasmine2": "^24.8.0", - "jest-regex-util": "^24.3.0", - "jest-resolve": "^24.8.0", - "jest-util": "^24.8.0", - "jest-validate": "^24.8.0", - "micromatch": "^3.1.10", - "pretty-format": "^24.8.0", - "realpath-native": "^1.1.0" - }, - "dependencies": { - "jest-resolve": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-24.8.0.tgz", - "integrity": "sha512-+hjSzi1PoRvnuOICoYd5V/KpIQmkAsfjFO71458hQ2Whi/yf1GDeBOFj8Gxw4LrApHsVJvn5fmjcPdmoUHaVKw==", - "requires": { - "@jest/types": "^24.8.0", - "browser-resolve": "^1.11.3", - "chalk": "^2.0.1", - "jest-pnp-resolver": "^1.2.1", - "realpath-native": "^1.1.0" - } - } - } - }, - "jest-diff": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.8.0.tgz", - "integrity": "sha512-wxetCEl49zUpJ/bvUmIFjd/o52J+yWcoc5ZyPq4/W1LUKGEhRYDIbP1KcF6t+PvqNrGAFk4/JhtxDq/Nnzs66g==", - "requires": { - "chalk": "^2.0.1", - "diff-sequences": "^24.3.0", - "jest-get-type": "^24.8.0", - "pretty-format": "^24.8.0" - } - }, - "jest-docblock": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-24.3.0.tgz", - "integrity": "sha512-nlANmF9Yq1dufhFlKG9rasfQlrY7wINJbo3q01tu56Jv5eBU5jirylhF2O5ZBnLxzOVBGRDz/9NAwNyBtG4Nyg==", - "requires": { - "detect-newline": "^2.1.0" - } - }, - "jest-each": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-24.8.0.tgz", - "integrity": "sha512-NrwK9gaL5+XgrgoCsd9svsoWdVkK4gnvyhcpzd6m487tXHqIdYeykgq3MKI1u4I+5Zf0tofr70at9dWJDeb+BA==", - "requires": { - "@jest/types": "^24.8.0", - "chalk": "^2.0.1", - "jest-get-type": "^24.8.0", - "jest-util": "^24.8.0", - "pretty-format": "^24.8.0" - } - }, - "jest-environment-jsdom": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-24.8.0.tgz", - "integrity": "sha512-qbvgLmR7PpwjoFjM/sbuqHJt/NCkviuq9vus9NBn/76hhSidO+Z6Bn9tU8friecegbJL8gzZQEMZBQlFWDCwAQ==", - "requires": { - "@jest/environment": "^24.8.0", - "@jest/fake-timers": "^24.8.0", - "@jest/types": "^24.8.0", - "jest-mock": "^24.8.0", - "jest-util": "^24.8.0", - "jsdom": "^11.5.1" - } - }, - "jest-environment-jsdom-fourteen": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom-fourteen/-/jest-environment-jsdom-fourteen-0.1.0.tgz", - "integrity": "sha512-4vtoRMg7jAstitRzL4nbw83VmGH8Rs13wrND3Ud2o1fczDhMUF32iIrNKwYGgeOPUdfvZU4oy8Bbv+ni1fgVCA==", - "requires": { - "jest-mock": "^24.5.0", - "jest-util": "^24.5.0", - "jsdom": "^14.0.0" - }, - "dependencies": { - "jsdom": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-14.1.0.tgz", - "integrity": "sha512-O901mfJSuTdwU2w3Sn+74T+RnDVP+FuV5fH8tcPWyqrseRAb0s5xOtPgCFiPOtLcyK7CLIJwPyD83ZqQWvA5ng==", - "requires": { - "abab": "^2.0.0", - "acorn": "^6.0.4", - "acorn-globals": "^4.3.0", - "array-equal": "^1.0.0", - "cssom": "^0.3.4", - "cssstyle": "^1.1.1", - "data-urls": "^1.1.0", - "domexception": "^1.0.1", - "escodegen": "^1.11.0", - "html-encoding-sniffer": "^1.0.2", - "nwsapi": "^2.1.3", - "parse5": "5.1.0", - "pn": "^1.1.0", - "request": "^2.88.0", - "request-promise-native": "^1.0.5", - "saxes": "^3.1.9", - "symbol-tree": "^3.2.2", - "tough-cookie": "^2.5.0", - "w3c-hr-time": "^1.0.1", - "w3c-xmlserializer": "^1.1.2", - "webidl-conversions": "^4.0.2", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^7.0.0", - "ws": "^6.1.2", - "xml-name-validator": "^3.0.0" - } - }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "whatwg-url": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", - "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, - "ws": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", - "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", - "requires": { - "async-limiter": "~1.0.0" - } - } - } - }, - "jest-environment-node": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-24.8.0.tgz", - "integrity": "sha512-vIGUEScd1cdDgR6sqn2M08sJTRLQp6Dk/eIkCeO4PFHxZMOgy+uYLPMC4ix3PEfM5Au/x3uQ/5Tl0DpXXZsJ/Q==", - "requires": { - "@jest/environment": "^24.8.0", - "@jest/fake-timers": "^24.8.0", - "@jest/types": "^24.8.0", - "jest-mock": "^24.8.0", - "jest-util": "^24.8.0" - } - }, - "jest-get-type": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.8.0.tgz", - "integrity": "sha512-RR4fo8jEmMD9zSz2nLbs2j0zvPpk/KCEz3a62jJWbd2ayNo0cb+KFRxPHVhE4ZmgGJEQp0fosmNz84IfqM8cMQ==" - }, - "jest-haste-map": { - "version": "24.8.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.8.1.tgz", - "integrity": "sha512-SwaxMGVdAZk3ernAx2Uv2sorA7jm3Kx+lR0grp6rMmnY06Kn/urtKx1LPN2mGTea4fCT38impYT28FfcLUhX0g==", - "requires": { - "@jest/types": "^24.8.0", - "anymatch": "^2.0.0", - "fb-watchman": "^2.0.0", - "fsevents": "^1.2.7", - "graceful-fs": "^4.1.15", - "invariant": "^2.2.4", - "jest-serializer": "^24.4.0", - "jest-util": "^24.8.0", - "jest-worker": "^24.6.0", - "micromatch": "^3.1.10", - "sane": "^4.0.3", - "walker": "^1.0.7" - }, - "dependencies": { - "fsevents": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", - "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", - "optional": true, - "requires": { - "nan": "^2.12.1", - "node-pre-gyp": "^0.12.0" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "optional": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.1", - "bundled": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "optional": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "debug": { - "version": "4.1.1", - "bundled": true, - "optional": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.5", - "bundled": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.3", - "bundled": true, - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.1", - "bundled": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true, - "optional": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "optional": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "optional": true - }, - "minipass": { - "version": "2.3.5", - "bundled": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.2.1", - "bundled": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "optional": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.1.1", - "bundled": true, - "optional": true - }, - "needle": { - "version": "2.3.0", - "bundled": true, - "optional": true, - "requires": { - "debug": "^4.1.0", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.12.0", - "bundled": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.0.6", - "bundled": true, - "optional": true - }, - "npm-packlist": { - "version": "1.4.1", - "bundled": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "optional": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.0", - "bundled": true, - "optional": true - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "optional": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "optional": 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" - } - }, - "rimraf": { - "version": "2.6.3", - "bundled": true, - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true, - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "optional": true - }, - "semver": { - "version": "5.7.0", - "bundled": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "optional": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "optional": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "tar": { - "version": "4.4.8", - "bundled": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.3.4", - "minizlib": "^1.1.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "optional": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "yallist": { - "version": "3.0.3", - "bundled": true, - "optional": true - } - } - } - } - }, - "jest-jasmine2": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-24.8.0.tgz", - "integrity": "sha512-cEky88npEE5LKd5jPpTdDCLvKkdyklnaRycBXL6GNmpxe41F0WN44+i7lpQKa/hcbXaQ+rc9RMaM4dsebrYong==", - "requires": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^24.8.0", - "@jest/test-result": "^24.8.0", - "@jest/types": "^24.8.0", - "chalk": "^2.0.1", - "co": "^4.6.0", - "expect": "^24.8.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^24.8.0", - "jest-matcher-utils": "^24.8.0", - "jest-message-util": "^24.8.0", - "jest-runtime": "^24.8.0", - "jest-snapshot": "^24.8.0", - "jest-util": "^24.8.0", - "pretty-format": "^24.8.0", - "throat": "^4.0.0" - } - }, - "jest-leak-detector": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-24.8.0.tgz", - "integrity": "sha512-cG0yRSK8A831LN8lIHxI3AblB40uhv0z+SsQdW3GoMMVcK+sJwrIIyax5tu3eHHNJ8Fu6IMDpnLda2jhn2pD/g==", - "requires": { - "pretty-format": "^24.8.0" - } - }, - "jest-matcher-utils": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-24.8.0.tgz", - "integrity": "sha512-lex1yASY51FvUuHgm0GOVj7DCYEouWSlIYmCW7APSqB9v8mXmKSn5+sWVF0MhuASG0bnYY106/49JU1FZNl5hw==", - "requires": { - "chalk": "^2.0.1", - "jest-diff": "^24.8.0", - "jest-get-type": "^24.8.0", - "pretty-format": "^24.8.0" - } - }, - "jest-message-util": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.8.0.tgz", - "integrity": "sha512-p2k71rf/b6ns8btdB0uVdljWo9h0ovpnEe05ZKWceQGfXYr4KkzgKo3PBi8wdnd9OtNh46VpNIJynUn/3MKm1g==", - "requires": { - "@babel/code-frame": "^7.0.0", - "@jest/test-result": "^24.8.0", - "@jest/types": "^24.8.0", - "@types/stack-utils": "^1.0.1", - "chalk": "^2.0.1", - "micromatch": "^3.1.10", - "slash": "^2.0.0", - "stack-utils": "^1.0.1" - } - }, - "jest-mock": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-24.8.0.tgz", - "integrity": "sha512-6kWugwjGjJw+ZkK4mDa0Df3sDlUTsV47MSrT0nGQ0RBWJbpODDQ8MHDVtGtUYBne3IwZUhtB7elxHspU79WH3A==", - "requires": { - "@jest/types": "^24.8.0" - } - }, - "jest-pnp-resolver": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz", - "integrity": "sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ==" - }, - "jest-regex-util": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.3.0.tgz", - "integrity": "sha512-tXQR1NEOyGlfylyEjg1ImtScwMq8Oh3iJbGTjN7p0J23EuVX1MA8rwU69K4sLbCmwzgCUbVkm0FkSF9TdzOhtg==" - }, - "jest-resolve": { - "version": "24.7.1", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-24.7.1.tgz", - "integrity": "sha512-Bgrc+/UUZpGJ4323sQyj85hV9d+ANyPNu6XfRDUcyFNX1QrZpSoM0kE4Mb2vZMAYTJZsBFzYe8X1UaOkOELSbw==", - "requires": { - "@jest/types": "^24.7.0", - "browser-resolve": "^1.11.3", - "chalk": "^2.0.1", - "jest-pnp-resolver": "^1.2.1", - "realpath-native": "^1.1.0" - } - }, - "jest-resolve-dependencies": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-24.8.0.tgz", - "integrity": "sha512-hyK1qfIf/krV+fSNyhyJeq3elVMhK9Eijlwy+j5jqmZ9QsxwKBiP6qukQxaHtK8k6zql/KYWwCTQ+fDGTIJauw==", - "requires": { - "@jest/types": "^24.8.0", - "jest-regex-util": "^24.3.0", - "jest-snapshot": "^24.8.0" - } - }, - "jest-runner": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-24.8.0.tgz", - "integrity": "sha512-utFqC5BaA3JmznbissSs95X1ZF+d+4WuOWwpM9+Ak356YtMhHE/GXUondZdcyAAOTBEsRGAgH/0TwLzfI9h7ow==", - "requires": { - "@jest/console": "^24.7.1", - "@jest/environment": "^24.8.0", - "@jest/test-result": "^24.8.0", - "@jest/types": "^24.8.0", - "chalk": "^2.4.2", - "exit": "^0.1.2", - "graceful-fs": "^4.1.15", - "jest-config": "^24.8.0", - "jest-docblock": "^24.3.0", - "jest-haste-map": "^24.8.0", - "jest-jasmine2": "^24.8.0", - "jest-leak-detector": "^24.8.0", - "jest-message-util": "^24.8.0", - "jest-resolve": "^24.8.0", - "jest-runtime": "^24.8.0", - "jest-util": "^24.8.0", - "jest-worker": "^24.6.0", - "source-map-support": "^0.5.6", - "throat": "^4.0.0" - }, - "dependencies": { - "jest-resolve": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-24.8.0.tgz", - "integrity": "sha512-+hjSzi1PoRvnuOICoYd5V/KpIQmkAsfjFO71458hQ2Whi/yf1GDeBOFj8Gxw4LrApHsVJvn5fmjcPdmoUHaVKw==", - "requires": { - "@jest/types": "^24.8.0", - "browser-resolve": "^1.11.3", - "chalk": "^2.0.1", - "jest-pnp-resolver": "^1.2.1", - "realpath-native": "^1.1.0" - } - } - } - }, - "jest-runtime": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-24.8.0.tgz", - "integrity": "sha512-Mq0aIXhvO/3bX44ccT+czU1/57IgOMyy80oM0XR/nyD5zgBcesF84BPabZi39pJVA6UXw+fY2Q1N+4BiVUBWOA==", - "requires": { - "@jest/console": "^24.7.1", - "@jest/environment": "^24.8.0", - "@jest/source-map": "^24.3.0", - "@jest/transform": "^24.8.0", - "@jest/types": "^24.8.0", - "@types/yargs": "^12.0.2", - "chalk": "^2.0.1", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.1.15", - "jest-config": "^24.8.0", - "jest-haste-map": "^24.8.0", - "jest-message-util": "^24.8.0", - "jest-mock": "^24.8.0", - "jest-regex-util": "^24.3.0", - "jest-resolve": "^24.8.0", - "jest-snapshot": "^24.8.0", - "jest-util": "^24.8.0", - "jest-validate": "^24.8.0", - "realpath-native": "^1.1.0", - "slash": "^2.0.0", - "strip-bom": "^3.0.0", - "yargs": "^12.0.2" - }, - "dependencies": { - "jest-resolve": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-24.8.0.tgz", - "integrity": "sha512-+hjSzi1PoRvnuOICoYd5V/KpIQmkAsfjFO71458hQ2Whi/yf1GDeBOFj8Gxw4LrApHsVJvn5fmjcPdmoUHaVKw==", - "requires": { - "@jest/types": "^24.8.0", - "browser-resolve": "^1.11.3", - "chalk": "^2.0.1", - "jest-pnp-resolver": "^1.2.1", - "realpath-native": "^1.1.0" - } - } - } - }, - "jest-serializer": { - "version": "24.4.0", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-24.4.0.tgz", - "integrity": "sha512-k//0DtglVstc1fv+GY/VHDIjrtNjdYvYjMlbLUed4kxrE92sIUewOi5Hj3vrpB8CXfkJntRPDRjCrCvUhBdL8Q==" - }, - "jest-snapshot": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-24.8.0.tgz", - "integrity": "sha512-5ehtWoc8oU9/cAPe6fez6QofVJLBKyqkY2+TlKTOf0VllBB/mqUNdARdcjlZrs9F1Cv+/HKoCS/BknT0+tmfPg==", - "requires": { - "@babel/types": "^7.0.0", - "@jest/types": "^24.8.0", - "chalk": "^2.0.1", - "expect": "^24.8.0", - "jest-diff": "^24.8.0", - "jest-matcher-utils": "^24.8.0", - "jest-message-util": "^24.8.0", - "jest-resolve": "^24.8.0", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "pretty-format": "^24.8.0", - "semver": "^5.5.0" - }, - "dependencies": { - "jest-resolve": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-24.8.0.tgz", - "integrity": "sha512-+hjSzi1PoRvnuOICoYd5V/KpIQmkAsfjFO71458hQ2Whi/yf1GDeBOFj8Gxw4LrApHsVJvn5fmjcPdmoUHaVKw==", - "requires": { - "@jest/types": "^24.8.0", - "browser-resolve": "^1.11.3", - "chalk": "^2.0.1", - "jest-pnp-resolver": "^1.2.1", - "realpath-native": "^1.1.0" - } - } - } - }, - "jest-util": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.8.0.tgz", - "integrity": "sha512-DYZeE+XyAnbNt0BG1OQqKy/4GVLPtzwGx5tsnDrFcax36rVE3lTA5fbvgmbVPUZf9w77AJ8otqR4VBbfFJkUZA==", - "requires": { - "@jest/console": "^24.7.1", - "@jest/fake-timers": "^24.8.0", - "@jest/source-map": "^24.3.0", - "@jest/test-result": "^24.8.0", - "@jest/types": "^24.8.0", - "callsites": "^3.0.0", - "chalk": "^2.0.1", - "graceful-fs": "^4.1.15", - "is-ci": "^2.0.0", - "mkdirp": "^0.5.1", - "slash": "^2.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" - } - } - }, - "jest-validate": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-24.8.0.tgz", - "integrity": "sha512-+/N7VOEMW1Vzsrk3UWBDYTExTPwf68tavEPKDnJzrC6UlHtUDU/fuEdXqFoHzv9XnQ+zW6X3qMZhJ3YexfeLDA==", - "requires": { - "@jest/types": "^24.8.0", - "camelcase": "^5.0.0", - "chalk": "^2.0.1", - "jest-get-type": "^24.8.0", - "leven": "^2.1.0", - "pretty-format": "^24.8.0" - } - }, - "jest-watch-typeahead": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-0.3.0.tgz", - "integrity": "sha512-+uOtlppt9ysST6k6ZTqsPI0WNz2HLa8bowiZylZoQCQaAVn7XsVmHhZREkz73FhKelrFrpne4hQQjdq42nFEmA==", - "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.4.1", - "jest-watcher": "^24.3.0", - "slash": "^2.0.0", - "string-length": "^2.0.0", - "strip-ansi": "^5.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "requires": { - "ansi-regex": "^4.1.0" - } - } + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" } }, - "jest-watcher": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-24.8.0.tgz", - "integrity": "sha512-SBjwHt5NedQoVu54M5GEx7cl7IGEFFznvd/HNT8ier7cCAx/Qgu9ZMlaTQkvK22G1YOpcWBLQPFSImmxdn3DAw==", + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, "requires": { - "@jest/test-result": "^24.8.0", - "@jest/types": "^24.8.0", - "@types/yargs": "^12.0.9", - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.1", - "jest-util": "^24.8.0", - "string-length": "^2.0.0" + "has-symbols": "^1.0.0" } }, - "jest-worker": { - "version": "24.6.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.6.0.tgz", - "integrity": "sha512-jDwgW5W9qGNvpI1tNnvajh0a5IE/PuGLFmHk6aR/BZFz8tSgGw17GsDPXAJ6p91IvYDjOw8GpFbvvZGAK+DPQQ==", + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isomorphic-fetch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", + "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", "requires": { - "merge-stream": "^1.0.1", - "supports-color": "^6.1.0" - }, - "dependencies": { - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "requires": { - "has-flag": "^3.0.0" - } - } + "node-fetch": "^1.0.1", + "whatwg-fetch": ">=0.10.0" } }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, "js-base64": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz", - "integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==" + "integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==", + "dev": true }, "js-levenshtein": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", - "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==" + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "dev": true }, "js-tokens": { "version": "4.0.0", @@ -8086,6 +6736,7 @@ "version": "3.13.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -8094,90 +6745,44 @@ "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" - }, - "jsdom": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", - "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", - "requires": { - "abab": "^2.0.0", - "acorn": "^5.5.3", - "acorn-globals": "^4.1.0", - "array-equal": "^1.0.0", - "cssom": ">= 0.3.2 < 0.4.0", - "cssstyle": "^1.0.0", - "data-urls": "^1.0.0", - "domexception": "^1.0.1", - "escodegen": "^1.9.1", - "html-encoding-sniffer": "^1.0.2", - "left-pad": "^1.3.0", - "nwsapi": "^2.0.7", - "parse5": "4.0.0", - "pn": "^1.1.0", - "request": "^2.87.0", - "request-promise-native": "^1.0.5", - "sax": "^1.2.4", - "symbol-tree": "^3.2.2", - "tough-cookie": "^2.3.4", - "w3c-hr-time": "^1.0.1", - "webidl-conversions": "^4.0.2", - "whatwg-encoding": "^1.0.3", - "whatwg-mimetype": "^2.1.0", - "whatwg-url": "^6.4.1", - "ws": "^5.2.0", - "xml-name-validator": "^3.0.0" - }, - "dependencies": { - "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==" - }, - "parse5": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", - "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==" - } - } + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true }, "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true }, "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "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==" - }, - "json-stable-stringify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "requires": { - "jsonify": "~0.0.0" - } + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=" + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true }, "json2mq": { "version": "0.2.0", @@ -8190,40 +6795,46 @@ "json3": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz", - "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==" + "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==", + "dev": true }, "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.1.tgz", + "integrity": "sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ==", + "dev": true, "requires": { "minimist": "^1.2.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - } } }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "jsonp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/jsonp/-/jsonp-0.2.1.tgz", + "integrity": "sha1-pltPoPEL2nGaBUQep7lMVfPhW64=", "requires": { - "graceful-fs": "^4.1.6" + "debug": "^2.1.3" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } } }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" - }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -8232,9 +6843,10 @@ } }, "jsx-ast-utils": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.0.tgz", - "integrity": "sha512-yAmhGSzR7TsD0OQpu1AGLz8Bx84cxMqtgoJrufomY6BlveEDlREhvu1rea21936xbe5tlUh7IPda82m5ae0H8Q==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz", + "integrity": "sha512-EdIHFMm+1BPynpKOpdPqiOsvnIrInRGJD7bzPZdPkjitQEqpdpUuFpq4T0npZFKTiB3RhWFdGN+oqOJIdhDhQA==", + "dev": true, "requires": { "array-includes": "^3.0.3", "object.assign": "^4.1.0" @@ -8243,59 +6855,29 @@ "killable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", - "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==" + "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==", + "dev": true }, "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - }, - "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - } - } - }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==" - }, - "last-call-webpack-plugin": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz", - "integrity": "sha512-7KI2l2GIZa9p2spzPIVZBYyNKkN+e/SQPpnjlTiPhdbDW3F86tdKKELxKpzJ5sgU19wQWsACULZmpTPYHeWO5w==", - "requires": { - "lodash": "^4.17.5", - "webpack-sources": "^1.1.0" - } - }, - "lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true }, "lcid": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, "requires": { "invert-kv": "^2.0.0" } }, - "left-pad": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", - "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==" - }, "less": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/less/-/less-3.9.0.tgz", - "integrity": "sha512-31CmtPEZraNUtuUREYjSqRkeETFdyEHSEPAGq4erDlUXtda7pzNmctdljdIagSb589d/qXGWiiP31R5JVf+v0w==", + "version": "3.10.3", + "resolved": "https://registry.npmjs.org/less/-/less-3.10.3.tgz", + "integrity": "sha512-vz32vqfgmoxF1h3K4J+yKCtajH0PWmjkIFgbs5d78E/c/e+UQTnI+lWK+1eQRE95PXM2mC3rJlLSSP9VQHnaow==", + "dev": true, "requires": { "clone": "^2.1.2", "errno": "^0.1.1", @@ -8306,200 +6888,146 @@ "promise": "^7.1.1", "request": "^2.83.0", "source-map": "~0.6.0" + }, + "dependencies": { + "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, + "optional": true + } } }, "less-loader": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-5.0.0.tgz", "integrity": "sha512-bquCU89mO/yWLaUq0Clk7qCsKhsF/TZpJUzETRvJa9KSVEL9SO3ovCvdEHISBhrC81OwC8QSVX7E0bzElZj9cg==", + "dev": true, "requires": { "clone": "^2.1.1", "loader-utils": "^1.1.0", "pify": "^4.0.1" } }, - "leven": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", - "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=" - }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, "requires": { "prelude-ls": "~1.1.2", "type-check": "~0.3.2" } }, "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, "requires": { "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", + "parse-json": "^2.2.0", + "pify": "^2.0.0", "strip-bom": "^3.0.0" }, "dependencies": { "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" - } - } - }, - "loader-fs-cache": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/loader-fs-cache/-/loader-fs-cache-1.0.2.tgz", - "integrity": "sha512-70IzT/0/L+M20jUlEqZhZyArTU6VKLRTYRDAYN26g4jfzpJqjipLL3/hgYpySqI9PwsVRHHFja0LfEmsx9X2Cw==", - "requires": { - "find-cache-dir": "^0.1.1", - "mkdirp": "0.5.1" - }, - "dependencies": { - "find-cache-dir": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-0.1.1.tgz", - "integrity": "sha1-yN765XyKUqinhPnjHFfHQumToLk=", - "requires": { - "commondir": "^1.0.1", - "mkdirp": "^0.5.1", - "pkg-dir": "^1.0.0" - } - }, - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "requires": { - "pinkie-promise": "^2.0.0" - } - }, - "pkg-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", - "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", - "requires": { - "find-up": "^1.0.0" - } + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true } } }, "loader-runner": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", - "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==" + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", + "dev": true }, "loader-utils": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "dev": true, "requires": { "big.js": "^5.2.2", "emojis-list": "^2.0.0", "json5": "^1.0.1" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + } } }, "locate-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, "requires": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, "lodash-es": { "version": "4.17.15", "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.15.tgz", "integrity": "sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==" }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" - }, "lodash._reinterpolate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", - "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=" + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", + "dev": true + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true }, "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" }, - "lodash.flow": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz", - "integrity": "sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=" - }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" - }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" - }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "requires": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" - }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" - }, - "lodash.tail": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz", - "integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=" + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", + "dev": true }, "lodash.template": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.4.0.tgz", - "integrity": "sha1-5zoDhcg1VZF0bgILmWecaQ5o+6A=", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", + "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", + "dev": true, "requires": { - "lodash._reinterpolate": "~3.0.0", + "lodash._reinterpolate": "^3.0.0", "lodash.templatesettings": "^4.0.0" } }, "lodash.templatesettings": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.1.0.tgz", - "integrity": "sha1-K01OlbpEDZFf8IvImeRVNmZxMxY=", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", + "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", + "dev": true, "requires": { - "lodash._reinterpolate": "~3.0.0" + "lodash._reinterpolate": "^3.0.0" } }, "lodash.throttle": { @@ -8507,20 +7035,11 @@ "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" }, - "lodash.unescape": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz", - "integrity": "sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=" - }, - "lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" - }, "loglevel": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.3.tgz", - "integrity": "sha512-LoEDv5pgpvWgPF4kNYuIp0qqSJVWak/dML0RY74xlzMZiT9w77teNAwKYKWBTYjlokMirg+o3jBwp+vlLrcfAA==" + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.4.tgz", + "integrity": "sha512-p0b6mOGKcGa+7nnmKbpzR6qloPbrgLcnio++E+14Vo/XffOGwZtRpUhr8dTH/x2oCMmEoIU0Zwm3ZauhvYD17g==", + "dev": true }, "loose-envify": { "version": "1.4.0", @@ -8534,6 +7053,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, "requires": { "currently-unhandled": "^0.4.1", "signal-exit": "^3.0.0" @@ -8542,42 +7062,40 @@ "lower-case": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", - "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=" + "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", + "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==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, "requires": { - "yallist": "^3.0.2" + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" } }, "make-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, "requires": { "pify": "^4.0.1", "semver": "^5.6.0" } }, - "makeerror": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", - "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", - "requires": { - "tmpl": "1.0.x" - } - }, "mamacro": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz", - "integrity": "sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==" + "integrity": "sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==", + "dev": true }, "map-age-cleaner": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, "requires": { "p-defer": "^1.0.0" } @@ -8585,17 +7103,20 @@ "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true }, "map-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true }, "map-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, "requires": { "object-visit": "^1.0.0" } @@ -8604,6 +7125,7 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, "requires": { "hash-base": "^3.0.0", "inherits": "^2.0.1", @@ -8611,45 +7133,75 @@ } }, "mdn-data": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-1.1.4.tgz", - "integrity": "sha512-FSYbp3lyKjyj3E7fMl6rYvUdX0FBXaluGqlFoYESWQlyUTq8R+wp0rkFxoYFqZlHCvsUXGjyJmLQSnXToYhOSA==" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", + "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", + "dev": true }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true }, "mem": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "dev": true, "requires": { "map-age-cleaner": "^0.1.1", "mimic-fn": "^2.0.0", "p-is-promise": "^2.0.0" - }, - "dependencies": { - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" - } } }, "memory-fs": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dev": true, "requires": { "errno": "^0.1.3", "readable-stream": "^2.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "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" + } + }, + "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" + } + } } }, "meow": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, "requires": { "camelcase-keys": "^2.0.0", "decamelize": "^1.1.2", @@ -8667,6 +7219,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, "requires": { "path-exists": "^2.0.0", "pinkie-promise": "^2.0.0" @@ -8676,6 +7229,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, "requires": { "graceful-fs": "^4.1.2", "parse-json": "^2.2.0", @@ -8684,23 +7238,11 @@ "strip-bom": "^2.0.0" } }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "requires": { - "error-ex": "^1.2.0" - } - }, "path-exists": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, "requires": { "pinkie-promise": "^2.0.0" } @@ -8709,6 +7251,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, "requires": { "graceful-fs": "^4.1.2", "pify": "^2.0.0", @@ -8718,12 +7261,14 @@ "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true }, "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, "requires": { "load-json-file": "^1.0.0", "normalize-package-data": "^2.3.2", @@ -8734,6 +7279,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, "requires": { "find-up": "^1.0.0", "read-pkg": "^1.0.0" @@ -8743,54 +7289,30 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, "requires": { "is-utf8": "^0.2.0" } } } }, - "merge-deep": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.2.tgz", - "integrity": "sha512-T7qC8kg4Zoti1cFd8Cr0M+qaZfOwjlPDEdZIIPPB2JZctjaPM4fX+i7HOId69tAti2fvO6X5ldfYUONDODsrkA==", - "requires": { - "arr-union": "^3.1.0", - "clone-deep": "^0.2.4", - "kind-of": "^3.0.2" - } - }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "merge-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", - "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", - "requires": { - "readable-stream": "^2.0.1" - } - }, - "merge2": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.3.tgz", - "integrity": "sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA==" + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" - }, - "microevent.ts": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/microevent.ts/-/microevent.ts-0.1.1.tgz", - "integrity": "sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g==" + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, "requires": { "arr-diff": "^4.0.0", "array-unique": "^0.3.2", @@ -8805,19 +7327,13 @@ "regex-not": "^1.0.0", "snapdragon": "^0.8.1", "to-regex": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" - } } }, "miller-rabin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, "requires": { "bn.js": "^4.0.0", "brorand": "^1.0.1" @@ -8826,25 +7342,29 @@ "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true }, "mime-db": { "version": "1.40.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", + "dev": true }, "mime-types": { "version": "2.1.24", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "dev": true, "requires": { "mime-db": "1.40.0" } }, "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true }, "mini-create-react-context": { "version": "0.3.2", @@ -8856,16 +7376,6 @@ "tiny-warning": "^1.0.2" } }, - "mini-css-extract-plugin": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.5.0.tgz", - "integrity": "sha512-IuaLjruM0vMKhUUT51fQdQzBYTX49dLj8w68ALEAe2A4iYNpIC4eMac67mt3NzycvjOlf07/kYxJDc0RTl1Wqw==", - "requires": { - "loader-utils": "^1.1.0", - "schema-utils": "^1.0.0", - "webpack-sources": "^1.1.0" - } - }, "mini-store": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mini-store/-/mini-store-2.0.0.tgz", @@ -8887,30 +7397,35 @@ "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==" + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true }, "minimalistic-crypto-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true }, "mississippi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "dev": true, "requires": { "concat-stream": "^1.5.0", "duplexify": "^3.4.2", @@ -8928,6 +7443,7 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, "requires": { "for-in": "^1.0.2", "is-extendable": "^1.0.1" @@ -8937,34 +7453,28 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, "requires": { "is-plain-object": "^2.0.4" } } } }, - "mixin-object": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", - "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", - "requires": { - "for-in": "^0.1.3", - "is-extendable": "^0.1.1" - }, - "dependencies": { - "for-in": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", - "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=" - } - } - }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, "requires": { "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } } }, "moment": { @@ -8976,6 +7486,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "dev": true, "requires": { "aproba": "^1.1.1", "copy-concurrently": "^1.0.0", @@ -8988,12 +7499,14 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "multicast-dns": { "version": "6.2.3", "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", + "dev": true, "requires": { "dns-packet": "^1.3.1", "thunky": "^1.0.2" @@ -9002,7 +7515,8 @@ "multicast-dns-service-types": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", - "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=" + "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", + "dev": true }, "mutationobserver-shim": { "version": "0.3.3", @@ -9010,19 +7524,22 @@ "integrity": "sha512-gciOLNN8Vsf7YzcqRjKzlAJ6y7e+B86u7i3KXes0xfxx/nfLmozlW1Vn+Sc9x3tPIePFgc1AeIFhtRgkqTjzDQ==" }, "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true }, "nan": { "version": "2.14.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "dev": true }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, "requires": { "arr-diff": "^4.0.0", "array-unique": "^0.3.2", @@ -9035,39 +7552,37 @@ "regex-not": "^1.0.0", "snapdragon": "^0.8.1", "to-regex": "^3.0.1" - }, - "dependencies": { - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" - } } }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=" + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true }, "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "dev": true }, "neo-async": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", - "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==" + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", + "dev": true }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true }, "no-case": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, "requires": { "lower-case": "^1.1.1" } @@ -9082,14 +7597,16 @@ } }, "node-forge": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz", - "integrity": "sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ==" + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz", + "integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==", + "dev": true }, "node-gyp": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==", + "dev": true, "requires": { "fstream": "^1.0.0", "glob": "^7.0.3", @@ -9108,19 +7625,16 @@ "semver": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", - "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=" + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "dev": true } } }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=" - }, "node-libs-browser": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", + "dev": true, "requires": { "assert": "^1.1.1", "browserify-zlib": "^0.2.0", @@ -9147,42 +7661,68 @@ "vm-browserify": "^1.0.1" }, "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "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" + }, + "dependencies": { + "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" + } + } + } } } }, - "node-modules-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", - "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=" - }, - "node-notifier": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.0.tgz", - "integrity": "sha512-SUDEb+o71XR5lXSTyivXd9J7fCloE3SyP4lSgt3lU2oSANiox+SxlNRGPjDKrwU1YN3ix2KN/VGGCg0t01rttQ==", - "requires": { - "growly": "^1.3.0", - "is-wsl": "^1.1.0", - "semver": "^5.5.0", - "shellwords": "^0.1.1", - "which": "^1.3.0" - } - }, "node-releases": { - "version": "1.1.23", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.23.tgz", - "integrity": "sha512-uq1iL79YjfYC0WXoHbC/z28q/9pOl8kSHaXdWmAAc8No+bDwqkZbzIJz55g/MUsPgSGm9LZ7QSUbzTcH5tz47w==", + "version": "1.1.39", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.39.tgz", + "integrity": "sha512-8MRC/ErwNCHOlAFycy9OPca46fQYUjbJRDcZTHVWIGXIjYLM73k70vv3WkYutVnM4cCo4hE0MqBVVZjP6vjISA==", + "dev": true, "requires": { - "semver": "^5.3.0" + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, "node-sass": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.12.0.tgz", - "integrity": "sha512-A1Iv4oN+Iel6EPv77/HddXErL2a+gZ4uBeZUy+a8O35CFYTXhgA8MgLCWBtwpGZdCvTvQ9d+bQxX/QC36GDPpQ==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.13.0.tgz", + "integrity": "sha512-W1XBrvoJ1dy7VsvTAS5q1V45lREbTlZQqFbiHb3R3OTTCma0XBtuG6xZ6Z4506nR4lmHPTqVRwxT6KgtWC97CA==", + "dev": true, "requires": { "async-foreach": "^0.1.3", "chalk": "^1.1.1", @@ -9191,7 +7731,7 @@ "get-stdin": "^4.0.1", "glob": "^7.0.3", "in-publish": "^2.0.0", - "lodash": "^4.17.11", + "lodash": "^4.17.15", "meow": "^3.7.0", "mkdirp": "^0.5.1", "nan": "^2.13.2", @@ -9203,20 +7743,17 @@ "true-case-path": "^1.0.2" }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true }, "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, "requires": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", @@ -9225,41 +7762,11 @@ "supports-color": "^2.0.0" } }, - "cross-spawn": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", - "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", - "requires": { - "lru-cache": "^4.0.1", - "which": "^1.2.9" - } - }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true } } }, @@ -9267,6 +7774,7 @@ "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, "requires": { "abbrev": "1" } @@ -9275,6 +7783,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, "requires": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", @@ -9283,27 +7792,22 @@ } }, "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "requires": { - "remove-trailing-separator": "^1.0.1" - } + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true }, "normalize-range": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=" - }, - "normalize-url": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", - "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==" + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", + "dev": true }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, "requires": { "path-key": "^2.0.0" } @@ -9312,6 +7816,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, "requires": { "are-we-there-yet": "~1.1.2", "console-control-strings": "~1.1.0", @@ -9323,6 +7828,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dev": true, "requires": { "boolbase": "~1.0.0" } @@ -9330,22 +7836,20 @@ "num2fraction": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", - "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=" + "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", + "dev": true }, "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, - "nwsapi": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.4.tgz", - "integrity": "sha512-iGfd9Y6SFdTNldEy2L0GUhcarIutFmk+MPWIn9dmj8NMIup03G08uUF2KGbbmv/Ux4RT0VZJoP/sVbWA6d/VIw==" + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true }, "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true }, "object-assign": { "version": "4.1.1", @@ -9356,6 +7860,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, "requires": { "copy-descriptor": "^0.1.0", "define-property": "^0.2.5", @@ -9366,26 +7871,45 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, "requires": { "is-descriptor": "^0.1.0" } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } } } }, - "object-hash": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.1.tgz", - "integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==" + "object-inspect": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", + "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", + "dev": true + }, + "object-is": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.1.tgz", + "integrity": "sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY=", + "dev": true }, "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true }, "object-visit": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, "requires": { "isobject": "^3.0.0" } @@ -9394,6 +7918,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, "requires": { "define-properties": "^1.1.2", "function-bind": "^1.1.1", @@ -9401,21 +7926,35 @@ "object-keys": "^1.0.11" } }, + "object.entries": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz", + "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, "object.fromentries": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.0.tgz", - "integrity": "sha512-9iLiI6H083uiqUuvzyY6qrlmc/Gz8hLQFOcb/Ri/0xXFkSNS3ctV+CbE6yM2+AnkYfOB3dGjdzC0wrMLIhQICA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.1.tgz", + "integrity": "sha512-PUQv8Hbg3j2QX0IQYv3iAGCbGcu4yY4KQ92/dhA4sFSixBmSmp13UpDLs6jGK8rBtbmhNNIK99LD2k293jpiGA==", + "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.11.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.15.0", "function-bind": "^1.1.1", - "has": "^1.0.1" + "has": "^1.0.3" } }, "object.getownpropertydescriptors": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "dev": true, "requires": { "define-properties": "^1.1.2", "es-abstract": "^1.5.1" @@ -9425,6 +7964,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, "requires": { "isobject": "^3.0.1" } @@ -9433,6 +7973,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz", "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==", + "dev": true, "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.12.0", @@ -9443,7 +7984,8 @@ "obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true }, "omit.js": { "version": "1.0.2", @@ -9457,6 +7999,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, "requires": { "ee-first": "1.1.1" } @@ -9464,74 +8007,55 @@ "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==" + "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": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, "requires": { "wrappy": "1" } }, "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "dev": true, "requires": { - "mimic-fn": "^1.0.0" + "mimic-fn": "^2.1.0" } }, "opn": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.4.0.tgz", - "integrity": "sha512-YF9MNdVy/0qvJvDtunAOzFw9iasOQHpVthTCvGzxt61Il64AYSGdK+rYwld7NAfk9qJ7dt+hymBNSc9LNYS+Sw==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", + "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", + "dev": true, "requires": { "is-wsl": "^1.1.0" } }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - }, - "dependencies": { - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" - } - } - }, - "optimize-css-assets-webpack-plugin": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.1.tgz", - "integrity": "sha512-Rqm6sSjWtx9FchdP0uzTQDc7GXDKnwVEGoSxjezPkzMewx7gEWE9IMUYKmigTRC4U3RaNSwYVnUDLuIdtTpm0A==", - "requires": { - "cssnano": "^4.1.0", - "last-call-webpack-plugin": "^3.0.0" - } - }, "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, "requires": { "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", + "fast-levenshtein": "~2.0.6", "levn": "~0.3.0", "prelude-ls": "~1.1.2", "type-check": "~0.3.2", - "wordwrap": "~1.0.0" + "word-wrap": "~1.2.3" } }, "original": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", + "dev": true, "requires": { "url-parse": "^1.4.3" } @@ -9539,32 +8063,76 @@ "os-browserify": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=" + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", + "dev": true }, "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true }, "os-locale": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, "requires": { "execa": "^1.0.0", "lcid": "^2.0.0", "mem": "^4.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + } } }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true }, "osenv": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, "requires": { "os-homedir": "^1.0.0", "os-tmpdir": "^1.0.0" @@ -9573,30 +8141,26 @@ "p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=" - }, - "p-each-series": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-1.0.0.tgz", - "integrity": "sha1-kw89Et0fUOdDRFeiLNbwSsatf3E=", - "requires": { - "p-reduce": "^1.0.0" - } + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true }, "p-is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==" + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true }, "p-limit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", - "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", + "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", + "dev": true, "requires": { "p-try": "^2.0.0" } @@ -9605,44 +8169,86 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, "requires": { "p-limit": "^2.0.0" } }, "p-map": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", - "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true }, - "p-reduce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz", - "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=" + "p-retry": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz", + "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==", + "dev": true, + "requires": { + "retry": "^0.12.0" + } }, "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true }, "pako": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", - "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==" + "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==", + "dev": true }, "parallel-transform": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz", - "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", + "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", + "dev": true, "requires": { - "cyclist": "~0.2.2", + "cyclist": "^1.0.1", "inherits": "^2.0.3", "readable-stream": "^2.1.5" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "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" + } + }, + "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" + } + } } }, "param-case": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", + "dev": true, "requires": { "no-case": "^2.2.0" } @@ -9651,6 +8257,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, "requires": { "callsites": "^3.0.0" }, @@ -9658,14 +8265,16 @@ "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true } } }, "parse-asn1": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.4.tgz", - "integrity": "sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", + "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==", + "dev": true, "requires": { "asn1.js": "^4.0.0", "browserify-aes": "^1.0.0", @@ -9676,63 +8285,73 @@ } }, "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" + "error-ex": "^1.2.0" } }, - "parse5": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", - "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==" + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true }, "pascalcase": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true }, "path-browserify": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", - "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==" + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", + "dev": true }, "path-dirname": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true }, "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "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": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true }, "path-is-inside": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true }, "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true }, "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true }, "path-to-regexp": { "version": "1.7.0", @@ -9743,176 +8362,120 @@ } }, "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, "requires": { - "pify": "^3.0.0" + "pify": "^2.0.0" }, "dependencies": { "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true } } }, "pbkdf2": { "version": "3.0.17", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", - "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", - "requires": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "requires": { - "pinkie": "^2.0.0" - } - }, - "pirates": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", - "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", - "requires": { - "node-modules-regexp": "^1.0.0" - } - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "requires": { - "find-up": "^3.0.0" - } - }, - "pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", - "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", - "requires": { - "find-up": "^2.1.0" - }, - "dependencies": { - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "requires": { - "locate-path": "^2.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" - } + "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "dev": true, + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" } }, - "pn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", - "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==" + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, - "pnp-webpack-plugin": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.2.1.tgz", - "integrity": "sha512-W6GctK7K2qQiVR+gYSv/Gyt6jwwIH4vwdviFqx+Y2jAtVf5eZyYIDf5Ac2NCDMBiX5yWscBLZElPTsyA1UtVVA==", + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, "requires": { - "ts-pnp": "^1.0.0" + "find-up": "^3.0.0" } }, "portfinder": { - "version": "1.0.20", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.20.tgz", - "integrity": "sha512-Yxe4mTyDzTd59PZJY4ojZR8F+E5e97iq2ZOHPz3HDgSvYC5siNad2tLooQ5y5QHyQhc3xVqvyk/eNA3wuoa7Sw==", + "version": "1.0.25", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz", + "integrity": "sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg==", + "dev": true, "requires": { - "async": "^1.5.2", - "debug": "^2.2.0", - "mkdirp": "0.5.x" + "async": "^2.6.2", + "debug": "^3.1.1", + "mkdirp": "^0.5.1" }, "dependencies": { "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true }, "postcss": { - "version": "7.0.17", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", - "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "version": "7.0.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.21.tgz", + "integrity": "sha512-uIFtJElxJo29QC753JzhidoAhvp/e/Exezkdhfmt8AymWT6/5B7W1WmponYWkHk2eg6sONyTch0A3nkMPun3SQ==", + "dev": true, "requires": { "chalk": "^2.4.2", "source-map": "^0.6.1", "supports-color": "^6.1.0" }, "dependencies": { + "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 + }, "supports-color": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -9923,6 +8486,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.1.tgz", "integrity": "sha512-L2YKB3vF4PetdTIthQVeT+7YiSzMoNMLLYxPXXppOOP7NoazEAy45sh2LvJ8leCQjfBcfkYQs8TtCcQjeZTp8A==", + "dev": true, "requires": { "postcss": "^7.0.2", "postcss-selector-parser": "^5.0.0" @@ -9931,48 +8495,14 @@ "cssesc": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", - "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==" - }, - "postcss-selector-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", - "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", - "requires": { - "cssesc": "^2.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - } - } - }, - "postcss-browser-comments": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-browser-comments/-/postcss-browser-comments-2.0.0.tgz", - "integrity": "sha512-xGG0UvoxwBc4Yx4JX3gc0RuDl1kc4bVihCzzk6UC72YPfq5fu3c717Nu8Un3nvnq1BJ31gBnFXIG/OaUTnpHgA==", - "requires": { - "postcss": "^7.0.2" - } - }, - "postcss-calc": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.1.tgz", - "integrity": "sha512-oXqx0m6tb4N3JGdmeMSc/i91KppbYsFZKdH0xMOqK8V1rJlzrKlTdokz8ozUXLVejydRN6u2IddxpcijRj2FqQ==", - "requires": { - "css-unit-converter": "^1.1.1", - "postcss": "^7.0.5", - "postcss-selector-parser": "^5.0.0-rc.4", - "postcss-value-parser": "^3.3.1" - }, - "dependencies": { - "cssesc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", - "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==" + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", + "dev": true }, "postcss-selector-parser": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "dev": true, "requires": { "cssesc": "^2.0.0", "indexes-of": "^1.0.1", @@ -9985,6 +8515,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz", "integrity": "sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g==", + "dev": true, "requires": { "postcss": "^7.0.2", "postcss-values-parser": "^2.0.0" @@ -9994,6 +8525,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/postcss-color-gray/-/postcss-color-gray-5.0.0.tgz", "integrity": "sha512-q6BuRnAGKM/ZRpfDascZlIZPjvwsRye7UDNalqVz3s7GDxMtqPY6+Q871liNxsonUw8oC61OG+PSaysYpl1bnw==", + "dev": true, "requires": { "@csstools/convert-colors": "^1.4.0", "postcss": "^7.0.5", @@ -10004,6 +8536,7 @@ "version": "5.0.3", "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.3.tgz", "integrity": "sha512-PF4GDel8q3kkreVXKLAGNpHKilXsZ6xuu+mOQMHWHLPNyjiUBOr75sp5ZKJfmv1MCus5/DWUGcK9hm6qHEnXYw==", + "dev": true, "requires": { "postcss": "^7.0.14", "postcss-values-parser": "^2.0.1" @@ -10013,6 +8546,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/postcss-color-mod-function/-/postcss-color-mod-function-3.0.3.tgz", "integrity": "sha512-YP4VG+xufxaVtzV6ZmhEtc+/aTXH3d0JLpnYfxqTvwZPbJhWqp8bSY3nfNzNRFLgB4XSaBA82OE4VjOOKpCdVQ==", + "dev": true, "requires": { "@csstools/convert-colors": "^1.4.0", "postcss": "^7.0.2", @@ -10023,36 +8557,17 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz", "integrity": "sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g==", + "dev": true, "requires": { "postcss": "^7.0.2", "postcss-values-parser": "^2.0.0" } }, - "postcss-colormin": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz", - "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==", - "requires": { - "browserslist": "^4.0.0", - "color": "^3.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-convert-values": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", - "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", - "requires": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, "postcss-custom-media": { "version": "7.0.8", "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-7.0.8.tgz", "integrity": "sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg==", + "dev": true, "requires": { "postcss": "^7.0.14" } @@ -10061,6 +8576,7 @@ "version": "8.0.11", "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-8.0.11.tgz", "integrity": "sha512-nm+o0eLdYqdnJ5abAJeXp4CEU1c1k+eB2yMCvhgzsds/e0umabFrN6HoTy/8Q4K5ilxERdl/JD1LO5ANoYBeMA==", + "dev": true, "requires": { "postcss": "^7.0.17", "postcss-values-parser": "^2.0.1" @@ -10070,6 +8586,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz", "integrity": "sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w==", + "dev": true, "requires": { "postcss": "^7.0.2", "postcss-selector-parser": "^5.0.0-rc.3" @@ -10078,12 +8595,14 @@ "cssesc": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", - "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==" + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", + "dev": true }, "postcss-selector-parser": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "dev": true, "requires": { "cssesc": "^2.0.0", "indexes-of": "^1.0.1", @@ -10096,6 +8615,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz", "integrity": "sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw==", + "dev": true, "requires": { "postcss": "^7.0.2", "postcss-selector-parser": "^5.0.0-rc.3" @@ -10104,12 +8624,14 @@ "cssesc": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", - "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==" + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", + "dev": true }, "postcss-selector-parser": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "dev": true, "requires": { "cssesc": "^2.0.0", "indexes-of": "^1.0.1", @@ -10118,42 +8640,11 @@ } } }, - "postcss-discard-comments": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", - "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==", - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-discard-duplicates": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", - "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-discard-empty": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", - "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-discard-overridden": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", - "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", - "requires": { - "postcss": "^7.0.0" - } - }, "postcss-double-position-gradients": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-1.0.0.tgz", "integrity": "sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA==", + "dev": true, "requires": { "postcss": "^7.0.5", "postcss-values-parser": "^2.0.0" @@ -10163,23 +8654,17 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-2.0.2.tgz", "integrity": "sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw==", + "dev": true, "requires": { "postcss": "^7.0.2", "postcss-values-parser": "^2.0.0" } }, - "postcss-flexbugs-fixes": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-4.1.0.tgz", - "integrity": "sha512-jr1LHxQvStNNAHlgco6PzY308zvLklh7SJVYuWUwyUQncofaAlD2l+P/gxKHOdqWKe7xJSkVLFF/2Tp+JqMSZA==", - "requires": { - "postcss": "^7.0.0" - } - }, "postcss-focus-visible": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz", "integrity": "sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g==", + "dev": true, "requires": { "postcss": "^7.0.2" } @@ -10188,6 +8673,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz", "integrity": "sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w==", + "dev": true, "requires": { "postcss": "^7.0.2" } @@ -10196,6 +8682,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-4.0.0.tgz", "integrity": "sha512-M8BFYKOvCrI2aITzDad7kWuXXTm0YhGdP9Q8HanmN4EF1Hmcgs1KK5rSHylt/lUJe8yLxiSwWAHdScoEiIxztg==", + "dev": true, "requires": { "postcss": "^7.0.2" } @@ -10204,6 +8691,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz", "integrity": "sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg==", + "dev": true, "requires": { "postcss": "^7.0.2" } @@ -10212,17 +8700,19 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz", "integrity": "sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw==", + "dev": true, "requires": { "postcss": "^7.0.2", "postcss-values-parser": "^2.0.0" } }, "postcss-initial": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-3.0.0.tgz", - "integrity": "sha512-WzrqZ5nG9R9fUtrA+we92R4jhVvEB32IIRTzfIG/PLL8UV4CvbF1ugTEHEFX6vWxl41Xt5RTCJPEZkuWzrOM+Q==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-3.0.2.tgz", + "integrity": "sha512-ugA2wKonC0xeNHgirR4D3VWHs2JcU08WAi1KFLVcnb7IN89phID6Qtg2RIctWbnvp1TM2BOmDtX8GGLCKdR8YA==", + "dev": true, "requires": { - "lodash.template": "^4.2.4", + "lodash.template": "^4.5.0", "postcss": "^7.0.2" } }, @@ -10230,6 +8720,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz", "integrity": "sha512-whLy1IeZKY+3fYdqQFuDBf8Auw+qFuVnChWjmxm/UhHWqNHZx+B99EwxTvGYmUBqe3Fjxs4L1BoZTJmPu6usVg==", + "dev": true, "requires": { "@csstools/convert-colors": "^1.4.0", "postcss": "^7.0.2", @@ -10240,6 +8731,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.0.tgz", "integrity": "sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==", + "dev": true, "requires": { "cosmiconfig": "^5.0.0", "import-cwd": "^2.0.0" @@ -10249,17 +8741,32 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz", "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", + "dev": true, "requires": { "loader-utils": "^1.1.0", "postcss": "^7.0.0", "postcss-load-config": "^2.0.0", "schema-utils": "^1.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } } }, "postcss-logical": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-3.0.0.tgz", "integrity": "sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA==", + "dev": true, "requires": { "postcss": "^7.0.2" } @@ -10268,261 +8775,66 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz", "integrity": "sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw==", + "dev": true, "requires": { "postcss": "^7.0.2" } }, - "postcss-merge-longhand": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", - "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==", - "requires": { - "css-color-names": "0.0.4", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "stylehacks": "^4.0.0" - } - }, - "postcss-merge-rules": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz", - "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==", - "requires": { - "browserslist": "^4.0.0", - "caniuse-api": "^3.0.0", - "cssnano-util-same-parent": "^4.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0", - "vendors": "^1.0.0" - }, - "dependencies": { - "postcss-selector-parser": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", - "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", - "requires": { - "dot-prop": "^4.1.1", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - } - } - }, - "postcss-minify-font-values": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", - "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", - "requires": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-minify-gradients": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz", - "integrity": "sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==", - "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "is-color-stop": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-minify-params": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz", - "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==", - "requires": { - "alphanum-sort": "^1.0.0", - "browserslist": "^4.0.0", - "cssnano-util-get-arguments": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "uniqs": "^2.0.0" - } - }, - "postcss-minify-selectors": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz", - "integrity": "sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==", - "requires": { - "alphanum-sort": "^1.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0" - }, - "dependencies": { - "postcss-selector-parser": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", - "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", - "requires": { - "dot-prop": "^4.1.1", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - } - } - }, "postcss-modules-extract-imports": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", + "dev": true, "requires": { "postcss": "^7.0.5" } }, "postcss-modules-local-by-default": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-2.0.6.tgz", - "integrity": "sha512-oLUV5YNkeIBa0yQl7EYnxMgy4N6noxmiwZStaEJUSe2xPMcdNc8WmBQuQCx18H5psYbVxz8zoHk0RAAYZXP9gA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.2.tgz", + "integrity": "sha512-jM/V8eqM4oJ/22j0gx4jrp63GSvDH6v86OqyTHHUvk4/k1vceipZsaymiZ5PvocqZOl5SFHiFJqjs3la0wnfIQ==", + "dev": true, "requires": { - "postcss": "^7.0.6", - "postcss-selector-parser": "^6.0.0", - "postcss-value-parser": "^3.3.1" + "icss-utils": "^4.1.1", + "postcss": "^7.0.16", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.0.0" } }, "postcss-modules-scope": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.1.0.tgz", "integrity": "sha512-91Rjps0JnmtUB0cujlc8KIKCsJXWjzuxGeT/+Q2i2HXKZ7nBUeF9YQTZZTNvHVoNYj1AthsjnGLtqDUE0Op79A==", + "dev": true, "requires": { "postcss": "^7.0.6", "postcss-selector-parser": "^6.0.0" } }, "postcss-modules-values": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-2.0.0.tgz", - "integrity": "sha512-Ki7JZa7ff1N3EIMlPnGTZfUMe69FFwiQPnVSXC9mnn3jozCRBYIxiZd44yJOV2AmabOo4qFf8s0dC/+lweG7+w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz", + "integrity": "sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==", + "dev": true, "requires": { - "icss-replace-symbols": "^1.1.0", + "icss-utils": "^4.0.0", "postcss": "^7.0.6" } }, "postcss-nesting": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-7.0.0.tgz", - "integrity": "sha512-WSsbVd5Ampi3Y0nk/SKr5+K34n52PqMqEfswu6RtU4r7wA8vSD+gM8/D9qq4aJkHImwn1+9iEFTbjoWsQeqtaQ==", - "requires": { - "postcss": "^7.0.2" - } - }, - "postcss-normalize": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize/-/postcss-normalize-7.0.1.tgz", - "integrity": "sha512-NOp1fwrG+6kVXWo7P9SizCHX6QvioxFD/hZcI2MLxPmVnFJFC0j0DDpIuNw2tUDeCFMni59gCVgeJ1/hYhj2OQ==", - "requires": { - "@csstools/normalize.css": "^9.0.1", - "browserslist": "^4.1.1", - "postcss": "^7.0.2", - "postcss-browser-comments": "^2.0.0" - } - }, - "postcss-normalize-charset": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", - "integrity": "sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==", - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-normalize-display-values": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz", - "integrity": "sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==", - "requires": { - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-normalize-positions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz", - "integrity": "sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA==", - "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-normalize-repeat-style": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz", - "integrity": "sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q==", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-7.0.1.tgz", + "integrity": "sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg==", + "dev": true, "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-normalize-string": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz", - "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==", - "requires": { - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-normalize-timing-functions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz", - "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==", - "requires": { - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-normalize-unicode": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", - "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", - "requires": { - "browserslist": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-normalize-url": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", - "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==", - "requires": { - "is-absolute-url": "^2.0.0", - "normalize-url": "^3.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-normalize-whitespace": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz", - "integrity": "sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==", - "requires": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-ordered-values": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz", - "integrity": "sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==", - "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" + "postcss": "^7.0.2" } }, "postcss-overflow-shorthand": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-2.0.0.tgz", "integrity": "sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g==", + "dev": true, "requires": { "postcss": "^7.0.2" } @@ -10531,6 +8843,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-2.0.0.tgz", "integrity": "sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ==", + "dev": true, "requires": { "postcss": "^7.0.2" } @@ -10539,32 +8852,34 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-4.0.1.tgz", "integrity": "sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg==", + "dev": true, "requires": { "postcss": "^7.0.2", "postcss-values-parser": "^2.0.0" } }, "postcss-preset-env": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-6.6.0.tgz", - "integrity": "sha512-I3zAiycfqXpPIFD6HXhLfWXIewAWO8emOKz+QSsxaUZb9Dp8HbF5kUf+4Wy/AxR33o+LRoO8blEWCHth0ZsCLA==", - "requires": { - "autoprefixer": "^9.4.9", - "browserslist": "^4.4.2", - "caniuse-lite": "^1.0.30000939", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-6.7.0.tgz", + "integrity": "sha512-eU4/K5xzSFwUFJ8hTdTQzo2RBLbDVt83QZrAvI07TULOkmyQlnYlpwep+2yIK+K+0KlZO4BvFcleOCCcUtwchg==", + "dev": true, + "requires": { + "autoprefixer": "^9.6.1", + "browserslist": "^4.6.4", + "caniuse-lite": "^1.0.30000981", "css-blank-pseudo": "^0.1.4", "css-has-pseudo": "^0.10.0", "css-prefers-color-scheme": "^3.1.1", - "cssdb": "^4.3.0", - "postcss": "^7.0.14", + "cssdb": "^4.4.0", + "postcss": "^7.0.17", "postcss-attribute-case-insensitive": "^4.0.1", "postcss-color-functional-notation": "^2.0.1", "postcss-color-gray": "^5.0.0", - "postcss-color-hex-alpha": "^5.0.2", + "postcss-color-hex-alpha": "^5.0.3", "postcss-color-mod-function": "^3.0.3", "postcss-color-rebeccapurple": "^4.0.1", - "postcss-custom-media": "^7.0.7", - "postcss-custom-properties": "^8.0.9", + "postcss-custom-media": "^7.0.8", + "postcss-custom-properties": "^8.0.11", "postcss-custom-selectors": "^5.1.2", "postcss-dir-pseudo-class": "^5.0.0", "postcss-double-position-gradients": "^1.0.0", @@ -10592,6 +8907,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz", "integrity": "sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew==", + "dev": true, "requires": { "postcss": "^7.0.2", "postcss-selector-parser": "^5.0.0-rc.3" @@ -10600,12 +8916,14 @@ "cssesc": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", - "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==" + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", + "dev": true }, "postcss-selector-parser": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "dev": true, "requires": { "cssesc": "^2.0.0", "indexes-of": "^1.0.1", @@ -10614,48 +8932,20 @@ } } }, - "postcss-reduce-initial": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", - "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==", - "requires": { - "browserslist": "^4.0.0", - "caniuse-api": "^3.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0" - } - }, - "postcss-reduce-transforms": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz", - "integrity": "sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg==", - "requires": { - "cssnano-util-get-match": "^4.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, "postcss-replace-overflow-wrap": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz", "integrity": "sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw==", + "dev": true, "requires": { "postcss": "^7.0.2" } }, - "postcss-safe-parser": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-4.0.1.tgz", - "integrity": "sha512-xZsFA3uX8MO3yAda03QrG3/Eg1LN3EPfjjf07vke/46HERLZyHrTsQ9E1r1w1W//fWEhtYNndo2hQplN2cVpCQ==", - "requires": { - "postcss": "^7.0.0" - } - }, "postcss-selector-matches": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz", "integrity": "sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww==", + "dev": true, "requires": { "balanced-match": "^1.0.0", "postcss": "^7.0.2" @@ -10665,51 +8955,34 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-4.0.0.tgz", "integrity": "sha512-W+bkBZRhqJaYN8XAnbbZPLWMvZD1wKTu0UxtFKdhtGjWYmxhkUneoeOhRJKdAE5V7ZTlnbHfCR+6bNwK9e1dTQ==", + "dev": true, "requires": { "balanced-match": "^1.0.0", "postcss": "^7.0.2" } }, - "postcss-selector-parser": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz", - "integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==", - "requires": { - "cssesc": "^3.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - }, - "postcss-svgo": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.2.tgz", - "integrity": "sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw==", - "requires": { - "is-svg": "^3.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "svgo": "^1.0.0" - } - }, - "postcss-unique-selectors": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz", - "integrity": "sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==", + "postcss-selector-parser": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz", + "integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==", + "dev": true, "requires": { - "alphanum-sort": "^1.0.0", - "postcss": "^7.0.0", - "uniqs": "^2.0.0" + "cssesc": "^3.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" } }, "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz", + "integrity": "sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ==", + "dev": true }, "postcss-values-parser": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz", "integrity": "sha512-2tLuBsA6P4rYTNKCXYG/71C7j1pU6pK503suYOmn4xYrQIzW+opD+7FAFNuGSdZC/3Qfy334QbeMu7MEb8gOxg==", + "dev": true, "requires": { "flatten": "^1.0.2", "indexes-of": "^1.0.1", @@ -10719,59 +8992,42 @@ "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" - }, - "pretty-bytes": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.2.0.tgz", - "integrity": "sha512-ujANBhiUsl9AhREUDUEY1GPOharMGm8x8juS7qOHybcLi7XsKfrYQ88hSly1l2i0klXHTDYrlL8ihMCG55Dc3w==" + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true }, "pretty-error": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=", + "dev": true, "requires": { "renderkid": "^2.0.1", "utila": "~0.4" } }, - "pretty-format": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.8.0.tgz", - "integrity": "sha512-P952T7dkrDEplsR+TuY7q3VXDae5Sr7zmQb12JU/NDQa/3CH7/QW0yvqLcGN6jL+zQFKaoJcPc+yJxMTGmosqw==", - "requires": { - "@jest/types": "^24.8.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" - } - } - }, "private": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==" + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "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==" + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true }, "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true }, "promise": { "version": "7.3.1", @@ -10784,16 +9040,8 @@ "promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" - }, - "prompts": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.1.0.tgz", - "integrity": "sha512-+x5TozgqYdOwWsQFZizE/Tra3fKvAoy037kOyU6cgz84n8f6zxngLOV4O32kTwt9FcLCxAqw0P/c8rOr9y+Gfg==", - "requires": { - "kleur": "^3.0.2", - "sisteransi": "^1.0.0" - } + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "dev": true }, "prop-types": { "version": "15.7.2", @@ -10805,18 +9053,11 @@ "react-is": "^16.8.1" } }, - "property-information": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.1.0.tgz", - "integrity": "sha512-tODH6R3+SwTkAQckSp2S9xyYX8dEKYkeXw+4TmJzTxnNzd6mQPu1OD4f9zPrvw/Rm4wpPgI+Zp63mNSGNzUgHg==", - "requires": { - "xtend": "^4.0.1" - } - }, "proxy-addr": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", + "dev": true, "requires": { "forwarded": "~0.1.2", "ipaddr.js": "1.9.0" @@ -10825,22 +9066,26 @@ "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true }, "psl": { - "version": "1.1.33", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.33.tgz", - "integrity": "sha512-LTDP2uSrsc7XCb5lO7A8BI1qYxRe/8EqlRvMeEl6rsnYAqDOl8xHR+8lSAIVfrNaSAlTPTNOCgNjWcoUL3AZsw==" + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.6.0.tgz", + "integrity": "sha512-SYKKmVel98NCOYXpkwUqZqh0ahZeeKfmisiLIcEZdsb+WbLv02g/dI5BUmZnIyOe7RzZtLax81nnb2HbvC2tzA==", + "dev": true }, "public-encrypt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, "requires": { "bn.js": "^4.1.0", "browserify-rsa": "^4.0.0", @@ -10854,6 +9099,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -10863,6 +9109,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, "requires": { "duplexify": "^3.6.0", "inherits": "^2.0.3", @@ -10873,6 +9120,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -10883,42 +9131,38 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true }, "q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true }, "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, - "query-string": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.8.1.tgz", - "integrity": "sha512-g6y0Lbq10a5pPQpjlFuojfMfV1Pd2Jw9h75ypiYPPia3Gcq2rgkKiIwbkS6JxH7c5f5u/B/sB+d13PU+g1eu4Q==", - "requires": { - "decode-uri-component": "^0.2.0", - "split-on-first": "^1.0.0", - "strict-uri-encode": "^2.0.0" - } + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "dev": true }, "querystring": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true }, "querystring-es3": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=" + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true }, "querystringify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", - "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==" + "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==", + "dev": true }, "raf": { "version": "3.4.1", @@ -10932,6 +9176,7 @@ "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" } @@ -10940,6 +9185,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, "requires": { "randombytes": "^2.0.5", "safe-buffer": "^5.1.0" @@ -10948,12 +9194,14 @@ "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==" + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true }, "raw-body": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "dev": true, "requires": { "bytes": "3.1.0", "http-errors": "1.7.2", @@ -10964,7 +9212,8 @@ "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true } } }, @@ -10980,22 +9229,23 @@ } }, "rc-animate": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/rc-animate/-/rc-animate-2.8.3.tgz", - "integrity": "sha512-VPSHJF/PW9zrPVCdQ94/YOI2lFfJVlaiAeQveJN2nlPVMivgvXkuFJyfe42GbZqm+qlnRjH9B4WbY9rCZz9miw==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/rc-animate/-/rc-animate-2.10.2.tgz", + "integrity": "sha512-cE/A7piAzoWFSgUD69NmmMraqCeqVBa51UErod8NS3LUEqWfppSVagHfa0qHAlwPVPiIBg3emRONyny3eiH0Dg==", "requires": { "babel-runtime": "6.x", "classnames": "^2.2.6", "css-animation": "^1.3.2", "prop-types": "15.x", "raf": "^3.4.0", + "rc-util": "^4.15.3", "react-lifecycles-compat": "^3.0.4" } }, "rc-calendar": { - "version": "9.15.1", - "resolved": "https://registry.npmjs.org/rc-calendar/-/rc-calendar-9.15.1.tgz", - "integrity": "sha512-cJcq3DnNYrYvDi0CvoguRf2eBGN/E+svs1Dm3cPEsi4yVhxMjNGdq/5Iyxa84dCOWOJHV5NcecUIasfwvaTuoQ==", + "version": "9.15.8", + "resolved": "https://registry.npmjs.org/rc-calendar/-/rc-calendar-9.15.8.tgz", + "integrity": "sha512-x3zVaZSRX7FkRNKw7nz3tutwrlIrU1aqMn5GtRUmlf84GnXLtd9fuuydxeNkFWfcHry3BPSto7+r9TK2al0h+g==", "requires": { "babel-runtime": "6.x", "classnames": "2.x", @@ -11007,9 +9257,9 @@ } }, "rc-cascader": { - "version": "0.17.4", - "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-0.17.4.tgz", - "integrity": "sha512-CeFQJIMzY7x++uPqlx4Xl/cH8iTs8nRoW522+DLb21kdL5kWqKlK+3iHXExoxcAymjwo5ScIiXi+NY4m8Pgq9w==", + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-0.17.5.tgz", + "integrity": "sha512-WYMVcxU0+Lj+xLr4YYH0+yXODumvNXDcVEs5i7L1mtpWwYkubPV/zbQpn+jGKFCIW/hOhjkU4J1db8/P/UKE7A==", "requires": { "array-tree-filter": "^2.1.0", "prop-types": "^15.5.8", @@ -11021,9 +9271,9 @@ } }, "rc-checkbox": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/rc-checkbox/-/rc-checkbox-2.1.7.tgz", - "integrity": "sha512-8L+0XuucUOMUM6F/7qH+hnQpEHPZfW1Um02lUHEVdpZNor5mC0Fj4x8GvTtwcM1pAl5tD3I6lHYD8cE1W8RZJw==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/rc-checkbox/-/rc-checkbox-2.1.8.tgz", + "integrity": "sha512-6qOgh0/by0nVNASx6LZnhRTy17Etcgav+IrI7kL9V9kcDZ/g7K14JFlqrtJ3NjDq/Kyn+BPI1st1XvbkhfaJeg==", "requires": { "babel-runtime": "^6.23.0", "classnames": "2.x", @@ -11032,37 +9282,38 @@ } }, "rc-collapse": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-1.11.3.tgz", - "integrity": "sha512-yECQX2iDPWnKcVi3Wz5bomZuJ2u+wv+kGxuKo2GIRz7Brh9jkGQz5ElghCV1jqDGnzy8GIRxxHHSwlSgdxdUog==", + "version": "1.11.7", + "resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-1.11.7.tgz", + "integrity": "sha512-ge3EEzIFtrDGuPX4bxXdQqwb91JnPIdj3B+FU88yNOUeOroNuA2q9kVK+UatpQ1Eft5hNo/ICTDrVFi8+685ng==", "requires": { "classnames": "2.x", "css-animation": "1.x", "prop-types": "^15.5.6", "rc-animate": "2.x", "react-is": "^16.7.0", + "react-lifecycles-compat": "^3.0.4", "shallowequal": "^1.1.0" } }, "rc-dialog": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-7.4.1.tgz", - "integrity": "sha512-vvVVP7AUjxs2AEGL5GUr6BjfVzaiBV5RoiPYchCDqHmf8n7xTrfsACAhZ2Vezj6mtl2446zhxoGvhxNpyCyX7A==", + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-7.5.13.tgz", + "integrity": "sha512-tmubIipW/qoCmRlHHV8tpepDaFhuhk+SeSFSyRhNKW4mYgflsEYQmYWilyCJHy6UzKl84bSyFvJskhc1z1Hniw==", "requires": { "babel-runtime": "6.x", "rc-animate": "2.x", - "rc-util": "^4.4.0" + "rc-util": "^4.8.1" } }, "rc-drawer": { - "version": "1.9.9", - "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-1.9.9.tgz", - "integrity": "sha512-4oG0okZ7JhOTnGHRkxhOO1yb1U13v5ocns+40xmfogdD+oVNTKHIamCU1cKVVcMQYWpUCn8aYbawY2JuuGN/pA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-3.0.2.tgz", + "integrity": "sha512-oPScGXB/8/ov9gEFLxPH8RBv/9jLTZboZtyF/GgrrnCAvbFwUxXdELH6n6XIowmuDKKvTGIMgZdnao0T46Yv3A==", "requires": { - "babel-runtime": "6.x", - "classnames": "^2.2.5", - "prop-types": "^15.5.0", - "rc-util": "^4.5.1" + "babel-runtime": "^6.26.0", + "classnames": "^2.2.6", + "rc-util": "^4.11.2", + "react-lifecycles-compat": "^3.0.4" } }, "rc-dropdown": { @@ -11107,16 +9358,17 @@ } }, "rc-form": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/rc-form/-/rc-form-2.4.6.tgz", - "integrity": "sha512-yv+G1X0lJODswtrbrsa5bkicwk8NoIQmsOADYTzGlzOtvlwsdSLS02YEBONSi9tWTACgwFLY0ifERN6CJFF9dg==", + "version": "2.4.10", + "resolved": "https://registry.npmjs.org/rc-form/-/rc-form-2.4.10.tgz", + "integrity": "sha512-h6a5Nvn6fMe3BfLpIWwL2RUkfXs1tvtifblTgGgH0UfzGgiQ5M12jiMJaAXek7TDDBUw90/c5vlZ6wFZjW0IgQ==", "requires": { - "async-validator": "~1.8.5", + "async-validator": "~1.11.3", "babel-runtime": "6.x", "create-react-class": "^15.5.3", "dom-scroll-into-view": "1.x", "hoist-non-react-statics": "^3.3.0", "lodash": "^4.17.4", + "rc-util": "^4.15.3", "warning": "^4.0.3" } }, @@ -11131,9 +9383,9 @@ } }, "rc-input-number": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-4.4.5.tgz", - "integrity": "sha512-Dt20e8Ylc/N/6oXiPUlwDVdx3fz7W5umUOa4z5pBuWFG7NPlBVXRWkq7+nbnTyaK24UxN67PVpmD3+Omo+QRZQ==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-4.5.1.tgz", + "integrity": "sha512-grO7/Lau7iv3NyHVyCajE1LuGLqGkG1tEAAZSwm9M0esYfrwXVSip4qhb5sF+8g6ACsiI20sOVLIihXuhSoifA==", "requires": { "babel-runtime": "6.x", "classnames": "^2.2.0", @@ -11143,12 +9395,11 @@ } }, "rc-mentions": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-0.3.1.tgz", - "integrity": "sha512-fa5dN3IMTahJfAga1nmma9OymK/ZBV/MZfV11h4kjDmCAVETv5EbAlV0mn6Y+JajvXS6n/XFoPUSF+nwK/AeWw==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-0.4.1.tgz", + "integrity": "sha512-XSJp6kcEPydUaM0I/gnxpXggiKgA5FjgFPKZCMQBDQJYUjXpQNyg5ogNkOJt1/4B2P7pwbYPZXgxP/30yZVahA==", "requires": { "@ant-design/create-react-context": "^0.2.4", - "babel-runtime": "^6.23.0", "classnames": "^2.2.6", "rc-menu": "^7.4.22", "rc-trigger": "^2.6.2", @@ -11157,21 +9408,19 @@ } }, "rc-menu": { - "version": "7.4.23", - "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-7.4.23.tgz", - "integrity": "sha512-d0pUMN0Zr3GCFxNpas8p7AUTeX8viItUOQXku4AsyX82ZzUz79HgGul2Nk17BIFTtLzqdB7/NT6WVb5PAOOILw==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-7.5.3.tgz", + "integrity": "sha512-H/jUyGbJxZI/iuVdC6Iu9KHfz7tucoqK0Vn8ahDnv+ppc1PnKb4SkBbXn5LrmUyaj7thCBiaktBxVnUXSmNE2g==", "requires": { - "babel-runtime": "6.x", "classnames": "2.x", "dom-scroll-into-view": "1.x", - "ismobilejs": "^0.5.1", "mini-store": "^2.0.0", "mutationobserver-shim": "^0.3.2", - "prop-types": "^15.5.6", - "rc-animate": "2.x", + "rc-animate": "^2.10.1", "rc-trigger": "^2.3.0", - "rc-util": "^4.1.0", - "resize-observer-polyfill": "^1.5.0" + "rc-util": "^4.13.0", + "resize-observer-polyfill": "^1.5.0", + "shallowequal": "^1.1.0" } }, "rc-notification": { @@ -11187,9 +9436,9 @@ } }, "rc-pagination": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-1.20.1.tgz", - "integrity": "sha512-EC2sxfKo1+R34fDN8EQgFiJn0Z6SKef4O0FizoqV3IomPczSikoarxL1RrHzqeGNsfg7JbUYaux5fQdmUAlPnA==", + "version": "1.20.11", + "resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-1.20.11.tgz", + "integrity": "sha512-2wKO5kO+ELx1/zlqTY8TwGBruzofi+1BcZ7Z4xalMlLbDMTuUU4FDljbBBP/n9D2llK+NtgWA619PMBhInozZw==", "requires": { "babel-runtime": "6.x", "classnames": "^2.2.6", @@ -11198,9 +9447,9 @@ } }, "rc-progress": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-2.3.0.tgz", - "integrity": "sha512-hYBKFSsNgD7jsF8j+ZC1J8y5UIC2X/ktCYI/OQhQNSX6mGV1IXnUCjAd9gbLmzmpChPvKyymRNfckScUNiTpFQ==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-2.5.2.tgz", + "integrity": "sha512-ajI+MJkbBz9zYDuE9GQsY5gsyqPF7HFioZEDZ9Fmc+ebNZoiSeSJsTJImPFCg0dW/5WiRGUy2F69SX1aPtSJgA==", "requires": { "babel-runtime": "6.x", "prop-types": "^15.5.8" @@ -11217,10 +9466,20 @@ "react-lifecycles-compat": "^3.0.4" } }, + "rc-resize-observer": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/rc-resize-observer/-/rc-resize-observer-0.1.3.tgz", + "integrity": "sha512-uzOQEwx83xdQSFOkOAM7x7GHIQKYnrDV4dWxtCxyG1BS1pkfJ4EvDeMfsvAJHSYkQXVBu+sgRHGbRtLG3qiuUg==", + "requires": { + "classnames": "^2.2.1", + "rc-util": "^4.13.0", + "resize-observer-polyfill": "^1.5.1" + } + }, "rc-select": { - "version": "9.1.5", - "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-9.1.5.tgz", - "integrity": "sha512-P2QDl5xSdrYuvODnwZIKxhBv2AzfsuFNfaoXjRsPTlQvOjLMCGYgyRzZ4xdUy1IAc1yER6LV+g7e4N9Qc+3DDQ==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-9.2.1.tgz", + "integrity": "sha512-nW/Zr2OCgxN26OX8ff3xcO1wK0e1l5ixnEfyN15Rbdk7TNI/rIPJIjPCQAoihRpk9A2C/GH8pahjlvKV1Vj++g==", "requires": { "babel-runtime": "^6.23.0", "classnames": "2.x", @@ -11237,23 +9496,24 @@ } }, "rc-slider": { - "version": "8.6.13", - "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-8.6.13.tgz", - "integrity": "sha512-fCUe8pPn8n9pq1ARX44nN2nzJoATtna4x/PdskUrxIvZXN8ja7HuceN/hq6kokZjo3FBD2B1yMZvZh6oi68l6Q==", + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-8.7.1.tgz", + "integrity": "sha512-WMT5mRFUEcrLWwTxsyS8jYmlaMsTVCZIGENLikHsNv+tE8ThU2lCoPfi/xFNUfJFNFSBFP3MwPez9ZsJmNp13g==", "requires": { "babel-runtime": "6.x", "classnames": "^2.2.5", "prop-types": "^15.5.4", "rc-tooltip": "^3.7.0", "rc-util": "^4.0.4", - "shallowequal": "^1.0.1", + "react-lifecycles-compat": "^3.0.4", + "shallowequal": "^1.1.0", "warning": "^4.0.3" } }, "rc-steps": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/rc-steps/-/rc-steps-3.4.1.tgz", - "integrity": "sha512-zdeOFmFqiXlXCQyHet1qrDDbGKZ7OQTrlzn8DP5N6M/WqN7HaYoUDy1fZ+NY2htL5WzzVFQpDRKzjiOiHaSqgw==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/rc-steps/-/rc-steps-3.5.0.tgz", + "integrity": "sha512-2Vkkrpa7PZbg7qPsqTNzVDov4u78cmxofjjnIHiGB9+9rqKS8oTLPzbW2uiWDr3Lk+yGwh8rbpGO1E6VAgBCOg==", "requires": { "babel-runtime": "^6.23.0", "classnames": "^2.2.3", @@ -11272,69 +9532,49 @@ } }, "rc-table": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-6.6.7.tgz", - "integrity": "sha512-NC6kaHl6q/Eed0THGVSFLfc0xRcctiVEv63oDCrpYRTJPCTfG9q5APDLzwh44dKDAfXHt2JCJ5QtRJH8zaM2IQ==", + "version": "6.9.5", + "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-6.9.5.tgz", + "integrity": "sha512-STL6387A/izVh6r9F1WDiIIZ0QeubCdTgIlzMeGTSl/bXhB0VqjAZEikvoijPoauTjJIkIzVuQEIDjOhAWbpkQ==", "requires": { - "babel-runtime": "6.x", "classnames": "^2.2.5", "component-classes": "^1.2.6", "lodash": "^4.17.5", "mini-store": "^2.0.0", "prop-types": "^15.5.8", - "rc-util": "^4.0.4", + "rc-util": "^4.13.0", "react-lifecycles-compat": "^3.0.2", - "shallowequal": "^1.0.2", - "warning": "^3.0.0" - }, - "dependencies": { - "warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", - "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", - "requires": { - "loose-envify": "^1.0.0" - } - } + "shallowequal": "^1.0.2" } }, "rc-tabs": { - "version": "9.6.4", - "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-9.6.4.tgz", - "integrity": "sha512-l4PoDSShNJ6pWGuR1UcUgvee48b3Qu1jgMEaD1hH3Rc+mqysoO7hA9AQ1YywkIy34afGTTejAWDSIFZ0lmg08g==", + "version": "9.6.7", + "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-9.6.7.tgz", + "integrity": "sha512-OXbDOgaqv2MGK9QaDi6cdva6bNz3XGw+M9BHQpm1gTGmVQEGx5VcclDClH/3xobIzooxy8hrxg/s0rTlgDnC2w==", "requires": { + "@ant-design/create-react-context": "^0.2.4", "babel-runtime": "6.x", "classnames": "2.x", - "create-react-context": "0.2.2", "lodash": "^4.17.5", "prop-types": "15.x", "raf": "^3.4.1", "rc-hammerjs": "~0.6.0", "rc-util": "^4.0.4", + "react-lifecycles-compat": "^3.0.4", "resize-observer-polyfill": "^1.5.1", - "warning": "^3.0.0" - }, - "dependencies": { - "warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", - "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", - "requires": { - "loose-envify": "^1.0.0" - } - } + "warning": "^4.0.3" } }, "rc-time-picker": { - "version": "3.6.6", - "resolved": "https://registry.npmjs.org/rc-time-picker/-/rc-time-picker-3.6.6.tgz", - "integrity": "sha512-NVeJuxWjg9eJ0+jcCCT2dxVY2OBYxOrjsgu8ly0lk9IUJ8lwjS6JU+OibHRPJPew3Smfz88dz7GQRdBE7BcnRA==", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/rc-time-picker/-/rc-time-picker-3.7.2.tgz", + "integrity": "sha512-UVWO9HXGyZoM4I2THlJsEAFcZQz+tYwdcpoHXCEFZsRLz9L2+7vV4EMp9Wa3UrtzMFEt83qSAX/90dCJeKl9sg==", "requires": { "classnames": "2.x", "moment": "2.x", "prop-types": "^15.5.8", "raf": "^3.4.1", - "rc-trigger": "^2.2.0" + "rc-trigger": "^2.2.0", + "react-lifecycles-compat": "^3.0.4" } }, "rc-tooltip": { @@ -11348,11 +9588,11 @@ } }, "rc-tree": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-2.1.0.tgz", - "integrity": "sha512-DyHG/W9rW8cYfBrqVrZUep5yt30scyBuYvFnGrU32bh1DUj8GKqOcdoRBaIiOBYurmIiJ02rq6BeBbvVtVp0mw==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-2.1.3.tgz", + "integrity": "sha512-COvV65spQ6omrHBUhHRKqKNL5+ddXjlS+qWZchaL9FFuQNvjM5pjp9RnmMWK4fJJ5kBhhpLneh6wh9Vh3kSMXQ==", "requires": { - "babel-runtime": "^6.23.0", + "@ant-design/create-react-context": "^0.2.4", "classnames": "2.x", "prop-types": "^15.5.8", "rc-animate": "^2.6.0", @@ -11437,22 +9677,23 @@ } }, "rc-trigger": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/rc-trigger/-/rc-trigger-2.6.4.tgz", - "integrity": "sha512-dN/Iia5E+xLh33OyvIol1Gg1WfHVfL9fUdq34/ejeYENghUNkF0965PDe+GYIgypYplhUM967rjbr/UEm6ctjA==", + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/rc-trigger/-/rc-trigger-2.6.5.tgz", + "integrity": "sha512-m6Cts9hLeZWsTvWnuMm7oElhf+03GOjOLfTuU0QmdB9ZrW7jR2IpI5rpNM7i9MvAAlMAmTx5Zr7g3uu/aMvZAw==", "requires": { "babel-runtime": "6.x", "classnames": "^2.2.6", "prop-types": "15.x", "rc-align": "^2.4.0", "rc-animate": "2.x", - "rc-util": "^4.4.0" + "rc-util": "^4.4.0", + "react-lifecycles-compat": "^3.0.4" } }, "rc-upload": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-2.6.7.tgz", - "integrity": "sha512-i6roYvM31ue50r0w/MbxOdbbkZHqpJLT29JyjQC2W5i/7w0/lZJkWEmj/DG5WRRJCnVfIiKmXp2437oXnUFNuw==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-2.9.2.tgz", + "integrity": "sha512-USjuWpTRJl3my32G5woysTaGrAld+S4dvvZ9kW6RX/RkekfmLDjvWc5ho8Mj/+6B6/tDRJnyGyvMxMQNkW7cvw==", "requires": { "babel-runtime": "6.x", "classnames": "^2.2.5", @@ -11461,177 +9702,50 @@ } }, "rc-util": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.6.0.tgz", - "integrity": "sha512-rbgrzm1/i8mgfwOI4t1CwWK7wGe+OwX+dNa7PVMgxZYPBADGh86eD4OcJO1UKGeajIMDUUKMluaZxvgraQIOmw==", + "version": "4.15.6", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.15.6.tgz", + "integrity": "sha512-W6HB1gIn+xZLxmQfLkhMnAtaZY9RktcOH2I0Tbam4D4ZDFrkO33f3M7IolN0EPtLMpf4Mv/dEQNclY77/PtBpg==", "requires": { "add-dom-event-listener": "^1.1.0", "babel-runtime": "6.x", "prop-types": "^15.5.10", - "shallowequal": "^0.2.2" - }, - "dependencies": { - "shallowequal": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-0.2.2.tgz", - "integrity": "sha1-HjL9W8q2rWiKSBLLDMBO/HXHAU4=", - "requires": { - "lodash.keys": "^3.1.2" - } - } + "react-lifecycles-compat": "^3.0.4", + "shallowequal": "^1.1.0" } }, "react": { - "version": "16.8.6", - "resolved": "https://registry.npmjs.org/react/-/react-16.8.6.tgz", - "integrity": "sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw==", + "version": "16.11.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.11.0.tgz", + "integrity": "sha512-M5Y8yITaLmU0ynd0r1Yvfq98Rmll6q8AxaEe88c8e7LxO8fZ2cNgmFt0aGAS9wzf1Ao32NKXtCl+/tVVtkxq6g==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.13.6" - } - }, - "react-app-polyfill": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-1.0.1.tgz", - "integrity": "sha512-LbVpT1NdzTdDDs7xEZdebjDrqsvKi5UyVKUQqtTYYNyC1JJYVAwNQWe4ybWvoT2V2WW9PGVO2u5Y6aVj4ER/Ow==", - "requires": { - "core-js": "3.0.1", - "object-assign": "4.1.1", - "promise": "8.0.2", - "raf": "3.4.1", - "regenerator-runtime": "0.13.2", - "whatwg-fetch": "3.0.0" - }, - "dependencies": { - "core-js": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.0.1.tgz", - "integrity": "sha512-sco40rF+2KlE0ROMvydjkrVMMG1vYilP2ALoRXcYR4obqbYIuV3Bg+51GEDW+HF8n7NRA+iaA4qD0nD9lo9mew==" - }, - "promise": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.0.2.tgz", - "integrity": "sha512-EIyzM39FpVOMbqgzEHhxdrEhtOSDOtjMZQ0M6iVfCE+kWNgCkAyOdnuCWqfmflylftfadU6FkiMgHZA2kUzwRw==", - "requires": { - "asap": "~2.0.6" - } - }, - "regenerator-runtime": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz", - "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==" - } - } - }, - "react-app-rewired": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/react-app-rewired/-/react-app-rewired-2.1.3.tgz", - "integrity": "sha512-NXC2EsQrnEMV7xD70rHcBq0B4PSEzjY/K2m/e+GRgit2jZO/uZApnpCZSKvIX2leLRN69Sqf2id0VXZ1F62CDw==", - "requires": { - "cross-spawn": "^6.0.5", - "dotenv": "^6.2.0", - "semver": "^5.6.0" - } - }, - "react-dev-utils": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-9.0.1.tgz", - "integrity": "sha512-pnaeMo/Pxel8aZpxk1WwxT3uXxM3tEwYvsjCYn5R7gNxjhN1auowdcLDzFB8kr7rafAj2rxmvfic/fbac5CzwQ==", - "requires": { - "@babel/code-frame": "7.0.0", - "address": "1.0.3", - "browserslist": "4.5.4", - "chalk": "2.4.2", - "cross-spawn": "6.0.5", - "detect-port-alt": "1.1.6", - "escape-string-regexp": "1.0.5", - "filesize": "3.6.1", - "find-up": "3.0.0", - "fork-ts-checker-webpack-plugin": "1.1.1", - "global-modules": "2.0.0", - "globby": "8.0.2", - "gzip-size": "5.0.0", - "immer": "1.10.0", - "inquirer": "6.2.2", - "is-root": "2.0.0", - "loader-utils": "1.2.3", - "opn": "5.4.0", - "pkg-up": "2.0.0", - "react-error-overlay": "^5.1.6", - "recursive-readdir": "2.2.2", - "shell-quote": "1.6.1", - "sockjs-client": "1.3.0", - "strip-ansi": "5.2.0", - "text-table": "0.2.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" - }, - "browserslist": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.5.4.tgz", - "integrity": "sha512-rAjx494LMjqKnMPhFkuLmLp8JWEX0o8ADTGeAbOqaF+XCvYLreZrG5uVjnPBlAQ8REZK4pzXGvp0bWgrFtKaag==", - "requires": { - "caniuse-lite": "^1.0.30000955", - "electron-to-chromium": "^1.3.122", - "node-releases": "^1.1.13" - } - }, - "inquirer": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.2.tgz", - "integrity": "sha512-Z2rREiXA6cHRR9KBOarR3WuLlFzlIfAEIiB45ll5SSadMg7WqOh1MKEjjndfuH5ewXdixWCxqnVfGOQzPeiztA==", - "requires": { - "ansi-escapes": "^3.2.0", - "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.3", - "figures": "^2.0.0", - "lodash": "^4.17.11", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rxjs": "^6.4.0", - "string-width": "^2.1.0", - "strip-ansi": "^5.0.0", - "through": "^2.3.6" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "requires": { - "ansi-regex": "^4.1.0" - } - } + "prop-types": "^15.6.2" } }, "react-dom": { - "version": "16.8.6", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.6.tgz", - "integrity": "sha512-1nL7PIq9LTL3fthPqwkvr2zY7phIPjYrT0jp4HjyEQrEROnw4dG41VVwi/wfoCneoleqrNX7iAD+pXebJZwrwA==", + "version": "16.11.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.11.0.tgz", + "integrity": "sha512-nrRyIUE1e7j8PaXSPtyRKtz+2y9ubW/ghNgqKFHHAHaeP0fpF5uXR+sq8IMRHC+ZUxw7W9NyCDTBtwWxvkb0iA==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "scheduler": "^0.13.6" + "scheduler": "^0.17.0" } }, - "react-error-overlay": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-5.1.6.tgz", - "integrity": "sha512-X1Y+0jR47ImDVr54Ab6V9eGk0Hnu7fVWGeHQSOXHf/C2pF9c6uy3gef8QUeuUiWlNb0i08InPSE5a/KJzNzw1Q==" + "react-hotkeys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/react-hotkeys/-/react-hotkeys-2.0.0.tgz", + "integrity": "sha512-3n3OU8vLX/pfcJrR3xJ1zlww6KS1kEJt0Whxc4FiGV+MJrQ1mYSYI3qS/11d2MJDFm8IhOXMTFQirfu6AVOF6Q==", + "requires": { + "prop-types": "^15.6.1" + } }, "react-is": { - "version": "16.8.6", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", - "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==" + "version": "16.11.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.11.0.tgz", + "integrity": "sha512-gbBVYR2p8mnriqAwWx9LbuUrShnAuSCNnuPGyc7GJrMVQtPDAh8iLpv7FRuMPFb56KkaVZIYSz1PrjI9q0QPCw==" }, "react-lazy-load": { "version": "3.0.13", @@ -11650,22 +9764,22 @@ "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, "react-redux": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.1.0.tgz", - "integrity": "sha512-hyu/PoFK3vZgdLTg9ozbt7WF3GgX5+Yn3pZm5/96/o4UueXA+zj08aiSC9Mfj2WtD1bvpIb3C5yvskzZySzzaw==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.1.1.tgz", + "integrity": "sha512-QsW0vcmVVdNQzEkrgzh2W3Ksvr8cqpAv5FhEk7tNEft+5pp7rXxAudTz3VOPawRkLIepItpkEIyLcN/VVXzjTg==", "requires": { - "@babel/runtime": "^7.4.5", + "@babel/runtime": "^7.5.5", "hoist-non-react-statics": "^3.3.0", "invariant": "^2.2.4", "loose-envify": "^1.4.0", "prop-types": "^15.7.2", - "react-is": "^16.8.6" + "react-is": "^16.9.0" } }, "react-router": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.0.1.tgz", - "integrity": "sha512-EM7suCPNKb1NxcTZ2LEOWFtQBQRQXecLxVpdsP4DW4PbbqYWeRiLyV/Tt1SdCrvT2jcyXAXmVTmzvSzrPR63Bg==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.1.2.tgz", + "integrity": "sha512-yjEuMFy1ONK246B+rsa0cUam5OeAQ8pyclRDgpxuSCrAlJ1qN9uZ5IgyKC7gQg0w8OM50NXHEegPh/ks9YuR2A==", "requires": { "@babel/runtime": "^7.1.2", "history": "^4.9.0", @@ -11680,90 +9794,34 @@ } }, "react-router-dom": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.0.1.tgz", - "integrity": "sha512-zaVHSy7NN0G91/Bz9GD4owex5+eop+KvgbxXsP/O+iW1/Ln+BrJ8QiIR5a6xNPtrdTvLkxqlDClx13QO1uB8CA==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.1.2.tgz", + "integrity": "sha512-7BPHAaIwWpZS074UKaw1FjVdZBSVWEk8IuDXdB+OkLb8vd/WRQIpA4ag9WQk61aEfQs47wHyjWUoUGGZxpQXew==", "requires": { "@babel/runtime": "^7.1.2", "history": "^4.9.0", "loose-envify": "^1.3.1", "prop-types": "^15.6.2", - "react-router": "5.0.1", + "react-router": "5.1.2", "tiny-invariant": "^1.0.2", "tiny-warning": "^1.0.0" } }, - "react-scripts": { + "react-share": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-3.0.1.tgz", - "integrity": "sha512-LKEjBhVpEB+c312NeJhzF+NATxF7JkHNr5GhtwMeRS1cMeLElMeIu8Ye7WGHtDP7iz7ra4ryy48Zpo6G/cwWUw==", - "requires": { - "@babel/core": "7.4.3", - "@svgr/webpack": "4.1.0", - "@typescript-eslint/eslint-plugin": "1.6.0", - "@typescript-eslint/parser": "1.6.0", - "babel-eslint": "10.0.1", - "babel-jest": "^24.8.0", - "babel-loader": "8.0.5", - "babel-plugin-named-asset-import": "^0.3.2", - "babel-preset-react-app": "^9.0.0", - "camelcase": "^5.2.0", - "case-sensitive-paths-webpack-plugin": "2.2.0", - "css-loader": "2.1.1", - "dotenv": "6.2.0", - "dotenv-expand": "4.2.0", - "eslint": "^5.16.0", - "eslint-config-react-app": "^4.0.1", - "eslint-loader": "2.1.2", - "eslint-plugin-flowtype": "2.50.1", - "eslint-plugin-import": "2.16.0", - "eslint-plugin-jsx-a11y": "6.2.1", - "eslint-plugin-react": "7.12.4", - "eslint-plugin-react-hooks": "^1.5.0", - "file-loader": "3.0.1", - "fs-extra": "7.0.1", - "fsevents": "2.0.6", - "html-webpack-plugin": "4.0.0-beta.5", - "identity-obj-proxy": "3.0.0", - "is-wsl": "^1.1.0", - "jest": "24.7.1", - "jest-environment-jsdom-fourteen": "0.1.0", - "jest-resolve": "24.7.1", - "jest-watch-typeahead": "0.3.0", - "mini-css-extract-plugin": "0.5.0", - "optimize-css-assets-webpack-plugin": "5.0.1", - "pnp-webpack-plugin": "1.2.1", - "postcss-flexbugs-fixes": "4.1.0", - "postcss-loader": "3.0.0", - "postcss-normalize": "7.0.1", - "postcss-preset-env": "6.6.0", - "postcss-safe-parser": "4.0.1", - "react-app-polyfill": "^1.0.1", - "react-dev-utils": "^9.0.1", - "resolve": "1.10.0", - "sass-loader": "7.1.0", - "semver": "6.0.0", - "style-loader": "0.23.1", - "terser-webpack-plugin": "1.2.3", - "ts-pnp": "1.1.2", - "url-loader": "1.1.2", - "webpack": "4.29.6", - "webpack-dev-server": "3.2.1", - "webpack-manifest-plugin": "2.0.4", - "workbox-webpack-plugin": "4.2.0" - }, - "dependencies": { - "semver": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.0.0.tgz", - "integrity": "sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ==" - } + "resolved": "https://registry.npmjs.org/react-share/-/react-share-3.0.1.tgz", + "integrity": "sha512-xo4zjYP78h6zrBN5rlC06bb877js7216KFeZELAZP6sYxVoqmU27ChrfnpKUCL9H8F5PwYXh6DLNdAp+0E17GA==", + "requires": { + "babel-runtime": "^6.26.0", + "classnames": "^2.2.5", + "jsonp": "^0.2.1", + "prop-types": "^15.5.8" } }, "react-slick": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/react-slick/-/react-slick-0.24.0.tgz", - "integrity": "sha512-Pvo0B74ohumQdYOf0qP+pdQpj9iUbAav7+2qiF3uTc5XeQp/Y/cnIeDBM2tB3txthfSe05jKIqLMJTS6qVvt5g==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/react-slick/-/react-slick-0.25.2.tgz", + "integrity": "sha512-8MNH/NFX/R7zF6W/w+FS5VXNyDusF+XDW1OU0SzODEU7wqYB+ZTGAiNJ++zVNAVqCAHdyCybScaUB+FCZOmBBw==", "requires": { "classnames": "^2.2.5", "enquire.js": "^2.1.6", @@ -11772,90 +9830,175 @@ "resize-observer-polyfill": "^1.5.0" } }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "react-svg-core": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/react-svg-core/-/react-svg-core-3.0.3.tgz", + "integrity": "sha512-Ws3eM3xCAwcaYeqm4Ajcz3zxBYNI6BeTWWhFR0cpOT+pWuVtozgHYK9xUM0S/ilapZgYMQDe49XgOxpvooFq4w==", + "dev": true, "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" + "@babel/core": "^7.4.5", + "@babel/plugin-syntax-jsx": "^7.2.0", + "@babel/preset-react": "^7.0.0", + "babel-plugin-react-svg": "^3.0.3", + "lodash.clonedeep": "^4.5.0", + "lodash.isplainobject": "^4.0.6", + "svgo": "^1.2.2" } }, - "read-pkg-up": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", - "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "react-svg-loader": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/react-svg-loader/-/react-svg-loader-3.0.3.tgz", + "integrity": "sha512-V1KnIUtvWVvc4xCig34n+f+/74ylMMugB2FbuAF/yq+QRi+WLi2hUYp9Ze3VylhA1D7ZgRygBh3Ojj8S3TPhJA==", + "dev": true, "requires": { - "find-up": "^3.0.0", - "read-pkg": "^3.0.0" + "loader-utils": "^1.2.3", + "react-svg-core": "^3.0.3" } }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "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" + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" }, "dependencies": { - "isarray": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true } } }, + "readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, "readdirp": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, "requires": { "graceful-fs": "^4.1.11", "micromatch": "^3.1.10", "readable-stream": "^2.0.2" - } - }, - "realpath-native": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", - "integrity": "sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==", - "requires": { - "util.promisify": "^1.0.0" - } - }, - "recursive-readdir": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz", - "integrity": "sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg==", - "requires": { - "minimatch": "3.0.4" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "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" + } + }, + "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" + } + } } }, "redent": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, "requires": { "indent-string": "^2.1.0", "strip-indent": "^1.0.1" } }, "redux": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.3.tgz", - "integrity": "sha512-v/Iaw67Pe+na+cZvcKvPxAKT1ww5kM+M09fmaCndCQC4Lo434AYb5975HJgJlp0D7dJxfYaLxMD4VwfpLOZ1Rw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.4.tgz", + "integrity": "sha512-vKv4WdiJxOWKxK0yRoaK3Y4pxxB0ilzVx6dszU2W8wLxlb2yikRph4iV/ymtdJ6ZxpBLFbyrxklnT5yBbQSl3Q==", "requires": { "loose-envify": "^1.4.0", "symbol-observable": "^1.2.0" } }, + "redux-devtools-extension": { + "version": "2.13.8", + "resolved": "https://registry.npmjs.org/redux-devtools-extension/-/redux-devtools-extension-2.13.8.tgz", + "integrity": "sha512-8qlpooP2QqPtZHQZRhx3x3OP5skEV1py/zUdMY28WNAocbafxdG2tRD1MWE7sp8obGMNYuLWanhhQ7EQvT1FBg==" + }, "redux-logger": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/redux-logger/-/redux-logger-3.0.6.tgz", @@ -11872,12 +10015,14 @@ "regenerate": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", - "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==" + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "dev": true }, "regenerate-unicode-properties": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz", "integrity": "sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA==", + "dev": true, "requires": { "regenerate": "^1.4.0" } @@ -11888,9 +10033,10 @@ "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" }, "regenerator-transform": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.0.tgz", - "integrity": "sha512-rtOelq4Cawlbmq9xuMR5gdFmv7ku/sFoB7sRiywx7aq53bc52b4j6zvH7Te1Vt/X2YveDKnCGUbioieU7FEL3w==", + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.1.tgz", + "integrity": "sha512-flVuee02C3FKRISbxhXl9mGzdbWUVHubl1SMaknjxkFB1/iqpJhArQUvRxOOPEc/9tAiX0BaQ28FJH10E4isSQ==", + "dev": true, "requires": { "private": "^0.1.6" } @@ -11899,28 +10045,35 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, "requires": { "extend-shallow": "^3.0.2", "safe-regex": "^1.1.0" } }, - "regexp-tree": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.10.tgz", - "integrity": "sha512-K1qVSbcedffwuIslMwpe6vGlj+ZXRnGkvjAtFHfDZZZuEdA/h0dxljAPu9vhUo6Rrx2U2AwJ+nSQ6hK+lrP5MQ==" + "regexp.prototype.flags": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.2.0.tgz", + "integrity": "sha512-ztaw4M1VqgMwl9HlPpOuiYgItcHlunW0He2fE6eNfT6E/CF2FtYi9ofOYe4mKntstYk0Fyh/rDRBdS3AnxjlrA==", + "dev": true, + "requires": { + "define-properties": "^1.1.2" + } }, "regexpp": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.0.0.tgz", + "integrity": "sha512-Z+hNr7RAVWxznLPuA7DIh8UNX1j9CDrUQxskw9IrBE1Dxue2lyXT+shqEIeLUjrokxIP8CMy1WkjgG3rTsd5/g==", + "dev": true }, "regexpu-core": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.5.4.tgz", - "integrity": "sha512-BtizvGtFQKGPUcTy56o3nk1bGRp4SZOTYrDtGNlqCQufptV5IkkLN6Emw+yunAJjzf+C9FQFtvq7IoA3+oMYHQ==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.6.0.tgz", + "integrity": "sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg==", + "dev": true, "requires": { "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.0.2", + "regenerate-unicode-properties": "^8.1.0", "regjsgen": "^0.5.0", "regjsparser": "^0.6.0", "unicode-match-property-ecmascript": "^1.0.4", @@ -11928,14 +10081,16 @@ } }, "regjsgen": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz", - "integrity": "sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA==" + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz", + "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==", + "dev": true }, "regjsparser": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz", "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==", + "dev": true, "requires": { "jsesc": "~0.5.0" }, @@ -11943,104 +10098,62 @@ "jsesc": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true } } }, - "rehype-parse": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-6.0.0.tgz", - "integrity": "sha512-V2OjMD0xcSt39G4uRdMTqDXXm6HwkUbLMDayYKA/d037j8/OtVSQ+tqKwYWOuyBeoCs/3clXRe30VUjeMDTBSA==", - "requires": { - "hast-util-from-parse5": "^5.0.0", - "parse5": "^5.0.0", - "xtend": "^4.0.1" - } - }, "relateurl": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=" + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "dev": true }, "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true }, "renderkid": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.3.tgz", "integrity": "sha512-z8CLQp7EZBPCwCnncgf9C4XAi3WR0dv+uWu/PjIyhhAb5d6IJ/QZqlHFprHeKT+59//V6BNUsLbvN8+2LarxGA==", + "dev": true, "requires": { "css-select": "^1.1.0", "dom-converter": "^0.2", "htmlparser2": "^3.3.0", "strip-ansi": "^3.0.0", "utila": "^0.4.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "css-select": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", - "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", - "requires": { - "boolbase": "~1.0.0", - "css-what": "2.1", - "domutils": "1.5.1", - "nth-check": "~1.0.1" - } - }, - "domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - } } }, "repeat-element": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true }, "repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true }, "repeating": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, "requires": { "is-finite": "^1.0.0" } }, - "replace-ext": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", - "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=" - }, "request": { "version": "2.88.0", "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "dev": true, "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -12062,45 +10175,33 @@ "tough-cookie": "~2.4.3", "tunnel-agent": "^0.6.0", "uuid": "^3.3.2" - } - }, - "request-promise-core": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", - "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", - "requires": { - "lodash": "^4.17.11" - } - }, - "request-promise-native": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", - "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", - "requires": { - "request-promise-core": "1.1.2", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" + }, + "dependencies": { + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + } } }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" - }, - "requireindex": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", - "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==" + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true }, "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true }, "resize-observer-polyfill": { "version": "1.5.1", @@ -12108,9 +10209,10 @@ "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" }, "resolve": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", - "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "dev": true, "requires": { "path-parse": "^1.0.6" } @@ -12119,53 +10221,78 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "dev": true, "requires": { "resolve-from": "^3.0.0" } }, + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "dependencies": { + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "requires": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + } + } + } + }, "resolve-from": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true }, "resolve-pathname": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz", - "integrity": "sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true }, "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, "requires": { - "onetime": "^2.0.0", + "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" - }, - "rgb-regex": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", - "integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=" + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true }, - "rgba-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", - "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=" + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "dev": true }, "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, "requires": { "glob": "^7.1.3" } @@ -12174,6 +10301,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, "requires": { "hash-base": "^3.0.0", "inherits": "^2.0.1" @@ -12188,15 +10316,11 @@ "classnames": "^2.2.5" } }, - "rsvp": { - "version": "4.8.5", - "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", - "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==" - }, "run-async": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, "requires": { "is-promise": "^2.1.0" } @@ -12205,14 +10329,16 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", + "dev": true, "requires": { "aproba": "^1.1.1" } }, "rxjs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz", - "integrity": "sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", + "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", + "dev": true, "requires": { "tslib": "^1.9.0" } @@ -12220,12 +10346,14 @@ "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==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true }, "safe-regex": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, "requires": { "ret": "~0.1.10" } @@ -12235,33 +10363,11 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "sane": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", - "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", - "requires": { - "@cnakazawa/watch": "^1.0.3", - "anymatch": "^2.0.0", - "capture-exit": "^2.0.0", - "exec-sh": "^0.3.2", - "execa": "^1.0.0", - "fb-watchman": "^2.0.0", - "micromatch": "^3.1.4", - "minimist": "^1.1.1", - "walker": "~1.0.5" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - } - } - }, "sass-graph": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz", "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=", + "dev": true, "requires": { "glob": "^7.0.0", "lodash": "^4.0.0", @@ -12269,20 +10375,17 @@ "yargs": "^7.0.0" }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, "camelcase": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=" + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true }, "cliui": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, "requires": { "string-width": "^1.0.1", "strip-ansi": "^3.0.1", @@ -12293,20 +10396,29 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, "requires": { "path-exists": "^2.0.0", "pinkie-promise": "^2.0.0" } }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, "invert-kv": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true }, "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, "requires": { "number-is-nan": "^1.0.0" } @@ -12315,6 +10427,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, "requires": { "invert-kv": "^1.0.0" } @@ -12323,6 +10436,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, "requires": { "graceful-fs": "^4.1.2", "parse-json": "^2.2.0", @@ -12335,22 +10449,16 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dev": true, "requires": { "lcid": "^1.0.0" } }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "requires": { - "error-ex": "^1.2.0" - } - }, "path-exists": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, "requires": { "pinkie-promise": "^2.0.0" } @@ -12359,6 +10467,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, "requires": { "graceful-fs": "^4.1.2", "pify": "^2.0.0", @@ -12368,12 +10477,14 @@ "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true }, "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, "requires": { "load-json-file": "^1.0.0", "normalize-package-data": "^2.3.2", @@ -12384,6 +10495,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, "requires": { "find-up": "^1.0.0", "read-pkg": "^1.0.0" @@ -12392,30 +10504,25 @@ "require-main-filename": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", "strip-ansi": "^3.0.0" } }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, "strip-bom": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, "requires": { "is-utf8": "^0.2.0" } @@ -12423,17 +10530,30 @@ "which-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=" + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + } }, "y18n": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true }, "yargs": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", + "dev": true, "requires": { "camelcase": "^3.0.0", "cliui": "^3.2.0", @@ -12454,6 +10574,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", + "dev": true, "requires": { "camelcase": "^3.0.0" } @@ -12461,102 +10582,56 @@ } }, "sass-loader": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.1.0.tgz", - "integrity": "sha512-+G+BKGglmZM2GUSfT9TLuEp6tzehHPjAMoRRItOojWIqIGPloVCMhNIQuG639eJ+y033PaGTSjLaTHts8Kw79w==", - "requires": { - "clone-deep": "^2.0.1", - "loader-utils": "^1.0.1", - "lodash.tail": "^4.1.1", - "neo-async": "^2.5.0", - "pify": "^3.0.0", - "semver": "^5.5.0" + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-8.0.0.tgz", + "integrity": "sha512-+qeMu563PN7rPdit2+n5uuYVR0SSVwm0JsOUsaJXzgYcClWSlmX0iHDnmeOobPkf5kUglVot3QS6SyLyaQoJ4w==", + "dev": true, + "requires": { + "clone-deep": "^4.0.1", + "loader-utils": "^1.2.3", + "neo-async": "^2.6.1", + "schema-utils": "^2.1.0", + "semver": "^6.3.0" }, "dependencies": { - "clone-deep": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-2.0.2.tgz", - "integrity": "sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==", - "requires": { - "for-own": "^1.0.0", - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.0", - "shallow-clone": "^1.0.0" - } - }, - "for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", - "requires": { - "for-in": "^1.0.1" - } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" - }, - "shallow-clone": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-1.0.0.tgz", - "integrity": "sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==", - "requires": { - "is-extendable": "^0.1.1", - "kind-of": "^5.0.0", - "mixin-object": "^2.0.1" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" - } - } + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true } } }, "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, - "saxes": { - "version": "3.1.11", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-3.1.11.tgz", - "integrity": "sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g==", - "requires": { - "xmlchars": "^2.1.1" - } + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true }, "scheduler": { - "version": "0.13.6", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.6.tgz", - "integrity": "sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.17.0.tgz", + "integrity": "sha512-7rro8Io3tnCPuY4la/NuI5F2yfESpnfZyT6TtkXnSWVkcu0BCDJ+8gk5ozUaFaxpIyNuWAPXrH0yFcSi28fnDA==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" } }, "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.5.0.tgz", + "integrity": "sha512-32ISrwW2scPXHUSusP8qMg5dLUawKkyV+/qIEV9JdXKx+rsM6mi8vZY8khg2M69Qom16rtroWXD3Ybtiws38gQ==", + "dev": true, "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1" } }, "scss-tokenizer": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz", "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=", + "dev": true, "requires": { "js-base64": "^2.1.8", "source-map": "^0.4.2" @@ -12566,6 +10641,7 @@ "version": "0.4.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, "requires": { "amdefine": ">=0.0.4" } @@ -12575,25 +10651,29 @@ "select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=" + "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", + "dev": true }, "selfsigned": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.4.tgz", - "integrity": "sha512-9AukTiDmHXGXWtWjembZ5NDmVvP2695EtpgbCsxCa68w3c88B+alqbmZ4O3hZ4VWGXeGWzEVdvqgAJD8DQPCDw==", + "version": "1.10.7", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz", + "integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==", + "dev": true, "requires": { - "node-forge": "0.7.5" + "node-forge": "0.9.0" } }, "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true }, "send": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "dev": true, "requires": { "debug": "2.6.9", "depd": "~1.1.2", @@ -12614,6 +10694,7 @@ "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" }, @@ -12621,26 +10702,30 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true } } }, "serialize-javascript": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.7.0.tgz", - "integrity": "sha512-ke8UG8ulpFOxO8f8gRYabHQe/ZntKlcig2Mp+8+URDP1D8vJZ0KUt7LYo07q25Z/+JVSgpr/cui9PIp5H6/+nA==" + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.9.1.tgz", + "integrity": "sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==", + "dev": true }, "serve-index": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", + "dev": true, "requires": { "accepts": "~1.3.4", "batch": "0.6.1", @@ -12655,6 +10740,7 @@ "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" } @@ -12663,6 +10749,7 @@ "version": "1.6.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, "requires": { "depd": "~1.1.2", "inherits": "2.0.3", @@ -12673,17 +10760,20 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true }, "setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true } } }, @@ -12691,6 +10781,7 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "dev": true, "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", @@ -12701,12 +10792,14 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true }, "set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, "requires": { "extend-shallow": "^2.0.1", "is-extendable": "^0.1.1", @@ -12718,6 +10811,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -12732,46 +10826,26 @@ "setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "dev": true }, "sha.js": { "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" } }, "shallow-clone": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", - "integrity": "sha1-WQnodLp3EG1zrEFM/sH/yofZcGA=", + "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": { - "is-extendable": "^0.1.1", - "kind-of": "^2.0.1", - "lazy-cache": "^0.2.3", - "mixin-object": "^2.0.1" - }, - "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "kind-of": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", - "integrity": "sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU=", - "requires": { - "is-buffer": "^1.0.2" - } - }, - "lazy-cache": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", - "integrity": "sha1-f+3fLctu23fRHvHRF6tf/fCrG2U=" - } + "kind-of": "^6.0.2" } }, "shallow-equal": { @@ -12788,6 +10862,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, "requires": { "shebang-regex": "^1.0.0" } @@ -12795,58 +10870,20 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" - }, - "shell-quote": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz", - "integrity": "sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c=", - "requires": { - "array-filter": "~0.0.0", - "array-map": "~0.0.0", - "array-reduce": "~0.0.0", - "jsonify": "~0.0.0" - } - }, - "shellwords": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==" + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" - }, - "simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", - "requires": { - "is-arrayish": "^0.3.1" - }, - "dependencies": { - "is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" - } - } - }, - "sisteransi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.0.tgz", - "integrity": "sha512-N+z4pHB4AmUv0SjveWRd6q1Nj5w62m5jodv+GD8lvmbY/83T/rpbJGZOnK5T149OldDj4Db07BSv9xY4K6NTPQ==" - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==" + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true }, "slice-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, "requires": { "ansi-styles": "^3.2.0", "astral-regex": "^1.0.0", @@ -12857,6 +10894,7 @@ "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, "requires": { "base": "^0.11.1", "debug": "^2.2.0", @@ -12872,6 +10910,7 @@ "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" } @@ -12880,6 +10919,7 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -12888,6 +10928,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -12895,12 +10936,8 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, @@ -12908,6 +10945,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, "requires": { "define-property": "^1.0.0", "isobject": "^3.0.0", @@ -12918,6 +10956,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, "requires": { "is-descriptor": "^1.0.0" } @@ -12926,6 +10965,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -12934,6 +10974,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -12942,16 +10983,12 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", "kind-of": "^6.0.2" } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" } } }, @@ -12959,33 +10996,37 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, "requires": { "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "sockjs": { "version": "0.3.19", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", + "dev": true, "requires": { "faye-websocket": "^0.10.0", "uuid": "^3.0.1" - }, - "dependencies": { - "faye-websocket": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", - "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", - "requires": { - "websocket-driver": ">=0.5.1" - } - } } }, "sockjs-client": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.3.0.tgz", - "integrity": "sha512-R9jxEzhnnrdxLCNln0xg5uGHqMnkhPSTzUZH2eXcR03S/On9Yvoq2wyUZILRUhZCNVu2PmwWVoyuiPz8th8zbg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.4.0.tgz", + "integrity": "sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g==", + "dev": true, "requires": { "debug": "^3.2.5", "eventsource": "^1.0.7", @@ -12999,57 +11040,39 @@ "version": "3.2.6", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, "requires": { "ms": "^2.1.1" } + }, + "faye-websocket": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", + "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } } } }, "source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==" + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true }, "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "source-map-explorer": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/source-map-explorer/-/source-map-explorer-1.8.0.tgz", - "integrity": "sha512-1Q0lNSw5J7pChKmjqniOCLbvLFi4KJfrtixk99CzvRcqFiGBJvRHMrw0PjLwKOvbuAo8rNOukJhEPA0Nj85xDw==", - "requires": { - "btoa": "^1.2.1", - "convert-source-map": "^1.6.0", - "docopt": "^0.6.2", - "ejs": "^2.6.1", - "fs-extra": "^7.0.1", - "glob": "^7.1.3", - "opn": "^5.5.0", - "source-map": "^0.5.1", - "temp": "^0.9.0" - }, - "dependencies": { - "opn": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", - "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", - "requires": { - "is-wsl": "^1.1.0" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - } - } + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true }, "source-map-resolve": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "dev": true, "requires": { "atob": "^2.1.1", "decode-uri-component": "^0.2.0", @@ -13059,28 +11082,34 @@ } }, "source-map-support": { - "version": "0.5.12", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", - "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", + "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", + "dev": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" + }, + "dependencies": { + "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-url": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" - }, - "space-separated-tokens": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.4.tgz", - "integrity": "sha512-UyhMSmeIqZrQn2UdjYpxEkwY9JUrn8pP+7L4f91zRzOQuI8MF1FGLfYU9DKCYeLdo7LXMxwrX5zKFy7eeeVHuA==" + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true }, "spdx-correct": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -13089,26 +11118,30 @@ "spdx-exceptions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==" + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true }, "spdx-expression-parse": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, "requires": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "spdx-license-ids": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", - "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==" + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true }, "spdy": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.0.tgz", - "integrity": "sha512-ot0oEGT/PGUpzf/6uk4AWLqkq+irlqHXkrdbk51oWONh3bxQmBuljxPNl66zlRRcIJStWq0QkLUCPOPjgjvU0Q==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.1.tgz", + "integrity": "sha512-HeZS3PBdMA+sZSu0qwpCxl3DeALD5ASx8pAX0jZdKXSpPWbQ6SYGnlg3BBmYLx5LtiZrmkAZfErCm2oECBcioA==", + "dev": true, "requires": { "debug": "^4.1.0", "handle-thing": "^2.0.0", @@ -13121,6 +11154,7 @@ "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", @@ -13128,29 +11162,13 @@ "obuf": "^1.1.2", "readable-stream": "^3.0.6", "wbuf": "^1.7.3" - }, - "dependencies": { - "readable-stream": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", - "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } } }, - "split-on-first": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", - "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==" - }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, "requires": { "extend-shallow": "^3.0.0" } @@ -13158,12 +11176,14 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true }, "sshpk": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -13180,6 +11200,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "dev": true, "requires": { "figgy-pudding": "^3.5.1" } @@ -13187,17 +11208,14 @@ "stable": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" - }, - "stack-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", - "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==" + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "dev": true }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, "requires": { "define-property": "^0.2.5", "object-copy": "^0.1.0" @@ -13207,6 +11225,7 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -13216,34 +11235,97 @@ "statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true }, "stdout-stream": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==", + "dev": true, "requires": { "readable-stream": "^2.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "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" + } + }, + "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" + } + } } }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" - }, "stream-browserify": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "dev": true, "requires": { "inherits": "~2.0.1", "readable-stream": "^2.0.2" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "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" + } + }, + "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" + } + } } }, "stream-each": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", + "dev": true, "requires": { "end-of-stream": "^1.1.0", "stream-shift": "^1.0.0" @@ -13253,156 +11335,189 @@ "version": "2.8.3", "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "dev": true, "requires": { "builtin-status-codes": "^3.0.0", "inherits": "^2.0.1", "readable-stream": "^2.3.6", "to-arraybuffer": "^1.0.0", "xtend": "^4.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "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" + } + }, + "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" + } + } } }, "stream-shift": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", - "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=" - }, - "strict-uri-encode": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", - "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=" + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "dev": true }, "string-convert": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", "integrity": "sha1-aYLMMEn7tM2F+LJFaLnZvznu/5c=" }, - "string-length": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", - "integrity": "sha1-1A27aGo6zpYMHP/KVivyxF+DY+0=", - "requires": { - "astral-regex": "^1.0.0", - "strip-ansi": "^4.0.0" - } - }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } } }, - "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==", + "string.prototype.trimleft": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz", + "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==", + "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" } }, - "stringify-object": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", - "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "string.prototype.trimright": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz", + "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "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": { - "get-own-enumerable-property-symbols": "^3.0.0", - "is-obj": "^1.0.1", - "is-regexp": "^1.0.0" + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", + "dev": true + } } }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^2.0.0" } }, "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" - }, - "strip-comments": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-1.0.2.tgz", - "integrity": "sha512-kL97alc47hoyIQSV165tTt9rG5dn4w1dNnBhOQ3bOU1Nc1hel09jnXANaHJ7vzHLd4Ju8kseDGzlev96pghLFw==", - "requires": { - "babel-extract-comments": "^1.0.0", - "babel-plugin-transform-object-rest-spread": "^6.26.0" - } + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true }, "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true }, "strip-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, "requires": { "get-stdin": "^4.0.1" } }, "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", + "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "dev": true }, "style-loader": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.23.1.tgz", - "integrity": "sha512-XK+uv9kWwhZMZ1y7mysB+zoihsEj4wneFWAS5qoiLwzW0WzSqMrrsIy+a3zkQJq0ipFtBpX5W3MqyRIBF/WFGg==", - "requires": { - "loader-utils": "^1.1.0", - "schema-utils": "^1.0.0" - } - }, - "stylehacks": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", - "integrity": "sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.0.0.tgz", + "integrity": "sha512-B0dOCFwv7/eY31a5PCieNwMgMhVGFe9w+rh7s/Bx8kfFkrth9zfTZquoYvdw8URgiqxObQKcpW51Ugz1HjfdZw==", + "dev": true, "requires": { - "browserslist": "^4.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0" - }, - "dependencies": { - "postcss-selector-parser": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", - "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", - "requires": { - "dot-prop": "^4.1.1", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - } + "loader-utils": "^1.2.3", + "schema-utils": "^2.0.1" } }, "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" } }, "svgo": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.2.2.tgz", - "integrity": "sha512-rAfulcwp2D9jjdGu+0CuqlrAUin6bBWrpoqXWwKDZZZJfXcUXQSxLJOFJCQCSA0x0pP2U0TxSlJu2ROq5Bq6qA==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", + "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", + "dev": true, "requires": { "chalk": "^2.4.1", "coa": "^2.0.2", "css-select": "^2.0.0", "css-select-base-adapter": "^0.1.1", - "css-tree": "1.0.0-alpha.28", - "css-url-regex": "^1.1.0", - "csso": "^3.5.1", + "css-tree": "1.0.0-alpha.37", + "csso": "^4.0.2", "js-yaml": "^3.13.1", "mkdirp": "~0.5.1", "object.values": "^1.1.0", @@ -13410,6 +11525,36 @@ "stable": "^0.1.8", "unquote": "~1.1.1", "util.promisify": "~1.0.0" + }, + "dependencies": { + "css-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", + "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^3.2.1", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" + } + }, + "css-what": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.2.1.tgz", + "integrity": "sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw==", + "dev": true + }, + "domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + } } }, "symbol-observable": { @@ -13417,18 +11562,14 @@ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" - }, "table": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.1.tgz", - "integrity": "sha512-E6CK1/pZe2N75rGZQotFOdmzWQ1AILtgYbMAbAjvms0S1l5IDB47zG3nCnFGB/w+7nB3vKofbLXCH7HPBo864w==", + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, "requires": { - "ajv": "^6.9.1", - "lodash": "^4.17.11", + "ajv": "^6.10.2", + "lodash": "^4.17.14", "slice-ansi": "^2.1.0", "string-width": "^3.0.0" }, @@ -13436,12 +11577,14 @@ "ansi-regex": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, "requires": { "emoji-regex": "^7.0.1", "is-fullwidth-code-point": "^2.0.0", @@ -13452,6 +11595,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, "requires": { "ansi-regex": "^4.1.0" } @@ -13461,113 +11605,163 @@ "tapable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==" + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "dev": true }, "tar": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz", "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==", + "dev": true, "requires": { "block-stream": "*", "fstream": "^1.0.12", "inherits": "2" } }, - "temp": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.0.tgz", - "integrity": "sha512-YfUhPQCJoNQE5N+FJQcdPz63O3x3sdT4Xju69Gj4iZe0lBKOtnAMi0SLj9xKhGkcGhsxThvTJ/usxtFPo438zQ==", - "requires": { - "rimraf": "~2.6.2" - } - }, "terser": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-3.17.0.tgz", - "integrity": "sha512-/FQzzPJmCpjAH9Xvk2paiWrFq+5M6aVOf+2KRbwhByISDX/EujxsK+BAvrhb6H+2rtrLCHK9N01wO014vrIwVQ==", + "version": "4.3.9", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.3.9.tgz", + "integrity": "sha512-NFGMpHjlzmyOtPL+fDw3G7+6Ueh/sz4mkaUYa4lJCxOPTNzd0Uj0aZJOmsDYoSQyfuVoWDMSWTPU3huyOm2zdA==", + "dev": true, "requires": { - "commander": "^2.19.0", + "commander": "^2.20.0", "source-map": "~0.6.1", - "source-map-support": "~0.5.10" + "source-map-support": "~0.5.12" + }, + "dependencies": { + "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 + } } }, "terser-webpack-plugin": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.2.3.tgz", - "integrity": "sha512-GOK7q85oAb/5kE12fMuLdn2btOS9OBZn4VsecpHDywoUC/jLhSAKOiYo0ezx7ss2EXPMzyEWFoE0s1WLE+4+oA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz", + "integrity": "sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg==", + "dev": true, "requires": { - "cacache": "^11.0.2", - "find-cache-dir": "^2.0.0", + "cacache": "^12.0.2", + "find-cache-dir": "^2.1.0", + "is-wsl": "^1.1.0", "schema-utils": "^1.0.0", - "serialize-javascript": "^1.4.0", + "serialize-javascript": "^1.7.0", "source-map": "^0.6.1", - "terser": "^3.16.1", - "webpack-sources": "^1.1.0", - "worker-farm": "^1.5.2" - } - }, - "test-exclude": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", - "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", - "requires": { - "glob": "^7.1.3", - "minimatch": "^3.0.4", - "read-pkg-up": "^4.0.0", - "require-main-filename": "^2.0.0" + "terser": "^4.1.2", + "webpack-sources": "^1.4.0", + "worker-farm": "^1.7.0" + }, + "dependencies": { + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + }, + "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 + } } }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" - }, - "throat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz", - "integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=" + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true }, "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, "requires": { "readable-stream": "~2.3.6", "xtend": "~4.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "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" + } + }, + "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" + } + } } }, "thunky": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.0.3.tgz", - "integrity": "sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true }, "timers-browserify": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", - "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==", + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz", + "integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==", + "dev": true, "requires": { "setimmediate": "^1.0.4" } }, - "timsort": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", - "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" + "tiny-glob": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.6.tgz", + "integrity": "sha512-A7ewMqPu1B5PWwC3m7KVgAu96Ch5LA0w4SnEN/LbDREj/gAD0nPWboRbn8YoP9ISZXqeNAlMvKSKoEuhcfK3Pw==", + "dev": true, + "requires": { + "globalyzer": "^0.1.0", + "globrex": "^0.1.1" + } }, "tiny-invariant": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.4.tgz", - "integrity": "sha512-lMhRd/djQJ3MoaHEBrw8e2/uM4rs9YMNk0iOr8rHQ0QdbM7D4l0gFl3szKdeixrlyfm9Zqi4dxHCM2qVG8ND5g==" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz", + "integrity": "sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA==" }, "tiny-warning": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.2.tgz", - "integrity": "sha512-rru86D9CpQRLvsFG5XFdy0KdLAvjdQDyZCsRcuu60WtzFylDM3eAWSxEVz5kzL2Gp544XiUvPbVKtOA/txLi9Q==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, "tinycolor2": { "version": "1.4.1", @@ -13578,37 +11772,48 @@ "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, "requires": { "os-tmpdir": "~1.0.2" } }, - "tmpl": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", - "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=" - }, "to-arraybuffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=" + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "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": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true }, "to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, "requires": { "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "to-regex": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, "requires": { "define-property": "^2.0.2", "extend-shallow": "^3.0.2", @@ -13620,6 +11825,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, "requires": { "is-number": "^3.0.0", "repeat-string": "^1.6.1" @@ -13633,12 +11839,20 @@ "toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "dev": true + }, + "toposort": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.7.tgz", + "integrity": "sha1-LmhELZ9k7HILjMieZEOsbKqVACk=", + "dev": true }, "tough-cookie": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, "requires": { "psl": "^1.1.24", "punycode": "^1.4.1" @@ -13647,55 +11861,71 @@ "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true } } }, - "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", - "requires": { - "punycode": "^2.1.0" - } - }, "trim-newlines": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=" - }, - "trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=" - }, - "trough": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.4.tgz", - "integrity": "sha512-tdzBRDGWcI1OpPVmChbdSKhvSVurznZ8X36AYURAcl+0o2ldlCY2XPzyXNNxwJwwyIU+rIglTCG4kxtNKBQH7Q==" + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true }, "true-case-path": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==", + "dev": true, "requires": { "glob": "^7.1.2" } }, - "ts-pnp": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.1.2.tgz", - "integrity": "sha512-f5Knjh7XCyRIzoC/z1Su1yLLRrPrFCgtUAh/9fCSP6NKbATwpOL1+idQVXQokK9GRFURn/jYPGPfegIctwunoA==" + "tsconfig-paths": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", + "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + } + } + }, + "tsconfig-paths-webpack-plugin": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-3.2.0.tgz", + "integrity": "sha512-S/gOOPOkV8rIL4LurZ1vUdYCVgo15iX9ZMJ6wx6w2OgcpT/G4wMyHB6WM+xheSqGMrWKuxFul+aXpCju3wmj/g==", + "dev": true, + "requires": { + "chalk": "^2.3.0", + "enhanced-resolve": "^4.0.0", + "tsconfig-paths": "^3.4.0" + } }, "tslib": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", - "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", + "dev": true }, "tsutils": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.14.0.tgz", - "integrity": "sha512-SmzGbB0l+8I0QwsPgjooFRaRvHLBLNYM8SeQ0k6rtNDru5sCGeLJcZdwilNndN+GysuFjF5EIYgN8GfFG6UeUw==", + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", + "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", + "dev": true, "requires": { "tslib": "^1.8.1" } @@ -13703,12 +11933,14 @@ "tty-browserify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", - "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=" + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, "requires": { "safe-buffer": "^5.0.1" } @@ -13716,20 +11948,29 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, "requires": { "prelude-ls": "~1.1.2" } }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "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" @@ -13738,12 +11979,14 @@ "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true }, "typescript": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.5.tgz", - "integrity": "sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw==" + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.3.tgz", + "integrity": "sha512-Mcr/Qk7hXqFBXMN7p7Lusj1ktCBydylfQM/FZCk5glCNQJrCUKPkMHdo9R0MTFWsC/4kPFvDS0fDPvukfCkFsw==", + "dev": true }, "ua-parser-js": { "version": "0.7.20", @@ -13754,6 +11997,7 @@ "version": "3.4.10", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz", "integrity": "sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==", + "dev": true, "requires": { "commander": "~2.19.0", "source-map": "~0.6.1" @@ -13762,19 +12006,28 @@ "commander": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", - "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==" + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", + "dev": true + }, + "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 } } }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", - "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==" + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", + "dev": true }, "unicode-match-property-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "dev": true, "requires": { "unicode-canonical-property-names-ecmascript": "^1.0.4", "unicode-property-aliases-ecmascript": "^1.0.4" @@ -13783,45 +12036,20 @@ "unicode-match-property-value-ecmascript": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz", - "integrity": "sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==" + "integrity": "sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==", + "dev": true }, "unicode-property-aliases-ecmascript": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz", - "integrity": "sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==" - }, - "unified": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/unified/-/unified-7.1.0.tgz", - "integrity": "sha512-lbk82UOIGuCEsZhPj8rNAkXSDXd6p0QLzIuSsCdxrqnqU56St4eyOB+AlXsVgVeRmetPTYydIuvFfpDIed8mqw==", - "requires": { - "@types/unist": "^2.0.0", - "@types/vfile": "^3.0.0", - "bail": "^1.0.0", - "extend": "^3.0.0", - "is-plain-obj": "^1.1.0", - "trough": "^1.0.0", - "vfile": "^3.0.0", - "x-is-string": "^0.1.0" - }, - "dependencies": { - "vfile": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-3.0.1.tgz", - "integrity": "sha512-y7Y3gH9BsUSdD4KzHsuMaCzRjglXN0W2EcMf0gpvu6+SbsGhMje7xDc8AEoeXy6mIwCKMI6BkjMsRjzQbhMEjQ==", - "requires": { - "is-buffer": "^2.0.0", - "replace-ext": "1.0.0", - "unist-util-stringify-position": "^1.0.0", - "vfile-message": "^1.0.0" - } - } - } + "integrity": "sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==", + "dev": true }, "union-value": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, "requires": { "arr-union": "^3.1.0", "get-value": "^2.0.6", @@ -13832,17 +12060,14 @@ "uniq": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", - "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=" - }, - "uniqs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", - "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=" + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", + "dev": true }, "unique-filename": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, "requires": { "unique-slug": "^2.0.0" } @@ -13851,34 +12076,28 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "dev": true, "requires": { "imurmurhash": "^0.1.4" } }, - "unist-util-stringify-position": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz", - "integrity": "sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ==" - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" - }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true }, "unquote": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", - "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=" + "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=", + "dev": true }, "unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, "requires": { "has-value": "^0.3.1", "isobject": "^3.0.0" @@ -13888,6 +12107,7 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, "requires": { "get-value": "^2.0.3", "has-values": "^0.1.4", @@ -13898,6 +12118,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, "requires": { "isarray": "1.0.0" } @@ -13907,29 +12128,34 @@ "has-values": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true } } }, "upath": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", - "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true }, "upper-case": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", - "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=" + "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", + "dev": true }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, "requires": { "punycode": "^2.1.0" } @@ -13937,12 +12163,14 @@ "urix": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true }, "url": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, "requires": { "punycode": "1.3.2", "querystring": "0.2.0" @@ -13951,24 +12179,8 @@ "punycode": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" - } - } - }, - "url-loader": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-1.1.2.tgz", - "integrity": "sha512-dXHkKmw8FhPqu8asTc1puBfe3TehOCo2+RmOOev5suNCIYBcT626kxiWg1NBVkwc4rO8BGa7gP70W7VXuqHrjg==", - "requires": { - "loader-utils": "^1.1.0", - "mime": "^2.0.3", - "schema-utils": "^1.0.0" - }, - "dependencies": { - "mime": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", - "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==" + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true } } }, @@ -13976,6 +12188,7 @@ "version": "1.4.7", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", + "dev": true, "requires": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" @@ -13984,12 +12197,14 @@ "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true }, "util": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", + "dev": true, "requires": { "inherits": "2.0.3" }, @@ -13997,19 +12212,22 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true } } }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true }, "util.promisify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "dev": true, "requires": { "define-properties": "^1.1.2", "object.getownpropertydescriptors": "^2.0.3" @@ -14018,122 +12236,65 @@ "utila": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=" + "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", + "dev": true }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true }, "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==", + "dev": true + }, + "v8-compile-cache": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz", + "integrity": "sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==", + "dev": true }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "value-equal": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.4.0.tgz", - "integrity": "sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw==" - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" - }, - "vendors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.3.tgz", - "integrity": "sha512-fOi47nsJP5Wqefa43kyWSg80qF+Q3XA6MUkgi7Hp1HQaKDQW4cQrK2D0P7mmbFtsV1N89am55Yru/nyEwRubcw==" - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "vfile": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.0.1.tgz", - "integrity": "sha512-lRHFCuC4SQBFr7Uq91oJDJxlnftoTLQ7eKIpMdubhYcVMho4781a8MWXLy3qZrZ0/STD1kRiKc0cQOHm4OkPeA==", - "requires": { - "@types/unist": "^2.0.0", - "is-buffer": "^2.0.0", - "replace-ext": "1.0.0", - "unist-util-stringify-position": "^2.0.0", - "vfile-message": "^2.0.0" - }, - "dependencies": { - "unist-util-stringify-position": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.1.tgz", - "integrity": "sha512-Zqlf6+FRI39Bah8Q6ZnNGrEHUhwJOkHde2MHVk96lLyftfJJckaPslKgzhVcviXj8KcE9UJM9F+a4JEiBUTYgA==", - "requires": { - "@types/unist": "^2.0.2" - } - }, - "vfile-message": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.1.tgz", - "integrity": "sha512-KtasSV+uVU7RWhUn4Lw+wW1Zl/nW8JWx7JCPps10Y9JRRIDeDXf8wfBLoOSsJLyo27DqMyAi54C6Jf/d6Kr2Bw==", - "requires": { - "@types/unist": "^2.0.2", - "unist-util-stringify-position": "^2.0.0" - } - } - } - }, - "vfile-message": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-1.1.1.tgz", - "integrity": "sha512-1WmsopSGhWt5laNir+633LszXvZ+Z/lxveBf6yhGsqnQIhlhzooZae7zV6YVM1Sdkw68dtAW3ow0pOdPANugvA==", - "requires": { - "unist-util-stringify-position": "^1.1.1" - } - }, - "vm-browserify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.0.tgz", - "integrity": "sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==" - }, - "w3c-hr-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", - "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", - "requires": { - "browser-process-hrtime": "^0.1.2" + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" } }, - "w3c-xmlserializer": { + "value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" + }, + "vary": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz", - "integrity": "sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg==", - "requires": { - "domexception": "^1.0.1", - "webidl-conversions": "^4.0.2", - "xml-name-validator": "^3.0.0" - } + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true }, - "walker": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", - "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, "requires": { - "makeerror": "1.0.x" + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" } }, + "vm-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.0.tgz", + "integrity": "sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==", + "dev": true + }, "warning": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", @@ -14146,6 +12307,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==", + "dev": true, "requires": { "chokidar": "^2.0.2", "graceful-fs": "^4.1.2", @@ -14156,58 +12318,118 @@ "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" } }, - "web-namespaces": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-1.1.3.tgz", - "integrity": "sha512-r8sAtNmgR0WKOKOxzuSgk09JsHlpKlB+uHi937qypOu3PZ17UxPrierFKDye/uNHjNTTEshu5PId8rojIPj/tA==" - }, - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" - }, "webpack": { - "version": "4.29.6", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.29.6.tgz", - "integrity": "sha512-MwBwpiE1BQpMDkbnUUaW6K8RFZjljJHArC6tWQJoFm0oQtfoSebtg4Y7/QHnJ/SddtjYLHaKGX64CFjG5rehJw==", + "version": "4.41.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.41.2.tgz", + "integrity": "sha512-Zhw69edTGfbz9/8JJoyRQ/pq8FYUoY0diOXqW0T6yhgdhCv6wr0hra5DwwWexNRns2Z2+gsnrNcbe9hbGBgk/A==", + "dev": true, "requires": { "@webassemblyjs/ast": "1.8.5", "@webassemblyjs/helper-module-context": "1.8.5", "@webassemblyjs/wasm-edit": "1.8.5", "@webassemblyjs/wasm-parser": "1.8.5", - "acorn": "^6.0.5", - "acorn-dynamic-import": "^4.0.0", - "ajv": "^6.1.0", - "ajv-keywords": "^3.1.0", - "chrome-trace-event": "^1.0.0", + "acorn": "^6.2.1", + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1", + "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^4.1.0", - "eslint-scope": "^4.0.0", + "eslint-scope": "^4.0.3", "json-parse-better-errors": "^1.0.2", - "loader-runner": "^2.3.0", - "loader-utils": "^1.1.0", - "memory-fs": "~0.4.1", - "micromatch": "^3.1.8", - "mkdirp": "~0.5.0", - "neo-async": "^2.5.0", - "node-libs-browser": "^2.0.0", + "loader-runner": "^2.4.0", + "loader-utils": "^1.2.3", + "memory-fs": "^0.4.1", + "micromatch": "^3.1.10", + "mkdirp": "^0.5.1", + "neo-async": "^2.6.1", + "node-libs-browser": "^2.2.1", "schema-utils": "^1.0.0", - "tapable": "^1.1.0", - "terser-webpack-plugin": "^1.1.0", - "watchpack": "^1.5.0", - "webpack-sources": "^1.3.0" + "tapable": "^1.1.3", + "terser-webpack-plugin": "^1.4.1", + "watchpack": "^1.6.0", + "webpack-sources": "^1.4.1" + }, + "dependencies": { + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "webpack-cli": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.10.tgz", + "integrity": "sha512-u1dgND9+MXaEt74sJR4PR7qkPxXUSQ0RXYq8x1L6Jg1MYVEmGPrH6Ah6C4arD4r0J1P5HKjRqpab36k0eIzPqg==", + "dev": true, + "requires": { + "chalk": "2.4.2", + "cross-spawn": "6.0.5", + "enhanced-resolve": "4.1.0", + "findup-sync": "3.0.0", + "global-modules": "2.0.0", + "import-local": "2.0.0", + "interpret": "1.2.0", + "loader-utils": "1.2.3", + "supports-color": "6.1.0", + "v8-compile-cache": "2.0.3", + "yargs": "13.2.4" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "enhanced-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz", + "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.4.0", + "tapable": "^1.0.0" + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "webpack-dev-middleware": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.0.tgz", - "integrity": "sha512-qvDesR1QZRIAZHOE3iQ4CXLZZSQ1lAUsSpnQmlB1PBfoN/xdRjmge3Dok0W4IdaVLJOGJy3sGI4sZHwjRU0PCA==", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz", + "integrity": "sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw==", + "dev": true, "requires": { "memory-fs": "^0.4.1", - "mime": "^2.4.2", + "mime": "^2.4.4", + "mkdirp": "^0.5.1", "range-parser": "^1.2.1", "webpack-log": "^2.0.0" }, @@ -14215,93 +12437,158 @@ "mime": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", - "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==" + "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", + "dev": true } } }, "webpack-dev-server": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.2.1.tgz", - "integrity": "sha512-sjuE4mnmx6JOh9kvSbPYw3u/6uxCLHNWfhWaIPwcXWsvWOPN+nc5baq4i9jui3oOBRXGonK9+OI0jVkaz6/rCw==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.9.0.tgz", + "integrity": "sha512-E6uQ4kRrTX9URN9s/lIbqTAztwEPdvzVrcmHE8EQ9YnuT9J8Es5Wrd8n9BKg1a0oZ5EgEke/EQFgUsp18dSTBw==", + "dev": true, "requires": { "ansi-html": "0.0.7", "bonjour": "^3.5.0", - "chokidar": "^2.0.0", - "compression": "^1.5.2", - "connect-history-api-fallback": "^1.3.0", + "chokidar": "^2.1.8", + "compression": "^1.7.4", + "connect-history-api-fallback": "^1.6.0", "debug": "^4.1.1", - "del": "^3.0.0", - "express": "^4.16.2", - "html-entities": "^1.2.0", - "http-proxy-middleware": "^0.19.1", + "del": "^4.1.1", + "express": "^4.17.1", + "html-entities": "^1.2.1", + "http-proxy-middleware": "0.19.1", "import-local": "^2.0.0", - "internal-ip": "^4.2.0", + "internal-ip": "^4.3.0", "ip": "^1.1.5", - "killable": "^1.0.0", - "loglevel": "^1.4.1", - "opn": "^5.1.0", - "portfinder": "^1.0.9", + "is-absolute-url": "^3.0.3", + "killable": "^1.0.1", + "loglevel": "^1.6.4", + "opn": "^5.5.0", + "p-retry": "^3.0.1", + "portfinder": "^1.0.25", "schema-utils": "^1.0.0", - "selfsigned": "^1.9.1", - "semver": "^5.6.0", - "serve-index": "^1.7.2", + "selfsigned": "^1.10.7", + "semver": "^6.3.0", + "serve-index": "^1.9.1", "sockjs": "0.3.19", - "sockjs-client": "1.3.0", - "spdy": "^4.0.0", - "strip-ansi": "^3.0.0", + "sockjs-client": "1.4.0", + "spdy": "^4.0.1", + "strip-ansi": "^3.0.1", "supports-color": "^6.1.0", "url": "^0.11.0", - "webpack-dev-middleware": "^3.5.1", + "webpack-dev-middleware": "^3.7.2", "webpack-log": "^2.0.0", - "yargs": "12.0.2" + "ws": "^6.2.1", + "yargs": "12.0.5" }, "dependencies": { "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true }, - "camelcase": { + "cliui": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } }, - "decamelize": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz", - "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==", + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, "requires": { - "xregexp": "4.0.0" + "number-is-nan": "^1.0.0" } }, "require-main-filename": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" } }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, "supports-color": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, "requires": { "has-flag": "^3.0.0" } }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, "yargs": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.2.tgz", - "integrity": "sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ==", + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, "requires": { "cliui": "^4.0.0", - "decamelize": "^2.0.0", + "decamelize": "^1.2.0", "find-up": "^3.0.0", "get-caller-file": "^1.0.1", "os-locale": "^3.0.0", @@ -14311,15 +12598,17 @@ "string-width": "^2.0.0", "which-module": "^2.0.0", "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^10.1.0" + "yargs-parser": "^11.1.1" } }, "yargs-parser": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", - "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, "requires": { - "camelcase": "^4.1.0" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" } } } @@ -14328,34 +12617,35 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", + "dev": true, "requires": { "ansi-colors": "^3.0.0", "uuid": "^3.3.2" } }, - "webpack-manifest-plugin": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-2.0.4.tgz", - "integrity": "sha512-nejhOHexXDBKQOj/5v5IZSfCeTO3x1Dt1RZEcGfBSul891X/eLIcIVH31gwxPDdsi2Z8LKKFGpM4w9+oTBOSCg==", - "requires": { - "fs-extra": "^7.0.0", - "lodash": ">=3.5 <5", - "tapable": "^1.0.0" - } - }, "webpack-sources": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz", - "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, "requires": { "source-list-map": "^2.0.0", "source-map": "~0.6.1" + }, + "dependencies": { + "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 + } } }, "websocket-driver": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.3.tgz", "integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==", + "dev": true, "requires": { "http-parser-js": ">=0.4.0 <0.4.11", "safe-buffer": ">=5.1.0", @@ -14365,40 +12655,19 @@ "websocket-extensions": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", - "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==" - }, - "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "requires": { - "iconv-lite": "0.4.24" - } + "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", + "dev": true }, "whatwg-fetch": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==" }, - "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" - }, - "whatwg-url": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", - "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, "requires": { "isexe": "^2.0.0" } @@ -14406,236 +12675,68 @@ "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true }, "wide-align": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, "requires": { "string-width": "^1.0.2 || 2" } }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" - }, - "workbox-background-sync": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-4.3.1.tgz", - "integrity": "sha512-1uFkvU8JXi7L7fCHVBEEnc3asPpiAL33kO495UMcD5+arew9IbKW2rV5lpzhoWcm/qhGB89YfO4PmB/0hQwPRg==", - "requires": { - "workbox-core": "^4.3.1" - } - }, - "workbox-broadcast-update": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-4.3.1.tgz", - "integrity": "sha512-MTSfgzIljpKLTBPROo4IpKjESD86pPFlZwlvVG32Kb70hW+aob4Jxpblud8EhNb1/L5m43DUM4q7C+W6eQMMbA==", - "requires": { - "workbox-core": "^4.3.1" - } - }, - "workbox-build": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-4.3.1.tgz", - "integrity": "sha512-UHdwrN3FrDvicM3AqJS/J07X0KXj67R8Cg0waq1MKEOqzo89ap6zh6LmaLnRAjpB+bDIz+7OlPye9iii9KBnxw==", - "requires": { - "@babel/runtime": "^7.3.4", - "@hapi/joi": "^15.0.0", - "common-tags": "^1.8.0", - "fs-extra": "^4.0.2", - "glob": "^7.1.3", - "lodash.template": "^4.4.0", - "pretty-bytes": "^5.1.0", - "stringify-object": "^3.3.0", - "strip-comments": "^1.0.2", - "workbox-background-sync": "^4.3.1", - "workbox-broadcast-update": "^4.3.1", - "workbox-cacheable-response": "^4.3.1", - "workbox-core": "^4.3.1", - "workbox-expiration": "^4.3.1", - "workbox-google-analytics": "^4.3.1", - "workbox-navigation-preload": "^4.3.1", - "workbox-precaching": "^4.3.1", - "workbox-range-requests": "^4.3.1", - "workbox-routing": "^4.3.1", - "workbox-strategies": "^4.3.1", - "workbox-streams": "^4.3.1", - "workbox-sw": "^4.3.1", - "workbox-window": "^4.3.1" - }, - "dependencies": { - "fs-extra": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", - "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - } - } - }, - "workbox-cacheable-response": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-4.3.1.tgz", - "integrity": "sha512-Rp5qlzm6z8IOvnQNkCdO9qrDgDpoPNguovs0H8C+wswLuPgSzSp9p2afb5maUt9R1uTIwOXrVQMmPfPypv+npw==", - "requires": { - "workbox-core": "^4.3.1" - } - }, - "workbox-core": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-4.3.1.tgz", - "integrity": "sha512-I3C9jlLmMKPxAC1t0ExCq+QoAMd0vAAHULEgRZ7kieCdUd919n53WC0AfvokHNwqRhGn+tIIj7vcb5duCjs2Kg==" - }, - "workbox-expiration": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-4.3.1.tgz", - "integrity": "sha512-vsJLhgQsQouv9m0rpbXubT5jw0jMQdjpkum0uT+d9tTwhXcEZks7qLfQ9dGSaufTD2eimxbUOJfWLbNQpIDMPw==", - "requires": { - "workbox-core": "^4.3.1" - } - }, - "workbox-google-analytics": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-4.3.1.tgz", - "integrity": "sha512-xzCjAoKuOb55CBSwQrbyWBKqp35yg1vw9ohIlU2wTy06ZrYfJ8rKochb1MSGlnoBfXGWss3UPzxR5QL5guIFdg==", - "requires": { - "workbox-background-sync": "^4.3.1", - "workbox-core": "^4.3.1", - "workbox-routing": "^4.3.1", - "workbox-strategies": "^4.3.1" - } - }, - "workbox-navigation-preload": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-4.3.1.tgz", - "integrity": "sha512-K076n3oFHYp16/C+F8CwrRqD25GitA6Rkd6+qAmLmMv1QHPI2jfDwYqrytOfKfYq42bYtW8Pr21ejZX7GvALOw==", - "requires": { - "workbox-core": "^4.3.1" - } - }, - "workbox-precaching": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-4.3.1.tgz", - "integrity": "sha512-piSg/2csPoIi/vPpp48t1q5JLYjMkmg5gsXBQkh/QYapCdVwwmKlU9mHdmy52KsDGIjVaqEUMFvEzn2LRaigqQ==", - "requires": { - "workbox-core": "^4.3.1" - } - }, - "workbox-range-requests": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-4.3.1.tgz", - "integrity": "sha512-S+HhL9+iTFypJZ/yQSl/x2Bf5pWnbXdd3j57xnb0V60FW1LVn9LRZkPtneODklzYuFZv7qK6riZ5BNyc0R0jZA==", - "requires": { - "workbox-core": "^4.3.1" - } - }, - "workbox-routing": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-4.3.1.tgz", - "integrity": "sha512-FkbtrODA4Imsi0p7TW9u9MXuQ5P4pVs1sWHK4dJMMChVROsbEltuE79fBoIk/BCztvOJ7yUpErMKa4z3uQLX+g==", - "requires": { - "workbox-core": "^4.3.1" - } - }, - "workbox-strategies": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-4.3.1.tgz", - "integrity": "sha512-F/+E57BmVG8dX6dCCopBlkDvvhg/zj6VDs0PigYwSN23L8hseSRwljrceU2WzTvk/+BSYICsWmRq5qHS2UYzhw==", - "requires": { - "workbox-core": "^4.3.1" - } - }, - "workbox-streams": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-4.3.1.tgz", - "integrity": "sha512-4Kisis1f/y0ihf4l3u/+ndMkJkIT4/6UOacU3A4BwZSAC9pQ9vSvJpIi/WFGQRH/uPXvuVjF5c2RfIPQFSS2uA==", - "requires": { - "workbox-core": "^4.3.1" - } - }, - "workbox-sw": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-4.3.1.tgz", - "integrity": "sha512-0jXdusCL2uC5gM3yYFT6QMBzKfBr2XTk0g5TPAV4y8IZDyVNDyj1a8uSXy3/XrvkVTmQvLN4O5k3JawGReXr9w==" - }, - "workbox-webpack-plugin": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/workbox-webpack-plugin/-/workbox-webpack-plugin-4.2.0.tgz", - "integrity": "sha512-YZsiA+y/ns/GdWRaBsfYv8dln1ebWtGnJcTOg1ppO0pO1tScAHX0yGtHIjndxz3L/UUhE8b0NQE9KeLNwJwA5A==", - "requires": { - "@babel/runtime": "^7.0.0", - "json-stable-stringify": "^1.0.1", - "workbox-build": "^4.2.0" - } - }, - "workbox-window": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-4.3.1.tgz", - "integrity": "sha512-C5gWKh6I58w3GeSc0wp2Ne+rqVw8qwcmZnQGpjiek8A2wpbxSJb1FdCoQVO+jDJs35bFgo/WETgl1fqgsxN0Hg==", - "requires": { - "workbox-core": "^4.3.1" - } + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true }, "worker-farm": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", + "dev": true, "requires": { "errno": "~0.1.7" } }, - "worker-rpc": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/worker-rpc/-/worker-rpc-0.1.1.tgz", - "integrity": "sha512-P1WjMrUB3qgJNI9jfmpZ/htmBEjFh//6l/5y8SD9hg1Ef5zTTVVoRjTrTEzPrNBQvmhMxkoTsjOXN10GWU7aCg==", - "requires": { - "microevent.ts": "~0.1.1" - } - }, "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" }, "dependencies": { "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "^1.0.0" - } + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true }, "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" } }, "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "^4.1.0" } } } @@ -14643,99 +12744,97 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true }, "write": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, "requires": { "mkdirp": "^0.5.1" } }, - "write-file-atomic": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.1.tgz", - "integrity": "sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg==", - "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - }, "ws": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", - "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", + "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", + "dev": true, "requires": { "async-limiter": "~1.0.0" } }, - "x-is-string": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/x-is-string/-/x-is-string-0.1.0.tgz", - "integrity": "sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI=" - }, - "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" - }, - "xmlchars": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.1.1.tgz", - "integrity": "sha512-7hew1RPJ1iIuje/Y01bGD/mXokXxegAgVS+e+E0wSi2ILHQkYAH1+JXARwTjZSM4Z4Z+c73aKspEcqj+zPPL/w==" - }, - "xregexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz", - "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==" - }, "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true }, "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true }, "yallist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true }, "yargs": { - "version": "12.0.5", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", - "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.4.tgz", + "integrity": "sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==", + "dev": true, "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.2.0", + "cliui": "^5.0.0", "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.0.0", + "get-caller-file": "^2.0.1", + "os-locale": "^3.1.0", "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", + "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", - "string-width": "^2.0.0", + "string-width": "^3.0.0", "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^11.1.1" + "y18n": "^4.0.0", + "yargs-parser": "^13.1.0" }, "dependencies": { - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } } } }, "yargs-parser": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", - "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", + "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", + "dev": true, "requires": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" diff --git a/cvat-ui/package.json b/cvat-ui/package.json index cc5aaff0fe3..60f6aa30c61 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,56 +1,74 @@ { "name": "cvat-ui", - "version": "0.1.0", + "version": "0.5.2", + "description": "CVAT single-page application", + "main": "src/index.tsx", + "scripts": { + "build": "webpack --config ./webpack.config.js", + "start": "REACT_APP_API_URL=http://localhost:7000 webpack-dev-server --config ./webpack.config.js --mode=development", + "type-check": "tsc --noEmit", + "type-check:watch": "npm run type-check -- --watch", + "lint": "eslint './src/**/*.{ts,tsx}'", + "lint:fix": "eslint './src/**/*.{ts,tsx}' --fix" + }, + "author": "Intel", "license": "MIT", - "private": true, + "devDependencies": { + "@babel/core": "^7.6.0", + "@babel/plugin-proposal-class-properties": "^7.5.5", + "@babel/preset-env": "^7.6.0", + "@babel/preset-react": "^7.0.0", + "@babel/preset-typescript": "^7.6.0", + "@typescript-eslint/eslint-plugin": "^2.19.2", + "@typescript-eslint/parser": "^2.19.2", + "babel-loader": "^8.0.6", + "babel-plugin-import": "^1.12.2", + "css-loader": "^3.2.0", + "eslint": "^6.8.0", + "eslint-config-airbnb-typescript": "^7.0.0", + "eslint-import-resolver-typescript": "^2.0.0", + "eslint-plugin-import": "^2.18.2", + "eslint-plugin-jsx-a11y": "^6.2.3", + "eslint-plugin-react": "^7.17.0", + "eslint-plugin-react-hooks": "^1.7.0", + "html-webpack-plugin": "^3.2.0", + "less": "^3.10.3", + "less-loader": "^5.0.0", + "node-sass": "^4.13.0", + "postcss-loader": "^3.0.0", + "postcss-preset-env": "^6.7.0", + "react-svg-loader": "^3.0.3", + "sass-loader": "^8.0.0", + "style-loader": "^1.0.0", + "tsconfig-paths-webpack-plugin": "^3.2.0", + "typescript": "^3.7.3", + "webpack": "^4.41.2", + "webpack-cli": "^3.3.8", + "webpack-dev-server": "^3.8.0" + }, "dependencies": { - "@types/jest": "24.0.13", - "@types/node": "^12.0.3", - "@types/react": "16.8.19", - "@types/react-dom": "16.8.4", - "@types/react-redux": "^7.1.1", - "@types/react-router-dom": "^4.3.4", + "@types/react": "^16.9.2", + "@types/react-dom": "^16.9.0", + "@types/react-redux": "^7.1.2", + "@types/react-router": "^5.0.5", + "@types/react-router-dom": "^5.1.0", + "@types/react-share": "^3.0.1", "@types/redux-logger": "^3.0.7", - "antd": "^3.19.1", - "babel-plugin-import": "^1.11.2", - "customize-cra": "^0.2.12", - "less": "^3.9.0", - "less-loader": "^5.0.0", - "node-sass": "^4.12.0", - "query-string": "^6.8.1", - "react": "^16.8.6", - "react-app-rewired": "^2.1.3", - "react-dom": "^16.8.6", - "react-redux": "^7.1.0", - "react-router-dom": "^5.0.1", - "react-scripts": "3.0.1", - "react-scripts-ts": "^3.1.0", + "antd": "^3.25.2", + "copy-to-clipboard": "^3.2.0", + "dotenv-webpack": "^1.7.0", + "moment": "^2.24.0", + "prop-types": "^15.7.2", + "react": "^16.9.0", + "react-dom": "^16.9.0", + "react-hotkeys": "^2.0.0", + "react-redux": "^7.1.1", + "react-router": "^5.1.0", + "react-router-dom": "^5.1.0", + "react-share": "^3.0.1", "redux": "^4.0.4", + "redux-devtools-extension": "^2.13.8", "redux-logger": "^3.0.6", - "redux-thunk": "^2.3.0", - "source-map-explorer": "^1.8.0", - "typescript": "3.4.5" - }, - "scripts": { - "analyze": "source-map-explorer 'build/static/js/*.js'", - "start": "react-app-rewired start", - "build": "react-app-rewired build", - "test": "react-app-rewired test", - "eject": "react-scripts eject" - }, - "eslintConfig": { - "extends": "react-app" - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] + "redux-thunk": "^2.3.0" } } diff --git a/cvat-ui/postcss.config.js b/cvat-ui/postcss.config.js new file mode 100644 index 00000000000..6493a9ea1ed --- /dev/null +++ b/cvat-ui/postcss.config.js @@ -0,0 +1,13 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +/* eslint-disable */ +module.exports = { + parser: false, + plugins: { + 'postcss-preset-env': { + browsers: '> 2.5%', // https://github.com/browserslist/browserslist + }, + }, +}; diff --git a/cvat-ui/public/cvat-core.min.js b/cvat-ui/public/cvat-core.min.js deleted file mode 100644 index 79057d9a915..00000000000 --- a/cvat-ui/public/cvat-core.min.js +++ /dev/null @@ -1,15 +0,0 @@ -window.cvat=function(t){var e={};function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)n.d(r,o,function(e){return t[e]}.bind(null,o));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=90)}([function(t,e,n){(function(e){var n="object",r=function(t){return t&&t.Math==Math&&t};t.exports=r(typeof globalThis==n&&globalThis)||r(typeof window==n&&window)||r(typeof self==n&&self)||r(typeof e==n&&e)||Function("return this")()}).call(this,n(28))},function(t,e,n){var r=n(0),o=n(31),i=n(57),s=n(97),a=r.Symbol,c=o("wks");t.exports=function(t){return c[t]||(c[t]=s&&a[t]||(s?a:i)("Symbol."+t))}},function(t,e){t.exports=function(t){try{return!!t()}catch(t){return!0}}},function(t,e,n){var r=n(11);t.exports=function(t){if(!r(t))throw TypeError(String(t)+" is not an object");return t}},function(t,e,n){"use strict";var r=n(30),o=n(108),i=n(25),s=n(17),a=n(73),c=s.set,u=s.getterFor("Array Iterator");t.exports=a(Array,"Array",function(t,e){c(this,{type:"Array Iterator",target:r(t),index:0,kind:e})},function(){var t=u(this),e=t.target,n=t.kind,r=t.index++;return!e||r>=e.length?(t.target=void 0,{value:void 0,done:!0}):"keys"==n?{value:r,done:!1}:"values"==n?{value:e[r],done:!1}:{value:[r,e[r]],done:!1}},"values"),i.Arguments=i.Array,o("keys"),o("values"),o("entries")},function(t,e,n){n(9),(()=>{const e=n(113),r=n(115),o=n(48);class i extends Error{constructor(t){super(t);const n=(new Date).toISOString(),i=e.os.toString(),s=`${e.name} ${e.version}`,a=r.parse(this)[0],c=`${a.fileName}`,u=a.lineNumber,l=a.columnNumber,{jobID:f,taskID:p,clientID:h}=o;Object.defineProperties(this,Object.freeze({system:{get:()=>i},client:{get:()=>s},time:{get:()=>n},jobID:{get:()=>f},taskID:{get:()=>p},projID:{get:()=>void 0},clientID:{get:()=>h},filename:{get:()=>c},line:{get:()=>u},column:{get:()=>l}}))}async save(){const t={system:this.system,client:this.client,time:this.time,job_id:this.jobID,task_id:this.taskID,proj_id:this.projID,client_id:this.clientID,message:this.message,filename:this.filename,line:this.line,column:this.column,stack:this.stack};try{const e=n(18);await e.server.exception(t)}catch(t){}}}t.exports={Exception:i,ArgumentError:class extends i{constructor(t){super(t)}},DataError:class extends i{constructor(t){super(t)}},ScriptingError:class extends i{constructor(t){super(t)}},PluginError:class extends i{constructor(t){super(t)}},ServerError:class extends i{constructor(t,e){super(t),Object.defineProperties(this,Object.freeze({code:{get:()=>e}}))}}}})()},function(t,e,n){"use strict";var r=n(78),o=n(136),i=Object.prototype.toString;function s(t){return"[object Array]"===i.call(t)}function a(t){return null!==t&&"object"==typeof t}function c(t){return"[object Function]"===i.call(t)}function u(t,e){if(null!=t)if("object"!=typeof t&&(t=[t]),s(t))for(var n=0,r=t.length;ns;){var a,c,u,l=r[s++],f=i?l.ok:l.fail,p=l.resolve,h=l.reject,d=l.domain;try{f?(i||(2===e.rejection&&et(t,e),e.rejection=1),!0===f?a=o:(d&&d.enter(),a=f(o),d&&(d.exit(),u=!0)),a===l.promise?h(D("Promise-chain cycle")):(c=K(a))?c.call(a,p,h):p(a)):h(o)}catch(t){d&&!u&&d.exit(),h(t)}}e.reactions=[],e.notified=!1,n&&!e.rejection&&Q(t,e)})}},Y=function(t,e,n){var r,o;V?((r=L.createEvent("Event")).promise=e,r.reason=n,r.initEvent(t,!1,!0),u.dispatchEvent(r)):r={promise:e,reason:n},(o=u["on"+t])?o(r):"unhandledrejection"===t&&A("Unhandled promise rejection",n)},Q=function(t,e){j.call(u,function(){var n,r=e.value;if(tt(e)&&(n=T(function(){q?z.emit("unhandledRejection",r,t):Y("unhandledrejection",t,r)}),e.rejection=q||tt(e)?2:1,n.error))throw n.value})},tt=function(t){return 1!==t.rejection&&!t.parent},et=function(t,e){j.call(u,function(){q?z.emit("rejectionHandled",t):Y("rejectionhandled",t,e.value)})},nt=function(t,e,n,r){return function(o){t(e,n,o,r)}},rt=function(t,e,n,r){e.done||(e.done=!0,r&&(e=r),e.value=n,e.state=2,Z(t,e,!0))},ot=function(t,e,n,r){if(!e.done){e.done=!0,r&&(e=r);try{if(t===n)throw D("Promise can't be resolved itself");var o=K(n);o?k(function(){var r={done:!1};try{o.call(n,nt(ot,t,r,e),nt(rt,t,r,e))}catch(n){rt(t,r,n,e)}}):(e.value=n,e.state=1,Z(t,e,!1))}catch(n){rt(t,{done:!1},n,e)}}};X&&(R=function(t){y(this,R,C),m(t),r.call(this);var e=N(this);try{t(nt(ot,this,e),nt(rt,this,e))}catch(t){rt(this,e,t)}},(r=function(t){$(this,{type:C,done:!1,notified:!1,parent:!1,reactions:[],rejection:!1,state:0,value:void 0})}).prototype=h(R.prototype,{then:function(t,e){var n=M(this),r=W(x(this,R));return r.ok="function"!=typeof t||t,r.fail="function"==typeof e&&e,r.domain=q?z.domain:void 0,n.parent=!0,n.reactions.push(r),0!=n.state&&Z(this,n,!1),r.promise},catch:function(t){return this.then(void 0,t)}}),o=function(){var t=new r,e=N(t);this.promise=t,this.resolve=nt(ot,t,e),this.reject=nt(rt,t,e)},E.f=W=function(t){return t===R||t===i?new o(t):J(t)},c||"function"!=typeof f||(s=f.prototype.then,p(f.prototype,"then",function(t,e){var n=this;return new R(function(t,e){s.call(n,t,e)}).then(t,e)}),"function"==typeof B&&a({global:!0,enumerable:!0,forced:!0},{fetch:function(t){return S(R,B.apply(u,arguments))}}))),a({global:!0,wrap:!0,forced:X},{Promise:R}),d(R,C,!1,!0),b(C),i=l.Promise,a({target:C,stat:!0,forced:X},{reject:function(t){var e=W(this);return e.reject.call(void 0,t),e.promise}}),a({target:C,stat:!0,forced:c||X},{resolve:function(t){return S(c&&this===i?R:this,t)}}),a({target:C,stat:!0,forced:H},{all:function(t){var e=this,n=W(e),r=n.resolve,o=n.reject,i=T(function(){var n=m(e.resolve),i=[],s=0,a=1;w(t,function(t){var c=s++,u=!1;i.push(void 0),a++,n.call(e,t).then(function(t){u||(u=!0,i[c]=t,--a||r(i))},o)}),--a||r(i)});return i.error&&o(i.value),n.promise},race:function(t){var e=this,n=W(e),r=n.reject,o=T(function(){var o=m(e.resolve);w(t,function(t){o.call(e,t).then(n.resolve,r)})});return o.error&&r(o.value),n.promise}})},function(t,e,n){var r=n(2);t.exports=!r(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(t,e){t.exports=function(t){return"object"==typeof t?null!==t:"function"==typeof t}},function(t,e,n){var r=n(10),o=n(14),i=n(29);t.exports=r?function(t,e,n){return o.f(t,e,i(1,n))}:function(t,e,n){return t[e]=n,t}},function(t,e,n){var r=n(0),o=n(40).f,i=n(12),s=n(15),a=n(43),c=n(58),u=n(62);t.exports=function(t,e){var n,l,f,p,h,d=t.target,b=t.global,g=t.stat;if(n=b?r:g?r[d]||a(d,{}):(r[d]||{}).prototype)for(l in e){if(p=e[l],f=t.noTargetGet?(h=o(n,l))&&h.value:n[l],!u(b?l:d+(g?".":"#")+l,t.forced)&&void 0!==f){if(typeof p==typeof f)continue;c(p,f)}(t.sham||f&&f.sham)&&i(p,"sham",!0),s(n,l,p,t)}}},function(t,e,n){var r=n(10),o=n(55),i=n(3),s=n(41),a=Object.defineProperty;e.f=r?a:function(t,e,n){if(i(t),e=s(e,!0),i(n),o)try{return a(t,e,n)}catch(t){}if("get"in n||"set"in n)throw TypeError("Accessors not supported");return"value"in n&&(t[e]=n.value),t}},function(t,e,n){var r=n(0),o=n(31),i=n(12),s=n(7),a=n(43),c=n(56),u=n(17),l=u.get,f=u.enforce,p=String(c).split("toString");o("inspectSource",function(t){return c.call(t)}),(t.exports=function(t,e,n,o){var c=!!o&&!!o.unsafe,u=!!o&&!!o.enumerable,l=!!o&&!!o.noTargetGet;"function"==typeof n&&("string"!=typeof e||s(n,"name")||i(n,"name",e),f(n).source=p.join("string"==typeof e?e:"")),t!==r?(c?!l&&t[e]&&(u=!0):delete t[e],u?t[e]=n:i(t,e,n)):u?t[e]=n:a(e,n)})(Function.prototype,"toString",function(){return"function"==typeof this&&l(this).source||c.call(this)})},function(t,e){var n={}.toString;t.exports=function(t){return n.call(t).slice(8,-1)}},function(t,e,n){var r,o,i,s=n(91),a=n(0),c=n(11),u=n(12),l=n(7),f=n(44),p=n(45),h=a.WeakMap;if(s){var d=new h,b=d.get,g=d.has,m=d.set;r=function(t,e){return m.call(d,t,e),e},o=function(t){return b.call(d,t)||{}},i=function(t){return g.call(d,t)}}else{var y=f("state");p[y]=!0,r=function(t,e){return u(t,y,e),e},o=function(t){return l(t,y)?t[y]:{}},i=function(t){return l(t,y)}}t.exports={set:r,get:o,has:i,enforce:function(t){return i(t)?o(t):r(t,{})},getterFor:function(t){return function(e){var n;if(!c(e)||(n=o(e)).type!==t)throw TypeError("Incompatible receiver, "+t+" required");return n}}}},function(t,e,n){n(9),n(117),(()=>{const e=n(122),{ServerError:r}=n(5),o=n(123),i=n(48);function s(t,e){if(t.response){const n=`${e}. `+`${t.message}. ${JSON.stringify(t.response.data)||""}.`;return new r(n,t.response.status)}const n=`${e}. `+`${t.message}.`;return new r(n,0)}const a=new class{constructor(){const a=n(134);a.defaults.withCredentials=!0,a.defaults.xsrfHeaderName="X-CSRFTOKEN",a.defaults.xsrfCookieName="csrftoken";let c=o.get("token");async function u(t=""){const{backendAPI:e}=i;let n=null;try{n=await a.get(`${e}/tasks?${t}`,{proxy:i.proxy})}catch(t){throw s(t,"Could not get tasks from a server")}return n.data.results.count=n.data.count,n.data.results}async function l(t){const{backendAPI:e}=i;try{await a.delete(`${e}/tasks/${t}`)}catch(t){throw s(t,"Could not delete the task from the server")}}c&&(a.defaults.headers.common.Authorization=`Token ${c}`),Object.defineProperties(this,Object.freeze({server:{value:Object.freeze({about:async function(){const{backendAPI:t}=i;let e=null;try{e=await a.get(`${t}/server/about`,{proxy:i.proxy})}catch(t){throw s(t,'Could not get "about" information from the server')}return e.data},share:async function(t){const{backendAPI:e}=i;t=encodeURIComponent(t);let n=null;try{n=await a.get(`${e}/server/share?directory=${t}`,{proxy:i.proxy})}catch(t){throw s(t,'Could not get "share" information from the server')}return n.data},formats:async function(){const{backendAPI:t}=i;let e=null;try{e=await a.get(`${t}/server/annotation/formats`,{proxy:i.proxy})}catch(t){throw s(t,"Could not get annotation formats from the server")}return e.data},exception:async function(t){const{backendAPI:e}=i;try{await a.post(`${e}/server/exception`,JSON.stringify(t),{proxy:i.proxy,headers:{"Content-Type":"application/json"}})}catch(t){throw s(t,"Could not send an exception to the server")}},login:async function(t,e){const n=[`${encodeURIComponent("username")}=${encodeURIComponent(t)}`,`${encodeURIComponent("password")}=${encodeURIComponent(e)}`].join("&").replace(/%20/g,"+");let r=null;try{r=await a.post(`${i.backendAPI}/auth/login`,n,{proxy:i.proxy})}catch(t){throw s(t,"Could not login on a server")}if(r.headers["set-cookie"]){const t=r.headers["set-cookie"].join(";");a.defaults.headers.common.Cookie=t}c=r.data.key,o.set("token",c),a.defaults.headers.common.Authorization=`Token ${c}`},logout:async function(){try{await a.post(`${i.backendAPI}/auth/logout`,{proxy:i.proxy})}catch(t){throw s(t,"Could not logout from the server")}o.remove("token"),a.defaults.headers.common.Authorization=""},authorized:async function(){try{await t.exports.users.getSelf()}catch(t){if(401===t.code)return!1;throw t}return!0},register:async function(t,e,n,r,o,c){let u=null;try{const l=JSON.stringify({username:t,first_name:e,last_name:n,email:r,password1:o,password2:c});u=await a.post(`${i.backendAPI}/auth/register`,l,{proxy:i.proxy,headers:{"Content-Type":"application/json"}})}catch(e){throw s(e,`Could not register '${t}' user on the server`)}return u.data}}),writable:!1},tasks:{value:Object.freeze({getTasks:u,saveTask:async function(t,e){const{backendAPI:n}=i;try{await a.patch(`${n}/tasks/${t}`,JSON.stringify(e),{proxy:i.proxy,headers:{"Content-Type":"application/json"}})}catch(t){throw s(t,"Could not save the task on the server")}},createTask:async function(t,n,o){const{backendAPI:c}=i,f=new e;for(const t in n)if(Object.prototype.hasOwnProperty.call(n,t))for(let e=0;e{setTimeout(async function i(){try{const u=await a.get(`${c}/tasks/${t}/status`);if(["Queued","Started"].includes(u.data.state))""!==u.data.message&&o(u.data.message),setTimeout(i,1e3);else if("Finished"===u.data.state)e();else if("Failed"===u.data.state){const t="Could not create the task on the server. "+`${u.data.message}.`;n(new r(t,400))}else n(new r(`Unknown task state has been received: ${u.data.state}`,500))}catch(t){n(s(t,"Could not put task to the server"))}},1e3)})}(p.data.id)}catch(t){throw await l(p.data.id),t}return(await u(`?id=${p.id}`))[0]},deleteTask:l}),writable:!1},jobs:{value:Object.freeze({getJob:async function(t){const{backendAPI:e}=i;let n=null;try{n=await a.get(`${e}/jobs/${t}`,{proxy:i.proxy})}catch(t){throw s(t,"Could not get jobs from a server")}return n.data},saveJob:async function(t,e){const{backendAPI:n}=i;try{await a.patch(`${n}/jobs/${t}`,JSON.stringify(e),{proxy:i.proxy,headers:{"Content-Type":"application/json"}})}catch(t){throw s(t,"Could not save the job on the server")}}}),writable:!1},users:{value:Object.freeze({getUsers:async function(){const{backendAPI:t}=i;let e=null;try{e=await a.get(`${t}/users`,{proxy:i.proxy})}catch(t){throw s(t,"Could not get users from the server")}return e.data.results},getSelf:async function(){const{backendAPI:t}=i;let e=null;try{e=await a.get(`${t}/users/self`,{proxy:i.proxy})}catch(t){throw s(t,"Could not get user data from the server")}return e.data}}),writable:!1},frames:{value:Object.freeze({getData:async function(t,e){const{backendAPI:n}=i;let r=null;try{r=await a.get(`${n}/tasks/${t}/frames/${e}`,{proxy:i.proxy,responseType:"blob"})}catch(n){throw s(n,`Could not get frame ${e} for the task ${t} from the server`)}return r.data},getMeta:async function(t){const{backendAPI:e}=i;let n=null;try{n=await a.get(`${e}/tasks/${t}/frames/meta`,{proxy:i.proxy})}catch(e){throw s(e,`Could not get frame meta info for the task ${t} from the server`)}return n.data}}),writable:!1},annotations:{value:Object.freeze({updateAnnotations:async function(t,e,n,r){const{backendAPI:o}=i;let c=null,u=null;"PUT"===r.toUpperCase()?(c=a.put.bind(a),u=`${o}/${t}s/${e}/annotations`):(c=a.patch.bind(a),u=`${o}/${t}s/${e}/annotations?action=${r}`);let l=null;try{l=await c(u,JSON.stringify(n),{proxy:i.proxy,headers:{"Content-Type":"application/json"}})}catch(n){throw s(n,`Could not ${r} annotations for the ${t} ${e} on the server`)}return l.data},getAnnotations:async function(t,e){const{backendAPI:n}=i;let r=null;try{r=await a.get(`${n}/${t}s/${e}/annotations`,{proxy:i.proxy})}catch(n){throw s(n,`Could not get annotations for the ${t} ${e} from the server`)}return r.data},dumpAnnotations:async function(t,e,n){const{backendAPI:r}=i,o=e.replace(/\//g,"_");let c=`${r}/tasks/${t}/annotations/${o}?format=${n}`;return new Promise((e,n)=>{setTimeout(async function r(){try{202===(await a.get(`${c}`,{proxy:i.proxy})).status?setTimeout(r,3e3):e(c=`${c}&action=download`)}catch(e){n(s(e,`Could not dump annotations for the task ${t} from the server`))}})})},uploadAnnotations:async function(t,n,r,o){const{backendAPI:c}=i;let u=new e;return u.append("annotation_file",r),new Promise((r,l)=>{setTimeout(async function f(){try{202===(await a.put(`${c}/${t}s/${n}/annotations?format=${o}`,u,{proxy:i.proxy})).status?(u=new e,setTimeout(f,3e3)):r()}catch(e){l(s(e,`Could not upload annotations for the ${t} ${n}`))}})})}}),writable:!1}}))}};t.exports=a})()},function(t,e,n){(function(e){var n=Object.assign?Object.assign:function(t,e,n,r){for(var o=1;o{const e=Object.freeze({DIR:"DIR",REG:"REG"}),n=Object.freeze({ANNOTATION:"annotation",VALIDATION:"validation",COMPLETED:"completed"}),r=Object.freeze({ANNOTATION:"annotation",INTERPOLATION:"interpolation"}),o=Object.freeze({CHECKBOX:"checkbox",RADIO:"radio",SELECT:"select",NUMBER:"number",TEXT:"text"}),i=Object.freeze({TAG:"tag",SHAPE:"shape",TRACK:"track"}),s=Object.freeze({RECTANGLE:"rectangle",POLYGON:"polygon",POLYLINE:"polyline",POINTS:"points"}),a=Object.freeze({ALL:"all",SHAPE:"shape",NONE:"none"});t.exports={ShareFileType:e,TaskStatus:n,TaskMode:r,AttributeType:o,ObjectType:i,ObjectShape:s,VisibleState:a,LogType:{pasteObject:0,changeAttribute:1,dragObject:2,deleteObject:3,pressShortcut:4,resizeObject:5,sendLogs:6,saveJob:7,jumpFrame:8,drawObject:9,changeLabel:10,sendTaskInfo:11,loadJob:12,moveImage:13,zoomImage:14,lockObject:15,mergeObjects:16,copyObject:17,propagateObject:18,undoAction:19,redoAction:20,sendUserActivity:21,sendException:22,changeFrame:23,debugInfo:24,fitImage:25,rotateImage:26}}})()},function(t,e){t.exports=function(t){if(null==t)throw TypeError("Can't call method on "+t);return t}},function(t,e){t.exports=!1},function(t,e,n){var r=n(14).f,o=n(7),i=n(1)("toStringTag");t.exports=function(t,e,n){t&&!o(t=n?t:t.prototype,i)&&r(t,i,{configurable:!0,value:e})}},function(t,e){t.exports=function(t){if("function"!=typeof t)throw TypeError(String(t)+" is not a function");return t}},function(t,e){t.exports={}},function(t,e,n){n(107),n(4),n(9),n(8),(()=>{const{PluginError:e}=n(5),r=[];class o{static async apiWrapper(t,...n){const r=await o.list();for(const o of r){const r=o.functions.filter(e=>e.callback===t)[0];if(r&&r.enter)try{await r.enter.call(this,o,...n)}catch(t){throw t instanceof e?t:new e(`Exception in plugin ${o.name}: ${t.toString()}`)}}let i=await t.implementation.call(this,...n);for(const o of r){const r=o.functions.filter(e=>e.callback===t)[0];if(r&&r.leave)try{i=await r.leave.call(this,o,i,...n)}catch(t){throw t instanceof e?t:new e(`Exception in plugin ${o.name}: ${t.toString()}`)}}return i}static async register(t){const n=[];if("object"!=typeof t)throw new e(`Plugin should be an object, but got "${typeof t}"`);if(!("name"in t)||"string"!=typeof t.name)throw new e('Plugin must contain a "name" field and it must be a string');if(!("description"in t)||"string"!=typeof t.description)throw new e('Plugin must contain a "description" field and it must be a string');if("functions"in t)throw new e('Plugin must not contain a "functions" field');!function t(e,r){const o={};for(const n in e)Object.prototype.hasOwnProperty.call(e,n)&&("object"==typeof e[n]?Object.prototype.hasOwnProperty.call(r,n)&&t(e[n],r[n]):["enter","leave"].includes(n)&&"function"==typeof r&&(e[n],1)&&(o.callback=r,o[n]=e[n]));Object.keys(o).length&&n.push(o)}(t,{cvat:this}),Object.defineProperty(t,"functions",{value:n,writable:!1}),r.push(t)}static async list(){return r}}t.exports=o})()},function(t,e,n){var r=n(21);t.exports=function(t){return Object(r(t))}},function(t,e){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(t){"object"==typeof window&&(n=window)}t.exports=n},function(t,e){t.exports=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}}},function(t,e,n){var r=n(54),o=n(21);t.exports=function(t){return r(o(t))}},function(t,e,n){var r=n(0),o=n(43),i=n(22),s=r["__core-js_shared__"]||o("__core-js_shared__",{});(t.exports=function(t,e){return s[t]||(s[t]=void 0!==e?e:{})})("versions",[]).push({version:"3.2.1",mode:i?"pure":"global",copyright:"© 2019 Denis Pushkarev (zloirock.ru)"})},function(t,e,n){var r=n(59),o=n(0),i=function(t){return"function"==typeof t?t:void 0};t.exports=function(t,e){return arguments.length<2?i(r[t])||i(o[t]):r[t]&&r[t][e]||o[t]&&o[t][e]}},function(t,e,n){var r=n(34),o=Math.min;t.exports=function(t){return t>0?o(r(t),9007199254740991):0}},function(t,e){var n=Math.ceil,r=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?r:n)(t)}},function(t,e,n){var r=n(24);t.exports=function(t,e,n){if(r(t),void 0===e)return t;switch(n){case 0:return function(){return t.call(e)};case 1:return function(n){return t.call(e,n)};case 2:return function(n,r){return t.call(e,n,r)};case 3:return function(n,r,o){return t.call(e,n,r,o)}}return function(){return t.apply(e,arguments)}}},function(t,e,n){var r=n(100),o=n(25),i=n(1)("iterator");t.exports=function(t){if(null!=t)return t[i]||t["@@iterator"]||o[r(t)]}},function(t,e,n){n(4),n(9),n(152),n(8),n(52),(()=>{const e=n(26),r=n(18),{getFrame:o}=n(155),{ArgumentError:i}=n(5),{TaskStatus:s}=n(20),{Label:a}=n(38);function c(t){Object.defineProperties(t,{annotations:Object.freeze({value:{async upload(n,r){return await e.apiWrapper.call(this,t.annotations.upload,n,r)},async save(){return await e.apiWrapper.call(this,t.annotations.save)},async clear(n=!1){return await e.apiWrapper.call(this,t.annotations.clear,n)},async dump(n,r){return await e.apiWrapper.call(this,t.annotations.dump,n,r)},async statistics(){return await e.apiWrapper.call(this,t.annotations.statistics)},async put(n=[]){return await e.apiWrapper.call(this,t.annotations.put,n)},async get(n,r={}){return await e.apiWrapper.call(this,t.annotations.get,n,r)},async search(n,r,o){return await e.apiWrapper.call(this,t.annotations.search,n,r,o)},async select(n,r,o){return await e.apiWrapper.call(this,t.annotations.select,n,r,o)},async hasUnsavedChanges(){return await e.apiWrapper.call(this,t.annotations.hasUnsavedChanges)},async merge(n){return await e.apiWrapper.call(this,t.annotations.merge,n)},async split(n,r){return await e.apiWrapper.call(this,t.annotations.split,n,r)},async group(n,r=!1){return await e.apiWrapper.call(this,t.annotations.group,n,r)}},writable:!0}),frames:Object.freeze({value:{async get(n){return await e.apiWrapper.call(this,t.frames.get,n)}},writable:!0}),logs:Object.freeze({value:{async put(n,r){return await e.apiWrapper.call(this,t.logs.put,n,r)},async save(n){return await e.apiWrapper.call(this,t.logs.save,n)}},writable:!0}),actions:Object.freeze({value:{async undo(n){return await e.apiWrapper.call(this,t.actions.undo,n)},async redo(n){return await e.apiWrapper.call(this,t.actions.redo,n)},async clear(){return await e.apiWrapper.call(this,t.actions.clear)}},writable:!0}),events:Object.freeze({value:{async subscribe(n,r){return await e.apiWrapper.call(this,t.events.subscribe,n,r)},async unsubscribe(n,r=null){return await e.apiWrapper.call(this,t.events.unsubscribe,n,r)}},writable:!0})})}class u{constructor(){}}class l extends u{constructor(t){super();const e={id:void 0,assignee:void 0,status:void 0,start_frame:void 0,stop_frame:void 0,task:void 0};for(const n in e)if(Object.prototype.hasOwnProperty.call(e,n)&&(n in t&&(e[n]=t[n]),void 0===e[n]))throw new i(`Job field "${n}" was not initialized`);Object.defineProperties(this,Object.freeze({id:{get:()=>e.id},assignee:{get:()=>e.assignee,set:()=>t=>{if(!Number.isInteger(t)||t<0)throw new i("Value must be a non negative integer");e.assignee=t}},status:{get:()=>e.status,set:t=>{const n=s;let r=!1;for(const e in n)if(n[e]===t){r=!0;break}if(!r)throw new i("Value must be a value from the enumeration cvat.enums.TaskStatus");e.status=t}},startFrame:{get:()=>e.start_frame},stopFrame:{get:()=>e.stop_frame},task:{get:()=>e.task}})),this.annotations={get:Object.getPrototypeOf(this).annotations.get.bind(this),put:Object.getPrototypeOf(this).annotations.put.bind(this),save:Object.getPrototypeOf(this).annotations.save.bind(this),dump:Object.getPrototypeOf(this).annotations.dump.bind(this),merge:Object.getPrototypeOf(this).annotations.merge.bind(this),split:Object.getPrototypeOf(this).annotations.split.bind(this),group:Object.getPrototypeOf(this).annotations.group.bind(this),clear:Object.getPrototypeOf(this).annotations.clear.bind(this),upload:Object.getPrototypeOf(this).annotations.upload.bind(this),select:Object.getPrototypeOf(this).annotations.select.bind(this),statistics:Object.getPrototypeOf(this).annotations.statistics.bind(this),hasUnsavedChanges:Object.getPrototypeOf(this).annotations.hasUnsavedChanges.bind(this)},this.frames={get:Object.getPrototypeOf(this).frames.get.bind(this)}}async save(){return await e.apiWrapper.call(this,l.prototype.save)}}class f extends u{constructor(t){super();const e={id:void 0,name:void 0,status:void 0,size:void 0,mode:void 0,owner:void 0,assignee:void 0,created_date:void 0,updated_date:void 0,bug_tracker:void 0,overlap:void 0,segment_size:void 0,z_order:void 0,image_quality:void 0,start_frame:void 0,stop_frame:void 0,frame_filter:void 0};for(const n in e)Object.prototype.hasOwnProperty.call(e,n)&&n in t&&(e[n]=t[n]);if(e.labels=[],e.jobs=[],e.files=Object.freeze({server_files:[],client_files:[],remote_files:[]}),Array.isArray(t.segments))for(const n of t.segments)if(Array.isArray(n.jobs))for(const t of n.jobs){const r=new l({url:t.url,id:t.id,assignee:t.assignee,status:t.status,start_frame:n.start_frame,stop_frame:n.stop_frame,task:this});e.jobs.push(r)}if(Array.isArray(t.labels))for(const n of t.labels){const t=new a(n);e.labels.push(t)}Object.defineProperties(this,Object.freeze({id:{get:()=>e.id},name:{get:()=>e.name,set:t=>{if(!t.trim().length)throw new i("Value must not be empty");e.name=t}},status:{get:()=>e.status},size:{get:()=>e.size},mode:{get:()=>e.mode},owner:{get:()=>e.owner},assignee:{get:()=>e.assignee,set:()=>t=>{if(!Number.isInteger(t)||t<0)throw new i("Value must be a non negative integer");e.assignee=t}},createdDate:{get:()=>e.created_date},updatedDate:{get:()=>e.updated_date},bugTracker:{get:()=>e.bug_tracker,set:t=>{e.bug_tracker=t}},overlap:{get:()=>e.overlap,set:t=>{if(!Number.isInteger(t)||t<0)throw new i("Value must be a non negative integer");e.overlap=t}},segmentSize:{get:()=>e.segment_size,set:t=>{if(!Number.isInteger(t)||t<0)throw new i("Value must be a positive integer");e.segment_size=t}},zOrder:{get:()=>e.z_order,set:t=>{if("boolean"!=typeof t)throw new i("Value must be a boolean");e.z_order=t}},imageQuality:{get:()=>e.image_quality,set:t=>{if(!Number.isInteger(t)||t<0)throw new i("Value must be a positive integer");e.image_quality=t}},labels:{get:()=>[...e.labels],set:t=>{if(!Array.isArray(t))throw new i("Value must be an array of Labels");for(const e of t)if(!(e instanceof a))throw new i("Each array value must be an instance of Label. "+`${typeof e} was found`);void 0===e.id?e.labels=[...t]:e.labels=e.labels.concat([...t])}},jobs:{get:()=>[...e.jobs]},serverFiles:{get:()=>[...e.files.server_files],set:t=>{if(!Array.isArray(t))throw new i(`Value must be an array. But ${typeof t} has been got.`);for(const e of t)if("string"!=typeof e)throw new i(`Array values must be a string. But ${typeof e} has been got.`);Array.prototype.push.apply(e.files.server_files,t)}},clientFiles:{get:()=>[...e.files.client_files],set:t=>{if(!Array.isArray(t))throw new i(`Value must be an array. But ${typeof t} has been got.`);for(const e of t)if(!(e instanceof File))throw new i(`Array values must be a File. But ${e.constructor.name} has been got.`);Array.prototype.push.apply(e.files.client_files,t)}},remoteFiles:{get:()=>[...e.files.remote_files],set:t=>{if(!Array.isArray(t))throw new i(`Value must be an array. But ${typeof t} has been got.`);for(const e of t)if("string"!=typeof e)throw new i(`Array values must be a string. But ${typeof e} has been got.`);Array.prototype.push.apply(e.files.remote_files,t)}},startFrame:{get:()=>e.start_frame,set:t=>{if(!Number.isInteger(t)||t<0)throw new i("Value must be a not negative integer");e.start_frame=t}},stopFrame:{get:()=>e.stop_frame,set:t=>{if(!Number.isInteger(t)||t<0)throw new i("Value must be a not negative integer");e.stop_frame=t}},frameFilter:{get:()=>e.frame_filter,set:t=>{if("string"!=typeof t)throw new i(`Filter value must be a string. But ${typeof t} has been got.`);e.frame_filter=t}}})),this.annotations={get:Object.getPrototypeOf(this).annotations.get.bind(this),put:Object.getPrototypeOf(this).annotations.put.bind(this),save:Object.getPrototypeOf(this).annotations.save.bind(this),dump:Object.getPrototypeOf(this).annotations.dump.bind(this),merge:Object.getPrototypeOf(this).annotations.merge.bind(this),split:Object.getPrototypeOf(this).annotations.split.bind(this),group:Object.getPrototypeOf(this).annotations.group.bind(this),clear:Object.getPrototypeOf(this).annotations.clear.bind(this),upload:Object.getPrototypeOf(this).annotations.upload.bind(this),select:Object.getPrototypeOf(this).annotations.select.bind(this),statistics:Object.getPrototypeOf(this).annotations.statistics.bind(this),hasUnsavedChanges:Object.getPrototypeOf(this).annotations.hasUnsavedChanges.bind(this)},this.frames={get:Object.getPrototypeOf(this).frames.get.bind(this)}}async save(t=(()=>{})){return await e.apiWrapper.call(this,f.prototype.save,t)}async delete(){return await e.apiWrapper.call(this,f.prototype.delete)}}t.exports={Job:l,Task:f};const{getAnnotations:p,putAnnotations:h,saveAnnotations:d,hasUnsavedChanges:b,mergeAnnotations:g,splitAnnotations:m,groupAnnotations:y,clearAnnotations:v,selectObject:w,annotationsStatistics:O,uploadAnnotations:x,dumpAnnotations:j}=n(157);c(l.prototype),c(f.prototype),l.prototype.save.implementation=async function(){if(this.id){const t={status:this.status};return await r.jobs.saveJob(this.id,t),this}throw new i("Can not save job without and id")},l.prototype.frames.get.implementation=async function(t){if(!Number.isInteger(t)||t<0)throw new i(`Frame must be a positive integer. Got: "${t}"`);if(tthis.stopFrame)throw new i(`The frame with number ${t} is out of the job`);return await o(this.task.id,this.task.mode,t)},l.prototype.annotations.get.implementation=async function(t,e){if(tthis.stopFrame)throw new i(`Frame ${t} does not exist in the job`);return await p(this,t,e)},l.prototype.annotations.save.implementation=async function(t){return await d(this,t)},l.prototype.annotations.merge.implementation=async function(t){return await g(this,t)},l.prototype.annotations.split.implementation=async function(t,e){return await m(this,t,e)},l.prototype.annotations.group.implementation=async function(t,e){return await y(this,t,e)},l.prototype.annotations.hasUnsavedChanges.implementation=function(){return b(this)},l.prototype.annotations.clear.implementation=async function(t){return await v(this,t)},l.prototype.annotations.select.implementation=function(t,e,n){return w(this,t,e,n)},l.prototype.annotations.statistics.implementation=function(){return O(this)},l.prototype.annotations.put.implementation=function(t){return h(this,t)},l.prototype.annotations.upload.implementation=async function(t,e){return await x(this,t,e)},l.prototype.annotations.dump.implementation=async function(t,e){return await j(this,t,e)},f.prototype.save.implementation=async function(t){if(void 0!==this.id){const t={name:this.name,bug_tracker:this.bugTracker,z_order:this.zOrder,labels:[...this.labels.map(t=>t.toJSON())]};return await r.tasks.saveTask(this.id,t),this}const e={name:this.name,labels:this.labels.map(t=>t.toJSON()),image_quality:this.imageQuality,z_order:Boolean(this.zOrder)};void 0!==this.bugTracker&&(e.bug_tracker=this.bugTracker),void 0!==this.segmentSize&&(e.segment_size=this.segmentSize),void 0!==this.overlap&&(e.overlap=this.overlap),void 0!==this.startFrame&&(e.start_frame=this.startFrame),void 0!==this.stopFrame&&(e.stop_frame=this.stopFrame),void 0!==this.frameFilter&&(e.frame_filter=this.frameFilter);const n={client_files:this.clientFiles,server_files:this.serverFiles,remote_files:this.remoteFiles},o=await r.tasks.createTask(e,n,t);return new f(o)},f.prototype.delete.implementation=async function(){return await r.tasks.deleteTask(this.id)},f.prototype.frames.get.implementation=async function(t){if(!Number.isInteger(t)||t<0)throw new i(`Frame must be a positive integer. Got: "${t}"`);if(t>=this.size)throw new i(`The frame with number ${t} is out of the task`);return await o(this.id,this.mode,t)},f.prototype.annotations.get.implementation=async function(t,e){if(!Number.isInteger(t)||t<0)throw new i(`Frame must be a positive integer. Got: "${t}"`);if(t>=this.size)throw new i(`Frame ${t} does not exist in the task`);return await p(this,t,e)},f.prototype.annotations.save.implementation=async function(t){return await d(this,t)},f.prototype.annotations.merge.implementation=async function(t){return await g(this,t)},f.prototype.annotations.split.implementation=async function(t,e){return await m(this,t,e)},f.prototype.annotations.group.implementation=async function(t,e){return await y(this,t,e)},f.prototype.annotations.hasUnsavedChanges.implementation=function(){return b(this)},f.prototype.annotations.clear.implementation=async function(t){return await v(this,t)},f.prototype.annotations.select.implementation=function(t,e,n){return w(this,t,e,n)},f.prototype.annotations.statistics.implementation=function(){return O(this)},f.prototype.annotations.put.implementation=function(t){return h(this,t)},f.prototype.annotations.upload.implementation=async function(t,e){return await x(this,t,e)},f.prototype.annotations.dump.implementation=async function(t,e){return await j(this,t,e)}})()},function(t,e,n){n(4),n(8),n(52),(()=>{const{AttributeType:e}=n(20),{ArgumentError:r}=n(5);class o{constructor(t){const n={id:void 0,default_value:void 0,input_type:void 0,mutable:void 0,name:void 0,values:void 0};for(const e in n)Object.prototype.hasOwnProperty.call(n,e)&&Object.prototype.hasOwnProperty.call(t,e)&&(Array.isArray(t[e])?n[e]=[...t[e]]:n[e]=t[e]);if(!Object.values(e).includes(n.input_type))throw new r(`Got invalid attribute type ${n.input_type}`);Object.defineProperties(this,Object.freeze({id:{get:()=>n.id},defaultValue:{get:()=>n.default_value},inputType:{get:()=>n.input_type},mutable:{get:()=>n.mutable},name:{get:()=>n.name},values:{get:()=>[...n.values]}}))}toJSON(){const t={name:this.name,mutable:this.mutable,input_type:this.inputType,default_value:this.defaultValue,values:this.values};return void 0!==this.id&&(t.id=this.id),t}}t.exports={Attribute:o,Label:class{constructor(t){const e={id:void 0,name:void 0};for(const n in e)Object.prototype.hasOwnProperty.call(e,n)&&Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);if(e.attributes=[],Object.prototype.hasOwnProperty.call(t,"attributes")&&Array.isArray(t.attributes))for(const n of t.attributes)e.attributes.push(new o(n));Object.defineProperties(this,Object.freeze({id:{get:()=>e.id},name:{get:()=>e.name},attributes:{get:()=>[...e.attributes]}}))}toJSON(){const t={name:this.name,attributes:[...this.attributes.map(t=>t.toJSON())]};return void 0!==this.id&&(t.id=this.id),t}}}})()},function(t,e,n){(()=>{const{ArgumentError:e}=n(5);t.exports={isBoolean:function(t){return"boolean"==typeof t},isInteger:function(t){return"number"==typeof t&&Number.isInteger(t)},isEnum:function(t){for(const e in this)if(Object.prototype.hasOwnProperty.call(this,e)&&this[e]===t)return!0;return!1},isString:function(t){return"string"==typeof t},checkFilter:function(t,n){for(const r in t)if(Object.prototype.hasOwnProperty.call(t,r)){if(!(r in n))throw new e(`Unsupported filter property has been recieved: "${r}"`);if(!n[r](t[r]))throw new e(`Received filter property "${r}" is not satisfied for checker`)}},checkObjectType:function(t,n,r,o){if(r){if(typeof n!==r){if("integer"===r&&Number.isInteger(n))return;throw new e(`"${t}" is expected to be "${r}", but "${typeof n}" has been got.`)}}else if(o&&!(n instanceof o)){if(void 0!==n)throw new e(`"${t}" is expected to be ${o.name}, but `+`"${n.constructor.name}" has been got`);throw new e(`"${t}" is expected to be ${o.name}, but "undefined" has been got.`)}}}})()},function(t,e,n){var r=n(10),o=n(53),i=n(29),s=n(30),a=n(41),c=n(7),u=n(55),l=Object.getOwnPropertyDescriptor;e.f=r?l:function(t,e){if(t=s(t),e=a(e,!0),u)try{return l(t,e)}catch(t){}if(c(t,e))return i(!o.f.call(t,e),t[e])}},function(t,e,n){var r=n(11);t.exports=function(t,e){if(!r(t))return t;var n,o;if(e&&"function"==typeof(n=t.toString)&&!r(o=n.call(t)))return o;if("function"==typeof(n=t.valueOf)&&!r(o=n.call(t)))return o;if(!e&&"function"==typeof(n=t.toString)&&!r(o=n.call(t)))return o;throw TypeError("Can't convert object to primitive value")}},function(t,e,n){var r=n(0),o=n(11),i=r.document,s=o(i)&&o(i.createElement);t.exports=function(t){return s?i.createElement(t):{}}},function(t,e,n){var r=n(0),o=n(12);t.exports=function(t,e){try{o(r,t,e)}catch(n){r[t]=e}return e}},function(t,e,n){var r=n(31),o=n(57),i=r("keys");t.exports=function(t){return i[t]||(i[t]=o(t))}},function(t,e){t.exports={}},function(t,e){t.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},function(t,e){t.exports=function(t,e,n){if(!(t instanceof e))throw TypeError("Incorrect "+(n?n+" ":"")+"invocation");return t}},function(t,e){t.exports={backendAPI:"http://localhost:7000/api/v1",proxy:!1,taskID:void 0,jobID:void 0,clientID:+Date.now().toString().substr(-6)}},function(t,e,n){var r=n(34),o=n(21),i=function(t){return function(e,n){var i,s,a=String(o(e)),c=r(n),u=a.length;return c<0||c>=u?t?"":void 0:(i=a.charCodeAt(c))<55296||i>56319||c+1===u||(s=a.charCodeAt(c+1))<56320||s>57343?t?a.charAt(c):i:t?a.slice(c,c+2):s-56320+(i-55296<<10)+65536}};t.exports={codeAt:i(!1),charAt:i(!0)}},function(t,e,n){"use strict";(function(e){var r=n(6),o=n(138),i={"Content-Type":"application/x-www-form-urlencoded"};function s(t,e){!r.isUndefined(t)&&r.isUndefined(t["Content-Type"])&&(t["Content-Type"]=e)}var a,c={adapter:("undefined"!=typeof XMLHttpRequest?a=n(80):void 0!==e&&(a=n(80)),a),transformRequest:[function(t,e){return o(e,"Content-Type"),r.isFormData(t)||r.isArrayBuffer(t)||r.isBuffer(t)||r.isStream(t)||r.isFile(t)||r.isBlob(t)?t:r.isArrayBufferView(t)?t.buffer:r.isURLSearchParams(t)?(s(e,"application/x-www-form-urlencoded;charset=utf-8"),t.toString()):r.isObject(t)?(s(e,"application/json;charset=utf-8"),JSON.stringify(t)):t}],transformResponse:[function(t){if("string"==typeof t)try{t=JSON.parse(t)}catch(t){}return t}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,validateStatus:function(t){return t>=200&&t<300}};c.headers={common:{Accept:"application/json, text/plain, */*"}},r.forEach(["delete","get","head"],function(t){c.headers[t]={}}),r.forEach(["post","put","patch"],function(t){c.headers[t]=r.merge(i)}),t.exports=c}).call(this,n(79))},function(t,e,n){n(4),n(9),n(8),(()=>{const e=n(26),{ArgumentError:r}=n(5);class o{constructor(t){const e={label:null,attributes:{},points:null,outside:null,occluded:null,keyframe:null,group:null,zOrder:null,lock:null,color:null,visibility:null,clientID:t.clientID,serverID:t.serverID,frame:t.frame,objectType:t.objectType,shapeType:t.shapeType,updateFlags:{}};Object.defineProperty(e.updateFlags,"reset",{value:function(){this.label=!1,this.attributes=!1,this.points=!1,this.outside=!1,this.occluded=!1,this.keyframe=!1,this.group=!1,this.zOrder=!1,this.lock=!1,this.color=!1,this.visibility=!1},writable:!1}),Object.defineProperties(this,Object.freeze({updateFlags:{get:()=>e.updateFlags},frame:{get:()=>e.frame},objectType:{get:()=>e.objectType},shapeType:{get:()=>e.shapeType},clientID:{get:()=>e.clientID},serverID:{get:()=>e.serverID},label:{get:()=>e.label,set:t=>{e.updateFlags.label=!0,e.label=t}},color:{get:()=>e.color,set:t=>{e.updateFlags.color=!0,e.color=t}},visibility:{get:()=>e.visibility,set:t=>{e.updateFlags.visibility=!0,e.visibility=t}},points:{get:()=>e.points,set:t=>{if(!Array.isArray(t))throw new r("Points are expected to be an array "+`but got ${"object"==typeof t?t.constructor.name:typeof t}`);e.updateFlags.points=!0,e.points=[...t]}},group:{get:()=>e.group,set:t=>{e.updateFlags.group=!0,e.group=t}},zOrder:{get:()=>e.zOrder,set:t=>{e.updateFlags.zOrder=!0,e.zOrder=t}},outside:{get:()=>e.outside,set:t=>{e.updateFlags.outside=!0,e.outside=t}},keyframe:{get:()=>e.keyframe,set:t=>{e.updateFlags.keyframe=!0,e.keyframe=t}},occluded:{get:()=>e.occluded,set:t=>{e.updateFlags.occluded=!0,e.occluded=t}},lock:{get:()=>e.lock,set:t=>{e.updateFlags.lock=!0,e.lock=t}},attributes:{get:()=>e.attributes,set:t=>{if("object"!=typeof t)throw new r("Attributes are expected to be an object "+`but got ${"object"==typeof t?t.constructor.name:typeof t}`);for(const n of Object.keys(t))e.updateFlags.attributes=!0,e.attributes[n]=t[n]}}})),this.label=t.label,this.group=t.group,this.zOrder=t.zOrder,this.outside=t.outside,this.keyframe=t.keyframe,this.occluded=t.occluded,this.color=t.color,this.lock=t.lock,this.visibility=t.visibility,void 0!==t.points&&(this.points=t.points),void 0!==t.attributes&&(this.attributes=t.attributes),e.updateFlags.reset()}async save(){return await e.apiWrapper.call(this,o.prototype.save)}async delete(t=!1){return await e.apiWrapper.call(this,o.prototype.delete,t)}async up(){return await e.apiWrapper.call(this,o.prototype.up)}async down(){return await e.apiWrapper.call(this,o.prototype.down)}}o.prototype.save.implementation=async function(){return this.hidden&&this.hidden.save?this.hidden.save():this},o.prototype.delete.implementation=async function(t){return!(!this.hidden||!this.hidden.delete)&&this.hidden.delete(t)},o.prototype.up.implementation=async function(){return!(!this.hidden||!this.hidden.up)&&this.hidden.up()},o.prototype.down.implementation=async function(){return!(!this.hidden||!this.hidden.down)&&this.hidden.down()},t.exports=o})()},function(t,e,n){"use strict";n(13)({target:"URL",proto:!0,enumerable:!0},{toJSON:function(){return URL.prototype.toString.call(this)}})},function(t,e,n){"use strict";var r={}.propertyIsEnumerable,o=Object.getOwnPropertyDescriptor,i=o&&!r.call({1:2},1);e.f=i?function(t){var e=o(this,t);return!!e&&e.enumerable}:r},function(t,e,n){var r=n(2),o=n(16),i="".split;t.exports=r(function(){return!Object("z").propertyIsEnumerable(0)})?function(t){return"String"==o(t)?i.call(t,""):Object(t)}:Object},function(t,e,n){var r=n(10),o=n(2),i=n(42);t.exports=!r&&!o(function(){return 7!=Object.defineProperty(i("div"),"a",{get:function(){return 7}}).a})},function(t,e,n){var r=n(31);t.exports=r("native-function-to-string",Function.toString)},function(t,e){var n=0,r=Math.random();t.exports=function(t){return"Symbol("+String(void 0===t?"":t)+")_"+(++n+r).toString(36)}},function(t,e,n){var r=n(7),o=n(92),i=n(40),s=n(14);t.exports=function(t,e){for(var n=o(e),a=s.f,c=i.f,u=0;uc;)r(a,n=e[c++])&&(~i(u,n)||u.push(n));return u}},function(t,e){e.f=Object.getOwnPropertySymbols},function(t,e,n){var r=n(2),o=/#|\.prototype\./,i=function(t,e){var n=a[s(t)];return n==u||n!=c&&("function"==typeof e?r(e):!!e)},s=i.normalize=function(t){return String(t).replace(o,".").toLowerCase()},a=i.data={},c=i.NATIVE="N",u=i.POLYFILL="P";t.exports=i},function(t,e,n){var r=n(15);t.exports=function(t,e,n){for(var o in e)r(t,o,e[o],n);return t}},function(t,e,n){var r=n(1),o=n(25),i=r("iterator"),s=Array.prototype;t.exports=function(t){return void 0!==t&&(o.Array===t||s[i]===t)}},function(t,e,n){var r=n(3);t.exports=function(t,e,n,o){try{return o?e(r(n)[0],n[1]):e(n)}catch(e){var i=t.return;throw void 0!==i&&r(i.call(t)),e}}},function(t,e,n){var r,o,i,s=n(0),a=n(2),c=n(16),u=n(35),l=n(67),f=n(42),p=s.location,h=s.setImmediate,d=s.clearImmediate,b=s.process,g=s.MessageChannel,m=s.Dispatch,y=0,v={},w=function(t){if(v.hasOwnProperty(t)){var e=v[t];delete v[t],e()}},O=function(t){return function(){w(t)}},x=function(t){w(t.data)},j=function(t){s.postMessage(t+"",p.protocol+"//"+p.host)};h&&d||(h=function(t){for(var e=[],n=1;arguments.length>n;)e.push(arguments[n++]);return v[++y]=function(){("function"==typeof t?t:Function(t)).apply(void 0,e)},r(y),y},d=function(t){delete v[t]},"process"==c(b)?r=function(t){b.nextTick(O(t))}:m&&m.now?r=function(t){m.now(O(t))}:g?(i=(o=new g).port2,o.port1.onmessage=x,r=u(i.postMessage,i,1)):!s.addEventListener||"function"!=typeof postMessage||s.importScripts||a(j)?r="onreadystatechange"in f("script")?function(t){l.appendChild(f("script")).onreadystatechange=function(){l.removeChild(this),w(t)}}:function(t){setTimeout(O(t),0)}:(r=j,s.addEventListener("message",x,!1))),t.exports={set:h,clear:d}},function(t,e,n){var r=n(32);t.exports=r("document","documentElement")},function(t,e,n){var r=n(32);t.exports=r("navigator","userAgent")||""},function(t,e,n){"use strict";var r=n(24),o=function(t){var e,n;this.promise=new t(function(t,r){if(void 0!==e||void 0!==n)throw TypeError("Bad Promise constructor");e=t,n=r}),this.resolve=r(e),this.reject=r(n)};t.exports.f=function(t){return new o(t)}},function(t,e,n){var r=n(3),o=n(71),i=n(46),s=n(45),a=n(67),c=n(42),u=n(44)("IE_PROTO"),l=function(){},f=function(){var t,e=c("iframe"),n=i.length;for(e.style.display="none",a.appendChild(e),e.src=String("javascript:"),(t=e.contentWindow.document).open(),t.write(" - - - -
- - - diff --git a/cvat-ui/public/manifest.json b/cvat-ui/public/manifest.json deleted file mode 100644 index 436112041c2..00000000000 --- a/cvat-ui/public/manifest.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "short_name": "CVAT", - "name": "Computer Vision Annotation Tool", - "icons": [ - { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - } - ], - "start_url": ".", - "display": "standalone", - "theme_color": "#000000", - "background_color": "#ffffff" -} diff --git a/cvat-ui/src/actions/about-actions.ts b/cvat-ui/src/actions/about-actions.ts new file mode 100644 index 00000000000..c539dfbc97a --- /dev/null +++ b/cvat-ui/src/actions/about-actions.ts @@ -0,0 +1,35 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; +import getCore from 'cvat-core'; + +const core = getCore(); + +export enum AboutActionTypes { + GET_ABOUT = 'GET_ABOUT', + GET_ABOUT_SUCCESS = 'GET_ABOUT_SUCCESS', + GET_ABOUT_FAILED = 'GET_ABOUT_FAILED', +} + +const aboutActions = { + getAbout: () => createAction(AboutActionTypes.GET_ABOUT), + getAboutSuccess: (server: any) => createAction(AboutActionTypes.GET_ABOUT_SUCCESS, { server }), + getAboutFailed: (error: any) => createAction(AboutActionTypes.GET_ABOUT_FAILED, { error }), +}; + +export type AboutActions = ActionUnion; + +export const getAboutAsync = (): ThunkAction => async (dispatch): Promise => { + dispatch(aboutActions.getAbout()); + + try { + const about = await core.server.about(); + dispatch( + aboutActions.getAboutSuccess(about), + ); + } catch (error) { + dispatch(aboutActions.getAboutFailed(error)); + } +}; diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts new file mode 100644 index 00000000000..44aeb752ceb --- /dev/null +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -0,0 +1,1220 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import { + AnyAction, + Dispatch, + ActionCreator, + Store, +} from 'redux'; +import { ThunkAction } from 'redux-thunk'; + +import { + CombinedState, + ActiveControl, + ShapeType, + ObjectType, + Task, + FrameSpeed, + Rotation, +} from 'reducers/interfaces'; + +import getCore from 'cvat-core'; +import { RectDrawingMethod } from 'cvat-canvas'; +import { getCVATStore } from 'cvat-store'; + +const cvat = getCore(); +let store: null | Store = null; + +function getStore(): Store { + if (store === null) { + store = getCVATStore(); + } + return store; +} + +function receiveAnnotationsParameters(): +{ filters: string[]; frame: number; showAllInterpolationTracks: boolean } { + if (store === null) { + store = getCVATStore(); + } + + const state: CombinedState = getStore().getState(); + const { filters } = state.annotation.annotations; + const frame = state.annotation.player.frame.number; + const { showAllInterpolationTracks } = state.settings.workspace; + return { + filters, + frame, + showAllInterpolationTracks, + }; +} + +function computeZRange(states: any[]): number[] { + let minZ = states.length ? states[0].zOrder : 0; + let maxZ = states.length ? states[0].zOrder : 0; + states.forEach((state: any): void => { + minZ = Math.min(minZ, state.zOrder); + maxZ = Math.max(maxZ, state.zOrder); + }); + + return [minZ, maxZ]; +} + +export enum AnnotationActionTypes { + GET_JOB = 'GET_JOB', + GET_JOB_SUCCESS = 'GET_JOB_SUCCESS', + GET_JOB_FAILED = 'GET_JOB_FAILED', + CLOSE_JOB = 'CLOSE_JOB', + CHANGE_FRAME = 'CHANGE_FRAME', + CHANGE_FRAME_SUCCESS = 'CHANGE_FRAME_SUCCESS', + CHANGE_FRAME_FAILED = 'CHANGE_FRAME_FAILED', + SAVE_ANNOTATIONS = 'SAVE_ANNOTATIONS', + SAVE_ANNOTATIONS_SUCCESS = 'SAVE_ANNOTATIONS_SUCCESS', + SAVE_ANNOTATIONS_FAILED = 'SAVE_ANNOTATIONS_FAILED', + SAVE_UPDATE_ANNOTATIONS_STATUS = 'SAVE_UPDATE_ANNOTATIONS_STATUS', + SWITCH_PLAY = 'SWITCH_PLAY', + CONFIRM_CANVAS_READY = 'CONFIRM_CANVAS_READY', + DRAG_CANVAS = 'DRAG_CANVAS', + ZOOM_CANVAS = 'ZOOM_CANVAS', + MERGE_OBJECTS = 'MERGE_OBJECTS', + GROUP_OBJECTS = 'GROUP_OBJECTS', + SPLIT_TRACK = 'SPLIT_TRACK', + COPY_SHAPE = 'COPY_SHAPE', + PASTE_SHAPE = 'PASTE_SHAPE', + EDIT_SHAPE = 'EDIT_SHAPE', + DRAW_SHAPE = 'DRAW_SHAPE', + REPEAT_DRAW_SHAPE = 'REPEAT_DRAW_SHAPE', + SHAPE_DRAWN = 'SHAPE_DRAWN', + RESET_CANVAS = 'RESET_CANVAS', + UPDATE_ANNOTATIONS_SUCCESS = 'UPDATE_ANNOTATIONS_SUCCESS', + UPDATE_ANNOTATIONS_FAILED = 'UPDATE_ANNOTATIONS_FAILED', + CREATE_ANNOTATIONS_SUCCESS = 'CREATE_ANNOTATIONS_SUCCESS', + CREATE_ANNOTATIONS_FAILED = 'CREATE_ANNOTATIONS_FAILED', + MERGE_ANNOTATIONS_SUCCESS = 'MERGE_ANNOTATIONS_SUCCESS', + MERGE_ANNOTATIONS_FAILED = 'MERGE_ANNOTATIONS_FAILED', + RESET_ANNOTATIONS_GROUP = 'RESET_ANNOTATIONS_GROUP', + GROUP_ANNOTATIONS = 'GROUP_ANNOTATIONS', + GROUP_ANNOTATIONS_SUCCESS = 'GROUP_ANNOTATIONS_SUCCESS', + GROUP_ANNOTATIONS_FAILED = 'GROUP_ANNOTATIONS_FAILED', + SPLIT_ANNOTATIONS_SUCCESS = 'SPLIT_ANNOTATIONS_SUCCESS', + SPLIT_ANNOTATIONS_FAILED = 'SPLIT_ANNOTATIONS_FAILED', + CHANGE_LABEL_COLOR_SUCCESS = 'CHANGE_LABEL_COLOR_SUCCESS', + CHANGE_LABEL_COLOR_FAILED = 'CHANGE_LABEL_COLOR_FAILED', + UPDATE_TAB_CONTENT_HEIGHT = 'UPDATE_TAB_CONTENT_HEIGHT', + COLLAPSE_SIDEBAR = 'COLLAPSE_SIDEBAR', + COLLAPSE_APPEARANCE = 'COLLAPSE_APPEARANCE', + COLLAPSE_OBJECT_ITEMS = 'COLLAPSE_OBJECT_ITEMS', + ACTIVATE_OBJECT = 'ACTIVATE_OBJECT', + SELECT_OBJECTS = 'SELECT_OBJECTS', + REMOVE_OBJECT_SUCCESS = 'REMOVE_OBJECT_SUCCESS', + REMOVE_OBJECT_FAILED = 'REMOVE_OBJECT_FAILED', + PROPAGATE_OBJECT = 'PROPAGATE_OBJECT', + PROPAGATE_OBJECT_SUCCESS = 'PROPAGATE_OBJECT_SUCCESS', + PROPAGATE_OBJECT_FAILED = 'PROPAGATE_OBJECT_FAILED', + CHANGE_PROPAGATE_FRAMES = 'CHANGE_PROPAGATE_FRAMES', + SWITCH_SHOWING_STATISTICS = 'SWITCH_SHOWING_STATISTICS', + COLLECT_STATISTICS = 'COLLECT_STATISTICS', + COLLECT_STATISTICS_SUCCESS = 'COLLECT_STATISTICS_SUCCESS', + COLLECT_STATISTICS_FAILED = 'COLLECT_STATISTICS_FAILED', + CHANGE_JOB_STATUS = 'CHANGE_JOB_STATUS', + CHANGE_JOB_STATUS_SUCCESS = 'CHANGE_JOB_STATUS_SUCCESS', + CHANGE_JOB_STATUS_FAILED = 'CHANGE_JOB_STATUS_FAILED', + UPLOAD_JOB_ANNOTATIONS = 'UPLOAD_JOB_ANNOTATIONS', + UPLOAD_JOB_ANNOTATIONS_SUCCESS = 'UPLOAD_JOB_ANNOTATIONS_SUCCESS', + UPLOAD_JOB_ANNOTATIONS_FAILED = 'UPLOAD_JOB_ANNOTATIONS_FAILED', + REMOVE_JOB_ANNOTATIONS_SUCCESS = 'REMOVE_JOB_ANNOTATIONS_SUCCESS', + REMOVE_JOB_ANNOTATIONS_FAILED = 'REMOVE_JOB_ANNOTATIONS_FAILED', + UPDATE_CANVAS_CONTEXT_MENU = 'UPDATE_CANVAS_CONTEXT_MENU', + UNDO_ACTION_SUCCESS = 'UNDO_ACTION_SUCCESS', + UNDO_ACTION_FAILED = 'UNDO_ACTION_FAILED', + REDO_ACTION_SUCCESS = 'REDO_ACTION_SUCCESS', + REDO_ACTION_FAILED = 'REDO_ACTION_FAILED', + CHANGE_ANNOTATIONS_FILTERS = 'CHANGE_ANNOTATIONS_FILTERS', + FETCH_ANNOTATIONS_SUCCESS = 'FETCH_ANNOTATIONS_SUCCESS', + FETCH_ANNOTATIONS_FAILED = 'FETCH_ANNOTATIONS_FAILED', + ROTATE_FRAME = 'ROTATE_FRAME', + SWITCH_Z_LAYER = 'SWITCH_Z_LAYER', + ADD_Z_LAYER = 'ADD_Z_LAYER', + SEARCH_ANNOTATIONS_FAILED = 'SEARCH_ANNOTATIONS_FAILED', +} + +export function addZLayer(): AnyAction { + return { + type: AnnotationActionTypes.ADD_Z_LAYER, + }; +} + +export function switchZLayer(cur: number): AnyAction { + return { + type: AnnotationActionTypes.SWITCH_Z_LAYER, + payload: { + cur, + }, + }; +} + +export function fetchAnnotationsAsync(sessionInstance: any): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + try { + const { filters, frame, showAllInterpolationTracks } = receiveAnnotationsParameters(); + const states = await sessionInstance.annotations + .get(frame, showAllInterpolationTracks, filters); + const [minZ, maxZ] = computeZRange(states); + + dispatch({ + type: AnnotationActionTypes.FETCH_ANNOTATIONS_SUCCESS, + payload: { + states, + minZ, + maxZ, + }, + }); + } catch (error) { + dispatch({ + type: AnnotationActionTypes.FETCH_ANNOTATIONS_FAILED, + payload: { + error, + }, + }); + } + }; +} + +export function changeAnnotationsFilters(filters: string[]): AnyAction { + const state: CombinedState = getStore().getState(); + const { filtersHistory, filters: oldFilters } = state.annotation.annotations; + + filters.forEach((element: string) => { + if (!(filtersHistory.includes(element) || oldFilters.includes(element))) { + filtersHistory.push(element); + } + }); + + window.localStorage.setItem('filtersHistory', JSON.stringify(filtersHistory.slice(-10))); + + return { + type: AnnotationActionTypes.CHANGE_ANNOTATIONS_FILTERS, + payload: { + filters, + filtersHistory: filtersHistory.slice(-10), + }, + }; +} + +export function undoActionAsync(sessionInstance: any, frame: number): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + try { + const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); + + // TODO: use affected IDs as an optimization + await sessionInstance.actions.undo(); + const history = await sessionInstance.actions.get(); + const states = await sessionInstance.annotations + .get(frame, showAllInterpolationTracks, filters); + const [minZ, maxZ] = computeZRange(states); + + dispatch({ + type: AnnotationActionTypes.UNDO_ACTION_SUCCESS, + payload: { + history, + states, + minZ, + maxZ, + }, + }); + } catch (error) { + dispatch({ + type: AnnotationActionTypes.UNDO_ACTION_FAILED, + payload: { + error, + }, + }); + } + }; +} + +export function redoActionAsync(sessionInstance: any, frame: number): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + try { + const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); + + // TODO: use affected IDs as an optimization + await sessionInstance.actions.redo(); + const history = await sessionInstance.actions.get(); + const states = await sessionInstance.annotations + .get(frame, showAllInterpolationTracks, filters); + const [minZ, maxZ] = computeZRange(states); + + dispatch({ + type: AnnotationActionTypes.REDO_ACTION_SUCCESS, + payload: { + history, + states, + minZ, + maxZ, + }, + }); + } catch (error) { + dispatch({ + type: AnnotationActionTypes.REDO_ACTION_FAILED, + payload: { + error, + }, + }); + } + }; +} + +export function updateCanvasContextMenu(visible: boolean, left: number, top: number): AnyAction { + return { + type: AnnotationActionTypes.UPDATE_CANVAS_CONTEXT_MENU, + payload: { + visible, + left, + top, + }, + }; +} + +export function removeAnnotationsAsync(sessionInstance: any): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + try { + await sessionInstance.annotations.clear(); + await sessionInstance.actions.clear(); + const history = await sessionInstance.actions.get(); + + dispatch({ + type: AnnotationActionTypes.REMOVE_JOB_ANNOTATIONS_SUCCESS, + payload: { + history, + }, + }); + } catch (error) { + dispatch({ + type: AnnotationActionTypes.REMOVE_JOB_ANNOTATIONS_FAILED, + payload: { + error, + }, + }); + } + }; +} + +export function uploadJobAnnotationsAsync(job: any, loader: any, file: File): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + try { + const state: CombinedState = getStore().getState(); + const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); + + if (state.tasks.activities.loads[job.task.id]) { + throw Error('Annotations is being uploaded for the task'); + } + if (state.annotation.activities.loads[job.id]) { + throw Error('Only one uploading of annotations for a job allowed at the same time'); + } + + dispatch({ + type: AnnotationActionTypes.UPLOAD_JOB_ANNOTATIONS, + payload: { + job, + loader, + }, + }); + + const frame = state.annotation.player.frame.number; + await job.annotations.upload(file, loader); + + // One more update to escape some problems + // in canvas when shape with the same + // clientID has different type (polygon, rectangle) for example + dispatch({ + type: AnnotationActionTypes.UPLOAD_JOB_ANNOTATIONS_SUCCESS, + payload: { + job, + states: [], + }, + }); + + await job.annotations.clear(true); + await job.actions.clear(); + const history = await job.actions.get(); + const states = await job.annotations.get(frame, showAllInterpolationTracks, filters); + + setTimeout(() => { + dispatch({ + type: AnnotationActionTypes.UPLOAD_JOB_ANNOTATIONS_SUCCESS, + payload: { + history, + job, + states, + }, + }); + }); + } catch (error) { + dispatch({ + type: AnnotationActionTypes.UPLOAD_JOB_ANNOTATIONS_FAILED, + payload: { + job, + error, + }, + }); + } + }; +} + +export function changeJobStatusAsync(jobInstance: any, status: string): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + const oldStatus = jobInstance.status; + try { + dispatch({ + type: AnnotationActionTypes.CHANGE_JOB_STATUS, + payload: {}, + }); + + // eslint-disable-next-line no-param-reassign + jobInstance.status = status; + await jobInstance.save(); + + dispatch({ + type: AnnotationActionTypes.CHANGE_JOB_STATUS_SUCCESS, + payload: {}, + }); + } catch (error) { + // eslint-disable-next-line no-param-reassign + jobInstance.status = oldStatus; + dispatch({ + type: AnnotationActionTypes.CHANGE_JOB_STATUS_FAILED, + payload: { + error, + }, + }); + } + }; +} + +export function collectStatisticsAsync(sessionInstance: any): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + try { + dispatch({ + type: AnnotationActionTypes.COLLECT_STATISTICS, + payload: {}, + }); + + const data = await sessionInstance.annotations.statistics(); + + dispatch({ + type: AnnotationActionTypes.COLLECT_STATISTICS_SUCCESS, + payload: { + data, + }, + }); + } catch (error) { + dispatch({ + type: AnnotationActionTypes.COLLECT_STATISTICS_FAILED, + payload: { + error, + }, + }); + } + }; +} + +export function showStatistics(visible: boolean): AnyAction { + return { + type: AnnotationActionTypes.SWITCH_SHOWING_STATISTICS, + payload: { + visible, + }, + }; +} + +export function propagateObjectAsync( + sessionInstance: any, + objectState: any, + from: number, + to: number, +): ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + try { + const copy = { + attributes: objectState.attributes, + points: objectState.points, + occluded: objectState.occluded, + objectType: objectState.objectType !== ObjectType.TRACK + ? objectState.objectType : ObjectType.SHAPE, + shapeType: objectState.shapeType, + label: objectState.label, + zOrder: objectState.zOrder, + frame: from, + }; + + const states = []; + for (let frame = from; frame <= to; frame++) { + copy.frame = frame; + const newState = new cvat.classes.ObjectState(copy); + states.push(newState); + } + + await sessionInstance.annotations.put(states); + const history = await sessionInstance.actions.get(); + + dispatch({ + type: AnnotationActionTypes.PROPAGATE_OBJECT_SUCCESS, + payload: { + objectState, + history, + }, + }); + } catch (error) { + dispatch({ + type: AnnotationActionTypes.PROPAGATE_OBJECT_FAILED, + payload: { + error, + }, + }); + } + }; +} + +export function propagateObject(objectState: any | null): AnyAction { + return { + type: AnnotationActionTypes.PROPAGATE_OBJECT, + payload: { + objectState, + }, + }; +} + +export function changePropagateFrames(frames: number): AnyAction { + return { + type: AnnotationActionTypes.CHANGE_PROPAGATE_FRAMES, + payload: { + frames, + }, + }; +} + +export function removeObjectAsync(sessionInstance: any, objectState: any, force: boolean): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + try { + const removed = await objectState.delete(force); + const history = await sessionInstance.actions.get(); + + if (removed) { + dispatch({ + type: AnnotationActionTypes.REMOVE_OBJECT_SUCCESS, + payload: { + objectState, + history, + }, + }); + } else { + throw new Error('Could not remove the object. Is it locked?'); + } + } catch (error) { + dispatch({ + type: AnnotationActionTypes.REMOVE_OBJECT_FAILED, + payload: { + objectState, + }, + }); + } + }; +} + +export function editShape(enabled: boolean): AnyAction { + return { + type: AnnotationActionTypes.EDIT_SHAPE, + payload: { + enabled, + }, + }; +} + +export function copyShape(objectState: any): AnyAction { + return { + type: AnnotationActionTypes.COPY_SHAPE, + payload: { + objectState, + }, + }; +} + +export function selectObjects(selectedStatesID: number[]): AnyAction { + return { + type: AnnotationActionTypes.SELECT_OBJECTS, + payload: { + selectedStatesID, + }, + }; +} + +export function activateObject(activatedStateID: number | null): AnyAction { + return { + type: AnnotationActionTypes.ACTIVATE_OBJECT, + payload: { + activatedStateID, + }, + }; +} + +export function updateTabContentHeight(tabContentHeight: number): AnyAction { + return { + type: AnnotationActionTypes.UPDATE_TAB_CONTENT_HEIGHT, + payload: { + tabContentHeight, + }, + }; +} + +export function collapseSidebar(): AnyAction { + return { + type: AnnotationActionTypes.COLLAPSE_SIDEBAR, + payload: {}, + }; +} + +export function collapseAppearance(): AnyAction { + return { + type: AnnotationActionTypes.COLLAPSE_APPEARANCE, + payload: {}, + }; +} + +export function collapseObjectItems(states: any[], collapsed: boolean): AnyAction { + return { + type: AnnotationActionTypes.COLLAPSE_OBJECT_ITEMS, + payload: { + states, + collapsed, + }, + }; +} + +export function switchPlay(playing: boolean): AnyAction { + return { + type: AnnotationActionTypes.SWITCH_PLAY, + payload: { + playing, + }, + }; +} + +export function changeFrameAsync(toFrame: number): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + const state: CombinedState = getStore().getState(); + const { instance: job } = state.annotation.job; + const { filters, frame, showAllInterpolationTracks } = receiveAnnotationsParameters(); + + try { + if (toFrame < job.startFrame || toFrame > job.stopFrame) { + throw Error(`Required frame ${toFrame} is out of the current job`); + } + + if (toFrame === frame) { + dispatch({ + type: AnnotationActionTypes.CHANGE_FRAME_SUCCESS, + payload: { + number: state.annotation.player.frame.number, + data: state.annotation.player.frame.data, + states: state.annotation.annotations.states, + }, + }); + + return; + } + + // Start async requests + dispatch({ + type: AnnotationActionTypes.CHANGE_FRAME, + payload: {}, + }); + + const data = await job.frames.get(toFrame); + const states = await job.annotations.get(toFrame, showAllInterpolationTracks, filters); + const [minZ, maxZ] = computeZRange(states); + const currentTime = new Date().getTime(); + let frameSpeed; + switch (state.settings.player.frameSpeed) { + case (FrameSpeed.Fast): { + frameSpeed = (FrameSpeed.Fast as number) / 2; + break; + } + case (FrameSpeed.Fastest): { + frameSpeed = (FrameSpeed.Fastest as number) / 3; + break; + } + default: { + frameSpeed = state.settings.player.frameSpeed as number; + } + } + const delay = Math.max(0, Math.round(1000 / frameSpeed) + - currentTime + (state.annotation.player.frame.changeTime as number)); + dispatch({ + type: AnnotationActionTypes.CHANGE_FRAME_SUCCESS, + payload: { + number: toFrame, + data, + states, + minZ, + maxZ, + changeTime: currentTime + delay, + delay, + }, + }); + } catch (error) { + dispatch({ + type: AnnotationActionTypes.CHANGE_FRAME_FAILED, + payload: { + number: toFrame, + error, + }, + }); + } + }; +} + + +export function rotateCurrentFrame(rotation: Rotation): AnyAction { + const state: CombinedState = getStore().getState(); + const { number: frameNumber } = state.annotation.player.frame; + const { startFrame } = state.annotation.job.instance; + const { frameAngles } = state.annotation.player; + const { rotateAll } = state.settings.player; + + const frameAngle = (frameAngles[frameNumber - startFrame] + + (rotation === Rotation.CLOCKWISE90 ? 90 : 270)) % 360; + + return { + type: AnnotationActionTypes.ROTATE_FRAME, + payload: { + offset: frameNumber - state.annotation.job.instance.startFrame, + angle: frameAngle, + rotateAll, + }, + }; +} + +export function dragCanvas(enabled: boolean): AnyAction { + return { + type: AnnotationActionTypes.DRAG_CANVAS, + payload: { + enabled, + }, + }; +} + +export function zoomCanvas(enabled: boolean): AnyAction { + return { + type: AnnotationActionTypes.ZOOM_CANVAS, + payload: { + enabled, + }, + }; +} + +export function resetCanvas(): AnyAction { + return { + type: AnnotationActionTypes.RESET_CANVAS, + payload: {}, + }; +} + +export function confirmCanvasReady(): AnyAction { + return { + type: AnnotationActionTypes.CONFIRM_CANVAS_READY, + payload: {}, + }; +} + +export function getJobAsync( + tid: number, + jid: number, + initialFrame: number, + initialFilters: string[], +): ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + dispatch({ + type: AnnotationActionTypes.GET_JOB, + payload: {}, + }); + + try { + const state: CombinedState = getStore().getState(); + const filters = initialFilters; + const { showAllInterpolationTracks } = state.settings.workspace; + + // Check if already loaded job is different from asking one + if (state.annotation.job.instance && state.annotation.job.instance.id !== jid) { + dispatch({ + type: AnnotationActionTypes.CLOSE_JOB, + }); + } + + // Check state if the task is already there + let task = state.tasks.current + .filter((_task: Task) => _task.instance.id === tid) + .map((_task: Task) => _task.instance)[0]; + + // If there aren't the task, get it from the server + if (!task) { + [task] = await cvat.tasks.get({ id: tid }); + } + + // Finally get the job from the task + const job = task.jobs + .filter((_job: any) => _job.id === jid)[0]; + if (!job) { + throw new Error(`Task ${tid} doesn't contain the job ${jid}`); + } + + const frameNumber = Math.max(Math.min(job.stopFrame, initialFrame), job.startFrame); + const frameData = await job.frames.get(frameNumber); + const states = await job.annotations + .get(frameNumber, showAllInterpolationTracks, filters); + const [minZ, maxZ] = computeZRange(states); + const colors = [...cvat.enums.colors]; + + dispatch({ + type: AnnotationActionTypes.GET_JOB_SUCCESS, + payload: { + job, + states, + frameNumber, + frameData, + colors, + filters, + minZ, + maxZ, + }, + }); + } catch (error) { + dispatch({ + type: AnnotationActionTypes.GET_JOB_FAILED, + payload: { + error, + }, + }); + } + }; +} + +export function saveAnnotationsAsync(sessionInstance: any): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + dispatch({ + type: AnnotationActionTypes.SAVE_ANNOTATIONS, + payload: {}, + }); + + try { + await sessionInstance.annotations.save((status: string) => { + dispatch({ + type: AnnotationActionTypes.SAVE_UPDATE_ANNOTATIONS_STATUS, + payload: { + status, + }, + }); + }); + + dispatch({ + type: AnnotationActionTypes.SAVE_ANNOTATIONS_SUCCESS, + payload: {}, + }); + } catch (error) { + dispatch({ + type: AnnotationActionTypes.SAVE_ANNOTATIONS_FAILED, + payload: { + error, + }, + }); + } + }; +} + +export function drawShape( + shapeType: ShapeType, + labelID: number, + objectType: ObjectType, + points?: number, + rectDrawingMethod?: RectDrawingMethod, +): AnyAction { + let activeControl = ActiveControl.DRAW_RECTANGLE; + if (shapeType === ShapeType.POLYGON) { + activeControl = ActiveControl.DRAW_POLYGON; + } else if (shapeType === ShapeType.POLYLINE) { + activeControl = ActiveControl.DRAW_POLYLINE; + } else if (shapeType === ShapeType.POINTS) { + activeControl = ActiveControl.DRAW_POINTS; + } + + return { + type: AnnotationActionTypes.DRAW_SHAPE, + payload: { + shapeType, + labelID, + objectType, + points, + activeControl, + rectDrawingMethod, + }, + }; +} + +export function shapeDrawn(): AnyAction { + return { + type: AnnotationActionTypes.SHAPE_DRAWN, + payload: {}, + }; +} + +export function mergeObjects(enabled: boolean): AnyAction { + return { + type: AnnotationActionTypes.MERGE_OBJECTS, + payload: { + enabled, + }, + }; +} + +export function groupObjects(enabled: boolean): AnyAction { + return { + type: AnnotationActionTypes.GROUP_OBJECTS, + payload: { + enabled, + }, + }; +} + +export function splitTrack(enabled: boolean): AnyAction { + return { + type: AnnotationActionTypes.SPLIT_TRACK, + payload: { + enabled, + }, + }; +} + +export function updateAnnotationsAsync(sessionInstance: any, frame: number, statesToUpdate: any[]): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + try { + if (statesToUpdate.some((state: any): boolean => state.updateFlags.zOrder)) { + // deactivate object to visualize changes immediately (UX) + dispatch(activateObject(null)); + } + + const promises = statesToUpdate + .map((objectState: any): Promise => objectState.save()); + const states = await Promise.all(promises); + const history = await sessionInstance.actions.get(); + const [minZ, maxZ] = computeZRange(states); + + dispatch({ + type: AnnotationActionTypes.UPDATE_ANNOTATIONS_SUCCESS, + payload: { + states, + history, + minZ, + maxZ, + }, + }); + } catch (error) { + const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); + const states = await sessionInstance.annotations + .get(frame, showAllInterpolationTracks, filters); + dispatch({ + type: AnnotationActionTypes.UPDATE_ANNOTATIONS_FAILED, + payload: { + error, + states, + }, + }); + } + }; +} + +export function createAnnotationsAsync(sessionInstance: any, frame: number, statesToCreate: any[]): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + try { + const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); + await sessionInstance.annotations.put(statesToCreate); + const states = await sessionInstance.annotations + .get(frame, showAllInterpolationTracks, filters); + const history = await sessionInstance.actions.get(); + + dispatch({ + type: AnnotationActionTypes.CREATE_ANNOTATIONS_SUCCESS, + payload: { + states, + history, + }, + }); + } catch (error) { + dispatch({ + type: AnnotationActionTypes.CREATE_ANNOTATIONS_FAILED, + payload: { + error, + }, + }); + } + }; +} + +export function mergeAnnotationsAsync(sessionInstance: any, frame: number, statesToMerge: any[]): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + try { + const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); + await sessionInstance.annotations.merge(statesToMerge); + const states = await sessionInstance.annotations + .get(frame, showAllInterpolationTracks, filters); + const history = await sessionInstance.actions.get(); + + dispatch({ + type: AnnotationActionTypes.MERGE_ANNOTATIONS_SUCCESS, + payload: { + states, + history, + }, + }); + } catch (error) { + dispatch({ + type: AnnotationActionTypes.MERGE_ANNOTATIONS_FAILED, + payload: { + error, + }, + }); + } + }; +} + +export function resetAnnotationsGroup(): AnyAction { + return { + type: AnnotationActionTypes.RESET_ANNOTATIONS_GROUP, + payload: {}, + }; +} + +export function groupAnnotationsAsync( + sessionInstance: any, + frame: number, + statesToGroup: any[], +): ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + try { + const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); + const reset = getStore().getState().annotation.annotations.resetGroupFlag; + + // The action below set resetFlag to false + dispatch({ + type: AnnotationActionTypes.GROUP_ANNOTATIONS, + payload: {}, + }); + + await sessionInstance.annotations.group(statesToGroup, reset); + const states = await sessionInstance.annotations + .get(frame, showAllInterpolationTracks, filters); + const history = await sessionInstance.actions.get(); + + dispatch({ + type: AnnotationActionTypes.GROUP_ANNOTATIONS_SUCCESS, + payload: { + states, + history, + }, + }); + } catch (error) { + dispatch({ + type: AnnotationActionTypes.GROUP_ANNOTATIONS_FAILED, + payload: { + error, + }, + }); + } + }; +} + +export function splitAnnotationsAsync(sessionInstance: any, frame: number, stateToSplit: any): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); + try { + await sessionInstance.annotations.split(stateToSplit, frame); + const states = await sessionInstance.annotations + .get(frame, showAllInterpolationTracks, filters); + const history = await sessionInstance.actions.get(); + + dispatch({ + type: AnnotationActionTypes.SPLIT_ANNOTATIONS_SUCCESS, + payload: { + states, + history, + }, + }); + } catch (error) { + dispatch({ + type: AnnotationActionTypes.SPLIT_ANNOTATIONS_FAILED, + payload: { + error, + }, + }); + } + }; +} + +export function changeLabelColorAsync( + sessionInstance: any, + frameNumber: number, + label: any, + color: string, +): ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + try { + const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); + const updatedLabel = label; + updatedLabel.color = color; + const states = await sessionInstance.annotations + .get(frameNumber, showAllInterpolationTracks, filters); + const history = await sessionInstance.actions.get(); + + dispatch({ + type: AnnotationActionTypes.CHANGE_LABEL_COLOR_SUCCESS, + payload: { + label: updatedLabel, + history, + states, + }, + }); + } catch (error) { + dispatch({ + type: AnnotationActionTypes.CHANGE_LABEL_COLOR_FAILED, + payload: { + error, + }, + }); + } + }; +} + +export function changeGroupColorAsync( + sessionInstance: any, + frameNumber: number, + group: number, + color: string, +): ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + const state: CombinedState = getStore().getState(); + const groupStates = state.annotation.annotations.states + .filter((_state: any): boolean => _state.group.id === group); + if (groupStates.length) { + groupStates[0].group.color = color; + dispatch(updateAnnotationsAsync(sessionInstance, frameNumber, groupStates)); + } else { + dispatch(updateAnnotationsAsync(sessionInstance, frameNumber, [])); + } + }; +} + +export function searchAnnotationsAsync( + sessionInstance: any, + frameFrom: number, + frameTo: number, +): ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + try { + const { filters } = receiveAnnotationsParameters(); + const frame = await sessionInstance.annotations.search(filters, frameFrom, frameTo); + if (frame !== null) { + dispatch(changeFrameAsync(frame)); + } + } catch (error) { + dispatch({ + type: AnnotationActionTypes.SEARCH_ANNOTATIONS_FAILED, + payload: { + error, + }, + }); + } + }; +} + +export function pasteShapeAsync(): ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + const initialState = getStore().getState().annotation.drawing.activeInitialState; + const { instance: canvasInstance } = getStore().getState().annotation.canvas; + + if (initialState) { + let activeControl = ActiveControl.DRAW_RECTANGLE; + if (initialState.shapeType === ShapeType.POINTS) { + activeControl = ActiveControl.DRAW_POINTS; + } else if (initialState.shapeType === ShapeType.POLYGON) { + activeControl = ActiveControl.DRAW_POLYGON; + } else if (initialState.shapeType === ShapeType.POLYLINE) { + activeControl = ActiveControl.DRAW_POLYLINE; + } + + dispatch({ + type: AnnotationActionTypes.PASTE_SHAPE, + payload: { + activeControl, + }, + }); + + canvasInstance.cancel(); + canvasInstance.draw({ + enabled: true, + initialState, + }); + } + }; +} + +export function repeatDrawShapeAsync(): ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + const { + activeShapeType, + activeNumOfPoints, + activeRectDrawingMethod, + } = getStore().getState().annotation.drawing; + + const { instance: canvasInstance } = getStore().getState().annotation.canvas; + + let activeControl = ActiveControl.DRAW_RECTANGLE; + if (activeShapeType === ShapeType.POLYGON) { + activeControl = ActiveControl.DRAW_POLYGON; + } else if (activeShapeType === ShapeType.POLYLINE) { + activeControl = ActiveControl.DRAW_POLYLINE; + } else if (activeShapeType === ShapeType.POINTS) { + activeControl = ActiveControl.DRAW_POINTS; + } + + dispatch({ + type: AnnotationActionTypes.REPEAT_DRAW_SHAPE, + payload: { + activeControl, + }, + }); + + canvasInstance.cancel(); + canvasInstance.draw({ + enabled: true, + rectDrawingMethod: activeRectDrawingMethod, + numberOfPoints: activeNumOfPoints, + shapeType: activeShapeType, + crosshair: activeShapeType === ShapeType.RECTANGLE, + }); + }; +} diff --git a/cvat-ui/src/actions/annotations.actions.ts b/cvat-ui/src/actions/annotations.actions.ts deleted file mode 100644 index 85b417ad693..00000000000 --- a/cvat-ui/src/actions/annotations.actions.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { Dispatch, ActionCreator } from 'redux'; - - -export const dumpAnnotation = () => (dispatch: Dispatch) => { - dispatch({ - type: 'DUMP_ANNOTATION', - }); -} - -export const dumpAnnotationSuccess = (downloadLink: string) => (dispatch: Dispatch) => { - dispatch({ - type: 'DUMP_ANNOTATION_SUCCESS', - payload: downloadLink, - }); -} - -export const dumpAnnotationError = (error = {}) => (dispatch: Dispatch) => { - dispatch({ - type: 'DUMP_ANNOTATION_ERROR', - payload: error, - }); -} - -export const uploadAnnotation = () => (dispatch: Dispatch) => { - dispatch({ - type: 'UPLOAD_ANNOTATION', - }); -} - -export const uploadAnnotationSuccess = () => (dispatch: Dispatch) => { - dispatch({ - type: 'UPLOAD_ANNOTATION_SUCCESS', - }); -} - -export const uploadAnnotationError = (error = {}) => (dispatch: Dispatch) => { - dispatch({ - type: 'UPLOAD_ANNOTATION_ERROR', - payload: error, - }); -} - -export const dumpAnnotationAsync = (task: any, dumper: any) => { - return (dispatch: ActionCreator) => { - dispatch(dumpAnnotation()); - - return task.annotations.dump(task.name, dumper).then( - (downloadLink: string) => { - dispatch(dumpAnnotationSuccess(downloadLink)); - }, - (error: any) => { - dispatch(dumpAnnotationError(error)); - - throw error; - }, - ); - }; -} - -export const uploadAnnotationAsync = (task: any, file: File, loader: any) => { - return (dispatch: ActionCreator) => { - dispatch(uploadAnnotation()); - - return task.annotations.upload(file, loader).then( - (response: any) => { - dispatch(uploadAnnotationSuccess()); - }, - (error: any) => { - dispatch(uploadAnnotationError(error)); - - throw error; - }, - ); - }; -} diff --git a/cvat-ui/src/actions/auth-actions.ts b/cvat-ui/src/actions/auth-actions.ts new file mode 100644 index 00000000000..71be07f2c54 --- /dev/null +++ b/cvat-ui/src/actions/auth-actions.ts @@ -0,0 +1,99 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; +import getCore from 'cvat-core'; + +const cvat = getCore(); + +export enum AuthActionTypes { + AUTHORIZED_SUCCESS = 'AUTHORIZED_SUCCESS', + AUTHORIZED_FAILED = 'AUTHORIZED_FAILED', + LOGIN = 'LOGIN', + LOGIN_SUCCESS = 'LOGIN_SUCCESS', + LOGIN_FAILED = 'LOGIN_FAILED', + REGISTER = 'REGISTER', + REGISTER_SUCCESS = 'REGISTER_SUCCESS', + REGISTER_FAILED = 'REGISTER_FAILED', + LOGOUT = 'LOGOUT', + LOGOUT_SUCCESS = 'LOGOUT_SUCCESS', + LOGOUT_FAILED = 'LOGOUT_FAILED', +} + +const authActions = { + authorizeSuccess: (user: any) => createAction(AuthActionTypes.AUTHORIZED_SUCCESS, { user }), + authorizeFailed: (error: any) => createAction(AuthActionTypes.AUTHORIZED_FAILED, { error }), + login: () => createAction(AuthActionTypes.LOGIN), + loginSuccess: (user: any) => createAction(AuthActionTypes.LOGIN_SUCCESS, { user }), + loginFailed: (error: any) => createAction(AuthActionTypes.LOGIN_FAILED, { error }), + register: () => createAction(AuthActionTypes.REGISTER), + registerSuccess: (user: any) => createAction(AuthActionTypes.REGISTER_SUCCESS, { user }), + registerFailed: (error: any) => createAction(AuthActionTypes.REGISTER_FAILED, { error }), + logout: () => createAction(AuthActionTypes.LOGOUT), + logoutSuccess: () => createAction(AuthActionTypes.LOGOUT_SUCCESS), + logoutFailed: (error: any) => createAction(AuthActionTypes.LOGOUT_FAILED, { error }), +}; + +export type AuthActions = ActionUnion; + +export const registerAsync = ( + username: string, + firstName: string, + lastName: string, + email: string, + password1: string, + password2: string, +): ThunkAction => async ( + dispatch, +) => { + dispatch(authActions.register()); + + try { + await cvat.server.register(username, firstName, lastName, email, password1, password2); + const users = await cvat.users.get({ self: true }); + + dispatch(authActions.registerSuccess(users[0])); + } catch (error) { + dispatch(authActions.registerFailed(error)); + } +}; + +export const loginAsync = (username: string, password: string): ThunkAction => async (dispatch) => { + dispatch(authActions.login()); + + try { + await cvat.server.login(username, password); + const users = await cvat.users.get({ self: true }); + + dispatch(authActions.loginSuccess(users[0])); + } catch (error) { + dispatch(authActions.loginFailed(error)); + } +}; + +export const logoutAsync = (): ThunkAction => async (dispatch) => { + dispatch(authActions.logout()); + + try { + await cvat.server.logout(); + dispatch(authActions.logoutSuccess()); + } catch (error) { + dispatch(authActions.logoutFailed(error)); + } +}; + +export const authorizedAsync = (): ThunkAction => async (dispatch) => { + try { + const result = await cvat.server.authorized(); + + if (result) { + const userInstance = (await cvat.users.get({ self: true }))[0]; + dispatch(authActions.authorizeSuccess(userInstance)); + } else { + dispatch(authActions.authorizeSuccess(null)); + } + } catch (error) { + dispatch(authActions.authorizeFailed(error)); + } +}; diff --git a/cvat-ui/src/actions/auth.actions.ts b/cvat-ui/src/actions/auth.actions.ts deleted file mode 100644 index 9447403e928..00000000000 --- a/cvat-ui/src/actions/auth.actions.ts +++ /dev/null @@ -1,172 +0,0 @@ -import { History } from 'history'; -import { Dispatch, ActionCreator } from 'redux'; - - -export const login = () => (dispatch: Dispatch) => { - dispatch({ - type: 'LOGIN', - }); -} - -export const loginSuccess = () => (dispatch: Dispatch) => { - dispatch({ - type: 'LOGIN_SUCCESS', - }); -} - -export const loginError = (error = {}) => (dispatch: Dispatch) => { - dispatch({ - type: 'LOGIN_ERROR', - payload: error, - }); -} - -export const logout = () => (dispatch: Dispatch) => { - dispatch({ - type: 'LOGOUT', - }); -} - -export const logoutSuccess = () => (dispatch: Dispatch) => { - dispatch({ - type: 'LOGOUT_SUCCESS', - }); -} - -export const logoutError = (error = {}) => (dispatch: Dispatch) => { - dispatch({ - type: 'LOGOUT_ERROR', - payload: error, - }); -} - -export const isAuthenticated = () => (dispatch: Dispatch) => { - dispatch({ - type: 'IS_AUTHENTICATED', - }); -} - -export const isAuthenticatedSuccess = () => (dispatch: Dispatch) => { - dispatch({ - type: 'IS_AUTHENTICATED_SUCCESS', - }); -} - -export const isAuthenticatedFail = () => (dispatch: Dispatch) => { - dispatch({ - type: 'IS_AUTHENTICATED_FAIL', - }); -} - -export const isAuthenticatedError = (error = {}) => (dispatch: Dispatch) => { - dispatch({ - type: 'IS_AUTHENTICATED_ERROR', - payload: error, - }); -} - -export const register = () => (dispatch: Dispatch) => { - dispatch({ - type: 'REGISTER', - }); -} - -export const registerSuccess = () => (dispatch: Dispatch) => { - dispatch({ - type: 'REGISTER_SUCCESS', - }); -} - -export const registerError = (error = {}) => (dispatch: Dispatch) => { - dispatch({ - type: 'REGISTER_ERROR', - payload: error, - }); -} - -export const loginAsync = (username: string, password: string, history: History) => { - return (dispatch: ActionCreator) => { - dispatch(login()); - - return (window as any).cvat.server.login(username, password).then( - (loggedIn: any) => { - dispatch(loginSuccess()); - - history.push(history.location.state ? history.location.state.from : '/tasks'); - }, - (error: any) => { - dispatch(loginError(error)); - - throw error; - }, - ); - }; -} - -export const logoutAsync = () => { - return (dispatch: ActionCreator) => { - dispatch(logout()); - - return (window as any).cvat.server.logout().then( - (loggedOut: any) => { - dispatch(logoutSuccess()); - }, - (error: any) => { - dispatch(logoutError(error)); - - throw error; - }, - ); - }; -} - -export const isAuthenticatedAsync = () => { - return (dispatch: ActionCreator) => { - dispatch(isAuthenticated()); - - return (window as any).cvat.server.authorized().then( - (isAuthenticated: boolean) => { - isAuthenticated ? dispatch(isAuthenticatedSuccess()) : dispatch(isAuthenticatedFail()); - }, - (error: any) => { - dispatch(isAuthenticatedError(error)); - - throw error; - }, - ); - }; -} - -export const registerAsync = ( - username: string, - firstName: string, - lastName: string, - email: string, - password: string, - passwordConfirmation: string, - history: History, -) => { - return (dispatch: ActionCreator) => { - dispatch(register()); - - return (window as any).cvat.server.register( - username, - firstName, - lastName, - email, - password, - passwordConfirmation, - ).then( - (registered: any) => { - dispatch(registerSuccess()); - - history.replace('/login'); - }, - (error: any) => { - dispatch(registerError(error)); - - throw error; - }, - ); - }; -} diff --git a/cvat-ui/src/actions/formats-actions.ts b/cvat-ui/src/actions/formats-actions.ts new file mode 100644 index 00000000000..2ef06269d9c --- /dev/null +++ b/cvat-ui/src/actions/formats-actions.ts @@ -0,0 +1,48 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; +import getCore from 'cvat-core'; + +const cvat = getCore(); + +export enum FormatsActionTypes { + GET_FORMATS = 'GET_FORMATS', + GET_FORMATS_SUCCESS = 'GET_FORMATS_SUCCESS', + GET_FORMATS_FAILED = 'GET_FORMATS_FAILED', +} + +const formatsActions = { + getFormats: () => createAction(FormatsActionTypes.GET_FORMATS), + getFormatsSuccess: (annotationFormats: any[], datasetFormats: any[]) => ( + createAction(FormatsActionTypes.GET_FORMATS_SUCCESS, { + annotationFormats, + datasetFormats, + }) + ), + getFormatsFailed: (error: any) => ( + createAction(FormatsActionTypes.GET_FORMATS_FAILED, { error }) + ), +}; + +export type FormatsActions = ActionUnion; + +export function getFormatsAsync(): ThunkAction { + return async (dispatch): Promise => { + dispatch(formatsActions.getFormats()); + let annotationFormats = null; + let datasetFormats = null; + + try { + annotationFormats = await cvat.server.formats(); + datasetFormats = await cvat.server.datasetFormats(); + + dispatch( + formatsActions.getFormatsSuccess(annotationFormats, datasetFormats), + ); + } catch (error) { + dispatch(formatsActions.getFormatsFailed(error)); + } + }; +} diff --git a/cvat-ui/src/actions/models-actions.ts b/cvat-ui/src/actions/models-actions.ts new file mode 100644 index 00000000000..c91ac39b79c --- /dev/null +++ b/cvat-ui/src/actions/models-actions.ts @@ -0,0 +1,547 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; +import { + Model, + ModelType, + ModelFiles, + ActiveInference, + CombinedState, +} from 'reducers/interfaces'; +import getCore from 'cvat-core'; + +export enum PreinstalledModels { + RCNN = 'RCNN Object Detector', + MaskRCNN = 'Mask RCNN Object Detector', +} + +export enum ModelsActionTypes { + GET_MODELS = 'GET_MODELS', + GET_MODELS_SUCCESS = 'GET_MODELS_SUCCESS', + GET_MODELS_FAILED = 'GET_MODELS_FAILED', + DELETE_MODEL = 'DELETE_MODEL', + DELETE_MODEL_SUCCESS = 'DELETE_MODEL_SUCCESS', + DELETE_MODEL_FAILED = 'DELETE_MODEL_FAILED', + CREATE_MODEL = 'CREATE_MODEL', + CREATE_MODEL_SUCCESS = 'CREATE_MODEL_SUCCESS', + CREATE_MODEL_FAILED = 'CREATE_MODEL_FAILED', + CREATE_MODEL_STATUS_UPDATED = 'CREATE_MODEL_STATUS_UPDATED', + START_INFERENCE_FAILED = 'START_INFERENCE_FAILED', + GET_INFERENCE_STATUS_SUCCESS = 'GET_INFERENCE_STATUS_SUCCESS', + GET_INFERENCE_STATUS_FAILED = 'GET_INFERENCE_STATUS_FAILED', + FETCH_META_FAILED = 'FETCH_META_FAILED', + SHOW_RUN_MODEL_DIALOG = 'SHOW_RUN_MODEL_DIALOG', + CLOSE_RUN_MODEL_DIALOG = 'CLOSE_RUN_MODEL_DIALOG', + CANCEL_INFERENCE_SUCCESS = 'CANCEL_INFERENCE_SUCCESS', + CANCEL_INFERENCE_FAILED = 'CANCEL_INFERENCE_FAILED', +} + +export const modelsActions = { + getModels: () => createAction(ModelsActionTypes.GET_MODELS), + getModelsSuccess: (models: Model[]) => createAction( + ModelsActionTypes.GET_MODELS_SUCCESS, { + models, + }, + ), + getModelsFailed: (error: any) => createAction( + ModelsActionTypes.GET_MODELS_FAILED, { + error, + }, + ), + deleteModelSuccess: (id: number) => createAction( + ModelsActionTypes.DELETE_MODEL_SUCCESS, { + id, + }, + ), + deleteModelFailed: (id: number, error: any) => createAction( + ModelsActionTypes.DELETE_MODEL_FAILED, { + error, id, + }, + ), + createModel: () => createAction(ModelsActionTypes.CREATE_MODEL), + createModelSuccess: () => createAction(ModelsActionTypes.CREATE_MODEL_SUCCESS), + createModelFailed: (error: any) => createAction( + ModelsActionTypes.CREATE_MODEL_FAILED, { + error, + }, + ), + createModelUpdateStatus: (status: string) => createAction( + ModelsActionTypes.CREATE_MODEL_STATUS_UPDATED, { + status, + }, + ), + fetchMetaFailed: (error: any) => createAction(ModelsActionTypes.FETCH_META_FAILED, { error }), + getInferenceStatusSuccess: (taskID: number, activeInference: ActiveInference) => createAction( + ModelsActionTypes.GET_INFERENCE_STATUS_SUCCESS, { + taskID, + activeInference, + }, + ), + getInferenceStatusFailed: (taskID: number, error: any) => createAction( + ModelsActionTypes.GET_INFERENCE_STATUS_FAILED, { + taskID, + error, + }, + ), + startInferenceFailed: (taskID: number, error: any) => createAction( + ModelsActionTypes.START_INFERENCE_FAILED, { + taskID, + error, + }, + ), + cancelInferenceSuccess: (taskID: number) => createAction( + ModelsActionTypes.CANCEL_INFERENCE_SUCCESS, { + taskID, + }, + ), + cancelInferenceFaild: (taskID: number, error: any) => createAction( + ModelsActionTypes.CANCEL_INFERENCE_FAILED, { + taskID, + error, + }, + ), + closeRunModelDialog: () => createAction(ModelsActionTypes.CLOSE_RUN_MODEL_DIALOG), + showRunModelDialog: (taskInstance: any) => createAction( + ModelsActionTypes.SHOW_RUN_MODEL_DIALOG, { + taskInstance, + }, + ), +}; + +export type ModelsActions = ActionUnion; + +const core = getCore(); +const baseURL = core.config.backendAPI.slice(0, -7); + +export function getModelsAsync(): ThunkAction { + return async (dispatch, getState): Promise => { + const state: CombinedState = getState(); + const OpenVINO = state.plugins.list.AUTO_ANNOTATION; + const RCNN = state.plugins.list.TF_ANNOTATION; + const MaskRCNN = state.plugins.list.TF_SEGMENTATION; + + dispatch(modelsActions.getModels()); + const models: Model[] = []; + + try { + if (OpenVINO) { + const response = await core.server.request( + `${baseURL}/auto_annotation/meta/get`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + data: JSON.stringify([]), + }, + ); + + + for (const model of response.models) { + models.push({ + id: model.id, + ownerID: model.owner, + primary: model.primary, + name: model.name, + uploadDate: model.uploadDate, + updateDate: model.updateDate, + labels: [...model.labels], + }); + } + } + + if (RCNN) { + models.push({ + id: null, + ownerID: null, + primary: true, + name: PreinstalledModels.RCNN, + uploadDate: '', + updateDate: '', + labels: ['surfboard', 'car', 'skateboard', 'boat', 'clock', + 'cat', 'cow', 'knife', 'apple', 'cup', 'tv', + 'baseball_bat', 'book', 'suitcase', 'tennis_racket', + 'stop_sign', 'couch', 'cell_phone', 'keyboard', + 'cake', 'tie', 'frisbee', 'truck', 'fire_hydrant', + 'snowboard', 'bed', 'vase', 'teddy_bear', + 'toaster', 'wine_glass', 'traffic_light', + 'broccoli', 'backpack', 'carrot', 'potted_plant', + 'donut', 'umbrella', 'parking_meter', 'bottle', + 'sandwich', 'motorcycle', 'bear', 'banana', + 'person', 'scissors', 'elephant', 'dining_table', + 'toothbrush', 'toilet', 'skis', 'bowl', 'sheep', + 'refrigerator', 'oven', 'microwave', 'train', + 'orange', 'mouse', 'laptop', 'bench', 'bicycle', + 'fork', 'kite', 'zebra', 'baseball_glove', 'bus', + 'spoon', 'horse', 'handbag', 'pizza', 'sports_ball', + 'airplane', 'hair_drier', 'hot_dog', 'remote', + 'sink', 'dog', 'bird', 'giraffe', 'chair', + ], + }); + } + + if (MaskRCNN) { + models.push({ + id: null, + ownerID: null, + primary: true, + name: PreinstalledModels.MaskRCNN, + uploadDate: '', + updateDate: '', + labels: ['BG', 'person', 'bicycle', 'car', 'motorcycle', 'airplane', + 'bus', 'train', 'truck', 'boat', 'traffic light', + 'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', + 'cat', 'dog', 'horse', 'sheep', 'cow', 'elephant', 'bear', + 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', + 'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball', + 'kite', 'baseball bat', 'baseball glove', 'skateboard', + 'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup', + 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple', + 'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', + 'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed', + 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', + 'keyboard', 'cell phone', 'microwave', 'oven', 'toaster', + 'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors', + 'teddy bear', 'hair drier', 'toothbrush', + ], + }); + } + } catch (error) { + dispatch(modelsActions.getModelsFailed(error)); + return; + } + + dispatch(modelsActions.getModelsSuccess(models)); + }; +} + +export function deleteModelAsync(id: number): ThunkAction { + return async (dispatch): Promise => { + try { + await core.server.request(`${baseURL}/auto_annotation/delete/${id}`, { + method: 'DELETE', + }); + } catch (error) { + dispatch(modelsActions.deleteModelFailed(id, error)); + return; + } + + dispatch(modelsActions.deleteModelSuccess(id)); + }; +} + +export function createModelAsync(name: string, files: ModelFiles, global: boolean): ThunkAction { + return async (dispatch): Promise => { + async function checkCallback(id: string): Promise { + try { + const data = await core.server.request( + `${baseURL}/auto_annotation/check/${id}`, { + method: 'GET', + }, + ); + + switch (data.status) { + case 'failed': + dispatch(modelsActions.createModelFailed( + `Checking request has returned the "${data.status}" status. Message: ${data.error}`, + )); + break; + case 'unknown': + dispatch(modelsActions.createModelFailed( + `Checking request has returned the "${data.status}" status.`, + )); + break; + case 'finished': + dispatch(modelsActions.createModelSuccess()); + break; + default: + if ('progress' in data) { + modelsActions.createModelUpdateStatus(data.progress); + } + setTimeout(checkCallback.bind(null, id), 1000); + } + } catch (error) { + dispatch(modelsActions.createModelFailed(error)); + } + } + + dispatch(modelsActions.createModel()); + const data = new FormData(); + data.append('name', name); + data.append('storage', typeof files.bin === 'string' ? 'shared' : 'local'); + data.append('shared', global.toString()); + Object.keys(files).reduce((acc, key: string): FormData => { + acc.append(key, files[key]); + return acc; + }, data); + + try { + dispatch(modelsActions.createModelUpdateStatus('Request is beign sent..')); + const response = await core.server.request( + `${baseURL}/auto_annotation/create`, { + method: 'POST', + data, + contentType: false, + processData: false, + }, + ); + + dispatch(modelsActions.createModelUpdateStatus('Request is being processed..')); + setTimeout(checkCallback.bind(null, response.id), 1000); + } catch (error) { + dispatch(modelsActions.createModelFailed(error)); + } + }; +} + +interface InferenceMeta { + active: boolean; + taskID: number; + requestID: string; + modelType: ModelType; +} + +const timers: any = {}; + +async function timeoutCallback( + url: string, + taskID: number, + modelType: ModelType, + dispatch: (action: ModelsActions) => void, +): Promise { + try { + delete timers[taskID]; + + const response = await core.server.request(url, { + method: 'GET', + }); + + const activeInference: ActiveInference = { + status: response.status, + progress: +response.progress || 0, + error: response.error || response.stderr || '', + modelType, + }; + + + if (activeInference.status === 'unknown') { + dispatch(modelsActions.getInferenceStatusFailed( + taskID, + new Error( + `Inference status for the task ${taskID} is unknown.`, + ), + )); + + return; + } + + if (activeInference.status === 'failed') { + dispatch(modelsActions.getInferenceStatusFailed( + taskID, + new Error( + `Inference status for the task ${taskID} is failed. ${activeInference.error}`, + ), + )); + + return; + } + + if (activeInference.status !== 'finished') { + timers[taskID] = setTimeout( + timeoutCallback.bind( + null, + url, + taskID, + modelType, + dispatch, + ), 3000, + ); + } + + dispatch(modelsActions.getInferenceStatusSuccess(taskID, activeInference)); + } catch (error) { + dispatch(modelsActions.getInferenceStatusFailed(taskID, new Error( + `Server request for the task ${taskID} was failed`, + ))); + } +} + +function subscribe( + inferenceMeta: InferenceMeta, + dispatch: (action: ModelsActions) => void, +): void { + if (!(inferenceMeta.taskID in timers)) { + let requestURL = `${baseURL}`; + if (inferenceMeta.modelType === ModelType.OPENVINO) { + requestURL = `${requestURL}/auto_annotation/check`; + } else if (inferenceMeta.modelType === ModelType.RCNN) { + requestURL = `${requestURL}/tensorflow/annotation/check/task`; + } else if (inferenceMeta.modelType === ModelType.MASK_RCNN) { + requestURL = `${requestURL}/tensorflow/segmentation/check/task`; + } + requestURL = `${requestURL}/${inferenceMeta.requestID}`; + timers[inferenceMeta.taskID] = setTimeout( + timeoutCallback.bind( + null, + requestURL, + inferenceMeta.taskID, + inferenceMeta.modelType, + dispatch, + ), + ); + } +} + +export function getInferenceStatusAsync(tasks: number[]): ThunkAction { + return async (dispatch, getState): Promise => { + function parse(response: any, modelType: ModelType): InferenceMeta[] { + return Object.keys(response).map((key: string): InferenceMeta => ({ + taskID: +key, + requestID: response[key].rq_id || key, + active: typeof (response[key].active) === 'undefined' ? ['queued', 'started'] + .includes(response[key].status.toLowerCase()) : response[key].active, + modelType, + })); + } + + const state: CombinedState = getState(); + const OpenVINO = state.plugins.list.AUTO_ANNOTATION; + const RCNN = state.plugins.list.TF_ANNOTATION; + const MaskRCNN = state.plugins.list.TF_SEGMENTATION; + + const dispatchCallback = (action: ModelsActions): void => { + dispatch(action); + }; + + try { + if (OpenVINO) { + const response = await core.server.request( + `${baseURL}/auto_annotation/meta/get`, { + method: 'POST', + data: JSON.stringify(tasks), + headers: { + 'Content-Type': 'application/json', + }, + }, + ); + + parse(response.run, ModelType.OPENVINO) + .filter((inferenceMeta: InferenceMeta): boolean => inferenceMeta.active) + .forEach((inferenceMeta: InferenceMeta): void => { + subscribe(inferenceMeta, dispatchCallback); + }); + } + + if (RCNN) { + const response = await core.server.request( + `${baseURL}/tensorflow/annotation/meta/get`, { + method: 'POST', + data: JSON.stringify(tasks), + headers: { + 'Content-Type': 'application/json', + }, + }, + ); + + parse(response, ModelType.RCNN) + .filter((inferenceMeta: InferenceMeta): boolean => inferenceMeta.active) + .forEach((inferenceMeta: InferenceMeta): void => { + subscribe(inferenceMeta, dispatchCallback); + }); + } + + if (MaskRCNN) { + const response = await core.server.request( + `${baseURL}/tensorflow/segmentation/meta/get`, { + method: 'POST', + data: JSON.stringify(tasks), + headers: { + 'Content-Type': 'application/json', + }, + }, + ); + + parse(response, ModelType.MASK_RCNN) + .filter((inferenceMeta: InferenceMeta): boolean => inferenceMeta.active) + .forEach((inferenceMeta: InferenceMeta): void => { + subscribe(inferenceMeta, dispatchCallback); + }); + } + } catch (error) { + dispatch(modelsActions.fetchMetaFailed(error)); + } + }; +} + +export function startInferenceAsync( + taskInstance: any, + model: Model, + mapping: { + [index: string]: string; + }, + cleanOut: boolean, +): ThunkAction { + return async (dispatch): Promise => { + try { + if (model.name === PreinstalledModels.RCNN) { + await core.server.request( + `${baseURL}/tensorflow/annotation/create/task/${taskInstance.id}`, + ); + } else if (model.name === PreinstalledModels.MaskRCNN) { + await core.server.request( + `${baseURL}/tensorflow/segmentation/create/task/${taskInstance.id}`, + ); + } else { + await core.server.request( + `${baseURL}/auto_annotation/start/${model.id}/${taskInstance.id}`, { + method: 'POST', + data: JSON.stringify({ + reset: cleanOut, + labels: mapping, + }), + headers: { + 'Content-Type': 'application/json', + }, + }, + ); + } + + dispatch(getInferenceStatusAsync([taskInstance.id])); + } catch (error) { + dispatch(modelsActions.startInferenceFailed(taskInstance.id, error)); + } + }; +} + +export function cancelInferenceAsync(taskID: number): ThunkAction { + return async (dispatch, getState): Promise => { + try { + const inference = getState().models.inferences[taskID]; + if (inference) { + if (inference.modelType === ModelType.OPENVINO) { + await core.server.request( + `${baseURL}/auto_annotation/cancel/${taskID}`, + ); + } else if (inference.modelType === ModelType.RCNN) { + await core.server.request( + `${baseURL}/tensorflow/annotation/cancel/task/${taskID}`, + ); + } else if (inference.modelType === ModelType.MASK_RCNN) { + await core.server.request( + `${baseURL}/tensorflow/segmentation/cancel/task/${taskID}`, + ); + } + + if (timers[taskID]) { + clearTimeout(timers[taskID]); + delete timers[taskID]; + } + } + + dispatch(modelsActions.cancelInferenceSuccess(taskID)); + } catch (error) { + dispatch(modelsActions.cancelInferenceFaild(taskID, error)); + } + }; +} diff --git a/cvat-ui/src/actions/notification-actions.ts b/cvat-ui/src/actions/notification-actions.ts new file mode 100644 index 00000000000..3f47d987917 --- /dev/null +++ b/cvat-ui/src/actions/notification-actions.ts @@ -0,0 +1,28 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import { AnyAction } from 'redux'; + +export enum NotificationsActionType { + RESET_ERRORS = 'RESET_ERRORS', + RESET_MESSAGES = 'RESET_MESSAGES', +} + +export function resetErrors(): AnyAction { + const action = { + type: NotificationsActionType.RESET_ERRORS, + payload: {}, + }; + + return action; +} + +export function resetMessages(): AnyAction { + const action = { + type: NotificationsActionType.RESET_MESSAGES, + payload: {}, + }; + + return action; +} diff --git a/cvat-ui/src/actions/plugins-actions.ts b/cvat-ui/src/actions/plugins-actions.ts new file mode 100644 index 00000000000..9e88b055969 --- /dev/null +++ b/cvat-ui/src/actions/plugins-actions.ts @@ -0,0 +1,55 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; +import { SupportedPlugins } from 'reducers/interfaces'; +import PluginChecker from 'utils/plugin-checker'; + +export enum PluginsActionTypes { + CHECK_PLUGINS = 'CHECK_PLUGINS', + CHECKED_ALL_PLUGINS = 'CHECKED_ALL_PLUGINS' +} + +type PluginObjects = Record; + +const pluginActions = { + checkPlugins: () => createAction(PluginsActionTypes.CHECK_PLUGINS), + checkedAllPlugins: (list: PluginObjects) => ( + createAction(PluginsActionTypes.CHECKED_ALL_PLUGINS, { + list, + }) + ), +}; + +export type PluginActions = ActionUnion; + +export function checkPluginsAsync(): ThunkAction { + return async (dispatch): Promise => { + dispatch(pluginActions.checkPlugins()); + const plugins: PluginObjects = { + ANALYTICS: false, + AUTO_ANNOTATION: false, + GIT_INTEGRATION: false, + TF_ANNOTATION: false, + TF_SEGMENTATION: false, + }; + + const promises: Promise[] = [ + PluginChecker.check(SupportedPlugins.ANALYTICS), + PluginChecker.check(SupportedPlugins.AUTO_ANNOTATION), + PluginChecker.check(SupportedPlugins.GIT_INTEGRATION), + PluginChecker.check(SupportedPlugins.TF_ANNOTATION), + PluginChecker.check(SupportedPlugins.TF_SEGMENTATION), + ]; + + const values = await Promise.all(promises); + [plugins.ANALYTICS] = values; + [, plugins.AUTO_ANNOTATION] = values; + [,, plugins.GIT_INTEGRATION] = values; + [,,, plugins.TF_ANNOTATION] = values; + [,,,, plugins.TF_SEGMENTATION] = values; + + dispatch(pluginActions.checkedAllPlugins(plugins)); + }; +} diff --git a/cvat-ui/src/actions/server.actions.ts b/cvat-ui/src/actions/server.actions.ts deleted file mode 100644 index 3c5e9af4dee..00000000000 --- a/cvat-ui/src/actions/server.actions.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { Dispatch, ActionCreator } from 'redux'; - - -export const getServerInfo = () => (dispatch: Dispatch) => { - dispatch({ - type: 'GET_SERVER_INFO', - }); -} - -export const getServerInfoSuccess = (information: null) => (dispatch: Dispatch) => { - dispatch({ - type: 'GET_SERVER_INFO_SUCCESS', - payload: information, - }); -} - -export const getServerInfoError = (error = {}) => (dispatch: Dispatch) => { - dispatch({ - type: 'GET_SERVER_INFO_ERROR', - payload: error, - }); -} - -export const getShareFiles = () => (dispatch: Dispatch) => { - dispatch({ - type: 'GET_SHARE_FILES', - }); -} - -export const getShareFilesSuccess = (files: []) => (dispatch: Dispatch) => { - dispatch({ - type: 'GET_SHARE_FILES_SUCCESS', - payload: files, - }); -} - -export const getShareFilesError = (error = {}) => (dispatch: Dispatch) => { - dispatch({ - type: 'GET_SHARE_FILES_ERROR', - payload: error, - }); -} - -export const getAnnotationFormats = () => (dispatch: Dispatch) => { - dispatch({ - type: 'GET_ANNOTATION_FORMATS', - }); -} - -export const getAnnotationFormatsSuccess = (annotationFormats: []) => (dispatch: Dispatch) => { - dispatch({ - type: 'GET_ANNOTATION_FORMATS_SUCCESS', - payload: annotationFormats, - }); -} - -export const getAnnotationFormatsError = (error = {}) => (dispatch: Dispatch) => { - dispatch({ - type: 'GET_ANNOTATION_FORMATS_ERROR', - payload: error, - }); -} - -export const getServerInfoAsync = () => { - return (dispatch: ActionCreator) => { - dispatch(getServerInfo()); - - return (window as any).cvat.server.about().then( - (information: any) => { - dispatch(getServerInfoSuccess(information)); - }, - (error: any) => { - dispatch(getServerInfoError(error)); - }, - ); - }; -} - -export const getShareFilesAsync = (directory: string) => { - return (dispatch: ActionCreator) => { - dispatch(getShareFiles()); - - return (window as any).cvat.server.share(directory).then( - (files: any) => { - dispatch(getShareFilesSuccess(files)); - }, - (error: any) => { - dispatch(getShareFilesError(error)); - }, - ); - }; -} - -export const getAnnotationFormatsAsync = () => { - return (dispatch: ActionCreator) => { - dispatch(getAnnotationFormats()); - - return (window as any).cvat.server.formats().then( - (formats: any) => { - dispatch(getAnnotationFormatsSuccess(formats)); - }, - (error: any) => { - dispatch(getAnnotationFormatsError(error)); - - throw error; - }, - ); - }; -} diff --git a/cvat-ui/src/actions/settings-actions.ts b/cvat-ui/src/actions/settings-actions.ts new file mode 100644 index 00000000000..c14c2db2138 --- /dev/null +++ b/cvat-ui/src/actions/settings-actions.ts @@ -0,0 +1,202 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import { AnyAction } from 'redux'; +import { + GridColor, + ColorBy, +} from 'reducers/interfaces'; + +export enum SettingsActionTypes { + SWITCH_ROTATE_ALL = 'SWITCH_ROTATE_ALL', + SWITCH_GRID = 'SWITCH_GRID', + CHANGE_GRID_SIZE = 'CHANGE_GRID_SIZE', + CHANGE_GRID_COLOR = 'CHANGE_GRID_COLOR', + CHANGE_GRID_OPACITY = 'CHANGE_GRID_OPACITY', + CHANGE_SHAPES_OPACITY = 'CHANGE_SHAPES_OPACITY', + CHANGE_SELECTED_SHAPES_OPACITY = 'CHANGE_SELECTED_SHAPES_OPACITY', + CHANGE_SHAPES_COLOR_BY = 'CHANGE_SHAPES_COLOR_BY', + CHANGE_SHAPES_BLACK_BORDERS = 'CHANGE_SHAPES_BLACK_BORDERS', + CHANGE_FRAME_STEP = 'CHANGE_FRAME_STEP', + CHANGE_FRAME_SPEED = 'CHANGE_FRAME_SPEED', + SWITCH_RESET_ZOOM = 'SWITCH_RESET_ZOOM', + CHANGE_BRIGHTNESS_LEVEL = 'CHANGE_BRIGHTNESS_LEVEL', + CHANGE_CONTRAST_LEVEL = 'CHANGE_CONTRAST_LEVEL', + CHANGE_SATURATION_LEVEL = 'CHANGE_SATURATION_LEVEL', + SWITCH_AUTO_SAVE = 'SWITCH_AUTO_SAVE', + CHANGE_AUTO_SAVE_INTERVAL = 'CHANGE_AUTO_SAVE_INTERVAL', + CHANGE_AAM_ZOOM_MARGIN = 'CHANGE_AAM_ZOOM_MARGIN', + SWITCH_SHOWNIG_INTERPOLATED_TRACKS = 'SWITCH_SHOWNIG_INTERPOLATED_TRACKS', +} + +export function changeShapesOpacity(opacity: number): AnyAction { + return { + type: SettingsActionTypes.CHANGE_SHAPES_OPACITY, + payload: { + opacity, + }, + }; +} + +export function changeSelectedShapesOpacity(selectedOpacity: number): AnyAction { + return { + type: SettingsActionTypes.CHANGE_SELECTED_SHAPES_OPACITY, + payload: { + selectedOpacity, + }, + }; +} + +export function changeShapesColorBy(colorBy: ColorBy): AnyAction { + return { + type: SettingsActionTypes.CHANGE_SHAPES_COLOR_BY, + payload: { + colorBy, + }, + }; +} + +export function changeShapesBlackBorders(blackBorders: boolean): AnyAction { + return { + type: SettingsActionTypes.CHANGE_SHAPES_BLACK_BORDERS, + payload: { + blackBorders, + }, + }; +} + +export function switchRotateAll(rotateAll: boolean): AnyAction { + return { + type: SettingsActionTypes.SWITCH_ROTATE_ALL, + payload: { + rotateAll, + }, + }; +} + +export function switchGrid(grid: boolean): AnyAction { + return { + type: SettingsActionTypes.SWITCH_GRID, + payload: { + grid, + }, + }; +} + +export function changeGridSize(gridSize: number): AnyAction { + return { + type: SettingsActionTypes.CHANGE_GRID_SIZE, + payload: { + gridSize, + }, + }; +} + +export function changeGridColor(gridColor: GridColor): AnyAction { + return { + type: SettingsActionTypes.CHANGE_GRID_COLOR, + payload: { + gridColor, + }, + }; +} + +export function changeGridOpacity(gridOpacity: number): AnyAction { + return { + type: SettingsActionTypes.CHANGE_GRID_OPACITY, + payload: { + gridOpacity, + }, + }; +} + +export function changeFrameStep(frameStep: number): AnyAction { + return { + type: SettingsActionTypes.CHANGE_FRAME_STEP, + payload: { + frameStep, + }, + }; +} + +export function changeFrameSpeed(frameSpeed: number): AnyAction { + return { + type: SettingsActionTypes.CHANGE_FRAME_SPEED, + payload: { + frameSpeed, + }, + }; +} + +export function switchResetZoom(resetZoom: boolean): AnyAction { + return { + type: SettingsActionTypes.SWITCH_RESET_ZOOM, + payload: { + resetZoom, + }, + }; +} + +export function changeBrightnessLevel(level: number): AnyAction { + return { + type: SettingsActionTypes.CHANGE_BRIGHTNESS_LEVEL, + payload: { + level, + }, + }; +} + +export function changeContrastLevel(level: number): AnyAction { + return { + type: SettingsActionTypes.CHANGE_CONTRAST_LEVEL, + payload: { + level, + }, + }; +} + +export function changeSaturationLevel(level: number): AnyAction { + return { + type: SettingsActionTypes.CHANGE_SATURATION_LEVEL, + payload: { + level, + }, + }; +} + +export function switchAutoSave(autoSave: boolean): AnyAction { + return { + type: SettingsActionTypes.SWITCH_AUTO_SAVE, + payload: { + autoSave, + }, + }; +} + +export function changeAutoSaveInterval(autoSaveInterval: number): AnyAction { + return { + type: SettingsActionTypes.CHANGE_AUTO_SAVE_INTERVAL, + payload: { + autoSaveInterval, + }, + }; +} + +export function changeAAMZoomMargin(aamZoomMargin: number): AnyAction { + return { + type: SettingsActionTypes.CHANGE_AAM_ZOOM_MARGIN, + payload: { + aamZoomMargin, + }, + }; +} + +export function switchShowingInterpolatedTracks(showAllInterpolationTracks: boolean): AnyAction { + return { + type: SettingsActionTypes.SWITCH_SHOWNIG_INTERPOLATED_TRACKS, + payload: { + showAllInterpolationTracks, + }, + }; +} diff --git a/cvat-ui/src/actions/share-actions.ts b/cvat-ui/src/actions/share-actions.ts new file mode 100644 index 00000000000..6ccef6014bd --- /dev/null +++ b/cvat-ui/src/actions/share-actions.ts @@ -0,0 +1,49 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; +import getCore from 'cvat-core'; + +import { ShareFileInfo } from 'reducers/interfaces'; + +const core = getCore(); + +export enum ShareActionTypes { + LOAD_SHARE_DATA = 'LOAD_SHARE_DATA', + LOAD_SHARE_DATA_SUCCESS = 'LOAD_SHARE_DATA_SUCCESS', + LOAD_SHARE_DATA_FAILED = 'LOAD_SHARE_DATA_FAILED', +} + +const shareActions = { + loadShareData: () => createAction(ShareActionTypes.LOAD_SHARE_DATA), + loadShareDataSuccess: (values: ShareFileInfo[], directory: string) => ( + createAction(ShareActionTypes.LOAD_SHARE_DATA_SUCCESS, { + values, + directory, + }) + ), + loadShareDataFailed: (error: any) => ( + createAction(ShareActionTypes.LOAD_SHARE_DATA_FAILED, { error }) + ), +}; + +export type ShareActions = ActionUnion; + +export function loadShareDataAsync( + directory: string, + success: () => void, + failure: () => void, +): ThunkAction { + return async (dispatch): Promise => { + try { + dispatch(shareActions.loadShareData()); + const values = await core.server.share(directory); + success(); + dispatch(shareActions.loadShareDataSuccess(values as ShareFileInfo[], directory)); + } catch (error) { + failure(); + dispatch(shareActions.loadShareDataFailed(error)); + } + }; +} diff --git a/cvat-ui/src/actions/shortcuts-actions.ts b/cvat-ui/src/actions/shortcuts-actions.ts new file mode 100644 index 00000000000..86d83e6d707 --- /dev/null +++ b/cvat-ui/src/actions/shortcuts-actions.ts @@ -0,0 +1,11 @@ +import { ActionUnion, createAction } from 'utils/redux'; + +export enum ShortcutsActionsTypes { + SWITCH_SHORTCUT_DIALOG = 'SWITCH_SHORTCUT_DIALOG', +} + +export const shortcutsActions = { + switchShortcutsDialog: () => createAction(ShortcutsActionsTypes.SWITCH_SHORTCUT_DIALOG), +}; + +export type ShortcutsActions = ActionUnion; diff --git a/cvat-ui/src/actions/tasks-actions.ts b/cvat-ui/src/actions/tasks-actions.ts new file mode 100644 index 00000000000..33c0d9621bf --- /dev/null +++ b/cvat-ui/src/actions/tasks-actions.ts @@ -0,0 +1,537 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import { AnyAction, Dispatch, ActionCreator } from 'redux'; +import { ThunkAction } from 'redux-thunk'; +import { + TasksQuery, + CombinedState, +} from 'reducers/interfaces'; +import { getCVATStore } from 'cvat-store'; +import getCore from 'cvat-core'; +import { getInferenceStatusAsync } from './models-actions'; + +const cvat = getCore(); + +export enum TasksActionTypes { + GET_TASKS = 'GET_TASKS', + GET_TASKS_SUCCESS = 'GET_TASKS_SUCCESS', + GET_TASKS_FAILED = 'GET_TASKS_FAILED', + LOAD_ANNOTATIONS = 'LOAD_ANNOTATIONS', + LOAD_ANNOTATIONS_SUCCESS = 'LOAD_ANNOTATIONS_SUCCESS', + LOAD_ANNOTATIONS_FAILED = 'LOAD_ANNOTATIONS_FAILED', + DUMP_ANNOTATIONS = 'DUMP_ANNOTATIONS', + DUMP_ANNOTATIONS_SUCCESS = 'DUMP_ANNOTATIONS_SUCCESS', + DUMP_ANNOTATIONS_FAILED = 'DUMP_ANNOTATIONS_FAILED', + EXPORT_DATASET = 'EXPORT_DATASET', + EXPORT_DATASET_SUCCESS = 'EXPORT_DATASET_SUCCESS', + EXPORT_DATASET_FAILED = 'EXPORT_DATASET_FAILED', + DELETE_TASK = 'DELETE_TASK', + DELETE_TASK_SUCCESS = 'DELETE_TASK_SUCCESS', + DELETE_TASK_FAILED = 'DELETE_TASK_FAILED', + CREATE_TASK = 'CREATE_TASK', + CREATE_TASK_STATUS_UPDATED = 'CREATE_TASK_STATUS_UPDATED', + CREATE_TASK_SUCCESS = 'CREATE_TASK_SUCCESS', + CREATE_TASK_FAILED = 'CREATE_TASK_FAILED', + UPDATE_TASK = 'UPDATE_TASK', + UPDATE_TASK_SUCCESS = 'UPDATE_TASK_SUCCESS', + UPDATE_TASK_FAILED = 'UPDATE_TASK_FAILED', + HIDE_EMPTY_TASKS = 'HIDE_EMPTY_TASKS', +} + +function getTasks(): AnyAction { + const action = { + type: TasksActionTypes.GET_TASKS, + payload: {}, + }; + + return action; +} + +function getTasksSuccess(array: any[], previews: string[], + count: number, query: TasksQuery): AnyAction { + const action = { + type: TasksActionTypes.GET_TASKS_SUCCESS, + payload: { + previews, + array, + count, + query, + }, + }; + + return action; +} + +function getTasksFailed(error: any, query: TasksQuery): AnyAction { + const action = { + type: TasksActionTypes.GET_TASKS_FAILED, + payload: { + error, + query, + }, + }; + + return action; +} + +export function getTasksAsync(query: TasksQuery): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + dispatch(getTasks()); + + // We need remove all keys with null values from query + const filteredQuery = { ...query }; + for (const key in filteredQuery) { + if (filteredQuery[key] === null) { + delete filteredQuery[key]; + } + } + + let result = null; + try { + result = await cvat.tasks.get(filteredQuery); + } catch (error) { + dispatch(getTasksFailed(error, query)); + return; + } + + const array = Array.from(result); + const previews = []; + const promises = array + .map((task): string => (task as any).frames.preview()); + + dispatch( + getInferenceStatusAsync( + array.map( + (task: any): number => task.id, + ), + ), + ); + + for (const promise of promises) { + try { + // a tricky moment + // await is okay in loop in this case, there aren't any performance bottleneck + // because all server requests have been already sent in parallel + + // eslint-disable-next-line no-await-in-loop + previews.push(await promise); + } catch (error) { + previews.push(''); + } + } + + dispatch(getTasksSuccess(array, previews, result.count, query)); + }; +} + +function dumpAnnotation(task: any, dumper: any): AnyAction { + const action = { + type: TasksActionTypes.DUMP_ANNOTATIONS, + payload: { + task, + dumper, + }, + }; + + return action; +} + +function dumpAnnotationSuccess(task: any, dumper: any): AnyAction { + const action = { + type: TasksActionTypes.DUMP_ANNOTATIONS_SUCCESS, + payload: { + task, + dumper, + }, + }; + + return action; +} + +function dumpAnnotationFailed(task: any, dumper: any, error: any): AnyAction { + const action = { + type: TasksActionTypes.DUMP_ANNOTATIONS_FAILED, + payload: { + task, + dumper, + error, + }, + }; + + return action; +} + +export function dumpAnnotationsAsync(task: any, dumper: any): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + try { + dispatch(dumpAnnotation(task, dumper)); + const url = await task.annotations.dump(task.name, dumper); + const downloadAnchor = (window.document.getElementById('downloadAnchor') as HTMLAnchorElement); + downloadAnchor.href = url; + downloadAnchor.click(); + } catch (error) { + dispatch(dumpAnnotationFailed(task, dumper, error)); + return; + } + + dispatch(dumpAnnotationSuccess(task, dumper)); + }; +} + +function loadAnnotations(task: any, loader: any): AnyAction { + const action = { + type: TasksActionTypes.LOAD_ANNOTATIONS, + payload: { + task, + loader, + }, + }; + + return action; +} + +function loadAnnotationsSuccess(task: any): AnyAction { + const action = { + type: TasksActionTypes.LOAD_ANNOTATIONS_SUCCESS, + payload: { + task, + }, + }; + + return action; +} + +function loadAnnotationsFailed(task: any, error: any): AnyAction { + const action = { + type: TasksActionTypes.LOAD_ANNOTATIONS_FAILED, + payload: { + task, + error, + }, + }; + + return action; +} + +export function loadAnnotationsAsync(task: any, loader: any, file: File): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + try { + const store = getCVATStore(); + const state: CombinedState = store.getState(); + if (state.tasks.activities.loads[task.id]) { + throw Error('Only one loading of annotations for a task allowed at the same time'); + } + dispatch(loadAnnotations(task, loader)); + await task.annotations.upload(file, loader); + } catch (error) { + dispatch(loadAnnotationsFailed(task, error)); + return; + } + + dispatch(loadAnnotationsSuccess(task)); + }; +} + +function exportDataset(task: any, exporter: any): AnyAction { + const action = { + type: TasksActionTypes.EXPORT_DATASET, + payload: { + task, + exporter, + }, + }; + + return action; +} + +function exportDatasetSuccess(task: any, exporter: any): AnyAction { + const action = { + type: TasksActionTypes.EXPORT_DATASET_SUCCESS, + payload: { + task, + exporter, + }, + }; + + return action; +} + +function exportDatasetFailed(task: any, exporter: any, error: any): AnyAction { + const action = { + type: TasksActionTypes.EXPORT_DATASET_FAILED, + payload: { + task, + exporter, + error, + }, + }; + + return action; +} + +export function exportDatasetAsync(task: any, exporter: any): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + dispatch(exportDataset(task, exporter)); + + try { + const url = await task.annotations.exportDataset(exporter.tag); + const downloadAnchor = (window.document.getElementById('downloadAnchor') as HTMLAnchorElement); + downloadAnchor.href = url; + downloadAnchor.click(); + } catch (error) { + dispatch(exportDatasetFailed(task, exporter, error)); + } + + dispatch(exportDatasetSuccess(task, exporter)); + }; +} + +function deleteTask(taskID: number): AnyAction { + const action = { + type: TasksActionTypes.DELETE_TASK, + payload: { + taskID, + }, + }; + + return action; +} + +function deleteTaskSuccess(taskID: number): AnyAction { + const action = { + type: TasksActionTypes.DELETE_TASK_SUCCESS, + payload: { + taskID, + }, + }; + + return action; +} + +function deleteTaskFailed(taskID: number, error: any): AnyAction { + const action = { + type: TasksActionTypes.DELETE_TASK_FAILED, + payload: { + taskID, + error, + }, + }; + + return action; +} + +export function deleteTaskAsync(taskInstance: any): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + try { + dispatch(deleteTask(taskInstance.id)); + await taskInstance.delete(); + } catch (error) { + dispatch(deleteTaskFailed(taskInstance.id, error)); + return; + } + + dispatch(deleteTaskSuccess(taskInstance.id)); + }; +} + +function createTask(): AnyAction { + const action = { + type: TasksActionTypes.CREATE_TASK, + payload: {}, + }; + + return action; +} + +function createTaskSuccess(): AnyAction { + const action = { + type: TasksActionTypes.CREATE_TASK_SUCCESS, + payload: {}, + }; + + return action; +} + +function createTaskFailed(error: any): AnyAction { + const action = { + type: TasksActionTypes.CREATE_TASK_FAILED, + payload: { + error, + }, + }; + + return action; +} + +function createTaskUpdateStatus(status: string): AnyAction { + const action = { + type: TasksActionTypes.CREATE_TASK_STATUS_UPDATED, + payload: { + status, + }, + }; + + return action; +} + +export function createTaskAsync(data: any): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + const description: any = { + name: data.basic.name, + labels: data.labels, + z_order: data.advanced.zOrder, + image_quality: 70, + }; + + if (data.advanced.bugTracker) { + description.bug_tracker = data.advanced.bugTracker; + } + if (data.advanced.segmentSize) { + description.segment_size = data.advanced.segmentSize; + } + if (data.advanced.overlapSize) { + description.overlap = data.advanced.overlapSize; + } + if (data.advanced.startFrame) { + description.start_frame = data.advanced.startFrame; + } + if (data.advanced.stopFrame) { + description.stop_frame = data.advanced.stopFrame; + } + if (data.advanced.frameFilter) { + description.frame_filter = data.advanced.frameFilter; + } + if (data.advanced.imageQuality) { + description.image_quality = data.advanced.imageQuality; + } + + const taskInstance = new cvat.classes.Task(description); + taskInstance.clientFiles = data.files.local; + taskInstance.serverFiles = data.files.share; + taskInstance.remoteFiles = data.files.remote; + + if (data.advanced.repository) { + const [gitPlugin] = (await cvat.plugins.list()).filter( + (plugin: any): boolean => plugin.name === 'Git', + ); + + if (gitPlugin) { + gitPlugin.callbacks.onStatusChange = (status: string): void => { + dispatch(createTaskUpdateStatus(status)); + }; + gitPlugin.data.task = taskInstance; + gitPlugin.data.repos = data.advanced.repository; + gitPlugin.data.lfs = data.advanced.lfs; + } + } + + dispatch(createTask()); + try { + await taskInstance.save((status: string): void => { + dispatch(createTaskUpdateStatus(status)); + }); + dispatch(createTaskSuccess()); + } catch (error) { + dispatch(createTaskFailed(error)); + } + }; +} + +function updateTask(): AnyAction { + const action = { + type: TasksActionTypes.UPDATE_TASK, + payload: {}, + }; + + return action; +} + +function updateTaskSuccess(task: any): AnyAction { + const action = { + type: TasksActionTypes.UPDATE_TASK_SUCCESS, + payload: { + task, + }, + }; + + return action; +} + +function updateTaskFailed(error: any, task: any): AnyAction { + const action = { + type: TasksActionTypes.UPDATE_TASK_FAILED, + payload: { + error, + task, + }, + }; + + return action; +} + +export function updateTaskAsync(taskInstance: any): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + try { + dispatch(updateTask()); + await taskInstance.save(); + const [task] = await cvat.tasks.get({ id: taskInstance.id }); + dispatch(updateTaskSuccess(task)); + } catch (error) { + // try abort all changes + let task = null; + try { + [task] = await cvat.tasks.get({ id: taskInstance.id }); + } catch (fetchError) { + dispatch(updateTaskFailed(error, taskInstance)); + return; + } + + dispatch(updateTaskFailed(error, task)); + } + }; +} + +// a job is a part of a task, so for simplify we consider +// updating the job as updating a task +export function updateJobAsync(jobInstance: any): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + try { + dispatch(updateTask()); + await jobInstance.save(); + const [task] = await cvat.tasks.get({ id: jobInstance.task.id }); + dispatch(updateTaskSuccess(task)); + } catch (error) { + // try abort all changes + let task = null; + try { + [task] = await cvat.tasks.get({ id: jobInstance.task.id }); + } catch (fetchError) { + dispatch(updateTaskFailed(error, jobInstance.task)); + return; + } + + dispatch(updateTaskFailed(error, task)); + } + }; +} + +export function hideEmptyTasks(hideEmpty: boolean): AnyAction { + const action = { + type: TasksActionTypes.HIDE_EMPTY_TASKS, + payload: { + hideEmpty, + }, + }; + + return action; +} diff --git a/cvat-ui/src/actions/tasks-filter.actions.ts b/cvat-ui/src/actions/tasks-filter.actions.ts deleted file mode 100644 index 6a333b359a5..00000000000 --- a/cvat-ui/src/actions/tasks-filter.actions.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Dispatch } from 'redux'; - - -export const filterTasks = (queryParams: { search?: string, page?: number }) => (dispatch: Dispatch) => { - dispatch({ - type: 'FILTER_TASKS', - payload: queryParams, - }); -} diff --git a/cvat-ui/src/actions/tasks.actions.ts b/cvat-ui/src/actions/tasks.actions.ts deleted file mode 100644 index cedddd8b0e8..00000000000 --- a/cvat-ui/src/actions/tasks.actions.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { History } from 'history'; -import { Dispatch, ActionCreator } from 'redux'; - -import queryString from 'query-string'; - -import setQueryObject from '../utils/tasks-filter' - - -export const getTasks = () => (dispatch: Dispatch) => { - dispatch({ - type: 'GET_TASKS', - }); -} - -export const getTasksSuccess = (tasks: []) => (dispatch: Dispatch) => { - dispatch({ - type: 'GET_TASKS_SUCCESS', - payload: tasks, - }); -} - -export const getTasksError = (error: {}) => (dispatch: Dispatch) => { - dispatch({ - type: 'GET_TASKS_ERROR', - payload: error, - }); -} - -export const createTask = () => (dispatch: Dispatch) => { - dispatch({ - type: 'CREATE_TASK', - }); -} - -export const createTaskSuccess = () => (dispatch: Dispatch) => { - dispatch({ - type: 'CREATE_TASK_SUCCESS', - }); -} - -export const createTaskError = (error: {}) => (dispatch: Dispatch) => { - dispatch({ - type: 'CREATE_TASK_ERROR', - payload: error, - }); -} - -export const updateTask = () => (dispatch: Dispatch) => { - dispatch({ - type: 'UPDATE_TASK', - }); -} - -export const updateTaskSuccess = () => (dispatch: Dispatch) => { - dispatch({ - type: 'UPDATE_TASK_SUCCESS', - }); -} - -export const updateTaskError = (error: {}) => (dispatch: Dispatch) => { - dispatch({ - type: 'UPDATE_TASK_ERROR', - payload: error, - }); -} - -export const deleteTask = () => (dispatch: Dispatch) => { - dispatch({ - type: 'DELETE_TASK', - }); -} - -export const deleteTaskSuccess = () => (dispatch: Dispatch) => { - dispatch({ - type: 'DELETE_TASK_SUCCESS', - }); -} - -export const deleteTaskError = (error: {}) => (dispatch: Dispatch) => { - dispatch({ - type: 'DELETE_TASK_ERROR', - payload: error, - }); -} - -export const getTasksAsync = (queryObject = {}) => { - return (dispatch: ActionCreator) => { - dispatch(getTasks()); - - return (window as any).cvat.tasks.get(queryObject).then( - (tasks: any) => { - dispatch(getTasksSuccess(tasks)); - }, - (error: any) => { - dispatch(getTasksError(error)); - - throw error; - }, - ); - }; -} - -export const createTaskAsync = (task: any) => { - return (dispatch: ActionCreator) => { - dispatch(createTask()); - - return task.save().then( - (created: any) => { - dispatch(createTaskSuccess()); - - return dispatch(getTasksAsync()); - }, - (error: any) => { - dispatch(createTaskError(error)); - - throw error; - }, - ); - }; -} - -export const updateTaskAsync = (task: any) => { - return (dispatch: ActionCreator) => { - dispatch(updateTask()); - - return task.save().then( - (updated: any) => { - dispatch(updateTaskSuccess()); - - return dispatch(getTasksAsync()); - }, - (error: any) => { - dispatch(updateTaskError(error)); - - throw error; - }, - ); - }; -} - -export const deleteTaskAsync = (task: any, history: History) => { - return (dispatch: ActionCreator, getState: any) => { - dispatch(deleteTask()); - - return task.delete().then( - (deleted: any) => { - dispatch(deleteTaskSuccess()); - - const state = getState(); - - const queryObject = { - page: state.tasksFilter.currentPage, - search: state.tasksFilter.searchQuery, - } - - if (state.tasks.tasks.length === 1 && state.tasks.tasksCount !== 1) { - queryObject.page = queryObject.page - 1; - - history.push({ search: queryString.stringify(queryObject) }); - } else if (state.tasks.tasksCount === 1) { - return dispatch(getTasksAsync()); - } else { - const query = setQueryObject(queryObject); - - return dispatch(getTasksAsync(query)); - } - }, - (error: any) => { - dispatch(deleteTaskError(error)); - - throw error; - }, - ); - }; -} diff --git a/cvat-ui/src/actions/users-actions.ts b/cvat-ui/src/actions/users-actions.ts new file mode 100644 index 00000000000..48bf8dffe13 --- /dev/null +++ b/cvat-ui/src/actions/users-actions.ts @@ -0,0 +1,37 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; +import getCore from 'cvat-core'; + +const core = getCore(); + +export enum UsersActionTypes { + GET_USERS = 'GET_USERS', + GET_USERS_SUCCESS = 'GET_USERS_SUCCESS', + GET_USERS_FAILED = 'GET_USERS_FAILED', +} + +const usersActions = { + getUsers: () => createAction(UsersActionTypes.GET_USERS), + getUsersSuccess: (users: any[]) => createAction(UsersActionTypes.GET_USERS_SUCCESS, { users }), + getUsersFailed: (error: any) => createAction(UsersActionTypes.GET_USERS_FAILED, { error }), +}; + +export type UsersActions = ActionUnion; + +export function getUsersAsync(): ThunkAction { + return async (dispatch): Promise => { + dispatch(usersActions.getUsers()); + + try { + const users = await core.users.get(); + const wrappedUsers = users + .map((userData: any): any => new core.classes.User(userData)); + dispatch(usersActions.getUsersSuccess(wrappedUsers)); + } catch (error) { + dispatch(usersActions.getUsersFailed(error)); + } + }; +} diff --git a/cvat-ui/src/actions/users.actions.ts b/cvat-ui/src/actions/users.actions.ts deleted file mode 100644 index 93e50ac1805..00000000000 --- a/cvat-ui/src/actions/users.actions.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Dispatch, ActionCreator } from 'redux'; - - -export const getUsers = () => (dispatch: Dispatch) => { - dispatch({ - type: 'GET_USERS', - }); -} - -export const getUsersSuccess = (users: [], isCurrentUser: boolean) => (dispatch: Dispatch) => { - dispatch({ - type: 'GET_USERS_SUCCESS', - payload: users, - currentUser: isCurrentUser ? (users as any)[0] : isCurrentUser, - }); -} - -export const getUsersError = (error: {}) => (dispatch: Dispatch) => { - dispatch({ - type: 'GET_USERS_ERROR', - payload: error, - }); -} - -export const getUsersAsync = (filter = {}) => { - return (dispatch: ActionCreator) => { - dispatch(getUsers()); - - return (window as any).cvat.users.get(filter).then( - (users: any) => { - dispatch(getUsersSuccess(users, (filter as any).self)); - }, - (error: any) => { - dispatch(getUsersError(error)); - - throw error; - }, - ); - }; -} diff --git a/cvat-ui/src/assets/account-icon.svg b/cvat-ui/src/assets/account-icon.svg new file mode 100644 index 00000000000..e9bdc372a40 --- /dev/null +++ b/cvat-ui/src/assets/account-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cvat-ui/src/assets/back-jump-icon.svg b/cvat-ui/src/assets/back-jump-icon.svg new file mode 100644 index 00000000000..bf099f5d022 --- /dev/null +++ b/cvat-ui/src/assets/back-jump-icon.svg @@ -0,0 +1,5 @@ + diff --git a/cvat-ui/src/assets/background-icon.svg b/cvat-ui/src/assets/background-icon.svg new file mode 100644 index 00000000000..8765d6d5cfa --- /dev/null +++ b/cvat-ui/src/assets/background-icon.svg @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/cvat-ui/src/assets/cursor-icon.svg b/cvat-ui/src/assets/cursor-icon.svg new file mode 100644 index 00000000000..6c325c337c3 --- /dev/null +++ b/cvat-ui/src/assets/cursor-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cvat-ui/src/assets/cvat-logo.svg b/cvat-ui/src/assets/cvat-logo.svg new file mode 100644 index 00000000000..523c87bc403 --- /dev/null +++ b/cvat-ui/src/assets/cvat-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cvat-ui/src/assets/empty-tasks-icon.svg b/cvat-ui/src/assets/empty-tasks-icon.svg new file mode 100644 index 00000000000..83cd7acdf7b --- /dev/null +++ b/cvat-ui/src/assets/empty-tasks-icon.svg @@ -0,0 +1,11 @@ + + + + 8EAC7454-72F0-4344-ACCC-8688B016EA51 + Created with sketchtool. + + + + + + \ No newline at end of file diff --git a/cvat-ui/src/assets/exit-fullscreen-icon.svg b/cvat-ui/src/assets/exit-fullscreen-icon.svg new file mode 100644 index 00000000000..ac7cc5eca5c --- /dev/null +++ b/cvat-ui/src/assets/exit-fullscreen-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cvat-ui/src/assets/first-icon.svg b/cvat-ui/src/assets/first-icon.svg new file mode 100644 index 00000000000..46eabc10674 --- /dev/null +++ b/cvat-ui/src/assets/first-icon.svg @@ -0,0 +1,5 @@ + diff --git a/cvat-ui/src/assets/fit-to-window-icon.svg b/cvat-ui/src/assets/fit-to-window-icon.svg new file mode 100644 index 00000000000..758b7568839 --- /dev/null +++ b/cvat-ui/src/assets/fit-to-window-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cvat-ui/src/assets/foreground-icon.svg b/cvat-ui/src/assets/foreground-icon.svg new file mode 100644 index 00000000000..e54d5337f85 --- /dev/null +++ b/cvat-ui/src/assets/foreground-icon.svg @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/cvat-ui/src/assets/forward-jump-icon.svg b/cvat-ui/src/assets/forward-jump-icon.svg new file mode 100644 index 00000000000..e6e0f18e798 --- /dev/null +++ b/cvat-ui/src/assets/forward-jump-icon.svg @@ -0,0 +1,5 @@ + diff --git a/cvat-ui/src/assets/fullscreen-icon.svg b/cvat-ui/src/assets/fullscreen-icon.svg new file mode 100644 index 00000000000..a5290f043fc --- /dev/null +++ b/cvat-ui/src/assets/fullscreen-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cvat-ui/src/assets/group-icon.svg b/cvat-ui/src/assets/group-icon.svg new file mode 100644 index 00000000000..72607b0ba08 --- /dev/null +++ b/cvat-ui/src/assets/group-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cvat-ui/src/assets/info-icon.svg b/cvat-ui/src/assets/info-icon.svg new file mode 100644 index 00000000000..f2f7b1dd10e --- /dev/null +++ b/cvat-ui/src/assets/info-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cvat-ui/src/assets/last-icon.svg b/cvat-ui/src/assets/last-icon.svg new file mode 100644 index 00000000000..710b62713da --- /dev/null +++ b/cvat-ui/src/assets/last-icon.svg @@ -0,0 +1,5 @@ + diff --git a/cvat-ui/src/assets/main-menu-icon.svg b/cvat-ui/src/assets/main-menu-icon.svg new file mode 100644 index 00000000000..e712d571f74 --- /dev/null +++ b/cvat-ui/src/assets/main-menu-icon.svg @@ -0,0 +1 @@ + diff --git a/cvat-ui/src/assets/menu-icon.svg b/cvat-ui/src/assets/menu-icon.svg new file mode 100644 index 00000000000..f6b0c75c28e --- /dev/null +++ b/cvat-ui/src/assets/menu-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cvat-ui/src/assets/merge-icon.svg b/cvat-ui/src/assets/merge-icon.svg new file mode 100644 index 00000000000..2a7ffd51a1f --- /dev/null +++ b/cvat-ui/src/assets/merge-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cvat-ui/src/assets/move-icon.svg b/cvat-ui/src/assets/move-icon.svg new file mode 100644 index 00000000000..e6c169b41e8 --- /dev/null +++ b/cvat-ui/src/assets/move-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cvat-ui/src/assets/next-icon.svg b/cvat-ui/src/assets/next-icon.svg new file mode 100644 index 00000000000..27af75f8772 --- /dev/null +++ b/cvat-ui/src/assets/next-icon.svg @@ -0,0 +1,5 @@ + diff --git a/cvat-ui/src/assets/object-filter-icon.svg b/cvat-ui/src/assets/object-filter-icon.svg new file mode 100644 index 00000000000..b62e9623bbe --- /dev/null +++ b/cvat-ui/src/assets/object-filter-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cvat-ui/src/assets/object-hide-icon.svg b/cvat-ui/src/assets/object-hide-icon.svg new file mode 100644 index 00000000000..7f5c73d15ac --- /dev/null +++ b/cvat-ui/src/assets/object-hide-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cvat-ui/src/assets/object-inside-icon.svg b/cvat-ui/src/assets/object-inside-icon.svg new file mode 100644 index 00000000000..f7e1236ebe4 --- /dev/null +++ b/cvat-ui/src/assets/object-inside-icon.svg @@ -0,0 +1,7 @@ + diff --git a/cvat-ui/src/assets/object-occlude-icon.svg b/cvat-ui/src/assets/object-occlude-icon.svg new file mode 100644 index 00000000000..1c36005de2c --- /dev/null +++ b/cvat-ui/src/assets/object-occlude-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cvat-ui/src/assets/object-outside-icon.svg b/cvat-ui/src/assets/object-outside-icon.svg new file mode 100644 index 00000000000..dae8de9fa49 --- /dev/null +++ b/cvat-ui/src/assets/object-outside-icon.svg @@ -0,0 +1,8 @@ + diff --git a/cvat-ui/src/assets/pause-icon.svg b/cvat-ui/src/assets/pause-icon.svg new file mode 100644 index 00000000000..255d8a9c7f9 --- /dev/null +++ b/cvat-ui/src/assets/pause-icon.svg @@ -0,0 +1,8 @@ + diff --git a/cvat-ui/src/assets/play-icon.svg b/cvat-ui/src/assets/play-icon.svg new file mode 100644 index 00000000000..fcd0b868b7f --- /dev/null +++ b/cvat-ui/src/assets/play-icon.svg @@ -0,0 +1,5 @@ + diff --git a/cvat-ui/src/assets/plus-icon.svg b/cvat-ui/src/assets/plus-icon.svg new file mode 100644 index 00000000000..0ef42441f7b --- /dev/null +++ b/cvat-ui/src/assets/plus-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cvat-ui/src/assets/point-icon.svg b/cvat-ui/src/assets/point-icon.svg new file mode 100644 index 00000000000..de8b5b35396 --- /dev/null +++ b/cvat-ui/src/assets/point-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cvat-ui/src/assets/polygon-icon.svg b/cvat-ui/src/assets/polygon-icon.svg new file mode 100644 index 00000000000..300c55e7ead --- /dev/null +++ b/cvat-ui/src/assets/polygon-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cvat-ui/src/assets/polyline-icon.svg b/cvat-ui/src/assets/polyline-icon.svg new file mode 100644 index 00000000000..d85553a797d --- /dev/null +++ b/cvat-ui/src/assets/polyline-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cvat-ui/src/assets/previous-icon.svg b/cvat-ui/src/assets/previous-icon.svg new file mode 100644 index 00000000000..3143484da1b --- /dev/null +++ b/cvat-ui/src/assets/previous-icon.svg @@ -0,0 +1,5 @@ + diff --git a/cvat-ui/src/assets/rectangle-icon.svg b/cvat-ui/src/assets/rectangle-icon.svg new file mode 100644 index 00000000000..a568531af2f --- /dev/null +++ b/cvat-ui/src/assets/rectangle-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cvat-ui/src/assets/redo-icon.svg b/cvat-ui/src/assets/redo-icon.svg new file mode 100644 index 00000000000..61cf325add9 --- /dev/null +++ b/cvat-ui/src/assets/redo-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cvat-ui/src/assets/rotate-icon.svg b/cvat-ui/src/assets/rotate-icon.svg new file mode 100644 index 00000000000..51966ac318e --- /dev/null +++ b/cvat-ui/src/assets/rotate-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cvat-ui/src/assets/save-icon.svg b/cvat-ui/src/assets/save-icon.svg new file mode 100644 index 00000000000..a87e524ea67 --- /dev/null +++ b/cvat-ui/src/assets/save-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cvat-ui/src/assets/settings-icon.svg b/cvat-ui/src/assets/settings-icon.svg new file mode 100644 index 00000000000..2b58c359d6e --- /dev/null +++ b/cvat-ui/src/assets/settings-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cvat-ui/src/assets/show-sidebar-icon.svg b/cvat-ui/src/assets/show-sidebar-icon.svg new file mode 100644 index 00000000000..c982adb3b6d --- /dev/null +++ b/cvat-ui/src/assets/show-sidebar-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cvat-ui/src/assets/side-icon-object-lock.svg b/cvat-ui/src/assets/side-icon-object-lock.svg new file mode 100644 index 00000000000..42d3d4af0f6 --- /dev/null +++ b/cvat-ui/src/assets/side-icon-object-lock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cvat-ui/src/assets/split-icon.svg b/cvat-ui/src/assets/split-icon.svg new file mode 100644 index 00000000000..7f77d16b6f9 --- /dev/null +++ b/cvat-ui/src/assets/split-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cvat-ui/src/assets/tag-icon.svg b/cvat-ui/src/assets/tag-icon.svg new file mode 100644 index 00000000000..956ae35b2d3 --- /dev/null +++ b/cvat-ui/src/assets/tag-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cvat-ui/src/assets/undo-icon.svg b/cvat-ui/src/assets/undo-icon.svg new file mode 100644 index 00000000000..d193a31408c --- /dev/null +++ b/cvat-ui/src/assets/undo-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cvat-ui/src/assets/zoom-icon.svg b/cvat-ui/src/assets/zoom-icon.svg new file mode 100644 index 00000000000..28e1af437c3 --- /dev/null +++ b/cvat-ui/src/assets/zoom-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cvat-ui/src/base.scss b/cvat-ui/src/base.scss new file mode 100644 index 00000000000..6f545305107 --- /dev/null +++ b/cvat-ui/src/base.scss @@ -0,0 +1,27 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +$header-color: #D8D8D8; +$text-color: #303030; +$hover-menu-color: rgba(24,144,255,0.05); +$completed-progress-color: #61C200; +$inprogress-progress-color: #1890FF; +$pending-progress-color: #C1C1C1; +$border-color-1: #c3c3c3; +$border-color-2: #d9d9d9; +$border-color-3: #242424; +$border-color-hover: #40a9ff; +$background-color-1: white; +$background-color-2: #F1F1F1; +$transparent-color: rgba(0, 0, 0, 0); +$player-slider-color: #979797; +$player-buttons-color: #242424; +$danger-icon-color: #FF4136; +$info-icon-color: #0074D9; +$objects-bar-tabs-color: #BEBEBE; +$objects-bar-icons-color: #242424; // #6E6E6E +$active-object-item-background-color: #D8ECFF; +$slider-color: #1890FF; + +$monospaced-fonts-stack: Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace; diff --git a/cvat-ui/src/components/actions-menu/actions-menu.tsx b/cvat-ui/src/components/actions-menu/actions-menu.tsx new file mode 100644 index 00000000000..d38a069f665 --- /dev/null +++ b/cvat-ui/src/components/actions-menu/actions-menu.tsx @@ -0,0 +1,162 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import './styles.scss'; +import React from 'react'; + +import { + Menu, + Modal, +} from 'antd'; + +import { ClickParam } from 'antd/lib/menu/index'; + +import DumpSubmenu from './dump-submenu'; +import LoadSubmenu from './load-submenu'; +import ExportSubmenu from './export-submenu'; + +interface Props { + taskID: number; + taskMode: string; + bugTracker: string; + + loaders: string[]; + dumpers: string[]; + exporters: string[]; + loadActivity: string | null; + dumpActivities: string[] | null; + exportActivities: string[] | null; + + installedTFAnnotation: boolean; + installedTFSegmentation: boolean; + installedAutoAnnotation: boolean; + inferenceIsActive: boolean; + + onClickMenu: (params: ClickParam, file?: File) => void; +} + +export enum Actions { + DUMP_TASK_ANNO = 'dump_task_anno', + LOAD_TASK_ANNO = 'load_task_anno', + EXPORT_TASK_DATASET = 'export_task_dataset', + DELETE_TASK = 'delete_task', + RUN_AUTO_ANNOTATION = 'run_auto_annotation', + OPEN_BUG_TRACKER = 'open_bug_tracker', +} + +export default function ActionsMenuComponent(props: Props): JSX.Element { + const { + taskID, + taskMode, + bugTracker, + + installedAutoAnnotation, + installedTFAnnotation, + installedTFSegmentation, + inferenceIsActive, + + dumpers, + loaders, + exporters, + onClickMenu, + dumpActivities, + exportActivities, + loadActivity, + } = props; + + const renderModelRunner = installedAutoAnnotation + || installedTFAnnotation || installedTFSegmentation; + + let latestParams: ClickParam | null = null; + function onClickMenuWrapper(params: ClickParam | null, file?: File): void { + const copyParams = params || latestParams; + if (!copyParams) { + return; + } + latestParams = copyParams; + + if (copyParams.keyPath.length === 2) { + const [, action] = copyParams.keyPath; + if (action === Actions.LOAD_TASK_ANNO) { + if (file) { + Modal.confirm({ + title: 'Current annotation will be lost', + content: 'You are going to upload new annotations to this task. Continue?', + onOk: () => { + onClickMenu(copyParams, file); + }, + okButtonProps: { + type: 'danger', + }, + okText: 'Update', + }); + } + } else { + onClickMenu(copyParams); + } + } else if (copyParams.key === Actions.DELETE_TASK) { + Modal.confirm({ + title: `The task ${taskID} will be deleted`, + content: 'All related data (images, annotations) will be lost. Continue?', + onOk: () => { + onClickMenu(copyParams); + }, + okButtonProps: { + type: 'danger', + }, + okText: 'Delete', + }); + } else { + onClickMenu(copyParams); + } + } + + return ( + + { + DumpSubmenu({ + taskMode, + dumpers, + dumpActivities, + menuKey: Actions.DUMP_TASK_ANNO, + }) + } + { + LoadSubmenu({ + loaders, + loadActivity, + onFileUpload: (file: File): void => { + onClickMenuWrapper(null, file); + }, + menuKey: Actions.LOAD_TASK_ANNO, + }) + } + { + ExportSubmenu({ + exporters, + exportActivities, + menuKey: Actions.EXPORT_TASK_DATASET, + }) + } + {!!bugTracker && Open bug tracker} + { + renderModelRunner + && ( + + Automatic annotation + + ) + } +
+ Delete +
+ ); +} diff --git a/cvat-ui/src/components/actions-menu/dump-submenu.tsx b/cvat-ui/src/components/actions-menu/dump-submenu.tsx new file mode 100644 index 00000000000..6a13e3f4aae --- /dev/null +++ b/cvat-ui/src/components/actions-menu/dump-submenu.tsx @@ -0,0 +1,55 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Menu, + Icon, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; + +function isDefaultFormat(dumperName: string, taskMode: string): boolean { + return (dumperName === 'CVAT XML 1.1 for videos' && taskMode === 'interpolation') + || (dumperName === 'CVAT XML 1.1 for images' && taskMode === 'annotation'); +} + +interface Props { + taskMode: string; + menuKey: string; + dumpers: string[]; + dumpActivities: string[] | null; +} + +export default function DumpSubmenu(props: Props): JSX.Element { + const { + taskMode, + menuKey, + dumpers, + dumpActivities, + } = props; + + return ( + + { + dumpers.map((dumper: string): JSX.Element => { + const pending = (dumpActivities || []).includes(dumper); + const isDefault = isDefaultFormat(dumper, taskMode); + return ( + + + {dumper} + {pending && } + + ); + }) + } + + ); +} diff --git a/cvat-ui/src/components/actions-menu/export-submenu.tsx b/cvat-ui/src/components/actions-menu/export-submenu.tsx new file mode 100644 index 00000000000..476d5aa1144 --- /dev/null +++ b/cvat-ui/src/components/actions-menu/export-submenu.tsx @@ -0,0 +1,47 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Menu, + Icon, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; + +interface Props { + menuKey: string; + exporters: string[]; + exportActivities: string[] | null; +} + +export default function ExportSubmenu(props: Props): JSX.Element { + const { + menuKey, + exporters, + exportActivities, + } = props; + + return ( + + { + exporters.map((exporter: string): JSX.Element => { + const pending = (exportActivities || []).includes(exporter); + return ( + + + {exporter} + {pending && } + + ); + }) + } + + ); +} diff --git a/cvat-ui/src/components/actions-menu/load-submenu.tsx b/cvat-ui/src/components/actions-menu/load-submenu.tsx new file mode 100644 index 00000000000..12211c0d788 --- /dev/null +++ b/cvat-ui/src/components/actions-menu/load-submenu.tsx @@ -0,0 +1,65 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Menu, + Icon, + Upload, + Button, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; + +interface Props { + menuKey: string; + loaders: string[]; + loadActivity: string | null; + onFileUpload(file: File): void; +} + +export default function LoadSubmenu(props: Props): JSX.Element { + const { + menuKey, + loaders, + loadActivity, + onFileUpload, + } = props; + + return ( + + { + loaders.map((_loader: string): JSX.Element => { + const [loader, accept] = _loader.split('::'); + const pending = loadActivity === loader; + return ( + + { + onFileUpload(file); + return false; + }} + > + + + + + ); + }) + } + + ); +} diff --git a/cvat-ui/src/components/actions-menu/styles.scss b/cvat-ui/src/components/actions-menu/styles.scss new file mode 100644 index 00000000000..1c70d839667 --- /dev/null +++ b/cvat-ui/src/components/actions-menu/styles.scss @@ -0,0 +1,50 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +@import '../../base.scss'; + +.ant-menu.cvat-actions-menu { + box-shadow: 0 0 17px rgba(0,0,0,0.2); + + > li:hover { + background-color: $hover-menu-color; + } + + .ant-menu-submenu-title { + margin: 0px; + width: 13em; + } +} + +.cvat-menu-load-submenu-item, +.cvat-menu-dump-submenu-item, +.cvat-menu-export-submenu-item { + > i { + color: $info-icon-color; + } + + &:hover { + background-color: $hover-menu-color; + } +} + +.ant-menu-item.cvat-menu-load-submenu-item { + margin: 0px; + padding: 0px; + + > span > .ant-upload { + width: 100%; + height: 100%; + + > span > button { + width: 100%; + height: 100%; + text-align: left; + } + } +} + +.cvat-menu-icon { + transform: scale(0.5); +} diff --git a/cvat-ui/src/components/annotation-page/annotation-page.tsx b/cvat-ui/src/components/annotation-page/annotation-page.tsx new file mode 100644 index 00000000000..3ea1c87c8cd --- /dev/null +++ b/cvat-ui/src/components/annotation-page/annotation-page.tsx @@ -0,0 +1,59 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import './styles.scss'; +import React from 'react'; + +import { + Layout, + Spin, + Result, +} from 'antd'; + +import AnnotationTopBarContainer from 'containers/annotation-page/top-bar/top-bar'; +import StatisticsModalContainer from 'containers/annotation-page/top-bar/statistics-modal'; +import StandardWorkspaceComponent from './standard-workspace/standard-workspace'; + +interface Props { + job: any | null | undefined; + fetching: boolean; + getJob(): void; +} + + +export default function AnnotationPageComponent(props: Props): JSX.Element { + const { + job, + fetching, + getJob, + } = props; + + + if (job === null) { + if (!fetching) { + getJob(); + } + + return ; + } + + if (typeof (job) === 'undefined') { + return ( + + ); + } + + return ( + + + + + + ); +} diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-context-menu.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-context-menu.tsx new file mode 100644 index 00000000000..3cbd316d99d --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-context-menu.tsx @@ -0,0 +1,35 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import ReactDOM from 'react-dom'; + +import ObjectItemContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/object-item'; + +interface Props { + activatedStateID: number | null; + visible: boolean; + left: number; + top: number; +} + +export default function CanvasContextMenu(props: Props): JSX.Element | null { + const { + activatedStateID, + visible, + left, + top, + } = props; + + if (!visible || activatedStateID === null) { + return null; + } + + return ReactDOM.createPortal( +
+ +
, + window.document.body, + ); +} diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx new file mode 100644 index 00000000000..1b5d750820d --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -0,0 +1,715 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import { GlobalHotKeys, KeyMap } from 'react-hotkeys'; + +import { + Layout, + Slider, + Icon, + Tooltip, +} from 'antd'; + +import { SliderValue } from 'antd/lib//slider'; +import { ColorBy, GridColor, ObjectType } from 'reducers/interfaces'; +import { Canvas } from 'cvat-canvas'; +import getCore from 'cvat-core'; + +const cvat = getCore(); + +const MAX_DISTANCE_TO_OPEN_SHAPE = 50; + +interface Props { + sidebarCollapsed: boolean; + canvasInstance: Canvas; + jobInstance: any; + activatedStateID: number | null; + selectedStatesID: number[]; + annotations: any[]; + frameData: any; + frameAngle: number; + frame: number; + opacity: number; + colorBy: ColorBy; + selectedOpacity: number; + blackBorders: boolean; + grid: boolean; + gridSize: number; + gridColor: GridColor; + gridOpacity: number; + activeLabelID: number; + activeObjectType: ObjectType; + curZLayer: number; + minZLayer: number; + maxZLayer: number; + brightnessLevel: number; + contrastLevel: number; + saturationLevel: number; + resetZoom: boolean; + onSetupCanvas: () => void; + onDragCanvas: (enabled: boolean) => void; + onZoomCanvas: (enabled: boolean) => void; + onMergeObjects: (enabled: boolean) => void; + onGroupObjects: (enabled: boolean) => void; + onSplitTrack: (enabled: boolean) => void; + onEditShape: (enabled: boolean) => void; + onShapeDrawn: () => void; + onResetCanvas: () => void; + onUpdateAnnotations(sessionInstance: any, frame: number, states: any[]): void; + onCreateAnnotations(sessionInstance: any, frame: number, states: any[]): void; + onMergeAnnotations(sessionInstance: any, frame: number, states: any[]): void; + onGroupAnnotations(sessionInstance: any, frame: number, states: any[]): void; + onSplitAnnotations(sessionInstance: any, frame: number, state: any): void; + onActivateObject(activatedStateID: number | null): void; + onSelectObjects(selectedStatesID: number[]): void; + onUpdateContextMenu(visible: boolean, left: number, top: number): void; + onAddZLayer(): void; + onSwitchZLayer(cur: number): void; + onChangeBrightnessLevel(level: number): void; + onChangeContrastLevel(level: number): void; + onChangeSaturationLevel(level: number): void; + onChangeGridOpacity(opacity: number): void; + onChangeGridColor(color: GridColor): void; + onSwitchGrid(enabled: boolean): void; +} + +export default class CanvasWrapperComponent extends React.PureComponent { + public componentDidMount(): void { + const { + canvasInstance, + curZLayer, + } = this.props; + + // It's awful approach from the point of view React + // But we do not have another way because cvat-canvas returns regular DOM element + const [wrapper] = window.document + .getElementsByClassName('cvat-canvas-container'); + wrapper.appendChild(canvasInstance.html()); + + canvasInstance.setZLayer(curZLayer); + this.initialSetup(); + this.updateCanvas(); + } + + public componentDidUpdate(prevProps: Props): void { + const { + opacity, + colorBy, + selectedOpacity, + blackBorders, + frameData, + frameAngle, + annotations, + canvasInstance, + sidebarCollapsed, + activatedStateID, + curZLayer, + resetZoom, + grid, + gridOpacity, + gridColor, + brightnessLevel, + contrastLevel, + saturationLevel, + } = this.props; + + if (prevProps.sidebarCollapsed !== sidebarCollapsed) { + const [sidebar] = window.document.getElementsByClassName('cvat-objects-sidebar'); + if (sidebar) { + sidebar.addEventListener('transitionend', () => { + canvasInstance.fitCanvas(); + }, { once: true }); + } + } + + if (prevProps.activatedStateID !== null + && prevProps.activatedStateID !== activatedStateID) { + canvasInstance.activate(null); + } + + if (activatedStateID) { + const el = window.document.getElementById(`cvat_canvas_shape_${prevProps.activatedStateID}`); + if (el) { + (el as any).instance.fill({ opacity: opacity / 100 }); + } + } + + if (gridOpacity !== prevProps.gridOpacity + || gridColor !== prevProps.gridColor + || grid !== prevProps.grid) { + const gridElement = window.document.getElementById('cvat_canvas_grid'); + const gridPattern = window.document.getElementById('cvat_canvas_grid_pattern'); + if (gridElement) { + gridElement.style.display = grid ? 'block' : 'none'; + } + if (gridPattern) { + gridPattern.style.stroke = gridColor.toLowerCase(); + gridPattern.style.opacity = `${gridOpacity / 100}`; + } + } + + if (brightnessLevel !== prevProps.brightnessLevel + || contrastLevel !== prevProps.contrastLevel + || saturationLevel !== prevProps.saturationLevel) { + const backgroundElement = window.document.getElementById('cvat_canvas_background'); + if (backgroundElement) { + backgroundElement.style.filter = `brightness(${brightnessLevel / 100})` + + `contrast(${contrastLevel / 100})` + + `saturate(${saturationLevel / 100})`; + } + } + + if (prevProps.annotations !== annotations || prevProps.frameData !== frameData) { + this.updateCanvas(); + } + + if (prevProps.frame !== frameData.number && resetZoom) { + canvasInstance.html().addEventListener('canvas.setup', () => { + canvasInstance.fit(); + }, { once: true }); + } + + if (prevProps.opacity !== opacity || prevProps.blackBorders !== blackBorders + || prevProps.selectedOpacity !== selectedOpacity || prevProps.colorBy !== colorBy) { + this.updateShapesView(); + } + + if (prevProps.curZLayer !== curZLayer) { + canvasInstance.setZLayer(curZLayer); + } + + if (prevProps.frameAngle !== frameAngle) { + canvasInstance.rotate(frameAngle); + } + + this.activateOnCanvas(); + } + + public componentWillUnmount(): void { + window.removeEventListener('resize', this.fitCanvas); + } + + private onShapeDrawn(event: any): void { + const { + jobInstance, + activeLabelID, + activeObjectType, + frame, + onShapeDrawn, + onCreateAnnotations, + } = this.props; + + if (!event.detail.continue) { + onShapeDrawn(); + } + + const { state } = event.detail; + if (!state.objectType) { + state.objectType = activeObjectType; + } + + if (!state.label) { + [state.label] = jobInstance.task.labels + .filter((label: any) => label.id === activeLabelID); + } + + if (typeof (state.occluded) === 'undefined') { + state.occluded = false; + } + + state.frame = frame; + const objectState = new cvat.classes.ObjectState(state); + onCreateAnnotations(jobInstance, frame, [objectState]); + } + + private onShapeEdited(event: any): void { + const { + jobInstance, + frame, + onEditShape, + onUpdateAnnotations, + } = this.props; + + onEditShape(false); + + const { + state, + points, + } = event.detail; + state.points = points; + onUpdateAnnotations(jobInstance, frame, [state]); + } + + private onObjectsMerged(event: any): void { + const { + jobInstance, + frame, + onMergeAnnotations, + onMergeObjects, + } = this.props; + + onMergeObjects(false); + + const { states } = event.detail; + onMergeAnnotations(jobInstance, frame, states); + } + + private onObjectsGroupped(event: any): void { + const { + jobInstance, + frame, + onGroupAnnotations, + onGroupObjects, + } = this.props; + + onGroupObjects(false); + + const { states } = event.detail; + onGroupAnnotations(jobInstance, frame, states); + } + + private onTrackSplitted(event: any): void { + const { + jobInstance, + frame, + onSplitAnnotations, + onSplitTrack, + } = this.props; + + onSplitTrack(false); + + const { state } = event.detail; + onSplitAnnotations(jobInstance, frame, state); + } + + private fitCanvas = (): void => { + const { canvasInstance } = this.props; + canvasInstance.fitCanvas(); + }; + + private activateOnCanvas(): void { + const { + activatedStateID, + canvasInstance, + selectedOpacity, + } = this.props; + + if (activatedStateID !== null) { + canvasInstance.activate(activatedStateID); + const el = window.document.getElementById(`cvat_canvas_shape_${activatedStateID}`); + if (el) { + (el as any as SVGElement).setAttribute('fill-opacity', `${selectedOpacity / 100}`); + } + } + } + + private updateShapesView(): void { + const { + annotations, + opacity, + colorBy, + blackBorders, + } = this.props; + + for (const state of annotations) { + let shapeColor = ''; + if (colorBy === ColorBy.INSTANCE) { + shapeColor = state.color; + } else if (colorBy === ColorBy.GROUP) { + shapeColor = state.group.color; + } else if (colorBy === ColorBy.LABEL) { + shapeColor = state.label.color; + } + + // TODO: In this approach CVAT-UI know details of implementations CVAT-CANVAS (svg.js) + const shapeView = window.document.getElementById(`cvat_canvas_shape_${state.clientID}`); + if (shapeView) { + const handler = (shapeView as any).instance.remember('_selectHandler'); + if (handler && handler.nested) { + handler.nested.fill({ color: shapeColor }); + } + (shapeView as any).instance.fill({ color: shapeColor, opacity: opacity / 100 }); + (shapeView as any).instance.stroke({ color: blackBorders ? 'black' : shapeColor }); + } + } + } + + private updateCanvas(): void { + const { + annotations, + frameData, + frameAngle, + canvasInstance, + } = this.props; + + if (frameData !== null) { + canvasInstance.setup(frameData, annotations); + canvasInstance.rotate(frameAngle); + } + } + + private initialSetup(): void { + const { + grid, + gridSize, + gridColor, + gridOpacity, + canvasInstance, + jobInstance, + onSetupCanvas, + onDragCanvas, + onZoomCanvas, + onResetCanvas, + onActivateObject, + onUpdateContextMenu, + onEditShape, + brightnessLevel, + contrastLevel, + saturationLevel, + } = this.props; + + // Size + window.addEventListener('resize', this.fitCanvas); + this.fitCanvas(); + + // Grid + const gridElement = window.document.getElementById('cvat_canvas_grid'); + const gridPattern = window.document.getElementById('cvat_canvas_grid_pattern'); + if (gridElement) { + gridElement.style.display = grid ? 'block' : 'none'; + } + if (gridPattern) { + gridPattern.style.stroke = gridColor.toLowerCase(); + gridPattern.style.opacity = `${gridOpacity / 100}`; + } + canvasInstance.grid(gridSize, gridSize); + + // Filters + const backgroundElement = window.document.getElementById('cvat_canvas_background'); + if (backgroundElement) { + backgroundElement.style.filter = `brightness(${brightnessLevel / 100})` + + `contrast(${contrastLevel / 100})` + + `saturate(${saturationLevel / 100})`; + } + + // Events + canvasInstance.html().addEventListener('mousedown', (e: MouseEvent): void => { + const { + activatedStateID, + } = this.props; + + if ((e.target as HTMLElement).tagName === 'svg' && activatedStateID !== null) { + onActivateObject(null); + } + }); + + canvasInstance.html().addEventListener('click', (): void => { + if (document.activeElement) { + (document.activeElement as HTMLElement).blur(); + } + }); + + canvasInstance.html().addEventListener('contextmenu', (e: MouseEvent): void => { + const { + activatedStateID, + } = this.props; + + onUpdateContextMenu(activatedStateID !== null, e.clientX, e.clientY); + }); + + canvasInstance.html().addEventListener('canvas.editstart', (): void => { + onActivateObject(null); + onEditShape(true); + }); + + canvasInstance.html().addEventListener('canvas.setup', (): void => { + onSetupCanvas(); + this.updateShapesView(); + this.activateOnCanvas(); + }); + + canvasInstance.html().addEventListener('canvas.setup', () => { + canvasInstance.fit(); + }, { once: true }); + + canvasInstance.html().addEventListener('canvas.canceled', () => { + onResetCanvas(); + }); + + canvasInstance.html().addEventListener('canvas.dragstart', () => { + onDragCanvas(true); + }); + + canvasInstance.html().addEventListener('canvas.dragstop', () => { + onDragCanvas(false); + }); + + canvasInstance.html().addEventListener('canvas.zoomstart', () => { + onZoomCanvas(true); + }); + + canvasInstance.html().addEventListener('canvas.zoomstop', () => { + onZoomCanvas(false); + }); + + canvasInstance.html().addEventListener('canvas.clicked', (e: any) => { + const { clientID } = e.detail.state; + const sidebarItem = window.document + .getElementById(`cvat-objects-sidebar-state-item-${clientID}`); + if (sidebarItem) { + sidebarItem.scrollIntoView(); + } + }); + + canvasInstance.html().addEventListener('canvas.deactivated', (e: any): void => { + const { activatedStateID } = this.props; + const { state } = e.detail; + + // when we activate element, canvas deactivates the previous + // and triggers this event + // in this case we do not need to update our state + if (state.clientID === activatedStateID) { + onActivateObject(null); + } + }); + + canvasInstance.html().addEventListener('canvas.moved', async (event: any): Promise => { + const { activatedStateID } = this.props; + const result = await jobInstance.annotations.select( + event.detail.states, + event.detail.x, + event.detail.y, + ); + + if (result && result.state) { + if (result.state.shapeType === 'polyline' || result.state.shapeType === 'points') { + if (result.distance > MAX_DISTANCE_TO_OPEN_SHAPE) { + return; + } + } + + if (activatedStateID !== result.state.clientID) { + onActivateObject(result.state.clientID); + } + } + }); + + canvasInstance.html().addEventListener('canvas.find', async (e: any) => { + const result = await jobInstance.annotations + .select(e.detail.states, e.detail.x, e.detail.y); + + if (result && result.state) { + if (result.state.shapeType === 'polyline' || result.state.shapeType === 'points') { + if (result.distance > MAX_DISTANCE_TO_OPEN_SHAPE) { + return; + } + } + + canvasInstance.select(result.state); + } + }); + + canvasInstance.html().addEventListener('canvas.edited', this.onShapeEdited.bind(this)); + canvasInstance.html().addEventListener('canvas.drawn', this.onShapeDrawn.bind(this)); + canvasInstance.html().addEventListener('canvas.merged', this.onObjectsMerged.bind(this)); + canvasInstance.html().addEventListener('canvas.groupped', this.onObjectsGroupped.bind(this)); + canvasInstance.html().addEventListener('canvas.splitted', this.onTrackSplitted.bind(this)); + } + + public render(): JSX.Element { + const { + maxZLayer, + curZLayer, + minZLayer, + onSwitchZLayer, + onAddZLayer, + brightnessLevel, + contrastLevel, + saturationLevel, + grid, + gridColor, + gridOpacity, + onChangeBrightnessLevel, + onChangeSaturationLevel, + onChangeContrastLevel, + onChangeGridColor, + onChangeGridOpacity, + onSwitchGrid, + } = this.props; + + const preventDefault = (event: KeyboardEvent | undefined): void => { + if (event) { + event.preventDefault(); + } + }; + + const keyMap = { + INCREASE_BRIGHTNESS: { + name: 'Brightness+', + description: 'Increase brightness level for the image', + sequence: 'shift+b+=', + action: 'keypress', + }, + DECREASE_BRIGHTNESS: { + name: 'Brightness-', + description: 'Decrease brightness level for the image', + sequence: 'shift+b+-', + action: 'keydown', + }, + INCREASE_CONTRAST: { + name: 'Contrast+', + description: 'Increase contrast level for the image', + sequence: 'shift+c+=', + action: 'keydown', + }, + DECREASE_CONTRAST: { + name: 'Contrast-', + description: 'Decrease contrast level for the image', + sequence: 'shift+c+-', + action: 'keydown', + }, + INCREASE_SATURATION: { + name: 'Saturation+', + description: 'Increase saturation level for the image', + sequence: 'shift+s+=', + action: 'keydown', + }, + DECREASE_SATURATION: { + name: 'Saturation-', + description: 'Increase contrast level for the image', + sequence: 'shift+s+-', + action: 'keydown', + }, + INCREASE_GRID_OPACITY: { + name: 'Grid opacity+', + description: 'Make the grid more visible', + sequence: 'shift+g+=', + action: 'keydown', + }, + DECREASE_GRID_OPACITY: { + name: 'Grid opacity-', + description: 'Make the grid less visible', + sequences: 'shift+g+-', + action: 'keydown', + }, + CHANGE_GRID_COLOR: { + name: 'Grid color', + description: 'Set another color for the image grid', + sequence: 'shift+g+enter', + action: 'keydown', + }, + }; + + const step = 10; + const handlers = { + INCREASE_BRIGHTNESS: (event: KeyboardEvent | undefined) => { + preventDefault(event); + const maxLevel = 200; + if (brightnessLevel < maxLevel) { + onChangeBrightnessLevel(Math.min(brightnessLevel + step, maxLevel)); + } + }, + DECREASE_BRIGHTNESS: (event: KeyboardEvent | undefined) => { + preventDefault(event); + const minLevel = 50; + if (brightnessLevel > minLevel) { + onChangeBrightnessLevel(Math.max(brightnessLevel - step, minLevel)); + } + }, + INCREASE_CONTRAST: (event: KeyboardEvent | undefined) => { + preventDefault(event); + const maxLevel = 200; + if (contrastLevel < maxLevel) { + onChangeContrastLevel(Math.min(contrastLevel + step, maxLevel)); + } + }, + DECREASE_CONTRAST: (event: KeyboardEvent | undefined) => { + preventDefault(event); + const minLevel = 50; + if (contrastLevel > minLevel) { + onChangeContrastLevel(Math.max(contrastLevel - step, minLevel)); + } + }, + INCREASE_SATURATION: (event: KeyboardEvent | undefined) => { + preventDefault(event); + const maxLevel = 300; + if (saturationLevel < maxLevel) { + onChangeSaturationLevel(Math.min(saturationLevel + step, maxLevel)); + } + }, + DECREASE_SATURATION: (event: KeyboardEvent | undefined) => { + preventDefault(event); + const minLevel = 0; + if (saturationLevel > minLevel) { + onChangeSaturationLevel(Math.max(saturationLevel - step, minLevel)); + } + }, + INCREASE_GRID_OPACITY: (event: KeyboardEvent | undefined) => { + preventDefault(event); + const maxLevel = 100; + if (!grid) { + onSwitchGrid(true); + } + + if (gridOpacity < maxLevel) { + onChangeGridOpacity(Math.min(gridOpacity + step, maxLevel)); + } + }, + DECREASE_GRID_OPACITY: (event: KeyboardEvent | undefined) => { + preventDefault(event); + const minLevel = 0; + if (gridOpacity - step <= minLevel) { + onSwitchGrid(false); + } + + if (gridOpacity > minLevel) { + onChangeGridOpacity(Math.max(gridOpacity - step, minLevel)); + } + }, + CHANGE_GRID_COLOR: (event: KeyboardEvent | undefined) => { + preventDefault(event); + const colors = [GridColor.Black, GridColor.Blue, + GridColor.Green, GridColor.Red, GridColor.White]; + const indexOf = colors.indexOf(gridColor) + 1; + const color = colors[indexOf >= colors.length ? 0 : indexOf]; + onChangeGridColor(color); + }, + }; + + return ( + + + {/* + This element doesn't have any props + So, React isn't going to rerender it + And it's a reason why cvat-canvas appended in mount function works + */} +
+
+ onSwitchZLayer(value as number)} + /> + + + +
+ + ); + } +} diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx new file mode 100644 index 00000000000..7deeabb49b9 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx @@ -0,0 +1,247 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import { GlobalHotKeys, KeyMap } from 'react-hotkeys'; + +import { + Icon, + Layout, + Tooltip, +} from 'antd'; + +import { + ActiveControl, + Rotation, +} from 'reducers/interfaces'; + +import { + TagIcon, +} from 'icons'; + +import { + Canvas, +} from 'cvat-canvas'; + +import RotateControl from './rotate-control'; +import CursorControl from './cursor-control'; +import MoveControl from './move-control'; +import FitControl from './fit-control'; +import ResizeControl from './resize-control'; +import DrawRectangleControl from './draw-rectangle-control'; +import DrawPolygonControl from './draw-polygon-control'; +import DrawPolylineControl from './draw-polyline-control'; +import DrawPointsControl from './draw-points-control'; +import MergeControl from './merge-control'; +import GroupControl from './group-control'; +import SplitControl from './split-control'; + +interface Props { + canvasInstance: Canvas; + activeControl: ActiveControl; + + mergeObjects(enabled: boolean): void; + groupObjects(enabled: boolean): void; + splitTrack(enabled: boolean): void; + rotateFrame(rotation: Rotation): void; + repeatDrawShape(): void; + pasteShape(): void; + resetGroup(): void; +} + +export default function ControlsSideBarComponent(props: Props): JSX.Element { + const { + canvasInstance, + activeControl, + + mergeObjects, + groupObjects, + splitTrack, + rotateFrame, + repeatDrawShape, + pasteShape, + resetGroup, + } = props; + + const preventDefault = (event: KeyboardEvent | undefined): void => { + if (event) { + event.preventDefault(); + } + }; + + const keyMap = { + PASTE_SHAPE: { + name: 'Paste shape', + description: 'Paste a shape from internal CVAT clipboard', + sequence: 'ctrl+v', + action: 'keydown', + }, + SWITCH_DRAW_MODE: { + name: 'Draw mode', + description: 'Repeat the latest procedure of drawing with the same parameters', + sequence: 'n', + action: 'keydown', + }, + SWITCH_MERGE_MODE: { + name: 'Merge mode', + description: 'Activate or deactivate mode to merging shapes', + sequence: 'm', + action: 'keydown', + }, + SWITCH_GROUP_MODE: { + name: 'Group mode', + description: 'Activate or deactivate mode to grouping shapes', + sequence: 'g', + action: 'keydown', + }, + RESET_GROUP: { + name: 'Reset group', + description: 'Reset group for selected shapes (in group mode)', + sequence: 'shift+g', + action: 'keyup', + }, + CANCEL: { + name: 'Cancel', + description: 'Cancel any active canvas mode', + sequence: 'esc', + action: 'keydown', + }, + CLOCKWISE_ROTATION: { + name: 'Rotate clockwise', + description: 'Change image angle (add 90 degrees)', + sequence: 'ctrl+r', + action: 'keydown', + }, + ANTICLOCKWISE_ROTATION: { + name: 'Rotate anticlockwise', + description: 'Change image angle (substract 90 degrees)', + sequence: 'ctrl+shift+r', + action: 'keydown', + }, + }; + + const handlers = { + PASTE_SHAPE: (event: KeyboardEvent | undefined) => { + preventDefault(event); + canvasInstance.cancel(); + pasteShape(); + }, + SWITCH_DRAW_MODE: (event: KeyboardEvent | undefined) => { + preventDefault(event); + const drawing = [ActiveControl.DRAW_POINTS, ActiveControl.DRAW_POLYGON, + ActiveControl.DRAW_POLYLINE, ActiveControl.DRAW_RECTANGLE].includes(activeControl); + + if (!drawing) { + canvasInstance.cancel(); + // repeateDrawShapes gets all the latest parameters + // and calls canvasInstance.draw() with them + repeatDrawShape(); + } else { + canvasInstance.draw({ enabled: false }); + } + }, + SWITCH_MERGE_MODE: (event: KeyboardEvent | undefined) => { + preventDefault(event); + const merging = activeControl === ActiveControl.MERGE; + if (!merging) { + canvasInstance.cancel(); + } + canvasInstance.merge({ enabled: !merging }); + mergeObjects(!merging); + }, + SWITCH_GROUP_MODE: (event: KeyboardEvent | undefined) => { + preventDefault(event); + const grouping = activeControl === ActiveControl.GROUP; + if (!grouping) { + canvasInstance.cancel(); + } + canvasInstance.group({ enabled: !grouping }); + groupObjects(!grouping); + }, + RESET_GROUP: (event: KeyboardEvent | undefined) => { + preventDefault(event); + const grouping = activeControl === ActiveControl.GROUP; + if (!grouping) { + return; + } + resetGroup(); + canvasInstance.group({ enabled: false }); + groupObjects(false); + }, + CANCEL: (event: KeyboardEvent | undefined) => { + preventDefault(event); + if (activeControl !== ActiveControl.CURSOR) { + canvasInstance.cancel(); + } + }, + CLOCKWISE_ROTATION: (event: KeyboardEvent | undefined) => { + preventDefault(event); + rotateFrame(Rotation.CLOCKWISE90); + }, + ANTICLOCKWISE_ROTATION: (event: KeyboardEvent | undefined) => { + preventDefault(event); + rotateFrame(Rotation.ANTICLOCKWISE90); + }, + }; + + return ( + + + + + + + +
+ + + + +
+ + + + + + + + + + +
+ + + + +
+ ); +} diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/cursor-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/cursor-control.tsx new file mode 100644 index 00000000000..9e72319b7d0 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/cursor-control.tsx @@ -0,0 +1,51 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Icon, + Tooltip, +} from 'antd'; + +import { + CursorIcon, +} from 'icons'; + +import { + ActiveControl, +} from 'reducers/interfaces'; + +import { + Canvas, +} from 'cvat-canvas'; + +interface Props { + canvasInstance: Canvas; + activeControl: ActiveControl; +} + +function CursorControl(props: Props): JSX.Element { + const { + canvasInstance, + activeControl, + } = props; + + return ( + + canvasInstance.cancel() + : undefined + } + /> + + ); +} + +export default React.memo(CursorControl); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-points-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-points-control.tsx new file mode 100644 index 00000000000..cce985d0461 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-points-control.tsx @@ -0,0 +1,58 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import { + Popover, + Icon, +} from 'antd'; + +import { Canvas } from 'cvat-canvas'; +import { PointIcon } from 'icons'; +import { ShapeType } from 'reducers/interfaces'; + +import DrawShapePopoverContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover'; + +interface Props { + canvasInstance: Canvas; + isDrawing: boolean; +} + +function DrawPointsControl(props: Props): JSX.Element { + const { + canvasInstance, + isDrawing, + } = props; + + const dynamcPopoverPros = isDrawing ? { + overlayStyle: { + display: 'none', + }, + } : {}; + + const dynamicIconProps = isDrawing ? { + className: 'cvat-active-canvas-control', + onClick: (): void => { + canvasInstance.draw({ enabled: false }); + }, + } : {}; + + return ( + + )} + > + + + ); +} + +export default React.memo(DrawPointsControl); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-polygon-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-polygon-control.tsx new file mode 100644 index 00000000000..77f2da28caf --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-polygon-control.tsx @@ -0,0 +1,58 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import { + Popover, + Icon, +} from 'antd'; + +import { Canvas } from 'cvat-canvas'; +import { PolygonIcon } from 'icons'; +import { ShapeType } from 'reducers/interfaces'; + +import DrawShapePopoverContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover'; + +interface Props { + canvasInstance: Canvas; + isDrawing: boolean; +} + +function DrawPolygonControl(props: Props): JSX.Element { + const { + canvasInstance, + isDrawing, + } = props; + + const dynamcPopoverPros = isDrawing ? { + overlayStyle: { + display: 'none', + }, + } : {}; + + const dynamicIconProps = isDrawing ? { + className: 'cvat-active-canvas-control', + onClick: (): void => { + canvasInstance.draw({ enabled: false }); + }, + } : {}; + + return ( + + )} + > + + + ); +} + +export default React.memo(DrawPolygonControl); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-polyline-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-polyline-control.tsx new file mode 100644 index 00000000000..1018c1f6282 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-polyline-control.tsx @@ -0,0 +1,58 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import { + Popover, + Icon, +} from 'antd'; + +import { Canvas } from 'cvat-canvas'; +import { PolylineIcon } from 'icons'; +import { ShapeType } from 'reducers/interfaces'; + +import DrawShapePopoverContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover'; + +interface Props { + canvasInstance: Canvas; + isDrawing: boolean; +} + +function DrawPolylineControl(props: Props): JSX.Element { + const { + canvasInstance, + isDrawing, + } = props; + + const dynamcPopoverPros = isDrawing ? { + overlayStyle: { + display: 'none', + }, + } : {}; + + const dynamicIconProps = isDrawing ? { + className: 'cvat-active-canvas-control', + onClick: (): void => { + canvasInstance.draw({ enabled: false }); + }, + } : {}; + + return ( + + )} + > + + + ); +} + +export default React.memo(DrawPolylineControl); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-rectangle-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-rectangle-control.tsx new file mode 100644 index 00000000000..5a8b7c5adc8 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-rectangle-control.tsx @@ -0,0 +1,58 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import { + Popover, + Icon, +} from 'antd'; + +import { Canvas } from 'cvat-canvas'; +import { RectangleIcon } from 'icons'; +import { ShapeType } from 'reducers/interfaces'; + +import DrawShapePopoverContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover'; + +interface Props { + canvasInstance: Canvas; + isDrawing: boolean; +} + +function DrawRectangleControl(props: Props): JSX.Element { + const { + canvasInstance, + isDrawing, + } = props; + + const dynamcPopoverPros = isDrawing ? { + overlayStyle: { + display: 'none', + }, + } : {}; + + const dynamicIconProps = isDrawing ? { + className: 'cvat-active-canvas-control', + onClick: (): void => { + canvasInstance.draw({ enabled: false }); + }, + } : {}; + + return ( + + )} + > + + + ); +} + +export default React.memo(DrawRectangleControl); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx new file mode 100644 index 00000000000..9ee0f8a95c3 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx @@ -0,0 +1,151 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Row, + Col, + Select, + Button, + InputNumber, + Radio, +} from 'antd'; + +import { RadioChangeEvent } from 'antd/lib/radio'; +import Text from 'antd/lib/typography/Text'; + +import { RectDrawingMethod } from 'cvat-canvas'; +import { ShapeType } from 'reducers/interfaces'; + +interface Props { + shapeType: ShapeType; + labels: any[]; + minimumPoints: number; + rectDrawingMethod?: RectDrawingMethod; + numberOfPoints?: number; + selectedLabeID: number; + onChangeLabel(value: string): void; + onChangePoints(value: number | undefined): void; + onChangeRectDrawingMethod(event: RadioChangeEvent): void; + onDrawTrack(): void; + onDrawShape(): void; +} + +function DrawShapePopoverComponent(props: Props): JSX.Element { + const { + labels, + shapeType, + minimumPoints, + selectedLabeID, + numberOfPoints, + rectDrawingMethod, + onDrawTrack, + onDrawShape, + onChangeLabel, + onChangePoints, + onChangeRectDrawingMethod, + } = props; + + return ( +
+ + + {`Draw new ${shapeType}`} + + + + + Label + + + + + + + + { + shapeType === ShapeType.RECTANGLE ? ( + <> + + + Drawing method + + + + + + + By 2 Points + + + By 4 Points + + + + + + ) : ( + + + Number of points: + + + + + + ) + } + + + + + + + + +
+ ); +} + +export default React.memo(DrawShapePopoverComponent); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/fit-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/fit-control.tsx new file mode 100644 index 00000000000..bee90af83a6 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/fit-control.tsx @@ -0,0 +1,36 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Icon, + Tooltip, +} from 'antd'; + +import { + FitIcon, +} from 'icons'; + +import { + Canvas, +} from 'cvat-canvas'; + +interface Props { + canvasInstance: Canvas; +} + +function FitControl(props: Props): JSX.Element { + const { + canvasInstance, + } = props; + + return ( + + canvasInstance.fit()} /> + + ); +} + +export default React.memo(FitControl); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/group-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/group-control.tsx new file mode 100644 index 00000000000..5fc2c40e62f --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/group-control.tsx @@ -0,0 +1,55 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Tooltip, + Icon, +} from 'antd'; + +import { + GroupIcon, +} from 'icons'; + +import { Canvas } from 'cvat-canvas'; +import { ActiveControl } from 'reducers/interfaces'; + +interface Props { + canvasInstance: Canvas; + activeControl: ActiveControl; + + groupObjects(enabled: boolean): void; +} + +function GroupControl(props: Props): JSX.Element { + const { + activeControl, + canvasInstance, + groupObjects, + } = props; + + const dynamicIconProps = activeControl === ActiveControl.GROUP + ? { + className: 'cvat-active-canvas-control', + onClick: (): void => { + canvasInstance.group({ enabled: false }); + groupObjects(false); + }, + } : { + onClick: (): void => { + canvasInstance.cancel(); + canvasInstance.group({ enabled: true }); + groupObjects(true); + }, + }; + + return ( + + + + ); +} + +export default React.memo(GroupControl); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/merge-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/merge-control.tsx new file mode 100644 index 00000000000..3bccdd4bd89 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/merge-control.tsx @@ -0,0 +1,55 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Tooltip, + Icon, +} from 'antd'; + +import { + MergeIcon, +} from 'icons'; + +import { Canvas } from 'cvat-canvas'; +import { ActiveControl } from 'reducers/interfaces'; + +interface Props { + canvasInstance: Canvas; + activeControl: ActiveControl; + + mergeObjects(enabled: boolean): void; +} + +function MergeControl(props: Props): JSX.Element { + const { + activeControl, + canvasInstance, + mergeObjects, + } = props; + + const dynamicIconProps = activeControl === ActiveControl.MERGE + ? { + className: 'cvat-active-canvas-control', + onClick: (): void => { + canvasInstance.merge({ enabled: false }); + mergeObjects(false); + }, + } : { + onClick: (): void => { + canvasInstance.cancel(); + canvasInstance.merge({ enabled: true }); + mergeObjects(true); + }, + }; + + return ( + + + + ); +} + +export default React.memo(MergeControl); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/move-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/move-control.tsx new file mode 100644 index 00000000000..b62ff354ce1 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/move-control.tsx @@ -0,0 +1,54 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Icon, + Tooltip, +} from 'antd'; + +import { + MoveIcon, +} from 'icons'; + +import { + ActiveControl, +} from 'reducers/interfaces'; + +import { + Canvas, +} from 'cvat-canvas'; + +interface Props { + canvasInstance: Canvas; + activeControl: ActiveControl; +} + +function MoveControl(props: Props): JSX.Element { + const { + canvasInstance, + activeControl, + } = props; + + return ( + + { + if (activeControl === ActiveControl.DRAG_CANVAS) { + canvasInstance.dragCanvas(false); + } else { + canvasInstance.cancel(); + canvasInstance.dragCanvas(true); + } + }} + /> + + ); +} + +export default React.memo(MoveControl); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/resize-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/resize-control.tsx new file mode 100644 index 00000000000..8b3343b65e3 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/resize-control.tsx @@ -0,0 +1,54 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Icon, + Tooltip, +} from 'antd'; + +import { + ZoomIcon, +} from 'icons'; + +import { + ActiveControl, +} from 'reducers/interfaces'; + +import { + Canvas, +} from 'cvat-canvas'; + +interface Props { + canvasInstance: Canvas; + activeControl: ActiveControl; +} + +function ResizeControl(props: Props): JSX.Element { + const { + activeControl, + canvasInstance, + } = props; + + return ( + + { + if (activeControl === ActiveControl.ZOOM_CANVAS) { + canvasInstance.zoomCanvas(false); + } else { + canvasInstance.cancel(); + canvasInstance.zoomCanvas(true); + } + }} + /> + + ); +} + +export default React.memo(ResizeControl); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx new file mode 100644 index 00000000000..139bff15332 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx @@ -0,0 +1,59 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Icon, + Tooltip, + Popover, +} from 'antd'; + +import { + RotateIcon, +} from 'icons'; + +import { + Rotation, +} from 'reducers/interfaces'; + +interface Props { + rotateFrame(rotation: Rotation): void; +} + +function RotateControl(props: Props): JSX.Element { + const { + rotateFrame, + } = props; + + return ( + + + rotateFrame(Rotation.ANTICLOCKWISE90)} + component={RotateIcon} + /> + + + rotateFrame(Rotation.CLOCKWISE90)} + component={RotateIcon} + /> + + + )} + trigger='hover' + > + + + ); +} + +export default React.memo(RotateControl); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/split-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/split-control.tsx new file mode 100644 index 00000000000..7f434d3a080 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/split-control.tsx @@ -0,0 +1,55 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Tooltip, + Icon, +} from 'antd'; + +import { + SplitIcon, +} from 'icons'; + +import { Canvas } from 'cvat-canvas'; +import { ActiveControl } from 'reducers/interfaces'; + +interface Props { + canvasInstance: Canvas; + activeControl: ActiveControl; + + splitTrack(enabled: boolean): void; +} + +function SplitControl(props: Props): JSX.Element { + const { + activeControl, + canvasInstance, + splitTrack, + } = props; + + const dynamicIconProps = activeControl === ActiveControl.SPLIT + ? { + className: 'cvat-active-canvas-control', + onClick: (): void => { + canvasInstance.split({ enabled: false }); + splitTrack(false); + }, + } : { + onClick: (): void => { + canvasInstance.cancel(); + canvasInstance.split({ enabled: true }); + splitTrack(true); + }, + }; + + return ( + + + + ); +} + +export default React.memo(SplitControl); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/appearance-block.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/appearance-block.tsx new file mode 100644 index 00000000000..56c5b5ac5fa --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/appearance-block.tsx @@ -0,0 +1,94 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Checkbox, + Collapse, + Slider, + Radio, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; +import { RadioChangeEvent } from 'antd/lib/radio'; +import { SliderValue } from 'antd/lib/slider'; +import { CheckboxChangeEvent } from 'antd/lib/checkbox'; + +import { ColorBy } from 'reducers/interfaces'; + +interface Props { + appearanceCollapsed: boolean; + colorBy: ColorBy; + opacity: number; + selectedOpacity: number; + blackBorders: boolean; + + collapseAppearance(): void; + changeShapesColorBy(event: RadioChangeEvent): void; + changeShapesOpacity(event: SliderValue): void; + changeSelectedShapesOpacity(event: SliderValue): void; + changeShapesBlackBorders(event: CheckboxChangeEvent): void; +} + +function AppearanceBlock(props: Props): JSX.Element { + const { + appearanceCollapsed, + colorBy, + opacity, + selectedOpacity, + blackBorders, + collapseAppearance, + changeShapesColorBy, + changeShapesOpacity, + changeSelectedShapesOpacity, + changeShapesBlackBorders, + } = props; + + return ( + + Appearance + } + key='appearance' + > +
+ Color by + + {ColorBy.INSTANCE} + {ColorBy.GROUP} + {ColorBy.LABEL} + + Opacity + + Selected opacity + + + Black borders + +
+
+
+ ); +} + +export default React.memo(AppearanceBlock); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/color-changer.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/color-changer.tsx new file mode 100644 index 00000000000..8929738da24 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/color-changer.tsx @@ -0,0 +1,60 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Row, + Col, + Button, +} from 'antd'; + +interface Props { + colors: string[]; + onChange(color: string): void; +} + +function ColorChanger(props: Props): JSX.Element { + const { + colors, + onChange, + } = props; + + const cols = 6; + const rows = Math.ceil(colors.length / cols); + + const antdRows = []; + for (let row = 0; row < rows; row++) { + const antdCols = []; + for (let col = 0; col < cols; col++) { + const idx = row * cols + col; + if (idx >= colors.length) { + break; + } + const color = colors[idx]; + antdCols.push( + + + + + + + + + + + + + + + + + + + + ); +} + +interface ItemTopComponentProps { + clientID: number; + serverID: number | undefined; + labelID: number; + labels: any[]; + type: string; + locked: boolean; + changeLabel(labelID: string): void; + copy(): void; + remove(): void; + propagate(): void; + createURL(): void; + toBackground(): void; + toForeground(): void; +} + +function ItemTopComponent(props: ItemTopComponentProps): JSX.Element { + const { + clientID, + serverID, + labelID, + labels, + type, + locked, + changeLabel, + copy, + remove, + propagate, + createURL, + toBackground, + toForeground, + } = props; + + return ( + + + {clientID} +
+ {type} + + + + + + + + + +
+ ); +} + +const ItemTop = React.memo(ItemTopComponent); + +interface ItemButtonsComponentProps { + objectType: ObjectType; + shapeType: ShapeType; + occluded: boolean; + outside: boolean | undefined; + locked: boolean; + pinned: boolean; + hidden: boolean; + keyframe: boolean | undefined; + + navigateFirstKeyframe: null | (() => void); + navigatePrevKeyframe: null | (() => void); + navigateNextKeyframe: null | (() => void); + navigateLastKeyframe: null | (() => void); + + setOccluded(): void; + unsetOccluded(): void; + setOutside(): void; + unsetOutside(): void; + setKeyframe(): void; + unsetKeyframe(): void; + lock(): void; + unlock(): void; + pin(): void; + unpin(): void; + hide(): void; + show(): void; +} + +function ItemButtonsComponent(props: ItemButtonsComponentProps): JSX.Element { + const { + objectType, + shapeType, + occluded, + outside, + locked, + pinned, + hidden, + keyframe, + + navigateFirstKeyframe, + navigatePrevKeyframe, + navigateNextKeyframe, + navigateLastKeyframe, + + setOccluded, + unsetOccluded, + setOutside, + unsetOutside, + setKeyframe, + unsetKeyframe, + lock, + unlock, + pin, + unpin, + hide, + show, + } = props; + + if (objectType === ObjectType.TRACK) { + return ( + + + + + { navigateFirstKeyframe + ? + : } + + + { navigatePrevKeyframe + ? + : } + + + { navigateNextKeyframe + ? + : } + + + { navigateLastKeyframe + ? + : } + + + + + { outside + ? + : } + + + { locked + ? + : } + + + { occluded + ? + : } + + + { hidden + ? + : } + + + { keyframe + ? + : } + + { + shapeType !== ShapeType.POINTS && ( + + { pinned + ? + : } + + ) + } + + + + ); + } + + return ( + + + + + { locked + ? + : } + + + { occluded + ? + : } + + + { hidden + ? + : } + + { + shapeType !== ShapeType.POINTS && ( + + { pinned + ? + : } + + ) + } + + + + ); +} + +const ItemButtons = React.memo(ItemButtonsComponent); + +interface ItemAttributeComponentProps { + attrInputType: string; + attrValues: string[]; + attrValue: string; + attrName: string; + attrID: number; + changeAttribute(attrID: number, value: string): void; +} + +function attrIsTheSame( + prevProps: ItemAttributeComponentProps, + nextProps: ItemAttributeComponentProps, +): boolean { + return nextProps.attrID === prevProps.attrID + && nextProps.attrValue === prevProps.attrValue + && nextProps.attrName === prevProps.attrName + && nextProps.attrInputType === prevProps.attrInputType + && nextProps.attrValues + .map((value: string, id: number): boolean => prevProps.attrValues[id] === value) + .every((value: boolean): boolean => value); +} + +function ItemAttributeComponent(props: ItemAttributeComponentProps): JSX.Element { + const { + attrInputType, + attrValues, + attrValue, + attrName, + attrID, + changeAttribute, + } = props; + + if (attrInputType === 'checkbox') { + return ( + + { + const value = event.target.checked ? 'true' : 'false'; + changeAttribute(attrID, value); + }} + > + + {attrName} + + + + ); + } + + if (attrInputType === 'radio') { + return ( + +
+ + {attrName} + + { + changeAttribute(attrID, event.target.value); + }} + > + { attrValues.map((value: string): JSX.Element => ( + {value} + )) } + +
+ + ); + } + + if (attrInputType === 'select') { + return ( + <> + + + {attrName} + + + + + + + ); + } + + if (attrInputType === 'number') { + const [min, max, step] = attrValues; + + return ( + <> + + + {attrName} + + + + { + if (typeof (value) !== 'undefined') { + changeAttribute(attrID, `${value}`); + } + }} + value={+attrValue} + className='cvat-object-item-number-attribute' + min={+min} + max={+max} + step={+step} + /> + + + ); + } + + return ( + <> + + + {attrName} + + + + ): void => { + changeAttribute(attrID, event.target.value); + }} + value={attrValue} + className='cvat-object-item-text-attribute' + /> + + + ); +} + +const ItemAttribute = React.memo(ItemAttributeComponent, attrIsTheSame); + + +interface ItemAttributesComponentProps { + collapsed: boolean; + attributes: any[]; + values: Record; + changeAttribute(attrID: number, value: string): void; + collapse(): void; +} + +function attrValuesAreEqual(next: Record, prev: Record): boolean { + const prevKeys = Object.keys(prev); + const nextKeys = Object.keys(next); + + return nextKeys.length === prevKeys.length + && nextKeys.map((key: string): boolean => prev[+key] === next[+key]) + .every((value: boolean) => value); +} + +function attrAreTheSame( + prevProps: ItemAttributesComponentProps, + nextProps: ItemAttributesComponentProps, +): boolean { + return nextProps.collapsed === prevProps.collapsed + && nextProps.attributes === prevProps.attributes + && attrValuesAreEqual(nextProps.values, prevProps.values); +} + +function ItemAttributesComponent(props: ItemAttributesComponentProps): JSX.Element { + const { + collapsed, + attributes, + values, + changeAttribute, + collapse, + } = props; + + const sorted = [...attributes] + .sort((a: any, b: any): number => a.inputType.localeCompare(b.inputType)); + + return ( + + + Details} + key='details' + > + { sorted.map((attribute: any): JSX.Element => ( + + + + ))} + + + + ); +} + +const ItemAttributes = React.memo(ItemAttributesComponent, attrAreTheSame); + +interface Props { + activated: boolean; + objectType: ObjectType; + shapeType: ShapeType; + clientID: number; + serverID: number | undefined; + labelID: number; + occluded: boolean; + outside: boolean | undefined; + locked: boolean; + pinned: boolean; + hidden: boolean; + keyframe: boolean | undefined; + attrValues: Record; + color: string; + colors: string[]; + + labels: any[]; + attributes: any[]; + collapsed: boolean; + navigateFirstKeyframe: null | (() => void); + navigatePrevKeyframe: null | (() => void); + navigateNextKeyframe: null | (() => void); + navigateLastKeyframe: null | (() => void); + + activate(): void; + copy(): void; + propagate(): void; + createURL(): void; + toBackground(): void; + toForeground(): void; + remove(): void; + setOccluded(): void; + unsetOccluded(): void; + setOutside(): void; + unsetOutside(): void; + setKeyframe(): void; + unsetKeyframe(): void; + lock(): void; + unlock(): void; + pin(): void; + unpin(): void; + hide(): void; + show(): void; + changeLabel(labelID: string): void; + changeAttribute(attrID: number, value: string): void; + changeColor(color: string): void; + collapse(): void; +} + +function objectItemsAreEqual(prevProps: Props, nextProps: Props): boolean { + return nextProps.activated === prevProps.activated + && nextProps.locked === prevProps.locked + && nextProps.pinned === prevProps.pinned + && nextProps.occluded === prevProps.occluded + && nextProps.outside === prevProps.outside + && nextProps.hidden === prevProps.hidden + && nextProps.keyframe === prevProps.keyframe + && nextProps.labelID === prevProps.labelID + && nextProps.color === prevProps.color + && nextProps.clientID === prevProps.clientID + && nextProps.serverID === prevProps.serverID + && nextProps.objectType === prevProps.objectType + && nextProps.shapeType === prevProps.shapeType + && nextProps.collapsed === prevProps.collapsed + && nextProps.labels === prevProps.labels + && nextProps.attributes === prevProps.attributes + && nextProps.navigateFirstKeyframe === prevProps.navigateFirstKeyframe + && nextProps.navigatePrevKeyframe === prevProps.navigatePrevKeyframe + && nextProps.navigateNextKeyframe === prevProps.navigateNextKeyframe + && nextProps.navigateLastKeyframe === prevProps.navigateLastKeyframe + && attrValuesAreEqual(nextProps.attrValues, prevProps.attrValues); +} + +function ObjectItemComponent(props: Props): JSX.Element { + const { + activated, + objectType, + shapeType, + clientID, + serverID, + occluded, + outside, + locked, + pinned, + hidden, + keyframe, + attrValues, + labelID, + color, + colors, + + attributes, + labels, + collapsed, + navigateFirstKeyframe, + navigatePrevKeyframe, + navigateNextKeyframe, + navigateLastKeyframe, + + activate, + copy, + propagate, + createURL, + toBackground, + toForeground, + remove, + setOccluded, + unsetOccluded, + setOutside, + unsetOutside, + setKeyframe, + unsetKeyframe, + lock, + unlock, + pin, + unpin, + hide, + show, + changeLabel, + changeAttribute, + changeColor, + collapse, + } = props; + + const type = objectType === ObjectType.TAG ? ObjectType.TAG.toUpperCase() + : `${shapeType.toUpperCase()} ${objectType.toUpperCase()}`; + + const className = !activated ? 'cvat-objects-sidebar-state-item' + : 'cvat-objects-sidebar-state-item cvat-objects-sidebar-state-active-item'; + + return ( +
+ + )} + > +
+ + +
+ +
+
+ ); +} + +export default React.memo(ObjectItemComponent, objectItemsAreEqual); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-list-header.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-list-header.tsx new file mode 100644 index 00000000000..d7406ccd8e1 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-list-header.tsx @@ -0,0 +1,143 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Row, + Col, + Icon, + Select, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; +import { SelectValue } from 'antd/lib/select'; + +import { StatesOrdering } from 'reducers/interfaces'; + + +interface StatesOrderingSelectorComponentProps { + statesOrdering: StatesOrdering; + changeStatesOrdering(value: StatesOrdering): void; +} + +function StatesOrderingSelectorComponent(props: StatesOrderingSelectorComponentProps): JSX.Element { + const { + statesOrdering, + changeStatesOrdering, + } = props; + + return ( + + Sort by + + + ); +} + +const StatesOrderingSelector = React.memo(StatesOrderingSelectorComponent); + +interface Props { + statesHidden: boolean; + statesLocked: boolean; + statesCollapsed: boolean; + statesOrdering: StatesOrdering; + annotationsFilters: string[]; + annotationsFiltersHistory: string[]; + changeStatesOrdering(value: StatesOrdering): void; + changeAnnotationsFilters(value: SelectValue): void; + lockAllStates(): void; + unlockAllStates(): void; + collapseAllStates(): void; + expandAllStates(): void; + hideAllStates(): void; + showAllStates(): void; +} + +function ObjectListHeader(props: Props): JSX.Element { + const { + annotationsFilters, + annotationsFiltersHistory, + statesHidden, + statesLocked, + statesCollapsed, + statesOrdering, + changeStatesOrdering, + lockAllStates, + unlockAllStates, + collapseAllStates, + expandAllStates, + hideAllStates, + showAllStates, + changeAnnotationsFilters, + } = props; + + return ( +
+ + + + + + + + { statesLocked + ? + : } + + + { statesHidden + ? + : } + + + { statesCollapsed + ? + : } + + + +
+ ); +} + +export default React.memo(ObjectListHeader); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx new file mode 100644 index 00000000000..e3af2e3cbd8 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx @@ -0,0 +1,79 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { SelectValue } from 'antd/lib/select'; +import { StatesOrdering } from 'reducers/interfaces'; +import ObjectItemContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/object-item'; +import ObjectListHeader from './objects-list-header'; + + +interface Props { + listHeight: number; + statesHidden: boolean; + statesLocked: boolean; + statesCollapsed: boolean; + statesOrdering: StatesOrdering; + sortedStatesID: number[]; + annotationsFilters: string[]; + annotationsFiltersHistory: string[]; + changeStatesOrdering(value: StatesOrdering): void; + changeAnnotationsFilters(value: SelectValue): void; + lockAllStates(): void; + unlockAllStates(): void; + collapseAllStates(): void; + expandAllStates(): void; + hideAllStates(): void; + showAllStates(): void; +} + +function ObjectListComponent(props: Props): JSX.Element { + const { + listHeight, + statesHidden, + statesLocked, + statesCollapsed, + statesOrdering, + sortedStatesID, + annotationsFilters, + annotationsFiltersHistory, + changeStatesOrdering, + changeAnnotationsFilters, + lockAllStates, + unlockAllStates, + collapseAllStates, + expandAllStates, + hideAllStates, + showAllStates, + } = props; + + return ( +
+ +
+ { sortedStatesID.map((id: number): JSX.Element => ( + + ))} +
+
+ ); +} + +export default React.memo(ObjectListComponent); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx new file mode 100644 index 00000000000..1a8cd5bd055 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx @@ -0,0 +1,114 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import './styles.scss'; +import React from 'react'; + +import { + Icon, + Tabs, + Layout, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; +import { RadioChangeEvent } from 'antd/lib/radio'; +import { SliderValue } from 'antd/lib/slider'; +import { CheckboxChangeEvent } from 'antd/lib/checkbox'; + +import { ColorBy } from 'reducers/interfaces'; + +import ObjectsListContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/objects-list'; +import LabelsListContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/labels-list'; +import AppearanceBlock from './appearance-block'; + +interface Props { + sidebarCollapsed: boolean; + appearanceCollapsed: boolean; + colorBy: ColorBy; + opacity: number; + selectedOpacity: number; + blackBorders: boolean; + + collapseSidebar(): void; + collapseAppearance(): void; + + changeShapesColorBy(event: RadioChangeEvent): void; + changeShapesOpacity(event: SliderValue): void; + changeSelectedShapesOpacity(event: SliderValue): void; + changeShapesBlackBorders(event: CheckboxChangeEvent): void; +} + +function ObjectsSideBar(props: Props): JSX.Element { + const { + sidebarCollapsed, + appearanceCollapsed, + colorBy, + opacity, + selectedOpacity, + blackBorders, + collapseSidebar, + collapseAppearance, + changeShapesColorBy, + changeShapesOpacity, + changeSelectedShapesOpacity, + changeShapesBlackBorders, + } = props; + + const appearanceProps = { + collapseAppearance, + appearanceCollapsed, + colorBy, + opacity, + selectedOpacity, + blackBorders, + + changeShapesColorBy, + changeShapesOpacity, + changeSelectedShapesOpacity, + changeShapesBlackBorders, + }; + + return ( + + {/* eslint-disable-next-line */} + + {sidebarCollapsed ? + : } + + + + Objects} + key='objects' + > + + + Labels} + key='labels' + > + + + + + { !sidebarCollapsed && } + + ); +} + +export default React.memo(ObjectsSideBar); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss new file mode 100644 index 00000000000..81e6ba9c284 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss @@ -0,0 +1,281 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +@import 'base.scss'; + +.cvat-objects-appearance-collapse.ant-collapse { + width: 100%; + bottom: 0px; + position: absolute; + border-radius: 0px; + + > .ant-collapse-item { + border: none; + > .ant-collapse-header { + padding-top: 2.5px; + padding-bottom: 2.5px; + background: $header-color; + border-radius: 0px; + height: 25px; + } + > .ant-collapse-content { + background: $background-color-2; + border-bottom: none; + height: 230px; + } + } +} + +.cvat-object-sidebar-icon { + fill: $objects-bar-icons-color; + color: $objects-bar-icons-color; + + font-size: 15px; + + &:hover { + transform: scale(1.1); + } +} + +.cvat-objects-sidebar-tabs.ant-tabs.ant-tabs-card { + background: $header-color; + box-sizing: border-box; + border: none; + + .ant-tabs-card-bar { + border: none; + margin-bottom: 0px; + padding-top: 25px; + + .ant-tabs-tab { + background: $transparent-color; + border: none; + + &:nth-child(1) { + margin-left: 5px; + } + } + + .ant-tabs-tab.ant-tabs-tab-active { + background: $objects-bar-tabs-color; + } + } +} + +.cvat-objects-sidebar-states-header { + background: $objects-bar-tabs-color; + padding: 5px; + + > div:nth-child(1) > div:nth-child(1) { + height: 32px; + > .ant-select > div { + height: 32px; + > div { + height: 32px; + + ul { + display: flex; + } + } + } + } + + > div:nth-child(2) { + margin-top: 5px; + + > div { + text-align: center; + margin: 0px 2px; + + > i { + @extend .cvat-object-sidebar-icon; + } + + &:nth-child(4) { + text-align: right; + + > .ant-select { + margin-left: 5px; + width: 60%; + } + } + } + } +} + +.cvat-objects-sidebar-states-header { + height: 80px; +} + +.cvat-objects-sidebar-states-list { + background-color: $background-color-2; + height: calc(100% - 80px); + overflow-y: auto; +} + +.cvat-objects-sidebar-state-active-item { + background: $active-object-item-background-color; +} + +.cvat-objects-sidebar-state-item-color { + width: 7px; + opacity: 1; + + &:hover { + opacity: 0.7; + } +} + +.cvat-objects-sidebar-state-item { + width: 100%; + padding: 5px 3px 3px 3px; + border-bottom: 1px dashed; + + > div:nth-child(1) { + > div:nth-child(1) { + line-height: 12px; + } + + > div:nth-child(3) > i { + @extend .cvat-object-sidebar-icon; + font-size: 25px; + } + + > div:nth-child(2) > .ant-select { + width: 100%; + } + } + + > div:nth-child(2) { + > div > div { + margin-top: 5px; + } + + i { + @extend .cvat-object-sidebar-icon; + } + } +} + +.cvat-objects-sidebar-state-item-collapse { + border: 0px; + background: inherit; + + > .ant-collapse-item { + background: inherit; + border: none; + + > .ant-collapse-header { + background: inherit; + padding-top: 2px; + padding-bottom: 2px; + } + + > .ant-collapse-content { + background: inherit; + } + } +} + +.cvat-object-item-attribute-wrapper { + margin-top: 5px; +} + +.cvat-object-item-select-attribute { + width: 100%; +} + +.cvat-object-item-number-attribute { + width: 100%; +} + +.cvat-object-item-text-attribute { + width: 100%; +} + +.cvat-object-item-radio-attribute { + border: 1px double $border-color-hover; + border-radius: 7px 7px 7px 7px; + + > legend { + text-align: center; + width: unset; + text-overflow: ellipsis; + max-width: 80%; + overflow: hidden; + max-height: 1.2em; + font-size: 1em; + + > span { + padding: 0 10px; + } + } + + > .ant-radio-group { + display: grid; + padding: 5px + } +} + +.cvat-objects-sidebar-labels-list { + background-color: $background-color-2; + height: 100%; + overflow-y: auto; +} + +.cvat-objects-sidebar-label-active-item { + background: $active-object-item-background-color; +} + +.cvat-objects-sidebar-label-item { + height: 2.5em; + border-bottom: 1px solid $border-color-2; + padding: 5px; + + i { + @extend .cvat-object-sidebar-icon; + } + + > div:nth-child(2) { + text-overflow: ellipsis; + overflow: hidden; + max-height: 1.5em; + font-size: 1em; + } + + &:hover { + @extend .cvat-objects-sidebar-label-active-item; + } +} + +.cvat-label-item-color-button { + width: 30px; + height: 20px; + border-radius: 5px; +} + +.cvat-objects-appearance-content { + > div { + width: 100%; + + > label { + text-align: center; + width: 33%; + } + } +} + +.cvat-object-item-menu { + > li { + padding: 0px; + + > button { + padding: 5px 32px; + color: $text-color; + width: 100%; + height: 100%; + text-align: left; + } + } +} diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/propagate-confirm.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/propagate-confirm.tsx new file mode 100644 index 00000000000..f47d5b12d86 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/propagate-confirm.tsx @@ -0,0 +1,59 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Modal, + InputNumber, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; + +interface Props { + visible: boolean; + propagateFrames: number; + propagateUpToFrame: number; + propagateObject(): void; + cancel(): void; + changePropagateFrames(value: number | undefined): void; + changeUpToFrame(value: number | undefined): void; +} + +export default function PropagateConfirmComponent(props: Props): JSX.Element { + const { + visible, + propagateFrames, + propagateUpToFrame, + propagateObject, + changePropagateFrames, + changeUpToFrame, + cancel, + } = props; + + return ( + +
+ Do you want to make a copy of the object on + + { + propagateFrames > 1 + ? frames + : frame + } + up to the + + frame +
+
+ ); +} diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx new file mode 100644 index 00000000000..83e2c70d664 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx @@ -0,0 +1,28 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import './styles.scss'; +import React from 'react'; + +import { + Layout, +} from 'antd'; + +import CanvasWrapperContainer from 'containers/annotation-page/standard-workspace/canvas-wrapper'; +import ControlsSideBarContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/controls-side-bar'; +import ObjectSideBarContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/objects-side-bar'; +import PropagateConfirmContainer from 'containers/annotation-page/standard-workspace/propagate-confirm'; +import CanvasContextMenuContainer from 'containers/annotation-page/standard-workspace/canvas-context-menu'; + +export default function StandardWorkspaceComponent(): JSX.Element { + return ( + + + + + + + + ); +} diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss new file mode 100644 index 00000000000..ed07a9056f3 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss @@ -0,0 +1,176 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +@import 'base.scss'; + +.cvat-canvas-container { + background-color: $background-color-1; +} + +.cvat-objects-sidebar-sider { + top: 0px; + right: 0px; + left: auto; + background-color: $background-color-2; + border-left: 1px solid $border-color-1; + z-index: 2; +} + +.cvat-objects-sidebar { + height: 100%; +} + +.cvat-canvas-controls-sidebar { + background-color: $background-color-2; + border-right: 1px solid $border-color-1; + + > div { + > i { + border-radius: 3.3px; + transform: scale(0.65); + padding: 2px; + + &:hover { + background: $header-color; + transform: scale(0.75); + } + + &:active { + transform: scale(0.65); + } + + > svg { + transform: scale(0.8); + } + } + } +} + +.cvat-active-canvas-control { + background: $header-color; + transform: scale(0.75); +} + +.cvat-rotate-canvas-controls-left, +.cvat-rotate-canvas-controls-right { + transform: scale(0.65); + border-radius: 5px; + + &:hover { + transform: scale(0.75); + } + &:active { + transform: scale(0.65); + } +} + +.cvat-rotate-canvas-controls > +.ant-popover-content > +.ant-popover-inner > div > +.ant-popover-inner-content { + padding: 0px; +} + +.cvat-rotate-canvas-controls-right > svg { + transform: scaleX(-1); +} + +.cvat-draw-shape-popover > +.ant-popover-content > +.ant-popover-inner > div > +.ant-popover-inner-content { + padding: 0px; +} + +.cvat-draw-shape-popover-points-selector { + width: 100%; +} + +.cvat-draw-shape-popover-content { + padding: 10px; + border-radius: 5px; + background: $background-color-2; + width: 250px; + > div { + margin-top: 5px; + } + > div:nth-child(3) > div > div { + width: 100%; + } + div:last-child > div > button { + width: 100%; + &:nth-child(1) { + border-radius: 3px 0px 0px 3px; + } + &:nth-child(2) { + border-radius: 0px 3px 3px 0px; + } + } +} + +.cvat-propagate-confirm { + > .ant-input-number { + width: 70px; + margin: 0px 5px; + } +} + +.cvat-canvas-context-menu { + opacity: 0.6; + position: fixed; + width: 300px; + z-index: 10; + max-height: 50%; + overflow-y: auto; + + &:hover { + opacity: 1; + } +} + +.cvat-canvas-z-axis-wrapper { + position: absolute; + background: $background-color-2; + bottom: 10px; + right: 10px; + height: 150px; + z-index: 100; + border-radius: 6px; + opacity: 0.5; + border: 1px solid $border-color-3; + display: flex; + flex-direction: column; + justify-content: space-between; + padding: 3px; + + &:hover { + opacity: 1; + } + + > .ant-slider { + height: 75%; + margin: 5px 3px; + + > .ant-slider-rail { + background-color: #979797; + } + + > .ant-slider-handle { + transform: none !important; + } + } + + > i { + opacity: 0.7; + color: $objects-bar-icons-color; + + &:hover { + opacity: 1; + } + + &:active { + opacity: 0.7; + } + } +} diff --git a/cvat-ui/src/components/annotation-page/styles.scss b/cvat-ui/src/components/annotation-page/styles.scss new file mode 100644 index 00000000000..366253b4e28 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/styles.scss @@ -0,0 +1,215 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +@import '../../base.scss'; + +.cvat-annotation-page.ant-layout { + height: 100% +} + +.ant-layout-header.cvat-annotation-header { + background-color: $background-color-2; + border-bottom: 1px solid $border-color-1; + height: 54px; + padding: 0px; +} + +.cvat-annotation-header-left-group { + > button:first-child { + filter: invert(0.9); + background: $background-color-1; + border-radius: 0px; + width: 70px; + } +} + +.ant-btn.cvat-annotation-header-button { + padding: 0px; + width: 54px; + height: 54px; + float: left; + text-align: center; + user-select: none; + color: $text-color; + display: flex; + flex-direction: column; + align-items: center; + margin: 0px 3px; + + > span { + margin-left: 0px; + font-size: 10px; + } + + > i { + transform: scale(0.8); + padding: 3px; + } + + &:hover > i { + transform: scale(0.85); + } + + &:active > i { + transform: scale(0.8); + } + + > * { + display: block; + line-height: 0px; + } +} + +.cvat-annotation-disabled-header-button { + @extend .cvat-annotation-header-button; + opacity: 0.5; + pointer-events: none; +} + +.cvat-annotation-header-player-group > div { + height: 54px; + line-height: 0px; +} + +.cvat-player-buttons { + display: flex; + align-items: center; + position: relative; + height: 100%; + margin-right: 10px; + + > i { + font-size: 25px; + margin: 0px 7px; + color: $player-buttons-color; + + &:hover { + transform: scale(1.1); + } + + &:active { + transform: scale(1); + } + } +} + +.cvat-player-controls { + position: relative; + height: 100%; + line-height: 27px; + + > div { + position: relative; + height: 50%; + } +} + +.cvat-player-slider { + width: 350px; + + > .ant-slider-rail { + background-color: $player-slider-color; + } + +} + +.cvat-player-filename-wrapper { + max-width: 300px; + overflow: hidden; + text-overflow: ellipsis; + user-select: none; +} + +.cvat-player-frame-url-icon { + opacity: 0.7; + color: $objects-bar-icons-color; + + &:hover { + opacity: 1; + } + + &:active { + opacity: 0.7; + } +} + +.cvat-player-frame-selector { + width: 5em; + padding-right: 5px; + margin-left: 5px; +} + +.cvat-annotation-header-right-group { + > div { + float: left; + display: block; + height: 54px; + margin-right: 15px; + } +} + +.cvat-workspace-selector { + width: 150px; +} + +.cvat-job-info-modal-window { + > div { + margin-top: 10px; + } + + > div:nth-child(1) { + > div { + > .ant-select, i { + margin-left: 10px; + } + } + } + + > div:nth-child(2) { + > div { + > span { + font-size: 20px; + } + } + } + + > div:nth-child(3) { + > div { + display: grid; + } + } + + > .cvat-job-info-bug-tracker { + > div { + display: grid; + } + } + + > .cvat-job-info-statistics { + > div { + > span { + font-size: 20px; + } + + .ant-table-thead { + > tr > th { + padding: 5px 5px; + } + } + } + } +} + +.ant-menu.cvat-annotation-menu { + box-shadow: 0 0 17px rgba(0,0,0,0.2); + + > li:hover { + background-color: $hover-menu-color; + } + + .ant-menu-submenu-title { + margin: 0px; + width: 15em; + } +} diff --git a/cvat-ui/src/components/annotation-page/top-bar/annotation-menu.tsx b/cvat-ui/src/components/annotation-page/top-bar/annotation-menu.tsx new file mode 100644 index 00000000000..7b8d7aa15c6 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/top-bar/annotation-menu.tsx @@ -0,0 +1,129 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Menu, Modal, +} from 'antd'; + +import { ClickParam } from 'antd/lib/menu/index'; + +import DumpSubmenu from 'components/actions-menu/dump-submenu'; +import LoadSubmenu from 'components/actions-menu/load-submenu'; +import ExportSubmenu from 'components/actions-menu/export-submenu'; + +interface Props { + taskMode: string; + loaders: string[]; + dumpers: string[]; + exporters: string[]; + loadActivity: string | null; + dumpActivities: string[] | null; + exportActivities: string[] | null; + onClickMenu(params: ClickParam, file?: File): void; +} + +export enum Actions { + DUMP_TASK_ANNO = 'dump_task_anno', + LOAD_JOB_ANNO = 'load_job_anno', + EXPORT_TASK_DATASET = 'export_task_dataset', + REMOVE_ANNO = 'remove_anno', + OPEN_TASK = 'open_task', +} + +export default function AnnotationMenuComponent(props: Props): JSX.Element { + const { + taskMode, + loaders, + dumpers, + exporters, + onClickMenu, + loadActivity, + dumpActivities, + exportActivities, + } = props; + + let latestParams: ClickParam | null = null; + function onClickMenuWrapper(params: ClickParam | null, file?: File): void { + const copyParams = params || latestParams; + if (!copyParams) { + return; + } + latestParams = params; + + if (copyParams.keyPath.length === 2) { + const [, action] = copyParams.keyPath; + if (action === Actions.LOAD_JOB_ANNO) { + if (file) { + Modal.confirm({ + title: 'Current annotation will be lost', + content: 'You are going to upload new annotations to this job. Continue?', + onOk: () => { + onClickMenu(copyParams, file); + }, + okButtonProps: { + type: 'danger', + }, + okText: 'Update', + }); + } + } else { + onClickMenu(copyParams); + } + } else if (copyParams.key === Actions.REMOVE_ANNO) { + Modal.confirm({ + title: 'All annotations will be removed', + content: 'You are goung to remove all annotations from the client. ' + + 'It will stay on the server till you save a job. Continue?', + onOk: () => { + onClickMenu(copyParams); + }, + okButtonProps: { + type: 'danger', + }, + okText: 'Delete', + }); + } else { + onClickMenu(copyParams); + } + } + + return ( + + { + DumpSubmenu({ + taskMode, + dumpers, + dumpActivities, + menuKey: Actions.DUMP_TASK_ANNO, + }) + } + { + LoadSubmenu({ + loaders, + loadActivity, + onFileUpload: (file: File): void => { + onClickMenuWrapper(null, file); + }, + menuKey: Actions.LOAD_JOB_ANNO, + }) + } + { + ExportSubmenu({ + exporters, + exportActivities, + menuKey: Actions.EXPORT_TASK_DATASET, + }) + } + + + Remove annotations + + + Open the task + + + ); +} diff --git a/cvat-ui/src/components/annotation-page/top-bar/left-group.tsx b/cvat-ui/src/components/annotation-page/top-bar/left-group.tsx new file mode 100644 index 00000000000..36eee40b18a --- /dev/null +++ b/cvat-ui/src/components/annotation-page/top-bar/left-group.tsx @@ -0,0 +1,107 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Col, + Icon, + Modal, + Button, + Timeline, + Dropdown, +} from 'antd'; + +import AnnotationMenuContainer from 'containers/annotation-page/top-bar/annotation-menu'; + +import { + MainMenuIcon, + SaveIcon, + UndoIcon, + RedoIcon, +} from '../../../icons'; + +interface Props { + saving: boolean; + savingStatuses: string[]; + undoAction?: string; + redoAction?: string; + onSaveAnnotation(): void; + onUndoClick(): void; + onRedoClick(): void; +} + +function LeftGroup(props: Props): JSX.Element { + const { + saving, + savingStatuses, + undoAction, + redoAction, + onSaveAnnotation, + onUndoClick, + onRedoClick, + } = props; + + return ( + + }> + + + + + + + ); +} + +export default React.memo(LeftGroup); diff --git a/cvat-ui/src/components/annotation-page/top-bar/player-buttons.tsx b/cvat-ui/src/components/annotation-page/top-bar/player-buttons.tsx new file mode 100644 index 00000000000..893f2c921c2 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/top-bar/player-buttons.tsx @@ -0,0 +1,90 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Col, + Icon, + Tooltip, +} from 'antd'; + +import { + FirstIcon, + BackJumpIcon, + PreviousIcon, + PlayIcon, + PauseIcon, + NextIcon, + ForwardJumpIcon, + LastIcon, +} from '../../../icons'; + +interface Props { + playing: boolean; + onSwitchPlay(): void; + onPrevFrame(): void; + onNextFrame(): void; + onForward(): void; + onBackward(): void; + onFirstFrame(): void; + onLastFrame(): void; +} + +function PlayerButtons(props: Props): JSX.Element { + const { + playing, + onSwitchPlay, + onPrevFrame, + onNextFrame, + onForward, + onBackward, + onFirstFrame, + onLastFrame, + } = props; + + return ( + + + + + + + + + + + + {!playing + ? ( + + + + ) + : ( + + + + )} + + + + + + + + + + + + ); +} + +export default React.memo(PlayerButtons); diff --git a/cvat-ui/src/components/annotation-page/top-bar/player-navigation.tsx b/cvat-ui/src/components/annotation-page/top-bar/player-navigation.tsx new file mode 100644 index 00000000000..397bf8d6918 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/top-bar/player-navigation.tsx @@ -0,0 +1,81 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Row, + Col, + Icon, + Slider, + Tooltip, + InputNumber, +} from 'antd'; + +import { SliderValue } from 'antd/lib/slider'; +import Text from 'antd/lib/typography/Text'; + +interface Props { + startFrame: number; + stopFrame: number; + frameNumber: number; + inputFrameRef: React.RefObject; + onSliderChange(value: SliderValue): void; + onInputChange(value: number | undefined): void; + onURLIconClick(): void; +} + +function PlayerNavigation(props: Props): JSX.Element { + const { + startFrame, + stopFrame, + frameNumber, + inputFrameRef, + onSliderChange, + onInputChange, + onURLIconClick, + } = props; + + return ( + <> + + + + + + + + + + filename.png + + + + + + + + + + + + + + ); +} + +export default React.memo(PlayerNavigation); diff --git a/cvat-ui/src/components/annotation-page/top-bar/right-group.tsx b/cvat-ui/src/components/annotation-page/top-bar/right-group.tsx new file mode 100644 index 00000000000..7e39eff7be1 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/top-bar/right-group.tsx @@ -0,0 +1,58 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Col, + Icon, + Select, + Button, +} from 'antd'; + +import { + InfoIcon, + FullscreenIcon, +} from '../../../icons'; + +interface Props { + showStatistics(): void; +} + +function RightGroup(props: Props): JSX.Element { + const { showStatistics } = props; + + return ( + + + +
+ +
+ + ); +} + +export default React.memo(RightGroup); diff --git a/cvat-ui/src/components/annotation-page/top-bar/statistics-modal.tsx b/cvat-ui/src/components/annotation-page/top-bar/statistics-modal.tsx new file mode 100644 index 00000000000..2388612dc1a --- /dev/null +++ b/cvat-ui/src/components/annotation-page/top-bar/statistics-modal.tsx @@ -0,0 +1,207 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Tooltip, + Select, + Table, + Modal, + Spin, + Icon, + Row, + Col, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; + +interface Props { + collecting: boolean; + data: any; + visible: boolean; + assignee: string; + startFrame: number; + stopFrame: number; + zOrder: boolean; + bugTracker: string; + jobStatus: string; + savingJobStatus: boolean; + closeStatistics(): void; + changeJobStatus(status: string): void; +} + +export default function StatisticsModalComponent(props: Props): JSX.Element { + const { + collecting, + data, + visible, + jobStatus, + assignee, + startFrame, + stopFrame, + zOrder, + bugTracker, + closeStatistics, + changeJobStatus, + savingJobStatus, + } = props; + + const baseProps = { + cancelButtonProps: { style: { display: 'none' } }, + okButtonProps: { style: { width: 100 } }, + onOk: closeStatistics, + width: 1000, + visible, + closable: false, + }; + + if (collecting || !data) { + return ( + + + + ); + } + + const rows = Object.keys(data.label).map((key: string) => ({ + key, + label: key, + rectangle: `${data.label[key].rectangle.shape} / ${data.label[key].rectangle.track}`, + polygon: `${data.label[key].polygon.shape} / ${data.label[key].polygon.track}`, + polyline: `${data.label[key].polyline.shape} / ${data.label[key].polyline.track}`, + points: `${data.label[key].points.shape} / ${data.label[key].points.track}`, + tags: data.label[key].tags, + manually: data.label[key].manually, + interpolated: data.label[key].interpolated, + total: data.label[key].total, + })); + + rows.push({ + key: '___total', + label: 'Total', + rectangle: `${data.total.rectangle.shape} / ${data.total.rectangle.track}`, + polygon: `${data.total.polygon.shape} / ${data.total.polygon.track}`, + polyline: `${data.total.polyline.shape} / ${data.total.polyline.track}`, + points: `${data.total.points.shape} / ${data.total.points.track}`, + tags: data.total.tags, + manually: data.total.manually, + interpolated: data.total.interpolated, + total: data.total.total, + }); + + const makeShapesTracksTitle = (title: string): JSX.Element => ( + + {title} + + + ); + + const columns = [{ + title: Label , + dataIndex: 'label', + key: 'label', + }, { + title: makeShapesTracksTitle('Rectangle'), + dataIndex: 'rectangle', + key: 'rectangle', + }, { + title: makeShapesTracksTitle('Polygon'), + dataIndex: 'polygon', + key: 'polygon', + }, { + title: makeShapesTracksTitle('Polyline'), + dataIndex: 'polyline', + key: 'polyline', + }, { + title: makeShapesTracksTitle('Points'), + dataIndex: 'points', + key: 'points', + }, { + title: Tags , + dataIndex: 'tags', + key: 'tags', + }, { + title: Manually , + dataIndex: 'manually', + key: 'manually', + }, { + title: Interpolated , + dataIndex: 'interpolated', + key: 'interpolated', + }, { + title: Total , + dataIndex: 'total', + key: 'total', + }]; + + return ( + +
+ + + Job status + + {savingJobStatus && } + + + + + Overview + + + + + Assignee + {assignee} + + + Start frame + {startFrame} + + + Stop frame + {stopFrame} + + + Frames + {stopFrame - startFrame + 1} + + + Z-Order + {zOrder.toString()} + + + { !!bugTracker && ( + + + Bug tracker + {bugTracker} + + + )} + + + Annotations statistics + + + + + + ); +} diff --git a/cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx b/cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx new file mode 100644 index 00000000000..a358720303f --- /dev/null +++ b/cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx @@ -0,0 +1,113 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Row, + Col, + Layout, + InputNumber, +} from 'antd'; + +import { SliderValue } from 'antd/lib/slider'; + +import LeftGroup from './left-group'; +import RightGroup from './right-group'; +import PlayerNavigation from './player-navigation'; +import PlayerButtons from './player-buttons'; + +interface Props { + playing: boolean; + saving: boolean; + savingStatuses: string[]; + frameNumber: number; + inputFrameRef: React.RefObject; + startFrame: number; + stopFrame: number; + undoAction?: string; + redoAction?: string; + showStatistics(): void; + onSwitchPlay(): void; + onSaveAnnotation(): void; + onPrevFrame(): void; + onNextFrame(): void; + onForward(): void; + onBackward(): void; + onFirstFrame(): void; + onLastFrame(): void; + onSliderChange(value: SliderValue): void; + onInputChange(value: number | undefined): void; + onURLIconClick(): void; + onUndoClick(): void; + onRedoClick(): void; +} + +export default function AnnotationTopBarComponent(props: Props): JSX.Element { + const { + saving, + savingStatuses, + undoAction, + redoAction, + playing, + frameNumber, + inputFrameRef, + startFrame, + stopFrame, + showStatistics, + onSwitchPlay, + onSaveAnnotation, + onPrevFrame, + onNextFrame, + onForward, + onBackward, + onFirstFrame, + onLastFrame, + onSliderChange, + onInputChange, + onURLIconClick, + onUndoClick, + onRedoClick, + } = props; + + return ( + + + + + + + + + + + + + ); +} diff --git a/cvat-ui/src/components/app/app.scss b/cvat-ui/src/components/app/app.scss deleted file mode 100644 index 34b8ee09c60..00000000000 --- a/cvat-ui/src/components/app/app.scss +++ /dev/null @@ -1,3 +0,0 @@ -.App { - -} diff --git a/cvat-ui/src/components/app/app.test.tsx b/cvat-ui/src/components/app/app.test.tsx deleted file mode 100644 index 21ab2b40c66..00000000000 --- a/cvat-ui/src/components/app/app.test.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; - -import App from './app'; - - -it('renders without crashing', () => { - const div = document.createElement('div'); - ReactDOM.render(, div); - ReactDOM.unmountComponentAtNode(div); -}); diff --git a/cvat-ui/src/components/app/app.tsx b/cvat-ui/src/components/app/app.tsx deleted file mode 100644 index 8df1edb68ac..00000000000 --- a/cvat-ui/src/components/app/app.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import React, { PureComponent } from 'react'; -import { BrowserRouter as Router, Route, Switch, Redirect } from 'react-router-dom'; - -import { connect } from 'react-redux'; - -import HeaderLayout from '../header-layout/header-layout'; - -import TasksPage from '../tasks-page/tasks-page'; -import LoginPage from '../login-page/login-page'; -import RegisterPage from '../register-page/register-page'; -import PageNotFound from '../page-not-found/page-not-found'; - -import './app.scss'; - - -const ProtectedRoute = ({ component: Component, ...rest }: any) => { - return ( - { - return rest.isAuthenticated ? ( - <> - - - - ) : ( - - ); - } } - /> - ); -}; - -class App extends PureComponent { - componentDidMount() { - (window as any).cvat.config.backendAPI = process.env.REACT_APP_API_FULL_URL; - } - - render() { - return( - - - - - - - - - - ); - } -} - -const mapStateToProps = (state: any) => { - return state.authContext; -}; - -export default connect(mapStateToProps)(App); diff --git a/cvat-ui/src/components/create-model-page/create-model-content.tsx b/cvat-ui/src/components/create-model-page/create-model-content.tsx new file mode 100644 index 00000000000..03efda5fff9 --- /dev/null +++ b/cvat-ui/src/components/create-model-page/create-model-content.tsx @@ -0,0 +1,164 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Row, + Col, + Icon, + Alert, + Button, + Tooltip, + message, + notification, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; + +import ConnectedFileManager, { + FileManagerContainer, +} from 'containers/file-manager/file-manager'; +import { ModelFiles } from 'reducers/interfaces'; + +import CreateModelForm, { + CreateModelForm as WrappedCreateModelForm, +} from './create-model-form'; + +interface Props { + createModel(name: string, files: ModelFiles, global: boolean): void; + isAdmin: boolean; + modelCreatingStatus: string; +} + +export default class CreateModelContent extends React.PureComponent { + private modelForm: WrappedCreateModelForm; + private fileManagerContainer: FileManagerContainer; + + public constructor(props: Props) { + super(props); + this.modelForm = null as any as WrappedCreateModelForm; + this.fileManagerContainer = null as any as FileManagerContainer; + } + + public componentDidUpdate(prevProps: Props): void { + const { modelCreatingStatus } = this.props; + + if (prevProps.modelCreatingStatus !== 'CREATED' + && modelCreatingStatus === 'CREATED') { + message.success('The model has been uploaded'); + this.modelForm.resetFields(); + this.fileManagerContainer.reset(); + } + } + + private handleSubmitClick = (): void => { + const { createModel } = this.props; + this.modelForm.submit() + .then((data) => { + const { + local, + share, + } = this.fileManagerContainer.getFiles(); + + const files = local.length ? local : share; + const grouppedFiles: ModelFiles = { + xml: '', + bin: '', + py: '', + json: '', + }; + + (files as any).reduce((acc: ModelFiles, value: File | string): ModelFiles => { + const name = typeof value === 'string' ? value : value.name; + const [extension] = name.split('.').reverse(); + if (extension in acc) { + acc[extension] = value; + } + + return acc; + }, grouppedFiles); + + if (Object.keys(grouppedFiles) + .map((key: string) => grouppedFiles[key]) + .filter((val) => !!val).length !== 4) { + notification.error({ + message: 'Could not upload a model', + description: 'Please, specify correct files', + }); + } else { + createModel(data.name, grouppedFiles, data.global); + } + }).catch(() => { + notification.error({ + message: 'Could not upload a model', + description: 'Please, check input fields', + }); + }); + }; + + public render(): JSX.Element { + const { + modelCreatingStatus, + } = this.props; + const loading = !!modelCreatingStatus + && modelCreatingStatus !== 'CREATED'; + const status = modelCreatingStatus + && modelCreatingStatus !== 'CREATED' ? modelCreatingStatus : ''; + + const guideLink = 'https://github.com/opencv/cvat/blob/develop/cvat/apps/auto_annotation/README.md'; + return ( + + + + { + // false positive + // eslint-disable-next-line + window.open(guideLink, '_blank'); + }} + type='question-circle' + /> + + + + { + this.modelForm = ref; + } + } + /> + + + * + Select files: + + + { + this.fileManagerContainer = container; + } + } + withRemote={false} + /> + + + {status && } + + + + + + ); + } +} diff --git a/cvat-ui/src/components/create-model-page/create-model-form.tsx b/cvat-ui/src/components/create-model-page/create-model-form.tsx new file mode 100644 index 00000000000..7db0c68ac59 --- /dev/null +++ b/cvat-ui/src/components/create-model-page/create-model-form.tsx @@ -0,0 +1,86 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Row, + Col, + Form, + Input, + Tooltip, + Checkbox, +} from 'antd'; + +import { FormComponentProps } from 'antd/lib/form/Form'; +import Text from 'antd/lib/typography/Text'; + +type Props = FormComponentProps; + +export class CreateModelForm extends React.PureComponent { + public submit(): Promise<{name: string; global: boolean}> { + const { form } = this.props; + return new Promise((resolve, reject) => { + form.validateFields((errors, values): void => { + if (!errors) { + resolve({ + name: values.name, + global: values.global, + }); + } else { + reject(errors); + } + }); + }); + } + + public resetFields(): void { + const { form } = this.props; + form.resetFields(); + } + + public render(): JSX.Element { + const { form } = this.props; + const { getFieldDecorator } = form; + + return ( +
e.preventDefault()}> + +
+ * + Name: + + + + { getFieldDecorator('name', { + rules: [{ + required: true, + message: 'Please, specify a model name', + }], + })()} + + + + + + { getFieldDecorator('global', { + initialValue: false, + valuePropName: 'checked', + })( + + + Load globally + + , + )} + + + + + + ); + } +} + +export default Form.create()(CreateModelForm); diff --git a/cvat-ui/src/components/create-model-page/create-model-page.tsx b/cvat-ui/src/components/create-model-page/create-model-page.tsx new file mode 100644 index 00000000000..88e7d3123a4 --- /dev/null +++ b/cvat-ui/src/components/create-model-page/create-model-page.tsx @@ -0,0 +1,42 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import './styles.scss'; +import React from 'react'; + +import { + Row, + Col, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; +import { ModelFiles } from 'reducers/interfaces'; +import CreateModelContent from './create-model-content'; + +interface Props { + createModel(name: string, files: ModelFiles, global: boolean): void; + isAdmin: boolean; + modelCreatingStatus: string; +} + +export default function CreateModelPageComponent(props: Props): JSX.Element { + const { + isAdmin, + modelCreatingStatus, + createModel, + } = props; + + return ( + + + Upload a new model + + + + ); +} diff --git a/cvat-ui/src/components/create-model-page/styles.scss b/cvat-ui/src/components/create-model-page/styles.scss new file mode 100644 index 00000000000..65df22b23d3 --- /dev/null +++ b/cvat-ui/src/components/create-model-page/styles.scss @@ -0,0 +1,43 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +@import '../../base.scss'; + +.cvat-create-model-form-wrapper { + text-align: center; + margin-top: 40px; + overflow-y: auto; + height: 90%; + + > div > span { + font-size: 36px; + } + + .cvat-create-model-content { + margin-top: 20px; + width: 100%; + height: auto; + border: 1px solid $border-color-1; + border-radius: 3px; + padding: 20px; + background: $background-color-1; + text-align: initial; + + > div:nth-child(1) > i { + float: right; + font-size: 20px; + color: $danger-icon-color; + } + + > div:nth-child(4) { + margin-top: 10px; + } + + > div:nth-child(6) > button { + margin-top: 10px; + float: right; + width: 120px; + } + } +} diff --git a/cvat-ui/src/components/create-task-page/advanced-configuration-form.tsx b/cvat-ui/src/components/create-task-page/advanced-configuration-form.tsx new file mode 100644 index 00000000000..7a769a1274b --- /dev/null +++ b/cvat-ui/src/components/create-task-page/advanced-configuration-form.tsx @@ -0,0 +1,339 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Row, + Col, + Icon, + Input, + Checkbox, + Tooltip, +} from 'antd'; + +import Form, { FormComponentProps } from 'antd/lib/form/Form'; +import Text from 'antd/lib/typography/Text'; + +import patterns from 'utils/validation-patterns'; + +export interface AdvancedConfiguration { + bugTracker?: string; + zOrder: boolean; + imageQuality?: number; + overlapSize?: number; + segmentSize?: number; + startFrame?: number; + stopFrame?: number; + frameFilter?: string; + lfs: boolean; + repository?: string; +} + +type Props = FormComponentProps & { + onSubmit(values: AdvancedConfiguration): void; + installedGit: boolean; +}; + +class AdvancedConfigurationForm extends React.PureComponent { + public submit(): Promise { + return new Promise((resolve, reject) => { + const { + form, + onSubmit, + } = this.props; + + form.validateFields((error, values): void => { + if (!error) { + const filteredValues = { ...values }; + delete filteredValues.frameStep; + + onSubmit({ + ...values, + frameFilter: values.frameStep ? `step=${values.frameStep}` : undefined, + }); + resolve(); + } else { + reject(); + } + }); + }); + } + + public resetFields(): void { + const { form } = this.props; + form.resetFields(); + } + + private renderZOrder(): JSX.Element { + const { form } = this.props; + return ( + + {form.getFieldDecorator('zOrder', { + initialValue: false, + valuePropName: 'checked', + })( + + + Z-order + + , + )} + + ); + } + + private renderImageQuality(): JSX.Element { + const { form } = this.props; + + return ( + Image quality}> + + {form.getFieldDecorator('imageQuality', { + initialValue: 70, + rules: [{ + required: true, + message: 'This field is required', + }], + })( + } + />, + )} + + + ); + } + + private renderOverlap(): JSX.Element { + const { form } = this.props; + + return ( + Overlap size}> + + {form.getFieldDecorator('overlapSize')( + , + )} + + + ); + } + + private renderSegmentSize(): JSX.Element { + const { form } = this.props; + + return ( + Segment size}> + + {form.getFieldDecorator('segmentSize')( + , + )} + + + ); + } + + private renderStartFrame(): JSX.Element { + const { form } = this.props; + + return ( + Start frame}> + {form.getFieldDecorator('startFrame')( + , + )} + + ); + } + + private renderStopFrame(): JSX.Element { + const { form } = this.props; + + return ( + Stop frame}> + {form.getFieldDecorator('stopFrame')( + , + )} + + ); + } + + private renderFrameStep(): JSX.Element { + const { form } = this.props; + + return ( + Frame step}> + {form.getFieldDecorator('frameStep')( + , + )} + + ); + } + + private renderGitLFSBox(): JSX.Element { + const { form } = this.props; + + return ( + + {form.getFieldDecorator('lfs', { + valuePropName: 'checked', + initialValue: false, + })( + + + Use LFS (Large File Support): + + , + )} + + ); + } + + private renderGitRepositoryURL(): JSX.Element { + const { form } = this.props; + + return ( + Dataset repository URL} + extra='Attach a repository to store annotations there' + > + {form.getFieldDecorator('repository', { + rules: [{ + validator: (_, value, callback): void => { + if (!value) { + callback(); + } else { + const [url, path] = value.split(/\s+/); + if (!patterns.validateURL.pattern.test(url)) { + callback('Git URL is not a valid'); + } + + if (path && !patterns.validatePath.pattern.test(path)) { + callback('Git path is not a valid'); + } + + callback(); + } + }, + }], + })( + , + )} + + ); + } + + private renderGit(): JSX.Element { + return ( + <> + + + {this.renderGitRepositoryURL()} + + + + + {this.renderGitLFSBox()} + + + + ); + } + + private renderBugTracker(): JSX.Element { + const { form } = this.props; + + return ( + Issue tracker} + extra='Attach issue tracker where the task is described' + > + {form.getFieldDecorator('bugTracker', { + rules: [{ + validator: (_, value, callback): void => { + if (value && !patterns.validateURL.pattern.test(value)) { + callback('Issue tracker must be URL'); + } else { + callback(); + } + }, + }], + })( + , + )} + + ); + } + + public render(): JSX.Element { + const { installedGit } = this.props; + + return ( + + + + {this.renderZOrder()} + + + + + + {this.renderImageQuality()} + + + {this.renderOverlap()} + + + {this.renderSegmentSize()} + + + + + + {this.renderStartFrame()} + + + {this.renderStopFrame()} + + + {this.renderFrameStep()} + + + + { installedGit ? this.renderGit() : null} + + + + {this.renderBugTracker()} + + + + ); + } +} + +export default Form.create()(AdvancedConfigurationForm); diff --git a/cvat-ui/src/components/create-task-page/basic-configuration-form.tsx b/cvat-ui/src/components/create-task-page/basic-configuration-form.tsx new file mode 100644 index 00000000000..bcc058fa0b1 --- /dev/null +++ b/cvat-ui/src/components/create-task-page/basic-configuration-form.tsx @@ -0,0 +1,68 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Input, +} from 'antd'; + +import Form, { FormComponentProps } from 'antd/lib/form/Form'; + +export interface BaseConfiguration { + name: string; +} + +type Props = FormComponentProps & { + onSubmit(values: BaseConfiguration): void; +}; + +class BasicConfigurationForm extends React.PureComponent { + public submit(): Promise { + return new Promise((resolve, reject) => { + const { + form, + onSubmit, + } = this.props; + + form.validateFields((error, values): void => { + if (!error) { + onSubmit({ + name: values.name, + }); + resolve(); + } else { + reject(); + } + }); + }); + } + + public resetFields(): void { + const { form } = this.props; + form.resetFields(); + } + + public render(): JSX.Element { + const { form } = this.props; + const { getFieldDecorator } = form; + + return ( +
e.preventDefault()}> + Name}> + { getFieldDecorator('name', { + rules: [{ + required: true, + message: 'Please, specify a name', + }], + })( + , + ) } + + + ); + } +} + +export default Form.create()(BasicConfigurationForm); diff --git a/cvat-ui/src/components/create-task-page/create-task-content.tsx b/cvat-ui/src/components/create-task-page/create-task-content.tsx new file mode 100644 index 00000000000..4bf825edace --- /dev/null +++ b/cvat-ui/src/components/create-task-page/create-task-content.tsx @@ -0,0 +1,258 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Row, + Col, + Alert, + Button, + Collapse, + notification, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; + +import FileManagerContainer from 'containers/file-manager/file-manager'; +import BasicConfigurationForm, { BaseConfiguration } from './basic-configuration-form'; +import AdvancedConfigurationForm, { AdvancedConfiguration } from './advanced-configuration-form'; +import LabelsEditor from '../labels-editor/labels-editor'; +import { Files } from '../file-manager/file-manager'; + +export interface CreateTaskData { + basic: BaseConfiguration; + advanced: AdvancedConfiguration; + labels: any[]; + files: Files; +} + +interface Props { + onCreate: (data: CreateTaskData) => void; + status: string; + installedGit: boolean; +} + +type State = CreateTaskData; + +const defaultState = { + basic: { + name: '', + }, + advanced: { + zOrder: false, + lfs: false, + }, + labels: [], + files: { + local: [], + share: [], + remote: [], + }, +}; + +export default class CreateTaskContent extends React.PureComponent { + private basicConfigurationComponent: any; + private advancedConfigurationComponent: any; + private fileManagerContainer: any; + + public constructor(props: Props) { + super(props); + this.state = { ...defaultState }; + } + + public componentDidUpdate(prevProps: Props): void { + const { status } = this.props; + + if (status === 'CREATED' && prevProps.status !== 'CREATED') { + notification.info({ + message: 'The task has been created', + }); + + this.basicConfigurationComponent.resetFields(); + if (this.advancedConfigurationComponent) { + this.advancedConfigurationComponent.resetFields(); + } + + this.fileManagerContainer.reset(); + + this.setState({ + ...defaultState, + }); + } + } + + private validateLabels = (): boolean => { + const { labels } = this.state; + return !!labels.length; + }; + + private validateFiles = (): boolean => { + const files = this.fileManagerContainer.getFiles(); + this.setState({ + files, + }); + const totalLen = Object.keys(files).reduce( + (acc, key) => acc + files[key].length, 0, + ); + + return !!totalLen; + }; + + private handleSubmitBasicConfiguration = (values: BaseConfiguration): void => { + this.setState({ + basic: { ...values }, + }); + }; + + private handleSubmitAdvancedConfiguration = (values: AdvancedConfiguration): void => { + this.setState({ + advanced: { ...values }, + }); + }; + + private handleSubmitClick = (): void => { + if (!this.validateLabels()) { + notification.error({ + message: 'Could not create a task', + description: 'A task must contain at least one label', + }); + return; + } + + if (!this.validateFiles()) { + notification.error({ + message: 'Could not create a task', + description: 'A task must contain at least one file', + }); + return; + } + + this.basicConfigurationComponent.submit() + .then(() => { + if (this.advancedConfigurationComponent) { + return this.advancedConfigurationComponent.submit(); + } + + return new Promise((resolve): void => { + resolve(); + }); + }).then((): void => { + const { onCreate } = this.props; + onCreate(this.state); + }).catch((): void => { + notification.error({ + message: 'Could not create a task', + description: 'Please, check configuration you specified', + }); + }); + }; + + private renderBasicBlock(): JSX.Element { + return ( +
+ { this.basicConfigurationComponent = component; } + } + onSubmit={this.handleSubmitBasicConfiguration} + /> + + ); + } + + private renderLabelsBlock(): JSX.Element { + const { labels } = this.state; + + return ( + + * + Labels: + { + this.setState({ + labels: newLabels, + }); + } + } + /> + + ); + } + + private renderFilesBlock(): JSX.Element { + return ( + + * + Select files: + { this.fileManagerContainer = container; } + } + withRemote + /> + + ); + } + + private renderAdvancedBlock(): JSX.Element { + const { installedGit } = this.props; + return ( + + + Advanced configuration + } + > + { + this.advancedConfigurationComponent = component; + } + } + onSubmit={this.handleSubmitAdvancedConfiguration} + /> + + + + ); + } + + public render(): JSX.Element { + const { status } = this.props; + const loading = !!status && status !== 'CREATED' && status !== 'FAILED'; + + return ( + + + Basic configuration + + + { this.renderBasicBlock() } + { this.renderLabelsBlock() } + { this.renderFilesBlock() } + { this.renderAdvancedBlock() } + + + {loading ? : null} + + + + + + ); + } +} diff --git a/cvat-ui/src/components/create-task-page/create-task-page.tsx b/cvat-ui/src/components/create-task-page/create-task-page.tsx new file mode 100644 index 00000000000..7961841016d --- /dev/null +++ b/cvat-ui/src/components/create-task-page/create-task-page.tsx @@ -0,0 +1,42 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import './styles.scss'; +import React from 'react'; + +import { + Row, + Col, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; + +import CreateTaskContent, { CreateTaskData } from './create-task-content'; + +interface Props { + onCreate: (data: CreateTaskData) => void; + status: string; + installedGit: boolean; +} + +export default function CreateTaskPage(props: Props): JSX.Element { + const { + status, + onCreate, + installedGit, + } = props; + + return ( + + + Create a new task + + + + ); +} diff --git a/cvat-ui/src/components/create-task-page/styles.scss b/cvat-ui/src/components/create-task-page/styles.scss new file mode 100644 index 00000000000..3419d860834 --- /dev/null +++ b/cvat-ui/src/components/create-task-page/styles.scss @@ -0,0 +1,36 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +@import '../../base.scss'; + +.cvat-create-task-form-wrapper { + text-align: center; + margin-top: 40px; + overflow-y: auto; + height: 90%; + + > div > span { + font-size: 36px; + } + + .cvat-create-task-content { + margin-top: 20px; + width: 100%; + height: auto; + border: 1px solid $border-color-1; + border-radius: 3px; + padding: 20px; + background: $background-color-1; + text-align: initial; + + > div:not(first-child) { + margin-top: 10px; + } + + > div:nth-child(7) > button { + float: right; + width: 120px; + } + } +} diff --git a/cvat-ui/src/components/cvat-app.tsx b/cvat-ui/src/components/cvat-app.tsx new file mode 100644 index 00000000000..91795797980 --- /dev/null +++ b/cvat-ui/src/components/cvat-app.tsx @@ -0,0 +1,285 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import 'antd/dist/antd.less'; +import '../styles.scss'; +import React from 'react'; +import { Switch, Route, Redirect } from 'react-router'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; +import { GlobalHotKeys, KeyMap, configure } from 'react-hotkeys'; + +import { + Spin, + Layout, + notification, +} from 'antd'; + +import ShorcutsDialog from 'components/shortcuts-dialog/shortcuts-dialog'; +import SettingsPageContainer from 'containers/settings-page/settings-page'; +import TasksPageContainer from 'containers/tasks-page/tasks-page'; +import CreateTaskPageContainer from 'containers/create-task-page/create-task-page'; +import TaskPageContainer from 'containers/task-page/task-page'; +import ModelsPageContainer from 'containers/models-page/models-page'; +import CreateModelPageContainer from 'containers/create-model-page/create-model-page'; +import AnnotationPageContainer from 'containers/annotation-page/annotation-page'; +import LoginPageContainer from 'containers/login-page/login-page'; +import RegisterPageContainer from 'containers/register-page/register-page'; +import HeaderContainer from 'containers/header/header'; + +import { NotificationsState } from 'reducers/interfaces'; + +interface CVATAppProps { + loadFormats: () => void; + loadUsers: () => void; + loadAbout: () => void; + verifyAuthorized: () => void; + initPlugins: () => void; + resetErrors: () => void; + resetMessages: () => void; + switchShortcutsDialog: () => void; + userInitialized: boolean; + pluginsInitialized: boolean; + pluginsFetching: boolean; + formatsInitialized: boolean; + formatsFetching: boolean; + usersInitialized: boolean; + usersFetching: boolean; + aboutInitialized: boolean; + aboutFetching: boolean; + installedAutoAnnotation: boolean; + installedTFAnnotation: boolean; + installedTFSegmentation: boolean; + notifications: NotificationsState; + user: any; +} + +class CVATApplication extends React.PureComponent { + public componentDidMount(): void { + const { verifyAuthorized } = this.props; + configure({ ignoreRepeatedEventsWhenKeyHeldDown: false }); + verifyAuthorized(); + } + + public componentDidUpdate(): void { + const { + loadFormats, + loadUsers, + loadAbout, + initPlugins, + userInitialized, + formatsInitialized, + formatsFetching, + usersInitialized, + usersFetching, + aboutInitialized, + aboutFetching, + pluginsInitialized, + pluginsFetching, + user, + } = this.props; + + this.showErrors(); + this.showMessages(); + + if (!userInitialized || user == null) { + // not authorized user + return; + } + + if (!formatsInitialized && !formatsFetching) { + loadFormats(); + } + + if (!usersInitialized && !usersFetching) { + loadUsers(); + } + + if (!aboutInitialized && !aboutFetching) { + loadAbout(); + } + + if (!pluginsInitialized && !pluginsFetching) { + initPlugins(); + } + } + + private showMessages(): void { + function showMessage(title: string): void { + notification.info({ + message: ( +
+ ), + duration: null, + }); + } + + const { + notifications, + resetMessages, + } = this.props; + + let shown = false; + for (const where of Object.keys(notifications.messages)) { + for (const what of Object.keys(notifications.messages[where])) { + const message = notifications.messages[where][what]; + shown = shown || !!message; + if (message) { + showMessage(message); + } + } + } + + if (shown) { + resetMessages(); + } + } + + private showErrors(): void { + function showError(title: string, _error: any): void { + const error = _error.toString(); + notification.error({ + message: ( +
+ ), + duration: null, + description: error.length > 200 ? 'Open the Browser Console to get details' : error, + }); + + console.error(error); + } + + const { + notifications, + resetErrors, + } = this.props; + + let shown = false; + for (const where of Object.keys(notifications.errors)) { + for (const what of Object.keys(notifications.errors[where])) { + const error = notifications.errors[where][what]; + shown = shown || !!error; + if (error) { + showError(error.message, error.reason); + } + } + } + + if (shown) { + resetErrors(); + } + } + + // Where you go depends on your URL + public render(): JSX.Element { + const { + userInitialized, + usersInitialized, + aboutInitialized, + pluginsInitialized, + formatsInitialized, + installedAutoAnnotation, + installedTFSegmentation, + installedTFAnnotation, + switchShortcutsDialog, + user, + history, + } = this.props; + + const readyForRender = (userInitialized && user == null) + || (userInitialized && formatsInitialized + && pluginsInitialized && usersInitialized && aboutInitialized); + + const withModels = installedAutoAnnotation + || installedTFAnnotation || installedTFSegmentation; + + const keyMap = { + SWITCH_SHORTCUTS: { + name: 'Show shortcuts', + description: 'Open/hide the list of available shortcuts', + sequence: 'f1', + action: 'keydown', + }, + OPEN_SETTINGS: { + name: 'Open settings', + description: 'Go to the settings page or go back', + sequence: 'f2', + action: 'keydown', + }, + }; + + const handlers = { + SWITCH_SHORTCUTS: (event: KeyboardEvent | undefined) => { + if (event) { + event.preventDefault(); + } + + switchShortcutsDialog(); + }, + OPEN_SETTINGS: (event: KeyboardEvent | undefined) => { + if (event) { + event.preventDefault(); + } + + if (history.location.pathname.endsWith('settings')) { + history.goBack(); + } else { + history.push('/settings'); + } + }, + }; + + if (readyForRender) { + if (user) { + return ( + + + + + + + + + + + + { withModels + && } + { installedAutoAnnotation + && } + + + + {/* eslint-disable-next-line */} + + + + ); + } + + return ( + + + + + + ); + } + + return ( + + ); + } +} + +export default withRouter(CVATApplication); diff --git a/cvat-ui/src/components/feedback/feedback.tsx b/cvat-ui/src/components/feedback/feedback.tsx new file mode 100644 index 00000000000..b61ba1f549e --- /dev/null +++ b/cvat-ui/src/components/feedback/feedback.tsx @@ -0,0 +1,118 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import './styles.scss'; +import React from 'react'; + +import { + Button, + Icon, + Popover, +} from 'antd'; + +import { + FacebookShareButton, + LinkedinShareButton, + TwitterShareButton, + TelegramShareButton, + WhatsappShareButton, + VKShareButton, + RedditShareButton, + ViberShareButton, + FacebookIcon, + TwitterIcon, + TelegramIcon, + WhatsappIcon, + VKIcon, + RedditIcon, + ViberIcon, + LinkedinIcon, +} from 'react-share'; + +import Text from 'antd/lib/typography/Text'; + +function renderContent(): JSX.Element { + const githubURL = 'https://github.com/opencv/cvat'; + const githubImage = 'https://raw.githubusercontent.com/opencv/' + + 'cvat/develop/cvat/apps/documentation/static/documentation/images/cvat.jpg'; + const questionsURL = 'https://gitter.im/opencv-cvat/public'; + const feedbackURL = 'https://gitter.im/opencv-cvat/public'; + + return ( + <> + + + Star us on + GitHub + +
+ + + Leave a + feedback + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + Do you need help? Contact us on + gitter + + + ); +} + +export default function Feedback(): JSX.Element { + const [visible, setVisible] = React.useState(false); + + return ( + <> + Help to make CVAT better + } + content={renderContent()} + visible={visible} + > + + + + ); +} diff --git a/cvat-ui/src/components/feedback/styles.scss b/cvat-ui/src/components/feedback/styles.scss new file mode 100644 index 00000000000..9a62fe18813 --- /dev/null +++ b/cvat-ui/src/components/feedback/styles.scss @@ -0,0 +1,16 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +.cvat-feedback-button { + position: absolute; + bottom: 20px; + right: 20px; + padding: 0px; +} + +.cvat-feedback-button { + > i { + font-size: 40px; + } +} diff --git a/cvat-ui/src/components/file-manager/file-manager.tsx b/cvat-ui/src/components/file-manager/file-manager.tsx new file mode 100644 index 00000000000..98de269786c --- /dev/null +++ b/cvat-ui/src/components/file-manager/file-manager.tsx @@ -0,0 +1,243 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import './styles.scss'; +import React from 'react'; + +import { + Tabs, + Icon, + Input, + Upload, +} from 'antd'; + +import Tree, { AntTreeNode, TreeNodeNormal } from 'antd/lib/tree/Tree'; +import { RcFile } from 'antd/lib/upload'; +import Text from 'antd/lib/typography/Text'; + +export interface Files { + local: File[]; + share: string[]; + remote: string[]; +} + +interface State { + files: Files; + expandedKeys: string[]; + active: 'local' | 'share' | 'remote'; +} + +interface Props { + withRemote: boolean; + treeData: TreeNodeNormal[]; + onLoadData: (key: string, success: () => void, failure: () => void) => void; +} + +export default class FileManager extends React.PureComponent { + public constructor(props: Props) { + super(props); + + this.state = { + files: { + local: [], + share: [], + remote: [], + }, + expandedKeys: [], + active: 'local', + }; + + this.loadData('/'); + } + + public getFiles(): Files { + const { + active, + files, + } = this.state; + return { + local: active === 'local' ? files.local : [], + share: active === 'share' ? files.share : [], + remote: active === 'remote' ? files.remote : [], + }; + } + + private loadData = (key: string): Promise => new Promise( + (resolve, reject): void => { + const { onLoadData } = this.props; + + const success = (): void => resolve(); + const failure = (): void => reject(); + onLoadData(key, success, failure); + }, + ); + + public reset(): void { + this.setState({ + expandedKeys: [], + active: 'local', + files: { + local: [], + share: [], + remote: [], + }, + }); + } + + private renderLocalSelector(): JSX.Element { + const { files } = this.state; + + return ( + + { + this.setState({ + files: { + ...files, + local: newLocalFiles, + }, + }); + return false; + }} + > +

+ +

+

Click or drag files to this area

+

+ Support for a bulk images or a single video +

+
+ { files.local.length >= 5 + && ( + <> +
+ + {`${files.local.length} files selected`} + + + )} +
+ ); + } + + private renderShareSelector(): JSX.Element { + function renderTreeNodes(data: TreeNodeNormal[]): JSX.Element[] { + return data.map((item: TreeNodeNormal) => { + if (item.children) { + return ( + + {renderTreeNodes(item.children)} + + ); + } + + return ; + }); + } + + const { treeData } = this.props; + const { + expandedKeys, + files, + } = this.state; + + return ( + + { treeData.length + ? ( + => this.loadData( + node.props.dataRef.key, + )} + onExpand={(newExpandedKeys: string[]): void => { + this.setState({ + expandedKeys: newExpandedKeys, + }); + }} + onCheck={ + (checkedKeys: string[] | { + checked: string[]; + halfChecked: string[]; + }): void => { + const keys = checkedKeys as string[]; + this.setState({ + files: { + ...files, + share: keys, + }, + }); + } + } + > + { renderTreeNodes(treeData) } + + ) : No data found} + + ); + } + + private renderRemoteSelector(): JSX.Element { + const { files } = this.state; + + return ( + + ): void => { + this.setState({ + files: { + ...files, + remote: event.target.value.split('\n'), + }, + }); + }} + /> + + ); + } + + public render(): JSX.Element { + const { withRemote } = this.props; + const { active } = this.state; + + return ( + <> + this.setState({ + active: activeKey as any, + }) + } + > + { this.renderLocalSelector() } + { this.renderShareSelector() } + { withRemote && this.renderRemoteSelector() } + + + ); + } +} diff --git a/cvat-ui/src/components/file-manager/styles.scss b/cvat-ui/src/components/file-manager/styles.scss new file mode 100644 index 00000000000..bff7e5a0c01 --- /dev/null +++ b/cvat-ui/src/components/file-manager/styles.scss @@ -0,0 +1,12 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +@import '../../base.scss'; + +.cvat-share-tree { + height: fit-content; + min-height: 10em; + max-height: 20em; + overflow: auto; +} diff --git a/cvat-ui/src/components/header-layout/header-layout.scss b/cvat-ui/src/components/header-layout/header-layout.scss deleted file mode 100644 index 5476e4c8f8b..00000000000 --- a/cvat-ui/src/components/header-layout/header-layout.scss +++ /dev/null @@ -1,66 +0,0 @@ -.header-layout { - min-width: 1024px; - height: 100%; - padding: 0 16px; - line-height: initial; - background: #d8d8d8; - - &__logo { - display: flex; - align-items: center; - justify-content: center; - - img { - height: 18px; - } - } - - &__menu { - .ant-menu { - font-size: 16px; - color: black; - background-color: #d8d8d8; - line-height: 44px; - border-bottom: none; - - .ant-menu-item { - border-bottom: 3px solid transparent; - } - - .last-menu-item { - float: right; - margin-right: 28px; - } - - .ant-menu-item-selected, .ant-menu-item-active { - color: black !important; - border-bottom: 3px solid black !important; - background-color: #c3c3c3 !important; - } - - a, a:hover { - color: black; - } - } - } - - &__dropdown { - border-left: 1px solid #c3c3c3; - cursor: pointer; - - display: flex; - align-items: center; - font-size: 16px; - color: black; - - i:first-child { - margin-right: 12px; - font-size: 18px; - } - - i:last-child { - margin-left: auto; - font-size: 18px; - } - } -} diff --git a/cvat-ui/src/components/header-layout/header-layout.test.tsx b/cvat-ui/src/components/header-layout/header-layout.test.tsx deleted file mode 100644 index 6153a99a625..00000000000 --- a/cvat-ui/src/components/header-layout/header-layout.test.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; - -import HeaderLayout from './header-layout'; - - -it('renders without crashing', () => { - const div = document.createElement('div'); - ReactDOM.render(, div); - ReactDOM.unmountComponentAtNode(div); -}); diff --git a/cvat-ui/src/components/header-layout/header-layout.tsx b/cvat-ui/src/components/header-layout/header-layout.tsx deleted file mode 100644 index b3edefd1e0d..00000000000 --- a/cvat-ui/src/components/header-layout/header-layout.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import React, { PureComponent } from 'react'; - -import { Link, withRouter } from 'react-router-dom'; - -import { connect } from 'react-redux'; - -import { logoutAsync } from '../../actions/auth.actions'; - -import { Layout, Row, Col, Menu, Dropdown, Icon } from 'antd'; -import { ClickParam } from 'antd/lib/menu'; - -import './header-layout.scss'; - - -const { Header } = Layout; - -class HeaderLayout extends PureComponent { - hostUrl: string | undefined; - - constructor(props: any) { - super(props); - - this.state = { - selectedMenuItem: null, - }; - - this.hostUrl = process.env.REACT_APP_API_HOST_URL; - } - - componentDidMount() { - this.setState({ selectedMenuItem: this.props.location.pathname.split('/')[1] }); - } - - render() { - const dropdownMenu = ( - - Logout - - ); - - return ( -
- -
- CVAT logo - - - - - Tasks - - - Models - - - Analitics - - - Help - - - - - - { this.props.currentUser ? { this.props.currentUser.username } : null } - - - - - - ); - } - - private selectMenuItem = (event: ClickParam) => { - this.setState({ selectedMenuItem: event.key }); - } - - private logout = () => { - this.props.dispatch(logoutAsync()); - } -} - -const mapStateToProps = (state: any) => { - return { ...state.authContext, ...state.users }; -}; - -export default withRouter(connect(mapStateToProps)(HeaderLayout) as any); diff --git a/cvat-ui/src/components/header/header.tsx b/cvat-ui/src/components/header/header.tsx new file mode 100644 index 00000000000..0ea4d342ffb --- /dev/null +++ b/cvat-ui/src/components/header/header.tsx @@ -0,0 +1,242 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import './styles.scss'; +import React from 'react'; + +import { RouteComponentProps } from 'react-router'; +import { withRouter } from 'react-router-dom'; +import { + Layout, + Icon, + Button, + Menu, + Dropdown, + Modal, + Row, + Col, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; + +import { + CVATLogo, + AccountIcon, +} from 'icons'; + +interface HeaderContainerProps { + onLogout: () => void; + logoutFetching: boolean; + installedAnalytics: boolean; + installedAutoAnnotation: boolean; + installedTFAnnotation: boolean; + installedTFSegmentation: boolean; + serverHost: string; + username: string; + toolName: string; + serverVersion: string; + serverDescription: string; + coreVersion: string; + canvasVersion: string; + uiVersion: string; +} + +type Props = HeaderContainerProps & RouteComponentProps; + +function HeaderContainer(props: Props): JSX.Element { + const { + installedTFSegmentation, + installedAutoAnnotation, + installedTFAnnotation, + installedAnalytics, + username, + toolName, + serverHost, + serverVersion, + serverDescription, + coreVersion, + canvasVersion, + uiVersion, + onLogout, + logoutFetching, + } = props; + + const renderModels = installedAutoAnnotation + || installedTFAnnotation + || installedTFSegmentation; + + function aboutModal(): void { + const CHANGELOG = 'https://github.com/opencv/cvat/blob/develop/CHANGELOG.md'; + const LICENSE = 'https://github.com/opencv/cvat/blob/develop/LICENSE'; + const GITTER = 'https://gitter.im/opencv-cvat'; + const FORUM = 'https://software.intel.com/en-us/forums/intel-distribution-of-openvino-toolkit'; + + Modal.info({ + title: `${toolName}`, + content: ( +
+

+ {`${serverDescription}`} +

+

+ + Server version: + + + {` ${serverVersion}`} + +

+

+ + Core version: + + + {` ${coreVersion}`} + +

+

+ + Canvas version: + + + {` ${canvasVersion}`} + +

+

+ + UI version: + + + {` ${uiVersion}`} + +

+ +
{'What\'s new?'} + License + Need help? + Forum on Intel Developer Zone + + + ), + width: 800, + okButtonProps: { + style: { + width: '100px', + }, + }, + }); + } + + const menu = ( + + props.history.push('/settings') + } + > + + Settings + + aboutModal()}> + + About + + + {logoutFetching ? : } + Logout + + + + ); + + return ( + +
+ + + + { renderModels + && ( + + )} + { installedAnalytics + && ( + + )} +
+
+ + + + + + + {username.length > 14 ? `${username.slice(0, 10)} ...` : username} + + + + +
+
+ ); +} + +export default withRouter(HeaderContainer); diff --git a/cvat-ui/src/components/header/styles.scss b/cvat-ui/src/components/header/styles.scss new file mode 100644 index 00000000000..d5c127bf82a --- /dev/null +++ b/cvat-ui/src/components/header/styles.scss @@ -0,0 +1,59 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +@import '../../base.scss'; + +.cvat-header.ant-layout-header { + display: flex; + padding-left: 0px; + padding-right: 0px; + line-height: 0px; + height: 44px; + background: $header-color; +} + +.cvat-left-header { + width: 50%; + display: flex; + justify-content: flex-start; + align-items: center; +} + +.cvat-right-header { + width: 50%; + display: flex; + justify-content: flex-end; + align-items: center; +} + +.anticon.cvat-logo-icon { + > svg { + transform: scale(0.7); + } +} + +.ant-btn.cvat-header-button { + color: $text-color; + padding: 0px 10px; + margin-right: 10px; +} + + +.ant-dropdown-trigger.cvat-header-menu-dropdown { + display: flex; + align-items: center; + border-left: 1px solid $border-color-1; + padding: 0px 20px; +} + +.anticon.cvat-header-account-icon { + > svg { + transform: scale(0.4); + } +} + +.anticon.cvat-header-menu-icon { + margin-left: 16px; + margin-right: 0px; +} diff --git a/cvat-ui/src/components/labels-editor/common.ts b/cvat-ui/src/components/labels-editor/common.ts new file mode 100644 index 00000000000..91fd1d8bd72 --- /dev/null +++ b/cvat-ui/src/components/labels-editor/common.ts @@ -0,0 +1,33 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +export interface Attribute { + id: number; + name: string; + type: string; + mutable: boolean; + values: string[]; +} + +export interface Label { + name: string; + id: number; + attributes: Attribute[]; +} + +let id = 0; + +export function idGenerator(): number { + return --id; +} + +export function equalArrayHead(arr1: string[], arr2: string[]): boolean { + for (let i = 0; i < arr1.length; i++) { + if (arr1[i] !== arr2[i]) { + return false; + } + } + + return true; +} diff --git a/cvat-ui/src/components/labels-editor/constructor-creator.tsx b/cvat-ui/src/components/labels-editor/constructor-creator.tsx new file mode 100644 index 00000000000..b5c8664dcc8 --- /dev/null +++ b/cvat-ui/src/components/labels-editor/constructor-creator.tsx @@ -0,0 +1,21 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import LabelForm from './label-form'; +import { Label } from './common'; + +interface Props { + onCreate: (label: Label | null) => void; +} + +export default function ConstructorCreator(props: Props): JSX.Element { + const { onCreate } = props; + return ( +
+ +
+ ); +} diff --git a/cvat-ui/src/components/labels-editor/constructor-updater.tsx b/cvat-ui/src/components/labels-editor/constructor-updater.tsx new file mode 100644 index 00000000000..f5f8f5a8e1c --- /dev/null +++ b/cvat-ui/src/components/labels-editor/constructor-updater.tsx @@ -0,0 +1,26 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import LabelForm from './label-form'; +import { Label } from './common'; + +interface Props { + label: Label; + onUpdate: (label: Label | null) => void; +} + +export default function ConstructorUpdater(props: Props): JSX.Element { + const { + label, + onUpdate, + } = props; + + return ( +
+ +
+ ); +} diff --git a/cvat-ui/src/components/labels-editor/constructor-viewer-item.tsx b/cvat-ui/src/components/labels-editor/constructor-viewer-item.tsx new file mode 100644 index 00000000000..1184e347620 --- /dev/null +++ b/cvat-ui/src/components/labels-editor/constructor-viewer-item.tsx @@ -0,0 +1,59 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Icon, + Tooltip, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; + +import { Label } from './common'; + +interface ConstructorViewerItemProps { + label: Label; + color: string; + onUpdate: (label: Label) => void; + onDelete: (label: Label) => void; +} + +export default function ConstructorViewerItem(props: ConstructorViewerItemProps): JSX.Element { + const { + color, + label, + onUpdate, + onDelete, + } = props; + + return ( +
+ {label.name} + + onUpdate(label)} + onKeyPress={(): boolean => false} + > + + + + { label.id < 0 + && ( + + onDelete(label)} + onKeyPress={(): boolean => false} + > + + + + )} +
+ ); +} diff --git a/cvat-ui/src/components/labels-editor/constructor-viewer.tsx b/cvat-ui/src/components/labels-editor/constructor-viewer.tsx new file mode 100644 index 00000000000..79bfa3ab95a --- /dev/null +++ b/cvat-ui/src/components/labels-editor/constructor-viewer.tsx @@ -0,0 +1,65 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Icon, + Button, +} from 'antd'; + +import ConstructorViewerItem from './constructor-viewer-item'; +import { Label } from './common'; + +interface ConstructorViewerProps { + labels: Label[]; + onUpdate: (label: Label) => void; + onDelete: (label: Label) => void; + onCreate: () => void; +} + +const colors = [ + '#ff811e', '#9013fe', '#0074d9', + '#549ca4', '#e8c720', '#3d9970', + '#6b2034', '#2c344c', '#2ecc40', +]; + +let currentColor = 0; + +function nextColor(): string { + const color = colors[currentColor]; + currentColor += 1; + if (currentColor >= colors.length) { + currentColor = 0; + } + return color; +} + +export default function ConstructorViewer(props: ConstructorViewerProps): JSX.Element { + const { onCreate } = props; + currentColor = 0; + + const list = [ + ]; + for (const label of props.labels) { + list.push( + , + ); + } + + return ( +
+ { list } +
+ ); +} diff --git a/cvat-ui/src/components/labels-editor/label-form.tsx b/cvat-ui/src/components/labels-editor/label-form.tsx new file mode 100644 index 00000000000..014967a26ad --- /dev/null +++ b/cvat-ui/src/components/labels-editor/label-form.tsx @@ -0,0 +1,525 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Row, + Col, + Icon, + Input, + Button, + Select, + Tooltip, + Checkbox, +} from 'antd'; + +import Form, { FormComponentProps } from 'antd/lib/form/Form'; +import Text from 'antd/lib/typography/Text'; +import patterns from 'utils/validation-patterns'; + +import { + equalArrayHead, + idGenerator, + Label, + Attribute, +} from './common'; + + +export enum AttributeType { + SELECT = 'SELECT', + RADIO = 'RADIO', + CHECKBOX = 'CHECKBOX', + TEXT = 'TEXT', + NUMBER = 'NUMBER', +} + +type Props = FormComponentProps & { + label: Label | null; + onSubmit: (label: Label | null) => void; +}; + +class LabelForm extends React.PureComponent { + private continueAfterSubmit: boolean; + + constructor(props: Props) { + super(props); + this.continueAfterSubmit = false; + } + + private handleSubmit = (e: React.FormEvent): void => { + e.preventDefault(); + + const { + form, + label, + onSubmit, + } = this.props; + + form.validateFields((error, formValues): void => { + if (!error) { + onSubmit({ + name: formValues.labelName, + id: label ? label.id : idGenerator(), + attributes: formValues.keys.map((key: number, index: number): Attribute => { + let attrValues = formValues.values[key]; + if (!Array.isArray(attrValues)) { + if (formValues.type[key] === AttributeType.NUMBER) { + attrValues = attrValues.split(';'); + } else { + attrValues = [attrValues]; + } + } + + attrValues = attrValues.map((value: string) => value.trim()); + + return { + name: formValues.attrName[key], + type: formValues.type[key], + mutable: formValues.mutable[key], + id: label && index < label.attributes.length + ? label.attributes[index].id : key, + values: attrValues, + }; + }), + }); + + form.resetFields(); + + if (!this.continueAfterSubmit) { + onSubmit(null); + } + } + }); + }; + + private addAttribute = (): void => { + const { form } = this.props; + const keys = form.getFieldValue('keys'); + const nextKeys = keys.concat(idGenerator()); + form.setFieldsValue({ + keys: nextKeys, + }); + }; + + private removeAttribute = (key: number): void => { + const { form } = this.props; + const keys = form.getFieldValue('keys'); + form.setFieldsValue({ + keys: keys.filter((_key: number) => _key !== key), + }); + }; + + private renderAttributeNameInput(key: number, attr: Attribute | null): JSX.Element { + const locked = attr ? attr.id >= 0 : false; + const value = attr ? attr.name : ''; + const { form } = this.props; + + return ( +
+ + {form.getFieldDecorator(`attrName[${key}]`, { + initialValue: value, + rules: [{ + required: true, + message: 'Please specify a name', + }, { + pattern: patterns.validateAttributeName.pattern, + message: patterns.validateAttributeName.message, + }], + })()} + + + ); + } + + private renderAttributeTypeInput(key: number, attr: Attribute | null): JSX.Element { + const locked = attr ? attr.id >= 0 : false; + const type = attr ? attr.type.toUpperCase() : AttributeType.SELECT; + const { form } = this.props; + + return ( + + + + { form.getFieldDecorator(`type[${key}]`, { + initialValue: type, + })( + , + )} + + + + ); + } + + private renderAttributeValuesInput(key: number, attr: Attribute | null): JSX.Element { + const locked = attr ? attr.id >= 0 : false; + const existedValues = attr ? attr.values : []; + const { form } = this.props; + + const validator = (_: any, values: string[], callback: any): void => { + if (locked && existedValues) { + if (!equalArrayHead(existedValues, values)) { + callback('You can only append new values'); + } + } + + for (const value of values) { + if (!patterns.validateAttributeValue.pattern.test(value)) { + callback(`Invalid attribute value: "${value}"`); + } + } + + callback(); + }; + + return ( + + + { form.getFieldDecorator(`values[${key}]`, { + initialValue: existedValues, + rules: [{ + required: true, + message: 'Please specify values', + }, { + validator, + }], + })( + + False + True + , + )} + + + ); + } + + private renderNumberRangeInput(key: number, attr: Attribute | null): JSX.Element { + const locked = attr ? attr.id >= 0 : false; + const value = attr ? attr.values.join(';') : ''; + const { form } = this.props; + + const validator = (_: any, strNumbers: string, callback: any): void => { + const numbers = strNumbers + .split(';') + .map((number): number => Number.parseFloat(number)); + if (numbers.length !== 3) { + callback('Invalid input'); + } + + for (const number of numbers) { + if (Number.isNaN(number)) { + callback('Invalid input'); + } + } + + if (numbers[0] >= numbers[1]) { + callback('Invalid input'); + } + + if (+numbers[1] - +numbers[0] < +numbers[2]) { + callback('Invalid input'); + } + + callback(); + }; + + return ( + + { form.getFieldDecorator(`values[${key}]`, { + initialValue: value, + rules: [{ + required: true, + message: 'Please set a range', + }, { + validator, + }], + })( + , + )} + + ); + } + + private renderDefaultValueInput(key: number, attr: Attribute | null): JSX.Element { + const value = attr ? attr.values[0] : ''; + const { form } = this.props; + + return ( + + { form.getFieldDecorator(`values[${key}]`, { + initialValue: value, + })( + , + )} + + ); + } + + private renderMutableAttributeInput(key: number, attr: Attribute | null): JSX.Element { + const locked = attr ? attr.id >= 0 : false; + const value = attr ? attr.mutable : false; + const { form } = this.props; + + return ( + + + { form.getFieldDecorator(`mutable[${key}]`, { + initialValue: value, + valuePropName: 'checked', + })( + Mutable , + )} + + + ); + } + + private renderDeleteAttributeButton(key: number, attr: Attribute | null): JSX.Element { + const locked = attr ? attr.id >= 0 : false; + + return ( + + + + + + ); + } + + private renderAttribute = (key: number, index: number): JSX.Element => { + const { + label, + form, + } = this.props; + + const attr = (label && index < label.attributes.length + ? label.attributes[index] + : null); + + return ( + + + { this.renderAttributeNameInput(key, attr) } + { this.renderAttributeTypeInput(key, attr) } + + {((): JSX.Element => { + const type = form.getFieldValue(`type[${key}]`); + let element = null; + if ([AttributeType.SELECT, AttributeType.RADIO].includes(type)) { + element = this.renderAttributeValuesInput(key, attr); + } else if (type === AttributeType.CHECKBOX) { + element = this.renderBooleanValueInput(key, attr); + } else if (type === AttributeType.NUMBER) { + element = this.renderNumberRangeInput(key, attr); + } else { + element = this.renderDefaultValueInput(key, attr); + } + + return element; + })()} + + + { this.renderMutableAttributeInput(key, attr) } + + + { this.renderDeleteAttributeButton(key, attr) } + + + + ); + }; + + private renderLabelNameInput(): JSX.Element { + const { + label, + form, + } = this.props; + const value = label ? label.name : ''; + const locked = label ? label.id >= 0 : false; + + return ( + + + {form.getFieldDecorator('labelName', { + initialValue: value, + rules: [{ + required: true, + message: 'Please specify a name', + }, { + pattern: patterns.validateAttributeName.pattern, + message: patterns.validateAttributeName.message, + }], + })()} + + + ); + } + + private renderNewAttributeButton(): JSX.Element { + return ( + + + + + + ); + } + + private renderDoneButton(): JSX.Element { + return ( + + + + + + ); + } + + private renderContinueButton(): JSX.Element { + const { label } = this.props; + + return ( + label ?
+ : ( +
+ + + + + ) + ); + } + + private renderCancelButton(): JSX.Element { + const { onSubmit } = this.props; + + return ( + + + + + + ); + } + + public render(): JSX.Element { + const { + label, + form, + } = this.props; + + form.getFieldDecorator('keys', { + initialValue: label + ? label.attributes.map((attr: Attribute): number => attr.id) + : [], + }); + + const keys = form.getFieldValue('keys'); + const attributeItems = keys.map(this.renderAttribute); + + return ( + + + { this.renderLabelNameInput() } + + { this.renderNewAttributeButton() } + + { attributeItems.length > 0 + && ( + + + Attributes + + + )} + { attributeItems.reverse() } + + { this.renderDoneButton() } + { this.renderContinueButton() } + { this.renderCancelButton() } + + + ); + } +} + +export default Form.create()(LabelForm); diff --git a/cvat-ui/src/components/labels-editor/labels-editor.tsx b/cvat-ui/src/components/labels-editor/labels-editor.tsx new file mode 100644 index 00000000000..06890d7500f --- /dev/null +++ b/cvat-ui/src/components/labels-editor/labels-editor.tsx @@ -0,0 +1,335 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import './styles.scss'; +import React from 'react'; + +import { + Tabs, + Icon, + Button, + Tooltip, + notification, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; + +import copy from 'copy-to-clipboard'; + +import RawViewer from './raw-viewer'; +import ConstructorViewer from './constructor-viewer'; +import ConstructorCreator from './constructor-creator'; +import ConstructorUpdater from './constructor-updater'; + +import { + idGenerator, + Label, + Attribute, +} from './common'; + +enum ConstructorMode { + SHOW = 'SHOW', + CREATE = 'CREATE', + UPDATE = 'UPDATE', +} + +interface LabelsEditortProps { + labels: Label[]; + onSubmit: (labels: any[]) => void; +} + +interface LabelsEditorState { + constructorMode: ConstructorMode; + savedLabels: Label[]; + unsavedLabels: Label[]; + labelForUpdate: Label | null; +} + +export default class LabelsEditor + extends React.PureComponent { + public constructor(props: LabelsEditortProps) { + super(props); + + this.state = { + savedLabels: [], + unsavedLabels: [], + constructorMode: ConstructorMode.SHOW, + labelForUpdate: null, + }; + } + + public componentDidMount(): void { + // just need performe the same code + this.componentDidUpdate(null as any as LabelsEditortProps); + } + + public componentDidUpdate(prevProps: LabelsEditortProps): void { + function transformLabel(label: any): Label { + return { + name: label.name, + id: label.id || idGenerator(), + attributes: label.attributes.map((attr: any): Attribute => ( + { + id: attr.id || idGenerator(), + name: attr.name, + type: attr.input_type, + mutable: attr.mutable, + values: [...attr.values], + } + )), + }; + } + + const { labels } = this.props; + + if (!prevProps || prevProps.labels !== labels) { + const transformedLabels = labels.map(transformLabel); + this.setState({ + savedLabels: transformedLabels + .filter((label: Label) => label.id >= 0), + unsavedLabels: transformedLabels + .filter((label: Label) => label.id < 0), + }); + } + } + + private handleRawSubmit = (labels: Label[]): void => { + const unsavedLabels = []; + const savedLabels = []; + + for (const label of labels) { + if (label.id >= 0) { + savedLabels.push(label); + } else { + unsavedLabels.push(label); + } + } + + this.setState({ + unsavedLabels, + savedLabels, + }); + + this.handleSubmit(savedLabels, unsavedLabels); + }; + + private handleCreate = (label: Label | null): void => { + if (label === null) { + this.setState({ + constructorMode: ConstructorMode.SHOW, + }); + } else { + const { + unsavedLabels, + savedLabels, + } = this.state; + const newUnsavedLabels = [ + ...unsavedLabels, + { + ...label, + id: idGenerator(), + }, + ]; + + this.setState({ + unsavedLabels: newUnsavedLabels, + }); + + this.handleSubmit(savedLabels, newUnsavedLabels); + } + }; + + private handleUpdate = (label: Label | null): void => { + const { + savedLabels, + unsavedLabels, + } = this.state; + + if (label) { + const filteredSavedLabels = savedLabels + .filter((_label: Label) => _label.id !== label.id); + const filteredUnsavedLabels = unsavedLabels + .filter((_label: Label) => _label.id !== label.id); + if (label.id >= 0) { + filteredSavedLabels.push(label); + this.setState({ + savedLabels: filteredSavedLabels, + constructorMode: ConstructorMode.SHOW, + }); + } else { + filteredUnsavedLabels.push(label); + this.setState({ + unsavedLabels: filteredUnsavedLabels, + constructorMode: ConstructorMode.SHOW, + }); + } + + this.handleSubmit(filteredSavedLabels, filteredUnsavedLabels); + } else { + this.setState({ + constructorMode: ConstructorMode.SHOW, + }); + } + }; + + private handleDelete = (label: Label): void => { + // the label is saved on the server, cannot delete it + if (typeof (label.id) !== 'undefined' && label.id >= 0) { + notification.error({ + message: 'Could not delete the label', + description: 'It has been already saved on the server', + }); + } + + const { + unsavedLabels, + savedLabels, + } = this.state; + + const filteredUnsavedLabels = unsavedLabels.filter( + (_label: Label): boolean => _label.id !== label.id, + ); + + this.setState({ + unsavedLabels: filteredUnsavedLabels, + }); + + this.handleSubmit(savedLabels, filteredUnsavedLabels); + }; + + private handleSubmit(savedLabels: Label[], unsavedLabels: Label[]): void { + function transformLabel(label: Label): any { + return { + name: label.name, + id: label.id < 0 ? undefined : label.id, + attributes: label.attributes.map((attr: Attribute): any => ( + { + name: attr.name, + id: attr.id < 0 ? undefined : attr.id, + input_type: attr.type.toLowerCase(), + default_value: attr.values[0], + mutable: attr.mutable, + values: [...attr.values], + } + )), + }; + } + + const { onSubmit } = this.props; + const output = []; + for (const label of savedLabels.concat(unsavedLabels)) { + output.push(transformLabel(label)); + } + + onSubmit(output); + } + + public render(): JSX.Element { + const { + savedLabels, + unsavedLabels, + constructorMode, + labelForUpdate, + } = this.state; + + return ( + + + + + + )} + > + + + Raw + + ) + } + key='1' + > + + + + + + Constructor + + ) + } + key='2' + > + { + constructorMode === ConstructorMode.SHOW + && ( + { + this.setState({ + constructorMode: ConstructorMode.UPDATE, + labelForUpdate: label, + }); + }} + onDelete={this.handleDelete} + onCreate={(): void => { + this.setState({ + constructorMode: ConstructorMode.CREATE, + }); + }} + /> + ) + } + { + constructorMode === ConstructorMode.UPDATE + && labelForUpdate !== null && ( + + ) + } + { + constructorMode === ConstructorMode.CREATE + && ( + + ) + } + + + ); + } +} diff --git a/cvat-ui/src/components/labels-editor/raw-viewer.tsx b/cvat-ui/src/components/labels-editor/raw-viewer.tsx new file mode 100644 index 00000000000..f083298ae83 --- /dev/null +++ b/cvat-ui/src/components/labels-editor/raw-viewer.tsx @@ -0,0 +1,112 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Row, + Col, + Form, + Input, + Button, + Tooltip, +} from 'antd'; + +import { FormComponentProps } from 'antd/lib/form/Form'; + +import { + Label, + Attribute, +} from './common'; + +type Props = FormComponentProps & { + labels: Label[]; + onSubmit: (labels: Label[]) => void; +}; + +class RawViewer extends React.PureComponent { + private validateLabels = (_: any, value: string, callback: any): void => { + try { + JSON.parse(value); + } catch (error) { + callback(error.toString()); + } + + callback(); + }; + + private handleSubmit = (e: React.FormEvent): void => { + const { + form, + onSubmit, + } = this.props; + + e.preventDefault(); + form.validateFields((error, values): void => { + if (!error) { + onSubmit(JSON.parse(values.labels)); + } + }); + }; + + public render(): JSX.Element { + const { labels } = this.props; + const convertedLabels = labels.map((label: any): Label => ( + { + ...label, + id: label.id < 0 ? undefined : label.id, + attributes: label.attributes.map((attribute: any): Attribute => ( + { + ...attribute, + id: attribute.id < 0 ? undefined : attribute.id, + } + )), + } + )); + + const textLabels = JSON.stringify(convertedLabels, null, 2); + const { form } = this.props; + + return ( +
+ + {form.getFieldDecorator('labels', { + initialValue: textLabels, + rules: [{ + validator: this.validateLabels, + }], + })()} + + +
+ + + + + + + + + + + + ); + } +} + +export default Form.create()(RawViewer); diff --git a/cvat-ui/src/components/labels-editor/styles.scss b/cvat-ui/src/components/labels-editor/styles.scss new file mode 100644 index 00000000000..f1d1e9a3342 --- /dev/null +++ b/cvat-ui/src/components/labels-editor/styles.scss @@ -0,0 +1,90 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +@import '../../base.scss'; + +textarea.ant-input.cvat-raw-labels-viewer { + border-color: $border-color-2; + box-shadow: none; + border-top: none; + border-radius: 0px 0px 5px 5px; + min-height: 9em; + font-family: $monospaced-fonts-stack; + + &:focus { + border-color: $border-color-2; + box-shadow: none; + } + + &:hover { + border-color: $border-color-2; + box-shadow: none; + } +} + +.cvat-constructor-viewer { + border: 1px solid $border-color-2; + box-shadow: none; + border-top: none; + border-radius: 0px 0px 5px 5px; + padding: 5px; + display: flex; + overflow-y: auto; + min-height: 9em; + flex-wrap: wrap; +} + +.cvat-constructor-viewer-item { + height: fit-content; + display: flex; + align-items: center; + padding: 2px 10px; + border-radius: 2px; + margin: 2px; + margin-left: 8px; + user-select: none; + border: 1px solid $transparent-color; + opacity: 0.6; + + > span { + margin-left: 5px; + color: white; + + > i:hover { + filter: invert(1); + } + } + + &:hover { + opacity: 1; + } +} + +.cvat-constructor-viewer-new-item { + height: fit-content; + display: flex; + align-items: center; + padding: 2px 10px; + border-radius: 2px; + margin: 2px; + margin-left: 8px; + user-select: none; + opacity: 1; +} + +.cvat-label-constructor-creator, +.cvat-label-constructor-updater +{ + > form:first-child { + margin-top: 10px; + } +} + +.cvat-attribute-constructor-form > div:first-child > div:nth-child(4) { + display: contents; +} + +.cvat-delete-attribute-button:hover > i { + color: $danger-icon-color; +} diff --git a/cvat-ui/src/components/login-page/login-form.tsx b/cvat-ui/src/components/login-page/login-form.tsx new file mode 100644 index 00000000000..5c9e213a21f --- /dev/null +++ b/cvat-ui/src/components/login-page/login-form.tsx @@ -0,0 +1,107 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import { FormComponentProps } from 'antd/lib/form/Form'; +import { + Button, + Icon, + Input, + Form, +} from 'antd'; + +export interface LoginData { + username: string; + password: string; +} + +type LoginFormProps = { + fetching: boolean; + onSubmit(loginData: LoginData): void; +} & FormComponentProps; + +class LoginFormComponent extends React.PureComponent { + private handleSubmit = (e: React.FormEvent): void => { + e.preventDefault(); + const { + form, + onSubmit, + } = this.props; + + form.validateFields((error, values): void => { + if (!error) { + onSubmit(values); + } + }); + }; + + private renderUsernameField(): JSX.Element { + const { form } = this.props; + const { getFieldDecorator } = form; + + return ( + + {getFieldDecorator('username', { + rules: [{ + required: true, + message: 'Please specify a username', + }], + })( + } + placeholder='Username' + />, + )} + + ); + } + + private renderPasswordField(): JSX.Element { + const { form } = this.props; + const { getFieldDecorator } = form; + + return ( + + {getFieldDecorator('password', { + rules: [{ + required: true, + message: 'Please specify a password', + }], + })( + } + placeholder='Password' + type='password' + />, + )} + + ); + } + + public render(): JSX.Element { + const { fetching } = this.props; + return ( +
+ {this.renderUsernameField()} + {this.renderPasswordField()} + + + + + + ); + } +} + +export default Form.create()(LoginFormComponent); diff --git a/cvat-ui/src/components/login-page/login-page.scss b/cvat-ui/src/components/login-page/login-page.scss deleted file mode 100644 index 18122d20068..00000000000 --- a/cvat-ui/src/components/login-page/login-page.scss +++ /dev/null @@ -1,10 +0,0 @@ -.login-form { - display: flex; - flex-direction: column; - justify-content: center; - height: 100vh; - - &__title { - - } -} diff --git a/cvat-ui/src/components/login-page/login-page.test.tsx b/cvat-ui/src/components/login-page/login-page.test.tsx deleted file mode 100644 index 6bec1fa7613..00000000000 --- a/cvat-ui/src/components/login-page/login-page.test.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; - -import Login from './login-page'; - - -it('renders without crashing', () => { - const div = document.createElement('div'); - ReactDOM.render(, div); - ReactDOM.unmountComponentAtNode(div); -}); diff --git a/cvat-ui/src/components/login-page/login-page.tsx b/cvat-ui/src/components/login-page/login-page.tsx index 47153be28ae..6c5e9b5921b 100644 --- a/cvat-ui/src/components/login-page/login-page.tsx +++ b/cvat-ui/src/components/login-page/login-page.tsx @@ -1,105 +1,61 @@ -import React, { PureComponent } from 'react'; -import { Link } from 'react-router-dom'; +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT -import { connect } from 'react-redux'; -import { loginAsync, isAuthenticatedAsync } from '../../actions/auth.actions'; -import { getUsersAsync } from '../../actions/users.actions'; +import React from 'react'; -import { Button, Icon, Input, Form, Col, Row, Spin } from 'antd'; -import Title from 'antd/lib/typography/Title'; - -import './login-page.scss'; - - -class LoginForm extends PureComponent { - constructor(props: any) { - super(props); +import { RouteComponentProps } from 'react-router'; +import { Link, withRouter } from 'react-router-dom'; - this.state = { loading: false }; - } +import Title from 'antd/lib/typography/Title'; +import Text from 'antd/lib/typography/Text'; +import { + Col, + Row, +} from 'antd'; - componentDidMount() { - this.setState({ loading: true }); +import LoginForm, { LoginData } from './login-form'; - this.props.dispatch(isAuthenticatedAsync()).then( - (isAuthenticated: boolean) => { - this.setState({ loading: false }); +interface LoginPageComponentProps { + fetching: boolean; + onLogin: (username: string, password: string) => void; +} - if (this.props.isAuthenticated) { - this.props.dispatch(getUsersAsync({ self: true })); - this.props.history.replace(this.props.location.state ? this.props.location.state.from : '/tasks'); - } - } - ); - } +function LoginPageComponent(props: LoginPageComponentProps & RouteComponentProps): JSX.Element { + const sizes = { + xs: { span: 14 }, + sm: { span: 14 }, + md: { span: 10 }, + lg: { span: 4 }, + xl: { span: 4 }, + }; - render() { - const { getFieldDecorator } = this.props.form; + const { + fetching, + onLogin, + } = props; return ( - - -
- - Login - - - {getFieldDecorator('username', { - rules: [{ required: true, message: 'Please enter your username!' }], - })( - } - type="text" - name="username" - placeholder="Username" - />, - )} - - - - {getFieldDecorator('password', { - rules: [{ required: true, message: 'Please enter your password!' }], - })( - } - type="password" - name="password" - placeholder="Password" - />, - )} - - - - - - - Have not registered yet? Register here. - - + + + Login + { + onLogin(loginData.username, loginData.password); + }} + /> + + + + New to CVAT? Create + an account + + + + - ); - } - - private onSubmit = (event: React.FormEvent) => { - event.preventDefault(); - - this.props.form.validateFields((error: any, values: any) => { - if (!error) { - this.props.dispatch(loginAsync(values.username, values.password, this.props.history)).then( - (loggedIn: any) => { - this.props.dispatch(getUsersAsync({ self: true })); - }, - ); - } - }); - } } -const mapStateToProps = (state: any) => { - return state.authContext; -}; - -export default Form.create()(connect(mapStateToProps)(LoginForm)); +export default withRouter(LoginPageComponent); diff --git a/cvat-ui/src/components/modals/task-create/task-create.scss b/cvat-ui/src/components/modals/task-create/task-create.scss deleted file mode 100644 index 5f68722aecd..00000000000 --- a/cvat-ui/src/components/modals/task-create/task-create.scss +++ /dev/null @@ -1,8 +0,0 @@ -.ant-badge { - width: 100%; -} - -.ant-tree.ant-tree-directory { - height: 108px; - overflow: auto; -} diff --git a/cvat-ui/src/components/modals/task-create/task-create.tsx b/cvat-ui/src/components/modals/task-create/task-create.tsx deleted file mode 100644 index 34d8addf7ca..00000000000 --- a/cvat-ui/src/components/modals/task-create/task-create.tsx +++ /dev/null @@ -1,352 +0,0 @@ -import React, { PureComponent } from 'react'; - -import { Form, Input, Icon, Checkbox, Radio, Upload, Badge, Tree, TreeSelect, InputNumber } from 'antd'; -import { UploadFile, UploadChangeParam } from 'antd/lib/upload/interface'; - -import configureStore from '../../../store'; -import { getShareFilesAsync } from '../../../actions/server.actions'; - -import { validateLabels, FileSource, fileModel } from '../../../utils/tasks-dto'; - -import './task-create.scss'; - - -const { TreeNode } = Tree; -const { SHOW_PARENT } = TreeSelect; -const { Dragger } = Upload; - -const formItemLayout = { - labelCol: { - xs: { span: 24 }, - sm: { span: 8 }, - }, - wrapperCol: { - xs: { span: 24 }, - sm: { span: 16 }, - }, -}; - -const formItemTailLayout = { - labelCol: { - xs: { span: 24 }, - }, - wrapperCol: { - xs: { span: 24 }, - }, -}; - -class TaskCreateForm extends PureComponent { - store: any; - - constructor(props: any) { - super(props); - - this.store = configureStore(); - - this.state = { - confirmDirty: false, - selectedFileList: [], - filesCounter: 0, - treeData: [], - }; - } - - componentDidMount() { - this.getSharedFiles('').then( - (data: any) => { - this.setState({ treeData: fileModel('', this.store.getState().server.files) }); - }, - ); - } - - private renderTreeNodes = (data: any) => { - return data.map((item: any) => { - if (!item.isLeaf) { - return ( - - { item.children ? this.renderTreeNodes(item.children) : '' } - - ); - } - - return ; - }); - } - - private renderUploader = () => { - const { getFieldDecorator } = this.props.form; - - switch (this.props.form.getFieldValue('source')) { - case FileSource.Local: - return ( - - -
- {getFieldDecorator('localUpload', { - rules: [{ required: true, message: 'Please, add some files!' }], - })( - -

- -

-

Click or drag file to this area to upload

-
- )} -
-
-
- ); - case FileSource.Remote: - return ( - - {getFieldDecorator('remoteURL', { - rules: [], - })( - - )} - - ); - case FileSource.Share: - return ( - - {getFieldDecorator('sharedFiles', { - rules: [{ required: true, message: 'Please, add some files!' }], - })( - - { this.renderTreeNodes(this.state.treeData) } - - )} - - ); - default: - break; - } - } - - render() { - const { getFieldDecorator } = this.props.form; - - return ( -
- - {getFieldDecorator('name', { - rules: [ - { - required: true, - pattern: new RegExp('[a-zA-Z0-9_]+'), - message: 'Bad task name!', - }, - ], - })( - } - type="text" - name="name" - /> - )} - - - - {getFieldDecorator('labels', { - rules: [ - { required: true, message: 'Please add some labels!' }, - { validator: validateLabels, message: 'Bad labels format!' }, - ], - })( - } - type="text" - name="labels" - /> - )} - - - - {getFieldDecorator('bugTracker', { - rules: [{ type: 'url', message: 'Bad bug tracker link!' }], - })( - } - type="text" - name="bug-tracker" - /> - )} - - - - {getFieldDecorator('source', { - rules: [], - initialValue: 1, - })( - - Local - Remote - Share - - )} - - - - {getFieldDecorator('zOrder', { - rules: [], - initialValue: false, - })( - - )} - - - - {getFieldDecorator('segmentSize', { - rules: [], - })( - - )} - - - - {getFieldDecorator('overlapSize', { - rules: [], - initialValue: 0, - })( - - )} - - - - {getFieldDecorator('imageQuality', { - rules: [{ required: true }], - initialValue: 50, - })( - - )} - - - - {getFieldDecorator('startFrame', { - rules: [], - initialValue: 0, - })( - - )} - - - - {getFieldDecorator('stopFrame', { - rules: [], - })( - - )} - - - - {getFieldDecorator('frameFilter', { - rules: [], - })( - } - type="text" - name="frame-filter" - /> - )} - - - { this.renderUploader() } - - ); - } - - private onLoadData = (treeNode: any) => { - return new Promise(resolve => { - if (treeNode.props.children) { - resolve(); - - return; - } - - this.getSharedFiles(treeNode.props.dataRef.id).then( - (data: any) => { - treeNode.props.dataRef.children = fileModel(treeNode, this.store.getState().server.files); - - this.setState({ - treeData: [...this.state.treeData], - }); - - resolve(); - }, - ); - }); - } - - private getSharedFiles = (directory: string) => { - return this.store.dispatch(getShareFilesAsync(directory)); - } - - private onUploaderChange = (info: UploadChangeParam) => { - const nextState: { selectedFileList: UploadFile[], filesCounter: number } = { - selectedFileList: this.state.selectedFileList, - filesCounter: this.state.filesCounter, - }; - - switch (info.file.status) { - case 'uploading': - nextState.selectedFileList.push(info.file); - nextState.filesCounter += 1; - break; - case 'done': - break; - default: - // INFO: error or removed - nextState.selectedFileList = info.fileList; - } - - this.setState(() => nextState); - } - - private resetUploader = () => { - this.setState({ selectedFileList: [], filesCounter: 0 }); - } - - private simulateRequest = ({ file, onSuccess }: any) => { - setTimeout(() => { - onSuccess(file); - }, 0); - } -} - -export default Form.create()(TaskCreateForm); diff --git a/cvat-ui/src/components/modals/task-update/task-update.tsx b/cvat-ui/src/components/modals/task-update/task-update.tsx deleted file mode 100644 index c3f4d2a2abc..00000000000 --- a/cvat-ui/src/components/modals/task-update/task-update.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React, { PureComponent } from 'react'; - -import { Form, Input, Icon } from 'antd'; - -import { serializeLabels, validateLabels } from '../../../utils/tasks-dto' - -import './task-update.scss'; - - -class TaskUpdateForm extends PureComponent { - render() { - const { getFieldDecorator } = this.props.form; - - return ( -
- - {getFieldDecorator('oldLabels', { - rules: [], - initialValue: serializeLabels(this.props.task), - })( - } - type="text" - name="oldLabels" - placeholder="Old labels" - />, - )} - - - - {getFieldDecorator('newLabels', { - rules: [ - { required: true, message: 'Please input new labels!' }, - { validator: validateLabels, message: 'Bad labels format!' }, - ], - })( - } - type="text" - name="new-labels" - placeholder="Expand the specification here" - />, - )} - - - ); - } -} - -export default Form.create()(TaskUpdateForm) as any; diff --git a/cvat-ui/src/components/model-runner-modal/model-runner-modal.tsx b/cvat-ui/src/components/model-runner-modal/model-runner-modal.tsx new file mode 100644 index 00000000000..50642d4e4c6 --- /dev/null +++ b/cvat-ui/src/components/model-runner-modal/model-runner-modal.tsx @@ -0,0 +1,411 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import './styles.scss'; +import React from 'react'; + +import { + Row, + Col, + Tag, + Spin, + Icon, + Modal, + Select, + Tooltip, + Checkbox, + notification, +} from 'antd'; + +import { + Model, + StringObject, +} from 'reducers/interfaces'; + +interface Props { + modelsFetching: boolean; + modelsInitialized: boolean; + models: Model[]; + activeProcesses: StringObject; + visible: boolean; + taskInstance: any; + getModels(): void; + closeDialog(): void; + runInference( + taskInstance: any, + model: Model, + mapping: StringObject, + cleanOut: boolean, + ): void; +} + +interface State { + selectedModel: string | null; + cleanOut: boolean; + mapping: StringObject; + colors: StringObject; + matching: { + model: string; + task: string; + }; +} + +function colorGenerator(): () => string { + const values = [ + 'magenta', 'green', 'geekblue', + 'orange', 'red', 'cyan', + 'blue', 'volcano', 'purple', + ]; + + let index = 0; + + return (): string => { + const color = values[index++]; + if (index >= values.length) { + index = 0; + } + + return color; + }; +} + +const nextColor = colorGenerator(); + +export default class ModelRunnerModalComponent extends React.PureComponent { + public constructor(props: Props) { + super(props); + this.state = { + selectedModel: null, + mapping: {}, + colors: {}, + cleanOut: false, + matching: { + model: '', + task: '', + }, + }; + } + + public componentDidUpdate(prevProps: Props, prevState: State): void { + const { + taskInstance, + modelsInitialized, + modelsFetching, + models, + visible, + getModels, + } = this.props; + + const { + selectedModel, + } = this.state; + + if (!modelsInitialized && !modelsFetching) { + getModels(); + } + + if (!prevProps.visible && visible) { + this.setState({ + selectedModel: null, + mapping: {}, + matching: { + model: '', + task: '', + }, + cleanOut: false, + }); + } + + if (selectedModel && prevState.selectedModel !== selectedModel) { + const selectedModelInstance = models + .filter((model) => model.name === selectedModel)[0]; + + if (!selectedModelInstance.primary) { + if (!selectedModelInstance.labels.length) { + notification.warning({ + message: 'The selected model does not include any lables', + }); + } + + let taskLabels: string[] = taskInstance.labels + .map((label: any): string => label.name); + const [defaultMapping, defaultColors]: StringObject[] = selectedModelInstance.labels + .reduce((acc: StringObject[], label): StringObject[] => { + if (taskLabels.includes(label)) { + acc[0][label] = label; + acc[1][label] = nextColor(); + taskLabels = taskLabels.filter((_label): boolean => _label !== label); + } + + return acc; + }, [{}, {}]); + + this.setState({ + mapping: defaultMapping, + colors: defaultColors, + }); + } + } + } + + private renderModelSelector(): JSX.Element { + const { models } = this.props; + + return ( + +
Model: + + + + + ); + } + + private renderMappingTag(modelLabel: string, taskLabel: string): JSX.Element { + const { + colors, + mapping, + } = this.state; + + return ( + + + {modelLabel} + + + {taskLabel} + + + + { + const newMapping = { ...mapping }; + delete newMapping[modelLabel]; + this.setState({ + mapping: newMapping, + }); + }} + /> + + + + ); + } + + private renderMappingInputSelector( + value: string, + current: string, + options: string[], + ): JSX.Element { + const { + matching, + mapping, + colors, + } = this.state; + + return ( + + ); + } + + private renderMappingInput( + availableModelLabels: string[], + availableTaskLabels: string[], + ): JSX.Element { + const { matching } = this.state; + return ( + + + {this.renderMappingInputSelector( + matching.model, + 'Model', + availableModelLabels, + )} + + + {this.renderMappingInputSelector( + matching.task, + 'Task', + availableTaskLabels, + )} + + + + + + + + ); + } + + private renderContent(): JSX.Element { + const { + selectedModel, + cleanOut, + mapping, + } = this.state; + const { + models, + taskInstance, + } = this.props; + + const model = selectedModel && models + .filter((_model): boolean => _model.name === selectedModel)[0]; + + const excludedModelLabels: string[] = Object.keys(mapping); + const withMapping = model && !model.primary; + const tags = withMapping ? excludedModelLabels + .map((modelLabel: string) => this.renderMappingTag( + modelLabel, + mapping[modelLabel], + )) : []; + + const availableModelLabels = model ? model.labels + .filter( + (label: string) => !excludedModelLabels.includes(label), + ) : []; + const taskLabels = taskInstance.labels.map( + (label: any) => label.name, + ); + + const mappingISAvailable = !!availableModelLabels.length + && !!taskLabels.length; + + return ( +
+ { this.renderModelSelector() } + { withMapping && tags} + { withMapping + && mappingISAvailable + && this.renderMappingInput(availableModelLabels, taskLabels)} + { withMapping + && ( +
+ this.setState({ + cleanOut: e.target.checked, + })} + > + Clean old annotations + +
+ )} +
+ ); + } + + public render(): JSX.Element | false { + const { + selectedModel, + mapping, + cleanOut, + } = this.state; + + const { + models, + visible, + taskInstance, + modelsInitialized, + runInference, + closeDialog, + } = this.props; + + const activeModel = models.filter( + (model): boolean => model.name === selectedModel, + )[0]; + + const enabledSubmit = (!!activeModel + && activeModel.primary) || !!Object.keys(mapping).length; + + return ( + visible && ( + { + runInference( + taskInstance, + models + .filter((model): boolean => model.name === selectedModel)[0], + mapping, + cleanOut, + ); + closeDialog(); + }} + onCancel={(): void => closeDialog()} + okButtonProps={{ disabled: !enabledSubmit }} + title='Automatic annotation' + visible + > + {!modelsInitialized + && } + {modelsInitialized && this.renderContent()} + + ) + ); + } +} diff --git a/cvat-ui/src/components/model-runner-modal/styles.scss b/cvat-ui/src/components/model-runner-modal/styles.scss new file mode 100644 index 00000000000..9c9bc0c0dec --- /dev/null +++ b/cvat-ui/src/components/model-runner-modal/styles.scss @@ -0,0 +1,13 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +@import '../../base.scss'; + +.cvat-run-model-dialog > div:not(first-child) { + margin-top: 10px; +} + +.cvat-run-model-dialog-remove-mapping-icon { + color: $danger-icon-color; +} diff --git a/cvat-ui/src/components/models-page/built-model-item.tsx b/cvat-ui/src/components/models-page/built-model-item.tsx new file mode 100644 index 00000000000..d352019f353 --- /dev/null +++ b/cvat-ui/src/components/models-page/built-model-item.tsx @@ -0,0 +1,54 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Row, + Col, + Tag, + Select, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; + +import { Model } from 'reducers/interfaces'; + +interface Props { + model: Model; +} + +export default function BuiltModelItemComponent(props: Props): JSX.Element { + const { model } = props; + + return ( + + + Tensorflow + + + + {model.name} + + + + + + + + ); +} diff --git a/cvat-ui/src/components/models-page/built-models-list.tsx b/cvat-ui/src/components/models-page/built-models-list.tsx new file mode 100644 index 00000000000..a6c5bd72717 --- /dev/null +++ b/cvat-ui/src/components/models-page/built-models-list.tsx @@ -0,0 +1,52 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Row, + Col, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; + +import { Model } from 'reducers/interfaces'; +import BuiltModelItemComponent from './built-model-item'; + +interface Props { + models: Model[]; +} + +export default function IntegratedModelsListComponent(props: Props): JSX.Element { + const { models } = props; + const items = models.map((model): JSX.Element => ( + + )); + + return ( + <> + + + Primary + + + + + + + Framework + + + Name + + + Labels + + + { items } + + + + ); +} diff --git a/cvat-ui/src/components/models-page/empty-list.tsx b/cvat-ui/src/components/models-page/empty-list.tsx new file mode 100644 index 00000000000..4c73f9debbf --- /dev/null +++ b/cvat-ui/src/components/models-page/empty-list.tsx @@ -0,0 +1,44 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { Link } from 'react-router-dom'; +import Text from 'antd/lib/typography/Text'; +import { + Col, + Row, + Icon, +} from 'antd'; + +import { + EmptyTasksIcon as EmptyModelsIcon, +} from 'icons'; + +export default function EmptyListComponent(): JSX.Element { + return ( +
+ +
+ + + + + + No models uploaded yet ... + + + + + To annotate your tasks automatically + + + + + upload a new model + + + + ); +} diff --git a/cvat-ui/src/components/models-page/models-page.tsx b/cvat-ui/src/components/models-page/models-page.tsx new file mode 100644 index 00000000000..d3bacc5e4ae --- /dev/null +++ b/cvat-ui/src/components/models-page/models-page.tsx @@ -0,0 +1,76 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import './styles.scss'; +import React from 'react'; + +import { + Spin, +} from 'antd'; + +import TopBarComponent from './top-bar'; +import UploadedModelsList from './uploaded-models-list'; +import BuiltModelsList from './built-models-list'; +import EmptyListComponent from './empty-list'; +import FeedbackComponent from '../feedback/feedback'; +import { Model } from '../../reducers/interfaces'; + +interface Props { + installedAutoAnnotation: boolean; + installedTFSegmentation: boolean; + installedTFAnnotation: boolean; + modelsInitialized: boolean; + modelsFetching: boolean; + registeredUsers: any[]; + models: Model[]; + getModels(): void; + deleteModel(id: number): void; +} + +export default function ModelsPageComponent(props: Props): JSX.Element { + const { + installedAutoAnnotation, + installedTFSegmentation, + installedTFAnnotation, + modelsInitialized, + modelsFetching, + registeredUsers, + models, + + deleteModel, + } = props; + + if (!modelsInitialized) { + if (!modelsFetching) { + props.getModels(); + } + return ( + + ); + } + + const uploadedModels = models.filter((model): boolean => model.id !== null); + const integratedModels = models.filter((model): boolean => model.id === null); + + return ( +
+ + { !!integratedModels.length + && } + { !!uploadedModels.length && ( + + )} + { installedAutoAnnotation + && !uploadedModels.length + && !installedTFAnnotation + && !installedTFSegmentation + && } + +
+ ); +} diff --git a/cvat-ui/src/components/models-page/styles.scss b/cvat-ui/src/components/models-page/styles.scss new file mode 100644 index 00000000000..c67c893c3be --- /dev/null +++ b/cvat-ui/src/components/models-page/styles.scss @@ -0,0 +1,86 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +@import '../../base.scss'; + +.cvat-models-page { + padding-top: 30px; + height: 100%; + overflow: auto; + + > div:nth-child(1) { + margin-bottom: 10px; + + > div:nth-child(1) { + display: flex; + } + + > div:nth-child(2) { + display: flex; + justify-content: flex-end; + } + } +} + +.cvat-empty-models-list { + /* empty-models icon */ + > div:nth-child(1) { + margin-top: 50px; + } + + /* No models uploaded yet */ + > div:nth-child(2) > div { + margin-top: 20px; + + > span { + font-size: 20px; + color: $text-color; + } + } + + /* To annotate your task automatically */ + > div:nth-child(3) { + margin-top: 10px; + } +} + +.cvat-models-list { + height: 100%; + overflow-y: auto; +} + +.cvat-models-list-item { + width: 100%; + height: 60px; + border: 1px solid $border-color-1; + border-radius: 3px; + margin-bottom: 15px; + padding: 15px; + background: $background-color-1; + + &:hover { + border: 1px solid $border-color-hover; + } + + > div { + display: flex; + align-items: center; + } + + > div:nth-child(2) > span { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + + > div:nth-child(3) > span { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } +} + +#cvat-create-model-button { + padding: 0 30px; +} diff --git a/cvat-ui/src/components/models-page/top-bar.tsx b/cvat-ui/src/components/models-page/top-bar.tsx new file mode 100644 index 00000000000..91635f39c0d --- /dev/null +++ b/cvat-ui/src/components/models-page/top-bar.tsx @@ -0,0 +1,56 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import { RouteComponentProps } from 'react-router'; +import { withRouter } from 'react-router-dom'; + +import { + Col, + Row, + Button, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; + +type Props = { + installedAutoAnnotation: boolean; +} & RouteComponentProps; + +function TopBarComponent(props: Props): JSX.Element { + const { + installedAutoAnnotation, + history, + } = props; + + return ( + +
+ Models + + + { installedAutoAnnotation + && ( + + )} + + + ); +} + +export default withRouter(TopBarComponent); diff --git a/cvat-ui/src/components/models-page/uploaded-model-item.tsx b/cvat-ui/src/components/models-page/uploaded-model-item.tsx new file mode 100644 index 00000000000..8768f216fd1 --- /dev/null +++ b/cvat-ui/src/components/models-page/uploaded-model-item.tsx @@ -0,0 +1,94 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Row, + Col, + Tag, + Select, + Menu, + Dropdown, + Icon, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; +import moment from 'moment'; + +import { MenuIcon } from 'icons'; +import { Model } from 'reducers/interfaces'; + +interface Props { + model: Model; + owner: any; + onDelete(): void; +} + +export default function UploadedModelItem(props: Props): JSX.Element { + const { + model, + owner, + onDelete, + } = props; + + return ( + + + OpenVINO + + + + {model.name} + + + + + {owner ? owner.username : 'undefined'} + + + + + {moment(model.uploadDate).format('MMMM Do YYYY')} + + + + + + + Actions + + { + onDelete(); + }} + key='delete' + > + Delete + + + ) + } + > + + + + + ); +} diff --git a/cvat-ui/src/components/models-page/uploaded-models-list.tsx b/cvat-ui/src/components/models-page/uploaded-models-list.tsx new file mode 100644 index 00000000000..3e332de5385 --- /dev/null +++ b/cvat-ui/src/components/models-page/uploaded-models-list.tsx @@ -0,0 +1,75 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Row, + Col, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; + +import { Model } from 'reducers/interfaces'; +import UploadedModelItem from './uploaded-model-item'; + + +interface Props { + registeredUsers: any[]; + models: Model[]; + deleteModel(id: number): void; +} + +export default function UploadedModelsListComponent(props: Props): JSX.Element { + const { + models, + registeredUsers, + deleteModel, + } = props; + + const items = models.map((model): JSX.Element => { + const owner = registeredUsers.filter((user) => user.id === model.ownerID)[0]; + return ( + deleteModel(model.id as number)} + /> + ); + }); + + return ( + <> + + + Uploaded by a user + + + + + + + Framework + + + Name + + + Owner + + + Uploaded + + + Labels + + + + { items } + + + + ); +} diff --git a/cvat-ui/src/components/page-not-found/page-not-found.scss b/cvat-ui/src/components/page-not-found/page-not-found.scss deleted file mode 100644 index 40c0b5d3d09..00000000000 --- a/cvat-ui/src/components/page-not-found/page-not-found.scss +++ /dev/null @@ -1,3 +0,0 @@ -.empty.not-found { - height: 100vh; -} diff --git a/cvat-ui/src/components/page-not-found/page-not-found.test.tsx b/cvat-ui/src/components/page-not-found/page-not-found.test.tsx deleted file mode 100644 index 101c2cc0e08..00000000000 --- a/cvat-ui/src/components/page-not-found/page-not-found.test.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; - -import NotFound from './page-not-found'; - - -it('renders without crashing', () => { - const div = document.createElement('div'); - ReactDOM.render(, div); - ReactDOM.unmountComponentAtNode(div); -}); diff --git a/cvat-ui/src/components/page-not-found/page-not-found.tsx b/cvat-ui/src/components/page-not-found/page-not-found.tsx deleted file mode 100644 index 0458896929c..00000000000 --- a/cvat-ui/src/components/page-not-found/page-not-found.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React, { PureComponent } from 'react'; -import { Link } from 'react-router-dom'; - -import { Empty } from 'antd'; - -import './page-not-found.scss'; - - -class PageNotFound extends PureComponent { - render() { - return( - - Go back to tasks - - ); - } -} - -export default PageNotFound; diff --git a/cvat-ui/src/components/register-page/register-form.tsx b/cvat-ui/src/components/register-page/register-form.tsx new file mode 100644 index 00000000000..f300a5e4552 --- /dev/null +++ b/cvat-ui/src/components/register-page/register-form.tsx @@ -0,0 +1,248 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import { FormComponentProps } from 'antd/lib/form/Form'; +import { + Button, + Icon, + Input, + Form, +} from 'antd'; + +import patterns from 'utils/validation-patterns'; + +export interface RegisterData { + username: string; + firstName: string; + lastName: string; + email: string; + password1: string; + password2: string; +} + +type RegisterFormProps = { + fetching: boolean; + onSubmit(registerData: RegisterData): void; +} & FormComponentProps; + +class RegisterFormComponent extends React.PureComponent { + private validateConfirmation = (rule: any, value: any, callback: any): void => { + const { form } = this.props; + if (value && value !== form.getFieldValue('password1')) { + callback('Two passwords that you enter is inconsistent!'); + } else { + callback(); + } + }; + + private validatePassword = (_: any, value: any, callback: any): void => { + const { form } = this.props; + if (!patterns.validatePasswordLength.pattern.test(value)) { + callback(patterns.validatePasswordLength.message); + } + + if (!patterns.passwordContainsNumericCharacters.pattern.test(value)) { + callback(patterns.passwordContainsNumericCharacters.message); + } + + if (!patterns.passwordContainsUpperCaseCharacter.pattern.test(value)) { + callback(patterns.passwordContainsUpperCaseCharacter.message); + } + + if (!patterns.passwordContainsLowerCaseCharacter.pattern.test(value)) { + callback(patterns.passwordContainsLowerCaseCharacter.message); + } + + if (value) { + form.validateFields(['password2'], { force: true }); + } + callback(); + }; + + private validateUsername = (_: any, value: any, callback: any): void => { + if (!patterns.validateUsernameLength.pattern.test(value)) { + callback(patterns.validateUsernameLength.message); + } + + if (!patterns.validateUsernameCharacters.pattern.test(value)) { + callback(patterns.validateUsernameCharacters.message); + } + + callback(); + }; + + private handleSubmit = (e: React.FormEvent): void => { + e.preventDefault(); + const { + form, + onSubmit, + } = this.props; + + form.validateFields((error, values): void => { + if (!error) { + onSubmit(values); + } + }); + }; + + private renderFirstNameField(): JSX.Element { + const { form } = this.props; + + return ( + + {form.getFieldDecorator('firstName', { + rules: [{ + required: true, + message: 'Please specify a first name', + pattern: patterns.validateName.pattern, + }], + })( + } + placeholder='First name' + />, + )} + + ); + } + + private renderLastNameField(): JSX.Element { + const { form } = this.props; + + return ( + + {form.getFieldDecorator('lastName', { + rules: [{ + required: true, + message: 'Please specify a last name', + pattern: patterns.validateName.pattern, + }], + })( + } + placeholder='Last name' + />, + )} + + ); + } + + private renderUsernameField(): JSX.Element { + const { form } = this.props; + + return ( + + {form.getFieldDecorator('username', { + rules: [{ + required: true, + message: 'Please specify a username', + }, { + validator: this.validateUsername, + }], + })( + } + placeholder='Username' + />, + )} + + ); + } + + private renderEmailField(): JSX.Element { + const { form } = this.props; + + return ( + + {form.getFieldDecorator('email', { + rules: [{ + type: 'email', + message: 'The input is not valid E-mail!', + }, { + required: true, + message: 'Please specify an email address', + }], + })( + } + placeholder='Email address' + />, + )} + + ); + } + + private renderPasswordField(): JSX.Element { + const { form } = this.props; + + return ( + + {form.getFieldDecorator('password1', { + rules: [{ + required: true, + message: 'Please input your password!', + }, { + validator: this.validatePassword, + }], + })(} + placeholder='Password' + />)} + + ); + } + + private renderPasswordConfirmationField(): JSX.Element { + const { form } = this.props; + + return ( + + {form.getFieldDecorator('password2', { + rules: [{ + required: true, + message: 'Please confirm your password!', + }, { + validator: this.validateConfirmation, + }], + })(} + placeholder='Confirm password' + />)} + + ); + } + + public render(): JSX.Element { + const { fetching } = this.props; + + return ( +
+ {this.renderFirstNameField()} + {this.renderLastNameField()} + {this.renderUsernameField()} + {this.renderEmailField()} + {this.renderPasswordField()} + {this.renderPasswordConfirmationField()} + + + + + + ); + } +} + +export default Form.create()(RegisterFormComponent); diff --git a/cvat-ui/src/components/register-page/register-page.scss b/cvat-ui/src/components/register-page/register-page.scss deleted file mode 100644 index 0e46e1a8aeb..00000000000 --- a/cvat-ui/src/components/register-page/register-page.scss +++ /dev/null @@ -1,10 +0,0 @@ -.register-form { - display: flex; - flex-direction: column; - justify-content: center; - height: 100vh; - - &__title { - - } -} diff --git a/cvat-ui/src/components/register-page/register-page.test.tsx b/cvat-ui/src/components/register-page/register-page.test.tsx deleted file mode 100644 index 2b96c1a720e..00000000000 --- a/cvat-ui/src/components/register-page/register-page.test.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; - -import RegisterPage from './register-page'; - - -it('renders without crashing', () => { - const div = document.createElement('div'); - ReactDOM.render(, div); - ReactDOM.unmountComponentAtNode(div); -}); diff --git a/cvat-ui/src/components/register-page/register-page.tsx b/cvat-ui/src/components/register-page/register-page.tsx index 0f03589363f..b9383884678 100644 --- a/cvat-ui/src/components/register-page/register-page.tsx +++ b/cvat-ui/src/components/register-page/register-page.tsx @@ -1,211 +1,72 @@ -import React, { PureComponent } from 'react'; -import { Link } from 'react-router-dom'; +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT -import { connect } from 'react-redux'; -import { registerAsync, isAuthenticatedAsync } from '../../actions/auth.actions'; +import React from 'react'; -import { Button, Icon, Input, Form, Col, Row, Spin } from 'antd'; -import Title from 'antd/lib/typography/Title'; - -import './register-page.scss'; - - -class RegisterForm extends PureComponent { - constructor(props: any) { - super(props); - - this.state = { confirmDirty: false, loading: false }; - } - - componentDidMount() { - this.setState({ loading: true }); - - this.props.dispatch(isAuthenticatedAsync()).then( - (isAuthenticated: boolean) => { - this.setState({ loading: false }); +import { RouteComponentProps } from 'react-router'; +import { Link, withRouter } from 'react-router-dom'; - if (this.props.isAuthenticated) { - this.props.history.replace('/tasks'); - } - } - ); - } +import Title from 'antd/lib/typography/Title'; +import Text from 'antd/lib/typography/Text'; +import { + Col, + Row, +} from 'antd'; + +import RegisterForm, { RegisterData } from './register-form'; + +interface RegisterPageComponentProps { + fetching: boolean; + onRegister: (username: string, firstName: string, + lastName: string, email: string, + password1: string, password2: string) => void; +} - render() { - const { getFieldDecorator } = this.props.form; +function RegisterPageComponent( + props: RegisterPageComponentProps & RouteComponentProps, +): JSX.Element { + const sizes = { + xs: { span: 14 }, + sm: { span: 14 }, + md: { span: 10 }, + lg: { span: 4 }, + xl: { span: 4 }, + }; + + const { + fetching, + onRegister, + } = props; return ( - - -
- - Register - - - {getFieldDecorator('username', { - rules: [{ required: true, message: 'Please enter your username!' }], - })( - } - type="text" - name="username" - placeholder="Username" - />, - )} - - - - {getFieldDecorator('firstName', { - rules: [], - })( - } - type="text" - name="first-name" - placeholder="First name" - />, - )} - - - - {getFieldDecorator('lastName', { - rules: [], - })( - } - type="text" - name="last-name" - placeholder="Last name" - />, - )} - - - - {getFieldDecorator('email', { - rules: [ - { - type: 'email', - message: 'The input is not valid email!', - }, - { - required: true, - message: 'Please input your email!', - }, - ], - })( - } - type="text" - name="email" - placeholder="Email" - />, - )} - - - - {getFieldDecorator('password', { - rules: [ - { - required: true, - message: 'Please input your password!', - }, - { - validator: this.validateToNextPassword, - }, - ], - })( - } - name="password" - placeholder="Password" - />, - )} - - - - {getFieldDecorator('passwordConfirmation', { - rules: [ - { - required: true, - message: 'Please confirm your password!', - }, - { - validator: this.compareToFirstPassword, - }, - ], - })( - } - name="password-confirmation" - placeholder="Password confirmation" - />, - )} - - - - - - - Already have an account? Login here. - - + + + Create an account + { + onRegister( + registerData.username, + registerData.firstName, + registerData.lastName, + registerData.email, + registerData.password1, + registerData.password2, + ); + }} + /> + + + + Already have an account? + Login + + + + - ); - } - - private handleConfirmBlur = (event: any) => { - const { value } = event.target; - - this.setState({ confirmDirty: this.state.confirmDirty || !!value }); - }; - - private compareToFirstPassword = (rule: any, value: string, callback: Function) => { - const { form } = this.props; - - if (value && value !== form.getFieldValue('password')) { - callback('Two passwords that you enter are inconsistent!'); - } else { - callback(); - } - }; - - private validateToNextPassword = (rule: any, value: string, callback: Function) => { - const { form } = this.props; - - if (value && this.state.confirmDirty) { - form.validateFields(['passwordConfirmation'], { force: true }); - } - - callback(); - }; - - private onSubmit = (event: React.FormEvent) => { - event.preventDefault(); - - this.props.form.validateFields((error: any, values: any) => { - if (!error) { - this.props.dispatch( - registerAsync( - values.username, - values.firstName, - values.lastName, - values.email, - values.password, - values.passwordConfirmation, - this.props.history, - ), - ); - } - }); - } } -const mapStateToProps = (state: any) => { - return state.authContext; -}; - -export default Form.create()(connect(mapStateToProps)(RegisterForm)); +export default withRouter(RegisterPageComponent); diff --git a/cvat-ui/src/components/settings-page/player-settings.tsx b/cvat-ui/src/components/settings-page/player-settings.tsx new file mode 100644 index 00000000000..73f2a00a8ea --- /dev/null +++ b/cvat-ui/src/components/settings-page/player-settings.tsx @@ -0,0 +1,268 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Row, + Col, + Checkbox, + Slider, + Select, + InputNumber, + Icon, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; +import { CheckboxChangeEvent } from 'antd/lib/checkbox'; + +import { + BackJumpIcon, + ForwardJumpIcon, +} from 'icons'; + +import { + FrameSpeed, + GridColor, +} from 'reducers/interfaces'; + +interface Props { + frameStep: number; + frameSpeed: FrameSpeed; + resetZoom: boolean; + rotateAll: boolean; + grid: boolean; + gridSize: number; + gridColor: GridColor; + gridOpacity: number; + brightnessLevel: number; + contrastLevel: number; + saturationLevel: number; + onChangeFrameStep(step: number): void; + onChangeFrameSpeed(speed: FrameSpeed): void; + onSwitchResetZoom(enabled: boolean): void; + onSwitchRotateAll(rotateAll: boolean): void; + onSwitchGrid(grid: boolean): void; + onChangeGridSize(gridSize: number): void; + onChangeGridColor(gridColor: GridColor): void; + onChangeGridOpacity(gridOpacity: number): void; + onChangeBrightnessLevel(level: number): void; + onChangeContrastLevel(level: number): void; + onChangeSaturationLevel(level: number): void; +} + +export default function PlayerSettingsComponent(props: Props): JSX.Element { + const { + frameStep, + frameSpeed, + resetZoom, + rotateAll, + grid, + gridSize, + gridColor, + gridOpacity, + brightnessLevel, + contrastLevel, + saturationLevel, + onChangeFrameStep, + onChangeFrameSpeed, + onSwitchResetZoom, + onSwitchRotateAll, + onSwitchGrid, + onChangeGridSize, + onChangeGridColor, + onChangeGridOpacity, + onChangeBrightnessLevel, + onChangeContrastLevel, + onChangeSaturationLevel, + } = props; + + return ( +
+ +
+ Player step + { + if (value) { + onChangeFrameStep(value); + } + }} + /> + + + + Number of frames skipped when selecting + + or + + + + + + + Player speed + + + + + + { + onSwitchGrid(event.target.checked); + }} + > + Show grid + + + + + + Grid size + { + if (value) { + onChangeGridSize(value); + } + }} + /> + + + Grid color + + + + Grid opacity + { + onChangeGridOpacity(value as number); + }} + /> + {`${gridOpacity} %`} + + + + + + + { + onSwitchResetZoom(event.target.checked); + }} + > + Reset zoom + + + + Fit image after changing frame + + + + + + + { + onSwitchRotateAll(event.target.checked); + }} + > + Rotate all images + + + + Rotate all images simultaneously + + + + + + + Brightness + + + { + onChangeBrightnessLevel(value as number); + }} + /> + + + + + Contrast + + + { + onChangeContrastLevel(value as number); + }} + /> + + + + + Saturation + + + { + onChangeSaturationLevel(value as number); + }} + /> + + + + ); +} diff --git a/cvat-ui/src/components/settings-page/settings-page.tsx b/cvat-ui/src/components/settings-page/settings-page.tsx new file mode 100644 index 00000000000..6253d33f001 --- /dev/null +++ b/cvat-ui/src/components/settings-page/settings-page.tsx @@ -0,0 +1,82 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import './styles.scss'; +import React from 'react'; +import { + Row, + Col, + Tabs, + Icon, + Button, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; + +import { RouteComponentProps } from 'react-router'; + +import WorkspaceSettingsContainer from 'containers/settings-page/workspace-settings'; +import PlayerSettingsContainer from 'containers/settings-page/player-settings'; + +function SettingsPage(props: RouteComponentProps): JSX.Element { + return ( +
+ +
+ Settings + + + + + + + + Player + + ) + } + key='player' + > + + + + + Workspace + + ) + } + key='workspace' + > + + + + + + + + + + + + ); +} + +export default SettingsPage; diff --git a/cvat-ui/src/components/settings-page/styles.scss b/cvat-ui/src/components/settings-page/styles.scss new file mode 100644 index 00000000000..ef4b005a5d0 --- /dev/null +++ b/cvat-ui/src/components/settings-page/styles.scss @@ -0,0 +1,90 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +@import '../../base.scss'; + +.cvat-settings-page { + > div:nth-child(1) { + margin-top: 30px; + margin-bottom: 10px; + } +} + +.cvat-workspace-settings, .cvat-player-settings { + width: 100%; + height: max-content; + background: $background-color-1; + padding: 50px; +} + +.cvat-player-settings-grid, +.cvat-workspace-settings-auto-save, +.cvat-workspace-settings-show-interpolated-checkbox { + margin-bottom: 10px; +} + +.cvat-player-settings-grid-size, +.cvat-player-settings-grid-color, +.cvat-player-settings-grid-opacity, +.cvat-player-settings-step, +.cvat-player-settings-speed, +.cvat-player-settings-reset-zoom, +.cvat-player-settings-rotate-all, +.cvat-workspace-settings-show-interpolated, +.cvat-workspace-settings-aam-zoom-margin, +.cvat-workspace-settings-auto-save-interval { + margin-bottom: 25px; +} + +.cvat-player-settings-grid-size, +.cvat-player-settings-grid-color, +.cvat-player-settings-grid-opacity { + display: grid; + justify-items: start; +} + +.cvat-player-settings-grid-color { + > .ant-select { + width: 150px; + } +} + +.cvat-player-settings-grid-opacity { + > .ant-slider { + width: 150px; + } +} + +.cvat-player-settings-step, +.cvat-player-settings-speed { + > div { + display: grid; + justify-items: start; + } +} + +.cvat-player-settings-step > div > span > i { + margin: 0px 5px; + font-size: 10px; +} + +.cvat-player-settings-speed > div > .ant-select { + width: 90px; +} + +.cvat-player-settings-brightness, +.cvat-player-settings-contrast, +.cvat-player-settings-saturation { + width: 40%; +} + +.cvat-settings-page-back-button { + width: 100px; + margin-top: 15px; +} + +.cvat-settings-page-back-button-wrapper { + display: flex; + justify-content: flex-end; +} diff --git a/cvat-ui/src/components/settings-page/workspace-settings.tsx b/cvat-ui/src/components/settings-page/workspace-settings.tsx new file mode 100644 index 00000000000..6d265e4ab41 --- /dev/null +++ b/cvat-ui/src/components/settings-page/workspace-settings.tsx @@ -0,0 +1,105 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Row, + Col, + Checkbox, + InputNumber, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; +import { CheckboxChangeEvent } from 'antd/lib/checkbox'; + +interface Props { + autoSave: boolean; + autoSaveInterval: number; + aamZoomMargin: number; + showAllInterpolationTracks: boolean; + onSwitchAutoSave(enabled: boolean): void; + onChangeAutoSaveInterval(interval: number): void; + onChangeAAMZoomMargin(margin: number): void; + onSwitchShowingInterpolatedTracks(enabled: boolean): void; +} + +export default function WorkspaceSettingsComponent(props: Props): JSX.Element { + const { + autoSave, + autoSaveInterval, + aamZoomMargin, + showAllInterpolationTracks, + onSwitchAutoSave, + onChangeAutoSaveInterval, + onChangeAAMZoomMargin, + onSwitchShowingInterpolatedTracks, + } = props; + + return ( +
+ +
+ { + onSwitchAutoSave(event.target.checked); + }} + > + Enable auto save + + + + + + Auto save every + { + if (value) { + onChangeAutoSaveInterval(value * 60 * 1000); + } + }} + /> + minutes + + + + + { + onSwitchShowingInterpolatedTracks(event.target.checked); + }} + > + Show all interpolation tracks + + + + Show hidden interpolated objects in the side panel + + + + + Attribute annotation mode (AAM) zoom margin + { + if (value) { + onChangeAAMZoomMargin(value); + } + }} + /> + + + + ); +} diff --git a/cvat-ui/src/components/shortcuts-dialog/shortcuts-dialog.tsx b/cvat-ui/src/components/shortcuts-dialog/shortcuts-dialog.tsx new file mode 100644 index 00000000000..c8a0ee7c67a --- /dev/null +++ b/cvat-ui/src/components/shortcuts-dialog/shortcuts-dialog.tsx @@ -0,0 +1,97 @@ +import React from 'react'; +import { getApplicationKeyMap } from 'react-hotkeys'; +import { Modal, Table } from 'antd'; +import { connect } from 'react-redux'; + +import { shortcutsActions } from 'actions/shortcuts-actions'; +import { CombinedState } from 'reducers/interfaces'; + +interface StateToProps { + visible: boolean; +} + +interface DispatchToProps { + switchShortcutsDialog(): void; +} + +function mapStateToProps(state: CombinedState): StateToProps { + const { + shortcuts: { + visibleShortcutsHelp: visible, + }, + } = state; + + return { + visible, + }; +} + +function mapDispatchToProps(dispatch: any): DispatchToProps { + return { + switchShortcutsDialog(): void { + dispatch(shortcutsActions.switchShortcutsDialog()); + }, + }; +} + +function ShorcutsDialog(props: StateToProps & DispatchToProps): JSX.Element | null { + const { visible, switchShortcutsDialog } = props; + const keyMap = getApplicationKeyMap(); + + const splitToRows = (data: string[]): JSX.Element[] => ( + data.map((item: string, id: number): JSX.Element => ( + // eslint-disable-next-line react/no-array-index-key + + {item} +
+
+ )) + ); + + const columns = [{ + title: 'Name', + dataIndex: 'name', + key: 'name', + }, { + title: 'Shortcut', + dataIndex: 'shortcut', + key: 'shortcut', + render: splitToRows, + }, { + title: 'Action', + dataIndex: 'action', + key: 'action', + render: splitToRows, + }, { + title: 'Description', + dataIndex: 'description', + key: 'description', + }]; + + const dataSource = Object.keys(keyMap).map((key: string, id: number) => ({ + key: id, + name: keyMap[key].name || key, + description: keyMap[key].description || '', + shortcut: keyMap[key].sequences.map((value) => value.sequence), + action: keyMap[key].sequences.map((value) => value.action || 'keydown'), + })); + + return ( + +
+ + ); +} + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(ShorcutsDialog); diff --git a/cvat-ui/src/components/task-page/details.tsx b/cvat-ui/src/components/task-page/details.tsx new file mode 100644 index 00000000000..2c690330e75 --- /dev/null +++ b/cvat-ui/src/components/task-page/details.tsx @@ -0,0 +1,421 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Row, + Col, + Tag, + Icon, + Modal, + Button, + notification, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; +import Title from 'antd/lib/typography/Title'; + +import moment from 'moment'; + +import getCore from 'cvat-core'; +import patterns from 'utils/validation-patterns'; +import { getReposData, syncRepos } from 'utils/git-utils'; +import UserSelector from './user-selector'; +import LabelsEditorComponent from '../labels-editor/labels-editor'; + +const core = getCore(); + +interface Props { + previewImage: string; + taskInstance: any; + installedGit: boolean; // change to git repos url + registeredUsers: any[]; + onTaskUpdate: (taskInstance: any) => void; +} + +interface State { + name: string; + bugTracker: string; + repository: string; + repositoryStatus: string; +} + +export default class DetailsComponent extends React.PureComponent { + private mounted: boolean; + + constructor(props: Props) { + super(props); + + const { taskInstance } = props; + + this.mounted = false; + this.state = { + name: taskInstance.name, + bugTracker: taskInstance.bugTracker, + repository: '', + repositoryStatus: '', + }; + } + + public componentDidMount(): void { + const { taskInstance } = this.props; + this.mounted = true; + + getReposData(taskInstance.id) + .then((data): void => { + if (data !== null && this.mounted) { + if (data.status.error) { + notification.error({ + message: 'Could not receive repository status', + description: data.status.error, + }); + } else { + this.setState({ + repositoryStatus: data.status.value, + }); + } + + this.setState({ + repository: data.url, + }); + } + }).catch((error): void => { + if (this.mounted) { + notification.error({ + message: 'Could not receive repository status', + description: error.toString(), + }); + } + }); + } + + + public componentDidUpdate(prevProps: Props): void { + const { taskInstance } = this.props; + + if (prevProps !== this.props) { + this.setState({ + name: taskInstance.name, + bugTracker: taskInstance.bugTracker, + }); + } + } + + public componentWillUnmount(): void { + this.mounted = false; + } + + private renderTaskName(): JSX.Element { + const { name } = this.state; + const { + taskInstance, + onTaskUpdate, + } = this.props; + + return ( + { + this.setState({ + name: value, + }); + + taskInstance.name = value; + onTaskUpdate(taskInstance); + }, + }} + className='cvat-text-color' + > + {name} + + ); + } + + private renderPreview(): JSX.Element { + const { previewImage } = this.props; + return ( +
+ Preview +
+ ); + } + + private renderParameters(): JSX.Element { + const { taskInstance } = this.props; + const { overlap } = taskInstance; + const { segmentSize } = taskInstance; + const { imageQuality } = taskInstance; + const zOrder = taskInstance.zOrder.toString(); + + return ( + <> + +
+ Overlap size +
+ {overlap} + + + Segment size +
+ {segmentSize} + + + + + Image quality +
+ {imageQuality} + + + Z-order +
+ {zOrder} + + + + ); + } + + private renderUsers(): JSX.Element { + const { + taskInstance, + registeredUsers, + onTaskUpdate, + } = this.props; + const owner = taskInstance.owner ? taskInstance.owner.username : null; + const assignee = taskInstance.assignee ? taskInstance.assignee.username : null; + const created = moment(taskInstance.createdDate).format('MMMM Do YYYY'); + const assigneeSelect = ( + { + let [userInstance] = registeredUsers + .filter((user: any) => user.username === value); + + if (userInstance === undefined) { + userInstance = null; + } + + taskInstance.assignee = userInstance; + onTaskUpdate(taskInstance); + } + } + /> + ); + + return ( + + + { owner && ( + + {`Created by ${owner} on ${created}`} + + )} + + + + Assigned to + { assigneeSelect } + + + + ); + } + + private renderDatasetRepository(): JSX.Element | boolean { + const { taskInstance } = this.props; + const { + repository, + repositoryStatus, + } = this.state; + + return ( + !!repository + && ( + + + Dataset Repository +
+ {repository} + {repositoryStatus === 'sync' + && ( + + + Synchronized + + )} + {repositoryStatus === 'merged' + && ( + + + Merged + + )} + {repositoryStatus === 'syncing' + && ( + + + Syncing + + )} + {repositoryStatus === '!sync' + && ( + { + this.setState({ + repositoryStatus: 'syncing', + }); + + syncRepos(taskInstance.id).then((): void => { + if (this.mounted) { + this.setState({ + repositoryStatus: 'sync', + }); + } + }).catch((): void => { + if (this.mounted) { + this.setState({ + repositoryStatus: '!sync', + }); + } + }); + }} + > + + Synchronize + + )} + + + ) + ); + } + + private renderBugTracker(): JSX.Element { + const { + taskInstance, + onTaskUpdate, + } = this.props; + const { bugTracker } = this.state; + + let shown = false; + const onChangeValue = (value: string): void => { + if (value && !patterns.validateURL.pattern.test(value)) { + if (!shown) { + Modal.error({ + title: `Could not update the task ${taskInstance.id}`, + content: 'Issue tracker is expected to be URL', + onOk: (() => { + shown = false; + }), + }); + shown = true; + } + } else { + this.setState({ + bugTracker: value, + }); + + taskInstance.bugTracker = value; + onTaskUpdate(taskInstance); + } + }; + + if (bugTracker) { + return ( + + + Issue Tracker +
+ {bugTracker} + + + + ); + } + + return ( + + + Issue Tracker +
+ Not specified + + + ); + } + + private renderLabelsEditor(): JSX.Element { + const { + taskInstance, + onTaskUpdate, + } = this.props; + + return ( + + + label.toJSON(), + )} + onSubmit={(labels: any[]): void => { + taskInstance.labels = labels + .map((labelData): any => new core.classes.Label(labelData)); + onTaskUpdate(taskInstance); + }} + /> + + + ); + } + + public render(): JSX.Element { + return ( +
+ +
+ { this.renderTaskName() } + + + + + + + { this.renderPreview() } + + + + + { this.renderParameters() } + + + + + { this.renderUsers() } + { this.renderBugTracker() } + { this.renderDatasetRepository() } + { this.renderLabelsEditor() } + + + + ); + } +} diff --git a/cvat-ui/src/components/task-page/job-list.tsx b/cvat-ui/src/components/task-page/job-list.tsx new file mode 100644 index 00000000000..9add6f4a41a --- /dev/null +++ b/cvat-ui/src/components/task-page/job-list.tsx @@ -0,0 +1,200 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { RouteComponentProps } from 'react-router'; +import { withRouter } from 'react-router-dom'; + +import { + Row, + Col, + Icon, + Table, + Button, + Tooltip, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; + +import moment from 'moment'; +import copy from 'copy-to-clipboard'; + +import getCore from 'cvat-core'; +import UserSelector from './user-selector'; + +const core = getCore(); + +const baseURL = core.config.backendAPI.slice(0, -7); + +interface Props { + taskInstance: any; + registeredUsers: any[]; + onJobUpdate(jobInstance: any): void; +} + +function JobListComponent(props: Props & RouteComponentProps): JSX.Element { + const { + taskInstance, + registeredUsers, + onJobUpdate, + history: { + push, + }, + } = props; + + const { jobs, id: taskId } = taskInstance; + const columns = [{ + title: 'Job', + dataIndex: 'job', + key: 'job', + render: (id: number): JSX.Element => ( +
+ + | + + + +
+ ), + }, { + title: 'Frames', + dataIndex: 'frames', + key: 'frames', + className: 'cvat-text-color', + }, { + title: 'Status', + dataIndex: 'status', + key: 'status', + render: (status: string): JSX.Element => { + let progressColor = null; + if (status === 'completed') { + progressColor = 'cvat-job-completed-color'; + } else if (status === 'validation') { + progressColor = 'cvat-job-validation-color'; + } else { + progressColor = 'cvat-job-annotation-color'; + } + + return ( + { status } + ); + }, + }, { + title: 'Started on', + dataIndex: 'started', + key: 'started', + className: 'cvat-text-color', + }, { + title: 'Duration', + dataIndex: 'duration', + key: 'duration', + className: 'cvat-text-color', + }, { + title: 'Assignee', + dataIndex: 'assignee', + key: 'assignee', + render: (jobInstance: any): JSX.Element => { + const assignee = jobInstance.assignee ? jobInstance.assignee.username : null; + + return ( + { + let [userInstance] = [...registeredUsers] + .filter((user: any) => user.username === value); + + if (userInstance === undefined) { + userInstance = null; + } + + // eslint-disable-next-line + jobInstance.assignee = userInstance; + onJobUpdate(jobInstance); + }} + /> + ); + }, + }]; + + let completed = 0; + const data = jobs.reduce((acc: any[], job: any) => { + if (job.status === 'completed') { + completed++; + } + + const created = moment(props.taskInstance.createdDate); + + acc.push({ + key: job.id, + job: job.id, + frames: `${job.startFrame}-${job.stopFrame}`, + status: `${job.status}`, + started: `${created.format('MMMM Do YYYY HH:MM')}`, + duration: `${moment.duration(moment(moment.now()).diff(created)).humanize()}`, + assignee: job, + }); + + return acc; + }, []); + + return ( +
+ +
+ Jobs + + + + + + + {`${completed} of ${data.length} jobs`} + + + +
+ + ); +} + +export default withRouter(JobListComponent); diff --git a/cvat-ui/src/components/task-page/styles.scss b/cvat-ui/src/components/task-page/styles.scss new file mode 100644 index 00000000000..e7e850686f5 --- /dev/null +++ b/cvat-ui/src/components/task-page/styles.scss @@ -0,0 +1,124 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +@import '../../base.scss'; + +.cvat-task-details-wrapper { + overflow-y: auto; + height: 100%; + + .cvat-task-details { + width: 100%; + height: auto; + border: 1px solid $border-color-1; + border-radius: 3px; + padding: 20px; + background: $background-color-1; + + > div:nth-child(2) { + > div:nth-child(2) { + padding-left: 20px; + + > div:not(:first-child) { + margin-top: 20px; + } + } + } + } + + > div > div > div > button { + display: flex; + align-items: center; + } +} + +.cvat-dataset-repository-url { + > a { + margin-right: 10px; + } + + > .ant-tag-red { + user-select: none; + opacity: 0.4; + + &:hover { + opacity: 0.8; + } + + &:active { + opacity: 1; + } + } + + > .ant-tag > i { + margin-right: 5px; + } +} + +.cvat-task-job-list { + width: 100%; + height: auto; + border: 1px solid $border-color-1; + border-radius: 3px; + margin-top: 20px; + padding: 20px; + background: $background-color-1; + + > div:nth-child(1) > div:nth-child(1) { + display: flex; + } +} + +.cvat-task-top-bar { + margin-top: 20px; + margin-bottom: 10px; +} + +.cvat-task-preview-wrapper { + display: flex; + justify-content: flex-start; + overflow: hidden; + margin-bottom: 20px; + + > .cvat-task-preview { + max-width: 252px; + max-height: 144px; + } +} + +.cvat-user-selector { + margin-left: 10px; + width: 150px; +} + +.cvat-open-bug-tracker-button { + margin-left: 15px; +} + +.ant-typography.cvat-jobs-header { + margin-bottom: 0px; + font-size: 20px; + font-weight: bold; +} + +/* Pagination in center */ +.cvat-task-jobs-table > div > div { + text-align: center; + + > .ant-table-pagination.ant-pagination { + float: none; + } +} + +.cvat-job-completed-color { + color: $completed-progress-color; +} + +.cvat-job-validation-color { + color: $inprogress-progress-color; +} + +.cvat-job-annotation-color { + color: $pending-progress-color; +} diff --git a/cvat-ui/src/components/task-page/task-page.tsx b/cvat-ui/src/components/task-page/task-page.tsx new file mode 100644 index 00000000000..370349a3302 --- /dev/null +++ b/cvat-ui/src/components/task-page/task-page.tsx @@ -0,0 +1,88 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import './styles.scss'; +import React from 'react'; +import { RouteComponentProps } from 'react-router'; +import { withRouter } from 'react-router-dom'; + +import { + Col, + Row, + Spin, + Result, +} from 'antd'; + +import DetailsContainer from 'containers/task-page/details'; +import JobListContainer from 'containers/task-page/job-list'; +import ModelRunnerModalContainer from 'containers/model-runner-dialog/model-runner-dialog'; +import { Task } from 'reducers/interfaces'; +import TopBarComponent from './top-bar'; + +interface TaskPageComponentProps { + task: Task | null | undefined; + fetching: boolean; + deleteActivity: boolean | null; + installedGit: boolean; + getTask: () => void; +} + +type Props = TaskPageComponentProps & RouteComponentProps<{id: string}>; + +class TaskPageComponent extends React.PureComponent { + public componentDidUpdate(): void { + const { + deleteActivity, + history, + } = this.props; + + if (deleteActivity) { + history.replace('/tasks'); + } + } + + public render(): JSX.Element { + const { + task, + fetching, + getTask, + } = this.props; + + if (task === null) { + if (!fetching) { + getTask(); + } + + return ( + + ); + } + + if (typeof (task) === 'undefined') { + return ( + + ); + } + + return ( + <> + + + + + + + + + + ); + } +} + +export default withRouter(TaskPageComponent); diff --git a/cvat-ui/src/components/task-page/top-bar.tsx b/cvat-ui/src/components/task-page/top-bar.tsx new file mode 100644 index 00000000000..05bcb645bf7 --- /dev/null +++ b/cvat-ui/src/components/task-page/top-bar.tsx @@ -0,0 +1,50 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Row, + Col, + Button, + Dropdown, + Icon, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; + +import ActionsMenuContainer from 'containers/actions-menu/actions-menu'; +import { MenuIcon } from 'icons'; + +interface DetailsComponentProps { + taskInstance: any; +} + +export default function DetailsComponent(props: DetailsComponentProps): JSX.Element { + const { taskInstance } = props; + const { id } = taskInstance; + + return ( + + + {`Task details #${id}`} + + + + ) + } + > + + + + + ); +} diff --git a/cvat-ui/src/components/task-page/user-selector.tsx b/cvat-ui/src/components/task-page/user-selector.tsx new file mode 100644 index 00000000000..186ff54da49 --- /dev/null +++ b/cvat-ui/src/components/task-page/user-selector.tsx @@ -0,0 +1,40 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Select, +} from 'antd'; + +interface Props { + value: string | null; + users: any[]; + onChange: (user: string) => void; +} + +export default function UserSelector(props: Props): JSX.Element { + const { + value, + users, + onChange, + } = props; + + return ( + + ); +} diff --git a/cvat-ui/src/components/tasks-page/empty-list.tsx b/cvat-ui/src/components/tasks-page/empty-list.tsx new file mode 100644 index 00000000000..3860dd98413 --- /dev/null +++ b/cvat-ui/src/components/tasks-page/empty-list.tsx @@ -0,0 +1,42 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { Link } from 'react-router-dom'; +import Text from 'antd/lib/typography/Text'; +import { + Col, + Row, + Icon, +} from 'antd'; + +import { EmptyTasksIcon } from 'icons'; + +export default function EmptyListComponent(): JSX.Element { + return ( +
+ +
+ + + + + + No tasks created yet ... + + + + + To get started with your annotation project + + + + + create a new task + + + + ); +} diff --git a/cvat-ui/src/components/tasks-page/styles.scss b/cvat-ui/src/components/tasks-page/styles.scss new file mode 100644 index 00000000000..b2fbc9be79c --- /dev/null +++ b/cvat-ui/src/components/tasks-page/styles.scss @@ -0,0 +1,164 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +@import '../../base.scss'; + +.cvat-tasks-page { + padding-top: 15px; + height: 100%; + + > div:nth-child(1) { + margin-bottom: 10px; + + div > { + span { + color: $text-color; + } + } + } + + > div:nth-child(2) { + > div:nth-child(1) { + display: flex; + + > span:nth-child(2) { + width: 200px; + margin-left: 10px; + } + } + + > div:nth-child(2) { + display: flex; + justify-content: flex-end; + } + } + + > div:nth-child(3) { + height: 83%; + margin-top: 10px; + } + + > div:nth-child(4) { + margin-top: 10px; + } +} + +/* empty-tasks icon */ +.cvat-empty-tasks-list { + > div:nth-child(1) { + margin-top: 50px; + } + + > div:nth-child(2) { + > div { + margin-top: 20px; + + /* No tasks created yet */ + > span { + font-size: 20px; + color: $text-color; + } + } + } + + /* To get started with your annotation project .. */ + > div:nth-child(3) { + margin-top: 10px; + } +} + +.cvat-tasks-pagination { + display: flex; + justify-content: center; +} + +.cvat-tasks-list { + height: 100%; + overflow-y: auto; +} + +.cvat-tasks-list-item { + width: 100%; + height: 120px; + border: 1px solid $border-color-1; + border-radius: 3px; + margin-bottom: 15px; + padding-top: 20px; + background: $background-color-1; + + &:hover { + border: 1px solid $border-color-hover; + } +} + +.cvat-task-item-preview-wrapper { + display: flex; + justify-content: center; + overflow: hidden; + margin: 20px; + margin-top: 0px; + + > .cvat-task-item-preview { + max-width: 140px; + max-height: 80px; + } +} + +.cvat-task-item-description { + word-break: break-all; + max-height: 100%; + overflow: hidden; +} + +.cvat-task-progress { + width: 100%; +} + +.cvat-task-completed-progress { + color: $completed-progress-color; + fill: $completed-progress-color; + margin-right: 5px; +} + +.cvat-task-completed-progress { + div.ant-progress-bg { + background: $completed-progress-color !important; /* csslint allow: important */ + } +} + +.cvat-task-progress-progress { + color: $inprogress-progress-color; + fill: $inprogress-progress-color; + margin-right: 5px; +} + +.cvat-task-pending-progress { + color: $pending-progress-color; + fill: $pending-progress-color; + margin-right: 5px; +} + +.close-auto-annotation-icon { + color: $danger-icon-color; + opacity: 0.7; + + &:hover { + opacity: 1; + } +} + +.cvat-item-open-task-actions { + margin-right: 5px; + margin-top: 10px; + display: flex; + align-items: center; +} + +.cvat-item-open-task-button { + margin-right: 20px; +} + +#cvat-create-task-button { + padding: 0 30px; +} diff --git a/cvat-ui/src/components/tasks-page/task-item.tsx b/cvat-ui/src/components/tasks-page/task-item.tsx new file mode 100644 index 00000000000..915544f65eb --- /dev/null +++ b/cvat-ui/src/components/tasks-page/task-item.tsx @@ -0,0 +1,236 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import { RouteComponentProps } from 'react-router'; +import { withRouter } from 'react-router-dom'; + +import Text from 'antd/lib/typography/Text'; +import { + Col, + Row, + Button, + Icon, + Progress, + Dropdown, + Tooltip, + Modal, +} from 'antd'; + +import moment from 'moment'; + +import ActionsMenuContainer from 'containers/actions-menu/actions-menu'; +import { ActiveInference } from 'reducers/interfaces'; +import { MenuIcon } from 'icons'; + +export interface TaskItemProps { + taskInstance: any; + previewImage: string; + deleted: boolean; + hidden: boolean; + activeInference: ActiveInference | null; + cancelAutoAnnotation(): void; +} + +class TaskItemComponent extends React.PureComponent { + private renderPreview(): JSX.Element { + const { previewImage } = this.props; + return ( + +
+ Preview +
+ + ); + } + + private renderDescription(): JSX.Element { + // Task info + const { taskInstance } = this.props; + const { id } = taskInstance; + const owner = taskInstance.owner ? taskInstance.owner.username : null; + const updated = moment(taskInstance.updatedDate).fromNow(); + const created = moment(taskInstance.createdDate).format('MMMM Do YYYY'); + + // Get and truncate a task name + const name = `${taskInstance.name.substring(0, 70)}${taskInstance.name.length > 70 ? '...' : ''}`; + + return ( + + {`#${id}: `} + {name} +
+ { owner + && ( + <> + + {`Created ${owner ? `by ${owner}` : ''} on ${created}`} + +
+ + )} + {`Last updated ${updated}`} + + ); + } + + private renderProgress(): JSX.Element { + const { + taskInstance, + activeInference, + cancelAutoAnnotation, + } = this.props; + // Count number of jobs and performed jobs + const numOfJobs = taskInstance.jobs.length; + const numOfCompleted = taskInstance.jobs.filter( + (job: any): boolean => job.status === 'completed', + ).length; + + // Progress appearence depends on number of jobs + let progressColor = null; + let progressText = null; + if (numOfCompleted === numOfJobs) { + progressColor = 'cvat-task-completed-progress'; + progressText = Completed; + } else if (numOfCompleted) { + progressColor = 'cvat-task-progress-progress'; + progressText = In Progress; + } else { + progressColor = 'cvat-task-pending-progress'; + progressText = Pending; + } + + const jobsProgress = numOfCompleted / numOfJobs; + + return ( + + + + + + + { progressText } + + + {`${numOfCompleted} of ${numOfJobs} jobs`} + + + + + + + + { activeInference + && ( + <> + + + Automatic annotation + + + + + + + + + { + Modal.confirm({ + title: 'You are going to cancel automatic annotation?', + content: 'Reached progress will be lost. Continue?', + okType: 'danger', + onOk() { + cancelAutoAnnotation(); + }, + }); + }} + /> + + + + + )} + + ); + } + + private renderNavigation(): JSX.Element { + const { + taskInstance, + history, + } = this.props; + const { id } = taskInstance; + + return ( + + + + + + + + + Actions + }> + + + + + + ); + } + + public render(): JSX.Element { + const { + deleted, + hidden, + } = this.props; + const style = {}; + if (deleted) { + (style as any).pointerEvents = 'none'; + (style as any).opacity = 0.5; + } + + if (hidden) { + (style as any).display = 'none'; + } + + return ( + + {this.renderPreview()} + {this.renderDescription()} + {this.renderProgress()} + {this.renderNavigation()} + + ); + } +} + +export default withRouter(TaskItemComponent); diff --git a/cvat-ui/src/components/tasks-page/task-list.tsx b/cvat-ui/src/components/tasks-page/task-list.tsx new file mode 100644 index 00000000000..c328170c915 --- /dev/null +++ b/cvat-ui/src/components/tasks-page/task-list.tsx @@ -0,0 +1,56 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Col, + Row, + Pagination, +} from 'antd'; + +import ModelRunnerModalContainer from 'containers/model-runner-dialog/model-runner-dialog'; +import TaskItem from 'containers/tasks-page/task-item'; + +export interface ContentListProps { + onSwitchPage(page: number): void; + currentTasksIndexes: number[]; + currentPage: number; + numberOfTasks: number; +} + +export default function TaskListComponent(props: ContentListProps): JSX.Element { + const { + currentTasksIndexes, + numberOfTasks, + currentPage, + onSwitchPage, + } = props; + const taskViews = currentTasksIndexes.map( + (tid, id): JSX.Element => , + ); + + return ( + <> + + + { taskViews } + + + + + + + + + + ); +} diff --git a/cvat-ui/src/components/tasks-page/tasks-content/tasks-content.scss b/cvat-ui/src/components/tasks-page/tasks-content/tasks-content.scss deleted file mode 100644 index 4c0578ed9c3..00000000000 --- a/cvat-ui/src/components/tasks-page/tasks-content/tasks-content.scss +++ /dev/null @@ -1,45 +0,0 @@ -.tasks-content { - width: 1024px; - min-height: calc(100vh - 90px - 64px - 46px); - margin: 0 auto; - - .tasks-content-сard { - margin-bottom: 20px; - padding: 20px; - border: 1px solid #001529; - border-radius: 3px; - background: white; - - &__header { - text-align: center; - - h2 { - margin-bottom: 20px; - } - } - - &__content { - - .card-cover { - - img { - max-width: 300px; - } - } - - .card-actions { - display: flex; - flex-flow: column wrap; - - button { - min-width: 200px; - margin-bottom: 1px; - } - } - - .card-jobs { - - } - } - } -} diff --git a/cvat-ui/src/components/tasks-page/tasks-content/tasks-content.test.tsx b/cvat-ui/src/components/tasks-page/tasks-content/tasks-content.test.tsx deleted file mode 100644 index 987e20ebdbf..00000000000 --- a/cvat-ui/src/components/tasks-page/tasks-content/tasks-content.test.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; - -import TasksContent from './tasks-content'; - - -it('renders without crashing', () => { - const div = document.createElement('div'); - ReactDOM.render(, div); - ReactDOM.unmountComponentAtNode(div); -}); diff --git a/cvat-ui/src/components/tasks-page/tasks-content/tasks-content.tsx b/cvat-ui/src/components/tasks-page/tasks-content/tasks-content.tsx deleted file mode 100644 index d8895c1eec5..00000000000 --- a/cvat-ui/src/components/tasks-page/tasks-content/tasks-content.tsx +++ /dev/null @@ -1,361 +0,0 @@ -import React, { Component } from 'react'; - -import { withRouter } from 'react-router-dom'; - -import { connect } from 'react-redux'; -import { createTaskAsync, updateTaskAsync, deleteTaskAsync } from '../../../actions/tasks.actions'; -import { getAnnotationFormatsAsync } from '../../../actions/server.actions'; -import { dumpAnnotationAsync, uploadAnnotationAsync } from '../../../actions/annotations.actions'; - -import { Layout, Empty, Button, Modal, Col, Row, Menu, Dropdown, Icon, Upload } from 'antd'; -import Title from 'antd/lib/typography/Title'; - -import { ClickParam } from 'antd/lib/menu'; -import { UploadChangeParam } from 'antd/lib/upload'; - -import TaskUpdateForm from '../../modals/task-update/task-update'; -import TaskCreateForm from '../../modals/task-create/task-create'; - -import { deserializeLabels, taskDTO } from '../../../utils/tasks-dto'; - -import './tasks-content.scss'; - - -const { Content } = Layout; - -class TasksContent extends Component { - hostUrl: string | undefined; - apiUrl: string | undefined; - - createFormRef: any; - updateFormRef: any; - - constructor(props: any) { - super(props); - - this.state = { - dumpers: [], - loaders: [], - selectedLoader: null, - activeTaskId: null, - }; - - this.hostUrl = process.env.REACT_APP_API_HOST_URL; - this.apiUrl = process.env.REACT_APP_API_FULL_URL; - } - - componentDidMount() { - this.props.dispatch(getAnnotationFormatsAsync()).then( - (formats: any) => { - const dumpers = []; - const loaders = []; - - for (const format of this.props.annotationFormats) { - for (const dumper of format.dumpers) { - dumpers.push(dumper); - } - - for (const loader of format.loaders) { - loaders.push(loader); - } - } - - this.setState({ dumpers, loaders }); - } - ); - } - - render() { - return( - <> - { this.props.tasks.length ? this.renderTasks() : this.renderEmpty() } - - ); - } - - private renderEmpty() { - return ( - - To get started with your annotation project - - {/* // TODO: uncomment when modals -> pages */} - {/* create a new task */} - - ) - } - - private renderTasks() { - return ( - - { - this.props.tasks.map( - (task: any) => ( -
- -
- { `${task.name}: ${task.mode}` } - - - - - - Task cover - - - - - - - - - - - - - - - - - - - - - - - Jobs - { - task.jobs.map( - (job: any) => ( - - {`${this.hostUrl}?id=${job.id}`} - - ) - ) - } - - - - ) - ) - } - - ); - } - - private dumpAnnotationMenu = (task: any) => { - return ( - this.onDumpAnnotation(task, params, this) }> - { - this.state.dumpers.map( - (dumper: any) => ( - - { dumper.name } - - ) - ) - } - - ); - } - - private uploadAnnotationMenu = (task: any) => { - return ( - this.setState({ selectedLoader: params.key, loaderTask: task }) }> - { - this.state.loaders.map( - (loader: any) => ( - - - - - - ), - ) - } - - ); - } - - private setTaskCreateFormRef = (ref: any) => { - this.createFormRef = ref; - } - - private setTaskUpdateFormRef = (ref: any) => { - this.updateFormRef = ref; - } - - private onCreateTask = () => { - Modal.confirm({ - title: 'Create new task', - content: , - centered: true, - className: 'crud-modal', - okText: 'Create', - okType: 'primary', - onOk: () => { - return new Promise((resolve, reject) => { - this.createFormRef.validateFields((error: any, values: any) => { - if (!error) { - const newTask = taskDTO(values); - - this.props.dispatch(createTaskAsync(newTask)).then( - (data: any) => { - resolve(data); - }, - (error: any) => { - reject(error); - Modal.error({ title: error.message, centered: true, okType: 'danger' }); - }, - ); - } else { - reject(error); - } - }); - }); - }, - onCancel: () => { - return; - }, - }); - } - - private onUpdateTask = (task: any) => { - Modal.confirm({ - title: 'Update task', - content: , - centered: true, - className: 'crud-modal', - okText: 'Update', - okType: 'primary', - onOk: () => { - return new Promise((resolve, reject) => { - this.updateFormRef.validateFields((error: any, values: any) => { - if (!error) { - const deserializedLabels = deserializeLabels(values.newLabels); - const newLabels = deserializedLabels.map(label => new (window as any).cvat.classes.Label(label)); - task.labels = newLabels; - this.props.dispatch(updateTaskAsync(task)).then( - (data: any) => { - resolve(data); - }, - (error: any) => { - reject(error); - Modal.error({ title: error.message, centered: true, okType: 'danger' }); - }, - ); - } else { - reject(error); - } - }); - }); - }, - onCancel: () => { - return; - }, - }); - } - - private onDeleteTask = (task: any) => { - Modal.confirm({ - title: 'Do you want to delete this task?', - okText: 'Yes', - okType: 'danger', - centered: true, - autoFocusButton: 'cancel', - onOk: () => { - return new Promise((resolve, reject) => { - this.props.dispatch(deleteTaskAsync(task, this.props.history)).then( - (deleted: any) => { - resolve(deleted); - }, - (error: any) => { - reject(error); - }, - ); - }); - }, - cancelText: 'No', - onCancel: () => { - return; - }, - }); - } - - private onDumpAnnotation = (task: any, event: any, component: TasksContent) => { - const dumper = component.state.dumpers.find((dumper: any) => dumper.name === event.key); - - component.setState({ activeTaskId: task.id }); - this.props.dispatch(dumpAnnotationAsync(task, dumper)).then( - (data: any) => { - const a = document.createElement('a'); - a.href = component.props.downloadLink; - document.body.appendChild(a); - a.click(); - a.remove(); - }, - (error: any) => { - Modal.error({ title: error.message, centered: true, okType: 'danger' }); - }, - ); - } - - private onUploadAnnotation = (task: any, file: File) => { - const loader = this.state.loaders.find((loader: any) => loader.name === this.state.selectedLoader); - - this.setState({ activeTaskId: task.id }); - this.props.dispatch(uploadAnnotationAsync(task, file, loader)).then( - (data: any) => { - - }, - (error: any) => { - Modal.error({ title: error.message, centered: true, okType: 'danger' }); - }, - ); - - return true; - } - - private onUploaderChange = (info: UploadChangeParam) => { - if (info.file.status === 'uploading') { - this.onUploadAnnotation(this.state.loaderTask, (info.file.originFileObj as File)); - } - } - - private simulateRequest = ({ file, onSuccess }: any) => { - setTimeout(() => { - onSuccess(file); - }, 0); - } -} - -const mapStateToProps = (state: any) => { - return { ...state.tasks, ...state.server, ...state.annotations }; -}; - -export default withRouter(connect(mapStateToProps)(TasksContent) as any); diff --git a/cvat-ui/src/components/tasks-page/tasks-footer/tasks-footer.scss b/cvat-ui/src/components/tasks-page/tasks-footer/tasks-footer.scss deleted file mode 100644 index ff33fe58cbb..00000000000 --- a/cvat-ui/src/components/tasks-page/tasks-footer/tasks-footer.scss +++ /dev/null @@ -1,9 +0,0 @@ -.tasks-footer { - display: flex; - align-items: center; - justify-content: center; - min-width: 1024px; - padding: 0 50px; - height: 64px; - background: #001529; -} diff --git a/cvat-ui/src/components/tasks-page/tasks-footer/tasks-footer.test.tsx b/cvat-ui/src/components/tasks-page/tasks-footer/tasks-footer.test.tsx deleted file mode 100644 index c62ff433d90..00000000000 --- a/cvat-ui/src/components/tasks-page/tasks-footer/tasks-footer.test.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; - -import TasksFooter from './tasks-footer'; - - -it('renders without crashing', () => { - const div = document.createElement('div'); - ReactDOM.render(, div); - ReactDOM.unmountComponentAtNode(div); -}); diff --git a/cvat-ui/src/components/tasks-page/tasks-footer/tasks-footer.tsx b/cvat-ui/src/components/tasks-page/tasks-footer/tasks-footer.tsx deleted file mode 100644 index 464dcc3cfdc..00000000000 --- a/cvat-ui/src/components/tasks-page/tasks-footer/tasks-footer.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React, { PureComponent } from 'react'; - -import * as queryString from 'query-string'; - -import { withRouter } from 'react-router-dom' - -import { connect } from 'react-redux'; - -import { Layout, Pagination, Row, Col } from 'antd'; - -import './tasks-footer.scss'; - - -const { Footer } = Layout; - -class TasksFooter extends PureComponent { - render() { - return( -
- -
- - - - - - ); - } - - private onPageChange = (page: number, pageSize?: number) => { - const params = { search: this.props.searchQuery, page } - - this.props.history.push({ search: queryString.stringify(params) }); - } -} - -const mapStateToProps = (state: any) => { - return { ...state.tasks, ...state.tasksFilter }; -}; - -export default withRouter(connect(mapStateToProps)(TasksFooter) as any); diff --git a/cvat-ui/src/components/tasks-page/tasks-header/tasks-header.scss b/cvat-ui/src/components/tasks-page/tasks-header/tasks-header.scss deleted file mode 100644 index b09ba38ef00..00000000000 --- a/cvat-ui/src/components/tasks-page/tasks-header/tasks-header.scss +++ /dev/null @@ -1,41 +0,0 @@ -.tasks-header { - display: flex; - align-items: center; - width: 1024px; - height: 90px; - line-height: initial; - margin: 0 auto; - padding: 0; - background-color: #f0f2f5; - - &__logo { - font-size: 20px; - - .logo { - margin: 0; - color: black; - } - } - - &__search { - text-align: center; - - .search { - max-width: 300px; - } - } - - &__actions { - text-align: right; - - .action:not(:nth-child(1)) { - margin-left: 8px; - } - - .action { - width: 180px; - font-size: 16px; - line-height: 19px; - } - } -} diff --git a/cvat-ui/src/components/tasks-page/tasks-header/tasks-header.test.tsx b/cvat-ui/src/components/tasks-page/tasks-header/tasks-header.test.tsx deleted file mode 100644 index eb1cfc73ea8..00000000000 --- a/cvat-ui/src/components/tasks-page/tasks-header/tasks-header.test.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; - -import TasksHeader from './tasks-header'; - - -it('renders without crashing', () => { - const div = document.createElement('div'); - ReactDOM.render(, div); - ReactDOM.unmountComponentAtNode(div); -}); diff --git a/cvat-ui/src/components/tasks-page/tasks-header/tasks-header.tsx b/cvat-ui/src/components/tasks-page/tasks-header/tasks-header.tsx deleted file mode 100644 index 2ac44da82d6..00000000000 --- a/cvat-ui/src/components/tasks-page/tasks-header/tasks-header.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import React, { Component } from 'react'; - -import { withRouter } from 'react-router-dom'; - -import { connect } from 'react-redux'; -import { createTaskAsync } from '../../../actions/tasks.actions'; - -import { Modal, Layout, Col, Button, Input } from 'antd'; -import Title from 'antd/lib/typography/Title'; - -import TaskCreateForm from '../../modals/task-create/task-create'; - -import { taskDTO } from '../../../utils/tasks-dto'; - -import './tasks-header.scss'; - - -const { Header } = Layout; -const { Search } = Input; - -class TasksHeader extends Component { - createFormRef: any; - - constructor(props: any) { - super(props); - - this.state = { searchQuery: this.props.searchQuery }; - } - - componentDidUpdate(prevProps: any) { - if (this.props.searchQuery !== prevProps.searchQuery) { - this.setState({ searchQuery: this.props.searchQuery }); - } - } - - render() { - return( -
-
- Tasks - - - this.onSearch(query) }> - - - - - - - ); - } - - private setTaskCreateFormRef = (ref: any) => { - this.createFormRef = ref; - } - - private onCreateTask = () => { - Modal.confirm({ - title: 'Create new task', - content: , - centered: true, - className: 'crud-modal', - okText: 'Create', - okType: 'primary', - onOk: (closeFunction: Function) => { - return new Promise((resolve, reject) => { - this.createFormRef.validateFields((error: any, values: any) => { - if (!error) { - const newTask = taskDTO(values); - - this.props.dispatch(createTaskAsync(newTask)).then( - (data: any) => { - resolve(data); - closeFunction(); - }, - (error: any) => { - reject(error); - Modal.error({ title: error.message, centered: true, okType: 'danger' }) - } - ); - } else { - reject(error); - } - }); - }); - }, - onCancel: () => { - return; - }, - }); - } - - private onValueChange = (event: any) => { - this.setState({ searchQuery: event.target.value }); - } - - private onSearch = (query: string) => { - if (query !== this.props.searchQuery) { - query ? this.props.history.push(`?search=${query}`) : this.props.history.push(this.props.location.pathname); - } - } -} - -const mapStateToProps = (state: any) => { - return { ...state.tasks, ...state.tasksFilter }; -}; - -export default withRouter(connect(mapStateToProps)(TasksHeader) as any); diff --git a/cvat-ui/src/components/tasks-page/tasks-page.scss b/cvat-ui/src/components/tasks-page/tasks-page.scss deleted file mode 100644 index 07b1cd83500..00000000000 --- a/cvat-ui/src/components/tasks-page/tasks-page.scss +++ /dev/null @@ -1,3 +0,0 @@ -.layout { - -} diff --git a/cvat-ui/src/components/tasks-page/tasks-page.test.tsx b/cvat-ui/src/components/tasks-page/tasks-page.test.tsx deleted file mode 100644 index bd48512fb6a..00000000000 --- a/cvat-ui/src/components/tasks-page/tasks-page.test.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; - -import TasksPage from './tasks-page'; - - -it('renders without crashing', () => { - const div = document.createElement('div'); - ReactDOM.render(, div); - ReactDOM.unmountComponentAtNode(div); -}); diff --git a/cvat-ui/src/components/tasks-page/tasks-page.tsx b/cvat-ui/src/components/tasks-page/tasks-page.tsx index 7bf31885c81..d4af0864fcd 100644 --- a/cvat-ui/src/components/tasks-page/tasks-page.tsx +++ b/cvat-ui/src/components/tasks-page/tasks-page.tsx @@ -1,59 +1,237 @@ -import React, { PureComponent } from 'react'; -import { Location, Action } from 'history'; +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT -import * as queryString from 'query-string'; +import './styles.scss'; +import React from 'react'; +import { RouteComponentProps } from 'react-router'; +import { withRouter } from 'react-router-dom'; -import setQueryObject from '../../utils/tasks-filter' +import { + Spin, + Button, + message, +} from 'antd'; -import { connect } from 'react-redux'; -import { getTasksAsync } from '../../actions/tasks.actions'; -import { filterTasks } from '../../actions/tasks-filter.actions'; +import Text from 'antd/lib/typography/Text'; -import { Layout, Spin } from 'antd'; +import { + TasksQuery, +} from 'reducers/interfaces'; -import TasksHeader from './tasks-header/tasks-header'; -import TasksContent from './tasks-content/tasks-content'; -import TasksFooter from './tasks-footer/tasks-footer'; +import FeedbackComponent from 'components/feedback/feedback'; +import TaskListContainer from 'containers/tasks-page/tasks-list'; +import TopBar from './top-bar'; +import EmptyListComponent from './empty-list'; -import './tasks-page.scss'; +interface TasksPageProps { + tasksFetching: boolean; + gettingQuery: TasksQuery; + numberOfTasks: number; + numberOfVisibleTasks: number; + numberOfHiddenTasks: number; + onGetTasks: (gettingQuery: TasksQuery) => void; + hideEmptyTasks: (hideEmpty: boolean) => void; +} -class TasksPage extends PureComponent { - componentDidMount() { - this.loadTasks(this.props.location.search); +function getSearchField(gettingQuery: TasksQuery): string { + let searchString = ''; + for (const field of Object.keys(gettingQuery)) { + if (gettingQuery[field] !== null && field !== 'page') { + if (field === 'search') { + return (gettingQuery[field] as any) as string; + } - this.props.history.listen( - (location: Location, action: Action) => { - if (location.pathname.includes('tasks')) { - this.loadTasks(location.search); + // not constant condition + // eslint-disable-next-line + if (typeof (gettingQuery[field] === 'number')) { + searchString += `${field}:${gettingQuery[field]} AND `; + } else { + searchString += `${field}:"${gettingQuery[field]}" AND `; + } } - } - ); - } + } - render() { - return ( - - - - - - - - ); - } + return searchString.slice(0, -5); +} - private loadTasks = (params: any) => { - const query = queryString.parse(params); - const queryObject = setQueryObject(query); +function updateQuery(previousQuery: TasksQuery, searchString: string): TasksQuery { + const params = new URLSearchParams(searchString); + const query = { ...previousQuery }; + for (const field of Object.keys(query)) { + if (params.has(field)) { + const value = params.get(field); + if (value) { + if (field === 'id' || field === 'page') { + if (Number.isInteger(+value)) { + query[field] = +value; + } + } else { + query[field] = value; + } + } + } else if (field === 'page') { + query[field] = 1; + } else { + query[field] = null; + } + } - this.props.dispatch(filterTasks(queryObject)); - this.props.dispatch(getTasksAsync(queryObject)); - } + return query; } -const mapStateToProps = (state: any) => { - return { ...state.authContext, ...state.tasks, ...state.tasksFilter }; -}; +class TasksPageComponent extends React.PureComponent { + public componentDidMount(): void { + const { + gettingQuery, + location, + onGetTasks, + } = this.props; + + const query = updateQuery(gettingQuery, location.search); + onGetTasks(query); + } + + public componentDidUpdate(prevProps: TasksPageProps & RouteComponentProps): void { + const { + location, + gettingQuery, + onGetTasks, + numberOfHiddenTasks, + hideEmptyTasks, + } = this.props; + + if (prevProps.location.search !== location.search) { + // get new tasks if any query changes + const query = updateQuery(gettingQuery, location.search); + message.destroy(); + onGetTasks(query); + return; + } + + if (numberOfHiddenTasks) { + message.destroy(); + message.info( + <> + + Some tasks have not been showed because they do not have any data. + + + , 7, + ); + } + } + + private handleSearch = (value: string): void => { + const { + gettingQuery, + } = this.props; + + const query = { ...gettingQuery }; + const search = value.replace(/\s+/g, ' ').replace(/\s*:+\s*/g, ':').trim(); + + const fields = ['name', 'mode', 'owner', 'assignee', 'status', 'id']; + for (const field of fields) { + query[field] = null; + } + query.search = null; + + let specificRequest = false; + for (const param of search.split(/[\s]+and[\s]+|[\s]+AND[\s]+/)) { + if (param.includes(':')) { + const [field, fieldValue] = param.split(':'); + if (fields.includes(field) && !!fieldValue) { + specificRequest = true; + if (field === 'id') { + if (Number.isInteger(+fieldValue)) { + query[field] = +fieldValue; + } + } else { + query[field] = fieldValue; + } + } + } + } + + query.page = 1; + if (!specificRequest && value) { // only id + query.search = value; + } + + this.updateURL(query); + }; + + private handlePagination = (page: number): void => { + const { + gettingQuery, + } = this.props; + + // modify query object + const query = { ...gettingQuery }; + query.page = page; + + // update url according to new query object + this.updateURL(query); + }; + + private updateURL(gettingQuery: TasksQuery): void { + const { history } = this.props; + let queryString = '?'; + for (const field of Object.keys(gettingQuery)) { + if (gettingQuery[field] !== null) { + queryString += `${field}=${gettingQuery[field]}&`; + } + } + + const oldQueryString = history.location.search; + if (oldQueryString !== queryString) { + history.push({ + search: queryString.slice(0, -1), + }); + + // force update if any changes + this.forceUpdate(); + } + } + + public render(): JSX.Element { + const { + tasksFetching, + gettingQuery, + numberOfVisibleTasks, + } = this.props; + + if (tasksFetching) { + return ( + + ); + } + + return ( +
+ + {numberOfVisibleTasks + ? ( + + ) : } + +
+ ); + } +} -export default connect(mapStateToProps)(TasksPage); +export default withRouter(TasksPageComponent); diff --git a/cvat-ui/src/components/tasks-page/top-bar.tsx b/cvat-ui/src/components/tasks-page/top-bar.tsx new file mode 100644 index 00000000000..d4cdbc6e02c --- /dev/null +++ b/cvat-ui/src/components/tasks-page/top-bar.tsx @@ -0,0 +1,69 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import { RouteComponentProps } from 'react-router'; +import { withRouter } from 'react-router-dom'; + +import { + Col, + Row, + Button, + Input, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; + +interface VisibleTopBarProps { + onSearch: (value: string) => void; + searchValue: string; +} + +function TopBarComponent(props: VisibleTopBarProps & RouteComponentProps): JSX.Element { + const { + searchValue, + history, + onSearch, + } = props; + + return ( + <> + +
+ Default project + + + + + Tasks + + + + + + + + ); +} + +export default withRouter(TopBarComponent); diff --git a/cvat-ui/src/containers/actions-menu/actions-menu.tsx b/cvat-ui/src/containers/actions-menu/actions-menu.tsx new file mode 100644 index 00000000000..77b1c6cd52b --- /dev/null +++ b/cvat-ui/src/containers/actions-menu/actions-menu.tsx @@ -0,0 +1,195 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import { connect } from 'react-redux'; + +import ActionsMenuComponent, { Actions } from 'components/actions-menu/actions-menu'; +import { + CombinedState, +} from 'reducers/interfaces'; + +import { modelsActions } from 'actions/models-actions'; +import { + dumpAnnotationsAsync, + loadAnnotationsAsync, + exportDatasetAsync, + deleteTaskAsync, +} from 'actions/tasks-actions'; +import { ClickParam } from 'antd/lib/menu'; + +interface OwnProps { + taskInstance: any; +} + +interface StateToProps { + annotationFormats: any[]; + exporters: any[]; + loadActivity: string | null; + dumpActivities: string[] | null; + exportActivities: string[] | null; + installedTFAnnotation: boolean; + installedTFSegmentation: boolean; + installedAutoAnnotation: boolean; + inferenceIsActive: boolean; +} + +interface DispatchToProps { + loadAnnotations: (taskInstance: any, loader: any, file: File) => void; + dumpAnnotations: (taskInstance: any, dumper: any) => void; + exportDataset: (taskInstance: any, exporter: any) => void; + deleteTask: (taskInstance: any) => void; + openRunModelWindow: (taskInstance: any) => void; +} + +function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { + const { + taskInstance: { + id: tid, + }, + } = own; + + const { + formats: { + annotationFormats, + datasetFormats, + }, + plugins: { + list: { + TF_ANNOTATION: installedTFAnnotation, + TF_SEGMENTATION: installedTFSegmentation, + AUTO_ANNOTATION: installedAutoAnnotation, + }, + }, + tasks: { + activities: { + dumps, + loads, + exports: activeExports, + }, + }, + } = state; + + return { + installedTFAnnotation, + installedTFSegmentation, + installedAutoAnnotation, + dumpActivities: tid in dumps ? dumps[tid] : null, + exportActivities: tid in activeExports ? activeExports[tid] : null, + loadActivity: tid in loads ? loads[tid] : null, + annotationFormats, + exporters: datasetFormats, + inferenceIsActive: tid in state.models.inferences, + }; +} + +function mapDispatchToProps(dispatch: any): DispatchToProps { + return { + loadAnnotations: (taskInstance: any, loader: any, file: File): void => { + dispatch(loadAnnotationsAsync(taskInstance, loader, file)); + }, + dumpAnnotations: (taskInstance: any, dumper: any): void => { + dispatch(dumpAnnotationsAsync(taskInstance, dumper)); + }, + exportDataset: (taskInstance: any, exporter: any): void => { + dispatch(exportDatasetAsync(taskInstance, exporter)); + }, + deleteTask: (taskInstance: any): void => { + dispatch(deleteTaskAsync(taskInstance)); + }, + openRunModelWindow: (taskInstance: any): void => { + dispatch(modelsActions.showRunModelDialog(taskInstance)); + }, + }; +} + +function ActionsMenuContainer(props: OwnProps & StateToProps & DispatchToProps): JSX.Element { + const { + taskInstance, + annotationFormats, + exporters, + loadActivity, + dumpActivities, + exportActivities, + inferenceIsActive, + installedAutoAnnotation, + installedTFAnnotation, + installedTFSegmentation, + + loadAnnotations, + dumpAnnotations, + exportDataset, + deleteTask, + openRunModelWindow, + } = props; + + + const loaders = annotationFormats + .map((format: any): any[] => format.loaders).flat(); + + const dumpers = annotationFormats + .map((format: any): any[] => format.dumpers).flat(); + + function onClickMenu(params: ClickParam, file?: File): void { + if (params.keyPath.length > 1) { + const [additionalKey, action] = params.keyPath; + if (action === Actions.DUMP_TASK_ANNO) { + const format = additionalKey; + const [dumper] = dumpers + .filter((_dumper: any): boolean => _dumper.name === format); + if (dumper) { + dumpAnnotations(taskInstance, dumper); + } + } else if (action === Actions.LOAD_TASK_ANNO) { + const [format] = additionalKey.split('::'); + const [loader] = loaders + .filter((_loader: any): boolean => _loader.name === format); + if (loader && file) { + loadAnnotations(taskInstance, loader, file); + } + } else if (action === Actions.EXPORT_TASK_DATASET) { + const format = additionalKey; + const [exporter] = exporters + .filter((_exporter: any): boolean => _exporter.name === format); + if (exporter) { + exportDataset(taskInstance, exporter); + } + } + } else { + const [action] = params.keyPath; + if (action === Actions.DELETE_TASK) { + deleteTask(taskInstance); + } else if (action === Actions.OPEN_BUG_TRACKER) { + // eslint-disable-next-line + window.open(`${taskInstance.bugTracker}`, '_blank'); + } else if (action === Actions.RUN_AUTO_ANNOTATION) { + openRunModelWindow(taskInstance); + } + } + } + + return ( + `${loader.name}::${loader.format}`)} + dumpers={dumpers.map((dumper: any): string => dumper.name)} + exporters={exporters.map((exporter: any): string => exporter.name)} + loadActivity={loadActivity} + dumpActivities={dumpActivities} + exportActivities={exportActivities} + inferenceIsActive={inferenceIsActive} + installedAutoAnnotation={installedAutoAnnotation} + installedTFAnnotation={installedTFAnnotation} + installedTFSegmentation={installedTFSegmentation} + onClickMenu={onClickMenu} + /> + ); +} + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(ActionsMenuContainer); diff --git a/cvat-ui/src/containers/annotation-page/annotation-page.tsx b/cvat-ui/src/containers/annotation-page/annotation-page.tsx new file mode 100644 index 00000000000..c66a93049a2 --- /dev/null +++ b/cvat-ui/src/containers/annotation-page/annotation-page.tsx @@ -0,0 +1,88 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import { connect } from 'react-redux'; +import { withRouter } from 'react-router-dom'; +import { RouteComponentProps } from 'react-router'; + +import AnnotationPageComponent from 'components/annotation-page/annotation-page'; +import { getJobAsync } from 'actions/annotation-actions'; + +import { + CombinedState, +} from 'reducers/interfaces'; + +type OwnProps = RouteComponentProps<{ + tid: string; + jid: string; +}>; + +interface StateToProps { + job: any | null | undefined; + fetching: boolean; +} + +interface DispatchToProps { + getJob(): void; +} + +function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { + const { params } = own.match; + const jobID = +params.jid; + const { + annotation: { + job: { + instance: job, + fetching, + }, + }, + } = state; + + return { + job: !job || jobID === job.id ? job : null, + fetching, + }; +} + +function mapDispatchToProps(dispatch: any, own: OwnProps): DispatchToProps { + const { params } = own.match; + const taskID = +params.tid; + const jobID = +params.jid; + const searchParams = new URLSearchParams(window.location.search); + const initialFilters: string[] = []; + let initialFrame = 0; + + + if (searchParams.has('frame')) { + const searchFrame = +(searchParams.get('frame') as string); + if (!Number.isNaN(searchFrame)) { + initialFrame = searchFrame; + } + } + + if (searchParams.has('object')) { + const searchObject = +(searchParams.get('object') as string); + if (!Number.isNaN(searchObject)) { + initialFilters.push(`serverID==${searchObject}`); + } + } + + if (searchParams.has('frame') || searchParams.has('object')) { + own.history.replace(own.history.location.state); + } + + return { + getJob(): void { + dispatch(getJobAsync(taskID, jobID, initialFrame, initialFilters)); + }, + }; +} + + +export default withRouter( + connect( + mapStateToProps, + mapDispatchToProps, + )(AnnotationPageComponent), +); diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-context-menu.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-context-menu.tsx new file mode 100644 index 00000000000..fd2ff980568 --- /dev/null +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-context-menu.tsx @@ -0,0 +1,193 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { connect } from 'react-redux'; +import { CombinedState } from 'reducers/interfaces'; + +import CanvasContextMenuComponent from 'components/annotation-page/standard-workspace/canvas-context-menu'; + +interface StateToProps { + activatedStateID: number | null; + visible: boolean; + top: number; + left: number; + collapsed: boolean | undefined; +} + +function mapStateToProps(state: CombinedState): StateToProps { + const { + annotation: { + annotations: { + activatedStateID, + collapsed, + }, + canvas: { + contextMenu: { + visible, + top, + left, + }, + }, + }, + } = state; + + return { + activatedStateID, + collapsed: activatedStateID !== null ? collapsed[activatedStateID] : undefined, + visible, + left, + top, + }; +} + +type Props = StateToProps; + +interface State { + latestLeft: number; + latestTop: number; + left: number; + top: number; +} + +class CanvasContextMenuContainer extends React.PureComponent { + private initialized: HTMLDivElement | null; + private dragging: boolean; + private dragInitPosX: number; + private dragInitPosY: number; + public constructor(props: Props) { + super(props); + + this.initialized = null; + this.dragging = false; + this.dragInitPosX = 0; + this.dragInitPosY = 0; + this.state = { + latestLeft: 0, + latestTop: 0, + left: 0, + top: 0, + }; + } + + static getDerivedStateFromProps(props: Props, state: State): State | null { + if (props.left === state.latestLeft + && props.top === state.latestTop) { + return null; + } + + return { + ...state, + latestLeft: props.left, + latestTop: props.top, + top: props.top, + left: props.left, + }; + } + + public componentDidMount(): void { + this.updatePositionIfOutOfScreen(); + window.addEventListener('mousemove', this.moveContextMenu); + } + + public componentDidUpdate(prevProps: Props): void { + const { collapsed } = this.props; + + const [element] = window.document.getElementsByClassName('cvat-canvas-context-menu'); + if (collapsed !== prevProps.collapsed && element) { + element.addEventListener('transitionend', () => { + this.updatePositionIfOutOfScreen(); + }, { once: true }); + } else if (element) { + this.updatePositionIfOutOfScreen(); + } + + if (element && (!this.initialized || this.initialized !== element)) { + this.initialized = element as HTMLDivElement; + + this.initialized.addEventListener('mousedown', (e: MouseEvent): any => { + this.dragging = true; + this.dragInitPosX = e.clientX; + this.dragInitPosY = e.clientY; + }); + + this.initialized.addEventListener('mouseup', () => { + this.dragging = false; + }); + } + } + + public componentWillUnmount(): void { + window.removeEventListener('mousemove', this.moveContextMenu); + } + + private moveContextMenu = (e: MouseEvent): void => { + if (this.dragging) { + this.setState((state) => { + const value = { + left: state.left + e.clientX - this.dragInitPosX, + top: state.top + e.clientY - this.dragInitPosY, + }; + + this.dragInitPosX = e.clientX; + this.dragInitPosY = e.clientY; + + return value; + }); + + e.preventDefault(); + } + }; + + private updatePositionIfOutOfScreen(): void { + const { + top, + left, + } = this.state; + + const { + innerWidth, + innerHeight, + } = window; + + const [element] = window.document.getElementsByClassName('cvat-canvas-context-menu'); + if (element) { + const height = element.clientHeight; + const width = element.clientWidth; + + if (top + height > innerHeight || left + width > innerWidth) { + this.setState({ + top: top - Math.max(top + height - innerHeight, 0), + left: left - Math.max(left + width - innerWidth, 0), + }); + } + } + } + + public render(): JSX.Element { + const { + left, + top, + } = this.state; + + const { + visible, + activatedStateID, + } = this.props; + + return ( + + ); + } +} + +export default connect( + mapStateToProps, +)(CanvasContextMenuContainer); diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx new file mode 100644 index 00000000000..0625bece913 --- /dev/null +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -0,0 +1,272 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import { connect } from 'react-redux'; + +import CanvasWrapperComponent from 'components/annotation-page/standard-workspace/canvas-wrapper'; +import { + confirmCanvasReady, + dragCanvas, + zoomCanvas, + resetCanvas, + shapeDrawn, + mergeObjects, + groupObjects, + splitTrack, + editShape, + updateAnnotationsAsync, + createAnnotationsAsync, + mergeAnnotationsAsync, + groupAnnotationsAsync, + splitAnnotationsAsync, + activateObject, + selectObjects, + updateCanvasContextMenu, + addZLayer, + switchZLayer, +} from 'actions/annotation-actions'; +import { + switchGrid, + changeGridColor, + changeGridOpacity, + changeBrightnessLevel, + changeContrastLevel, + changeSaturationLevel, +} from 'actions/settings-actions'; +import { + ColorBy, + GridColor, + ObjectType, + CombinedState, +} from 'reducers/interfaces'; + +import { Canvas } from 'cvat-canvas'; + +interface StateToProps { + sidebarCollapsed: boolean; + canvasInstance: Canvas; + jobInstance: any; + activatedStateID: number | null; + selectedStatesID: number[]; + annotations: any[]; + frameData: any; + frameAngle: number; + frame: number; + opacity: number; + colorBy: ColorBy; + selectedOpacity: number; + blackBorders: boolean; + grid: boolean; + gridSize: number; + gridColor: GridColor; + gridOpacity: number; + activeLabelID: number; + activeObjectType: ObjectType; + brightnessLevel: number; + contrastLevel: number; + saturationLevel: number; + resetZoom: boolean; + minZLayer: number; + maxZLayer: number; + curZLayer: number; +} + +interface DispatchToProps { + onSetupCanvas(): void; + onDragCanvas: (enabled: boolean) => void; + onZoomCanvas: (enabled: boolean) => void; + onResetCanvas: () => void; + onShapeDrawn: () => void; + onMergeObjects: (enabled: boolean) => void; + onGroupObjects: (enabled: boolean) => void; + onSplitTrack: (enabled: boolean) => void; + onEditShape: (enabled: boolean) => void; + onUpdateAnnotations(sessionInstance: any, frame: number, states: any[]): void; + onCreateAnnotations(sessionInstance: any, frame: number, states: any[]): void; + onMergeAnnotations(sessionInstance: any, frame: number, states: any[]): void; + onGroupAnnotations(sessionInstance: any, frame: number, states: any[]): void; + onSplitAnnotations(sessionInstance: any, frame: number, state: any): void; + onActivateObject: (activatedStateID: number | null) => void; + onSelectObjects: (selectedStatesID: number[]) => void; + onUpdateContextMenu(visible: boolean, left: number, top: number): void; + onAddZLayer(): void; + onSwitchZLayer(cur: number): void; + onChangeBrightnessLevel(level: number): void; + onChangeContrastLevel(level: number): void; + onChangeSaturationLevel(level: number): void; + onChangeGridOpacity(opacity: number): void; + onChangeGridColor(color: GridColor): void; + onSwitchGrid(enabled: boolean): void; +} + +function mapStateToProps(state: CombinedState): StateToProps { + const { + annotation: { + canvas: { + instance: canvasInstance, + }, + drawing: { + activeLabelID, + activeObjectType, + }, + job: { + instance: jobInstance, + }, + player: { + frame: { + data: frameData, + number: frame, + }, + frameAngles, + }, + annotations: { + states: annotations, + activatedStateID, + selectedStatesID, + zLayer: { + cur: curZLayer, + min: minZLayer, + max: maxZLayer, + }, + }, + sidebarCollapsed, + }, + settings: { + player: { + grid, + gridSize, + gridColor, + gridOpacity, + brightnessLevel, + contrastLevel, + saturationLevel, + resetZoom, + }, + shapes: { + opacity, + colorBy, + selectedOpacity, + blackBorders, + }, + }, + } = state; + + return { + sidebarCollapsed, + canvasInstance, + jobInstance, + frameData, + frameAngle: frameAngles[frame - jobInstance.startFrame], + frame, + activatedStateID, + selectedStatesID, + annotations, + opacity, + colorBy, + selectedOpacity, + blackBorders, + grid, + gridSize, + gridColor, + gridOpacity, + activeLabelID, + activeObjectType, + brightnessLevel, + contrastLevel, + saturationLevel, + resetZoom, + curZLayer, + minZLayer, + maxZLayer, + }; +} + +function mapDispatchToProps(dispatch: any): DispatchToProps { + return { + onSetupCanvas(): void { + dispatch(confirmCanvasReady()); + }, + onDragCanvas(enabled: boolean): void { + dispatch(dragCanvas(enabled)); + }, + onZoomCanvas(enabled: boolean): void { + dispatch(zoomCanvas(enabled)); + }, + onResetCanvas(): void { + dispatch(resetCanvas()); + }, + onShapeDrawn(): void { + dispatch(shapeDrawn()); + }, + onMergeObjects(enabled: boolean): void { + dispatch(mergeObjects(enabled)); + }, + onGroupObjects(enabled: boolean): void { + dispatch(groupObjects(enabled)); + }, + onSplitTrack(enabled: boolean): void { + dispatch(splitTrack(enabled)); + }, + onEditShape(enabled: boolean): void { + dispatch(editShape(enabled)); + }, + onUpdateAnnotations(sessionInstance: any, frame: number, states: any[]): void { + dispatch(updateAnnotationsAsync(sessionInstance, frame, states)); + }, + onCreateAnnotations(sessionInstance: any, frame: number, states: any[]): void { + dispatch(createAnnotationsAsync(sessionInstance, frame, states)); + }, + onMergeAnnotations(sessionInstance: any, frame: number, states: any[]): void { + dispatch(mergeAnnotationsAsync(sessionInstance, frame, states)); + }, + onGroupAnnotations(sessionInstance: any, frame: number, states: any[]): void { + dispatch(groupAnnotationsAsync(sessionInstance, frame, states)); + }, + onSplitAnnotations(sessionInstance: any, frame: number, state: any): void { + dispatch(splitAnnotationsAsync(sessionInstance, frame, state)); + }, + onActivateObject(activatedStateID: number | null): void { + if (activatedStateID === null) { + dispatch(updateCanvasContextMenu(false, 0, 0)); + } + + dispatch(activateObject(activatedStateID)); + }, + onSelectObjects(selectedStatesID: number[]): void { + dispatch(selectObjects(selectedStatesID)); + }, + onUpdateContextMenu(visible: boolean, left: number, top: number): void { + dispatch(updateCanvasContextMenu(visible, left, top)); + }, + onAddZLayer(): void { + dispatch(addZLayer()); + }, + onSwitchZLayer(cur: number): void { + dispatch(switchZLayer(cur)); + }, + onChangeBrightnessLevel(level: number): void { + dispatch(changeBrightnessLevel(level)); + }, + onChangeContrastLevel(level: number): void { + dispatch(changeContrastLevel(level)); + }, + onChangeSaturationLevel(level: number): void { + dispatch(changeSaturationLevel(level)); + }, + onChangeGridOpacity(opacity: number): void { + dispatch(changeGridOpacity(opacity)); + }, + onChangeGridColor(color: GridColor): void { + dispatch(changeGridColor(color)); + }, + onSwitchGrid(enabled: boolean): void { + dispatch(switchGrid(enabled)); + }, + }; +} + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(CanvasWrapperComponent); diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx new file mode 100644 index 00000000000..1f0b1f31515 --- /dev/null +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx @@ -0,0 +1,92 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import { connect } from 'react-redux'; + +import { Canvas } from 'cvat-canvas'; + +import { + mergeObjects, + groupObjects, + splitTrack, + rotateCurrentFrame, + repeatDrawShapeAsync, + pasteShapeAsync, + resetAnnotationsGroup, +} from 'actions/annotation-actions'; +import ControlsSideBarComponent from 'components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar'; +import { + ActiveControl, + CombinedState, + Rotation, +} from 'reducers/interfaces'; + +interface StateToProps { + canvasInstance: Canvas; + rotateAll: boolean; + activeControl: ActiveControl; +} + +interface DispatchToProps { + mergeObjects(enabled: boolean): void; + groupObjects(enabled: boolean): void; + splitTrack(enabled: boolean): void; + rotateFrame(angle: Rotation): void; + resetGroup(): void; + repeatDrawShape(): void; + pasteShape(): void; +} + +function mapStateToProps(state: CombinedState): StateToProps { + const { + annotation: { + canvas: { + instance: canvasInstance, + activeControl, + }, + }, + settings: { + player: { + rotateAll, + }, + }, + } = state; + + return { + rotateAll, + canvasInstance, + activeControl, + }; +} + +function dispatchToProps(dispatch: any): DispatchToProps { + return { + mergeObjects(enabled: boolean): void { + dispatch(mergeObjects(enabled)); + }, + groupObjects(enabled: boolean): void { + dispatch(groupObjects(enabled)); + }, + splitTrack(enabled: boolean): void { + dispatch(splitTrack(enabled)); + }, + rotateFrame(rotation: Rotation): void { + dispatch(rotateCurrentFrame(rotation)); + }, + repeatDrawShape(): void { + dispatch(repeatDrawShapeAsync()); + }, + pasteShape(): void { + dispatch(pasteShapeAsync()); + }, + resetGroup(): void { + dispatch(resetAnnotationsGroup()); + }, + }; +} + +export default connect( + mapStateToProps, + dispatchToProps, +)(ControlsSideBarComponent); diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx new file mode 100644 index 00000000000..8d418e68b71 --- /dev/null +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx @@ -0,0 +1,198 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import { connect } from 'react-redux'; +import { RadioChangeEvent } from 'antd/lib/radio'; + +import { + CombinedState, + ShapeType, + ObjectType, +} from 'reducers/interfaces'; + +import { + drawShape, +} from 'actions/annotation-actions'; +import { Canvas, RectDrawingMethod } from 'cvat-canvas'; +import DrawShapePopoverComponent from 'components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover'; + +interface OwnProps { + shapeType: ShapeType; +} + +interface DispatchToProps { + onDrawStart( + shapeType: ShapeType, + labelID: number, + objectType: ObjectType, + points?: number, + rectDrawingMethod?: RectDrawingMethod, + ): void; +} + +interface StateToProps { + canvasInstance: Canvas; + shapeType: ShapeType; + labels: any[]; +} + +function mapDispatchToProps(dispatch: any): DispatchToProps { + return { + onDrawStart( + shapeType: ShapeType, + labelID: number, + objectType: ObjectType, + points?: number, + rectDrawingMethod?: RectDrawingMethod, + ): void { + dispatch(drawShape(shapeType, labelID, objectType, points, rectDrawingMethod)); + }, + }; +} + +function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { + const { + annotation: { + canvas: { + instance: canvasInstance, + }, + job: { + labels, + }, + }, + } = state; + + return { + ...own, + canvasInstance, + labels, + }; +} + +type Props = StateToProps & DispatchToProps; + +interface State { + rectDrawingMethod?: RectDrawingMethod; + numberOfPoints?: number; + selectedLabelID: number; +} + +class DrawShapePopoverContainer extends React.PureComponent { + private minimumPoints = 3; + constructor(props: Props) { + super(props); + + const { shapeType } = props; + const defaultLabelID = props.labels[0].id; + const defaultRectDrawingMethod = RectDrawingMethod.CLASSIC; + this.state = { + selectedLabelID: defaultLabelID, + rectDrawingMethod: shapeType === ShapeType.RECTANGLE + ? defaultRectDrawingMethod : undefined, + }; + + if (shapeType === ShapeType.POLYGON) { + this.minimumPoints = 3; + } + if (shapeType === ShapeType.POLYLINE) { + this.minimumPoints = 2; + } + if (shapeType === ShapeType.POINTS) { + this.minimumPoints = 1; + } + } + + private onDraw(objectType: ObjectType): void { + const { + canvasInstance, + shapeType, + onDrawStart, + } = this.props; + + const { + rectDrawingMethod, + numberOfPoints, + selectedLabelID, + } = this.state; + + canvasInstance.cancel(); + canvasInstance.draw({ + enabled: true, + rectDrawingMethod, + numberOfPoints, + shapeType, + crosshair: shapeType === ShapeType.RECTANGLE, + }); + + onDrawStart(shapeType, selectedLabelID, + objectType, numberOfPoints, rectDrawingMethod); + } + + private onChangeRectDrawingMethod = (event: RadioChangeEvent): void => { + this.setState({ + rectDrawingMethod: event.target.value, + }); + }; + + private onDrawShape = (): void => { + this.onDraw(ObjectType.SHAPE); + }; + + private onDrawTrack = (): void => { + this.onDraw(ObjectType.TRACK); + }; + + private onChangePoints = (value: number | undefined): void => { + if (typeof (value) === 'undefined') { + this.setState({ + numberOfPoints: value, + }); + } else if (typeof (value) === 'number') { + this.setState({ + numberOfPoints: Math.max(value, this.minimumPoints), + }); + } + }; + + private onChangeLabel = (value: string): void => { + this.setState({ + selectedLabelID: +value, + }); + }; + + public render(): JSX.Element { + const { + rectDrawingMethod, + selectedLabelID, + numberOfPoints, + } = this.state; + + const { + labels, + shapeType, + } = this.props; + + return ( + + ); + } +} + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(DrawShapePopoverContainer); diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/label-item.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/label-item.tsx new file mode 100644 index 00000000000..2e70ed1bb03 --- /dev/null +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/label-item.tsx @@ -0,0 +1,226 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import { connect } from 'react-redux'; + +import { + changeLabelColorAsync, + updateAnnotationsAsync, +} from 'actions/annotation-actions'; + +import LabelItemComponent from 'components/annotation-page/standard-workspace/objects-side-bar/label-item'; +import { CombinedState } from 'reducers/interfaces'; + + +interface OwnProps { + labelID: number; +} + +interface StateToProps { + label: any; + labelName: string; + labelColor: string; + labelColors: string[]; + objectStates: any[]; + jobInstance: any; + frameNumber: any; +} + +interface DispatchToProps { + updateAnnotations(sessionInstance: any, frameNumber: number, states: any[]): void; + changeLabelColor(sessionInstance: any, frameNumber: number, label: any, color: string): void; +} + +function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { + const { + annotation: { + annotations: { + states: objectStates, + }, + job: { + labels, + instance: jobInstance, + }, + player: { + frame: { + number: frameNumber, + }, + }, + colors: labelColors, + }, + } = state; + + const [label] = labels + .filter((_label: any) => _label.id === own.labelID); + + return { + label, + labelColor: label.color, + labelName: label.name, + labelColors, + objectStates, + jobInstance, + frameNumber, + }; +} + +function mapDispatchToProps(dispatch: any): DispatchToProps { + return { + updateAnnotations(sessionInstance: any, frameNumber: number, states: any[]): void { + dispatch(updateAnnotationsAsync(sessionInstance, frameNumber, states)); + }, + changeLabelColor( + sessionInstance: any, + frameNumber: number, + label: any, + color: string, + ): void { + dispatch(changeLabelColorAsync(sessionInstance, frameNumber, label, color)); + }, + }; +} + +type Props = StateToProps & DispatchToProps & OwnProps; +interface State { + objectStates: any[]; + ownObjectStates: any[]; + visible: boolean; + statesHidden: boolean; + statesLocked: boolean; +} + +class LabelItemContainer extends React.PureComponent { + public constructor(props: Props) { + super(props); + this.state = { + objectStates: [], + ownObjectStates: [], + visible: true, + statesHidden: false, + statesLocked: false, + }; + } + + static getDerivedStateFromProps(props: Props, state: State): State | null { + if (props.objectStates === state.objectStates) { + return null; + } + + const ownObjectStates = props.objectStates + .filter((ownObjectState: any): boolean => ownObjectState.label.id === props.labelID); + const visible = !!ownObjectStates.length; + let statesHidden = true; + let statesLocked = true; + + ownObjectStates.forEach((objectState: any) => { + const { lock } = objectState; + if (!lock) { + statesHidden = statesHidden && objectState.hidden; + statesLocked = statesLocked && objectState.lock; + } + }); + + return { + ...state, + objectStates: props.objectStates, + ownObjectStates, + statesHidden, + statesLocked, + visible, + }; + } + + private hideStates = (): void => { + this.switchHidden(true); + }; + + private showStates = (): void => { + this.switchHidden(false); + }; + + private lockStates = (): void => { + this.switchLock(true); + }; + + private unlockStates = (): void => { + this.switchLock(false); + }; + + private changeColor = (color: string): void => { + const { + changeLabelColor, + label, + frameNumber, + jobInstance, + } = this.props; + + changeLabelColor(jobInstance, frameNumber, label, color); + }; + + private switchHidden(value: boolean): void { + const { + updateAnnotations, + jobInstance, + frameNumber, + } = this.props; + + const { ownObjectStates } = this.state; + for (const state of ownObjectStates) { + state.hidden = value; + } + + updateAnnotations(jobInstance, frameNumber, ownObjectStates); + } + + private switchLock(value: boolean): void { + const { + updateAnnotations, + jobInstance, + frameNumber, + } = this.props; + + const { ownObjectStates } = this.state; + for (const state of ownObjectStates) { + state.lock = value; + } + + updateAnnotations(jobInstance, frameNumber, ownObjectStates); + } + + public render(): JSX.Element { + const { + visible, + statesHidden, + statesLocked, + } = this.state; + + const { + labelName, + labelColor, + labelColors, + } = this.props; + + return ( + + ); + } +} + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(LabelItemContainer); diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/labels-list.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/labels-list.tsx new file mode 100644 index 00000000000..3494ba976b6 --- /dev/null +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/labels-list.tsx @@ -0,0 +1,33 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import { connect } from 'react-redux'; + +import LabelsListComponent from 'components/annotation-page/standard-workspace/objects-side-bar/labels-list'; +import { CombinedState } from 'reducers/interfaces'; + +interface StateToProps { + labelIDs: number[]; + listHeight: number; +} + +function mapStateToProps(state: CombinedState): StateToProps { + const { + annotation: { + job: { + labels, + }, + tabContentHeight: listHeight, + }, + } = state; + + return { + labelIDs: labels.map((label: any): number => label.id), + listHeight, + }; +} + +export default connect( + mapStateToProps, +)(LabelsListComponent); diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx new file mode 100644 index 00000000000..9c65ee79343 --- /dev/null +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx @@ -0,0 +1,525 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import copy from 'copy-to-clipboard'; +import { connect } from 'react-redux'; +import { + ActiveControl, + CombinedState, + ColorBy, +} from 'reducers/interfaces'; +import { + collapseObjectItems, + changeLabelColorAsync, + updateAnnotationsAsync, + changeFrameAsync, + removeObjectAsync, + changeGroupColorAsync, + copyShape as copyShapeAction, + activateObject as activateObjectAction, + propagateObject as propagateObjectAction, + pasteShapeAsync, +} from 'actions/annotation-actions'; + +import ObjectStateItemComponent from 'components/annotation-page/standard-workspace/objects-side-bar/object-item'; + +interface OwnProps { + clientID: number; +} + +interface StateToProps { + objectState: any; + collapsed: boolean; + labels: any[]; + attributes: any[]; + jobInstance: any; + frameNumber: number; + activated: boolean; + colorBy: ColorBy; + ready: boolean; + colors: string[]; + activeControl: ActiveControl; + minZLayer: number; + maxZLayer: number; +} + +interface DispatchToProps { + changeFrame(frame: number): void; + updateState(sessionInstance: any, frameNumber: number, objectState: any): void; + collapseOrExpand(objectStates: any[], collapsed: boolean): void; + activateObject: (activatedStateID: number | null) => void; + removeObject: (sessionInstance: any, objectState: any) => void; + copyShape: (objectState: any) => void; + propagateObject: (objectState: any) => void; + changeLabelColor(sessionInstance: any, frameNumber: number, label: any, color: string): void; + changeGroupColor(sessionInstance: any, frameNumber: number, group: number, color: string): void; +} + +function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { + const { + annotation: { + annotations: { + states, + collapsed: statesCollapsed, + activatedStateID, + zLayer: { + min: minZLayer, + max: maxZLayer, + }, + }, + job: { + attributes: jobAttributes, + instance: jobInstance, + labels, + }, + player: { + frame: { + number: frameNumber, + }, + }, + canvas: { + ready, + activeControl, + }, + colors, + }, + settings: { + shapes: { + colorBy, + }, + }, + } = state; + + const index = states + .map((_state: any): number => _state.clientID) + .indexOf(own.clientID); + + const collapsedState = typeof (statesCollapsed[own.clientID]) === 'undefined' + ? true : statesCollapsed[own.clientID]; + + return { + objectState: states[index], + collapsed: collapsedState, + attributes: jobAttributes[states[index].label.id], + labels, + ready, + activeControl, + colorBy, + colors, + jobInstance, + frameNumber, + activated: activatedStateID === own.clientID, + minZLayer, + maxZLayer, + }; +} + +function mapDispatchToProps(dispatch: any): DispatchToProps { + return { + changeFrame(frame: number): void { + dispatch(changeFrameAsync(frame)); + }, + updateState(sessionInstance: any, frameNumber: number, state: any): void { + dispatch(updateAnnotationsAsync(sessionInstance, frameNumber, [state])); + }, + collapseOrExpand(objectStates: any[], collapsed: boolean): void { + dispatch(collapseObjectItems(objectStates, collapsed)); + }, + activateObject(activatedStateID: number | null): void { + dispatch(activateObjectAction(activatedStateID)); + }, + removeObject(sessionInstance: any, objectState: any): void { + dispatch(removeObjectAsync(sessionInstance, objectState, true)); + }, + copyShape(objectState: any): void { + dispatch(copyShapeAction(objectState)); + dispatch(pasteShapeAsync()); + }, + propagateObject(objectState: any): void { + dispatch(propagateObjectAction(objectState)); + }, + changeLabelColor( + sessionInstance: any, + frameNumber: number, + label: any, + color: string, + ): void { + dispatch(changeLabelColorAsync(sessionInstance, frameNumber, label, color)); + }, + changeGroupColor( + sessionInstance: any, + frameNumber: number, + group: number, + color: string, + ): void { + dispatch(changeGroupColorAsync(sessionInstance, frameNumber, group, color)); + }, + }; +} + +type Props = StateToProps & DispatchToProps; +class ObjectItemContainer extends React.PureComponent { + private navigateFirstKeyframe = (): void => { + const { + objectState, + changeFrame, + frameNumber, + } = this.props; + + const { first } = objectState.keyframes; + if (first !== frameNumber) { + changeFrame(first); + } + }; + + private navigatePrevKeyframe = (): void => { + const { + objectState, + changeFrame, + frameNumber, + } = this.props; + + const { prev } = objectState.keyframes; + if (prev !== null && prev !== frameNumber) { + changeFrame(prev); + } + }; + + private navigateNextKeyframe = (): void => { + const { + objectState, + changeFrame, + frameNumber, + } = this.props; + + const { next } = objectState.keyframes; + if (next !== null && next !== frameNumber) { + changeFrame(next); + } + }; + + private navigateLastKeyframe = (): void => { + const { + objectState, + changeFrame, + frameNumber, + } = this.props; + + const { last } = objectState.keyframes; + if (last !== frameNumber) { + changeFrame(last); + } + }; + + private copy = (): void => { + const { + objectState, + copyShape, + } = this.props; + + copyShape(objectState); + }; + + private propagate = (): void => { + const { + objectState, + propagateObject, + } = this.props; + + propagateObject(objectState); + }; + + private remove = (): void => { + const { + objectState, + removeObject, + jobInstance, + } = this.props; + + removeObject(jobInstance, objectState); + }; + + private createURL = (): void => { + const { + objectState, + frameNumber, + } = this.props; + + const { + origin, + pathname, + } = window.location; + + const search = `frame=${frameNumber}&object=${objectState.serverID}`; + const url = `${origin}${pathname}?${search}`; + copy(url); + }; + + private toBackground = (): void => { + const { + objectState, + minZLayer, + } = this.props; + + objectState.zOrder = minZLayer - 1; + this.commit(); + }; + + private toForeground = (): void => { + const { + objectState, + maxZLayer, + } = this.props; + + objectState.zOrder = maxZLayer + 1; + this.commit(); + }; + + private activate = (): void => { + const { + activateObject, + objectState, + ready, + activeControl, + } = this.props; + + if (ready && activeControl === ActiveControl.CURSOR) { + activateObject(objectState.clientID); + } + }; + + private lock = (): void => { + const { objectState } = this.props; + objectState.lock = true; + this.commit(); + }; + + private unlock = (): void => { + const { objectState } = this.props; + objectState.lock = false; + this.commit(); + }; + + private pin = (): void => { + const { objectState } = this.props; + objectState.pinned = true; + this.commit(); + }; + + private unpin = (): void => { + const { objectState } = this.props; + objectState.pinned = false; + this.commit(); + }; + + private show = (): void => { + const { objectState } = this.props; + objectState.hidden = false; + this.commit(); + }; + + private hide = (): void => { + const { objectState } = this.props; + objectState.hidden = true; + this.commit(); + }; + + private setOccluded = (): void => { + const { objectState } = this.props; + objectState.occluded = true; + this.commit(); + }; + + private unsetOccluded = (): void => { + const { objectState } = this.props; + objectState.occluded = false; + this.commit(); + }; + + private setOutside = (): void => { + const { objectState } = this.props; + objectState.outside = true; + this.commit(); + }; + + private unsetOutside = (): void => { + const { objectState } = this.props; + objectState.outside = false; + this.commit(); + }; + + private setKeyframe = (): void => { + const { objectState } = this.props; + objectState.keyframe = true; + this.commit(); + }; + + private unsetKeyframe = (): void => { + const { objectState } = this.props; + objectState.keyframe = false; + this.commit(); + }; + + private collapse = (): void => { + const { + collapseOrExpand, + objectState, + collapsed, + } = this.props; + + collapseOrExpand([objectState], !collapsed); + }; + + private changeColor = (color: string): void => { + const { + jobInstance, + objectState, + colorBy, + changeLabelColor, + changeGroupColor, + frameNumber, + } = this.props; + + if (colorBy === ColorBy.INSTANCE) { + objectState.color = color; + this.commit(); + } else if (colorBy === ColorBy.GROUP) { + changeGroupColor(jobInstance, frameNumber, objectState.group.id, color); + } else if (colorBy === ColorBy.LABEL) { + changeLabelColor(jobInstance, frameNumber, objectState.label, color); + } + }; + + private changeLabel = (labelID: string): void => { + const { + objectState, + labels, + } = this.props; + + const [label] = labels.filter((_label: any): boolean => _label.id === +labelID); + objectState.label = label; + this.commit(); + }; + + private changeAttribute = (id: number, value: string): void => { + const { objectState } = this.props; + const attr: Record = {}; + attr[id] = value; + objectState.attributes = attr; + this.commit(); + }; + + private commit(): void { + const { + objectState, + updateState, + jobInstance, + frameNumber, + } = this.props; + + updateState(jobInstance, frameNumber, objectState); + } + + public render(): JSX.Element { + const { + objectState, + collapsed, + labels, + attributes, + frameNumber, + activated, + colorBy, + colors, + } = this.props; + + const { + first, + prev, + next, + last, + } = objectState.keyframes || { + first: null, // shapes don't have keyframes, so we use null + prev: null, + next: null, + last: null, + }; + + let stateColor = ''; + if (colorBy === ColorBy.INSTANCE) { + stateColor = objectState.color; + } else if (colorBy === ColorBy.GROUP) { + stateColor = objectState.group.color; + } else if (colorBy === ColorBy.LABEL) { + stateColor = objectState.label.color; + } + + return ( +
- - - - - - - - -
Name Upload Date Actions
-
-
-
-
- -
- - - - - - - - - - - - - -
- - -
- - -
-
-
- - - -
-
-
-
- - -
-
- -
-
-
-
`; - - this.el = $(html); - - this.table = this.el.find(`#${window.cvatUI.autoAnnotation.managerUploadedModelsId}`); - this.globallyBlock = this.el.find(`#${window.cvatUI.autoAnnotation.uploadGloballyBlockId}`); - this.uploadTitle = this.el.find(`#${window.cvatUI.autoAnnotation.uploadTitleId}`); - this.uploadNameInput = this.el.find(`#${window.cvatUI.autoAnnotation.uploadNameInputId}`); - this.uploadMessage = this.el.find(`#${window.cvatUI.autoAnnotation.uploadMessageId}`); - this.selectedFilesLabel = this.el.find(`#${window.cvatUI.autoAnnotation.selectedFilesId}`); - this.modelNameInput = this.el.find(`#${window.cvatUI.autoAnnotation.uploadNameInputId}`); - this.localSource = this.el.find(`#${window.cvatUI.autoAnnotation.uploadLocalSourceId}`); - this.shareSource = this.el.find(`#${window.cvatUI.autoAnnotation.uploadShareSourceId}`); - this.cancelButton = this.el.find(`#${window.cvatUI.autoAnnotation.cancelUploadButtonId}`); - this.submitButton = this.el.find(`#${window.cvatUI.autoAnnotation.submitUploadButtonId}`); - this.globallyBox = this.el.find(`#${window.cvatUI.autoAnnotation.uploadGloballyId}`); - this.selectButton = this.el.find(`#${window.cvatUI.autoAnnotation.selectFilesButtonId}`); - this.localSelector = this.el.find(`#${window.cvatUI.autoAnnotation.localFileSelectorId}`); - this.shareSelector = $('#dashboardShareBrowseModal'); - this.shareBrowseTree = $('#dashboardShareBrowser'); - this.submitShare = $('#dashboardSubmitBrowseServer'); - - this.id = null; - this.source = this.localSource.prop('checked') ? 'local' : 'shared'; - this.files = []; - - function filesLabel(source, files) { - const fileLabels = source === 'local' ? [...files].map(el => el.name) : files; - if (fileLabels.length) { - const labelStr = fileLabels.join(', '); - if (labelStr.length > 30) { - return `${labelStr.substr(0, 30)}..`; - } - - return labelStr; - } - - return 'No Files'; - } - - function extractFiles(extensions, files, source) { - const extractedFiles = {}; - function getExt(file) { - return source === 'local' ? file.name.split('.').pop() : file.split('.').pop(); - } - - function addFile(file, extention) { - if (extention in files) { - throw Error(`More than one file with the extension .${extention} have been found`); - } - - extractedFiles[extention] = file; - } - - files.forEach((file) => { - const fileExt = getExt(file); - if (extensions.includes(fileExt)) { - addFile(file, fileExt); - } - }); - - return extractedFiles; - } - - function validateFiles(isUpdate, files, source) { - const extensions = ['xml', 'bin', 'py', 'json']; - const extractedFiles = extractFiles(extensions, files, source); - - if (!isUpdate) { - extensions.forEach((extension) => { - if (!(extension in extractedFiles)) { - throw Error(`Please specify a .${extension} file`); - } - }); - } - - return extractedFiles; - } - - this.localSource.on('click', () => { - if (this.source !== 'local') { - this.source = 'local'; - this.files = []; - this.selectedFilesLabel.text(filesLabel(this.source, this.files)); - } - }); - - this.shareSource.on('click', () => { - if (this.source !== 'shared') { - this.source = 'shared'; - this.files = []; - this.selectedFilesLabel.text(filesLabel(this.source, this.files)); - } - }); - - this.selectButton.on('click', () => { - if (this.source === 'local') { - this.localSelector.click(); - } else { - this.shareSelector.appendTo('body'); - this.shareBrowseTree.jstree('refresh'); - this.shareSelector.removeClass('hidden'); - this.shareBrowseTree.jstree({ - core: { - async data(obj, callback) { - const directory = obj.id === '#' ? '' : `${obj.id}/`; - - let shareFiles = await window.cvat.server.share(directory); - shareFiles = Array.from(shareFiles, (element) => { - const shareFileInfo = { - id: `${directory}${element.name}`, - children: element.type === 'DIR', - text: element.name, - icon: element.type === 'DIR' ? 'jstree-folder' : 'jstree-file', - }; - - return shareFileInfo; - }); - - callback.call(this, shareFiles); - }, - }, - plugins: ['checkbox', 'sort'], - }); - } - }); - - this.submitShare.on('click', () => { - if (!this.el.hasClass('hidden')) { - this.shareSelector.addClass('hidden'); - this.files = this.shareBrowseTree.jstree(true).get_selected(); - this.selectedFilesLabel.text(filesLabel(this.source, this.files)); - } - }); - - this.localSelector.on('change', (e) => { - this.files = Array.from(e.target.files); - this.selectedFilesLabel.text(filesLabel(this.source, this.files)); - }); - - this.cancelButton.on('click', () => this.el.addClass('hidden')); - this.submitButton.on('click', () => { - try { - this.submitButton.prop('disabled', true); - - const name = $.trim(this.modelNameInput.prop('value')); - if (!name.length) { - this.uploadMessage.css('color', 'red'); - this.uploadMessage.text('Please specify a model name'); - return; - } - - let validatedFiles = {}; - try { - validatedFiles = validateFiles(this.id !== null, this.files, this.source); - } catch (err) { - this.uploadMessage.css('color', 'red'); - this.uploadMessage.text(err); - return; - } - - const modelData = new FormData(); - modelData.append('name', name); - modelData.append('storage', this.source); - modelData.append('shared', this.globallyBox.prop('checked')); - - ['xml', 'bin', 'json', 'py'].filter(e => e in validatedFiles).forEach((ext) => { - modelData.append(ext, validatedFiles[ext]); - }); - - this.uploadMessage.text(''); - const overlay = showOverlay('Send request to the server..'); - window.cvatUI.autoAnnotation.server.update(modelData, () => { - window.location.reload(); - }, (message) => { - overlay.remove(); - showMessage(message); - }, (progress) => { - overlay.setMessage(progress); - }, window.cvatUI.autoAnnotation.server.check, this.id); - } finally { - this.submitButton.prop('disabled', false); - } - }); - } - - reset() { - const setBlocked = () => { - if (window.cvatUI.autoAnnotation.data.admin) { - this.globallyBlock.removeClass('hidden'); - } else { - this.globallyBlock.addClass('hidden'); - } - }; - - setBlocked(); - this.uploadTitle.text('Create Model'); - this.uploadNameInput.prop('value', ''); - this.uploadMessage.css('color', ''); - this.uploadMessage.text(''); - this.selectedFilesLabel.text('No Files'); - this.localSource.prop('checked', true); - this.globallyBox.prop('checked', false); - this.table.empty(); - - this.id = null; - this.source = this.localSource.prop('checked') ? 'local' : 'share'; - this.files = []; - - const updateButtonClickHandler = (event) => { - this.reset(); - - this.uploadTitle.text('Update Model'); - this.uploadNameInput.prop('value', `${event.data.model.name}`); - this.id = event.data.model.id; - }; - - const deleteButtonClickHandler = (event) => { - userConfirm(`Do you actually want to delete the "${event.data.model.name}" model. Are you sure?`, () => { - window.cvatUI.autoAnnotation.server.delete(event.data.model.id, () => { - const filtered = window.cvatUI.autoAnnotation.data.models.filter( - item => item !== event.data.model, - ); - window.cvatUI.autoAnnotation.data.models = filtered; - this.reset(); - }, (message) => { - showMessage(message); - }); - }); - }; - - const getModelModifyButtons = (model) => { - if (model.primary) { - return ' '; - } - - const updateButtonHtml = ''; - const deleteButtonHtml = ''; - - return $(' ').append( - $(updateButtonHtml).on('click', { model }, updateButtonClickHandler), - $(deleteButtonHtml).on('click', { model }, deleteButtonClickHandler), - ); - }; - - window.cvatUI.autoAnnotation.data.models.forEach((model) => { - const rowHtml = ` - ${model.name} - ${model.uploadDate} - `; - - this.table.append( - $(rowHtml).append(getModelModifyButtons(model)), - ); - }); - - return this; - } - - show() { - this.el.removeClass('hidden'); - return this; - } - - get element() { - return this.el; - } -} - - -class AutoAnnotationModelRunnerView { - constructor() { - const html = ``; - - this.el = $(html); - this.id = null; - this.tid = null; - this.initButton = null; - this.modelsTable = this.el.find(`#${window.cvatUI.autoAnnotation.runnerUploadedModelsId}`); - this.labelsTable = this.el.find(`#${window.cvatUI.autoAnnotation.annotationLabelsId}`); - this.active = null; - - this.el.find(`#${window.cvatUI.autoAnnotation.cancelAnnotationId}`).on('click', () => { - this.el.addClass('hidden'); - }); - - this.el.find(`#${window.cvatUI.autoAnnotation.submitAnnotationId}`).on('click', () => { - try { - if (this.id === null) { - throw Error('Please specify a model for an annotation process'); - } - - const mapping = {}; - $('.annotatorMappingRow').each((_, element) => { - const dlModelLabel = $(element).find('.annotatorDlLabelSelector')[0].value; - const taskLabel = $(element).find('.annotatorTaskLabelSelector')[0].value; - if (dlModelLabel in mapping) { - throw Error(`The label "${dlModelLabel}" has been specified twice or more`); - } - mapping[dlModelLabel] = taskLabel; - }); - - if (!Object.keys(mapping).length) { - throw Error('Labels for an annotation process haven\'t been found'); - } - - const overlay = showOverlay('Request has been sent'); - window.cvatUI.autoAnnotation.server.start(this.id, this.tid, { - reset: $(`#${window.cvatUI.autoAnnotation.removeCurrentAnnotationId}`).prop('checked'), - labels: mapping, - }, () => { - overlay.remove(); - this.initButton[0].setupRun(); - window.cvatUI.autoAnnotation.runner.hide(); - }, (message) => { - overlay.remove(); - this.initButton[0].setupRun(); - showMessage(message); - }, () => { - window.location.reload(); - }, window.cvatUI.autoAnnotation.server.check); - } catch (error) { - showMessage(error); - } - }); - } - - reset(data, initButton) { - function labelsSelect(labels, elClass) { - const select = $(``); - labels.forEach(label => select.append($(``))); - select.prop('value', null); - - return select; - } - - function makeCreator(dlSelect, taskSelect, callback) { - let dlIsFilled = false; - let taskIsFilled = false; - const creator = $(' ').append( - $(' ').append(taskSelect), - $(' ').append(dlSelect), - ); - - const onSelectHandler = () => { - $(' ').append( - $('').css('top', '0px').on('click', (e) => { - $(e.target.parentNode.parentNode).remove(); - }), - ).appendTo(creator); - - creator.addClass('annotatorMappingRow'); - callback(); - }; - - dlSelect.on('change', (e) => { - if (e.target.value && taskIsFilled) { - dlSelect.off('change'); - taskSelect.off('change'); - onSelectHandler(); - } - dlIsFilled = Boolean(e.target.value); - }); - - taskSelect.on('change', (e) => { - if (e.target.value && dlIsFilled) { - dlSelect.off('change'); - taskSelect.off('change'); - onSelectHandler(); - } - - taskIsFilled = Boolean(e.target.value); - }); - - return creator; - } - - this.id = null; - this.initButton = initButton; - this.tid = data.id; - this.modelsTable.empty(); - this.labelsTable.empty(); - this.active = null; - - const modelItemClickHandler = (event) => { - if (this.active) { - this.active.style.color = ''; - } - - this.id = event.data.model.id; - this.active = event.target; - this.active.style.color = 'darkblue'; - - this.labelsTable.empty(); - const labels = event.data.data.labels.map(x => x.name); - const intersection = labels.filter(el => event.data.model.labels.indexOf(el) !== -1); - intersection.forEach((label) => { - const dlSelect = labelsSelect(event.data.model.labels, 'annotatorDlLabelSelector'); - dlSelect.prop('value', label); - const taskSelect = labelsSelect(labels, 'annotatorTaskLabelSelector'); - taskSelect.prop('value', label); - $(' ').append( - $(' ').append(taskSelect), - $(' ').append(dlSelect), - $(' ').append( - $('').css('top', '0px').on('click', (e) => { - $(e.target.parentNode.parentNode).remove(); - }), - ), - ).appendTo(this.labelsTable); - }); - - const dlSelect = labelsSelect(event.data.model.labels, 'annotatorDlLabelSelector'); - const taskSelect = labelsSelect(labels, 'annotatorTaskLabelSelector'); - - const callback = () => { - makeCreator( - labelsSelect(event.data.model.labels, 'annotatorDlLabelSelector'), - labelsSelect(labels, 'annotatorTaskLabelSelector'), - callback, - ).appendTo(this.labelsTable); - }; - - makeCreator(dlSelect, taskSelect, callback).appendTo(this.labelsTable); - }; - - window.cvatUI.autoAnnotation.data.models.forEach((model) => { - this.modelsTable.append( - $(` `).on( - 'click', { model, data }, modelItemClickHandler, - ), - ); - }); - - return this; - } - - show() { - this.el.removeClass('hidden'); - return this; - } - - hide() { - this.el.addClass('hidden'); - return this; - } - - get element() { - return this.el; - } -} - -window.cvatUI.autoAnnotation = { - managerWindowId: 'annotatorManagerWindow', - managerContentId: 'annotatorManagerContent', - managerUploadedModelsId: 'annotatorManagerUploadedModels', - uploadContentId: 'annotatorManagerUploadModel', - uploadTitleId: 'annotatorManagerUploadTitle', - uploadNameInputId: 'annotatorManagerUploadNameInput', - uploadLocalSourceId: 'annotatorManagerUploadLocalSource', - uploadShareSourceId: 'annotatorManagerUploadShareSource', - uploadGloballyId: 'annotatorManagerUploadGlobally', - uploadGloballyBlockId: 'annotatorManagerUploadGloballyblock', - selectFilesButtonId: 'annotatorManagerUploadSelector', - selectedFilesId: 'annotatorManagerUploadSelectedFiles', - localFileSelectorId: 'annotatorManagerUploadLocalSelector', - shareFileSelectorId: 'annotatorManagerUploadShareSelector', - submitUploadButtonId: 'annotatorManagerSubmitUploadButton', - cancelUploadButtonId: 'annotatorManagerCancelUploadButton', - uploadMessageId: 'annotatorUploadStatusMessage', - - runnerWindowId: 'annotatorRunnerWindow', - runnerContentId: 'annotatorRunnerContent', - runnerUploadedModelsId: 'annotatorRunnerUploadedModels', - removeCurrentAnnotationId: 'annotatorRunnerRemoveCurrentAnnotationBox', - annotationLabelsId: 'annotatorRunnerAnnotationLabels', - submitAnnotationId: 'annotatorRunnerSubmitAnnotationButton', - cancelAnnotationId: 'annotatorRunnerCancelAnnotationButton', - - managerButtonId: 'annotatorManagerButton', -}; - -window.addEventListener('DOMContentLoaded', () => { - window.cvatUI.autoAnnotation.server = AutoAnnotationServer; - window.cvatUI.autoAnnotation.manager = new AutoAnnotationModelManagerView(); - window.cvatUI.autoAnnotation.runner = new AutoAnnotationModelRunnerView(); - - $('body').append(window.cvatUI.autoAnnotation.manager.element, window.cvatUI.autoAnnotation.runner.element); - $(``) - .on('click', () => { - const overlay = showOverlay('The manager are being setup..'); - window.cvatUI.autoAnnotation.manager.reset().show(); - overlay.remove(); - }).appendTo('#dashboardManageButtons'); -}); - - -window.addEventListener('dashboardReady', (event) => { - const elements = $('.dashboardItem'); - const tids = Array.from(elements, el => +el.getAttribute('tid')); - - window.cvatUI.autoAnnotation.server.meta(tids, (data) => { - window.cvatUI.autoAnnotation.data = data; - - elements.each(function setupDashboardItem() { - const elem = $(this); - const tid = +elem.attr('tid'); - - const button = $('').addClass('regular dashboardButtonUI'); - button[0].setupRun = function setupRun() { - const self = $(this); - const taskInfo = event.detail.filter(task => task.id === tid)[0]; - self.text('Run Auto Annotation').off('click').on('click', () => { - window.cvatUI.autoAnnotation.runner.reset(taskInfo, self).show(); - }); - }; - - button[0].setupCancel = function setupCancel() { - const self = $(this); - self.off('click').text('Cancel Auto Annotation').on('click', () => { - userConfirm('Process will be canceled. Are you sure?', () => { - window.cvatUI.autoAnnotation.server.cancel(tid, () => { - this.setupRun(); - }, (message) => { - showMessage(message); - }); - }); - }); - - window.cvatUI.autoAnnotation.server.check( - window.cvatUI.autoAnnotation.data.run[tid].rq_id, - () => { - this.setupRun(); - }, - (error) => { - button[0].setupRun(); - button.text('Annotation has failed'); - button.title(error); - }, - (progress) => { - button.text(`Cancel Auto Annotation (${progress.toString().slice(0, 4)})%`); - }, - ); - }; - - const taskStatus = window.cvatUI.autoAnnotation.data.run[tid]; - if (taskStatus && ['queued', 'started'].includes(taskStatus.status)) { - button[0].setupCancel(); - } else { - button[0].setupRun(); - } - - button.appendTo(elem.find('div.dashboardButtonsUI')[0]); - }); - }, (error) => { - showMessage(`Cannot get models meta information: ${error}`); - }); -}); diff --git a/cvat/apps/auto_annotation/static/auto_annotation/stylesheet.css b/cvat/apps/auto_annotation/static/auto_annotation/stylesheet.css deleted file mode 100644 index 78828bdf7ed..00000000000 --- a/cvat/apps/auto_annotation/static/auto_annotation/stylesheet.css +++ /dev/null @@ -1,83 +0,0 @@ -#annotatorManagerContent, #annotatorRunnerContent { - width: 800px; - height: 300px; -} - -#annotatorManagerButton { - padding: 7px; - margin-left: 4px; -} - -.modelsTable { - width: 100%; - color:#666; - text-shadow: 1px 1px 0px #fff; - background:#D2D3D4; - border:#ccc 1px solid; - border-radius: 3px; - box-shadow: 0 1px 2px black; -} - -.modelsTable th { - border-top: 1px solid #fafafa; - border-bottom: 1px solid #e0e0e0; - background: #ededed; -} - -.modelsTable th:first-child { - text-align: left; - padding-left:20px; -} - -.modelsTable tr:first-child th:first-child { - border-top-left-radius:3px; -} - -.modelsTable tr:first-child th:last-child { - border-top-right-radius:3px; -} - -.modelsTable tr { - text-align: center; - padding-left: 20px; -} - -.modelsTable td:first-child { - text-align: left; - padding-left: 20px; - border-left: 0; -} - -.modelsTable td { - padding: 18px; - border-top: 1px solid #ffffff; - border-bottom:1px solid #e0e0e0; - border-left: 1px solid #e0e0e0; - background: #fafafa; -} - -.modelsTable tr.even td { - background: #f6f6f6; -} - -.modelsTable tr:last-child td { - border-bottom:0; -} - -.modelsTable tr:last-child td:first-child { - border-bottom-left-radius:3px; -} - -.modelsTable tr:last-child td:last-child { - border-bottom-right-radius:3px; -} - -.modelsTable tr:hover td { - background: #f2f2f2; -} - -#annotatorManagerUploadModel { - float: left; - padding-left: 3%; - width: 40%; -} diff --git a/cvat/apps/auto_annotation/views.py b/cvat/apps/auto_annotation/views.py index e13ed2c0766..c521424dfde 100644 --- a/cvat/apps/auto_annotation/views.py +++ b/cvat/apps/auto_annotation/views.py @@ -7,6 +7,7 @@ import os from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest +from rest_framework.decorators import api_view from django.db.models import Q from rules.contrib.views import permission_required, objectgetter @@ -124,10 +125,11 @@ def delete_model(request, mid): model_manager.delete(mid) return HttpResponse() +@api_view(['POST']) @login_required def get_meta_info(request): try: - tids = json.loads(request.body.decode('utf-8')) + tids = request.data response = { "admin": has_admin_role(request.user), "models": [], @@ -147,6 +149,7 @@ def get_meta_info(request): "uploadDate": dl_model.created_date, "updateDate": dl_model.updated_date, "labels": labels, + "owner": dl_model.owner.id, }) queue = django_rq.get_queue("low") diff --git a/cvat/apps/auto_segmentation/__init__.py b/cvat/apps/auto_segmentation/__init__.py new file mode 100644 index 00000000000..a0fca4cb39e --- /dev/null +++ b/cvat/apps/auto_segmentation/__init__.py @@ -0,0 +1,4 @@ + +# Copyright (C) 2018-2019 Intel Corporation +# +# SPDX-License-Identifier: MIT diff --git a/cvat/apps/auto_segmentation/admin.py b/cvat/apps/auto_segmentation/admin.py new file mode 100644 index 00000000000..3c40ebdfe11 --- /dev/null +++ b/cvat/apps/auto_segmentation/admin.py @@ -0,0 +1,8 @@ + +# Copyright (C) 2018 Intel Corporation +# +# SPDX-License-Identifier: MIT + + +# Register your models here. + diff --git a/cvat/apps/auto_segmentation/apps.py b/cvat/apps/auto_segmentation/apps.py new file mode 100644 index 00000000000..03322710f45 --- /dev/null +++ b/cvat/apps/auto_segmentation/apps.py @@ -0,0 +1,11 @@ + +# Copyright (C) 2018 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from django.apps import AppConfig + + +class AutoSegmentationConfig(AppConfig): + name = 'auto_segmentation' + diff --git a/cvat/apps/dashboard/migrations/__init__.py b/cvat/apps/auto_segmentation/migrations/__init__.py similarity index 100% rename from cvat/apps/dashboard/migrations/__init__.py rename to cvat/apps/auto_segmentation/migrations/__init__.py diff --git a/cvat/apps/auto_segmentation/models.py b/cvat/apps/auto_segmentation/models.py new file mode 100644 index 00000000000..37401bdd220 --- /dev/null +++ b/cvat/apps/auto_segmentation/models.py @@ -0,0 +1,8 @@ + +# Copyright (C) 2018 Intel Corporation +# +# SPDX-License-Identifier: MIT + + +# Create your models here. + diff --git a/cvat/apps/auto_segmentation/tests.py b/cvat/apps/auto_segmentation/tests.py new file mode 100644 index 00000000000..d20a46ab6a6 --- /dev/null +++ b/cvat/apps/auto_segmentation/tests.py @@ -0,0 +1,8 @@ + +# Copyright (C) 2018 Intel Corporation +# +# SPDX-License-Identifier: MIT + + +# Create your tests here. + diff --git a/cvat/apps/auto_segmentation/urls.py b/cvat/apps/auto_segmentation/urls.py new file mode 100644 index 00000000000..f84019be969 --- /dev/null +++ b/cvat/apps/auto_segmentation/urls.py @@ -0,0 +1,14 @@ + +# Copyright (C) 2018 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from django.urls import path +from . import views + +urlpatterns = [ + path('create/task/', views.create), + path('check/task/', views.check), + path('cancel/task/', views.cancel), + path('meta/get', views.get_meta_info), +] diff --git a/cvat/apps/auto_segmentation/views.py b/cvat/apps/auto_segmentation/views.py new file mode 100644 index 00000000000..1f47d1ec1df --- /dev/null +++ b/cvat/apps/auto_segmentation/views.py @@ -0,0 +1,325 @@ + +# Copyright (C) 2018-2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + + +from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest +from rest_framework.decorators import api_view +from rules.contrib.views import permission_required, objectgetter +from cvat.apps.authentication.decorators import login_required +from cvat.apps.engine.models import Task as TaskModel +from cvat.apps.engine.serializers import LabeledDataSerializer +from cvat.apps.engine.annotation import put_task_data + +import django_rq +import fnmatch +import json +import os +import rq + +import numpy as np + +from cvat.apps.engine.log import slogger + +import sys +import skimage.io +from skimage.measure import find_contours, approximate_polygon + + +def load_image_into_numpy(image): + (im_width, im_height) = image.size + return np.array(image.getdata()).reshape((im_height, im_width, 3)).astype(np.uint8) + + +def run_tensorflow_auto_segmentation(image_list, labels_mapping, treshold): + def _convert_to_int(boolean_mask): + return boolean_mask.astype(np.uint8) + + def _convert_to_segmentation(mask): + contours = find_contours(mask, 0.5) + # only one contour exist in our case + contour = contours[0] + contour = np.flip(contour, axis=1) + # Approximate the contour and reduce the number of points + contour = approximate_polygon(contour, tolerance=2.5) + segmentation = contour.ravel().tolist() + return segmentation + + ## INITIALIZATION + + # Root directory of the project + ROOT_DIR = os.environ.get('AUTO_SEGMENTATION_PATH') + # Import Mask RCNN + sys.path.append(ROOT_DIR) # To find local version of the library + import mrcnn.model as modellib + + # Import COCO config + sys.path.append(os.path.join(ROOT_DIR, "samples/coco/")) # To find local version + import coco + + # Directory to save logs and trained model + MODEL_DIR = os.path.join(ROOT_DIR, "logs") + + # Local path to trained weights file + COCO_MODEL_PATH = os.path.join(ROOT_DIR, "mask_rcnn_coco.h5") + if COCO_MODEL_PATH is None: + raise OSError('Model path env not found in the system.') + job = rq.get_current_job() + + ## CONFIGURATION + + class InferenceConfig(coco.CocoConfig): + # Set batch size to 1 since we'll be running inference on + # one image at a time. Batch size = GPU_COUNT * IMAGES_PER_GPU + GPU_COUNT = 1 + IMAGES_PER_GPU = 1 + + # Print config details + config = InferenceConfig() + config.display() + + ## CREATE MODEL AND LOAD TRAINED WEIGHTS + + # Create model object in inference mode. + model = modellib.MaskRCNN(mode="inference", model_dir=MODEL_DIR, config=config) + # Load weights trained on MS-COCO + model.load_weights(COCO_MODEL_PATH, by_name=True) + + ## RUN OBJECT DETECTION + result = {} + for image_num, image_path in enumerate(image_list): + job.refresh() + if 'cancel' in job.meta: + del job.meta['cancel'] + job.save() + return None + job.meta['progress'] = image_num * 100 / len(image_list) + job.save_meta() + + image = skimage.io.imread(image_path) + + # for multiple image detection, "batch size" must be equal to number of images + r = model.detect([image], verbose=1) + + r = r[0] + # "r['rois'][index]" gives bounding box around the object + for index, c_id in enumerate(r['class_ids']): + if c_id in labels_mapping.keys(): + if r['scores'][index] >= treshold: + mask = _convert_to_int(r['masks'][:,:,index]) + segmentation = _convert_to_segmentation(mask) + label = labels_mapping[c_id] + if label not in result: + result[label] = [] + result[label].append( + [image_num, segmentation]) + + return result + + +def make_image_list(path_to_data): + def get_image_key(item): + return int(os.path.splitext(os.path.basename(item))[0]) + + image_list = [] + for root, _, filenames in os.walk(path_to_data): + for filename in fnmatch.filter(filenames, '*.jpg'): + image_list.append(os.path.join(root, filename)) + + image_list.sort(key=get_image_key) + return image_list + + +def convert_to_cvat_format(data): + result = { + "tracks": [], + "shapes": [], + "tags": [], + "version": 0, + } + + for label in data: + segments = data[label] + for segment in segments: + result['shapes'].append({ + "type": "polygon", + "label_id": label, + "frame": segment[0], + "points": segment[1], + "z_order": 0, + "group": None, + "occluded": False, + "attributes": [], + }) + + return result + +def create_thread(tid, labels_mapping, user): + try: + # If detected object accuracy bigger than threshold it will returend + TRESHOLD = 0.5 + # Init rq job + job = rq.get_current_job() + job.meta['progress'] = 0 + job.save_meta() + # Get job indexes and segment length + db_task = TaskModel.objects.get(pk=tid) + # Get image list + image_list = make_image_list(db_task.get_data_dirname()) + + # Run auto segmentation by tf + result = None + slogger.glob.info("auto segmentation with tensorflow framework for task {}".format(tid)) + result = run_tensorflow_auto_segmentation(image_list, labels_mapping, TRESHOLD) + + if result is None: + slogger.glob.info('auto segmentation for task {} canceled by user'.format(tid)) + return + + # Modify data format and save + result = convert_to_cvat_format(result) + serializer = LabeledDataSerializer(data = result) + if serializer.is_valid(raise_exception=True): + put_task_data(tid, user, result) + slogger.glob.info('auto segmentation for task {} done'.format(tid)) + except Exception as ex: + try: + slogger.task[tid].exception('exception was occured during auto segmentation of the task', exc_info=True) + except Exception: + slogger.glob.exception('exception was occured during auto segmentation of the task {}'.format(tid), exc_info=True) + raise ex + +@api_view(['POST']) +@login_required +def get_meta_info(request): + try: + queue = django_rq.get_queue('low') + tids = request.data + result = {} + for tid in tids: + job = queue.fetch_job('auto_segmentation.create/{}'.format(tid)) + if job is not None: + result[tid] = { + "active": job.is_queued or job.is_started, + "success": not job.is_failed + } + + return JsonResponse(result) + except Exception as ex: + slogger.glob.exception('exception was occured during tf meta request', exc_info=True) + return HttpResponseBadRequest(str(ex)) + + +@login_required +@permission_required(perm=['engine.task.change'], + fn=objectgetter(TaskModel, 'tid'), raise_exception=True) +def create(request, tid): + slogger.glob.info('auto segmentation create request for task {}'.format(tid)) + try: + db_task = TaskModel.objects.get(pk=tid) + queue = django_rq.get_queue('low') + job = queue.fetch_job('auto_segmentation.create/{}'.format(tid)) + if job is not None and (job.is_started or job.is_queued): + raise Exception("The process is already running") + + db_labels = db_task.label_set.prefetch_related('attributespec_set').all() + db_labels = {db_label.id:db_label.name for db_label in db_labels} + + # COCO Labels + auto_segmentation_labels = { "BG": 0, + "person": 1, "bicycle": 2, "car": 3, "motorcycle": 4, "airplane": 5, + "bus": 6, "train": 7, "truck": 8, "boat": 9, "traffic_light": 10, + "fire_hydrant": 11, "stop_sign": 12, "parking_meter": 13, "bench": 14, + "bird": 15, "cat": 16, "dog": 17, "horse": 18, "sheep": 19, "cow": 20, + "elephant": 21, "bear": 22, "zebra": 23, "giraffe": 24, "backpack": 25, + "umbrella": 26, "handbag": 27, "tie": 28, "suitcase": 29, "frisbee": 30, + "skis": 31, "snowboard": 32, "sports_ball": 33, "kite": 34, "baseball_bat": 35, + "baseball_glove": 36, "skateboard": 37, "surfboard": 38, "tennis_racket": 39, + "bottle": 40, "wine_glass": 41, "cup": 42, "fork": 43, "knife": 44, "spoon": 45, + "bowl": 46, "banana": 47, "apple": 48, "sandwich": 49, "orange": 50, "broccoli": 51, + "carrot": 52, "hot_dog": 53, "pizza": 54, "donut": 55, "cake": 56, "chair": 57, + "couch": 58, "potted_plant": 59, "bed": 60, "dining_table": 61, "toilet": 62, + "tv": 63, "laptop": 64, "mouse": 65, "remote": 66, "keyboard": 67, "cell_phone": 68, + "microwave": 69, "oven": 70, "toaster": 71, "sink": 72, "refrigerator": 73, + "book": 74, "clock": 75, "vase": 76, "scissors": 77, "teddy_bear": 78, "hair_drier": 79, + "toothbrush": 80 + } + + labels_mapping = {} + for key, labels in db_labels.items(): + if labels in auto_segmentation_labels.keys(): + labels_mapping[auto_segmentation_labels[labels]] = key + + if not len(labels_mapping.values()): + raise Exception('No labels found for auto segmentation') + + # Run auto segmentation job + queue.enqueue_call(func=create_thread, + args=(tid, labels_mapping, request.user), + job_id='auto_segmentation.create/{}'.format(tid), + timeout=604800) # 7 days + + slogger.task[tid].info('tensorflow segmentation job enqueued with labels {}'.format(labels_mapping)) + + except Exception as ex: + try: + slogger.task[tid].exception("exception was occured during tensorflow segmentation request", exc_info=True) + except Exception: + pass + return HttpResponseBadRequest(str(ex)) + + return HttpResponse() + +@login_required +@permission_required(perm=['engine.task.access'], + fn=objectgetter(TaskModel, 'tid'), raise_exception=True) +def check(request, tid): + try: + queue = django_rq.get_queue('low') + job = queue.fetch_job('auto_segmentation.create/{}'.format(tid)) + if job is not None and 'cancel' in job.meta: + return JsonResponse({'status': 'finished'}) + data = {} + if job is None: + data['status'] = 'unknown' + elif job.is_queued: + data['status'] = 'queued' + elif job.is_started: + data['status'] = 'started' + data['progress'] = job.meta['progress'] + elif job.is_finished: + data['status'] = 'finished' + job.delete() + else: + data['status'] = 'failed' + data['stderr'] = job.exc_info + job.delete() + + except Exception: + data['status'] = 'unknown' + + return JsonResponse(data) + + +@login_required +@permission_required(perm=['engine.task.change'], + fn=objectgetter(TaskModel, 'tid'), raise_exception=True) +def cancel(request, tid): + try: + queue = django_rq.get_queue('low') + job = queue.fetch_job('auto_segmentation.create/{}'.format(tid)) + if job is None or job.is_finished or job.is_failed: + raise Exception('Task is not being segmented currently') + elif 'cancel' not in job.meta: + job.meta['cancel'] = True + job.save() + + except Exception as ex: + try: + slogger.task[tid].exception("cannot cancel tensorflow segmentation for task #{}".format(tid), exc_info=True) + except Exception: + pass + return HttpResponseBadRequest(str(ex)) + + return HttpResponse() diff --git a/cvat/apps/dashboard/__init__.py b/cvat/apps/dashboard/__init__.py deleted file mode 100644 index da270801d9c..00000000000 --- a/cvat/apps/dashboard/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from cvat.settings.base import JS_3RDPARTY - -JS_3RDPARTY['engine'] = JS_3RDPARTY.get('engine', []) + ['dashboard/js/enginePlugin.js'] diff --git a/cvat/apps/dashboard/apps.py b/cvat/apps/dashboard/apps.py deleted file mode 100644 index 96362762375..00000000000 --- a/cvat/apps/dashboard/apps.py +++ /dev/null @@ -1,13 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.apps import AppConfig - -class DashboardConfig(AppConfig): - name = 'cvat.apps.dashboard' - - def ready(self): - # plugin registration - pass \ No newline at end of file diff --git a/cvat/apps/dashboard/static/dashboard/js/3rdparty/jstree/jstree.min.js b/cvat/apps/dashboard/static/dashboard/js/3rdparty/jstree/jstree.min.js deleted file mode 100644 index 9221c3384cc..00000000000 --- a/cvat/apps/dashboard/static/dashboard/js/3rdparty/jstree/jstree.min.js +++ /dev/null @@ -1,6 +0,0 @@ -/*! jsTree - v3.3.7 - 2018-11-06 - (MIT) */ -!function(a){"use strict";"function"==typeof define&&define.amd?define(["jquery"],a):"undefined"!=typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a,b){"use strict";if(!a.jstree){var c=0,d=!1,e=!1,f=!1,g=[],h=a("script:last").attr("src"),i=window.document;a.jstree={version:"3.3.7",defaults:{plugins:[]},plugins:{},path:h&&-1!==h.indexOf("/")?h.replace(/\/[^\/]+$/,""):"",idregex:/[\\:&!^|()\[\]<>@*'+~#";.,=\- \/${}%?`]/g,root:"#"},a.jstree.create=function(b,d){var e=new a.jstree.core(++c),f=d;return d=a.extend(!0,{},a.jstree.defaults,d),f&&f.plugins&&(d.plugins=f.plugins),a.each(d.plugins,function(a,b){"core"!==a&&(e=e.plugin(b,d[b]))}),a(b).data("jstree",e),e.init(b,d),e},a.jstree.destroy=function(){a(".jstree:jstree").jstree("destroy"),a(i).off(".jstree")},a.jstree.core=function(a){this._id=a,this._cnt=0,this._wrk=null,this._data={core:{themes:{name:!1,dots:!1,icons:!1,ellipsis:!1},selected:[],last_error:{},working:!1,worker_queue:[],focused:null}}},a.jstree.reference=function(b){var c=null,d=null;if(!b||!b.id||b.tagName&&b.nodeType||(b=b.id),!d||!d.length)try{d=a(b)}catch(e){}if(!d||!d.length)try{d=a("#"+b.replace(a.jstree.idregex,"\\$&"))}catch(e){}return d&&d.length&&(d=d.closest(".jstree")).length&&(d=d.data("jstree"))?c=d:a(".jstree").each(function(){var d=a(this).data("jstree");return d&&d._model.data[b]?(c=d,!1):void 0}),c},a.fn.jstree=function(c){var d="string"==typeof c,e=Array.prototype.slice.call(arguments,1),f=null;return c!==!0||this.length?(this.each(function(){var g=a.jstree.reference(this),h=d&&g?g[c]:null;return f=d&&h?h.apply(g,e):null,g||d||c!==b&&!a.isPlainObject(c)||a.jstree.create(this,c),(g&&!d||c===!0)&&(f=g||!1),null!==f&&f!==b?!1:void 0}),null!==f&&f!==b?f:this):!1},a.expr.pseudos.jstree=a.expr.createPseudo(function(c){return function(c){return a(c).hasClass("jstree")&&a(c).data("jstree")!==b}}),a.jstree.defaults.core={data:!1,strings:!1,check_callback:!1,error:a.noop,animation:200,multiple:!0,themes:{name:!1,url:!1,dir:!1,dots:!0,icons:!0,ellipsis:!1,stripes:!1,variant:!1,responsive:!1},expand_selected_onload:!0,worker:!0,force_text:!1,dblclick_toggle:!0,loaded_state:!1,restore_focus:!0,keyboard:{"ctrl-space":function(b){b.type="click",a(b.currentTarget).trigger(b)},enter:function(b){b.type="click",a(b.currentTarget).trigger(b)},left:function(b){if(b.preventDefault(),this.is_open(b.currentTarget))this.close_node(b.currentTarget);else{var c=this.get_parent(b.currentTarget);c&&c.id!==a.jstree.root&&this.get_node(c,!0).children(".jstree-anchor").focus()}},up:function(a){a.preventDefault();var b=this.get_prev_dom(a.currentTarget);b&&b.length&&b.children(".jstree-anchor").focus()},right:function(b){if(b.preventDefault(),this.is_closed(b.currentTarget))this.open_node(b.currentTarget,function(a){this.get_node(a,!0).children(".jstree-anchor").focus()});else if(this.is_open(b.currentTarget)){var c=this.get_node(b.currentTarget,!0).children(".jstree-children")[0];c&&a(this._firstChild(c)).children(".jstree-anchor").focus()}},down:function(a){a.preventDefault();var b=this.get_next_dom(a.currentTarget);b&&b.length&&b.children(".jstree-anchor").focus()},"*":function(a){this.open_all()},home:function(b){b.preventDefault();var c=this._firstChild(this.get_container_ul()[0]);c&&a(c).children(".jstree-anchor").filter(":visible").focus()},end:function(a){a.preventDefault(),this.element.find(".jstree-anchor").filter(":visible").last().focus()},f2:function(a){a.preventDefault(),this.edit(a.currentTarget)}}},a.jstree.core.prototype={plugin:function(b,c){var d=a.jstree.plugins[b];return d?(this._data[b]={},d.prototype=this,new d(c,this)):this},init:function(b,c){this._model={data:{},changed:[],force_full_redraw:!1,redraw_timeout:!1,default_state:{loaded:!0,opened:!1,selected:!1,disabled:!1}},this._model.data[a.jstree.root]={id:a.jstree.root,parent:null,parents:[],children:[],children_d:[],state:{loaded:!1}},this.element=a(b).addClass("jstree jstree-"+this._id),this.settings=c,this._data.core.ready=!1,this._data.core.loaded=!1,this._data.core.rtl="rtl"===this.element.css("direction"),this.element[this._data.core.rtl?"addClass":"removeClass"]("jstree-rtl"),this.element.attr("role","tree"),this.settings.core.multiple&&this.element.attr("aria-multiselectable",!0),this.element.attr("tabindex")||this.element.attr("tabindex","0"),this.bind(),this.trigger("init"),this._data.core.original_container_html=this.element.find(" > ul > li").clone(!0),this._data.core.original_container_html.find("li").addBack().contents().filter(function(){return 3===this.nodeType&&(!this.nodeValue||/^\s+$/.test(this.nodeValue))}).remove(),this.element.html(""),this.element.attr("aria-activedescendant","j"+this._id+"_loading"),this._data.core.li_height=this.get_container_ul().children("li").first().outerHeight()||24,this._data.core.node=this._create_prototype_node(),this.trigger("loading"),this.load_node(a.jstree.root)},destroy:function(a){if(this.trigger("destroy"),this._wrk)try{window.URL.revokeObjectURL(this._wrk),this._wrk=null}catch(b){}a||this.element.empty(),this.teardown()},_create_prototype_node:function(){var a=i.createElement("LI"),b,c;return a.setAttribute("role","treeitem"),b=i.createElement("I"),b.className="jstree-icon jstree-ocl",b.setAttribute("role","presentation"),a.appendChild(b),b=i.createElement("A"),b.className="jstree-anchor",b.setAttribute("href","#"),b.setAttribute("tabindex","-1"),c=i.createElement("I"),c.className="jstree-icon jstree-themeicon",c.setAttribute("role","presentation"),b.appendChild(c),a.appendChild(b),b=c=null,a},_kbevent_to_func:function(a){var b={8:"Backspace",9:"Tab",13:"Return",19:"Pause",27:"Esc",32:"Space",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"Left",38:"Up",39:"Right",40:"Down",44:"Print",45:"Insert",46:"Delete",96:"Numpad0",97:"Numpad1",98:"Numpad2",99:"Numpad3",100:"Numpad4",101:"Numpad5",102:"Numpad6",103:"Numpad7",104:"Numpad8",105:"Numpad9","-13":"NumpadEnter",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"Numlock",145:"Scrolllock",16:"Shift",17:"Ctrl",18:"Alt",48:"0",49:"1",50:"2",51:"3",52:"4",53:"5",54:"6",55:"7",56:"8",57:"9",59:";",61:"=",65:"a",66:"b",67:"c",68:"d",69:"e",70:"f",71:"g",72:"h",73:"i",74:"j",75:"k",76:"l",77:"m",78:"n",79:"o",80:"p",81:"q",82:"r",83:"s",84:"t",85:"u",86:"v",87:"w",88:"x",89:"y",90:"z",107:"+",109:"-",110:".",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'",111:"/",106:"*",173:"-"},c=[];a.ctrlKey&&c.push("ctrl"),a.altKey&&c.push("alt"),a.shiftKey&&c.push("shift"),c.push(b[a.which]||a.which),c=c.sort().join("-").toLowerCase();var d=this.settings.core.keyboard,e,f;for(e in d)if(d.hasOwnProperty(e)&&(f=e,"-"!==f&&"+"!==f&&(f=f.replace("--","-MINUS").replace("+-","-MINUS").replace("++","-PLUS").replace("-+","-PLUS"),f=f.split(/-|\+/).sort().join("-").replace("MINUS","-").replace("PLUS","+").toLowerCase()),f===c))return d[e];return null},teardown:function(){this.unbind(),this.element.removeClass("jstree").removeData("jstree").find("[class^='jstree']").addBack().attr("class",function(){return this.className.replace(/jstree[^ ]*|$/gi,"")}),this.element=null},bind:function(){var b="",c=null,d=0;this.element.on("dblclick.jstree",function(a){if(a.target.tagName&&"input"===a.target.tagName.toLowerCase())return!0;if(i.selection&&i.selection.empty)i.selection.empty();else if(window.getSelection){var b=window.getSelection();try{b.removeAllRanges(),b.collapse()}catch(c){}}}).on("mousedown.jstree",a.proxy(function(a){a.target===this.element[0]&&(a.preventDefault(),d=+new Date)},this)).on("mousedown.jstree",".jstree-ocl",function(a){a.preventDefault()}).on("click.jstree",".jstree-ocl",a.proxy(function(a){this.toggle_node(a.target)},this)).on("dblclick.jstree",".jstree-anchor",a.proxy(function(a){return a.target.tagName&&"input"===a.target.tagName.toLowerCase()?!0:void(this.settings.core.dblclick_toggle&&this.toggle_node(a.target))},this)).on("click.jstree",".jstree-anchor",a.proxy(function(b){b.preventDefault(),b.currentTarget!==i.activeElement&&a(b.currentTarget).focus(),this.activate_node(b.currentTarget,b)},this)).on("keydown.jstree",".jstree-anchor",a.proxy(function(a){if(a.target.tagName&&"input"===a.target.tagName.toLowerCase())return!0;this._data.core.rtl&&(37===a.which?a.which=39:39===a.which&&(a.which=37));var b=this._kbevent_to_func(a);if(b){var c=b.call(this,a);if(c===!1||c===!0)return c}},this)).on("load_node.jstree",a.proxy(function(b,c){c.status&&(c.node.id!==a.jstree.root||this._data.core.loaded||(this._data.core.loaded=!0,this._firstChild(this.get_container_ul()[0])&&this.element.attr("aria-activedescendant",this._firstChild(this.get_container_ul()[0]).id),this.trigger("loaded")),this._data.core.ready||setTimeout(a.proxy(function(){if(this.element&&!this.get_container_ul().find(".jstree-loading").length){if(this._data.core.ready=!0,this._data.core.selected.length){if(this.settings.core.expand_selected_onload){var b=[],c,d;for(c=0,d=this._data.core.selected.length;d>c;c++)b=b.concat(this._model.data[this._data.core.selected[c]].parents);for(b=a.vakata.array_unique(b),c=0,d=b.length;d>c;c++)this.open_node(b[c],!1,0)}this.trigger("changed",{action:"ready",selected:this._data.core.selected})}this.trigger("ready")}},this),0))},this)).on("keypress.jstree",a.proxy(function(d){if(d.target.tagName&&"input"===d.target.tagName.toLowerCase())return!0;c&&clearTimeout(c),c=setTimeout(function(){b=""},500);var e=String.fromCharCode(d.which).toLowerCase(),f=this.element.find(".jstree-anchor").filter(":visible"),g=f.index(i.activeElement)||0,h=!1;if(b+=e,b.length>1){if(f.slice(g).each(a.proxy(function(c,d){return 0===a(d).text().toLowerCase().indexOf(b)?(a(d).focus(),h=!0,!1):void 0},this)),h)return;if(f.slice(0,g).each(a.proxy(function(c,d){return 0===a(d).text().toLowerCase().indexOf(b)?(a(d).focus(),h=!0,!1):void 0},this)),h)return}if(new RegExp("^"+e.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")+"+$").test(b)){if(f.slice(g+1).each(a.proxy(function(b,c){return a(c).text().toLowerCase().charAt(0)===e?(a(c).focus(),h=!0,!1):void 0},this)),h)return;if(f.slice(0,g+1).each(a.proxy(function(b,c){return a(c).text().toLowerCase().charAt(0)===e?(a(c).focus(),h=!0,!1):void 0},this)),h)return}},this)).on("init.jstree",a.proxy(function(){var a=this.settings.core.themes;this._data.core.themes.dots=a.dots,this._data.core.themes.stripes=a.stripes,this._data.core.themes.icons=a.icons,this._data.core.themes.ellipsis=a.ellipsis,this.set_theme(a.name||"default",a.url),this.set_theme_variant(a.variant)},this)).on("loading.jstree",a.proxy(function(){this[this._data.core.themes.dots?"show_dots":"hide_dots"](),this[this._data.core.themes.icons?"show_icons":"hide_icons"](),this[this._data.core.themes.stripes?"show_stripes":"hide_stripes"](),this[this._data.core.themes.ellipsis?"show_ellipsis":"hide_ellipsis"]()},this)).on("blur.jstree",".jstree-anchor",a.proxy(function(b){this._data.core.focused=null,a(b.currentTarget).filter(".jstree-hovered").mouseleave(),this.element.attr("tabindex","0")},this)).on("focus.jstree",".jstree-anchor",a.proxy(function(b){var c=this.get_node(b.currentTarget);c&&c.id&&(this._data.core.focused=c.id),this.element.find(".jstree-hovered").not(b.currentTarget).mouseleave(),a(b.currentTarget).mouseenter(),this.element.attr("tabindex","-1")},this)).on("focus.jstree",a.proxy(function(){if(+new Date-d>500&&!this._data.core.focused&&this.settings.core.restore_focus){d=0;var a=this.get_node(this.element.attr("aria-activedescendant"),!0);a&&a.find("> .jstree-anchor").focus()}},this)).on("mouseenter.jstree",".jstree-anchor",a.proxy(function(a){this.hover_node(a.currentTarget)},this)).on("mouseleave.jstree",".jstree-anchor",a.proxy(function(a){this.dehover_node(a.currentTarget)},this))},unbind:function(){this.element.off(".jstree"),a(i).off(".jstree-"+this._id)},trigger:function(a,b){b||(b={}),b.instance=this,this.element.triggerHandler(a.replace(".jstree","")+".jstree",b)},get_container:function(){return this.element},get_container_ul:function(){return this.element.children(".jstree-children").first()},get_string:function(b){var c=this.settings.core.strings;return a.isFunction(c)?c.call(this,b):c&&c[b]?c[b]:b},_firstChild:function(a){a=a?a.firstChild:null;while(null!==a&&1!==a.nodeType)a=a.nextSibling;return a},_nextSibling:function(a){a=a?a.nextSibling:null;while(null!==a&&1!==a.nodeType)a=a.nextSibling;return a},_previousSibling:function(a){a=a?a.previousSibling:null;while(null!==a&&1!==a.nodeType)a=a.previousSibling;return a},get_node:function(b,c){b&&b.id&&(b=b.id),b instanceof jQuery&&b.length&&b[0].id&&(b=b[0].id);var d;try{if(this._model.data[b])b=this._model.data[b];else if("string"==typeof b&&this._model.data[b.replace(/^#/,"")])b=this._model.data[b.replace(/^#/,"")];else if("string"==typeof b&&(d=a("#"+b.replace(a.jstree.idregex,"\\$&"),this.element)).length&&this._model.data[d.closest(".jstree-node").attr("id")])b=this._model.data[d.closest(".jstree-node").attr("id")];else if((d=this.element.find(b)).length&&this._model.data[d.closest(".jstree-node").attr("id")])b=this._model.data[d.closest(".jstree-node").attr("id")];else{if(!(d=this.element.find(b)).length||!d.hasClass("jstree"))return!1;b=this._model.data[a.jstree.root]}return c&&(b=b.id===a.jstree.root?this.element:a("#"+b.id.replace(a.jstree.idregex,"\\$&"),this.element)),b}catch(e){return!1}},get_path:function(b,c,d){if(b=b.parents?b:this.get_node(b),!b||b.id===a.jstree.root||!b.parents)return!1;var e,f,g=[];for(g.push(d?b.id:b.text),e=0,f=b.parents.length;f>e;e++)g.push(d?b.parents[e]:this.get_text(b.parents[e]));return g=g.reverse().slice(1),c?g.join(c):g},get_next_dom:function(b,c){var d;if(b=this.get_node(b,!0),b[0]===this.element[0]){d=this._firstChild(this.get_container_ul()[0]);while(d&&0===d.offsetHeight)d=this._nextSibling(d);return d?a(d):!1}if(!b||!b.length)return!1;if(c){d=b[0];do d=this._nextSibling(d);while(d&&0===d.offsetHeight);return d?a(d):!1}if(b.hasClass("jstree-open")){d=this._firstChild(b.children(".jstree-children")[0]);while(d&&0===d.offsetHeight)d=this._nextSibling(d);if(null!==d)return a(d)}d=b[0];do d=this._nextSibling(d);while(d&&0===d.offsetHeight);return null!==d?a(d):b.parentsUntil(".jstree",".jstree-node").nextAll(".jstree-node:visible").first()},get_prev_dom:function(b,c){var d;if(b=this.get_node(b,!0),b[0]===this.element[0]){d=this.get_container_ul()[0].lastChild;while(d&&0===d.offsetHeight)d=this._previousSibling(d);return d?a(d):!1}if(!b||!b.length)return!1;if(c){d=b[0];do d=this._previousSibling(d);while(d&&0===d.offsetHeight);return d?a(d):!1}d=b[0];do d=this._previousSibling(d);while(d&&0===d.offsetHeight);if(null!==d){b=a(d);while(b.hasClass("jstree-open"))b=b.children(".jstree-children").first().children(".jstree-node:visible:last");return b}return d=b[0].parentNode.parentNode,d&&d.className&&-1!==d.className.indexOf("jstree-node")?a(d):!1},get_parent:function(b){return b=this.get_node(b),b&&b.id!==a.jstree.root?b.parent:!1},get_children_dom:function(a){return a=this.get_node(a,!0),a[0]===this.element[0]?this.get_container_ul().children(".jstree-node"):a&&a.length?a.children(".jstree-children").children(".jstree-node"):!1},is_parent:function(a){return a=this.get_node(a),a&&(a.state.loaded===!1||a.children.length>0)},is_loaded:function(a){return a=this.get_node(a),a&&a.state.loaded},is_loading:function(a){return a=this.get_node(a),a&&a.state&&a.state.loading},is_open:function(a){return a=this.get_node(a),a&&a.state.opened},is_closed:function(a){return a=this.get_node(a),a&&this.is_parent(a)&&!a.state.opened},is_leaf:function(a){return!this.is_parent(a)},load_node:function(b,c){var d,e,f,g,h;if(a.isArray(b))return this._load_nodes(b.slice(),c),!0;if(b=this.get_node(b),!b)return c&&c.call(this,b,!1),!1;if(b.state.loaded){for(b.state.loaded=!1,f=0,g=b.parents.length;g>f;f++)this._model.data[b.parents[f]].children_d=a.vakata.array_filter(this._model.data[b.parents[f]].children_d,function(c){return-1===a.inArray(c,b.children_d)});for(d=0,e=b.children_d.length;e>d;d++)this._model.data[b.children_d[d]].state.selected&&(h=!0),delete this._model.data[b.children_d[d]];h&&(this._data.core.selected=a.vakata.array_filter(this._data.core.selected,function(c){return-1===a.inArray(c,b.children_d)})),b.children=[],b.children_d=[],h&&this.trigger("changed",{action:"load_node",node:b,selected:this._data.core.selected})}return b.state.failed=!1,b.state.loading=!0,this.get_node(b,!0).addClass("jstree-loading").attr("aria-busy",!0),this._load_node(b,a.proxy(function(a){b=this._model.data[b.id],b.state.loading=!1,b.state.loaded=a,b.state.failed=!b.state.loaded;var d=this.get_node(b,!0),e=0,f=0,g=this._model.data,h=!1;for(e=0,f=b.children.length;f>e;e++)if(g[b.children[e]]&&!g[b.children[e]].state.hidden){h=!0;break}b.state.loaded&&d&&d.length&&(d.removeClass("jstree-closed jstree-open jstree-leaf"),h?"#"!==b.id&&d.addClass(b.state.opened?"jstree-open":"jstree-closed"):d.addClass("jstree-leaf")),d.removeClass("jstree-loading").attr("aria-busy",!1),this.trigger("load_node",{node:b,status:a}),c&&c.call(this,b,a)},this)),!0},_load_nodes:function(a,b,c,d){var e=!0,f=function(){this._load_nodes(a,b,!0)},g=this._model.data,h,i,j=[];for(h=0,i=a.length;i>h;h++)g[a[h]]&&(!g[a[h]].state.loaded&&!g[a[h]].state.failed||!c&&d)&&(this.is_loading(a[h])||this.load_node(a[h],f),e=!1);if(e){for(h=0,i=a.length;i>h;h++)g[a[h]]&&g[a[h]].state.loaded&&j.push(a[h]);b&&!b.done&&(b.call(this,j),b.done=!0)}},load_all:function(b,c){if(b||(b=a.jstree.root),b=this.get_node(b),!b)return!1;var d=[],e=this._model.data,f=e[b.id].children_d,g,h;for(b.state&&!b.state.loaded&&d.push(b.id),g=0,h=f.length;h>g;g++)e[f[g]]&&e[f[g]].state&&!e[f[g]].state.loaded&&d.push(f[g]);d.length?this._load_nodes(d,function(){this.load_all(b,c)}):(c&&c.call(this,b),this.trigger("load_all",{node:b}))},_load_node:function(b,c){var d=this.settings.core.data,e,f=function g(){return 3!==this.nodeType&&8!==this.nodeType};return d?a.isFunction(d)?d.call(this,b,a.proxy(function(d){d===!1?c.call(this,!1):this["string"==typeof d?"_append_html_data":"_append_json_data"](b,"string"==typeof d?a(a.parseHTML(d)).filter(f):d,function(a){c.call(this,a)})},this)):"object"==typeof d?d.url?(d=a.extend(!0,{},d),a.isFunction(d.url)&&(d.url=d.url.call(this,b)),a.isFunction(d.data)&&(d.data=d.data.call(this,b)),a.ajax(d).done(a.proxy(function(d,e,g){var h=g.getResponseHeader("Content-Type");return h&&-1!==h.indexOf("json")||"object"==typeof d?this._append_json_data(b,d,function(a){c.call(this,a)}):h&&-1!==h.indexOf("html")||"string"==typeof d?this._append_html_data(b,a(a.parseHTML(d)).filter(f),function(a){c.call(this,a)}):(this._data.core.last_error={error:"ajax",plugin:"core",id:"core_04",reason:"Could not load node",data:JSON.stringify({id:b.id,xhr:g})},this.settings.core.error.call(this,this._data.core.last_error),c.call(this,!1))},this)).fail(a.proxy(function(a){this._data.core.last_error={error:"ajax",plugin:"core",id:"core_04",reason:"Could not load node",data:JSON.stringify({id:b.id,xhr:a})},c.call(this,!1),this.settings.core.error.call(this,this._data.core.last_error)},this))):(e=a.isArray(d)?a.extend(!0,[],d):a.isPlainObject(d)?a.extend(!0,{},d):d,b.id===a.jstree.root?this._append_json_data(b,e,function(a){c.call(this,a)}):(this._data.core.last_error={error:"nodata",plugin:"core",id:"core_05",reason:"Could not load node",data:JSON.stringify({id:b.id})},this.settings.core.error.call(this,this._data.core.last_error),c.call(this,!1))):"string"==typeof d?b.id===a.jstree.root?this._append_html_data(b,a(a.parseHTML(d)).filter(f),function(a){c.call(this,a)}):(this._data.core.last_error={error:"nodata",plugin:"core",id:"core_06",reason:"Could not load node",data:JSON.stringify({id:b.id})},this.settings.core.error.call(this,this._data.core.last_error),c.call(this,!1)):c.call(this,!1):b.id===a.jstree.root?this._append_html_data(b,this._data.core.original_container_html.clone(!0),function(a){c.call(this,a)}):c.call(this,!1)},_node_changed:function(b){b=this.get_node(b),b&&-1===a.inArray(b.id,this._model.changed)&&this._model.changed.push(b.id)},_append_html_data:function(b,c,d){b=this.get_node(b),b.children=[],b.children_d=[];var e=c.is("ul")?c.children():c,f=b.id,g=[],h=[],i=this._model.data,j=i[f],k=this._data.core.selected.length,l,m,n;for(e.each(a.proxy(function(b,c){l=this._parse_model_from_html(a(c),f,j.parents.concat()),l&&(g.push(l),h.push(l),i[l].children_d.length&&(h=h.concat(i[l].children_d)))},this)),j.children=g,j.children_d=h,m=0,n=j.parents.length;n>m;m++)i[j.parents[m]].children_d=i[j.parents[m]].children_d.concat(h);this.trigger("model",{nodes:h,parent:f}),f!==a.jstree.root?(this._node_changed(f),this.redraw()):(this.get_container_ul().children(".jstree-initial-node").remove(),this.redraw(!0)),this._data.core.selected.length!==k&&this.trigger("changed",{action:"model",selected:this._data.core.selected}),d.call(this,!0)},_append_json_data:function(b,c,d,e){if(null!==this.element){b=this.get_node(b),b.children=[],b.children_d=[],c.d&&(c=c.d,"string"==typeof c&&(c=JSON.parse(c))),a.isArray(c)||(c=[c]);var f=null,g={df:this._model.default_state,dat:c,par:b.id,m:this._model.data,t_id:this._id,t_cnt:this._cnt,sel:this._data.core.selected},h=function(a,b){a.data&&(a=a.data);var c=a.dat,d=a.par,e=[],f=[],g=[],h=a.df,i=a.t_id,j=a.t_cnt,k=a.m,l=k[d],m=a.sel,n,o,p,q,r=function(a,c,d){d=d?d.concat():[],c&&d.unshift(c);var e=a.id.toString(),f,i,j,l,m={id:e,text:a.text||"",icon:a.icon!==b?a.icon:!0,parent:c,parents:d,children:a.children||[],children_d:a.children_d||[],data:a.data,state:{},li_attr:{id:!1},a_attr:{href:"#"},original:!1};for(f in h)h.hasOwnProperty(f)&&(m.state[f]=h[f]);if(a&&a.data&&a.data.jstree&&a.data.jstree.icon&&(m.icon=a.data.jstree.icon),(m.icon===b||null===m.icon||""===m.icon)&&(m.icon=!0),a&&a.data&&(m.data=a.data,a.data.jstree))for(f in a.data.jstree)a.data.jstree.hasOwnProperty(f)&&(m.state[f]=a.data.jstree[f]);if(a&&"object"==typeof a.state)for(f in a.state)a.state.hasOwnProperty(f)&&(m.state[f]=a.state[f]);if(a&&"object"==typeof a.li_attr)for(f in a.li_attr)a.li_attr.hasOwnProperty(f)&&(m.li_attr[f]=a.li_attr[f]);if(m.li_attr.id||(m.li_attr.id=e),a&&"object"==typeof a.a_attr)for(f in a.a_attr)a.a_attr.hasOwnProperty(f)&&(m.a_attr[f]=a.a_attr[f]);for(a&&a.children&&a.children===!0&&(m.state.loaded=!1,m.children=[],m.children_d=[]),k[m.id]=m,f=0,i=m.children.length;i>f;f++)j=r(k[m.children[f]],m.id,d),l=k[j],m.children_d.push(j),l.children_d.length&&(m.children_d=m.children_d.concat(l.children_d));return delete a.data,delete a.children,k[m.id].original=a,m.state.selected&&g.push(m.id),m.id},s=function(a,c,d){d=d?d.concat():[],c&&d.unshift(c);var e=!1,f,l,m,n,o;do e="j"+i+"_"+ ++j;while(k[e]);o={id:!1,text:"string"==typeof a?a:"",icon:"object"==typeof a&&a.icon!==b?a.icon:!0,parent:c,parents:d,children:[],children_d:[],data:null,state:{},li_attr:{id:!1},a_attr:{href:"#"},original:!1};for(f in h)h.hasOwnProperty(f)&&(o.state[f]=h[f]);if(a&&a.id&&(o.id=a.id.toString()),a&&a.text&&(o.text=a.text),a&&a.data&&a.data.jstree&&a.data.jstree.icon&&(o.icon=a.data.jstree.icon),(o.icon===b||null===o.icon||""===o.icon)&&(o.icon=!0),a&&a.data&&(o.data=a.data,a.data.jstree))for(f in a.data.jstree)a.data.jstree.hasOwnProperty(f)&&(o.state[f]=a.data.jstree[f]);if(a&&"object"==typeof a.state)for(f in a.state)a.state.hasOwnProperty(f)&&(o.state[f]=a.state[f]);if(a&&"object"==typeof a.li_attr)for(f in a.li_attr)a.li_attr.hasOwnProperty(f)&&(o.li_attr[f]=a.li_attr[f]);if(o.li_attr.id&&!o.id&&(o.id=o.li_attr.id.toString()),o.id||(o.id=e),o.li_attr.id||(o.li_attr.id=o.id),a&&"object"==typeof a.a_attr)for(f in a.a_attr)a.a_attr.hasOwnProperty(f)&&(o.a_attr[f]=a.a_attr[f]);if(a&&a.children&&a.children.length){for(f=0,l=a.children.length;l>f;f++)m=s(a.children[f],o.id,d),n=k[m],o.children.push(m),n.children_d.length&&(o.children_d=o.children_d.concat(n.children_d));o.children_d=o.children_d.concat(o.children)}return a&&a.children&&a.children===!0&&(o.state.loaded=!1,o.children=[],o.children_d=[]),delete a.data,delete a.children,o.original=a,k[o.id]=o,o.state.selected&&g.push(o.id),o.id};if(c.length&&c[0].id!==b&&c[0].parent!==b){for(o=0,p=c.length;p>o;o++)c[o].children||(c[o].children=[]),c[o].state||(c[o].state={}),k[c[o].id.toString()]=c[o];for(o=0,p=c.length;p>o;o++)k[c[o].parent.toString()]?(k[c[o].parent.toString()].children.push(c[o].id.toString()),l.children_d.push(c[o].id.toString())):(this._data.core.last_error={error:"parse",plugin:"core",id:"core_07",reason:"Node with invalid parent",data:JSON.stringify({id:c[o].id.toString(),parent:c[o].parent.toString()})},this.settings.core.error.call(this,this._data.core.last_error));for(o=0,p=l.children.length;p>o;o++)n=r(k[l.children[o]],d,l.parents.concat()),f.push(n),k[n].children_d.length&&(f=f.concat(k[n].children_d));for(o=0,p=l.parents.length;p>o;o++)k[l.parents[o]].children_d=k[l.parents[o]].children_d.concat(f);q={cnt:j,mod:k,sel:m,par:d,dpc:f,add:g}}else{for(o=0,p=c.length;p>o;o++)n=s(c[o],d,l.parents.concat()),n&&(e.push(n),f.push(n),k[n].children_d.length&&(f=f.concat(k[n].children_d)));for(l.children=e,l.children_d=f,o=0,p=l.parents.length;p>o;o++)k[l.parents[o]].children_d=k[l.parents[o]].children_d.concat(f);q={cnt:j,mod:k,sel:m,par:d,dpc:f,add:g}}return"undefined"!=typeof window&&"undefined"!=typeof window.document?q:void postMessage(q)},i=function(b,c){if(null!==this.element){this._cnt=b.cnt;var e,f=this._model.data;for(e in f)f.hasOwnProperty(e)&&f[e].state&&f[e].state.loading&&b.mod[e]&&(b.mod[e].state.loading=!0);if(this._model.data=b.mod,c){var g,h=b.add,i=b.sel,j=this._data.core.selected.slice();if(f=this._model.data,i.length!==j.length||a.vakata.array_unique(i.concat(j)).length!==i.length){for(e=0,g=i.length;g>e;e++)-1===a.inArray(i[e],h)&&-1===a.inArray(i[e],j)&&(f[i[e]].state.selected=!1);for(e=0,g=j.length;g>e;e++)-1===a.inArray(j[e],i)&&(f[j[e]].state.selected=!0)}}b.add.length&&(this._data.core.selected=this._data.core.selected.concat(b.add)),this.trigger("model",{nodes:b.dpc,parent:b.par}),b.par!==a.jstree.root?(this._node_changed(b.par),this.redraw()):this.redraw(!0),b.add.length&&this.trigger("changed",{action:"model",selected:this._data.core.selected}),d.call(this,!0)}};if(this.settings.core.worker&&window.Blob&&window.URL&&window.Worker)try{null===this._wrk&&(this._wrk=window.URL.createObjectURL(new window.Blob(["self.onmessage = "+h.toString()],{type:"text/javascript"}))),!this._data.core.working||e?(this._data.core.working=!0,f=new window.Worker(this._wrk),f.onmessage=a.proxy(function(a){i.call(this,a.data,!0);try{f.terminate(),f=null}catch(b){}this._data.core.worker_queue.length?this._append_json_data.apply(this,this._data.core.worker_queue.shift()):this._data.core.working=!1},this),g.par?f.postMessage(g):this._data.core.worker_queue.length?this._append_json_data.apply(this,this._data.core.worker_queue.shift()):this._data.core.working=!1):this._data.core.worker_queue.push([b,c,d,!0])}catch(j){i.call(this,h(g),!1),this._data.core.worker_queue.length?this._append_json_data.apply(this,this._data.core.worker_queue.shift()):this._data.core.working=!1}else i.call(this,h(g),!1)}},_parse_model_from_html:function(c,d,e){e=e?[].concat(e):[],d&&e.unshift(d);var f,g,h=this._model.data,i={id:!1,text:!1,icon:!0,parent:d,parents:e,children:[],children_d:[],data:null,state:{},li_attr:{id:!1},a_attr:{href:"#"},original:!1},j,k,l;for(j in this._model.default_state)this._model.default_state.hasOwnProperty(j)&&(i.state[j]=this._model.default_state[j]);if(k=a.vakata.attributes(c,!0),a.each(k,function(b,c){return c=a.trim(c),c.length?(i.li_attr[b]=c,void("id"===b&&(i.id=c.toString()))):!0}),k=c.children("a").first(),k.length&&(k=a.vakata.attributes(k,!0),a.each(k,function(b,c){c=a.trim(c),c.length&&(i.a_attr[b]=c)})),k=c.children("a").first().length?c.children("a").first().clone():c.clone(),k.children("ins, i, ul").remove(),k=k.html(),k=a("
").html(k),i.text=this.settings.core.force_text?k.text():k.html(),k=c.data(),i.data=k?a.extend(!0,{},k):null,i.state.opened=c.hasClass("jstree-open"),i.state.selected=c.children("a").hasClass("jstree-clicked"),i.state.disabled=c.children("a").hasClass("jstree-disabled"),i.data&&i.data.jstree)for(j in i.data.jstree)i.data.jstree.hasOwnProperty(j)&&(i.state[j]=i.data.jstree[j]);k=c.children("a").children(".jstree-themeicon"),k.length&&(i.icon=k.hasClass("jstree-themeicon-hidden")?!1:k.attr("rel")),i.state.icon!==b&&(i.icon=i.state.icon),(i.icon===b||null===i.icon||""===i.icon)&&(i.icon=!0),k=c.children("ul").children("li");do l="j"+this._id+"_"+ ++this._cnt;while(h[l]);return i.id=i.li_attr.id?i.li_attr.id.toString():l,k.length?(k.each(a.proxy(function(b,c){f=this._parse_model_from_html(a(c),i.id,e),g=this._model.data[f],i.children.push(f),g.children_d.length&&(i.children_d=i.children_d.concat(g.children_d))},this)),i.children_d=i.children_d.concat(i.children)):c.hasClass("jstree-closed")&&(i.state.loaded=!1),i.li_attr["class"]&&(i.li_attr["class"]=i.li_attr["class"].replace("jstree-closed","").replace("jstree-open","")),i.a_attr["class"]&&(i.a_attr["class"]=i.a_attr["class"].replace("jstree-clicked","").replace("jstree-disabled","")),h[i.id]=i,i.state.selected&&this._data.core.selected.push(i.id),i.id},_parse_model_from_flat_json:function(a,c,d){d=d?d.concat():[],c&&d.unshift(c);var e=a.id.toString(),f=this._model.data,g=this._model.default_state,h,i,j,k,l={id:e,text:a.text||"",icon:a.icon!==b?a.icon:!0,parent:c,parents:d,children:a.children||[],children_d:a.children_d||[],data:a.data,state:{},li_attr:{id:!1},a_attr:{href:"#"},original:!1};for(h in g)g.hasOwnProperty(h)&&(l.state[h]=g[h]);if(a&&a.data&&a.data.jstree&&a.data.jstree.icon&&(l.icon=a.data.jstree.icon),(l.icon===b||null===l.icon||""===l.icon)&&(l.icon=!0),a&&a.data&&(l.data=a.data,a.data.jstree))for(h in a.data.jstree)a.data.jstree.hasOwnProperty(h)&&(l.state[h]=a.data.jstree[h]);if(a&&"object"==typeof a.state)for(h in a.state)a.state.hasOwnProperty(h)&&(l.state[h]=a.state[h]);if(a&&"object"==typeof a.li_attr)for(h in a.li_attr)a.li_attr.hasOwnProperty(h)&&(l.li_attr[h]=a.li_attr[h]);if(l.li_attr.id||(l.li_attr.id=e),a&&"object"==typeof a.a_attr)for(h in a.a_attr)a.a_attr.hasOwnProperty(h)&&(l.a_attr[h]=a.a_attr[h]);for(a&&a.children&&a.children===!0&&(l.state.loaded=!1,l.children=[],l.children_d=[]),f[l.id]=l,h=0,i=l.children.length;i>h;h++)j=this._parse_model_from_flat_json(f[l.children[h]],l.id,d),k=f[j],l.children_d.push(j),k.children_d.length&&(l.children_d=l.children_d.concat(k.children_d));return delete a.data,delete a.children,f[l.id].original=a,l.state.selected&&this._data.core.selected.push(l.id),l.id},_parse_model_from_json:function(a,c,d){d=d?d.concat():[],c&&d.unshift(c);var e=!1,f,g,h,i,j=this._model.data,k=this._model.default_state,l;do e="j"+this._id+"_"+ ++this._cnt;while(j[e]);l={id:!1,text:"string"==typeof a?a:"",icon:"object"==typeof a&&a.icon!==b?a.icon:!0,parent:c,parents:d,children:[],children_d:[],data:null,state:{},li_attr:{id:!1},a_attr:{href:"#"},original:!1};for(f in k)k.hasOwnProperty(f)&&(l.state[f]=k[f]);if(a&&a.id&&(l.id=a.id.toString()),a&&a.text&&(l.text=a.text),a&&a.data&&a.data.jstree&&a.data.jstree.icon&&(l.icon=a.data.jstree.icon),(l.icon===b||null===l.icon||""===l.icon)&&(l.icon=!0),a&&a.data&&(l.data=a.data,a.data.jstree))for(f in a.data.jstree)a.data.jstree.hasOwnProperty(f)&&(l.state[f]=a.data.jstree[f]);if(a&&"object"==typeof a.state)for(f in a.state)a.state.hasOwnProperty(f)&&(l.state[f]=a.state[f]);if(a&&"object"==typeof a.li_attr)for(f in a.li_attr)a.li_attr.hasOwnProperty(f)&&(l.li_attr[f]=a.li_attr[f]);if(l.li_attr.id&&!l.id&&(l.id=l.li_attr.id.toString()), -l.id||(l.id=e),l.li_attr.id||(l.li_attr.id=l.id),a&&"object"==typeof a.a_attr)for(f in a.a_attr)a.a_attr.hasOwnProperty(f)&&(l.a_attr[f]=a.a_attr[f]);if(a&&a.children&&a.children.length){for(f=0,g=a.children.length;g>f;f++)h=this._parse_model_from_json(a.children[f],l.id,d),i=j[h],l.children.push(h),i.children_d.length&&(l.children_d=l.children_d.concat(i.children_d));l.children_d=l.children_d.concat(l.children)}return a&&a.children&&a.children===!0&&(l.state.loaded=!1,l.children=[],l.children_d=[]),delete a.data,delete a.children,l.original=a,j[l.id]=l,l.state.selected&&this._data.core.selected.push(l.id),l.id},_redraw:function(){var b=this._model.force_full_redraw?this._model.data[a.jstree.root].children.concat([]):this._model.changed.concat([]),c=i.createElement("UL"),d,e,f,g=this._data.core.focused;for(e=0,f=b.length;f>e;e++)d=this.redraw_node(b[e],!0,this._model.force_full_redraw),d&&this._model.force_full_redraw&&c.appendChild(d);this._model.force_full_redraw&&(c.className=this.get_container_ul()[0].className,c.setAttribute("role","group"),this.element.empty().append(c)),null!==g&&this.settings.core.restore_focus&&(d=this.get_node(g,!0),d&&d.length&&d.children(".jstree-anchor")[0]!==i.activeElement?d.children(".jstree-anchor").focus():this._data.core.focused=null),this._model.force_full_redraw=!1,this._model.changed=[],this.trigger("redraw",{nodes:b})},redraw:function(a){a&&(this._model.force_full_redraw=!0),this._redraw()},draw_children:function(b){var c=this.get_node(b),d=!1,e=!1,f=!1,g=i;if(!c)return!1;if(c.id===a.jstree.root)return this.redraw(!0);if(b=this.get_node(b,!0),!b||!b.length)return!1;if(b.children(".jstree-children").remove(),b=b[0],c.children.length&&c.state.loaded){for(f=g.createElement("UL"),f.setAttribute("role","group"),f.className="jstree-children",d=0,e=c.children.length;e>d;d++)f.appendChild(this.redraw_node(c.children[d],!0,!0));b.appendChild(f)}},redraw_node:function(b,c,d,e){var f=this.get_node(b),g=!1,h=!1,j=!1,k=!1,l=!1,m=!1,n="",o=i,p=this._model.data,q=!1,r=!1,s=null,t=0,u=0,v=!1,w=!1;if(!f)return!1;if(f.id===a.jstree.root)return this.redraw(!0);if(c=c||0===f.children.length,b=i.querySelector?this.element[0].querySelector("#"+(-1!=="0123456789".indexOf(f.id[0])?"\\3"+f.id[0]+" "+f.id.substr(1).replace(a.jstree.idregex,"\\$&"):f.id.replace(a.jstree.idregex,"\\$&"))):i.getElementById(f.id))b=a(b),d||(g=b.parent().parent()[0],g===this.element[0]&&(g=null),h=b.index()),c||!f.children.length||b.children(".jstree-children").length||(c=!0),c||(j=b.children(".jstree-children")[0]),q=b.children(".jstree-anchor")[0]===i.activeElement,b.remove();else if(c=!0,!d){if(g=f.parent!==a.jstree.root?a("#"+f.parent.replace(a.jstree.idregex,"\\$&"),this.element)[0]:null,!(null===g||g&&p[f.parent].state.opened))return!1;h=a.inArray(f.id,null===g?p[a.jstree.root].children:p[f.parent].children)}b=this._data.core.node.cloneNode(!0),n="jstree-node ";for(k in f.li_attr)if(f.li_attr.hasOwnProperty(k)){if("id"===k)continue;"class"!==k?b.setAttribute(k,f.li_attr[k]):n+=f.li_attr[k]}for(f.a_attr.id||(f.a_attr.id=f.id+"_anchor"),b.setAttribute("aria-selected",!!f.state.selected),b.setAttribute("aria-level",f.parents.length),b.setAttribute("aria-labelledby",f.a_attr.id),f.state.disabled&&b.setAttribute("aria-disabled",!0),k=0,l=f.children.length;l>k;k++)if(!p[f.children[k]].state.hidden){v=!0;break}if(null!==f.parent&&p[f.parent]&&!f.state.hidden&&(k=a.inArray(f.id,p[f.parent].children),w=f.id,-1!==k))for(k++,l=p[f.parent].children.length;l>k;k++)if(p[p[f.parent].children[k]].state.hidden||(w=p[f.parent].children[k]),w!==f.id)break;f.state.hidden&&(n+=" jstree-hidden"),f.state.loading&&(n+=" jstree-loading"),f.state.loaded&&!v?n+=" jstree-leaf":(n+=f.state.opened&&f.state.loaded?" jstree-open":" jstree-closed",b.setAttribute("aria-expanded",f.state.opened&&f.state.loaded)),w===f.id&&(n+=" jstree-last"),b.id=f.id,b.className=n,n=(f.state.selected?" jstree-clicked":"")+(f.state.disabled?" jstree-disabled":"");for(l in f.a_attr)if(f.a_attr.hasOwnProperty(l)){if("href"===l&&"#"===f.a_attr[l])continue;"class"!==l?b.childNodes[1].setAttribute(l,f.a_attr[l]):n+=" "+f.a_attr[l]}if(n.length&&(b.childNodes[1].className="jstree-anchor "+n),(f.icon&&f.icon!==!0||f.icon===!1)&&(f.icon===!1?b.childNodes[1].childNodes[0].className+=" jstree-themeicon-hidden":-1===f.icon.indexOf("/")&&-1===f.icon.indexOf(".")?b.childNodes[1].childNodes[0].className+=" "+f.icon+" jstree-themeicon-custom":(b.childNodes[1].childNodes[0].style.backgroundImage='url("'+f.icon+'")',b.childNodes[1].childNodes[0].style.backgroundPosition="center center",b.childNodes[1].childNodes[0].style.backgroundSize="auto",b.childNodes[1].childNodes[0].className+=" jstree-themeicon-custom")),this.settings.core.force_text?b.childNodes[1].appendChild(o.createTextNode(f.text)):b.childNodes[1].innerHTML+=f.text,c&&f.children.length&&(f.state.opened||e)&&f.state.loaded){for(m=o.createElement("UL"),m.setAttribute("role","group"),m.className="jstree-children",k=0,l=f.children.length;l>k;k++)m.appendChild(this.redraw_node(f.children[k],c,!0));b.appendChild(m)}if(j&&b.appendChild(j),!d){for(g||(g=this.element[0]),k=0,l=g.childNodes.length;l>k;k++)if(g.childNodes[k]&&g.childNodes[k].className&&-1!==g.childNodes[k].className.indexOf("jstree-children")){s=g.childNodes[k];break}s||(s=o.createElement("UL"),s.setAttribute("role","group"),s.className="jstree-children",g.appendChild(s)),g=s,hf;f++)this.open_node(c[f],d,e);return!0}return c=this.get_node(c),c&&c.id!==a.jstree.root?(e=e===b?this.settings.core.animation:e,this.is_closed(c)?this.is_loaded(c)?(h=this.get_node(c,!0),i=this,h.length&&(e&&h.children(".jstree-children").length&&h.children(".jstree-children").stop(!0,!0),c.children.length&&!this._firstChild(h.children(".jstree-children")[0])&&this.draw_children(c),e?(this.trigger("before_open",{node:c}),h.children(".jstree-children").css("display","none").end().removeClass("jstree-closed").addClass("jstree-open").attr("aria-expanded",!0).children(".jstree-children").stop(!0,!0).slideDown(e,function(){this.style.display="",i.element&&i.trigger("after_open",{node:c})})):(this.trigger("before_open",{node:c}),h[0].className=h[0].className.replace("jstree-closed","jstree-open"),h[0].setAttribute("aria-expanded",!0))),c.state.opened=!0,d&&d.call(this,c,!0),h.length||this.trigger("before_open",{node:c}),this.trigger("open_node",{node:c}),e&&h.length||this.trigger("after_open",{node:c}),!0):this.is_loading(c)?setTimeout(a.proxy(function(){this.open_node(c,d,e)},this),500):void this.load_node(c,function(a,b){return b?this.open_node(a,d,e):d?d.call(this,a,!1):!1}):(d&&d.call(this,c,!1),!1)):!1},_open_to:function(b){if(b=this.get_node(b),!b||b.id===a.jstree.root)return!1;var c,d,e=b.parents;for(c=0,d=e.length;d>c;c+=1)c!==a.jstree.root&&this.open_node(e[c],!1,0);return a("#"+b.id.replace(a.jstree.idregex,"\\$&"),this.element)},close_node:function(c,d){var e,f,g,h;if(a.isArray(c)){for(c=c.slice(),e=0,f=c.length;f>e;e++)this.close_node(c[e],d);return!0}return c=this.get_node(c),c&&c.id!==a.jstree.root?this.is_closed(c)?!1:(d=d===b?this.settings.core.animation:d,g=this,h=this.get_node(c,!0),c.state.opened=!1,this.trigger("close_node",{node:c}),void(h.length?d?h.children(".jstree-children").attr("style","display:block !important").end().removeClass("jstree-open").addClass("jstree-closed").attr("aria-expanded",!1).children(".jstree-children").stop(!0,!0).slideUp(d,function(){this.style.display="",h.children(".jstree-children").remove(),g.element&&g.trigger("after_close",{node:c})}):(h[0].className=h[0].className.replace("jstree-open","jstree-closed"),h.attr("aria-expanded",!1).children(".jstree-children").remove(),this.trigger("after_close",{node:c})):this.trigger("after_close",{node:c}))):!1},toggle_node:function(b){var c,d;if(a.isArray(b)){for(b=b.slice(),c=0,d=b.length;d>c;c++)this.toggle_node(b[c]);return!0}return this.is_closed(b)?this.open_node(b):this.is_open(b)?this.close_node(b):void 0},open_all:function(b,c,d){if(b||(b=a.jstree.root),b=this.get_node(b),!b)return!1;var e=b.id===a.jstree.root?this.get_container_ul():this.get_node(b,!0),f,g,h;if(!e.length){for(f=0,g=b.children_d.length;g>f;f++)this.is_closed(this._model.data[b.children_d[f]])&&(this._model.data[b.children_d[f]].state.opened=!0);return this.trigger("open_all",{node:b})}d=d||e,h=this,e=this.is_closed(b)?e.find(".jstree-closed").addBack():e.find(".jstree-closed"),e.each(function(){h.open_node(this,function(a,b){b&&this.is_parent(a)&&this.open_all(a,c,d)},c||0)}),0===d.find(".jstree-closed").length&&this.trigger("open_all",{node:this.get_node(d)})},close_all:function(b,c){if(b||(b=a.jstree.root),b=this.get_node(b),!b)return!1;var d=b.id===a.jstree.root?this.get_container_ul():this.get_node(b,!0),e=this,f,g;for(d.length&&(d=this.is_open(b)?d.find(".jstree-open").addBack():d.find(".jstree-open"),a(d.get().reverse()).each(function(){e.close_node(this,c||0)})),f=0,g=b.children_d.length;g>f;f++)this._model.data[b.children_d[f]].state.opened=!1;this.trigger("close_all",{node:b})},is_disabled:function(a){return a=this.get_node(a),a&&a.state&&a.state.disabled},enable_node:function(b){var c,d;if(a.isArray(b)){for(b=b.slice(),c=0,d=b.length;d>c;c++)this.enable_node(b[c]);return!0}return b=this.get_node(b),b&&b.id!==a.jstree.root?(b.state.disabled=!1,this.get_node(b,!0).children(".jstree-anchor").removeClass("jstree-disabled").attr("aria-disabled",!1),void this.trigger("enable_node",{node:b})):!1},disable_node:function(b){var c,d;if(a.isArray(b)){for(b=b.slice(),c=0,d=b.length;d>c;c++)this.disable_node(b[c]);return!0}return b=this.get_node(b),b&&b.id!==a.jstree.root?(b.state.disabled=!0,this.get_node(b,!0).children(".jstree-anchor").addClass("jstree-disabled").attr("aria-disabled",!0),void this.trigger("disable_node",{node:b})):!1},is_hidden:function(a){return a=this.get_node(a),a.state.hidden===!0},hide_node:function(b,c){var d,e;if(a.isArray(b)){for(b=b.slice(),d=0,e=b.length;e>d;d++)this.hide_node(b[d],!0);return c||this.redraw(),!0}return b=this.get_node(b),b&&b.id!==a.jstree.root?void(b.state.hidden||(b.state.hidden=!0,this._node_changed(b.parent),c||this.redraw(),this.trigger("hide_node",{node:b}))):!1},show_node:function(b,c){var d,e;if(a.isArray(b)){for(b=b.slice(),d=0,e=b.length;e>d;d++)this.show_node(b[d],!0);return c||this.redraw(),!0}return b=this.get_node(b),b&&b.id!==a.jstree.root?void(b.state.hidden&&(b.state.hidden=!1,this._node_changed(b.parent),c||this.redraw(),this.trigger("show_node",{node:b}))):!1},hide_all:function(b){var c,d=this._model.data,e=[];for(c in d)d.hasOwnProperty(c)&&c!==a.jstree.root&&!d[c].state.hidden&&(d[c].state.hidden=!0,e.push(c));return this._model.force_full_redraw=!0,b||this.redraw(),this.trigger("hide_all",{nodes:e}),e},show_all:function(b){var c,d=this._model.data,e=[];for(c in d)d.hasOwnProperty(c)&&c!==a.jstree.root&&d[c].state.hidden&&(d[c].state.hidden=!1,e.push(c));return this._model.force_full_redraw=!0,b||this.redraw(),this.trigger("show_all",{nodes:e}),e},activate_node:function(a,c){if(this.is_disabled(a))return!1;if(c&&"object"==typeof c||(c={}),this._data.core.last_clicked=this._data.core.last_clicked&&this._data.core.last_clicked.id!==b?this.get_node(this._data.core.last_clicked.id):null,this._data.core.last_clicked&&!this._data.core.last_clicked.state.selected&&(this._data.core.last_clicked=null),!this._data.core.last_clicked&&this._data.core.selected.length&&(this._data.core.last_clicked=this.get_node(this._data.core.selected[this._data.core.selected.length-1])),this.settings.core.multiple&&(c.metaKey||c.ctrlKey||c.shiftKey)&&(!c.shiftKey||this._data.core.last_clicked&&this.get_parent(a)&&this.get_parent(a)===this._data.core.last_clicked.parent))if(c.shiftKey){var d=this.get_node(a).id,e=this._data.core.last_clicked.id,f=this.get_node(this._data.core.last_clicked.parent).children,g=!1,h,i;for(h=0,i=f.length;i>h;h+=1)f[h]===d&&(g=!g),f[h]===e&&(g=!g),this.is_disabled(f[h])||!g&&f[h]!==d&&f[h]!==e?this.deselect_node(f[h],!0,c):this.is_hidden(f[h])||this.select_node(f[h],!0,!1,c);this.trigger("changed",{action:"select_node",node:this.get_node(a),selected:this._data.core.selected,event:c})}else this.is_selected(a)?this.deselect_node(a,!1,c):this.select_node(a,!1,!1,c);else!this.settings.core.multiple&&(c.metaKey||c.ctrlKey||c.shiftKey)&&this.is_selected(a)?this.deselect_node(a,!1,c):(this.deselect_all(!0),this.select_node(a,!1,!1,c),this._data.core.last_clicked=this.get_node(a));this.trigger("activate_node",{node:this.get_node(a),event:c})},hover_node:function(a){if(a=this.get_node(a,!0),!a||!a.length||a.children(".jstree-hovered").length)return!1;var b=this.element.find(".jstree-hovered"),c=this.element;b&&b.length&&this.dehover_node(b),a.children(".jstree-anchor").addClass("jstree-hovered"),this.trigger("hover_node",{node:this.get_node(a)}),setTimeout(function(){c.attr("aria-activedescendant",a[0].id)},0)},dehover_node:function(a){return a=this.get_node(a,!0),a&&a.length&&a.children(".jstree-hovered").length?(a.children(".jstree-anchor").removeClass("jstree-hovered"),void this.trigger("dehover_node",{node:this.get_node(a)})):!1},select_node:function(b,c,d,e){var f,g,h,i;if(a.isArray(b)){for(b=b.slice(),g=0,h=b.length;h>g;g++)this.select_node(b[g],c,d,e);return!0}return b=this.get_node(b),b&&b.id!==a.jstree.root?(f=this.get_node(b,!0),void(b.state.selected||(b.state.selected=!0,this._data.core.selected.push(b.id),d||(f=this._open_to(b)),f&&f.length&&f.attr("aria-selected",!0).children(".jstree-anchor").addClass("jstree-clicked"),this.trigger("select_node",{node:b,selected:this._data.core.selected,event:e}),c||this.trigger("changed",{action:"select_node",node:b,selected:this._data.core.selected,event:e})))):!1},deselect_node:function(b,c,d){var e,f,g;if(a.isArray(b)){for(b=b.slice(),e=0,f=b.length;f>e;e++)this.deselect_node(b[e],c,d);return!0}return b=this.get_node(b),b&&b.id!==a.jstree.root?(g=this.get_node(b,!0),void(b.state.selected&&(b.state.selected=!1,this._data.core.selected=a.vakata.array_remove_item(this._data.core.selected,b.id),g.length&&g.attr("aria-selected",!1).children(".jstree-anchor").removeClass("jstree-clicked"),this.trigger("deselect_node",{node:b,selected:this._data.core.selected,event:d}),c||this.trigger("changed",{action:"deselect_node",node:b,selected:this._data.core.selected,event:d})))):!1},select_all:function(b){var c=this._data.core.selected.concat([]),d,e;for(this._data.core.selected=this._model.data[a.jstree.root].children_d.concat(),d=0,e=this._data.core.selected.length;e>d;d++)this._model.data[this._data.core.selected[d]]&&(this._model.data[this._data.core.selected[d]].state.selected=!0);this.redraw(!0),this.trigger("select_all",{selected:this._data.core.selected}),b||this.trigger("changed",{action:"select_all",selected:this._data.core.selected,old_selection:c})},deselect_all:function(a){var b=this._data.core.selected.concat([]),c,d;for(c=0,d=this._data.core.selected.length;d>c;c++)this._model.data[this._data.core.selected[c]]&&(this._model.data[this._data.core.selected[c]].state.selected=!1);this._data.core.selected=[],this.element.find(".jstree-clicked").removeClass("jstree-clicked").parent().attr("aria-selected",!1),this.trigger("deselect_all",{selected:this._data.core.selected,node:b}),a||this.trigger("changed",{action:"deselect_all",selected:this._data.core.selected,old_selection:b})},is_selected:function(b){return b=this.get_node(b),b&&b.id!==a.jstree.root?b.state.selected:!1},get_selected:function(b){return b?a.map(this._data.core.selected,a.proxy(function(a){return this.get_node(a)},this)):this._data.core.selected.slice()},get_top_selected:function(b){var c=this.get_selected(!0),d={},e,f,g,h;for(e=0,f=c.length;f>e;e++)d[c[e].id]=c[e];for(e=0,f=c.length;f>e;e++)for(g=0,h=c[e].children_d.length;h>g;g++)d[c[e].children_d[g]]&&delete d[c[e].children_d[g]];c=[];for(e in d)d.hasOwnProperty(e)&&c.push(e);return b?a.map(c,a.proxy(function(a){return this.get_node(a)},this)):c},get_bottom_selected:function(b){var c=this.get_selected(!0),d=[],e,f;for(e=0,f=c.length;f>e;e++)c[e].children.length||d.push(c[e].id);return b?a.map(d,a.proxy(function(a){return this.get_node(a)},this)):d},get_state:function(){var b={core:{open:[],loaded:[],scroll:{left:this.element.scrollLeft(),top:this.element.scrollTop()},selected:[]}},c;for(c in this._model.data)this._model.data.hasOwnProperty(c)&&c!==a.jstree.root&&(this._model.data[c].state.loaded&&this.settings.core.loaded_state&&b.core.loaded.push(c),this._model.data[c].state.opened&&b.core.open.push(c),this._model.data[c].state.selected&&b.core.selected.push(c));return b},set_state:function(c,d){if(c){if(c.core&&c.core.selected&&c.core.initial_selection===b&&(c.core.initial_selection=this._data.core.selected.concat([]).sort().join(",")),c.core){var e,f,g,h,i;if(c.core.loaded)return this.settings.core.loaded_state&&a.isArray(c.core.loaded)&&c.core.loaded.length?this._load_nodes(c.core.loaded,function(a){delete c.core.loaded,this.set_state(c,d)}):(delete c.core.loaded,this.set_state(c,d)),!1;if(c.core.open)return a.isArray(c.core.open)&&c.core.open.length?this._load_nodes(c.core.open,function(a){this.open_node(a,!1,0),delete c.core.open,this.set_state(c,d)}):(delete c.core.open,this.set_state(c,d)),!1;if(c.core.scroll)return c.core.scroll&&c.core.scroll.left!==b&&this.element.scrollLeft(c.core.scroll.left),c.core.scroll&&c.core.scroll.top!==b&&this.element.scrollTop(c.core.scroll.top),delete c.core.scroll,this.set_state(c,d),!1;if(c.core.selected)return h=this,(c.core.initial_selection===b||c.core.initial_selection===this._data.core.selected.concat([]).sort().join(","))&&(this.deselect_all(),a.each(c.core.selected,function(a,b){h.select_node(b,!1,!0)})),delete c.core.initial_selection,delete c.core.selected,this.set_state(c,d),!1;for(i in c)c.hasOwnProperty(i)&&"core"!==i&&-1===a.inArray(i,this.settings.plugins)&&delete c[i];if(a.isEmptyObject(c.core))return delete c.core,this.set_state(c,d),!1}return a.isEmptyObject(c)?(c=null,d&&d.call(this),this.trigger("set_state"),!1):!0}return!1},refresh:function(b,c){this._data.core.state=c===!0?{}:this.get_state(),c&&a.isFunction(c)&&(this._data.core.state=c.call(this,this._data.core.state)),this._cnt=0,this._model.data={},this._model.data[a.jstree.root]={id:a.jstree.root,parent:null,parents:[],children:[],children_d:[],state:{loaded:!1}},this._data.core.selected=[],this._data.core.last_clicked=null,this._data.core.focused=null;var d=this.get_container_ul()[0].className;b||(this.element.html(""),this.element.attr("aria-activedescendant","j"+this._id+"_loading")),this.load_node(a.jstree.root,function(b,c){c&&(this.get_container_ul()[0].className=d,this._firstChild(this.get_container_ul()[0])&&this.element.attr("aria-activedescendant",this._firstChild(this.get_container_ul()[0]).id),this.set_state(a.extend(!0,{},this._data.core.state),function(){this.trigger("refresh")})),this._data.core.state=null})},refresh_node:function(b){if(b=this.get_node(b),!b||b.id===a.jstree.root)return!1;var c=[],d=[],e=this._data.core.selected.concat([]);d.push(b.id),b.state.opened===!0&&c.push(b.id),this.get_node(b,!0).find(".jstree-open").each(function(){d.push(this.id),c.push(this.id)}),this._load_nodes(d,a.proxy(function(a){this.open_node(c,!1,0),this.select_node(e),this.trigger("refresh_node",{node:b,nodes:a})},this),!1,!0)},set_id:function(b,c){if(b=this.get_node(b),!b||b.id===a.jstree.root)return!1;var d,e,f=this._model.data,g=b.id;for(c=c.toString(),f[b.parent].children[a.inArray(b.id,f[b.parent].children)]=c,d=0,e=b.parents.length;e>d;d++)f[b.parents[d]].children_d[a.inArray(b.id,f[b.parents[d]].children_d)]=c;for(d=0,e=b.children.length;e>d;d++)f[b.children[d]].parent=c;for(d=0,e=b.children_d.length;e>d;d++)f[b.children_d[d]].parents[a.inArray(b.id,f[b.children_d[d]].parents)]=c;return d=a.inArray(b.id,this._data.core.selected),-1!==d&&(this._data.core.selected[d]=c),d=this.get_node(b.id,!0),d&&(d.attr("id",c),this.element.attr("aria-activedescendant")===b.id&&this.element.attr("aria-activedescendant",c)),delete f[b.id],b.id=c,b.li_attr.id=c,f[c]=b,this.trigger("set_id",{node:b,"new":b.id,old:g}),!0},get_text:function(b){return b=this.get_node(b),b&&b.id!==a.jstree.root?b.text:!1},set_text:function(b,c){var d,e;if(a.isArray(b)){for(b=b.slice(),d=0,e=b.length;e>d;d++)this.set_text(b[d],c);return!0}return b=this.get_node(b),b&&b.id!==a.jstree.root?(b.text=c,this.get_node(b,!0).length&&this.redraw_node(b.id),this.trigger("set_text",{obj:b,text:c}),!0):!1},get_json:function(b,c,d){if(b=this.get_node(b||a.jstree.root),!b)return!1;c&&c.flat&&!d&&(d=[]);var e={id:b.id,text:b.text,icon:this.get_icon(b),li_attr:a.extend(!0,{},b.li_attr),a_attr:a.extend(!0,{},b.a_attr),state:{},data:c&&c.no_data?!1:a.extend(!0,a.isArray(b.data)?[]:{},b.data)},f,g;if(c&&c.flat?e.parent=b.parent:e.children=[],c&&c.no_state)delete e.state;else for(f in b.state)b.state.hasOwnProperty(f)&&(e.state[f]=b.state[f]);if(c&&c.no_li_attr&&delete e.li_attr,c&&c.no_a_attr&&delete e.a_attr,c&&c.no_id&&(delete e.id,e.li_attr&&e.li_attr.id&&delete e.li_attr.id,e.a_attr&&e.a_attr.id&&delete e.a_attr.id),c&&c.flat&&b.id!==a.jstree.root&&d.push(e),!c||!c.no_children)for(f=0,g=b.children.length;g>f;f++)c&&c.flat?this.get_json(b.children[f],c,d):e.children.push(this.get_json(b.children[f],c));return c&&c.flat?d:b.id===a.jstree.root?e.children:e},create_node:function(c,d,e,f,g){if(null===c&&(c=a.jstree.root),c=this.get_node(c),!c)return!1;if(e=e===b?"last":e,!e.toString().match(/^(before|after)$/)&&!g&&!this.is_loaded(c))return this.load_node(c,function(){this.create_node(c,d,e,f,!0)});d||(d={text:this.get_string("New node")}),d="string"==typeof d?{text:d}:a.extend(!0,{},d),d.text===b&&(d.text=this.get_string("New node"));var h,i,j,k;switch(c.id===a.jstree.root&&("before"===e&&(e="first"),"after"===e&&(e="last")),e){case"before":h=this.get_node(c.parent),e=a.inArray(c.id,h.children),c=h;break;case"after":h=this.get_node(c.parent),e=a.inArray(c.id,h.children)+1,c=h;break;case"inside":case"first":e=0;break;case"last":e=c.children.length;break;default:e||(e=0)}if(e>c.children.length&&(e=c.children.length),d.id||(d.id=!0),!this.check("create_node",d,c,e))return this.settings.core.error.call(this,this._data.core.last_error),!1;if(d.id===!0&&delete d.id,d=this._parse_model_from_json(d,c.id,c.parents.concat()),!d)return!1;for(h=this.get_node(d),i=[],i.push(d),i=i.concat(h.children_d),this.trigger("model",{nodes:i,parent:c.id}),c.children_d=c.children_d.concat(i),j=0,k=c.parents.length;k>j;j++)this._model.data[c.parents[j]].children_d=this._model.data[c.parents[j]].children_d.concat(i);for(d=h,h=[],j=0,k=c.children.length;k>j;j++)h[j>=e?j+1:j]=c.children[j];return h[e]=d.id,c.children=h,this.redraw_node(c,!0),this.trigger("create_node",{node:this.get_node(d),parent:c.id,position:e}),f&&f.call(this,this.get_node(d)),d.id},rename_node:function(b,c){var d,e,f;if(a.isArray(b)){for(b=b.slice(),d=0,e=b.length;e>d;d++)this.rename_node(b[d],c);return!0}return b=this.get_node(b),b&&b.id!==a.jstree.root?(f=b.text,this.check("rename_node",b,this.get_parent(b),c)?(this.set_text(b,c),this.trigger("rename_node",{node:b,text:c,old:f}),!0):(this.settings.core.error.call(this,this._data.core.last_error),!1)):!1},delete_node:function(b){var c,d,e,f,g,h,i,j,k,l,m,n;if(a.isArray(b)){for(b=b.slice(),c=0,d=b.length;d>c;c++)this.delete_node(b[c]);return!0}if(b=this.get_node(b),!b||b.id===a.jstree.root)return!1;if(e=this.get_node(b.parent),f=a.inArray(b.id,e.children),l=!1,!this.check("delete_node",b,e,f))return this.settings.core.error.call(this,this._data.core.last_error),!1;for(-1!==f&&(e.children=a.vakata.array_remove(e.children,f)),g=b.children_d.concat([]),g.push(b.id),h=0,i=b.parents.length;i>h;h++)this._model.data[b.parents[h]].children_d=a.vakata.array_filter(this._model.data[b.parents[h]].children_d,function(b){return-1===a.inArray(b,g)});for(j=0,k=g.length;k>j;j++)if(this._model.data[g[j]].state.selected){l=!0;break}for(l&&(this._data.core.selected=a.vakata.array_filter(this._data.core.selected,function(b){return-1===a.inArray(b,g)})),this.trigger("delete_node",{node:b,parent:e.id}),l&&this.trigger("changed",{action:"delete_node",node:b,selected:this._data.core.selected,parent:e.id}),j=0,k=g.length;k>j;j++)delete this._model.data[g[j]];return-1!==a.inArray(this._data.core.focused,g)&&(this._data.core.focused=null,m=this.element[0].scrollTop,n=this.element[0].scrollLeft,e.id===a.jstree.root?this._model.data[a.jstree.root].children[0]&&this.get_node(this._model.data[a.jstree.root].children[0],!0).children(".jstree-anchor").focus():this.get_node(e,!0).children(".jstree-anchor").focus(),this.element[0].scrollTop=m,this.element[0].scrollLeft=n),this.redraw_node(e,!0),!0},check:function(b,c,d,e,f){c=c&&c.id?c:this.get_node(c),d=d&&d.id?d:this.get_node(d);var g=b.match(/^move_node|copy_node|create_node$/i)?d:c,h=this.settings.core.check_callback;return"move_node"!==b&&"copy_node"!==b||f&&f.is_multi||c.id!==d.id&&("move_node"!==b||a.inArray(c.id,d.children)!==e)&&-1===a.inArray(d.id,c.children_d)?(g&&g.data&&(g=g.data),g&&g.functions&&(g.functions[b]===!1||g.functions[b]===!0)?(g.functions[b]===!1&&(this._data.core.last_error={error:"check",plugin:"core",id:"core_02",reason:"Node data prevents function: "+b,data:JSON.stringify({chk:b,pos:e,obj:c&&c.id?c.id:!1,par:d&&d.id?d.id:!1})}),g.functions[b]):h===!1||a.isFunction(h)&&h.call(this,b,c,d,e,f)===!1||h&&h[b]===!1?(this._data.core.last_error={error:"check",plugin:"core",id:"core_03",reason:"User config for core.check_callback prevents function: "+b,data:JSON.stringify({chk:b,pos:e,obj:c&&c.id?c.id:!1,par:d&&d.id?d.id:!1})},!1):!0):(this._data.core.last_error={error:"check",plugin:"core",id:"core_01",reason:"Moving parent inside child",data:JSON.stringify({chk:b,pos:e,obj:c&&c.id?c.id:!1,par:d&&d.id?d.id:!1})},!1)},last_error:function(){return this._data.core.last_error},move_node:function(c,d,e,f,g,h,i){var j,k,l,m,n,o,p,q,r,s,t,u,v,w;if(d=this.get_node(d),e=e===b?0:e,!d)return!1;if(!e.toString().match(/^(before|after)$/)&&!g&&!this.is_loaded(d))return this.load_node(d,function(){this.move_node(c,d,e,f,!0,!1,i)});if(a.isArray(c)){if(1!==c.length){for(j=0,k=c.length;k>j;j++)(r=this.move_node(c[j],d,e,f,g,!1,i))&&(d=r,e="after");return this.redraw(),!0}c=c[0]}if(c=c&&c.id?c:this.get_node(c),!c||c.id===a.jstree.root)return!1;if(l=(c.parent||a.jstree.root).toString(),n=e.toString().match(/^(before|after)$/)&&d.id!==a.jstree.root?this.get_node(d.parent):d,o=i?i:this._model.data[c.id]?this:a.jstree.reference(c.id),p=!o||!o._id||this._id!==o._id,m=o&&o._id&&l&&o._model.data[l]&&o._model.data[l].children?a.inArray(c.id,o._model.data[l].children):-1,o&&o._id&&(c=o._model.data[c.id]),p)return(r=this.copy_node(c,d,e,f,g,!1,i))?(o&&o.delete_node(c),r):!1;switch(d.id===a.jstree.root&&("before"===e&&(e="first"),"after"===e&&(e="last")),e){case"before":e=a.inArray(d.id,n.children);break;case"after":e=a.inArray(d.id,n.children)+1;break;case"inside":case"first":e=0;break;case"last":e=n.children.length;break;default:e||(e=0)}if(e>n.children.length&&(e=n.children.length),!this.check("move_node",c,n,e,{core:!0,origin:i,is_multi:o&&o._id&&o._id!==this._id,is_foreign:!o||!o._id}))return this.settings.core.error.call(this,this._data.core.last_error),!1;if(c.parent===n.id){for(q=n.children.concat(),r=a.inArray(c.id,q),-1!==r&&(q=a.vakata.array_remove(q,r),e>r&&e--),r=[],s=0,t=q.length;t>s;s++)r[s>=e?s+1:s]=q[s];r[e]=c.id,n.children=r,this._node_changed(n.id),this.redraw(n.id===a.jstree.root)}else{for(r=c.children_d.concat(),r.push(c.id),s=0,t=c.parents.length;t>s;s++){for(q=[],w=o._model.data[c.parents[s]].children_d,u=0,v=w.length;v>u;u++)-1===a.inArray(w[u],r)&&q.push(w[u]);o._model.data[c.parents[s]].children_d=q}for(o._model.data[l].children=a.vakata.array_remove_item(o._model.data[l].children,c.id),s=0,t=n.parents.length;t>s;s++)this._model.data[n.parents[s]].children_d=this._model.data[n.parents[s]].children_d.concat(r);for(q=[],s=0,t=n.children.length;t>s;s++)q[s>=e?s+1:s]=n.children[s];for(q[e]=c.id,n.children=q,n.children_d.push(c.id),n.children_d=n.children_d.concat(c.children_d),c.parent=n.id,r=n.parents.concat(),r.unshift(n.id),w=c.parents.length,c.parents=r,r=r.concat(),s=0,t=c.children_d.length;t>s;s++)this._model.data[c.children_d[s]].parents=this._model.data[c.children_d[s]].parents.slice(0,-1*w),Array.prototype.push.apply(this._model.data[c.children_d[s]].parents,r);(l===a.jstree.root||n.id===a.jstree.root)&&(this._model.force_full_redraw=!0),this._model.force_full_redraw||(this._node_changed(l),this._node_changed(n.id)),h||this.redraw()}return f&&f.call(this,c,n,e),this.trigger("move_node",{node:c,parent:n.id,position:e,old_parent:l,old_position:m,is_multi:o&&o._id&&o._id!==this._id,is_foreign:!o||!o._id,old_instance:o,new_instance:this}),c.id},copy_node:function(c,d,e,f,g,h,i){var j,k,l,m,n,o,p,q,r,s,t;if(d=this.get_node(d),e=e===b?0:e,!d)return!1;if(!e.toString().match(/^(before|after)$/)&&!g&&!this.is_loaded(d))return this.load_node(d,function(){this.copy_node(c,d,e,f,!0,!1,i)});if(a.isArray(c)){if(1!==c.length){for(j=0,k=c.length;k>j;j++)(m=this.copy_node(c[j],d,e,f,g,!0,i))&&(d=m,e="after");return this.redraw(),!0}c=c[0]}if(c=c&&c.id?c:this.get_node(c),!c||c.id===a.jstree.root)return!1;switch(q=(c.parent||a.jstree.root).toString(),r=e.toString().match(/^(before|after)$/)&&d.id!==a.jstree.root?this.get_node(d.parent):d,s=i?i:this._model.data[c.id]?this:a.jstree.reference(c.id),t=!s||!s._id||this._id!==s._id,s&&s._id&&(c=s._model.data[c.id]),d.id===a.jstree.root&&("before"===e&&(e="first"),"after"===e&&(e="last")),e){case"before":e=a.inArray(d.id,r.children);break;case"after":e=a.inArray(d.id,r.children)+1;break;case"inside":case"first":e=0;break;case"last":e=r.children.length;break;default:e||(e=0)}if(e>r.children.length&&(e=r.children.length),!this.check("copy_node",c,r,e,{core:!0,origin:i,is_multi:s&&s._id&&s._id!==this._id,is_foreign:!s||!s._id}))return this.settings.core.error.call(this,this._data.core.last_error),!1;if(p=s?s.get_json(c,{no_id:!0,no_data:!0,no_state:!0}):c,!p)return!1;if(p.id===!0&&delete p.id,p=this._parse_model_from_json(p,r.id,r.parents.concat()),!p)return!1;for(m=this.get_node(p),c&&c.state&&c.state.loaded===!1&&(m.state.loaded=!1),l=[],l.push(p),l=l.concat(m.children_d),this.trigger("model",{nodes:l,parent:r.id}),n=0,o=r.parents.length;o>n;n++)this._model.data[r.parents[n]].children_d=this._model.data[r.parents[n]].children_d.concat(l);for(l=[],n=0,o=r.children.length;o>n;n++)l[n>=e?n+1:n]=r.children[n];return l[e]=m.id,r.children=l,r.children_d.push(m.id),r.children_d=r.children_d.concat(m.children_d),r.id===a.jstree.root&&(this._model.force_full_redraw=!0),this._model.force_full_redraw||this._node_changed(r.id),h||this.redraw(r.id===a.jstree.root),f&&f.call(this,m,r,e),this.trigger("copy_node",{node:m,original:c,parent:r.id,position:e,old_parent:q,old_position:s&&s._id&&q&&s._model.data[q]&&s._model.data[q].children?a.inArray(c.id,s._model.data[q].children):-1,is_multi:s&&s._id&&s._id!==this._id,is_foreign:!s||!s._id,old_instance:s,new_instance:this}),m.id},cut:function(b){if(b||(b=this._data.core.selected.concat()),a.isArray(b)||(b=[b]),!b.length)return!1;var c=[],g,h,i;for(h=0,i=b.length;i>h;h++)g=this.get_node(b[h]),g&&g.id&&g.id!==a.jstree.root&&c.push(g); -return c.length?(d=c,f=this,e="move_node",void this.trigger("cut",{node:b})):!1},copy:function(b){if(b||(b=this._data.core.selected.concat()),a.isArray(b)||(b=[b]),!b.length)return!1;var c=[],g,h,i;for(h=0,i=b.length;i>h;h++)g=this.get_node(b[h]),g&&g.id&&g.id!==a.jstree.root&&c.push(g);return c.length?(d=c,f=this,e="copy_node",void this.trigger("copy",{node:b})):!1},get_buffer:function(){return{mode:e,node:d,inst:f}},can_paste:function(){return e!==!1&&d!==!1},paste:function(a,b){return a=this.get_node(a),a&&e&&e.match(/^(copy_node|move_node)$/)&&d?(this[e](d,a,b,!1,!1,!1,f)&&this.trigger("paste",{parent:a.id,node:d,mode:e}),d=!1,e=!1,void(f=!1)):!1},clear_buffer:function(){d=!1,e=!1,f=!1,this.trigger("clear_buffer")},edit:function(b,c,d){var e,f,g,h,j,k,l,m,n,o=!1;return(b=this.get_node(b))?this.check("edit",b,this.get_parent(b))?(n=b,c="string"==typeof c?c:b.text,this.set_text(b,""),b=this._open_to(b),n.text=c,e=this._data.core.rtl,f=this.element.width(),this._data.core.focused=n.id,g=b.children(".jstree-anchor").focus(),h=a(""),j=c,k=a("
",{css:{position:"absolute",top:"-200px",left:e?"0px":"-1000px",visibility:"hidden"}}).appendTo(i.body),l=a("",{value:j,"class":"jstree-rename-input",css:{padding:"0",border:"1px solid silver","box-sizing":"border-box",display:"inline-block",height:this._data.core.li_height+"px",lineHeight:this._data.core.li_height+"px",width:"150px"},blur:a.proxy(function(c){c.stopImmediatePropagation(),c.preventDefault();var e=h.children(".jstree-rename-input"),f=e.val(),i=this.settings.core.force_text,m;""===f&&(f=j),k.remove(),h.replaceWith(g),h.remove(),j=i?j:a("
").append(a.parseHTML(j)).html(),b=this.get_node(b),this.set_text(b,j),m=!!this.rename_node(b,i?a("
").text(f).text():a("
").append(a.parseHTML(f)).html()),m||this.set_text(b,j),this._data.core.focused=n.id,setTimeout(a.proxy(function(){var a=this.get_node(n.id,!0);a.length&&(this._data.core.focused=n.id,a.children(".jstree-anchor").focus())},this),0),d&&d.call(this,n,m,o),l=null},this),keydown:function(a){var b=a.which;27===b&&(o=!0,this.value=j),(27===b||13===b||37===b||38===b||39===b||40===b||32===b)&&a.stopImmediatePropagation(),(27===b||13===b)&&(a.preventDefault(),this.blur())},click:function(a){a.stopImmediatePropagation()},mousedown:function(a){a.stopImmediatePropagation()},keyup:function(a){l.width(Math.min(k.text("pW"+this.value).width(),f))},keypress:function(a){return 13===a.which?!1:void 0}}),m={fontFamily:g.css("fontFamily")||"",fontSize:g.css("fontSize")||"",fontWeight:g.css("fontWeight")||"",fontStyle:g.css("fontStyle")||"",fontStretch:g.css("fontStretch")||"",fontVariant:g.css("fontVariant")||"",letterSpacing:g.css("letterSpacing")||"",wordSpacing:g.css("wordSpacing")||""},h.attr("class",g.attr("class")).append(g.contents().clone()).append(l),g.replaceWith(h),k.css(m),l.css(m).width(Math.min(k.text("pW"+l[0].value).width(),f))[0].select(),void a(i).one("mousedown.jstree touchstart.jstree dnd_start.vakata",function(b){l&&b.target!==l&&a(l).blur()})):(this.settings.core.error.call(this,this._data.core.last_error),!1):!1},set_theme:function(b,c){if(!b)return!1;if(c===!0){var d=this.settings.core.themes.dir;d||(d=a.jstree.path+"/themes"),c=d+"/"+b+"/style.css"}c&&-1===a.inArray(c,g)&&(a("head").append(''),g.push(c)),this._data.core.themes.name&&this.element.removeClass("jstree-"+this._data.core.themes.name),this._data.core.themes.name=b,this.element.addClass("jstree-"+b),this.element[this.settings.core.themes.responsive?"addClass":"removeClass"]("jstree-"+b+"-responsive"),this.trigger("set_theme",{theme:b})},get_theme:function(){return this._data.core.themes.name},set_theme_variant:function(a){this._data.core.themes.variant&&this.element.removeClass("jstree-"+this._data.core.themes.name+"-"+this._data.core.themes.variant),this._data.core.themes.variant=a,a&&this.element.addClass("jstree-"+this._data.core.themes.name+"-"+this._data.core.themes.variant)},get_theme_variant:function(){return this._data.core.themes.variant},show_stripes:function(){this._data.core.themes.stripes=!0,this.get_container_ul().addClass("jstree-striped"),this.trigger("show_stripes")},hide_stripes:function(){this._data.core.themes.stripes=!1,this.get_container_ul().removeClass("jstree-striped"),this.trigger("hide_stripes")},toggle_stripes:function(){this._data.core.themes.stripes?this.hide_stripes():this.show_stripes()},show_dots:function(){this._data.core.themes.dots=!0,this.get_container_ul().removeClass("jstree-no-dots"),this.trigger("show_dots")},hide_dots:function(){this._data.core.themes.dots=!1,this.get_container_ul().addClass("jstree-no-dots"),this.trigger("hide_dots")},toggle_dots:function(){this._data.core.themes.dots?this.hide_dots():this.show_dots()},show_icons:function(){this._data.core.themes.icons=!0,this.get_container_ul().removeClass("jstree-no-icons"),this.trigger("show_icons")},hide_icons:function(){this._data.core.themes.icons=!1,this.get_container_ul().addClass("jstree-no-icons"),this.trigger("hide_icons")},toggle_icons:function(){this._data.core.themes.icons?this.hide_icons():this.show_icons()},show_ellipsis:function(){this._data.core.themes.ellipsis=!0,this.get_container_ul().addClass("jstree-ellipsis"),this.trigger("show_ellipsis")},hide_ellipsis:function(){this._data.core.themes.ellipsis=!1,this.get_container_ul().removeClass("jstree-ellipsis"),this.trigger("hide_ellipsis")},toggle_ellipsis:function(){this._data.core.themes.ellipsis?this.hide_ellipsis():this.show_ellipsis()},set_icon:function(c,d){var e,f,g,h;if(a.isArray(c)){for(c=c.slice(),e=0,f=c.length;f>e;e++)this.set_icon(c[e],d);return!0}return c=this.get_node(c),c&&c.id!==a.jstree.root?(h=c.icon,c.icon=d===!0||null===d||d===b||""===d?!0:d,g=this.get_node(c,!0).children(".jstree-anchor").children(".jstree-themeicon"),d===!1?(g.removeClass("jstree-themeicon-custom "+h).css("background","").removeAttr("rel"),this.hide_icon(c)):d===!0||null===d||d===b||""===d?(g.removeClass("jstree-themeicon-custom "+h).css("background","").removeAttr("rel"),h===!1&&this.show_icon(c)):-1===d.indexOf("/")&&-1===d.indexOf(".")?(g.removeClass(h).css("background",""),g.addClass(d+" jstree-themeicon-custom").attr("rel",d),h===!1&&this.show_icon(c)):(g.removeClass(h).css("background",""),g.addClass("jstree-themeicon-custom").css("background","url('"+d+"') center center no-repeat").attr("rel",d),h===!1&&this.show_icon(c)),!0):!1},get_icon:function(b){return b=this.get_node(b),b&&b.id!==a.jstree.root?b.icon:!1},hide_icon:function(b){var c,d;if(a.isArray(b)){for(b=b.slice(),c=0,d=b.length;d>c;c++)this.hide_icon(b[c]);return!0}return b=this.get_node(b),b&&b!==a.jstree.root?(b.icon=!1,this.get_node(b,!0).children(".jstree-anchor").children(".jstree-themeicon").addClass("jstree-themeicon-hidden"),!0):!1},show_icon:function(b){var c,d,e;if(a.isArray(b)){for(b=b.slice(),c=0,d=b.length;d>c;c++)this.show_icon(b[c]);return!0}return b=this.get_node(b),b&&b!==a.jstree.root?(e=this.get_node(b,!0),b.icon=e.length?e.children(".jstree-anchor").children(".jstree-themeicon").attr("rel"):!0,b.icon||(b.icon=!0),e.children(".jstree-anchor").children(".jstree-themeicon").removeClass("jstree-themeicon-hidden"),!0):!1}},a.vakata={},a.vakata.attributes=function(b,c){b=a(b)[0];var d=c?{}:[];return b&&b.attributes&&a.each(b.attributes,function(b,e){-1===a.inArray(e.name.toLowerCase(),["style","contenteditable","hasfocus","tabindex"])&&null!==e.value&&""!==a.trim(e.value)&&(c?d[e.name]=e.value:d.push(e.name))}),d},a.vakata.array_unique=function(a){var c=[],d,e,f,g={};for(d=0,f=a.length;f>d;d++)g[a[d]]===b&&(c.push(a[d]),g[a[d]]=!0);return c},a.vakata.array_remove=function(a,b){return a.splice(b,1),a},a.vakata.array_remove_item=function(b,c){var d=a.inArray(c,b);return-1!==d?a.vakata.array_remove(b,d):b},a.vakata.array_filter=function(a,b,c,d,e){if(a.filter)return a.filter(b,c);d=[];for(e in a)~~e+""==e+""&&e>=0&&b.call(c,a[e],+e,a)&&d.push(a[e]);return d},a.jstree.plugins.changed=function(a,b){var c=[];this.trigger=function(a,d){var e,f;if(d||(d={}),"changed"===a.replace(".jstree","")){d.changed={selected:[],deselected:[]};var g={};for(e=0,f=c.length;f>e;e++)g[c[e]]=1;for(e=0,f=d.selected.length;f>e;e++)g[d.selected[e]]?g[d.selected[e]]=2:d.changed.selected.push(d.selected[e]);for(e=0,f=c.length;f>e;e++)1===g[c[e]]&&d.changed.deselected.push(c[e]);c=d.selected.slice()}b.trigger.call(this,a,d)},this.refresh=function(a,d){return c=[],b.refresh.apply(this,arguments)}};var j=i.createElement("I");j.className="jstree-icon jstree-checkbox",j.setAttribute("role","presentation"),a.jstree.defaults.checkbox={visible:!0,three_state:!0,whole_node:!0,keep_selected_style:!0,cascade:"",tie_selection:!0,cascade_to_disabled:!0,cascade_to_hidden:!0},a.jstree.plugins.checkbox=function(c,d){this.bind=function(){d.bind.call(this),this._data.checkbox.uto=!1,this._data.checkbox.selected=[],this.settings.checkbox.three_state&&(this.settings.checkbox.cascade="up+down+undetermined"),this.element.on("init.jstree",a.proxy(function(){this._data.checkbox.visible=this.settings.checkbox.visible,this.settings.checkbox.keep_selected_style||this.element.addClass("jstree-checkbox-no-clicked"),this.settings.checkbox.tie_selection&&this.element.addClass("jstree-checkbox-selection")},this)).on("loading.jstree",a.proxy(function(){this[this._data.checkbox.visible?"show_checkboxes":"hide_checkboxes"]()},this)),-1!==this.settings.checkbox.cascade.indexOf("undetermined")&&this.element.on("changed.jstree uncheck_node.jstree check_node.jstree uncheck_all.jstree check_all.jstree move_node.jstree copy_node.jstree redraw.jstree open_node.jstree",a.proxy(function(){this._data.checkbox.uto&&clearTimeout(this._data.checkbox.uto),this._data.checkbox.uto=setTimeout(a.proxy(this._undetermined,this),50)},this)),this.settings.checkbox.tie_selection||this.element.on("model.jstree",a.proxy(function(a,b){var c=this._model.data,d=c[b.parent],e=b.nodes,f,g;for(f=0,g=e.length;g>f;f++)c[e[f]].state.checked=c[e[f]].state.checked||c[e[f]].original&&c[e[f]].original.state&&c[e[f]].original.state.checked,c[e[f]].state.checked&&this._data.checkbox.selected.push(e[f])},this)),(-1!==this.settings.checkbox.cascade.indexOf("up")||-1!==this.settings.checkbox.cascade.indexOf("down"))&&this.element.on("model.jstree",a.proxy(function(b,c){var d=this._model.data,e=d[c.parent],f=c.nodes,g=[],h,i,j,k,l,m,n=this.settings.checkbox.cascade,o=this.settings.checkbox.tie_selection;if(-1!==n.indexOf("down"))if(e.state[o?"selected":"checked"]){for(i=0,j=f.length;j>i;i++)d[f[i]].state[o?"selected":"checked"]=!0;this._data[o?"core":"checkbox"].selected=this._data[o?"core":"checkbox"].selected.concat(f)}else for(i=0,j=f.length;j>i;i++)if(d[f[i]].state[o?"selected":"checked"]){for(k=0,l=d[f[i]].children_d.length;l>k;k++)d[d[f[i]].children_d[k]].state[o?"selected":"checked"]=!0;this._data[o?"core":"checkbox"].selected=this._data[o?"core":"checkbox"].selected.concat(d[f[i]].children_d)}if(-1!==n.indexOf("up")){for(i=0,j=e.children_d.length;j>i;i++)d[e.children_d[i]].children.length||g.push(d[e.children_d[i]].parent);for(g=a.vakata.array_unique(g),k=0,l=g.length;l>k;k++){e=d[g[k]];while(e&&e.id!==a.jstree.root){for(h=0,i=0,j=e.children.length;j>i;i++)h+=d[e.children[i]].state[o?"selected":"checked"];if(h!==j)break;e.state[o?"selected":"checked"]=!0,this._data[o?"core":"checkbox"].selected.push(e.id),m=this.get_node(e,!0),m&&m.length&&m.attr("aria-selected",!0).children(".jstree-anchor").addClass(o?"jstree-clicked":"jstree-checked"),e=this.get_node(e.parent)}}}this._data[o?"core":"checkbox"].selected=a.vakata.array_unique(this._data[o?"core":"checkbox"].selected)},this)).on(this.settings.checkbox.tie_selection?"select_node.jstree":"check_node.jstree",a.proxy(function(b,c){var d=this,e=c.node,f=this._model.data,g=this.get_node(e.parent),h,i,j,k,l=this.settings.checkbox.cascade,m=this.settings.checkbox.tie_selection,n={},o=this._data[m?"core":"checkbox"].selected;for(h=0,i=o.length;i>h;h++)n[o[h]]=!0;if(-1!==l.indexOf("down")){var p=this._cascade_new_checked_state(e.id,!0),q=e.children_d.concat(e.id);for(h=0,i=q.length;i>h;h++)p.indexOf(q[h])>-1?n[q[h]]=!0:delete n[q[h]]}if(-1!==l.indexOf("up"))while(g&&g.id!==a.jstree.root){for(j=0,h=0,i=g.children.length;i>h;h++)j+=f[g.children[h]].state[m?"selected":"checked"];if(j!==i)break;g.state[m?"selected":"checked"]=!0,n[g.id]=!0,k=this.get_node(g,!0),k&&k.length&&k.attr("aria-selected",!0).children(".jstree-anchor").addClass(m?"jstree-clicked":"jstree-checked"),g=this.get_node(g.parent)}o=[];for(h in n)n.hasOwnProperty(h)&&o.push(h);this._data[m?"core":"checkbox"].selected=o},this)).on(this.settings.checkbox.tie_selection?"deselect_all.jstree":"uncheck_all.jstree",a.proxy(function(b,c){var d=this.get_node(a.jstree.root),e=this._model.data,f,g,h;for(f=0,g=d.children_d.length;g>f;f++)h=e[d.children_d[f]],h&&h.original&&h.original.state&&h.original.state.undetermined&&(h.original.state.undetermined=!1)},this)).on(this.settings.checkbox.tie_selection?"deselect_node.jstree":"uncheck_node.jstree",a.proxy(function(a,b){var c=this,d=b.node,e=this.get_node(d,!0),f,g,h,i=this.settings.checkbox.cascade,j=this.settings.checkbox.tie_selection,k=this._data[j?"core":"checkbox"].selected,l={},m=[],n=d.children_d.concat(d.id);if(-1!==i.indexOf("down")){var o=this._cascade_new_checked_state(d.id,!1);k=k.filter(function(a){return-1===n.indexOf(a)||o.indexOf(a)>-1})}if(-1!==i.indexOf("up")&&-1===k.indexOf(d.id)){for(f=0,g=d.parents.length;g>f;f++)h=this._model.data[d.parents[f]],h.state[j?"selected":"checked"]=!1,h&&h.original&&h.original.state&&h.original.state.undetermined&&(h.original.state.undetermined=!1),h=this.get_node(d.parents[f],!0),h&&h.length&&h.attr("aria-selected",!1).children(".jstree-anchor").removeClass(j?"jstree-clicked":"jstree-checked");k=k.filter(function(a){return-1===d.parents.indexOf(a)})}this._data[j?"core":"checkbox"].selected=k},this)),-1!==this.settings.checkbox.cascade.indexOf("up")&&this.element.on("delete_node.jstree",a.proxy(function(b,c){var d=this.get_node(c.parent),e=this._model.data,f,g,h,i,j=this.settings.checkbox.tie_selection;while(d&&d.id!==a.jstree.root&&!d.state[j?"selected":"checked"]){for(h=0,f=0,g=d.children.length;g>f;f++)h+=e[d.children[f]].state[j?"selected":"checked"];if(!(g>0&&h===g))break;d.state[j?"selected":"checked"]=!0,this._data[j?"core":"checkbox"].selected.push(d.id),i=this.get_node(d,!0),i&&i.length&&i.attr("aria-selected",!0).children(".jstree-anchor").addClass(j?"jstree-clicked":"jstree-checked"),d=this.get_node(d.parent)}},this)).on("move_node.jstree",a.proxy(function(b,c){var d=c.is_multi,e=c.old_parent,f=this.get_node(c.parent),g=this._model.data,h,i,j,k,l,m=this.settings.checkbox.tie_selection;if(!d){h=this.get_node(e);while(h&&h.id!==a.jstree.root&&!h.state[m?"selected":"checked"]){for(i=0,j=0,k=h.children.length;k>j;j++)i+=g[h.children[j]].state[m?"selected":"checked"];if(!(k>0&&i===k))break;h.state[m?"selected":"checked"]=!0,this._data[m?"core":"checkbox"].selected.push(h.id),l=this.get_node(h,!0),l&&l.length&&l.attr("aria-selected",!0).children(".jstree-anchor").addClass(m?"jstree-clicked":"jstree-checked"),h=this.get_node(h.parent)}}h=f;while(h&&h.id!==a.jstree.root){for(i=0,j=0,k=h.children.length;k>j;j++)i+=g[h.children[j]].state[m?"selected":"checked"];if(i===k)h.state[m?"selected":"checked"]||(h.state[m?"selected":"checked"]=!0,this._data[m?"core":"checkbox"].selected.push(h.id),l=this.get_node(h,!0),l&&l.length&&l.attr("aria-selected",!0).children(".jstree-anchor").addClass(m?"jstree-clicked":"jstree-checked"));else{if(!h.state[m?"selected":"checked"])break;h.state[m?"selected":"checked"]=!1,this._data[m?"core":"checkbox"].selected=a.vakata.array_remove_item(this._data[m?"core":"checkbox"].selected,h.id),l=this.get_node(h,!0),l&&l.length&&l.attr("aria-selected",!1).children(".jstree-anchor").removeClass(m?"jstree-clicked":"jstree-checked")}h=this.get_node(h.parent)}},this))},this.get_undetermined=function(c){if(-1===this.settings.checkbox.cascade.indexOf("undetermined"))return[];var d,e,f,g,h={},i=this._model.data,j=this.settings.checkbox.tie_selection,k=this._data[j?"core":"checkbox"].selected,l=[],m=this,n=[];for(d=0,e=k.length;e>d;d++)if(i[k[d]]&&i[k[d]].parents)for(f=0,g=i[k[d]].parents.length;g>f;f++){if(h[i[k[d]].parents[f]]!==b)break;i[k[d]].parents[f]!==a.jstree.root&&(h[i[k[d]].parents[f]]=!0,l.push(i[k[d]].parents[f]))}for(this.element.find(".jstree-closed").not(":has(.jstree-children)").each(function(){var c=m.get_node(this),j;if(c)if(c.state.loaded){for(d=0,e=c.children_d.length;e>d;d++)if(j=i[c.children_d[d]],!j.state.loaded&&j.original&&j.original.state&&j.original.state.undetermined&&j.original.state.undetermined===!0)for(h[j.id]===b&&j.id!==a.jstree.root&&(h[j.id]=!0,l.push(j.id)),f=0,g=j.parents.length;g>f;f++)h[j.parents[f]]===b&&j.parents[f]!==a.jstree.root&&(h[j.parents[f]]=!0,l.push(j.parents[f]))}else if(c.original&&c.original.state&&c.original.state.undetermined&&c.original.state.undetermined===!0)for(h[c.id]===b&&c.id!==a.jstree.root&&(h[c.id]=!0,l.push(c.id)),f=0,g=c.parents.length;g>f;f++)h[c.parents[f]]===b&&c.parents[f]!==a.jstree.root&&(h[c.parents[f]]=!0,l.push(c.parents[f]))}),d=0,e=l.length;e>d;d++)i[l[d]].state[j?"selected":"checked"]||n.push(c?i[l[d]]:l[d]);return n},this._undetermined=function(){if(null!==this.element){var a=this.get_undetermined(!1),b,c,d;for(this.element.find(".jstree-undetermined").removeClass("jstree-undetermined"),b=0,c=a.length;c>b;b++)d=this.get_node(a[b],!0),d&&d.length&&d.children(".jstree-anchor").children(".jstree-checkbox").addClass("jstree-undetermined")}},this.redraw_node=function(b,c,e,f){if(b=d.redraw_node.apply(this,arguments)){var g,h,i=null,k=null;for(g=0,h=b.childNodes.length;h>g;g++)if(b.childNodes[g]&&b.childNodes[g].className&&-1!==b.childNodes[g].className.indexOf("jstree-anchor")){i=b.childNodes[g];break}i&&(!this.settings.checkbox.tie_selection&&this._model.data[b.id].state.checked&&(i.className+=" jstree-checked"),k=j.cloneNode(!1),this._model.data[b.id].state.checkbox_disabled&&(k.className+=" jstree-checkbox-disabled"),i.insertBefore(k,i.childNodes[0]))}return e||-1===this.settings.checkbox.cascade.indexOf("undetermined")||(this._data.checkbox.uto&&clearTimeout(this._data.checkbox.uto),this._data.checkbox.uto=setTimeout(a.proxy(this._undetermined,this),50)),b},this.show_checkboxes=function(){this._data.core.themes.checkboxes=!0,this.get_container_ul().removeClass("jstree-no-checkboxes")},this.hide_checkboxes=function(){this._data.core.themes.checkboxes=!1,this.get_container_ul().addClass("jstree-no-checkboxes")},this.toggle_checkboxes=function(){this._data.core.themes.checkboxes?this.hide_checkboxes():this.show_checkboxes()},this.is_undetermined=function(b){b=this.get_node(b);var c=this.settings.checkbox.cascade,d,e,f=this.settings.checkbox.tie_selection,g=this._data[f?"core":"checkbox"].selected,h=this._model.data;if(!b||b.state[f?"selected":"checked"]===!0||-1===c.indexOf("undetermined")||-1===c.indexOf("down")&&-1===c.indexOf("up"))return!1;if(!b.state.loaded&&b.original.state.undetermined===!0)return!0;for(d=0,e=b.children_d.length;e>d;d++)if(-1!==a.inArray(b.children_d[d],g)||!h[b.children_d[d]].state.loaded&&h[b.children_d[d]].original.state.undetermined)return!0;return!1},this.disable_checkbox=function(b){var c,d,e;if(a.isArray(b)){for(b=b.slice(),c=0,d=b.length;d>c;c++)this.disable_checkbox(b[c]);return!0}return b=this.get_node(b),b&&b.id!==a.jstree.root?(e=this.get_node(b,!0),void(b.state.checkbox_disabled||(b.state.checkbox_disabled=!0,e&&e.length&&e.children(".jstree-anchor").children(".jstree-checkbox").addClass("jstree-checkbox-disabled"),this.trigger("disable_checkbox",{node:b})))):!1},this.enable_checkbox=function(b){var c,d,e;if(a.isArray(b)){for(b=b.slice(),c=0,d=b.length;d>c;c++)this.enable_checkbox(b[c]);return!0}return b=this.get_node(b),b&&b.id!==a.jstree.root?(e=this.get_node(b,!0),void(b.state.checkbox_disabled&&(b.state.checkbox_disabled=!1,e&&e.length&&e.children(".jstree-anchor").children(".jstree-checkbox").removeClass("jstree-checkbox-disabled"),this.trigger("enable_checkbox",{node:b})))):!1},this.activate_node=function(b,c){return a(c.target).hasClass("jstree-checkbox-disabled")?!1:(this.settings.checkbox.tie_selection&&(this.settings.checkbox.whole_node||a(c.target).hasClass("jstree-checkbox"))&&(c.ctrlKey=!0),this.settings.checkbox.tie_selection||!this.settings.checkbox.whole_node&&!a(c.target).hasClass("jstree-checkbox")?d.activate_node.call(this,b,c):this.is_disabled(b)?!1:(this.is_checked(b)?this.uncheck_node(b,c):this.check_node(b,c),void this.trigger("activate_node",{node:this.get_node(b)})))},this._cascade_new_checked_state=function(a,b){var c=this,d=this.settings.checkbox.tie_selection,e=this._model.data[a],f=[],g=[],h,i,j;if(!this.settings.checkbox.cascade_to_disabled&&e.state.disabled||!this.settings.checkbox.cascade_to_hidden&&e.state.hidden)j=this.get_checked_descendants(a),e.state[d?"selected":"checked"]&&j.push(e.id),f=f.concat(j);else{if(e.children)for(h=0,i=e.children.length;i>h;h++){var k=e.children[h];j=c._cascade_new_checked_state(k,b),f=f.concat(j),j.indexOf(k)>-1&&g.push(k)}var l=c.get_node(e,!0),m=g.length>0&&g.lengthe;e++)this.check_node(b[e],c);return!0}return b=this.get_node(b),b&&b.id!==a.jstree.root?(d=this.get_node(b,!0),void(b.state.checked||(b.state.checked=!0,this._data.checkbox.selected.push(b.id),d&&d.length&&d.children(".jstree-anchor").addClass("jstree-checked"),this.trigger("check_node",{node:b,selected:this._data.checkbox.selected,event:c})))):!1},this.uncheck_node=function(b,c){if(this.settings.checkbox.tie_selection)return this.deselect_node(b,!1,c);var d,e,f;if(a.isArray(b)){for(b=b.slice(),d=0,e=b.length;e>d;d++)this.uncheck_node(b[d],c);return!0}return b=this.get_node(b),b&&b.id!==a.jstree.root?(f=this.get_node(b,!0),void(b.state.checked&&(b.state.checked=!1,this._data.checkbox.selected=a.vakata.array_remove_item(this._data.checkbox.selected,b.id),f.length&&f.children(".jstree-anchor").removeClass("jstree-checked"),this.trigger("uncheck_node",{node:b,selected:this._data.checkbox.selected,event:c})))):!1},this.check_all=function(){if(this.settings.checkbox.tie_selection)return this.select_all();var b=this._data.checkbox.selected.concat([]),c,d;for(this._data.checkbox.selected=this._model.data[a.jstree.root].children_d.concat(),c=0,d=this._data.checkbox.selected.length;d>c;c++)this._model.data[this._data.checkbox.selected[c]]&&(this._model.data[this._data.checkbox.selected[c]].state.checked=!0);this.redraw(!0),this.trigger("check_all",{selected:this._data.checkbox.selected})},this.uncheck_all=function(){if(this.settings.checkbox.tie_selection)return this.deselect_all();var a=this._data.checkbox.selected.concat([]),b,c;for(b=0,c=this._data.checkbox.selected.length;c>b;b++)this._model.data[this._data.checkbox.selected[b]]&&(this._model.data[this._data.checkbox.selected[b]].state.checked=!1);this._data.checkbox.selected=[],this.element.find(".jstree-checked").removeClass("jstree-checked"),this.trigger("uncheck_all",{selected:this._data.checkbox.selected,node:a})},this.is_checked=function(b){return this.settings.checkbox.tie_selection?this.is_selected(b):(b=this.get_node(b),b&&b.id!==a.jstree.root?b.state.checked:!1)},this.get_checked=function(b){return this.settings.checkbox.tie_selection?this.get_selected(b):b?a.map(this._data.checkbox.selected,a.proxy(function(a){return this.get_node(a)},this)):this._data.checkbox.selected},this.get_top_checked=function(b){if(this.settings.checkbox.tie_selection)return this.get_top_selected(b);var c=this.get_checked(!0),d={},e,f,g,h;for(e=0,f=c.length;f>e;e++)d[c[e].id]=c[e];for(e=0,f=c.length;f>e;e++)for(g=0,h=c[e].children_d.length;h>g;g++)d[c[e].children_d[g]]&&delete d[c[e].children_d[g]];c=[];for(e in d)d.hasOwnProperty(e)&&c.push(e);return b?a.map(c,a.proxy(function(a){return this.get_node(a)},this)):c},this.get_bottom_checked=function(b){if(this.settings.checkbox.tie_selection)return this.get_bottom_selected(b);var c=this.get_checked(!0),d=[],e,f;for(e=0,f=c.length;f>e;e++)c[e].children.length||d.push(c[e].id);return b?a.map(d,a.proxy(function(a){return this.get_node(a)},this)):d},this.load_node=function(b,c){var e,f,g,h,i,j;if(!a.isArray(b)&&!this.settings.checkbox.tie_selection&&(j=this.get_node(b),j&&j.state.loaded))for(e=0,f=j.children_d.length;f>e;e++)this._model.data[j.children_d[e]].state.checked&&(i=!0,this._data.checkbox.selected=a.vakata.array_remove_item(this._data.checkbox.selected,j.children_d[e]));return d.load_node.apply(this,arguments)},this.get_state=function(){var a=d.get_state.apply(this,arguments);return this.settings.checkbox.tie_selection?a:(a.checkbox=this._data.checkbox.selected.slice(),a)},this.set_state=function(b,c){var e=d.set_state.apply(this,arguments);if(e&&b.checkbox){if(!this.settings.checkbox.tie_selection){this.uncheck_all();var f=this;a.each(b.checkbox,function(a,b){f.check_node(b)})}return delete b.checkbox,this.set_state(b,c),!1}return e},this.refresh=function(a,b){return this.settings.checkbox.tie_selection&&(this._data.checkbox.selected=[]),d.refresh.apply(this,arguments)}},a.jstree.defaults.conditionalselect=function(){return!0},a.jstree.plugins.conditionalselect=function(a,b){this.activate_node=function(a,c){return this.settings.conditionalselect.call(this,this.get_node(a),c)?b.activate_node.call(this,a,c):void 0}},a.jstree.defaults.contextmenu={select_node:!0,show_at_node:!0,items:function(b,c){return{create:{separator_before:!1,separator_after:!0,_disabled:!1,label:"Create",action:function(b){var c=a.jstree.reference(b.reference),d=c.get_node(b.reference);c.create_node(d,{},"last",function(a){try{c.edit(a)}catch(b){setTimeout(function(){c.edit(a)},0)}})}},rename:{separator_before:!1,separator_after:!1,_disabled:!1,label:"Rename",action:function(b){var c=a.jstree.reference(b.reference),d=c.get_node(b.reference);c.edit(d)}},remove:{separator_before:!1,icon:!1,separator_after:!1,_disabled:!1,label:"Delete",action:function(b){var c=a.jstree.reference(b.reference),d=c.get_node(b.reference);c.is_selected(d)?c.delete_node(c.get_selected()):c.delete_node(d)}},ccp:{separator_before:!0,icon:!1,separator_after:!1,label:"Edit",action:!1,submenu:{cut:{separator_before:!1,separator_after:!1,label:"Cut",action:function(b){var c=a.jstree.reference(b.reference),d=c.get_node(b.reference);c.is_selected(d)?c.cut(c.get_top_selected()):c.cut(d)}},copy:{separator_before:!1,icon:!1,separator_after:!1,label:"Copy",action:function(b){var c=a.jstree.reference(b.reference),d=c.get_node(b.reference);c.is_selected(d)?c.copy(c.get_top_selected()):c.copy(d)}},paste:{separator_before:!1,icon:!1,_disabled:function(b){return!a.jstree.reference(b.reference).can_paste()},separator_after:!1,label:"Paste",action:function(b){var c=a.jstree.reference(b.reference),d=c.get_node(b.reference);c.paste(d)}}}}}}},a.jstree.plugins.contextmenu=function(c,d){this.bind=function(){d.bind.call(this);var b=0,c=null,e,f;this.element.on("init.jstree loading.jstree ready.jstree",a.proxy(function(){this.get_container_ul().addClass("jstree-contextmenu")},this)).on("contextmenu.jstree",".jstree-anchor",a.proxy(function(a,d){"input"!==a.target.tagName.toLowerCase()&&(a.preventDefault(),b=a.ctrlKey?+new Date:0,(d||c)&&(b=+new Date+1e4),c&&clearTimeout(c),this.is_loading(a.currentTarget)||this.show_contextmenu(a.currentTarget,a.pageX,a.pageY,a))},this)).on("click.jstree",".jstree-anchor",a.proxy(function(c){this._data.contextmenu.visible&&(!b||+new Date-b>250)&&a.vakata.context.hide(),b=0},this)).on("touchstart.jstree",".jstree-anchor",function(b){b.originalEvent&&b.originalEvent.changedTouches&&b.originalEvent.changedTouches[0]&&(e=b.originalEvent.changedTouches[0].clientX,f=b.originalEvent.changedTouches[0].clientY,c=setTimeout(function(){a(b.currentTarget).trigger("contextmenu",!0)},750))}).on("touchmove.vakata.jstree",function(b){c&&b.originalEvent&&b.originalEvent.changedTouches&&b.originalEvent.changedTouches[0]&&(Math.abs(e-b.originalEvent.changedTouches[0].clientX)>10||Math.abs(f-b.originalEvent.changedTouches[0].clientY)>10)&&(clearTimeout(c),a.vakata.context.hide())}).on("touchend.vakata.jstree",function(a){c&&clearTimeout(c)}),a(i).on("context_hide.vakata.jstree",a.proxy(function(b,c){this._data.contextmenu.visible=!1,a(c.reference).removeClass("jstree-context")},this))},this.teardown=function(){this._data.contextmenu.visible&&a.vakata.context.hide(),d.teardown.call(this)},this.show_contextmenu=function(c,d,e,f){if(c=this.get_node(c),!c||c.id===a.jstree.root)return!1;var g=this.settings.contextmenu,h=this.get_node(c,!0),i=h.children(".jstree-anchor"),j=!1,k=!1;(g.show_at_node||d===b||e===b)&&(j=i.offset(),d=j.left,e=j.top+this._data.core.li_height),this.settings.contextmenu.select_node&&!this.is_selected(c)&&this.activate_node(c,f),k=g.items,a.isFunction(k)&&(k=k.call(this,c,a.proxy(function(a){this._show_contextmenu(c,d,e,a)},this))),a.isPlainObject(k)&&this._show_contextmenu(c,d,e,k)},this._show_contextmenu=function(b,c,d,e){var f=this.get_node(b,!0),g=f.children(".jstree-anchor");a(i).one("context_show.vakata.jstree",a.proxy(function(b,c){var d="jstree-contextmenu jstree-"+this.get_theme()+"-contextmenu";a(c.element).addClass(d),g.addClass("jstree-context")},this)),this._data.contextmenu.visible=!0,a.vakata.context.show(g,{x:c,y:d},e),this.trigger("show_contextmenu",{node:b,x:c,y:d})}},function(a){var b=!1,c={element:!1,reference:!1,position_x:0,position_y:0,items:[],html:"",is_visible:!1};a.vakata.context={settings:{hide_onmouseleave:0,icons:!0},_trigger:function(b){a(i).triggerHandler("context_"+b+".vakata",{reference:c.reference,element:c.element,position:{x:c.position_x,y:c.position_y}})},_execute:function(b){return b=c.items[b],b&&(!b._disabled||a.isFunction(b._disabled)&&!b._disabled({item:b,reference:c.reference,element:c.element}))&&b.action?b.action.call(null,{item:b,reference:c.reference,element:c.element,position:{x:c.position_x,y:c.position_y}}):!1},_parse:function(b,d){if(!b)return!1;d||(c.html="",c.items=[]);var e="",f=!1,g;return d&&(e+=""),d||(c.html=e,a.vakata.context._trigger("parse")),e.length>10?e:!1},_show_submenu:function(c){if(c=a(c),c.length&&c.children("ul").length){var d=c.children("ul"),e=c.offset().left,f=e+c.outerWidth(),g=c.offset().top,h=d.width(),i=d.height(),j=a(window).width()+a(window).scrollLeft(),k=a(window).height()+a(window).scrollTop();b?c[f-(h+10+c.outerWidth())<0?"addClass":"removeClass"]("vakata-context-left"):c[f+h>j&&e>j-f?"addClass":"removeClass"]("vakata-context-right"),g+i+10>k&&d.css("bottom","-1px"),c.hasClass("vakata-context-right")?h>e&&d.css("margin-right",e-h):h>j-f&&d.css("margin-left",j-f-h),d.show()}},show:function(d,e,f){var g,h,j,k,l,m,n,o,p=!0;switch(c.element&&c.element.length&&c.element.width(""),p){case!e&&!d:return!1;case!!e&&!!d:c.reference=d,c.position_x=e.x,c.position_y=e.y;break;case!e&&!!d:c.reference=d,g=d.offset(),c.position_x=g.left+d.outerHeight(),c.position_y=g.top;break;case!!e&&!d:c.position_x=e.x,c.position_y=e.y}d&&!f&&a(d).data("vakata_contextmenu")&&(f=a(d).data("vakata_contextmenu")),a.vakata.context._parse(f)&&c.element.html(c.html),c.items.length&&(c.element.appendTo(i.body),h=c.element,j=c.position_x,k=c.position_y,l=h.width(),m=h.height(),n=a(window).width()+a(window).scrollLeft(),o=a(window).height()+a(window).scrollTop(),b&&(j-=h.outerWidth()-a(d).outerWidth(),jn&&(j=n-(l+20)),k+m+20>o&&(k=o-(m+20)),c.element.css({left:j,top:k}).show().find("a").first().focus().parent().addClass("vakata-context-hover"),c.is_visible=!0,a.vakata.context._trigger("show"))},hide:function(){c.is_visible&&(c.element.hide().find("ul").hide().end().find(":focus").blur().end().detach(),c.is_visible=!1,a.vakata.context._trigger("hide"))}},a(function(){b="rtl"===a(i.body).css("direction");var d=!1;c.element=a("
    "),c.element.on("mouseenter","li",function(b){b.stopImmediatePropagation(),a.contains(this,b.relatedTarget)||(d&&clearTimeout(d),c.element.find(".vakata-context-hover").removeClass("vakata-context-hover").end(),a(this).siblings().find("ul").hide().end().end().parentsUntil(".vakata-context","li").addBack().addClass("vakata-context-hover"),a.vakata.context._show_submenu(this))}).on("mouseleave","li",function(b){a.contains(this,b.relatedTarget)||a(this).find(".vakata-context-hover").addBack().removeClass("vakata-context-hover")}).on("mouseleave",function(b){a(this).find(".vakata-context-hover").removeClass("vakata-context-hover"),a.vakata.context.settings.hide_onmouseleave&&(d=setTimeout(function(b){return function(){a.vakata.context.hide()}}(this),a.vakata.context.settings.hide_onmouseleave))}).on("click","a",function(b){b.preventDefault(),a(this).blur().parent().hasClass("vakata-context-disabled")||a.vakata.context._execute(a(this).attr("rel"))===!1||a.vakata.context.hide()}).on("keydown","a",function(b){var d=null;switch(b.which){case 13:case 32:b.type="click",b.preventDefault(),a(b.currentTarget).trigger(b);break;case 37:c.is_visible&&(c.element.find(".vakata-context-hover").last().closest("li").first().find("ul").hide().find(".vakata-context-hover").removeClass("vakata-context-hover").end().end().children("a").focus(),b.stopImmediatePropagation(),b.preventDefault());break;case 38:c.is_visible&&(d=c.element.find("ul:visible").addBack().last().children(".vakata-context-hover").removeClass("vakata-context-hover").prevAll("li:not(.vakata-context-separator)").first(),d.length||(d=c.element.find("ul:visible").addBack().last().children("li:not(.vakata-context-separator)").last()),d.addClass("vakata-context-hover").children("a").focus(),b.stopImmediatePropagation(),b.preventDefault());break;case 39:c.is_visible&&(c.element.find(".vakata-context-hover").last().children("ul").show().children("li:not(.vakata-context-separator)").removeClass("vakata-context-hover").first().addClass("vakata-context-hover").children("a").focus(),b.stopImmediatePropagation(),b.preventDefault());break;case 40:c.is_visible&&(d=c.element.find("ul:visible").addBack().last().children(".vakata-context-hover").removeClass("vakata-context-hover").nextAll("li:not(.vakata-context-separator)").first(),d.length||(d=c.element.find("ul:visible").addBack().last().children("li:not(.vakata-context-separator)").first()),d.addClass("vakata-context-hover").children("a").focus(),b.stopImmediatePropagation(),b.preventDefault());break;case 27:a.vakata.context.hide(),b.preventDefault()}}).on("keydown",function(a){a.preventDefault();var b=c.element.find(".vakata-contextmenu-shortcut-"+a.which).parent();b.parent().not(".vakata-context-disabled")&&b.click()}),a(i).on("mousedown.vakata.jstree",function(b){c.is_visible&&c.element[0]!==b.target&&!a.contains(c.element[0],b.target)&&a.vakata.context.hide()}).on("context_show.vakata.jstree",function(a,d){c.element.find("li:has(ul)").children("a").addClass("vakata-context-parent"),b&&c.element.addClass("vakata-context-rtl").css("direction","rtl"),c.element.find("ul").hide().end()})})}(a),a.jstree.defaults.dnd={copy:!0,open_timeout:500,is_draggable:!0,check_while_dragging:!0,always_copy:!1,inside_pos:0,drag_selection:!0,touch:!0,large_drop_target:!1,large_drag_target:!1,use_html5:!1};var k,l;a.jstree.plugins.dnd=function(b,c){this.init=function(a,b){c.init.call(this,a,b),this.settings.dnd.use_html5=this.settings.dnd.use_html5&&"draggable"in i.createElement("span")},this.bind=function(){c.bind.call(this),this.element.on(this.settings.dnd.use_html5?"dragstart.jstree":"mousedown.jstree touchstart.jstree",this.settings.dnd.large_drag_target?".jstree-node":".jstree-anchor",a.proxy(function(b){if(this.settings.dnd.large_drag_target&&a(b.target).closest(".jstree-node")[0]!==b.currentTarget)return!0;if("touchstart"===b.type&&(!this.settings.dnd.touch||"selected"===this.settings.dnd.touch&&!a(b.currentTarget).closest(".jstree-node").children(".jstree-anchor").hasClass("jstree-clicked")))return!0;var c=this.get_node(b.target),d=this.is_selected(c)&&this.settings.dnd.drag_selection?this.get_top_selected().length:1,e=d>1?d+" "+this.get_string("nodes"):this.get_text(b.currentTarget);if(this.settings.core.force_text&&(e=a.vakata.html.escape(e)),c&&c.id&&c.id!==a.jstree.root&&(1===b.which||"touchstart"===b.type||"dragstart"===b.type)&&(this.settings.dnd.is_draggable===!0||a.isFunction(this.settings.dnd.is_draggable)&&this.settings.dnd.is_draggable.call(this,d>1?this.get_top_selected(!0):[c],b))){if(k={jstree:!0,origin:this,obj:this.get_node(c,!0),nodes:d>1?this.get_top_selected():[c.id]},l=b.currentTarget,!this.settings.dnd.use_html5)return this.element.trigger("mousedown.jstree"),a.vakata.dnd.start(b,k,'
    '+e+'
    ');a.vakata.dnd._trigger("start",b,{helper:a(),element:l,data:k})}},this)),this.settings.dnd.use_html5&&this.element.on("dragover.jstree",function(b){return b.preventDefault(),a.vakata.dnd._trigger("move",b,{helper:a(),element:l,data:k}),!1}).on("drop.jstree",a.proxy(function(b){return b.preventDefault(),a.vakata.dnd._trigger("stop",b,{helper:a(),element:l,data:k}),!1},this))},this.redraw_node=function(a,b,d,e){if(a=c.redraw_node.apply(this,arguments),a&&this.settings.dnd.use_html5)if(this.settings.dnd.large_drag_target)a.setAttribute("draggable",!0);else{var f,g,h=null;for(f=0,g=a.childNodes.length;g>f;f++)if(a.childNodes[f]&&a.childNodes[f].className&&-1!==a.childNodes[f].className.indexOf("jstree-anchor")){h=a.childNodes[f];break}h&&h.setAttribute("draggable",!0)}return a}},a(function(){var c=!1,d=!1,e=!1,f=!1,g=a('
     
    ').hide();a(i).on("dragover.vakata.jstree",function(b){l&&a.vakata.dnd._trigger("move",b,{helper:a(),element:l,data:k})}).on("drop.vakata.jstree",function(b){l&&(a.vakata.dnd._trigger("stop",b,{helper:a(),element:l,data:k}),l=null,k=null)}).on("dnd_start.vakata.jstree",function(a,b){c=!1,e=!1,b&&b.data&&b.data.jstree&&g.appendTo(i.body)}).on("dnd_move.vakata.jstree",function(h,i){var j=i.event.target!==e.target;if(f&&(!i.event||"dragover"!==i.event.type||j)&&clearTimeout(f),i&&i.data&&i.data.jstree&&(!i.event.target.id||"jstree-marker"!==i.event.target.id)){e=i.event;var k=a.jstree.reference(i.event.target),l=!1,m=!1,n=!1,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E;if(k&&k._data&&k._data.dnd)if(g.attr("class","jstree-"+k.get_theme()+(k.settings.core.themes.responsive?" jstree-dnd-responsive":"")),D=i.data.origin&&(i.data.origin.settings.dnd.always_copy||i.data.origin.settings.dnd.copy&&(i.event.metaKey||i.event.ctrlKey)),i.helper.children().attr("class","jstree-"+k.get_theme()+" jstree-"+k.get_theme()+"-"+k.get_theme_variant()+" "+(k.settings.core.themes.responsive?" jstree-dnd-responsive":"")).find(".jstree-copy").first()[D?"show":"hide"](),i.event.target!==k.element[0]&&i.event.target!==k.get_container_ul()[0]||0!==k.get_container_ul().children().length){if(l=k.settings.dnd.large_drop_target?a(i.event.target).closest(".jstree-node").children(".jstree-anchor"):a(i.event.target).closest(".jstree-anchor"),l&&l.length&&l.parent().is(".jstree-closed, .jstree-open, .jstree-leaf")&&(m=l.offset(),n=(i.event.pageY!==b?i.event.pageY:i.event.originalEvent.pageY)-m.top,r=l.outerHeight(),u=r/3>n?["b","i","a"]:n>r-r/3?["a","i","b"]:n>r/2?["i","a","b"]:["i","b","a"],a.each(u,function(b,e){switch(e){case"b":p=m.left-6,q=m.top,s=k.get_parent(l),t=l.parent().index();break;case"i":B=k.settings.dnd.inside_pos,C=k.get_node(l.parent()),p=m.left-2,q=m.top+r/2+1,s=C.id,t="first"===B?0:"last"===B?C.children.length:Math.min(B,C.children.length);break;case"a":p=m.left-6,q=m.top+r,s=k.get_parent(l),t=l.parent().index()+1}for(v=!0,w=0,x=i.data.nodes.length;x>w;w++)if(y=i.data.origin&&(i.data.origin.settings.dnd.always_copy||i.data.origin.settings.dnd.copy&&(i.event.metaKey||i.event.ctrlKey))?"copy_node":"move_node",z=t,"move_node"===y&&"a"===e&&i.data.origin&&i.data.origin===k&&s===k.get_parent(i.data.nodes[w])&&(A=k.get_node(s),z>a.inArray(i.data.nodes[w],A.children)&&(z-=1)),v=v&&(k&&k.settings&&k.settings.dnd&&k.settings.dnd.check_while_dragging===!1||k.check(y,i.data.origin&&i.data.origin!==k?i.data.origin.get_node(i.data.nodes[w]):i.data.nodes[w],s,z,{dnd:!0,ref:k.get_node(l.parent()),pos:e,origin:i.data.origin,is_multi:i.data.origin&&i.data.origin!==k,is_foreign:!i.data.origin})),!v){k&&k.last_error&&(d=k.last_error());break}return"i"===e&&l.parent().is(".jstree-closed")&&k.settings.dnd.open_timeout&&(!i.event||"dragover"!==i.event.type||j)&&(f&&clearTimeout(f),f=setTimeout(function(a,b){return function(){a.open_node(b)}}(k,l),k.settings.dnd.open_timeout)),v?(E=k.get_node(s,!0),E.hasClass(".jstree-dnd-parent")||(a(".jstree-dnd-parent").removeClass("jstree-dnd-parent"),E.addClass("jstree-dnd-parent")),c={ins:k,par:s,pos:"i"!==e||"last"!==B||0!==t||k.is_loaded(C)?t:"last"},g.css({left:p+"px",top:q+"px"}).show(),i.helper.find(".jstree-icon").first().removeClass("jstree-er").addClass("jstree-ok"),i.event.originalEvent&&i.event.originalEvent.dataTransfer&&(i.event.originalEvent.dataTransfer.dropEffect=D?"copy":"move"),d={},u=!0,!1):void 0}),u===!0))return}else{for(v=!0,w=0,x=i.data.nodes.length;x>w;w++)if(v=v&&k.check(i.data.origin&&(i.data.origin.settings.dnd.always_copy||i.data.origin.settings.dnd.copy&&(i.event.metaKey||i.event.ctrlKey))?"copy_node":"move_node",i.data.origin&&i.data.origin!==k?i.data.origin.get_node(i.data.nodes[w]):i.data.nodes[w],a.jstree.root,"last",{dnd:!0,ref:k.get_node(a.jstree.root),pos:"i",origin:i.data.origin,is_multi:i.data.origin&&i.data.origin!==k,is_foreign:!i.data.origin}),!v)break;if(v)return c={ins:k,par:a.jstree.root,pos:"last"},g.hide(),i.helper.find(".jstree-icon").first().removeClass("jstree-er").addClass("jstree-ok"),void(i.event.originalEvent&&i.event.originalEvent.dataTransfer&&(i.event.originalEvent.dataTransfer.dropEffect=D?"copy":"move"))}a(".jstree-dnd-parent").removeClass("jstree-dnd-parent"),c=!1,i.helper.find(".jstree-icon").removeClass("jstree-ok").addClass("jstree-er"),i.event.originalEvent&&i.event.originalEvent.dataTransfer,g.hide()}}).on("dnd_scroll.vakata.jstree",function(a,b){b&&b.data&&b.data.jstree&&(g.hide(),c=!1,e=!1,b.helper.find(".jstree-icon").first().removeClass("jstree-ok").addClass("jstree-er"))}).on("dnd_stop.vakata.jstree",function(b,h){if(a(".jstree-dnd-parent").removeClass("jstree-dnd-parent"),f&&clearTimeout(f),h&&h.data&&h.data.jstree){g.hide().detach();var i,j,k=[];if(c){for(i=0,j=h.data.nodes.length;j>i;i++)k[i]=h.data.origin?h.data.origin.get_node(h.data.nodes[i]):h.data.nodes[i];c.ins[h.data.origin&&(h.data.origin.settings.dnd.always_copy||h.data.origin.settings.dnd.copy&&(h.event.metaKey||h.event.ctrlKey))?"copy_node":"move_node"](k,c.par,c.pos,!1,!1,!1,h.data.origin)}else i=a(h.event.target).closest(".jstree"),i.length&&d&&d.error&&"check"===d.error&&(i=i.jstree(!0),i&&i.settings.core.error.call(this,d));e=!1,c=!1}}).on("keyup.jstree keydown.jstree",function(b,h){h=a.vakata.dnd._get(),h&&h.data&&h.data.jstree&&("keyup"===b.type&&27===b.which?(f&&clearTimeout(f),c=!1,d=!1,e=!1,f=!1,g.hide().detach(),a.vakata.dnd._clean()):(h.helper.find(".jstree-copy").first()[h.data.origin&&(h.data.origin.settings.dnd.always_copy||h.data.origin.settings.dnd.copy&&(b.metaKey||b.ctrlKey))?"show":"hide"](),e&&(e.metaKey=b.metaKey,e.ctrlKey=b.ctrlKey,a.vakata.dnd._trigger("move",e))))})}),function(a){a.vakata.html={div:a("
    "),escape:function(b){return a.vakata.html.div.text(b).html()},strip:function(b){return a.vakata.html.div.empty().append(a.parseHTML(b)).text()}};var c={element:!1,target:!1,is_down:!1,is_drag:!1,helper:!1,helper_w:0,data:!1,init_x:0,init_y:0,scroll_l:0,scroll_t:0,scroll_e:!1,scroll_i:!1,is_touch:!1};a.vakata.dnd={settings:{scroll_speed:10,scroll_proximity:20,helper_left:5,helper_top:10,threshold:5,threshold_touch:10},_trigger:function(c,d,e){e===b&&(e=a.vakata.dnd._get()),e.event=d,a(i).triggerHandler("dnd_"+c+".vakata",e)},_get:function(){return{data:c.data,element:c.element,helper:c.helper}},_clean:function(){c.helper&&c.helper.remove(),c.scroll_i&&(clearInterval(c.scroll_i),c.scroll_i=!1),c={element:!1,target:!1,is_down:!1,is_drag:!1,helper:!1,helper_w:0,data:!1,init_x:0,init_y:0,scroll_l:0,scroll_t:0,scroll_e:!1,scroll_i:!1,is_touch:!1},a(i).off("mousemove.vakata.jstree touchmove.vakata.jstree",a.vakata.dnd.drag),a(i).off("mouseup.vakata.jstree touchend.vakata.jstree",a.vakata.dnd.stop)},_scroll:function(b){if(!c.scroll_e||!c.scroll_l&&!c.scroll_t)return c.scroll_i&&(clearInterval(c.scroll_i),c.scroll_i=!1),!1;if(!c.scroll_i)return c.scroll_i=setInterval(a.vakata.dnd._scroll,100),!1;if(b===!0)return!1;var d=c.scroll_e.scrollTop(),e=c.scroll_e.scrollLeft();c.scroll_e.scrollTop(d+c.scroll_t*a.vakata.dnd.settings.scroll_speed),c.scroll_e.scrollLeft(e+c.scroll_l*a.vakata.dnd.settings.scroll_speed),(d!==c.scroll_e.scrollTop()||e!==c.scroll_e.scrollLeft())&&a.vakata.dnd._trigger("scroll",c.scroll_e)},start:function(b,d,e){"touchstart"===b.type&&b.originalEvent&&b.originalEvent.changedTouches&&b.originalEvent.changedTouches[0]&&(b.pageX=b.originalEvent.changedTouches[0].pageX,b.pageY=b.originalEvent.changedTouches[0].pageY,b.target=i.elementFromPoint(b.originalEvent.changedTouches[0].pageX-window.pageXOffset,b.originalEvent.changedTouches[0].pageY-window.pageYOffset)),c.is_drag&&a.vakata.dnd.stop({});try{b.currentTarget.unselectable="on",b.currentTarget.onselectstart=function(){return!1},b.currentTarget.style&&(b.currentTarget.style.touchAction="none",b.currentTarget.style.msTouchAction="none",b.currentTarget.style.MozUserSelect="none")}catch(f){}return c.init_x=b.pageX,c.init_y=b.pageY,c.data=d,c.is_down=!0,c.element=b.currentTarget,c.target=b.target,c.is_touch="touchstart"===b.type,e!==!1&&(c.helper=a("
    ").html(e).css({display:"block",margin:"0",padding:"0",position:"absolute",top:"-2000px",lineHeight:"16px",zIndex:"10000"})),a(i).on("mousemove.vakata.jstree touchmove.vakata.jstree",a.vakata.dnd.drag),a(i).on("mouseup.vakata.jstree touchend.vakata.jstree",a.vakata.dnd.stop),!1},drag:function(b){if("touchmove"===b.type&&b.originalEvent&&b.originalEvent.changedTouches&&b.originalEvent.changedTouches[0]&&(b.pageX=b.originalEvent.changedTouches[0].pageX,b.pageY=b.originalEvent.changedTouches[0].pageY,b.target=i.elementFromPoint(b.originalEvent.changedTouches[0].pageX-window.pageXOffset,b.originalEvent.changedTouches[0].pageY-window.pageYOffset)),c.is_down){if(!c.is_drag){if(!(Math.abs(b.pageX-c.init_x)>(c.is_touch?a.vakata.dnd.settings.threshold_touch:a.vakata.dnd.settings.threshold)||Math.abs(b.pageY-c.init_y)>(c.is_touch?a.vakata.dnd.settings.threshold_touch:a.vakata.dnd.settings.threshold)))return;c.helper&&(c.helper.appendTo(i.body),c.helper_w=c.helper.outerWidth()),c.is_drag=!0,a(c.target).one("click.vakata",!1),a.vakata.dnd._trigger("start",b)}var d=!1,e=!1,f=!1,g=!1,h=!1,j=!1,k=!1,l=!1,m=!1,n=!1;return c.scroll_t=0,c.scroll_l=0,c.scroll_e=!1,a(a(b.target).parentsUntil("body").addBack().get().reverse()).filter(function(){return/^auto|scroll$/.test(a(this).css("overflow"))&&(this.scrollHeight>this.offsetHeight||this.scrollWidth>this.offsetWidth)}).each(function(){var d=a(this),e=d.offset();return this.scrollHeight>this.offsetHeight&&(e.top+d.height()-b.pageYthis.offsetWidth&&(e.left+d.width()-b.pageXg&&b.pageY-kg&&g-(b.pageY-k)j&&b.pageX-lj&&j-(b.pageX-l)f&&(m=f-50),h&&n+c.helper_w>h&&(n=h-(c.helper_w+2)),c.helper.css({left:n+"px",top:m+"px"})),a.vakata.dnd._trigger("move",b),!1}},stop:function(b){if("touchend"===b.type&&b.originalEvent&&b.originalEvent.changedTouches&&b.originalEvent.changedTouches[0]&&(b.pageX=b.originalEvent.changedTouches[0].pageX,b.pageY=b.originalEvent.changedTouches[0].pageY,b.target=i.elementFromPoint(b.originalEvent.changedTouches[0].pageX-window.pageXOffset,b.originalEvent.changedTouches[0].pageY-window.pageYOffset)),c.is_drag)b.target!==c.target&&a(c.target).off("click.vakata"),a.vakata.dnd._trigger("stop",b);else if("touchend"===b.type&&b.target===c.target){var d=setTimeout(function(){a(b.target).click()},100);a(b.target).one("click",function(){d&&clearTimeout(d)})}return a.vakata.dnd._clean(),!1}}}(a),a.jstree.defaults.massload=null,a.jstree.plugins.massload=function(b,c){this.init=function(a,b){this._data.massload={},c.init.call(this,a,b)},this._load_nodes=function(b,d,e,f){var g=this.settings.massload,h=JSON.stringify(b),i=[],j=this._model.data,k,l,m;if(!e){for(k=0,l=b.length;l>k;k++)(!j[b[k]]||!j[b[k]].state.loaded&&!j[b[k]].state.failed||f)&&(i.push(b[k]),m=this.get_node(b[k],!0),m&&m.length&&m.addClass("jstree-loading").attr("aria-busy",!0));if(this._data.massload={},i.length){if(a.isFunction(g))return g.call(this,i,a.proxy(function(a){var g,h;if(a)for(g in a)a.hasOwnProperty(g)&&(this._data.massload[g]=a[g]);for(g=0,h=b.length;h>g;g++)m=this.get_node(b[g],!0),m&&m.length&&m.removeClass("jstree-loading").attr("aria-busy",!1);c._load_nodes.call(this,b,d,e,f)},this));if("object"==typeof g&&g&&g.url)return g=a.extend(!0,{},g),a.isFunction(g.url)&&(g.url=g.url.call(this,i)),a.isFunction(g.data)&&(g.data=g.data.call(this,i)),a.ajax(g).done(a.proxy(function(a,g,h){var i,j;if(a)for(i in a)a.hasOwnProperty(i)&&(this._data.massload[i]=a[i]);for(i=0,j=b.length;j>i;i++)m=this.get_node(b[i],!0),m&&m.length&&m.removeClass("jstree-loading").attr("aria-busy",!1);c._load_nodes.call(this,b,d,e,f)},this)).fail(a.proxy(function(a){c._load_nodes.call(this,b,d,e,f)},this))}}return c._load_nodes.call(this,b,d,e,f)},this._load_node=function(b,d){var e=this._data.massload[b.id],f=null,g;return e?(f=this["string"==typeof e?"_append_html_data":"_append_json_data"](b,"string"==typeof e?a(a.parseHTML(e)).filter(function(){return 3!==this.nodeType}):e,function(a){d.call(this,a)}),g=this.get_node(b.id,!0),g&&g.length&&g.removeClass("jstree-loading").attr("aria-busy",!1),delete this._data.massload[b.id],f):c._load_node.call(this,b,d)}},a.jstree.defaults.search={ajax:!1,fuzzy:!1,case_sensitive:!1,show_only_matches:!1,show_only_matches_children:!1,close_opened_onclear:!0,search_leaves_only:!1,search_callback:!1},a.jstree.plugins.search=function(c,d){this.bind=function(){d.bind.call(this),this._data.search.str="",this._data.search.dom=a(),this._data.search.res=[],this._data.search.opn=[],this._data.search.som=!1,this._data.search.smc=!1,this._data.search.hdn=[],this.element.on("search.jstree",a.proxy(function(b,c){if(this._data.search.som&&c.res.length){var d=this._model.data,e,f,g=[],h,i;for(e=0,f=c.res.length;f>e;e++)if(d[c.res[e]]&&!d[c.res[e]].state.hidden&&(g.push(c.res[e]),g=g.concat(d[c.res[e]].parents),this._data.search.smc))for(h=0,i=d[c.res[e]].children_d.length;i>h;h++)d[d[c.res[e]].children_d[h]]&&!d[d[c.res[e]].children_d[h]].state.hidden&&g.push(d[c.res[e]].children_d[h]);g=a.vakata.array_remove_item(a.vakata.array_unique(g),a.jstree.root),this._data.search.hdn=this.hide_all(!0),this.show_node(g,!0),this.redraw(!0)}},this)).on("clear_search.jstree",a.proxy(function(a,b){this._data.search.som&&b.res.length&&(this.show_node(this._data.search.hdn,!0),this.redraw(!0))},this))},this.search=function(c,d,e,f,g,h){if(c===!1||""===a.trim(c.toString()))return this.clear_search();f=this.get_node(f),f=f&&f.id?f.id:null,c=c.toString();var i=this.settings.search,j=i.ajax?i.ajax:!1,k=this._model.data,l=null,m=[],n=[],o,p;if(this._data.search.res.length&&!g&&this.clear_search(),e===b&&(e=i.show_only_matches),h===b&&(h=i.show_only_matches_children),!d&&j!==!1)return a.isFunction(j)?j.call(this,c,a.proxy(function(b){b&&b.d&&(b=b.d),this._load_nodes(a.isArray(b)?a.vakata.array_unique(b):[],function(){this.search(c,!0,e,f,g,h)})},this),f):(j=a.extend({},j),j.data||(j.data={}),j.data.str=c,f&&(j.data.inside=f),this._data.search.lastRequest&&this._data.search.lastRequest.abort(),this._data.search.lastRequest=a.ajax(j).fail(a.proxy(function(){this._data.core.last_error={error:"ajax",plugin:"search",id:"search_01",reason:"Could not load search parents",data:JSON.stringify(j)},this.settings.core.error.call(this,this._data.core.last_error)},this)).done(a.proxy(function(b){b&&b.d&&(b=b.d),this._load_nodes(a.isArray(b)?a.vakata.array_unique(b):[],function(){this.search(c,!0,e,f,g,h)})},this)),this._data.search.lastRequest);if(g||(this._data.search.str=c,this._data.search.dom=a(),this._data.search.res=[],this._data.search.opn=[],this._data.search.som=e,this._data.search.smc=h),l=new a.vakata.search(c,!0,{caseSensitive:i.case_sensitive,fuzzy:i.fuzzy}),a.each(k[f?f:a.jstree.root].children_d,function(a,b){var d=k[b];d.text&&!d.state.hidden&&(!i.search_leaves_only||d.state.loaded&&0===d.children.length)&&(i.search_callback&&i.search_callback.call(this,c,d)||!i.search_callback&&l.search(d.text).isMatch)&&(m.push(b),n=n.concat(d.parents))}),m.length){for(n=a.vakata.array_unique(n),o=0,p=n.length;p>o;o++)n[o]!==a.jstree.root&&k[n[o]]&&this.open_node(n[o],null,0)===!0&&this._data.search.opn.push(n[o]);g?(this._data.search.dom=this._data.search.dom.add(a(this.element[0].querySelectorAll("#"+a.map(m,function(b){return-1!=="0123456789".indexOf(b[0])?"\\3"+b[0]+" "+b.substr(1).replace(a.jstree.idregex,"\\$&"):b.replace(a.jstree.idregex,"\\$&")}).join(", #")))),this._data.search.res=a.vakata.array_unique(this._data.search.res.concat(m))):(this._data.search.dom=a(this.element[0].querySelectorAll("#"+a.map(m,function(b){return-1!=="0123456789".indexOf(b[0])?"\\3"+b[0]+" "+b.substr(1).replace(a.jstree.idregex,"\\$&"):b.replace(a.jstree.idregex,"\\$&")}).join(", #"))),this._data.search.res=m),this._data.search.dom.children(".jstree-anchor").addClass("jstree-search")}this.trigger("search",{nodes:this._data.search.dom,str:c,res:this._data.search.res,show_only_matches:e})},this.clear_search=function(){this.settings.search.close_opened_onclear&&this.close_node(this._data.search.opn,0),this.trigger("clear_search",{nodes:this._data.search.dom,str:this._data.search.str,res:this._data.search.res}),this._data.search.res.length&&(this._data.search.dom=a(this.element[0].querySelectorAll("#"+a.map(this._data.search.res,function(b){return-1!=="0123456789".indexOf(b[0])?"\\3"+b[0]+" "+b.substr(1).replace(a.jstree.idregex,"\\$&"):b.replace(a.jstree.idregex,"\\$&")}).join(", #"))),this._data.search.dom.children(".jstree-anchor").removeClass("jstree-search")),this._data.search.str="",this._data.search.res=[],this._data.search.opn=[],this._data.search.dom=a()},this.redraw_node=function(b,c,e,f){if(b=d.redraw_node.apply(this,arguments),b&&-1!==a.inArray(b.id,this._data.search.res)){var g,h,i=null;for(g=0,h=b.childNodes.length;h>g;g++)if(b.childNodes[g]&&b.childNodes[g].className&&-1!==b.childNodes[g].className.indexOf("jstree-anchor")){i=b.childNodes[g];break}i&&(i.className+=" jstree-search")}return b}},function(a){a.vakata.search=function(b,c,d){d=d||{},d=a.extend({},a.vakata.search.defaults,d),d.fuzzy!==!1&&(d.fuzzy=!0),b=d.caseSensitive?b:b.toLowerCase();var e=d.location,f=d.distance,g=d.threshold,h=b.length,i,j,k,l;return h>32&&(d.fuzzy=!1),d.fuzzy&&(i=1<c;c++)a[b.charAt(c)]=0;for(c=0;h>c;c++)a[b.charAt(c)]|=1<c;c++){o=0,p=q;while(p>o)k(c,e+p)<=m?o=p:q=p,p=Math.floor((q-o)/2+o);for(q=p,s=Math.max(1,e-p+1),t=Math.min(e+p,l)+h,u=new Array(t+2),u[t+1]=(1<=s;f--)if(v=j[a.charAt(f-1)],0===c?u[f]=(u[f+1]<<1|1)&v:u[f]=(u[f+1]<<1|1)&v|((r[f+1]|r[f])<<1|1)|r[f+1],u[f]&i&&(w=k(c,f-1),m>=w)){if(m=w,n=f-1,x.push(n),!(n>e))break;s=Math.max(1,2*e-n)}if(k(c+1,e)>m)break;r=u}return{isMatch:n>=0,score:w}},c===!0?{search:l}:l(c)},a.vakata.search.defaults={location:0,distance:100,threshold:.6,fuzzy:!1,caseSensitive:!1}}(a),a.jstree.defaults.sort=function(a,b){return this.get_text(a)>this.get_text(b)?1:-1},a.jstree.plugins.sort=function(b,c){this.bind=function(){c.bind.call(this),this.element.on("model.jstree",a.proxy(function(a,b){this.sort(b.parent,!0)},this)).on("rename_node.jstree create_node.jstree",a.proxy(function(a,b){this.sort(b.parent||b.node.parent,!1),this.redraw_node(b.parent||b.node.parent,!0)},this)).on("move_node.jstree copy_node.jstree",a.proxy(function(a,b){this.sort(b.parent,!1),this.redraw_node(b.parent,!0)},this))},this.sort=function(b,c){var d,e;if(b=this.get_node(b),b&&b.children&&b.children.length&&(b.children.sort(a.proxy(this.settings.sort,this)),c))for(d=0,e=b.children_d.length;e>d;d++)this.sort(b.children_d[d],!1)}};var m=!1;a.jstree.defaults.state={key:"jstree",events:"changed.jstree open_node.jstree close_node.jstree check_node.jstree uncheck_node.jstree",ttl:!1,filter:!1,preserve_loaded:!1},a.jstree.plugins.state=function(b,c){this.bind=function(){c.bind.call(this);var b=a.proxy(function(){this.element.on(this.settings.state.events,a.proxy(function(){m&&clearTimeout(m),m=setTimeout(a.proxy(function(){this.save_state()},this),100)},this)),this.trigger("state_ready")},this);this.element.on("ready.jstree",a.proxy(function(a,c){this.element.one("restore_state.jstree",b),this.restore_state()||b()},this))},this.save_state=function(){var b=this.get_state();this.settings.state.preserve_loaded||delete b.core.loaded;var c={state:b,ttl:this.settings.state.ttl,sec:+new Date};a.vakata.storage.set(this.settings.state.key,JSON.stringify(c))},this.restore_state=function(){var b=a.vakata.storage.get(this.settings.state.key);if(b)try{b=JSON.parse(b)}catch(c){return!1}return b&&b.ttl&&b.sec&&+new Date-b.sec>b.ttl?!1:(b&&b.state&&(b=b.state),b&&a.isFunction(this.settings.state.filter)&&(b=this.settings.state.filter.call(this,b)),b?(this.settings.state.preserve_loaded||delete b.core.loaded,this.element.one("set_state.jstree",function(c,d){d.instance.trigger("restore_state",{state:a.extend(!0,{},b)})}),this.set_state(b),!0):!1)},this.clear_state=function(){return a.vakata.storage.del(this.settings.state.key)}},function(a,b){a.vakata.storage={set:function(a,b){return window.localStorage.setItem(a,b)},get:function(a){return window.localStorage.getItem(a)},del:function(a){return window.localStorage.removeItem(a)}}}(a),a.jstree.defaults.types={"default":{}},a.jstree.defaults.types[a.jstree.root]={},a.jstree.plugins.types=function(c,d){this.init=function(c,e){var f,g;if(e&&e.types&&e.types["default"])for(f in e.types)if("default"!==f&&f!==a.jstree.root&&e.types.hasOwnProperty(f))for(g in e.types["default"])e.types["default"].hasOwnProperty(g)&&e.types[f][g]===b&&(e.types[f][g]=e.types["default"][g]);d.init.call(this,c,e),this._model.data[a.jstree.root].type=a.jstree.root},this.refresh=function(b,c){d.refresh.call(this,b,c),this._model.data[a.jstree.root].type=a.jstree.root},this.bind=function(){this.element.on("model.jstree",a.proxy(function(c,d){var e=this._model.data,f=d.nodes,g=this.settings.types,h,i,j="default",k;for(h=0,i=f.length;i>h;h++){if(j="default",e[f[h]].original&&e[f[h]].original.type&&g[e[f[h]].original.type]&&(j=e[f[h]].original.type),e[f[h]].data&&e[f[h]].data.jstree&&e[f[h]].data.jstree.type&&g[e[f[h]].data.jstree.type]&&(j=e[f[h]].data.jstree.type),e[f[h]].type=j,e[f[h]].icon===!0&&g[j].icon!==b&&(e[f[h]].icon=g[j].icon),g[j].li_attr!==b&&"object"==typeof g[j].li_attr)for(k in g[j].li_attr)if(g[j].li_attr.hasOwnProperty(k)){if("id"===k)continue;e[f[h]].li_attr[k]===b?e[f[h]].li_attr[k]=g[j].li_attr[k]:"class"===k&&(e[f[h]].li_attr["class"]=g[j].li_attr["class"]+" "+e[f[h]].li_attr["class"])}if(g[j].a_attr!==b&&"object"==typeof g[j].a_attr)for(k in g[j].a_attr)if(g[j].a_attr.hasOwnProperty(k)){if("id"===k)continue;e[f[h]].a_attr[k]===b?e[f[h]].a_attr[k]=g[j].a_attr[k]:"href"===k&&"#"===e[f[h]].a_attr[k]?e[f[h]].a_attr.href=g[j].a_attr.href:"class"===k&&(e[f[h]].a_attr["class"]=g[j].a_attr["class"]+" "+e[f[h]].a_attr["class"])}}e[a.jstree.root].type=a.jstree.root},this)),d.bind.call(this)},this.get_json=function(b,c,e){var f,g,h=this._model.data,i=c?a.extend(!0,{},c,{no_id:!1}):{},j=d.get_json.call(this,b,i,e);if(j===!1)return!1;if(a.isArray(j))for(f=0,g=j.length;g>f;f++)j[f].type=j[f].id&&h[j[f].id]&&h[j[f].id].type?h[j[f].id].type:"default",c&&c.no_id&&(delete j[f].id,j[f].li_attr&&j[f].li_attr.id&&delete j[f].li_attr.id,j[f].a_attr&&j[f].a_attr.id&&delete j[f].a_attr.id);else j.type=j.id&&h[j.id]&&h[j.id].type?h[j.id].type:"default",c&&c.no_id&&(j=this._delete_ids(j));return j},this._delete_ids=function(b){if(a.isArray(b)){for(var c=0,d=b.length;d>c;c++)b[c]=this._delete_ids(b[c]);return b}return delete b.id, -b.li_attr&&b.li_attr.id&&delete b.li_attr.id,b.a_attr&&b.a_attr.id&&delete b.a_attr.id,b.children&&a.isArray(b.children)&&(b.children=this._delete_ids(b.children)),b},this.check=function(c,e,f,g,h){if(d.check.call(this,c,e,f,g,h)===!1)return!1;e=e&&e.id?e:this.get_node(e),f=f&&f.id?f:this.get_node(f);var i=e&&e.id?h&&h.origin?h.origin:a.jstree.reference(e.id):null,j,k,l,m;switch(i=i&&i._model&&i._model.data?i._model.data:null,c){case"create_node":case"move_node":case"copy_node":if("move_node"!==c||-1===a.inArray(e.id,f.children)){if(j=this.get_rules(f),j.max_children!==b&&-1!==j.max_children&&j.max_children===f.children.length)return this._data.core.last_error={error:"check",plugin:"types",id:"types_01",reason:"max_children prevents function: "+c,data:JSON.stringify({chk:c,pos:g,obj:e&&e.id?e.id:!1,par:f&&f.id?f.id:!1})},!1;if(j.valid_children!==b&&-1!==j.valid_children&&-1===a.inArray(e.type||"default",j.valid_children))return this._data.core.last_error={error:"check",plugin:"types",id:"types_02",reason:"valid_children prevents function: "+c,data:JSON.stringify({chk:c,pos:g,obj:e&&e.id?e.id:!1,par:f&&f.id?f.id:!1})},!1;if(i&&e.children_d&&e.parents){for(k=0,l=0,m=e.children_d.length;m>l;l++)k=Math.max(k,i[e.children_d[l]].parents.length);k=k-e.parents.length+1}(0>=k||k===b)&&(k=1);do{if(j.max_depth!==b&&-1!==j.max_depth&&j.max_depthg;g++)this.set_type(c[g],d);return!0}if(f=this.settings.types,c=this.get_node(c),!f[d]||!c)return!1;if(l=this.get_node(c,!0),l&&l.length&&(m=l.children(".jstree-anchor")),i=c.type,j=this.get_icon(c),c.type=d,(j===!0||!f[i]||f[i].icon!==b&&j===f[i].icon)&&this.set_icon(c,f[d].icon!==b?f[d].icon:!0),f[i]&&f[i].li_attr!==b&&"object"==typeof f[i].li_attr)for(k in f[i].li_attr)if(f[i].li_attr.hasOwnProperty(k)){if("id"===k)continue;"class"===k?(e[c.id].li_attr["class"]=(e[c.id].li_attr["class"]||"").replace(f[i].li_attr[k],""),l&&l.removeClass(f[i].li_attr[k])):e[c.id].li_attr[k]===f[i].li_attr[k]&&(e[c.id].li_attr[k]=null,l&&l.removeAttr(k))}if(f[i]&&f[i].a_attr!==b&&"object"==typeof f[i].a_attr)for(k in f[i].a_attr)if(f[i].a_attr.hasOwnProperty(k)){if("id"===k)continue;"class"===k?(e[c.id].a_attr["class"]=(e[c.id].a_attr["class"]||"").replace(f[i].a_attr[k],""),m&&m.removeClass(f[i].a_attr[k])):e[c.id].a_attr[k]===f[i].a_attr[k]&&("href"===k?(e[c.id].a_attr[k]="#",m&&m.attr("href","#")):(delete e[c.id].a_attr[k],m&&m.removeAttr(k)))}if(f[d].li_attr!==b&&"object"==typeof f[d].li_attr)for(k in f[d].li_attr)if(f[d].li_attr.hasOwnProperty(k)){if("id"===k)continue;e[c.id].li_attr[k]===b?(e[c.id].li_attr[k]=f[d].li_attr[k],l&&("class"===k?l.addClass(f[d].li_attr[k]):l.attr(k,f[d].li_attr[k]))):"class"===k&&(e[c.id].li_attr["class"]=f[d].li_attr[k]+" "+e[c.id].li_attr["class"],l&&l.addClass(f[d].li_attr[k]))}if(f[d].a_attr!==b&&"object"==typeof f[d].a_attr)for(k in f[d].a_attr)if(f[d].a_attr.hasOwnProperty(k)){if("id"===k)continue;e[c.id].a_attr[k]===b?(e[c.id].a_attr[k]=f[d].a_attr[k],m&&("class"===k?m.addClass(f[d].a_attr[k]):m.attr(k,f[d].a_attr[k]))):"href"===k&&"#"===e[c.id].a_attr[k]?(e[c.id].a_attr.href=f[d].a_attr.href,m&&m.attr("href",f[d].a_attr.href)):"class"===k&&(e[c.id].a_attr["class"]=f[d].a_attr["class"]+" "+e[c.id].a_attr["class"],m&&m.addClass(f[d].a_attr[k]))}return!0}},a.jstree.defaults.unique={case_sensitive:!1,trim_whitespace:!1,duplicate:function(a,b){return a+" ("+b+")"}},a.jstree.plugins.unique=function(c,d){this.check=function(b,c,e,f,g){if(d.check.call(this,b,c,e,f,g)===!1)return!1;if(c=c&&c.id?c:this.get_node(c),e=e&&e.id?e:this.get_node(e),!e||!e.children)return!0;var h="rename_node"===b?f:c.text,i=[],j=this.settings.unique.case_sensitive,k=this.settings.unique.trim_whitespace,l=this._model.data,m,n,o;for(m=0,n=e.children.length;n>m;m++)o=l[e.children[m]].text,j||(o=o.toLowerCase()),k&&(o=o.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"")),i.push(o);switch(j||(h=h.toLowerCase()),k&&(h=h.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"")),b){case"delete_node":return!0;case"rename_node":return o=c.text||"",j||(o=o.toLowerCase()),k&&(o=o.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"")),m=-1===a.inArray(h,i)||c.text&&o===h,m||(this._data.core.last_error={error:"check",plugin:"unique",id:"unique_01",reason:"Child with name "+h+" already exists. Preventing: "+b,data:JSON.stringify({chk:b,pos:f,obj:c&&c.id?c.id:!1,par:e&&e.id?e.id:!1})}),m;case"create_node":return m=-1===a.inArray(h,i),m||(this._data.core.last_error={error:"check",plugin:"unique",id:"unique_04",reason:"Child with name "+h+" already exists. Preventing: "+b,data:JSON.stringify({chk:b,pos:f,obj:c&&c.id?c.id:!1,par:e&&e.id?e.id:!1})}),m;case"copy_node":return m=-1===a.inArray(h,i),m||(this._data.core.last_error={error:"check",plugin:"unique",id:"unique_02",reason:"Child with name "+h+" already exists. Preventing: "+b,data:JSON.stringify({chk:b,pos:f,obj:c&&c.id?c.id:!1,par:e&&e.id?e.id:!1})}),m;case"move_node":return m=c.parent===e.id&&(!g||!g.is_multi)||-1===a.inArray(h,i),m||(this._data.core.last_error={error:"check",plugin:"unique",id:"unique_03",reason:"Child with name "+h+" already exists. Preventing: "+b,data:JSON.stringify({chk:b,pos:f,obj:c&&c.id?c.id:!1,par:e&&e.id?e.id:!1})}),m}return!0},this.create_node=function(c,e,f,g,h){if(!e||e.text===b){if(null===c&&(c=a.jstree.root),c=this.get_node(c),!c)return d.create_node.call(this,c,e,f,g,h);if(f=f===b?"last":f,!f.toString().match(/^(before|after)$/)&&!h&&!this.is_loaded(c))return d.create_node.call(this,c,e,f,g,h);e||(e={});var i,j,k,l,m,n=this._model.data,o=this.settings.unique.case_sensitive,p=this.settings.unique.trim_whitespace,q=this.settings.unique.duplicate,r;for(j=i=this.get_string("New node"),k=[],l=0,m=c.children.length;m>l;l++)r=n[c.children[l]].text,o||(r=r.toLowerCase()),p&&(r=r.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"")),k.push(r);l=1,r=j,o||(r=r.toLowerCase()),p&&(r=r.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,""));while(-1!==a.inArray(r,k))j=q.call(this,i,++l).toString(),r=j,o||(r=r.toLowerCase()),p&&(r=r.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,""));e.text=j}return d.create_node.call(this,c,e,f,g,h)}};var n=i.createElement("DIV");if(n.setAttribute("unselectable","on"),n.setAttribute("role","presentation"),n.className="jstree-wholerow",n.innerHTML=" ",a.jstree.plugins.wholerow=function(b,c){this.bind=function(){c.bind.call(this),this.element.on("ready.jstree set_state.jstree",a.proxy(function(){this.hide_dots()},this)).on("init.jstree loading.jstree ready.jstree",a.proxy(function(){this.get_container_ul().addClass("jstree-wholerow-ul")},this)).on("deselect_all.jstree",a.proxy(function(a,b){this.element.find(".jstree-wholerow-clicked").removeClass("jstree-wholerow-clicked")},this)).on("changed.jstree",a.proxy(function(a,b){this.element.find(".jstree-wholerow-clicked").removeClass("jstree-wholerow-clicked");var c=!1,d,e;for(d=0,e=b.selected.length;e>d;d++)c=this.get_node(b.selected[d],!0),c&&c.length&&c.children(".jstree-wholerow").addClass("jstree-wholerow-clicked")},this)).on("open_node.jstree",a.proxy(function(a,b){this.get_node(b.node,!0).find(".jstree-clicked").parent().children(".jstree-wholerow").addClass("jstree-wholerow-clicked")},this)).on("hover_node.jstree dehover_node.jstree",a.proxy(function(a,b){"hover_node"===a.type&&this.is_disabled(b.node)||this.get_node(b.node,!0).children(".jstree-wholerow")["hover_node"===a.type?"addClass":"removeClass"]("jstree-wholerow-hovered")},this)).on("contextmenu.jstree",".jstree-wholerow",a.proxy(function(b){if(this._data.contextmenu){b.preventDefault();var c=a.Event("contextmenu",{metaKey:b.metaKey,ctrlKey:b.ctrlKey,altKey:b.altKey,shiftKey:b.shiftKey,pageX:b.pageX,pageY:b.pageY});a(b.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(c)}},this)).on("click.jstree",".jstree-wholerow",function(b){b.stopImmediatePropagation();var c=a.Event("click",{metaKey:b.metaKey,ctrlKey:b.ctrlKey,altKey:b.altKey,shiftKey:b.shiftKey});a(b.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(c).focus()}).on("dblclick.jstree",".jstree-wholerow",function(b){b.stopImmediatePropagation();var c=a.Event("dblclick",{metaKey:b.metaKey,ctrlKey:b.ctrlKey,altKey:b.altKey,shiftKey:b.shiftKey});a(b.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(c).focus()}).on("click.jstree",".jstree-leaf > .jstree-ocl",a.proxy(function(b){b.stopImmediatePropagation();var c=a.Event("click",{metaKey:b.metaKey,ctrlKey:b.ctrlKey,altKey:b.altKey,shiftKey:b.shiftKey});a(b.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(c).focus()},this)).on("mouseover.jstree",".jstree-wholerow, .jstree-icon",a.proxy(function(a){return a.stopImmediatePropagation(),this.is_disabled(a.currentTarget)||this.hover_node(a.currentTarget),!1},this)).on("mouseleave.jstree",".jstree-node",a.proxy(function(a){this.dehover_node(a.currentTarget)},this))},this.teardown=function(){this.settings.wholerow&&this.element.find(".jstree-wholerow").remove(),c.teardown.call(this)},this.redraw_node=function(b,d,e,f){if(b=c.redraw_node.apply(this,arguments)){var g=n.cloneNode(!0);-1!==a.inArray(b.id,this._data.core.selected)&&(g.className+=" jstree-wholerow-clicked"),this._data.core.focused&&this._data.core.focused===b.id&&(g.className+=" jstree-wholerow-hovered"),b.insertBefore(g,b.childNodes[0])}return b}},window.customElements&&Object&&Object.create){var o=Object.create(HTMLElement.prototype);o.createdCallback=function(){var b={core:{},plugins:[]},c;for(c in a.jstree.plugins)a.jstree.plugins.hasOwnProperty(c)&&this.attributes[c]&&(b.plugins.push(c),this.getAttribute(c)&&JSON.parse(this.getAttribute(c))&&(b[c]=JSON.parse(this.getAttribute(c))));for(c in a.jstree.defaults.core)a.jstree.defaults.core.hasOwnProperty(c)&&this.attributes[c]&&(b.core[c]=JSON.parse(this.getAttribute(c))||this.getAttribute(c));a(this).jstree(b)};try{window.customElements.define("vakata-jstree",function(){},{prototype:o})}catch(p){}}}}); \ No newline at end of file diff --git a/cvat/apps/dashboard/static/dashboard/js/3rdparty/jstree/themes/default/32px.png b/cvat/apps/dashboard/static/dashboard/js/3rdparty/jstree/themes/default/32px.png deleted file mode 100644 index 4357b910285aba63ad76538b347febe2c2007c8b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5667 zcmaKwWmHsc)c0q`0cU6!TACrG1`$zEB&3m&6lnn|5owSZx}-t6Q#yvBTSAZ&q#LB9 zYY5>Pyx;fJv(|GyoVC|Jdtb5E+1Ky-?+{gGSwcKoJOBVdC@&}V3IG6#V{Ib{F7|Ds zSj-6kP;ALdNocrE@1)`DXpB$yJPyuItBR042%hSC<1{KLuPRnkRmIcFaM|*f-NJ1^ zK*jY!;i;urI>(-D=T`y=1%?EBfvgaJS$poCG~!`bqOHy@fmng0x)-1##= z(zNa7HAH)AW4_azbbes?skB)Z#PnCUPDde2RyzXlIm9WMIQW&3dQX5=fO@;74rmlkFZ45viAovCBhIH+X9qF`+Ji3X z(R zku1z9CcfV^bea1@3X4mltxs?)zSW~vAT4N`b%B;1oKC3Tk6;POU-Ejrp)#}KusS{FTPfE{?lP@k{QE!Tm?txc;qUI8$zJbQA9*3kgrwBm&jj{Wk z?7E4IUR2W!8Z?%6folkF3EEE`-&fTC+%`s`ld=&W;m{bar7je=YlEYWXv1AD<_V6r ztb|5gGx;pMmvxBFXa-yKNR1UmwDO6+>-+Ho$=r`bp#*aa3RtuBIs<`+=XEW#u^AdI-wbk>5tSCs4mw#)?rkGSm(HWmBRtlZ&^?vVH-`RVy zR*ewr>6nB<1iXSHvkiM{qk$)&*^e?o@l?Ot9?#wDKT)qZ9@XFp%fKCKqheC}xLFMCaWg(dd*|MUY_e2>R!7*>1V?vxxHpmG;Xk5}xZN=^xJpa2M!EZoqH< z9vB?=yKc{lyVCFZJ&qv!_QQg>>5mvF!@FM3>3k9|$|4a$@cOuwe^-|-TzoYcjPbrJTFG>c z!z1Sb%wUgb=a7H9|8e<9QaNYex#ZsU<1OGzC_U>hO%>#*=|mPQdOql*Y=hCCEM;nn zDco`?m%w+J*hJ?=n$IB(_&myBM7pD>s95gw*}r!Q z@aylyO3H*eIcV;Hyz5TY38rT$7_ubom?@{iXMa+5cF-+R!rR{m`Ahh=-*abXe}KNG zE2IR$E8`8qY&4LlS;|tJNiJBX{oq&TRKT0v1CAs!=t%7TGVE9`SOsa|P4j&sMw<3z zrDIqCc#|$mfIrh6M7~bTfg|R}+&;Juu?;fLqW9mQeyd~mscz58j|05h{(h-`o5rm| zMhQ6D0?wEYYAv~eF%ZY`8>RU7IQjyhfB&)_!;TFd5JZN-EtWSmQHIK+6PyioDwL`3 zSu}EGhqzH2;7PhciJE0g=F%<9Pp?-UVJdRs9!&vYX36G47!1M6HpSAe@zFMl?19p2 zwKqt|rsk{e6!&wvjhz+b{2TZ)R2R_GgKfCVAbWRbJ2<3K=KFRLuXxOK{(k93P0>S! zYhB5V0WGPS$P!mT&>%y6q>tInI4m<3?v$B4xPBjml8(+iJUUUoDJqcWX!|XIH>Ha+ znH(XugYc}NIG>WVOZX6)2wbkR4~wt<jGh*aUvx`{L_473{Efq%i?Ai4*V!%>RY}B8sB1< zCh(TVrqoyxV`x)2>!)Dqh>x_=k9v+`_z$Mp+6r!yiMsWRPOt-iQz4r@|qs3^){Wb zQP&+#6_HSC2$)9c?@~bwVR2ypGWof7S;zsb3G-9} zdxf~Pf@B`+5?CqP+vZ_@nZ>_$*Tj*M%$j(lxt5d9L@{obRCxcpl;%f~A_d#b{4wM- z-t-rnL@xYQr%jhJ1v`by<2)5HX`5H%R|tf}{yCujZ@z-b@n6yFTP%_~77C!&I_0b5{Khx%Vy%>gsDrKg5J2Ge>_4&uy@RcVCUU~*W zzs4nzgl7w<)HY%(#0$mv(K>FBr9h|s6Z(P%4>^nQEXa@+5G_HNhFt;qyYGkV`5i;c;eKd27dPoRyeqrgG_f2F7#I)ux-YMT6bc5&iy_r7M{RrUZ*6?$?$~1t%Ymr>dP$(+ zmI*k&tZiEAgJbH*KOi2oqvQ$Kx{%=!(PPR&!5Tn{G)SWbHO;^%F4A`TC9@f3q$qYx z2^=i>Di;YGRTOvYv($(pcz9w@ThgaYw!Zd?M}IY2aNDijCLZU>j7GFK#ZB;G$2<9z z3g)*0LgoWVm0vs<>fqW+R&O8IT{c(QYfYO-caUT9{P>*p4d6$lH{SeukkJ7#!sImi zL9lQG=}6GyrRI%l1wyy;fC}XJ^6&3~qC?aX=TMV>)v7wDqHsv)Ju0=NkMdtkG|_T_fpCp=buNVo z8Kj#w|4jwQHR;W>=gPF~INomy|N7b`)cj1(Fk zq$LQvAm#kz1sKPWaNQ_*S(j?XT;BIz`dia*L{z3koUd(~?ElFP$#>ZZytixi{Qzs2r;&{xDV!dz;r5g<7_p$R>p4sJDkldQm#AH zedQTs^8Cud{1Url>SxvH$;@N`uOHJGj&k>E1=S+ytbtsiXkhR(Ywn#{zW|JL4e4gT z+AuE6Z;Xnm`Pc3w$usmd!!rg><2E5^yo%2V zmG^@E@bsV?nURgzT)TWb*Wc6eEmJKS5|vBoZuE#j*NOQ5!!qTL)@j>j^^(wKc`4w7 zwuPi%BfYs2Q-20CW zyvmc^`$3LKy-Qu|3`U%ddZ#9NZs6!OE-vE5UY@s9IU;S?!4JluZ;yyzIEg)*bH1rb zhIlsCBmG`JD{jb_vGDdUPf6Valb%n;UA-E_#Y3~6UpbSrL6{Au5%9VNt{%@5 z?avk;*@AbG%8*^n*n|bwSPo{>^_uuC?X?~$o#XTi2RkdgD1+)_-Q1TLpKv?Fzf{la z-ARNrzfn8XFS*}xLi9fNX=g2mY|ji;Fj1Bc&Kic{%lC4jQfzM zCbKLM*Uw6@kt}y{FNnwU={MxBQU`7Osr zzk{#v8ttRSf0GzhpWl%={-KeBp1OQtKq;ug&Y{qdgk8nqmHfC&P5auo z&_g_G0^BIb`HLYt5XujLrLq}vXgu1ky?Uq^wFEBo0X%KKS*$J(YNlR(HlDZ6`+cafv4^(V#o2Yx3X{ht zW3+`=qI4|NQ$HtDd~o+lIi^JfA#@VRq^LSp|In&HMfSW%vrw-JtW%AvQ%f{ig)`Yg zKiSGyNi2%e2;<|FkMw`BKsqxFe2E#i7 ztn52$%_vN05^+%%S|uJL=GPz(}KlSsAZFq90X!~+O~EqZf`7i{X!^)PN|i-=v{cgVEd- zOzipNi@em@K|=qDgXgX*lj1)gL$Kv!2h!N4!=mjSBPLrZU$zi@MiTZ%X4f*z*f_!tKV}jAqJycuxj0o? z;Lmkp(LD?9HuQUrwECmLQSyVTO%NOOl}p`Lv}}}^wN}UudD0h-Hsf^ur0snN>B;{_ zcOaiEzEl+b^C^T~|Voqc3K(=C|^C0WORD=>1ibKu9;e=1-+9d~8?y<-BK@)WT*Z`P1G z_j584&)=zVw+5my(>wRig+-KSw-;4EDKyQ#1`-D9F;CZYYL3tkRuM(43O~)USrn&c zS1y^GsoD1bA_NhzUzJ0|e%H7IGb$?flXrA6`?k_BEmD73Yt`Dlw zQ_U@ol0$^bb)T}1C@hzocav$7M>DFA#Lhy#B6{fPNm&hI^1r20RLzWMb=p4QuUgN> z2zMq!9G*J+w|CwloohslzCtSTiV+hN2a&SMxQ=URYsYR_4Cmx`gVK(QbCL&dpg&Rp z*ovW5_CIy>JdLJk#=G#C-=5xy$p{7Ik}mFg4Gu2sQjmS7gk?wKk>ZL7k67&a`EM}n zJ$omLa6KPcK`-l?Fo!AxN@Qj!{G_lDjW zT^LY8ehHdng<&d-z_N?UGV=iu+7gU@MH;2ZvfU&DsZ+r2pR(mhCm<`UBa%EgOebF zvVy>qqTosqDT#E0-r*eK&v1e7@z)cjohE=6!$k^KnyG z(Nh6|K}YisMxPa+3$5eNjI!52L|Jp2WJ z4eagh8H$-Jaz|RR7$1MRRRBYw$|@R~28Mh5P6wTlNM)}>Whf8`w&~$~>?Gx_fE!p; zW~-JmsoX+r^3HjBXLHk^T3@Is+yi|d@T`PHp52P*P{>4@-x=3zC}L|@w2&ccx(7m- zDGCtizX1#oCn1Fn*Tn7sRXOq;T&@$&!2mkeOH4GbU$>W z-|GGh&eiYwsy&_T-aA-1s>_D5T0dr@Cj}a^1vefwXyO@FTaxT6;+0M9<^3H94;Jx4 zr)+sKz!j2+_sx_m&mSNri+IwhQ#@;+*)4tvA8q~668*l7m4RK&qdiii9){JL4m`px z*RR|wT$@uul%Mab*2X0)M|vYBDESriNIV=Usu*LLNRMAK%@`esh)BqCV&F3@ysusP zg>ehtd3Q#&zBY_O#^XjV#wJkAujlU=hK@08+HC#p-$vMpJ?sTtHBy^e@*J0Dgr`P# z%P`m1M1|6B8-Wr=%q}O6bJu=;g|q**zmwu(F`aLbg}rHu=TB0)%ct|$IYeOCGpObz&yg~5pQvHOm?%h5k4)nR@kynb~w z?TSj`(pkdOk6`ossHX}SwfI!%c=op|RuR+ZR*$h;0>t%U_!@nRzP30XY4&4Ms#ez^ zbGDnutn6N761rtf({2scY!`x6*=5NhbfrAGxAQRtxs9?FcPKt2!5y5UnSBtW-!%PM`v`p@;wo)Py}6Ps5qU`tD~OegYZG-wG<=noYq{s3DT`< zj%^j4MXCRxZ2p|_??=V3dD)g|=`=sB0i_&SH}ki>&82b2=+(y^)gLS`@(I5rmET}q z;cd`tCp^5Ag63-lgpsyRP39`Eg=WjM5ZF+1nkq_Oe58+nvD?VSK$#Osoz{ePsR50O zM%74-Eb7wHdEl+xZRS=JJPfl%w>5CRgu{-8UZ#0COGwVdz4fZQGxv?}umg@3tTZpapL8A`=3g>P z3J#7>prAeG$&ST(O>XWW5E*{Tx8(^3hu5lMpiw0m*kHZ-_LrsY>*2J6-bwKz8{glT zfg4wqqNUrF=1t%mF8fBW<5t+2H3b&j71jPsoeKW@3+4l;eAj5R|F+)$;ZRLicmN=~ z?9*m>vojY`cj@F6LYgeK9z!qV)gsj<+9$@e*VUD{70ji5Z%|tX5t4I2Q{qC;?qUAt_(?R<=A&E`k)y4~zmOam0TH#UGui?7*0+1}9RrWTo2Whw^g?FxYonMCpWxaES1Yat0i~G7r zn-$mI$Rqna_1r)l5;d0~RHPtT^+iLPhA(!b zEwwq;7w-YT;sn;jv!6TfAp_0C$yClM+y@ykwwP2;q$ zSE}~IqFQ7)Z2|9h9Wk1J?Fli<0gtb5Z~-M!!^6-PdW;RpJ*C`!kaXTJi8*whQHU5U zDvI5O#d3GfANgJ<>Sb0Ce3s}Ln%8yVW8^|$__2hPvg|M_nr-`oiPZ=NGojM}L(h4( z%N-eeS!NMFs=1x%cU2y*2^T#$oD7#Q+>{RkV?&hL6D|9nJ%n{+_dIo?o3~x83u8Vk zTbAWAUdCfX`cpo1cH9lvaA`7D7(^Q74>Quwd(C7M5w$UEP@dRztq87TWxnzhFN@2NLx^3=eEY8>TXPBdDiI%#QzFoh}MIY z7}ASg1}uzz$vpGPy>QxU9oES4RsJ_X?C_&H#|bUu%={CTVow}nKWHPZoZ>K+YIr2| ztzOPxh}juVK0q9P6x2Pk3pp2mtY$$YgwuN~ zSQs{SLkvT8oI`i^u&hIzfJs-r{zm|LxcE3fI3AMrFTEfO AIsgCw diff --git a/cvat/apps/dashboard/static/dashboard/js/3rdparty/jstree/themes/default/style.min.css b/cvat/apps/dashboard/static/dashboard/js/3rdparty/jstree/themes/default/style.min.css deleted file mode 100644 index 93f80e91562..00000000000 --- a/cvat/apps/dashboard/static/dashboard/js/3rdparty/jstree/themes/default/style.min.css +++ /dev/null @@ -1 +0,0 @@ -.jstree-node,.jstree-children,.jstree-container-ul{display:block;margin:0;padding:0;list-style-type:none;list-style-image:none}.jstree-node{white-space:nowrap}.jstree-anchor{display:inline-block;color:black;white-space:nowrap;padding:0 4px 0 1px;margin:0;vertical-align:top}.jstree-anchor:focus{outline:0}.jstree-anchor,.jstree-anchor:link,.jstree-anchor:visited,.jstree-anchor:hover,.jstree-anchor:active{text-decoration:none;color:inherit}.jstree-icon{display:inline-block;text-decoration:none;margin:0;padding:0;vertical-align:top;text-align:center}.jstree-icon:empty{display:inline-block;text-decoration:none;margin:0;padding:0;vertical-align:top;text-align:center}.jstree-ocl{cursor:pointer}.jstree-leaf>.jstree-ocl{cursor:default}.jstree .jstree-open>.jstree-children{display:block}.jstree .jstree-closed>.jstree-children,.jstree .jstree-leaf>.jstree-children{display:none}.jstree-anchor>.jstree-themeicon{margin-right:2px}.jstree-no-icons .jstree-themeicon,.jstree-anchor>.jstree-themeicon-hidden{display:none}.jstree-hidden,.jstree-node.jstree-hidden{display:none}.jstree-rtl .jstree-anchor{padding:0 1px 0 4px}.jstree-rtl .jstree-anchor>.jstree-themeicon{margin-left:2px;margin-right:0}.jstree-rtl .jstree-node{margin-left:0}.jstree-rtl .jstree-container-ul>.jstree-node{margin-right:0}.jstree-wholerow-ul{position:relative;display:inline-block;min-width:100%}.jstree-wholerow-ul .jstree-leaf>.jstree-ocl{cursor:pointer}.jstree-wholerow-ul .jstree-anchor,.jstree-wholerow-ul .jstree-icon{position:relative}.jstree-wholerow-ul .jstree-wholerow{width:100%;cursor:pointer;position:absolute;left:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.jstree-contextmenu .jstree-anchor{-webkit-user-select:none;-webkit-touch-callout:none}.vakata-context{display:none}.vakata-context,.vakata-context ul{margin:0;padding:2px;position:absolute;background:#f5f5f5;border:1px solid #979797;box-shadow:2px 2px 2px #999999}.vakata-context ul{list-style:none;left:100%;margin-top:-2.7em;margin-left:-4px}.vakata-context .vakata-context-right ul{left:auto;right:100%;margin-left:auto;margin-right:-4px}.vakata-context li{list-style:none}.vakata-context li>a{display:block;padding:0 2em 0 2em;text-decoration:none;width:auto;color:black;white-space:nowrap;line-height:2.4em;text-shadow:1px 1px 0 white;border-radius:1px}.vakata-context li>a:hover{position:relative;background-color:#e8eff7;box-shadow:0 0 2px #0a6aa1}.vakata-context li>a.vakata-context-parent{background-image:url("data:image/gif;base64,R0lGODlhCwAHAIAAACgoKP///yH5BAEAAAEALAAAAAALAAcAAAIORI4JlrqN1oMSnmmZDQUAOw==");background-position:right center;background-repeat:no-repeat}.vakata-context li>a:focus{outline:0}.vakata-context .vakata-context-hover>a{position:relative;background-color:#e8eff7;box-shadow:0 0 2px #0a6aa1}.vakata-context .vakata-context-separator>a,.vakata-context .vakata-context-separator>a:hover{background:white;border:0;border-top:1px solid #e2e3e3;height:1px;min-height:1px;max-height:1px;padding:0;margin:0 0 0 2.4em;border-left:1px solid #e0e0e0;text-shadow:0 0 0 transparent;box-shadow:0 0 0 transparent;border-radius:0}.vakata-context .vakata-contextmenu-disabled a,.vakata-context .vakata-contextmenu-disabled a:hover{color:silver;background-color:transparent;border:0;box-shadow:0 0 0}.vakata-context .vakata-contextmenu-disabled>a>i{filter:grayscale(100%)}.vakata-context li>a>i{text-decoration:none;display:inline-block;width:2.4em;height:2.4em;background:transparent;margin:0 0 0 -2em;vertical-align:top;text-align:center;line-height:2.4em}.vakata-context li>a>i:empty{width:2.4em;line-height:2.4em}.vakata-context li>a .vakata-contextmenu-sep{display:inline-block;width:1px;height:2.4em;background:white;margin:0 .5em 0 0;border-left:1px solid #e2e3e3}.vakata-context .vakata-contextmenu-shortcut{font-size:.8em;color:silver;opacity:.5;display:none}.vakata-context-rtl ul{left:auto;right:100%;margin-left:auto;margin-right:-4px}.vakata-context-rtl li>a.vakata-context-parent{background-image:url("data:image/gif;base64,R0lGODlhCwAHAIAAACgoKP///yH5BAEAAAEALAAAAAALAAcAAAINjI+AC7rWHIsPtmoxLAA7");background-position:left center;background-repeat:no-repeat}.vakata-context-rtl .vakata-context-separator>a{margin:0 2.4em 0 0;border-left:0;border-right:1px solid #e2e3e3}.vakata-context-rtl .vakata-context-left ul{right:auto;left:100%;margin-left:-4px;margin-right:auto}.vakata-context-rtl li>a>i{margin:0 -2em 0 0}.vakata-context-rtl li>a .vakata-contextmenu-sep{margin:0 0 0 .5em;border-left-color:white;background:#e2e3e3}#jstree-marker{position:absolute;top:0;left:0;margin:-5px 0 0 0;padding:0;border-right:0;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid;width:0;height:0;font-size:0;line-height:0}#jstree-dnd{line-height:16px;margin:0;padding:4px}#jstree-dnd .jstree-icon,#jstree-dnd .jstree-copy{display:inline-block;text-decoration:none;margin:0 2px 0 0;padding:0;width:16px;height:16px}#jstree-dnd .jstree-ok{background:green}#jstree-dnd .jstree-er{background:red}#jstree-dnd .jstree-copy{margin:0 2px 0 2px}.jstree-default .jstree-node,.jstree-default .jstree-icon{background-repeat:no-repeat;background-color:transparent}.jstree-default .jstree-anchor,.jstree-default .jstree-animated,.jstree-default .jstree-wholerow{transition:background-color .15s,box-shadow .15s}.jstree-default .jstree-hovered{background:#e7f4f9;border-radius:2px;box-shadow:inset 0 0 1px #cccccc}.jstree-default .jstree-context{background:#e7f4f9;border-radius:2px;box-shadow:inset 0 0 1px #cccccc}.jstree-default .jstree-clicked{background:#beebff;border-radius:2px;box-shadow:inset 0 0 1px #999999}.jstree-default .jstree-no-icons .jstree-anchor>.jstree-themeicon{display:none}.jstree-default .jstree-disabled{background:transparent;color:#666666}.jstree-default .jstree-disabled.jstree-hovered{background:transparent;box-shadow:none}.jstree-default .jstree-disabled.jstree-clicked{background:#efefef}.jstree-default .jstree-disabled>.jstree-icon{opacity:.8;filter:url("data:image/svg+xml;utf8,#jstree-grayscale");filter:gray;-webkit-filter:grayscale(100%)}.jstree-default .jstree-search{font-style:italic;color:#8b0000;font-weight:bold}.jstree-default .jstree-no-checkboxes .jstree-checkbox{display:none !important}.jstree-default.jstree-checkbox-no-clicked .jstree-clicked{background:transparent;box-shadow:none}.jstree-default.jstree-checkbox-no-clicked .jstree-clicked.jstree-hovered{background:#e7f4f9}.jstree-default.jstree-checkbox-no-clicked>.jstree-wholerow-ul .jstree-wholerow-clicked{background:transparent}.jstree-default.jstree-checkbox-no-clicked>.jstree-wholerow-ul .jstree-wholerow-clicked.jstree-wholerow-hovered{background:#e7f4f9}.jstree-default>.jstree-striped{min-width:100%;display:inline-block;background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAkCAMAAAB/qqA+AAAABlBMVEUAAAAAAAClZ7nPAAAAAnRSTlMNAMM9s3UAAAAXSURBVHjajcEBAQAAAIKg/H/aCQZ70AUBjAATb6YPDgAAAABJRU5ErkJggg==") left top repeat}.jstree-default>.jstree-wholerow-ul .jstree-hovered,.jstree-default>.jstree-wholerow-ul .jstree-clicked{background:transparent;box-shadow:none;border-radius:0}.jstree-default .jstree-wholerow{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.jstree-default .jstree-wholerow-hovered{background:#e7f4f9}.jstree-default .jstree-wholerow-clicked{background:#beebff;background:-webkit-linear-gradient(top, #beebff 0, #a8e4ff 100%);background:linear-gradient(to bottom, #beebff 0, #a8e4ff 100%)}.jstree-default .jstree-node{min-height:24px;line-height:24px;margin-left:24px;min-width:24px}.jstree-default .jstree-anchor{line-height:24px;height:24px}.jstree-default .jstree-icon{width:24px;height:24px;line-height:24px}.jstree-default .jstree-icon:empty{width:24px;height:24px;line-height:24px}.jstree-default.jstree-rtl .jstree-node{margin-right:24px}.jstree-default .jstree-wholerow{height:24px}.jstree-default .jstree-node,.jstree-default .jstree-icon{background-image:url("32px.png")}.jstree-default .jstree-node{background-position:-292px -4px;background-repeat:repeat-y}.jstree-default .jstree-last{background:transparent}.jstree-default .jstree-open>.jstree-ocl{background-position:-132px -4px}.jstree-default .jstree-closed>.jstree-ocl{background-position:-100px -4px}.jstree-default .jstree-leaf>.jstree-ocl{background-position:-68px -4px}.jstree-default .jstree-themeicon{background-position:-260px -4px}.jstree-default>.jstree-no-dots .jstree-node,.jstree-default>.jstree-no-dots .jstree-leaf>.jstree-ocl{background:transparent}.jstree-default>.jstree-no-dots .jstree-open>.jstree-ocl{background-position:-36px -4px}.jstree-default>.jstree-no-dots .jstree-closed>.jstree-ocl{background-position:-4px -4px}.jstree-default .jstree-disabled{background:transparent}.jstree-default .jstree-disabled.jstree-hovered{background:transparent}.jstree-default .jstree-disabled.jstree-clicked{background:#efefef}.jstree-default .jstree-checkbox{background-position:-164px -4px}.jstree-default .jstree-checkbox:hover{background-position:-164px -36px}.jstree-default.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox,.jstree-default .jstree-checked>.jstree-checkbox{background-position:-228px -4px}.jstree-default.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox:hover,.jstree-default .jstree-checked>.jstree-checkbox:hover{background-position:-228px -36px}.jstree-default .jstree-anchor>.jstree-undetermined{background-position:-196px -4px}.jstree-default .jstree-anchor>.jstree-undetermined:hover{background-position:-196px -36px}.jstree-default .jstree-checkbox-disabled{opacity:.8;filter:url("data:image/svg+xml;utf8,#jstree-grayscale");filter:gray;-webkit-filter:grayscale(100%)}.jstree-default>.jstree-striped{background-size:auto 48px}.jstree-default.jstree-rtl .jstree-node{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==");background-position:100% 1px;background-repeat:repeat-y}.jstree-default.jstree-rtl .jstree-last{background:transparent}.jstree-default.jstree-rtl .jstree-open>.jstree-ocl{background-position:-132px -36px}.jstree-default.jstree-rtl .jstree-closed>.jstree-ocl{background-position:-100px -36px}.jstree-default.jstree-rtl .jstree-leaf>.jstree-ocl{background-position:-68px -36px}.jstree-default.jstree-rtl>.jstree-no-dots .jstree-node,.jstree-default.jstree-rtl>.jstree-no-dots .jstree-leaf>.jstree-ocl{background:transparent}.jstree-default.jstree-rtl>.jstree-no-dots .jstree-open>.jstree-ocl{background-position:-36px -36px}.jstree-default.jstree-rtl>.jstree-no-dots .jstree-closed>.jstree-ocl{background-position:-4px -36px}.jstree-default .jstree-themeicon-custom{background-color:transparent;background-image:none;background-position:0 0}.jstree-default>.jstree-container-ul .jstree-loading>.jstree-ocl{background:url("throbber.gif") center center no-repeat}.jstree-default .jstree-file{background:url("32px.png") -100px -68px no-repeat}.jstree-default .jstree-folder{background:url("32px.png") -260px -4px no-repeat}.jstree-default>.jstree-container-ul>.jstree-node{margin-left:0;margin-right:0}#jstree-dnd.jstree-default{line-height:24px;padding:0 4px}#jstree-dnd.jstree-default .jstree-ok,#jstree-dnd.jstree-default .jstree-er{background-image:url("32px.png");background-repeat:no-repeat;background-color:transparent}#jstree-dnd.jstree-default i{background:transparent;width:24px;height:24px;line-height:24px}#jstree-dnd.jstree-default .jstree-ok{background-position:-4px -68px}#jstree-dnd.jstree-default .jstree-er{background-position:-36px -68px}.jstree-default .jstree-ellipsis{overflow:hidden}.jstree-default .jstree-ellipsis .jstree-anchor{width:calc(100% - 29px);text-overflow:ellipsis;overflow:hidden}.jstree-default.jstree-rtl .jstree-node{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==")}.jstree-default.jstree-rtl .jstree-last{background:transparent}.jstree-default-small .jstree-node{min-height:18px;line-height:18px;margin-left:18px;min-width:18px}.jstree-default-small .jstree-anchor{line-height:18px;height:18px}.jstree-default-small .jstree-icon{width:18px;height:18px;line-height:18px}.jstree-default-small .jstree-icon:empty{width:18px;height:18px;line-height:18px}.jstree-default-small.jstree-rtl .jstree-node{margin-right:18px}.jstree-default-small .jstree-wholerow{height:18px}.jstree-default-small .jstree-node,.jstree-default-small .jstree-icon{background-image:url("32px.png")}.jstree-default-small .jstree-node{background-position:-295px -7px;background-repeat:repeat-y}.jstree-default-small .jstree-last{background:transparent}.jstree-default-small .jstree-open>.jstree-ocl{background-position:-135px -7px}.jstree-default-small .jstree-closed>.jstree-ocl{background-position:-103px -7px}.jstree-default-small .jstree-leaf>.jstree-ocl{background-position:-71px -7px}.jstree-default-small .jstree-themeicon{background-position:-263px -7px}.jstree-default-small>.jstree-no-dots .jstree-node,.jstree-default-small>.jstree-no-dots .jstree-leaf>.jstree-ocl{background:transparent}.jstree-default-small>.jstree-no-dots .jstree-open>.jstree-ocl{background-position:-39px -7px}.jstree-default-small>.jstree-no-dots .jstree-closed>.jstree-ocl{background-position:-7px -7px}.jstree-default-small .jstree-disabled{background:transparent}.jstree-default-small .jstree-disabled.jstree-hovered{background:transparent}.jstree-default-small .jstree-disabled.jstree-clicked{background:#efefef}.jstree-default-small .jstree-checkbox{background-position:-167px -7px}.jstree-default-small .jstree-checkbox:hover{background-position:-167px -39px}.jstree-default-small.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox,.jstree-default-small .jstree-checked>.jstree-checkbox{background-position:-231px -7px}.jstree-default-small.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox:hover,.jstree-default-small .jstree-checked>.jstree-checkbox:hover{background-position:-231px -39px}.jstree-default-small .jstree-anchor>.jstree-undetermined{background-position:-199px -7px}.jstree-default-small .jstree-anchor>.jstree-undetermined:hover{background-position:-199px -39px}.jstree-default-small .jstree-checkbox-disabled{opacity:.8;filter:url("data:image/svg+xml;utf8,#jstree-grayscale");filter:gray;-webkit-filter:grayscale(100%)}.jstree-default-small>.jstree-striped{background-size:auto 36px}.jstree-default-small.jstree-rtl .jstree-node{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==");background-position:100% 1px;background-repeat:repeat-y}.jstree-default-small.jstree-rtl .jstree-last{background:transparent}.jstree-default-small.jstree-rtl .jstree-open>.jstree-ocl{background-position:-135px -39px}.jstree-default-small.jstree-rtl .jstree-closed>.jstree-ocl{background-position:-103px -39px}.jstree-default-small.jstree-rtl .jstree-leaf>.jstree-ocl{background-position:-71px -39px}.jstree-default-small.jstree-rtl>.jstree-no-dots .jstree-node,.jstree-default-small.jstree-rtl>.jstree-no-dots .jstree-leaf>.jstree-ocl{background:transparent}.jstree-default-small.jstree-rtl>.jstree-no-dots .jstree-open>.jstree-ocl{background-position:-39px -39px}.jstree-default-small.jstree-rtl>.jstree-no-dots .jstree-closed>.jstree-ocl{background-position:-7px -39px}.jstree-default-small .jstree-themeicon-custom{background-color:transparent;background-image:none;background-position:0 0}.jstree-default-small>.jstree-container-ul .jstree-loading>.jstree-ocl{background:url("throbber.gif") center center no-repeat}.jstree-default-small .jstree-file{background:url("32px.png") -103px -71px no-repeat}.jstree-default-small .jstree-folder{background:url("32px.png") -263px -7px no-repeat}.jstree-default-small>.jstree-container-ul>.jstree-node{margin-left:0;margin-right:0}#jstree-dnd.jstree-default-small{line-height:18px;padding:0 4px}#jstree-dnd.jstree-default-small .jstree-ok,#jstree-dnd.jstree-default-small .jstree-er{background-image:url("32px.png");background-repeat:no-repeat;background-color:transparent}#jstree-dnd.jstree-default-small i{background:transparent;width:18px;height:18px;line-height:18px}#jstree-dnd.jstree-default-small .jstree-ok{background-position:-7px -71px}#jstree-dnd.jstree-default-small .jstree-er{background-position:-39px -71px}.jstree-default-small .jstree-ellipsis{overflow:hidden}.jstree-default-small .jstree-ellipsis .jstree-anchor{width:calc(100% - 23px);text-overflow:ellipsis;overflow:hidden}.jstree-default-small.jstree-rtl .jstree-node{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAACAQMAAABv1h6PAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMHBgAAiABBI4gz9AAAAABJRU5ErkJggg==")}.jstree-default-small.jstree-rtl .jstree-last{background:transparent}.jstree-default-large .jstree-node{min-height:32px;line-height:32px;margin-left:32px;min-width:32px}.jstree-default-large .jstree-anchor{line-height:32px;height:32px}.jstree-default-large .jstree-icon{width:32px;height:32px;line-height:32px}.jstree-default-large .jstree-icon:empty{width:32px;height:32px;line-height:32px}.jstree-default-large.jstree-rtl .jstree-node{margin-right:32px}.jstree-default-large .jstree-wholerow{height:32px}.jstree-default-large .jstree-node,.jstree-default-large .jstree-icon{background-image:url("32px.png")}.jstree-default-large .jstree-node{background-position:-288px 0;background-repeat:repeat-y}.jstree-default-large .jstree-last{background:transparent}.jstree-default-large .jstree-open>.jstree-ocl{background-position:-128px 0}.jstree-default-large .jstree-closed>.jstree-ocl{background-position:-96px 0}.jstree-default-large .jstree-leaf>.jstree-ocl{background-position:-64px 0}.jstree-default-large .jstree-themeicon{background-position:-256px 0}.jstree-default-large>.jstree-no-dots .jstree-node,.jstree-default-large>.jstree-no-dots .jstree-leaf>.jstree-ocl{background:transparent}.jstree-default-large>.jstree-no-dots .jstree-open>.jstree-ocl{background-position:-32px 0}.jstree-default-large>.jstree-no-dots .jstree-closed>.jstree-ocl{background-position:0 0}.jstree-default-large .jstree-disabled{background:transparent}.jstree-default-large .jstree-disabled.jstree-hovered{background:transparent}.jstree-default-large .jstree-disabled.jstree-clicked{background:#efefef}.jstree-default-large .jstree-checkbox{background-position:-160px 0}.jstree-default-large .jstree-checkbox:hover{background-position:-160px -32px}.jstree-default-large.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox,.jstree-default-large .jstree-checked>.jstree-checkbox{background-position:-224px 0}.jstree-default-large.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox:hover,.jstree-default-large .jstree-checked>.jstree-checkbox:hover{background-position:-224px -32px}.jstree-default-large .jstree-anchor>.jstree-undetermined{background-position:-192px 0}.jstree-default-large .jstree-anchor>.jstree-undetermined:hover{background-position:-192px -32px}.jstree-default-large .jstree-checkbox-disabled{opacity:.8;filter:url("data:image/svg+xml;utf8,#jstree-grayscale");filter:gray;-webkit-filter:grayscale(100%)}.jstree-default-large>.jstree-striped{background-size:auto 64px}.jstree-default-large.jstree-rtl .jstree-node{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==");background-position:100% 1px;background-repeat:repeat-y}.jstree-default-large.jstree-rtl .jstree-last{background:transparent}.jstree-default-large.jstree-rtl .jstree-open>.jstree-ocl{background-position:-128px -32px}.jstree-default-large.jstree-rtl .jstree-closed>.jstree-ocl{background-position:-96px -32px}.jstree-default-large.jstree-rtl .jstree-leaf>.jstree-ocl{background-position:-64px -32px}.jstree-default-large.jstree-rtl>.jstree-no-dots .jstree-node,.jstree-default-large.jstree-rtl>.jstree-no-dots .jstree-leaf>.jstree-ocl{background:transparent}.jstree-default-large.jstree-rtl>.jstree-no-dots .jstree-open>.jstree-ocl{background-position:-32px -32px}.jstree-default-large.jstree-rtl>.jstree-no-dots .jstree-closed>.jstree-ocl{background-position:0 -32px}.jstree-default-large .jstree-themeicon-custom{background-color:transparent;background-image:none;background-position:0 0}.jstree-default-large>.jstree-container-ul .jstree-loading>.jstree-ocl{background:url("throbber.gif") center center no-repeat}.jstree-default-large .jstree-file{background:url("32px.png") -96px -64px no-repeat}.jstree-default-large .jstree-folder{background:url("32px.png") -256px 0 no-repeat}.jstree-default-large>.jstree-container-ul>.jstree-node{margin-left:0;margin-right:0}#jstree-dnd.jstree-default-large{line-height:32px;padding:0 4px}#jstree-dnd.jstree-default-large .jstree-ok,#jstree-dnd.jstree-default-large .jstree-er{background-image:url("32px.png");background-repeat:no-repeat;background-color:transparent}#jstree-dnd.jstree-default-large i{background:transparent;width:32px;height:32px;line-height:32px}#jstree-dnd.jstree-default-large .jstree-ok{background-position:0 -64px}#jstree-dnd.jstree-default-large .jstree-er{background-position:-32px -64px}.jstree-default-large .jstree-ellipsis{overflow:hidden}.jstree-default-large .jstree-ellipsis .jstree-anchor{width:calc(100% - 37px);text-overflow:ellipsis;overflow:hidden}.jstree-default-large.jstree-rtl .jstree-node{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAACAQMAAAAD0EyKAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjgIIGBgABCgCBvVLXcAAAAABJRU5ErkJggg==")}.jstree-default-large.jstree-rtl .jstree-last{background:transparent}@media (max-width:768px){#jstree-dnd.jstree-dnd-responsive{line-height:40px;font-weight:bold;font-size:1.1em;text-shadow:1px 1px white}#jstree-dnd.jstree-dnd-responsive>i{background:transparent;width:40px;height:40px}#jstree-dnd.jstree-dnd-responsive>.jstree-ok{background-image:url("40px.png");background-position:0 -200px;background-size:120px 240px}#jstree-dnd.jstree-dnd-responsive>.jstree-er{background-image:url("40px.png");background-position:-40px -200px;background-size:120px 240px}#jstree-marker.jstree-dnd-responsive{border-left-width:10px;border-top-width:10px;border-bottom-width:10px;margin-top:-10px}}@media (max-width:768px){.jstree-default-responsive .jstree-icon{background-image:url("40px.png")}.jstree-default-responsive .jstree-node,.jstree-default-responsive .jstree-leaf>.jstree-ocl{background:transparent}.jstree-default-responsive .jstree-node{min-height:40px;line-height:40px;margin-left:40px;min-width:40px;white-space:nowrap}.jstree-default-responsive .jstree-anchor{line-height:40px;height:40px}.jstree-default-responsive .jstree-icon,.jstree-default-responsive .jstree-icon:empty{width:40px;height:40px;line-height:40px}.jstree-default-responsive>.jstree-container-ul>.jstree-node{margin-left:0}.jstree-default-responsive.jstree-rtl .jstree-node{margin-left:0;margin-right:40px;background:transparent}.jstree-default-responsive.jstree-rtl .jstree-container-ul>.jstree-node{margin-right:0}.jstree-default-responsive .jstree-ocl,.jstree-default-responsive .jstree-themeicon,.jstree-default-responsive .jstree-checkbox{background-size:120px 240px}.jstree-default-responsive .jstree-leaf>.jstree-ocl,.jstree-default-responsive.jstree-rtl .jstree-leaf>.jstree-ocl{background:transparent}.jstree-default-responsive .jstree-open>.jstree-ocl{background-position:0 0 !important}.jstree-default-responsive .jstree-closed>.jstree-ocl{background-position:0 -40px !important}.jstree-default-responsive.jstree-rtl .jstree-closed>.jstree-ocl{background-position:-40px 0 !important}.jstree-default-responsive .jstree-themeicon{background-position:-40px -40px}.jstree-default-responsive .jstree-checkbox,.jstree-default-responsive .jstree-checkbox:hover{background-position:-40px -80px}.jstree-default-responsive.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox,.jstree-default-responsive.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox:hover,.jstree-default-responsive .jstree-checked>.jstree-checkbox,.jstree-default-responsive .jstree-checked>.jstree-checkbox:hover{background-position:0 -80px}.jstree-default-responsive .jstree-anchor>.jstree-undetermined,.jstree-default-responsive .jstree-anchor>.jstree-undetermined:hover{background-position:0 -120px}.jstree-default-responsive .jstree-anchor{font-weight:bold;font-size:1.1em;text-shadow:1px 1px white}.jstree-default-responsive>.jstree-striped{background:transparent}.jstree-default-responsive .jstree-wholerow{border-top:1px solid rgba(255,255,255,0.7);border-bottom:1px solid rgba(64,64,64,0.2);background:#ebebeb;height:40px}.jstree-default-responsive .jstree-wholerow-hovered{background:#e7f4f9}.jstree-default-responsive .jstree-wholerow-clicked{background:#beebff}.jstree-default-responsive .jstree-children .jstree-last>.jstree-wholerow{box-shadow:inset 0 -6px 3px -5px #666666}.jstree-default-responsive .jstree-children .jstree-open>.jstree-wholerow{box-shadow:inset 0 6px 3px -5px #666666;border-top:0}.jstree-default-responsive .jstree-children .jstree-open+.jstree-open{box-shadow:none}.jstree-default-responsive .jstree-node,.jstree-default-responsive .jstree-icon,.jstree-default-responsive .jstree-node>.jstree-ocl,.jstree-default-responsive .jstree-themeicon,.jstree-default-responsive .jstree-checkbox{background-image:url("40px.png");background-size:120px 240px}.jstree-default-responsive .jstree-node{background-position:-80px 0;background-repeat:repeat-y}.jstree-default-responsive .jstree-last{background:transparent}.jstree-default-responsive .jstree-leaf>.jstree-ocl{background-position:-40px -120px}.jstree-default-responsive .jstree-last>.jstree-ocl{background-position:-40px -160px}.jstree-default-responsive .jstree-themeicon-custom{background-color:transparent;background-image:none;background-position:0 0}.jstree-default-responsive .jstree-file{background:url("40px.png") 0 -160px no-repeat;background-size:120px 240px}.jstree-default-responsive .jstree-folder{background:url("40px.png") -40px -40px no-repeat;background-size:120px 240px}.jstree-default-responsive>.jstree-container-ul>.jstree-node{margin-left:0;margin-right:0}} \ No newline at end of file diff --git a/cvat/apps/dashboard/static/dashboard/js/3rdparty/jstree/themes/default/throbber.gif b/cvat/apps/dashboard/static/dashboard/js/3rdparty/jstree/themes/default/throbber.gif deleted file mode 100644 index 64c8eebb87be51df85e7fe51d80c6a4918feccd0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1464 zcmZvbe^8Tk9LK*8dp5SQ!7~^z3FH}!ITG-J1cw?om>b&;=J+8!Aleo`1T_TIT%6c8 z1~~jh1NjxzHYAH94(OM4M&n1oj0+GXOzM`NNuFKvJO#aq9=zzR>;Lcf`~JMX@7E_; zE)~XW5P*OOUZB0boy}%Doz8)Qfr^R>0055TadB~Vb#*S6i^t;?6%|RP((dkV7=~9? zR=6u3N=ashI8h~uiNZ(_ae@11fVjLsbEF_2Jyv|A2-WP@Xb&FJS_GSA*lWq&wS|6Kz&We7i(Rj!J?%GlRQKJ&Z{()`heumO#_HJZsj z#HGMq*)Ql(ltilcV*PsvFJcG)?{PUEkDEMnDk(mcxp84FatgO zM&SAVq5nxl2z~(P5Q)HsM>#ST$^3Zf)v%hDOvy}a111}p)-eo$)jIZzsi-drczong z_oe$qnf0FN`0*a5A#0@4{zW6%cAbn+Owf}qGYY$my3^2v(<^@I)MQ4;zX4Z&8Nj(N zM>zDQ`NF=I&7?)D*9LFDNAGfTHkH zBcyVpNC=&t3gfx<@+!)KDaS9PmibOT>rG>Q7+i~b+D^9iQZFKw*{PFMH7mIjGmn61 zS&Fw?)h)Z3Dr|&F>RD>fF1lTr4~HABq*Rp&5v7kfwC;!Pe$j}=s^yi`W>|FE+}AB5ZL-tb=>B8TB|>BP zS{}g*0f_Kq)s(C$Id`l+^={5@_>G_Ud~=(3k*`vUg$BLMq^lXZU$RuwVs+|76L4&B z)jiop@`3KQp}rezIu*{_nzu}ctKFO})$mISv`iRBZ?=DL1znqK^5FloWOWVFZTOEH zjDgSbbA#NCnKzHpZff2tV4g98b&Lq@S!h$RL?@tca5MBjN@HL>+z!=ZDhD!-rU|ud z-KgO{5cql9XI>M|Rt%a#rcZDJ6ULTM?wvSA{4;QXfctw5h%(d7hW*92=W~``ZRI!G zsY+%{Zl?T$Z@m{h=I;%s{mvl#RI~%A749v+%r||l4^Nx*rb~zQn2-s}#S{#q=p$1O zm$n2M9~G*PVusk;6g~LIou}Qc#o;c8{N*k!KaD&#B=IGun;b4?{{2)#4ofm)GTZh| zIz1@KsAzmS;N2Zn`o3KZb2Hpmn&5dZWY|fH;K90ll(mKG0X6xnZNY#q8_@CVF86bM zpl5$`tvF)ES;ouDXKnq>(&^*rOH|2EG)J-9t@FJcCnI|!*L@g$r&T0UarB>CJ7Y0z vY0OZlvlz7~od)b6Q1;I7Fp4H`1;`;0BrjUuXgq+ diff --git a/cvat/apps/dashboard/static/dashboard/js/3rdparty/pagination/bootstrap.css b/cvat/apps/dashboard/static/dashboard/js/3rdparty/pagination/bootstrap.css deleted file mode 100644 index c0749aa81eb..00000000000 --- a/cvat/apps/dashboard/static/dashboard/js/3rdparty/pagination/bootstrap.css +++ /dev/null @@ -1,107 +0,0 @@ -/*! - * Generated using the Bootstrap Customizer (https://getbootstrap.com/docs/3.4/customize/) - */ -/*! - * Bootstrap v3.4.1 (https://getbootstrap.com/) - * Copyright 2011-2019 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */ -/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ - -.pagination { - display: inline-block; - padding-left: 0; - margin: 20px 0; - border-radius: 4px; -} -.pagination > li { - display: inline; -} -.pagination > li > a, -.pagination > li > span { - position: relative; - float: left; - padding: 6px 12px; - margin-left: -1px; - line-height: 1.42857143; - color: #337ab7; - text-decoration: none; - background-color: #ffffff; - border: 1px solid #dddddd; -} -.pagination > li > a:hover, -.pagination > li > span:hover, -.pagination > li > a:focus, -.pagination > li > span:focus { - z-index: 2; - color: #23527c; - background-color: #eeeeee; - border-color: #dddddd; -} -.pagination > li:first-child > a, -.pagination > li:first-child > span { - margin-left: 0; - border-top-left-radius: 4px; - border-bottom-left-radius: 4px; -} -.pagination > li:last-child > a, -.pagination > li:last-child > span { - border-top-right-radius: 4px; - border-bottom-right-radius: 4px; -} -.pagination > .active > a, -.pagination > .active > span, -.pagination > .active > a:hover, -.pagination > .active > span:hover, -.pagination > .active > a:focus, -.pagination > .active > span:focus { - z-index: 3; - color: #ffffff; - cursor: default; - background-color: #337ab7; - border-color: #337ab7; -} -.pagination > .disabled > span, -.pagination > .disabled > span:hover, -.pagination > .disabled > span:focus, -.pagination > .disabled > a, -.pagination > .disabled > a:hover, -.pagination > .disabled > a:focus { - color: #777777; - cursor: not-allowed; - background-color: #ffffff; - border-color: #dddddd; -} -.pagination-lg > li > a, -.pagination-lg > li > span { - padding: 10px 16px; - font-size: 18px; - line-height: 1.3333333; -} -.pagination-lg > li:first-child > a, -.pagination-lg > li:first-child > span { - border-top-left-radius: 6px; - border-bottom-left-radius: 6px; -} -.pagination-lg > li:last-child > a, -.pagination-lg > li:last-child > span { - border-top-right-radius: 6px; - border-bottom-right-radius: 6px; -} -.pagination-sm > li > a, -.pagination-sm > li > span { - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; -} -.pagination-sm > li:first-child > a, -.pagination-sm > li:first-child > span { - border-top-left-radius: 3px; - border-bottom-left-radius: 3px; -} -.pagination-sm > li:last-child > a, -.pagination-sm > li:last-child > span { - border-top-right-radius: 3px; - border-bottom-right-radius: 3px; -} - diff --git a/cvat/apps/dashboard/static/dashboard/js/3rdparty/pagination/jquery.twbsPagination.js b/cvat/apps/dashboard/static/dashboard/js/3rdparty/pagination/jquery.twbsPagination.js deleted file mode 100644 index 2451a6109ec..00000000000 --- a/cvat/apps/dashboard/static/dashboard/js/3rdparty/pagination/jquery.twbsPagination.js +++ /dev/null @@ -1,364 +0,0 @@ -/*! - * jQuery pagination plugin v1.4.2 - * http://josecebe.github.io/twbs-pagination/ - * - * Copyright 2014-2018, Eugene Simakin - * Released under Apache 2.0 license - * http://apache.org/licenses/LICENSE-2.0.html - */ -(function ($, window, document, undefined) { - - 'use strict'; - - var old = $.fn.twbsPagination; - - // PROTOTYPE AND CONSTRUCTOR - - var TwbsPagination = function (element, options) { - this.$element = $(element); - this.options = $.extend({}, $.fn.twbsPagination.defaults, options); - - if (this.options.startPage < 1 || this.options.startPage > this.options.totalPages) { - throw new Error('Start page option is incorrect'); - } - - this.options.totalPages = parseInt(this.options.totalPages); - if (isNaN(this.options.totalPages)) { - throw new Error('Total pages option is not correct!'); - } - - this.options.visiblePages = parseInt(this.options.visiblePages); - if (isNaN(this.options.visiblePages)) { - throw new Error('Visible pages option is not correct!'); - } - - if (this.options.beforePageClick instanceof Function) { - this.$element.first().on('beforePage', this.options.beforePageClick); - } - - if (this.options.onPageClick instanceof Function) { - this.$element.first().on('page', this.options.onPageClick); - } - - // hide if only one page exists - if (this.options.hideOnlyOnePage && this.options.totalPages == 1) { - if (this.options.initiateStartPageClick) { - this.$element.trigger('page', 1); - } - return this; - } - - if (this.options.href) { - this.options.startPage = this.getPageFromQueryString(); - if (!this.options.startPage) { - this.options.startPage = 1; - } - } - - var tagName = (typeof this.$element.prop === 'function') ? - this.$element.prop('tagName') : this.$element.attr('tagName'); - - if (tagName === 'UL') { - this.$listContainer = this.$element; - } else { - var elements = this.$element; - var $newListContainer = $([]); - elements.each(function(index) { - var $newElem = $("
      "); - $(this).append($newElem); - $newListContainer.push($newElem[0]); - }); - this.$listContainer = $newListContainer; - this.$element = $newListContainer; - } - - this.$listContainer.addClass(this.options.paginationClass); - - if (this.options.initiateStartPageClick) { - this.show(this.options.startPage); - } else { - this.currentPage = this.options.startPage; - this.render(this.getPages(this.options.startPage)); - this.setupEvents(); - } - - return this; - }; - - TwbsPagination.prototype = { - - constructor: TwbsPagination, - - destroy: function () { - this.$element.empty(); - this.$element.removeData('twbs-pagination'); - this.$element.off('page'); - - return this; - }, - - show: function (page) { - if (page < 1 || page > this.options.totalPages) { - throw new Error('Page is incorrect.'); - } - this.currentPage = page; - - this.$element.trigger('beforePage', page); - - var pages = this.getPages(page); - this.render(pages); - this.setupEvents(); - - this.$element.trigger('page', page); - - return pages; - }, - - enable: function () { - this.show(this.currentPage); - }, - - disable: function () { - var _this = this; - this.$listContainer.off('click').on('click', 'li', function (evt) { - evt.preventDefault(); - }); - this.$listContainer.children().each(function () { - var $this = $(this); - if (!$this.hasClass(_this.options.activeClass)) { - $(this).addClass(_this.options.disabledClass); - } - }); - }, - - buildListItems: function (pages) { - var listItems = []; - - if (this.options.first) { - listItems.push(this.buildItem('first', 1)); - } - - if (this.options.prev) { - var prev = pages.currentPage > 1 ? pages.currentPage - 1 : this.options.loop ? this.options.totalPages : 1; - listItems.push(this.buildItem('prev', prev)); - } - - for (var i = 0; i < pages.numeric.length; i++) { - listItems.push(this.buildItem('page', pages.numeric[i])); - } - - if (this.options.next) { - var next = pages.currentPage < this.options.totalPages ? pages.currentPage + 1 : this.options.loop ? 1 : this.options.totalPages; - listItems.push(this.buildItem('next', next)); - } - - if (this.options.last) { - listItems.push(this.buildItem('last', this.options.totalPages)); - } - - return listItems; - }, - - buildItem: function (type, page) { - var $itemContainer = $('
    • '), - $itemContent = $(''), - itemText = this.options[type] ? this.makeText(this.options[type], page) : page; - - $itemContainer.addClass(this.options[type + 'Class']); - $itemContainer.data('page', page); - $itemContainer.data('page-type', type); - $itemContainer.append($itemContent.attr('href', this.makeHref(page)).addClass(this.options.anchorClass).html(itemText)); - - return $itemContainer; - }, - - getPages: function (currentPage) { - var pages = []; - - var half = Math.floor(this.options.visiblePages / 2); - var start = currentPage - half + 1 - this.options.visiblePages % 2; - var end = currentPage + half; - - var visiblePages = this.options.visiblePages; - if (visiblePages > this.options.totalPages) { - visiblePages = this.options.totalPages; - } - - // handle boundary case - if (start <= 0) { - start = 1; - end = visiblePages; - } - if (end > this.options.totalPages) { - start = this.options.totalPages - visiblePages + 1; - end = this.options.totalPages; - } - - var itPage = start; - while (itPage <= end) { - pages.push(itPage); - itPage++; - } - - return {"currentPage": currentPage, "numeric": pages}; - }, - - render: function (pages) { - var _this = this; - this.$listContainer.children().remove(); - var items = this.buildListItems(pages); - $.each(items, function(key, item){ - _this.$listContainer.append(item); - }); - - this.$listContainer.children().each(function () { - var $this = $(this), - pageType = $this.data('page-type'); - - switch (pageType) { - case 'page': - if ($this.data('page') === pages.currentPage) { - $this.addClass(_this.options.activeClass); - } - break; - case 'first': - $this.toggleClass(_this.options.disabledClass, pages.currentPage === 1); - break; - case 'last': - $this.toggleClass(_this.options.disabledClass, pages.currentPage === _this.options.totalPages); - break; - case 'prev': - $this.toggleClass(_this.options.disabledClass, !_this.options.loop && pages.currentPage === 1); - break; - case 'next': - $this.toggleClass(_this.options.disabledClass, - !_this.options.loop && pages.currentPage === _this.options.totalPages); - break; - default: - break; - } - - }); - }, - - setupEvents: function () { - var _this = this; - this.$listContainer.off('click').on('click', 'li', function (evt) { - var $this = $(this); - if ($this.hasClass(_this.options.disabledClass) || $this.hasClass(_this.options.activeClass)) { - return false; - } - // Prevent click event if href is not set. - !_this.options.href && evt.preventDefault(); - _this.show(parseInt($this.data('page'))); - }); - }, - - changeTotalPages: function(totalPages, currentPage) { - this.options.totalPages = totalPages; - return this.show(currentPage); - }, - - makeHref: function (page) { - return this.options.href ? this.generateQueryString(page) : "#"; - }, - - makeText: function (text, page) { - return text.replace(this.options.pageVariable, page) - .replace(this.options.totalPagesVariable, this.options.totalPages) - }, - - getPageFromQueryString: function (searchStr) { - var search = this.getSearchString(searchStr), - regex = new RegExp(this.options.pageVariable + '(=([^&#]*)|&|#|$)'), - page = regex.exec(search); - if (!page || !page[2]) { - return null; - } - page = decodeURIComponent(page[2]); - page = parseInt(page); - if (isNaN(page)) { - return null; - } - return page; - }, - - generateQueryString: function (pageNumber, searchStr) { - var search = this.getSearchString(searchStr), - regex = new RegExp(this.options.pageVariable + '=*[^&#]*'); - if (!search) return ''; - return '?' + search.replace(regex, this.options.pageVariable + '=' + pageNumber); - }, - - getSearchString: function (searchStr) { - var search = searchStr || window.location.search; - if (search === '') { - return null; - } - if (search.indexOf('?') === 0) search = search.substr(1); - return search; - }, - - getCurrentPage: function () { - return this.currentPage; - }, - - getTotalPages: function () { - return this.options.totalPages; - } - }; - - // PLUGIN DEFINITION - - $.fn.twbsPagination = function (option) { - var args = Array.prototype.slice.call(arguments, 1); - var methodReturn; - - var $this = $(this); - var data = $this.data('twbs-pagination'); - var options = typeof option === 'object' ? option : {}; - - if (!data) $this.data('twbs-pagination', (data = new TwbsPagination(this, options) )); - if (typeof option === 'string') methodReturn = data[ option ].apply(data, args); - - return ( methodReturn === undefined ) ? $this : methodReturn; - }; - - $.fn.twbsPagination.defaults = { - totalPages: 1, - startPage: 1, - visiblePages: 5, - initiateStartPageClick: true, - hideOnlyOnePage: false, - href: false, - pageVariable: '{{page}}', - totalPagesVariable: '{{total_pages}}', - page: null, - first: 'First', - prev: 'Previous', - next: 'Next', - last: 'Last', - loop: false, - beforePageClick: null, - onPageClick: null, - paginationClass: 'pagination', - nextClass: 'page-item next', - prevClass: 'page-item prev', - lastClass: 'page-item last', - firstClass: 'page-item first', - pageClass: 'page-item', - activeClass: 'active', - disabledClass: 'disabled', - anchorClass: 'page-link' - }; - - $.fn.twbsPagination.Constructor = TwbsPagination; - - $.fn.twbsPagination.noConflict = function () { - $.fn.twbsPagination = old; - return this; - }; - - $.fn.twbsPagination.version = "1.4.2"; - -})(window.jQuery, window, document); diff --git a/cvat/apps/dashboard/static/dashboard/js/dashboard.js b/cvat/apps/dashboard/static/dashboard/js/dashboard.js deleted file mode 100644 index 8ebf05ea934..00000000000 --- a/cvat/apps/dashboard/static/dashboard/js/dashboard.js +++ /dev/null @@ -1,754 +0,0 @@ -/* - * Copyright (C) 2018 Intel Corporation - * - * SPDX-License-Identifier: MIT - */ - -/* global - userConfirm:false - LabelsInfo:false - showMessage:false - showOverlay:false - isDefaultFormat:false -*/ - -class TaskView { - constructor(task, annotationFormats) { - this.init(task); - this._annotationFormats = annotationFormats; - - this._UI = null; - } - - _disable() { - this._UI.find('*').attr('disabled', true).css('opacity', 0.5); - this._UI.find('.dashboardJobList').empty(); - } - - async _remove() { - try { - await this._task.delete(); - this._disable(); - } catch (exception) { - let { message } = exception; - if (exception instanceof window.cvat.exceptions.ServerError) { - message += ` Code: ${exception.code}`; - } - showMessage(message); - } - } - - _update() { - $('#dashboardUpdateModal').remove(); - - const dashboardUpdateModal = $($('#dashboardUpdateTemplate').html()).appendTo('body'); - - // TODO: Use JSON labels format instead of custom - $('#dashboardOldLabels').prop('value', LabelsInfo.serialize(this._task.labels.map(el => el.toJSON()))); - $('#dashboardCancelUpdate').on('click', () => { - dashboardUpdateModal.remove(); - }); - $('#dashboardSubmitUpdate').on('click', async () => { - let jsonLabels = null; - try { - jsonLabels = LabelsInfo.deserialize($('#dashboardNewLabels').prop('value')); - } catch (exception) { - showMessage(exception); - return; - } - - try { - const labels = jsonLabels.map(label => new window.cvat.classes.Label(label)); - this._task.labels = labels; - await this._task.save(); - showMessage('Task has been successfully updated'); - } catch (exception) { - let { message } = exception; - if (exception instanceof window.cvat.exceptions.ServerError) { - message += ` Code: ${exception.code}`; - } - showMessage(message); - } - - dashboardUpdateModal.remove(); - }); - } - - _upload(uploadAnnotationButton, format) { - const button = $(uploadAnnotationButton); - $(``) - .on('change', async (onChangeEvent) => { - const file = onChangeEvent.target.files[0]; - $(onChangeEvent.target).remove(); - if (file) { - button.prop('disabled', true); - try { - await this._task.annotations.upload(file, format); - } catch (error) { - showMessage(error.message); - } finally { - button.prop('disabled', false); - } - } - }).click(); - } - - async _dump(button, format) { - button.disabled = true; - try { - const url = await this._task.annotations.dump(this._task.name, format); - const a = document.createElement('a'); - a.href = `${url}`; - document.body.appendChild(a); - a.click(); - a.remove(); - } catch (error) { - showMessage(error.message); - } finally { - button.disabled = false; - } - } - - init(task) { - this._task = task; - } - - render(baseURL) { - this._UI = $(`
      `).append( - $(`
      - -
      `), - ).append( - $(`
      - -
      `), - ).append( - $('
      ').css({ - 'background-image': `url("/api/v1/tasks/${this._task.id}/frames/0")`, - }), - ); - - const buttonsContainer = $('
      ').appendTo(this._UI); - const downloadButton = $(''); - $('').appendTo(downloadButton); - - const uploadButton = $(''); - $('').appendTo(uploadButton); - - const dumpers = {}; - const loaders = {}; - - for (const format of this._annotationFormats) { - for (const dumper of format.dumpers) { - dumpers[dumper.name] = dumper; - const item = $(`
      - - - - -{% endblock %} diff --git a/cvat/apps/dashboard/urls.py b/cvat/apps/dashboard/urls.py deleted file mode 100644 index d05317f7901..00000000000 --- a/cvat/apps/dashboard/urls.py +++ /dev/null @@ -1,13 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.urls import path -from . import views - -urlpatterns = [ - path('', views.DashboardView), - path('meta', views.DashboardMeta), -] - diff --git a/cvat/apps/dashboard/views.py b/cvat/apps/dashboard/views.py deleted file mode 100644 index 3ba6b102ba8..00000000000 --- a/cvat/apps/dashboard/views.py +++ /dev/null @@ -1,30 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest -from django.shortcuts import redirect -from django.shortcuts import render -from django.conf import settings -from cvat.apps.authentication.decorators import login_required - -from cvat.settings.base import JS_3RDPARTY, CSS_3RDPARTY - -import os - -@login_required -def DashboardView(request): - return render(request, 'dashboard/dashboard.html', { - 'js_3rdparty': JS_3RDPARTY.get('dashboard', []), - 'css_3rdparty': CSS_3RDPARTY.get('dashboard', []), - }) - -@login_required -def DashboardMeta(request): - return JsonResponse({ - 'max_upload_size': settings.LOCAL_LOAD_MAX_FILES_SIZE, - 'max_upload_count': settings.LOCAL_LOAD_MAX_FILES_COUNT, - 'base_url': "{0}://{1}/".format(request.scheme, request.get_host()), - 'share_path': os.getenv('CVAT_SHARE_URL', default=r'${cvat_root}/share'), - }) \ No newline at end of file diff --git a/cvat-ui/src/components/modals/task-create/task-create.test.tsx b/cvat/apps/dataset_manager/__init__.py similarity index 100% rename from cvat-ui/src/components/modals/task-create/task-create.test.tsx rename to cvat/apps/dataset_manager/__init__.py diff --git a/cvat/apps/dataset_manager/bindings.py b/cvat/apps/dataset_manager/bindings.py new file mode 100644 index 00000000000..da37a3048e6 --- /dev/null +++ b/cvat/apps/dataset_manager/bindings.py @@ -0,0 +1,249 @@ + +# Copyright (C) 2019-2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from collections import OrderedDict +import os +import os.path as osp + +from django.db import transaction + +from cvat.apps.annotation.annotation import Annotation +from cvat.apps.engine.annotation import TaskAnnotation +from cvat.apps.engine.models import Task, ShapeType, AttributeType + +import datumaro.components.extractor as datumaro +from datumaro.util.image import Image + + +class CvatImagesDirExtractor(datumaro.Extractor): + _SUPPORTED_FORMATS = ['.png', '.jpg'] + + def __init__(self, url): + super().__init__() + + items = [] + for (dirpath, _, filenames) in os.walk(url): + for name in filenames: + path = osp.join(dirpath, name) + if self._is_image(path): + item_id = Task.get_image_frame(path) + item = datumaro.DatasetItem(id=item_id, image=path) + items.append((item.id, item)) + + items = sorted(items, key=lambda e: int(e[0])) + items = OrderedDict(items) + self._items = items + + self._subsets = None + + def __iter__(self): + for item in self._items.values(): + yield item + + def __len__(self): + return len(self._items) + + def subsets(self): + return self._subsets + + def _is_image(self, path): + for ext in self._SUPPORTED_FORMATS: + if osp.isfile(path) and path.endswith(ext): + return True + return False + + +class CvatAnnotationsExtractor(datumaro.Extractor): + def __init__(self, url, cvat_annotations): + self._categories = self._load_categories(cvat_annotations) + + dm_annotations = [] + + for cvat_frame_anno in cvat_annotations.group_by_frame(): + dm_anno = self._read_cvat_anno(cvat_frame_anno, cvat_annotations) + dm_image = Image(path=cvat_frame_anno.name, size=( + cvat_frame_anno.height, cvat_frame_anno.width) + ) + dm_item = datumaro.DatasetItem(id=cvat_frame_anno.frame, + annotations=dm_anno, image=dm_image) + dm_annotations.append((dm_item.id, dm_item)) + + dm_annotations = sorted(dm_annotations, key=lambda e: int(e[0])) + self._items = OrderedDict(dm_annotations) + + def __iter__(self): + for item in self._items.values(): + yield item + + def __len__(self): + return len(self._items) + + # pylint: disable=no-self-use + def subsets(self): + return [] + # pylint: enable=no-self-use + + def categories(self): + return self._categories + + @staticmethod + def _load_categories(cvat_anno): + categories = {} + label_categories = datumaro.LabelCategories() + + for _, label in cvat_anno.meta['task']['labels']: + label_categories.add(label['name']) + for _, attr in label['attributes']: + label_categories.attributes.add(attr['name']) + + categories[datumaro.AnnotationType.label] = label_categories + + return categories + + def _read_cvat_anno(self, cvat_frame_anno, cvat_task_anno): + item_anno = [] + + categories = self.categories() + label_cat = categories[datumaro.AnnotationType.label] + map_label = lambda name: label_cat.find(name)[0] + label_attrs = { + label['name']: label['attributes'] + for _, label in cvat_task_anno.meta['task']['labels'] + } + + def convert_attrs(label, cvat_attrs): + cvat_attrs = {a.name: a.value for a in cvat_attrs} + dm_attr = dict() + for _, a_desc in label_attrs[label]: + a_name = a_desc['name'] + a_value = cvat_attrs.get(a_name, a_desc['default_value']) + try: + if a_desc['input_type'] == AttributeType.NUMBER: + a_value = float(a_value) + elif a_desc['input_type'] == AttributeType.CHECKBOX: + a_value = (a_value.lower() == 'true') + dm_attr[a_name] = a_value + except Exception as e: + raise Exception( + "Failed to convert attribute '%s'='%s': %s" % + (a_name, a_value, e)) + return dm_attr + + for tag_obj in cvat_frame_anno.tags: + anno_group = tag_obj.group + anno_label = map_label(tag_obj.label) + anno_attr = convert_attrs(tag_obj.label, tag_obj.attributes) + + anno = datumaro.Label(label=anno_label, + attributes=anno_attr, group=anno_group) + item_anno.append(anno) + + for shape_obj in cvat_frame_anno.labeled_shapes: + anno_group = shape_obj.group + anno_label = map_label(shape_obj.label) + anno_attr = convert_attrs(shape_obj.label, shape_obj.attributes) + + anno_points = shape_obj.points + if shape_obj.type == ShapeType.POINTS: + anno = datumaro.Points(anno_points, + label=anno_label, attributes=anno_attr, group=anno_group) + elif shape_obj.type == ShapeType.POLYLINE: + anno = datumaro.PolyLine(anno_points, + label=anno_label, attributes=anno_attr, group=anno_group) + elif shape_obj.type == ShapeType.POLYGON: + anno = datumaro.Polygon(anno_points, + label=anno_label, attributes=anno_attr, group=anno_group) + elif shape_obj.type == ShapeType.RECTANGLE: + x0, y0, x1, y1 = anno_points + anno = datumaro.Bbox(x0, y0, x1 - x0, y1 - y0, + label=anno_label, attributes=anno_attr, group=anno_group) + else: + raise Exception("Unknown shape type '%s'" % shape_obj.type) + + item_anno.append(anno) + + return item_anno + + +class CvatTaskExtractor(CvatAnnotationsExtractor): + def __init__(self, url, db_task, user): + cvat_annotations = TaskAnnotation(db_task.id, user) + with transaction.atomic(): + cvat_annotations.init_from_db() + cvat_annotations = Annotation(cvat_annotations.ir_data, db_task) + super().__init__(url, cvat_annotations) + + +def match_frame(item, cvat_task_anno): + frame_number = None + if frame_number is None: + try: + frame_number = cvat_task_anno.match_frame(item.id) + except Exception: + pass + if frame_number is None and item.has_image: + try: + frame_number = cvat_task_anno.match_frame(item.image.filename) + except Exception: + pass + if frame_number is None: + try: + frame_number = int(item.id) + except Exception: + pass + if not frame_number in cvat_task_anno.frame_info: + raise Exception("Could not match item id: '%s' with any task frame" % + item.id) + return frame_number + +def import_dm_annotations(dm_dataset, cvat_task_anno): + shapes = { + datumaro.AnnotationType.bbox: ShapeType.RECTANGLE, + datumaro.AnnotationType.polygon: ShapeType.POLYGON, + datumaro.AnnotationType.polyline: ShapeType.POLYLINE, + datumaro.AnnotationType.points: ShapeType.POINTS, + } + + label_cat = dm_dataset.categories()[datumaro.AnnotationType.label] + + for item in dm_dataset: + frame_number = match_frame(item, cvat_task_anno) + + # do not store one-item groups + group_map = { 0: 0 } + group_size = { 0: 0 } + for ann in item.annotations: + if ann.type in shapes: + group = group_map.get(ann.group) + if group is None: + group = len(group_map) + group_map[ann.group] = group + group_size[ann.group] = 1 + else: + group_size[ann.group] += 1 + group_map = {g: s for g, s in group_size.items() + if 1 < s and group_map[g]} + group_map = {g: i for i, g in enumerate([0] + sorted(group_map))} + + for ann in item.annotations: + if ann.type in shapes: + cvat_task_anno.add_shape(cvat_task_anno.LabeledShape( + type=shapes[ann.type], + frame=frame_number, + label=label_cat.items[ann.label].name, + points=ann.points, + occluded=False, + group=group_map.get(ann.group, 0), + attributes=[cvat_task_anno.Attribute(name=n, value=str(v)) + for n, v in ann.attributes.items()], + )) + elif ann.type == datumaro.AnnotationType.label: + cvat_task_anno.add_tag(cvat_task_anno.Tag( + frame=frame_number, + label=label_cat.items[ann.label].name, + group=group_map.get(ann.group, 0), + attributes=[cvat_task_anno.Attribute(name=n, value=str(v)) + for n, v in ann.attributes.items()], + )) \ No newline at end of file diff --git a/cvat/apps/dataset_manager/export_templates/README.md b/cvat/apps/dataset_manager/export_templates/README.md new file mode 100644 index 00000000000..a375bbdc0c4 --- /dev/null +++ b/cvat/apps/dataset_manager/export_templates/README.md @@ -0,0 +1,20 @@ +# Quick start + +``` bash +# optionally make a virtualenv +python -m virtualenv .venv +. .venv/bin/activate + +# install dependencies +pip install -e datumaro/ +pip install -r cvat/utils/cli/requirements.txt + +# set up environment +PYTHONPATH=':' +export PYTHONPATH + +# use Datumaro +datum --help +``` + +Check Datumaro [docs](datumaro/README.md) for more info. diff --git a/cvat/apps/dataset_manager/export_templates/plugins/cvat_rest_api_task_images.py b/cvat/apps/dataset_manager/export_templates/plugins/cvat_rest_api_task_images.py new file mode 100644 index 00000000000..2bf1b507e3b --- /dev/null +++ b/cvat/apps/dataset_manager/export_templates/plugins/cvat_rest_api_task_images.py @@ -0,0 +1,135 @@ + +# Copyright (C) 2019-2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from collections import OrderedDict +import getpass +import json +import os, os.path as osp +import requests + +from datumaro.components.config import (Config, + SchemaBuilder as _SchemaBuilder, +) +import datumaro.components.extractor as datumaro +from datumaro.util.image import lazy_image, load_image, Image + +from cvat.utils.cli.core import CLI as CVAT_CLI, CVAT_API_V1 + + +CONFIG_SCHEMA = _SchemaBuilder() \ + .add('task_id', int) \ + .add('server_host', str) \ + .add('server_port', int) \ + .build() + +DEFAULT_CONFIG = Config({ + 'server_port': 80 +}, schema=CONFIG_SCHEMA, mutable=False) + +class cvat_rest_api_task_images(datumaro.SourceExtractor): + def _image_local_path(self, item_id): + task_id = self._config.task_id + return osp.join(self._cache_dir, + 'task_{}_frame_{:06d}.jpg'.format(task_id, int(item_id))) + + def _make_image_loader(self, item_id): + return lazy_image(item_id, + lambda item_id: self._image_loader(item_id, self)) + + def _is_image_cached(self, item_id): + return osp.isfile(self._image_local_path(item_id)) + + def _download_image(self, item_id): + self._connect() + os.makedirs(self._cache_dir, exist_ok=True) + self._cvat_cli.tasks_frame(task_id=self._config.task_id, + frame_ids=[item_id], outdir=self._cache_dir) + + def _connect(self): + if self._session is not None: + return + + session = None + try: + print("Enter credentials for '%s:%s' to read task data:" % \ + (self._config.server_host, self._config.server_port)) + username = input('User: ') + password = getpass.getpass() + + session = requests.Session() + session.auth = (username, password) + + api = CVAT_API_V1(self._config.server_host, + self._config.server_port) + cli = CVAT_CLI(session, api) + + self._session = session + self._cvat_cli = cli + except Exception: + if session is not None: + session.close() + + def __del__(self): + if hasattr(self, '_session'): + if self._session is not None: + self._session.close() + + @staticmethod + def _image_loader(item_id, extractor): + if not extractor._is_image_cached(item_id): + extractor._download_image(item_id) + local_path = extractor._image_local_path(item_id) + return load_image(local_path) + + def __init__(self, url): + super().__init__() + + local_dir = url + self._local_dir = local_dir + self._cache_dir = osp.join(local_dir, 'images') + + with open(osp.join(url, 'config.json'), 'r') as config_file: + config = json.load(config_file) + config = Config(config, + fallback=DEFAULT_CONFIG, schema=CONFIG_SCHEMA) + self._config = config + + with open(osp.join(url, 'images_meta.json'), 'r') as images_file: + images_meta = json.load(images_file) + image_list = images_meta['images'] + + items = [] + for entry in image_list: + item_id = entry['id'] + item_filename = entry.get('name', str(item_id)) + size = None + if entry.get('height') and entry.get('width'): + size = (entry['height'], entry['width']) + image = Image(data=self._make_image_loader(item_id), + path=item_filename, size=size) + item = datumaro.DatasetItem(id=item_id, image=image) + items.append((item.id, item)) + + items = sorted(items, key=lambda e: int(e[0])) + items = OrderedDict(items) + self._items = items + + self._cvat_cli = None + self._session = None + + def __iter__(self): + for item in self._items.values(): + yield item + + def __len__(self): + return len(self._items) + + def subsets(self): + return None + + def get(self, item_id, subset=None, path=None): + if path or subset: + raise KeyError() + return self._items[item_id] diff --git a/cvat/apps/dataset_manager/task.py b/cvat/apps/dataset_manager/task.py new file mode 100644 index 00000000000..4c5e941081a --- /dev/null +++ b/cvat/apps/dataset_manager/task.py @@ -0,0 +1,348 @@ + +# Copyright (C) 2019-2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from datetime import timedelta +import json +import os +import os.path as osp +import shutil +import sys +import tempfile + +from django.utils import timezone +import django_rq + +from cvat.apps.engine.log import slogger +from cvat.apps.engine.models import Task +from .util import current_function_name, make_zip_archive + +_CVAT_ROOT_DIR = __file__[:__file__.rfind('cvat/')] +_DATUMARO_REPO_PATH = osp.join(_CVAT_ROOT_DIR, 'datumaro') +sys.path.append(_DATUMARO_REPO_PATH) +from datumaro.components.project import Project, Environment +import datumaro.components.extractor as datumaro +from .bindings import CvatImagesDirExtractor, CvatTaskExtractor + + +_MODULE_NAME = __package__ + '.' + osp.splitext(osp.basename(__file__))[0] +def log_exception(logger=None, exc_info=True): + if logger is None: + logger = slogger + logger.exception("[%s @ %s]: exception occurred" % \ + (_MODULE_NAME, current_function_name(2)), + exc_info=exc_info) + +_TASK_IMAGES_EXTRACTOR = '_cvat_task_images' +_TASK_ANNO_EXTRACTOR = '_cvat_task_anno' +_TASK_IMAGES_REMOTE_EXTRACTOR = 'cvat_rest_api_task_images' + +def get_export_cache_dir(db_task): + return osp.join(db_task.get_task_dirname(), 'export_cache') + +EXPORT_FORMAT_DATUMARO_PROJECT = "datumaro_project" + + +class TaskProject: + @staticmethod + def _get_datumaro_project_dir(db_task): + return osp.join(db_task.get_task_dirname(), 'datumaro') + + @staticmethod + def create(db_task): + task_project = TaskProject(db_task) + task_project._create() + return task_project + + @staticmethod + def load(db_task): + task_project = TaskProject(db_task) + task_project._load() + task_project._init_dataset() + return task_project + + @staticmethod + def from_task(db_task, user): + task_project = TaskProject(db_task) + task_project._import_from_task(user) + return task_project + + def __init__(self, db_task): + self._db_task = db_task + self._project_dir = self._get_datumaro_project_dir(db_task) + self._project = None + self._dataset = None + + def _create(self): + self._project = Project.generate(self._project_dir) + self._project.add_source('task_%s' % self._db_task.id, { + 'url': self._db_task.get_data_dirname(), + 'format': _TASK_IMAGES_EXTRACTOR, + }) + self._project.env.extractors.register(_TASK_IMAGES_EXTRACTOR, + CvatImagesDirExtractor) + + self._init_dataset() + self._dataset.define_categories(self._generate_categories()) + + self.save() + + def _load(self): + self._project = Project.load(self._project_dir) + self._project.env.extractors.register(_TASK_IMAGES_EXTRACTOR, + CvatImagesDirExtractor) + + def _import_from_task(self, user): + self._project = Project.generate(self._project_dir, + config={'project_name': self._db_task.name}) + + self._project.add_source('task_%s_images' % self._db_task.id, { + 'url': self._db_task.get_data_dirname(), + 'format': _TASK_IMAGES_EXTRACTOR, + }) + self._project.env.extractors.register(_TASK_IMAGES_EXTRACTOR, + CvatImagesDirExtractor) + + self._project.add_source('task_%s_anno' % self._db_task.id, { + 'format': _TASK_ANNO_EXTRACTOR, + }) + self._project.env.extractors.register(_TASK_ANNO_EXTRACTOR, + lambda url: CvatTaskExtractor(url, + db_task=self._db_task, user=user)) + + self._init_dataset() + + def _init_dataset(self): + self._dataset = self._project.make_dataset() + + def _generate_categories(self): + categories = {} + label_categories = datumaro.LabelCategories() + + db_labels = self._db_task.label_set.all() + for db_label in db_labels: + db_attributes = db_label.attributespec_set.all() + label_categories.add(db_label.name) + + for db_attr in db_attributes: + label_categories.attributes.add(db_attr.name) + + categories[datumaro.AnnotationType.label] = label_categories + + return categories + + def put_annotations(self, annotations): + raise NotImplementedError() + + def save(self, save_dir=None, save_images=False): + if self._dataset is not None: + self._dataset.save(save_dir=save_dir, save_images=save_images) + else: + self._project.save(save_dir=save_dir) + + def export(self, dst_format, save_dir, save_images=False, server_url=None): + if self._dataset is None: + self._init_dataset() + if dst_format == EXPORT_FORMAT_DATUMARO_PROJECT: + self._remote_export(save_dir=save_dir, server_url=server_url) + else: + converter = self._dataset.env.make_converter(dst_format, + save_images=save_images) + self._dataset.export_project(converter=converter, save_dir=save_dir) + + def _remote_image_converter(self, save_dir, server_url=None): + os.makedirs(save_dir, exist_ok=True) + + db_task = self._db_task + items = [] + config = { + 'server_host': 'localhost', + 'task_id': db_task.id, + } + if server_url: + if ':' in server_url: + host, port = server_url.rsplit(':', maxsplit=1) + else: + host = server_url + port = None + config['server_host'] = host + if port is not None: + config['server_port'] = int(port) + + images_meta = { + 'images': items, + } + db_video = getattr(self._db_task, 'video', None) + if db_video is not None: + for i in range(self._db_task.size): + frame_info = { + 'id': i, + 'width': db_video.width, + 'height': db_video.height, + } + items.append(frame_info) + else: + for db_image in self._db_task.image_set.all(): + frame_info = { + 'id': db_image.frame, + 'name': osp.basename(db_image.path), + 'width': db_image.width, + 'height': db_image.height, + } + items.append(frame_info) + + with open(osp.join(save_dir, 'config.json'), 'w') as config_file: + json.dump(config, config_file) + with open(osp.join(save_dir, 'images_meta.json'), 'w') as images_file: + json.dump(images_meta, images_file) + + def _remote_export(self, save_dir, server_url=None): + if self._dataset is None: + self._init_dataset() + + os.makedirs(save_dir, exist_ok=True) + self._dataset.save(save_dir=save_dir, save_images=False, merge=True) + + exported_project = Project.load(save_dir) + source_name = 'task_%s_images' % self._db_task.id + exported_project.add_source(source_name, { + 'format': _TASK_IMAGES_REMOTE_EXTRACTOR, + }) + self._remote_image_converter( + osp.join(save_dir, exported_project.local_source_dir(source_name)), + server_url=server_url) + exported_project.save() + + + templates_dir = osp.join(osp.dirname(__file__), 'export_templates') + target_dir = exported_project.config.project_dir + os.makedirs(target_dir, exist_ok=True) + shutil.copyfile( + osp.join(templates_dir, 'README.md'), + osp.join(target_dir, 'README.md')) + + templates_dir = osp.join(templates_dir, 'plugins') + target_dir = osp.join(target_dir, + exported_project.config.env_dir, + exported_project.config.plugins_dir) + os.makedirs(target_dir, exist_ok=True) + shutil.copyfile( + osp.join(templates_dir, _TASK_IMAGES_REMOTE_EXTRACTOR + '.py'), + osp.join(target_dir, _TASK_IMAGES_REMOTE_EXTRACTOR + '.py')) + + # NOTE: put datumaro component to the archive so that + # it was available to the user + shutil.copytree(_DATUMARO_REPO_PATH, osp.join(save_dir, 'datumaro'), + ignore=lambda src, names: ['__pycache__'] + [ + n for n in names + if sum([int(n.endswith(ext)) for ext in + ['.pyx', '.pyo', '.pyd', '.pyc']]) + ]) + + # include CVAT CLI module also + cvat_utils_dst_dir = osp.join(save_dir, 'cvat', 'utils') + os.makedirs(cvat_utils_dst_dir) + shutil.copytree(osp.join(_CVAT_ROOT_DIR, 'utils', 'cli'), + osp.join(cvat_utils_dst_dir, 'cli')) + + +DEFAULT_FORMAT = EXPORT_FORMAT_DATUMARO_PROJECT +DEFAULT_CACHE_TTL = timedelta(hours=10) +CACHE_TTL = DEFAULT_CACHE_TTL + +def export_project(task_id, user, dst_format=None, server_url=None): + try: + db_task = Task.objects.get(pk=task_id) + + if not dst_format: + dst_format = DEFAULT_FORMAT + + cache_dir = get_export_cache_dir(db_task) + save_dir = osp.join(cache_dir, dst_format) + archive_path = osp.normpath(save_dir) + '.zip' + + task_time = timezone.localtime(db_task.updated_date).timestamp() + if not (osp.exists(archive_path) and \ + task_time <= osp.getmtime(archive_path)): + os.makedirs(cache_dir, exist_ok=True) + with tempfile.TemporaryDirectory( + dir=cache_dir, prefix=dst_format + '_') as temp_dir: + project = TaskProject.from_task(db_task, user) + project.export(dst_format, save_dir=temp_dir, save_images=True, + server_url=server_url) + + os.makedirs(cache_dir, exist_ok=True) + make_zip_archive(temp_dir, archive_path) + + archive_ctime = osp.getctime(archive_path) + scheduler = django_rq.get_scheduler() + cleaning_job = scheduler.enqueue_in(time_delta=CACHE_TTL, + func=clear_export_cache, + task_id=task_id, + file_path=archive_path, file_ctime=archive_ctime) + slogger.task[task_id].info( + "The task '{}' is exported as '{}' " + "and available for downloading for next '{}'. " + "Export cache cleaning job is enqueued, " + "id '{}', start in '{}'".format( + db_task.name, dst_format, CACHE_TTL, + cleaning_job.id, CACHE_TTL)) + + return archive_path + except Exception: + log_exception(slogger.task[task_id]) + raise + +def clear_export_cache(task_id, file_path, file_ctime): + try: + if osp.exists(file_path) and osp.getctime(file_path) == file_ctime: + os.remove(file_path) + slogger.task[task_id].info( + "Export cache file '{}' successfully removed" \ + .format(file_path)) + except Exception: + log_exception(slogger.task[task_id]) + raise + + +EXPORT_FORMATS = [ + { + 'name': 'Datumaro', + 'tag': EXPORT_FORMAT_DATUMARO_PROJECT, + 'is_default': True, + }, + { + 'name': 'PASCAL VOC 2012', + 'tag': 'voc', + 'is_default': False, + }, + { + 'name': 'MS COCO', + 'tag': 'coco', + 'is_default': False, + }, + { + 'name': 'YOLO', + 'tag': 'yolo', + 'is_default': False, + }, + { + 'name': 'TF Detection API TFrecord', + 'tag': 'tf_detection_api', + 'is_default': False, + }, +] + +def get_export_formats(): + converters = Environment().converters + + available_formats = set(converters.items) + available_formats.add(EXPORT_FORMAT_DATUMARO_PROJECT) + + public_formats = [] + for fmt in EXPORT_FORMATS: + if fmt['tag'] in available_formats: + public_formats.append(fmt) + + return public_formats \ No newline at end of file diff --git a/cvat/apps/dataset_manager/util.py b/cvat/apps/dataset_manager/util.py new file mode 100644 index 00000000000..c18db840105 --- /dev/null +++ b/cvat/apps/dataset_manager/util.py @@ -0,0 +1,20 @@ + +# Copyright (C) 2019-2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import inspect +import os, os.path as osp +import zipfile + + +def current_function_name(depth=1): + return inspect.getouterframes(inspect.currentframe())[depth].function + + +def make_zip_archive(src_path, dst_path): + with zipfile.ZipFile(dst_path, 'w') as archive: + for (dirpath, _, filenames) in os.walk(src_path): + for name in filenames: + path = osp.join(dirpath, name) + archive.write(path, osp.relpath(path, src_path)) diff --git a/cvat/apps/dextr_segmentation/static/dextr_segmentation/js/enginePlugin.js b/cvat/apps/dextr_segmentation/static/dextr_segmentation/js/enginePlugin.js index 548f19f64aa..0869ba8df04 100644 --- a/cvat/apps/dextr_segmentation/static/dextr_segmentation/js/enginePlugin.js +++ b/cvat/apps/dextr_segmentation/static/dextr_segmentation/js/enginePlugin.js @@ -54,7 +54,8 @@ window.addEventListener('DOMContentLoaded', () => { Object.defineProperty(instance, 'defaultType', { get: () => instance._defaultType, set: (type) => { - if (!['box', 'points', 'polygon', 'polyline', 'auto_segmentation'].includes(type)) { + if (!['box', 'box_by_4_points', 'points', 'polygon', + 'polyline', 'auto_segmentation', 'cuboid'].includes(type)) { throw Error(`Unknown shape type found ${type}`); } instance._defaultType = type; diff --git a/cvat/apps/documentation/AWS-Deployment-Guide.md b/cvat/apps/documentation/AWS-Deployment-Guide.md index 5b40ee392b3..79aa404ac00 100644 --- a/cvat/apps/documentation/AWS-Deployment-Guide.md +++ b/cvat/apps/documentation/AWS-Deployment-Guide.md @@ -6,4 +6,27 @@ Overall setup instruction is explained in [main readme file](https://github.com/ 2. **On Any other AWS Machine:** We can follow the same instruction guide mentioned in the [Readme file](https://github.com/opencv/cvat/). The additional step is to add a [security group and rule to allow incoming connections](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-network-security.html). -For any of above, don't forget to add exposed AWS public IP address to `docker-compose.override.com`. +For any of above, don't forget to add exposed AWS public IP address and port to `docker-compose.override.yml`: + +You need at least 2 opened ports on your Amazon instance, for UI and Django apps. + +``` +version: "2.3" + +services: + cvat: + environment: + UI_HOST: *your Amazon AWS instance's url or IP* + UI_PORT: *port for UI app* + ports: + - "REACT_APP_API_PORT specified below:8080" + + + cvat_ui: + build: + args: + REACT_APP_API_HOST: *your Amazon AWS instance's url or IP* + REACT_APP_API_PORT: *port for Django app* + ports: + - "UI_PORT specified above":80" +``` \ No newline at end of file diff --git a/cvat/apps/documentation/__init__.py b/cvat/apps/documentation/__init__.py index 743392981f0..a0fca4cb39e 100644 --- a/cvat/apps/documentation/__init__.py +++ b/cvat/apps/documentation/__init__.py @@ -1,8 +1,4 @@ -# Copyright (C) 2018 Intel Corporation +# Copyright (C) 2018-2019 Intel Corporation # # SPDX-License-Identifier: MIT - -from cvat.settings.base import JS_3RDPARTY - -JS_3RDPARTY['dashboard'] = JS_3RDPARTY.get('dashboard', []) + ['documentation/js/dashboardPlugin.js'] diff --git a/cvat/apps/documentation/installation.md b/cvat/apps/documentation/installation.md index 7fb94fc7eee..1286fe813c2 100644 --- a/cvat/apps/documentation/installation.md +++ b/cvat/apps/documentation/installation.md @@ -58,7 +58,7 @@ server. Proxy is an advanced topic and it is not covered by the guide. ```bash sudo apt-get install -y python3-pip - sudo pip3 install docker-compose + sudo python3 -m pip install docker-compose ``` - Clone _CVAT_ source code from the @@ -100,7 +100,7 @@ server. Proxy is an advanced topic and it is not covered by the guide. install it as well. Type commands below in a terminal window: ```sh - wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - + curl https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list' sudo apt-get update sudo apt-get install -y google-chrome-stable @@ -240,6 +240,7 @@ server. Proxy is an advanced topic and it is not covered by the guide. - [TF Object Detection API: auto annotation](/components/tf_annotation/README.md) - [Support for NVIDIA GPUs](/components/cuda/README.md) - [Semi-automatic segmentation with Deep Extreme Cut](/cvat/apps/dextr_segmentation/README.md) +- [Auto segmentation: Keras+Tensorflow Mask R-CNN Segmentation](/components/auto_segmentation/README.md) ```bash # Build and run containers with CUDA and OpenVINO support @@ -261,9 +262,8 @@ docker-compose down ### Advanced settings -If you want to access you instance of CVAT outside of your localhost you should -specify the [ALLOWED_HOSTS](https://docs.djangoproject.com/en/2.0/ref/settings/#allowed-hosts) -environment variable. The best way to do that is to create +If you want to access your instance of CVAT outside of your localhost you should +specify the `CVAT_HOST` environment variable. The best way to do that is to create [docker-compose.override.yml](https://docs.docker.com/compose/extends/) and put all your extra settings here. @@ -271,11 +271,9 @@ all your extra settings here. version: "2.3" services: - cvat: + cvat_proxy: environment: - ALLOWED_HOSTS: .example.com - ports: - - "80:8080" + CVAT_HOST: .example.com ``` Please don't forget include this file to docker-compose commands using the `-f` diff --git a/cvat/apps/documentation/static/documentation/images/CuboidDrawing1.gif b/cvat/apps/documentation/static/documentation/images/CuboidDrawing1.gif new file mode 100644 index 0000000000000000000000000000000000000000..42b31b06e217a95ae55d277c55d6ffdc06feed74 GIT binary patch literal 716427 zcmV(wK^5f}~< z4GtP64-XR$5)%&{Ee{|!5ET><78wv4AQci56&4#77Zny7A{QAK7aJZIBQF;vH5eTi z7$77VCOaJ+866!V9uyQFC^Q}_Js%?+A0s3mB`Y5>KqEFzBrrB4GC?IFBqb^|CLtRp zC?h5*EG94|CNMB35fmykKrA^=EjvOjK~gR!CN3&7E;BYRMPV-{9WOF0Fc1?oC@eHI zD>OJfG(=K2OJX=TG&wpvIYBl#QgJ#94?IFeJVsAFBPKmZJ3U88K220WG$lYbExOi@H&fJI17MnXA8Pf|!oKS^70N@#~m2MkM8RZLSpOjch` zO+rp@kWW}vPg`P7WOPqzfKhj$QeHt)YIjp%WK?Z}R&j@1Y<68uPF{3~Uqd-xd6HmP zWMF`zVOBt4g|%a1WMgYeWM^h%b9H82QfCt;XKGe!1_x`Cx^7NSZ)|9Be^+vdt#X>a zbbf+#g_w6mIe2z-cz%0$rpn2(j3la;pFmX(2)nwFU=Cz+3dnVXZFabuf!RGWTyn~Hp!ot2zlN}RO0 zorGnbkdU31ke!&3ouQYWm5`sBlb@ZHqMVANq@1LqmZYYjq^YE(n0}_6m8bv!sD@>! zgLtYWDXbP#tdDuDq?)X(ovk4wtyp=k92u^Pd9SXfudk`GUS+bEd9$2`v$nUjWKXrL znzn^#wco7RRH6%N%seu$Rmd5X=)e z&R}`cf_vD%s@U)M+P}5h(Z1WcncZuj-QL>bzsu(d2kZ)Z?O|Q<+r9Mn`1R%8_C-PX z0s;K_`~Um-A^!_bMO0HmK~P09E-(WD0000X`2+ePushEJa;VB~}$l;}*CL~*plffR<)moahNKq1vBRGL>&6qNB}#t#}a zj)46kLu(0yCQUkA@lfE+wrNAUAUd~b-MTn_;MmLb=3KseYYLuwa|hrZg9Q&p2v?zzL)AttW2xYF4(i{kQrjZiVe;dZHTmKLp1+R8M~z7=t!A5 z1>E=MdcaPb5@v6(<_^L&bG|Llda`87k>ytzTCx>HQ)?@+=9Xv-ju!?W<=q(b-t=mM@dN7Erwm@ z7H7AAru$e+wxwqvq+uSU-%gQERZzR_CYs|@$k~Whz0&zBWu#ou;1zZTm#STGS|#+| zg-0HAR>a7{dK0ZqP6^+w?!9E|dQKU(s}Qy#=3YX$<#%k9_*K?ye`l6NV4o1ZyAWfm zVwj|3s*U9Hs}zyOkw>lp9q(mAF_x!fJi~^NjaEsu9JJ-?WG{2=BE{RjQmvRNO}5p0 zoS-gpii1x_AgnN`rIMx8!)AtO6Vu@B_9SAy=2azMNL~N@>y?1>rPr=FY>61IkP+Kb zs-Gz&zAm*z&w30;V#u-|jcZRz(d64C%wEa2*6o+QQo|h2k zeXMyJzFM@kqVPs)QV9-@VgjA5MWtR2EE^2aX0TDw00^X_!4R_ZgN8i@L*IGWSvcet zyVdG2FhER`_69h*1de2bBiRrLS1T&fg){OpR+|5wg{Jg@2SEENVs13EnJ%4he)P)| zXdIKR(J|y(5RxCW{5LlMR;xU!n-yU0^)Jd?W>EaH-2-tagMA4qMmZuQ?|MU>ul#5R z3Cm8}z@j#?Fz;s0tC_^g46W3&@8lOwnbvbiAu_92c-xpE2H2jGUXh~9{v0~@N1XU|ZONk`E2Bn`&&PY0HgcNV0 zqC42#MpMY#*S~V)6Sj#(EO^|b!bAls6Vl8raD!9na><@ueGE%PiJ`6dCM&0Sb9%q> zCg5bqp70roNEQ+i43gzCh<&n%Cp#3o5SRaut{G%z)Pkizqo}9RsWKz^JD0P#h?OFh zZf6iBh`+i9Hb}XIq4}yxZ3e}_O93oLO_2@(lS9WHbr6p~O4u}sC#p`t>3<6{*K7Kz zJ@%jzobJ&Wdgii}?OiWEdaEJd4i`>$n$ZOZ3Lp7q#>oNU)2T>{T9@9Frc)}^pxTlX zx(I3#EPA9&>pIFlE7y>0Z7zT;(Hie)Vmo}jk)u#!E2g~G9ExgcM$8%JqRu8lmTE^k zcnl%is)-PIJ`bMSjMoeA)}HyGGo1Q)58u?op1i?`F2(9kl=>zZy>byvpXDS|*|i=N z*d#p&l%<3?N2h@j^ophFNFWn9I}!i2tCIr-4H)AIou=d~n9FomdI6$S! z1to0U-rC4HYIWCBjN2lfoNfQQ*ZGPb4>n!Fir9}}9f(Z#V@~Tisi+A_Z+udQ<3;|E zk(S)cn3{@2521F9^UX44p4el^f-;FuLr(gdmM14av@_o-4Xzg0z}uzlHm>sAmaSFW z-wIR1#ALIqX~djhdQ?gr1tzIN)8ok^s?6*cp#1$IqXqE&TW zes8;~VwLyC`@IhQ?e9pt>jzeck8+lQ!tN;8U{kY%y~K?LUFSL+k8{v7qz^dx%|Jw7 zX)xi1Z}Pxna4(J=o$EoY~7#CzJaZ$zG@L&e2lh&C9Zcd9MD9cXHQ(o%{wS5k-Y z1tZ>E^xwxcb%K2ibJk(G)uY~P!M@U!h671qaYp!9Oa-=r@P#VEks#QG6I8x^XzcWP zlyTI9V@2DiD%g%;xEq}~jtc8!=WXv}#$EDC$4!CYfieqsTXPlk09?5?;Hb6cPu@Iv zl}sBT)PqU%rYs#NmN#_}G0)^PCT&p%*E(S9EZDzpy=^dY3fSE?IIf)S&3WNUcU6BW z!2t$p#vPlD!6!ps`M%spJzvRBejxG0Vf4mgd48$&1nr%RV z-ZV~VCqZckRODq8<(4J!(JOv-AGi`@!Y6z!)KUt0pG0XuL}}0xWP7GvoQ7{1 zLLspgF6+f~1b1>X5j$FBbI8C`#g%e_PDFstvrPm`s=xbDG zVqT#ajN*P|u`9B-gih#9u2(Cd5`bUFc%-Fi#o}l()OK5FTrThexw2^6);REDN=hX} z_QYe$5-*c>G>jutT6AwhrxJ#Tc&!E|xN>_!PTb0o&sv;=z zGEBm;74QE?gdubU3g;t0f-v$I7LRyhwYP)-NI1K~HdHt`a`sGwLlylt9JAD4`h{{t z;d?HSNbRL8b4O%uF*ZmeU&pt3)Y5X0!;7ji8;o;0?k0Wih7v+UWoI;6r`1K9BZ8?@ zHy34U48$VSArr>6OIl%Dy{Kn=qED`KHU%SzM3_=1Wk**xVuOW=`3DnI;e-Il6-@|* zOz2I|bOcB672;JDF(`icK@<1UcAYhbc42;e(jmCvKiI^Lxj`gEvSMrKj7PLj?X^Ua zSB#KXRyL4xK*Ut><9#DYeQ=Q?(s3LzLP5txL19BLpcOabQILm{G_MDWcSL$2W_qP^ zFns@Xj-Xf;5#)~Y$cgiq7v2Oa_81mCv^b8!c~&tsAcHtKhH7KDh&&iX4>J@xL6wP> zO%tO+IMXVzvOY%QOK*r>5D9s1ObHCD3|iLw(xBvTy8gY=?L zj#y_Q7HlB|n1ET6TQ^;919tE@k4`9*{->BGMRlo1aKK0%fl*WjbRO!lU(h!*TvS+7 zc__i7l6DyaTtpHf>``M2G)k z6AY=BXQL>=xt9`yZcpTuMkF&kb~>>%9=1t`C73O12}WE+o3P=M{yCn(nI;~nmjJq# zD#9sb0hmTuXAUO=cr=OR=$D`f6I2nMohW;eZ_Q9a{CJ~a^n`EeF@$(K&bYvA}3Wubq58XP|%6IY3D?-?&)Ic}!phj0G@bQLKO zUPX+OGbeM%EJ~UtaKk52TAmR@G1USmRe3L+@)M+2oOWc6DK(QNHi@QEgl9t=XZmLS zSEhu6p%{}pw>licF?L{QD(v`q>6tXyNJqmeW-t+`ed?!V@e|<4DfBW=LAFYBXgWDl zCaG4N;8B8sH?8aDC${vh9C#29lc@_yo}iVGP{a_Rs%Lg%HoZ2EgbA49c%_jzVytSI z8j3rMxtJI_olXcUuXn50$v0jnlvL7$Vn;{3#%q#dtS9oYZA7EV8mK!$suX*yCDKcl zu|nKxt%NsVMH-ujR~k|FX%cZiYzdOT*pLz9KPtMO@7Wz3YZCe-94Y?*onTQgllX%v zrEuh!Hahu!!A2IQ!Z2IWdh!UJOc+T3+g-RCuz(?U!tsgtD5!4pqBD9Tg8F*Qy0wB@ zp%qH8V!Nz>>a|KyA_aM*9Xm*EIgH>MK-&6!l{zQ*CbwT187w+6-Lal~yAUm@JP^^6 zEonESN~${wsA6lJV@tZ6;v5-Epq^ox9UHPL`-~7#hY)h8>O!{3WnYkJR~uLAgCn`+AgXo$UXCu%TPF7i+qJ8oDqc zqoPZ;=1Z}_;jBDqqscbEDyF-$J4IYZH&doR_uH-B`lzvCPo_Cf4kNBzG#SKunr*SJ zc#2Gb`j@3jnDBafzZQR>IH6%zxk%fuPbfi{+cjr;wRMG<(g}`eTCwaawx~;?S`oIT zdp2U$6UxgdWsw5EL99B#e0wPrfXl6ts!(z$f*V1yA?SS#Vv&)zu5;s%1kpk~#<4L4 zxCzOz4QU%+OR71$oX|@=lUQ|-Xq<%^reZ9@lB={L{FB^6AEKC;%^^Ws>wZ+a!l*mP zqASBVAfp)zdR`mDHrm1>;2SNx0wU0&qq(O;Q87RK!xsPfo{e#g`cpAJ+)5^k$g!&s z+9bT6lvtACm24r!E!z@*YPEy;ybU*9Uuvp}OPHJ(u(9fnW*mh&1-|@exfGhFd!@BH zBEzHVtT5WkQ<29jN}BYN#~B;Q=&7)I>ZJ10LQpIwF9TK~YnGuSTaSv!Mmj%048RI} zBzyN%E2$)HSuw&qrpkIS_j8#Cd-ae1wIGe_a2}iSQ`Tv-*E}lbBIhNjGh|dV$QJ zC(+1E$PK*{r}?+5kwQnZ0>b>%CUT&zgToQs8|#^<<*{Nwx~*H?g17r1{A;(>Jbi9r z#7j-HveQ4p$P$6n%?hcvp5hZa@}(y>wA8yc5S+6^`?&en*Q__WIK8!0Se=?1tHF^d zpWM_(z1ZeTp9r1Q4V)7U9h?t6)p-o8I&973cDz4hF&`-z8vV#6YZ6}RsJeC0JHynU zrEJ1zz(}nimV6lvth_Ri0$4m)@#n>09Ad1Rj$(Ylt?Im#%ddfLE9xY*;0u(Y$+v~I z*bTjv55d&panRA-)RE1bEj$$w>wc30(K`Q;85*0UFtb{D7I-d*mel;Vx75klT$Z&< zND@6ro!L}Tsvj|R)h6N-%iBy^_lSc9!Ch?5c|8_j%G0wNd!f5M1lXYn$e|f374vJn z&K;oZD%}y2z!XC-lWii$`l^CD*RR?z5}h=9i=)nb$(yD=ZQX~{EZS|`%&O**CX2+M z*{M}&8C)5>-H501!q8#M(!5RJ@Vc`w?cdLfe_hPetPH+TTUTF~T6o&w__enae$+%= z zD@gYH7Ye7 z2JXY$?UKFn>+aMp|MCSL^Q8Y7;=I|i7X6Em!PzIYi)#s~U_C`g+P6kT-V>3PE53{O zou>tQC~#fK<(i(l48zPT!C^WB)Z4{bOnM6X>_yAMXIneSF=dim96{#m{N<$Ef zVlNceMr&Wx8m<#ejnILs=Ph^RV1N8B?-n76;-q>>2V}WiUtX;5V3+`PJ z_SO5a<%$vwk5VKYRPj{CRFyV{99eQ>hg})!#O(N@M4A&LLj4?3<4n$_W1fcT(gwS`O)+B#K!~6kFhO8TE3?c1WkPL1mQG?#+1FYkVYX*uBP@g) zWazWc4S_%ch!5Qw(I6Rk&5kgT3cS$K<o1{f6jPp500 zv$y}K%W8uUOIjr@tTv&rDz7aezY@Epm7@w z5KT0P{iw`XOG1-DXF+mMrOqrolTL>PlHpIB`RsN>5aZ*uI~iCq*P?)@oN}fW9eQ^l zg>b-g-c2{1dOfV)yR+z|-3vY7fxVK>$h6@4N^@Wddo8G!2YY*NNS-=$9j&b8TVRO{UTEdQkd;tvF9e-uMgkh; z2t{iD`c#Svf<43}>T{zR3Z}*)q#>1Mcku(Bf~o{81tq6SNyK0nD|RoO(RR4o{7091~ggCc+)Zd2r;63KsO7YvHD6e?)=LNMyv+-2io{YZ9LBI6}Xr%UqqC z&F{zrABG$fUQUt{M4Yl5n>@*5?b=lC$hNx%Ugty%nO2KH^2kVKMvSlAQc${t85uk) zi;nXkRP56p<8#p0i5cph$n@~$Ww8a5u^; z(qIUux@8j;B0xz!1)#OUiq*85FRlXgpw00oN=GzSLS7|rI+4K^Z}Kh}{6(p6?Tb?X zLeYsj4Vca{TnQ4_8qY}OX+g#0vBbol>b0$Hi5X{!47p9C+UrtFQqTV(w<%fF0n@Hh zGtNMo#t^w(=02EQiZ79*BlsoNu{)}#SSnP4#s2kvQWDD34*Jy@`ADGf^6FIm$&{-y zrL6MtikDp4pI_<)qS@@r4lruSr{SQv*Q{(?Ns3pRY_D0?!{l9?a)ajuYjJA|-XxLY z6?&cavCz3zHX}P(fAtcwe2Lm;F=IEoZS6lW{mYHwr_`}rR7YkLi&Mhu5Wh&pnV8F0 zXZwjcpJr8c^Yu{x7t9#0A`eIdCCVYYl3t5Ow8OvL!3{!;+^3azu}=GCyuze1**M6f z;{26%SGB6|dCze$p$QZPa?yiW=AxD{Z!Z(#WAgCDs2n~IV6OkU-fs4DDV+6ZbM+$R zPW`yFr~NOYPIHhS3_+<}oUmq_OpxC8&uU)f6>eSmPA&abGI#;#l24c6LO#u|J8%Ja zLkxo*m{_)U^>c89C``}Hv3oeaX*YoxSf^AdSsNwX8GHN#K05Zt(~%-zjEtTtpSHi6 zE^g}LtIguJR%&`_Y<{hbX-?@^o=Zua3x05fD;xoJtK4R9@KfL2QkbA!wOX3djOGQ? z*3FX<%Y(NH-I_x5X(8S-b5RWFQn&T4gjQBDRoGYhSa!w+e)I>2skVj46Lkc;)R9n^?Mh;&wY9zUBFc2>#L9)aZFR_U>$I)l2un!#!X+lZ zE%N%Gzy8m!ZM#8+klnTo2bipMsFToooL+Blu0@s0+zzLwq8To^fFKQ%y{0MA-VQ45 zZk*R}m($#njd!}|t#5tzd)0V}Uy-Lg<%Bfd(&i0#=5yp!dmdqTPwkF*xCoOK%=!pJ zz`-%m?)9$=^weYT=iUIi!B+xlA#E+ObSs;g3B>!xL@xxGUcV;MpDDGvWDTQy5tk~l;<(UPmj8$Zk|kAkT|sXISY zN_6;E1$|t0u7}lNvyu5;NbTu!V3RN$e*l)Rb512^2t)9Sc5_vm{HZ zyo&lT2uu%*iYWy7!Un;>ms7M2%snC+ro9t4;ove#10yQ4m&gOP9>cy9oVU8LL&z%? zbIZe0d5g>w8?^|;^#Md?R6nRnrMvinM!S%0ij{%!CnID%D8!(jc#ceYso=Xp*2%&T zYcKZ#HB%Hcn_HZ|!>*fZzM)dFGo&sH#F|NypOCt~qe{FXBRUquvKTBzS8FvR$+GIG z4z{?C4H<$Q+(AKvNC>i}c2X0oN{s*fbHpaYsW9WlS}};V`Np78I!&v*DZ9eIcqw~J zvh{i)!$H35`Ut21v@xVHY#Y2YQ4}ug3*@*y7(_u;b3vn0H4_<+I!rus(?#-#JcHy8 z2bw3f7y|YALmYHS_1m1ZX{)fH#=*Fno+_xR*vRDR$cg!hjZ4Xqq%w3gu5?T?rn4wm zJe*}&MG*q5MJvP35jQpgA(D8hgb=A1VlSn0K{yk|PD?r@$;Ez5%3(xIDH5gsS_>Uf z#;UB!hFqV#cohQMNi{KueEgcQVv4XFOI<5Ko$!hsD@9N{#|#@jrE974Aik(64UlP& zmNBNjg1Lxt5`}P*6@xzSVj}-MQ8K=Xp(hJWI4iJ2LdO_Hx5vUh#xzV(+K~*&JnXoR zA=s0td`9*2JVT6_CZWIr%8dJSsnL{`jMEQ|NjA51FL$H7)kMo)n!KH)#t&3Qn6wDL z8be?Lx0{qjcI+#B%PIbm3X?<#i(4|`yRYkuCpcUZp}a%3(2n0o$Q_x1h6Kb}%g*%c z!I=vU7_yejD5vriqg#m>;rYlV5-Sp6#1>7vl>K1v0Tx+Ng5eRtdBgPUS$8d#w4y#q@Hw_(VFR_@L@aJ|xY}c%%;xBs@kPwED;8o z(Si_|JvG?R+=~CL*q4eW7=_%M7=$T*d_j@~Kh*@ab+ptAbQ#h>z<3m@83A zH|I%{GU?Rj6c1D*)cE49C2^&vIHO65m7=r^ez7=E84~RD4yl7FR63yVx|7WKt&9nZ zVm+y<^_meOn)z{2uoA8&d=BUM6fQ}qf;o~NxeX)PF{Fw>lZ4PYbla$D*^fYpM~6Iu;SSKVE#$I21}cV+vn#C$4GIJtDBiyji8owGyFD%_~3210GbQ zpmLHzBcdHUMOdmO)|A52`!R?o!!7?oD?d_QBgvbAQr5Wu7p@e<%u~?HQr^vQR9Tpvx3euh!TQ}=B|h?>5~p=x&ml@ek=KdD}5eli!RJ(mT5(b zsF2RHJQL1pK4pl4X^ZZQZWiC6K93W*>5_H$j$B%Z}``zU-9H zY`Lav(5CFPmTb&6?a@Z<)K=@cMr_EIY|W+u$zE+LXc^en?ArG1+>UMCChh-}fPy_} z0^k>ls&rh?xNZtNECJveXj_U`o7ZtD*3?-uU%R&Ml`Z}fieO6U-C3BaxK?# zD8F$ezi~IG@)?(MH+OT^u=62*5I2`{B+qaxh=M4911f+6GlzpT-|Z2P^DU=wDDQzR z5Aq~;W~-#_8kA<|Bv#T5I>y^D_8(s}t$;f!=DH zveMFtA(`g&I*hBh8g^^;_kQj$UH;`+NA-VD^$7?GvA(lgHVyw__8|+76TJfF)bpEM zZslVCr6EJ+m%cKTe52yM-M@O5oYrL(kjc^-_}I!uMLY5mB)3@xPii$ zDt!;5zl68(wNBDHG1e(t;u7U+Li%x;vja_vrUSndBxM$aP1nL)1vzzn@CSu2019ZP zY;`W@IAwdu-BM>EoEqA`$Jk#R2P9D&XoQIG$ia!ViPIBx>C;2&ojG0d1U zGWPRVg;^>kn$wuX6+$Yj--_fw>c4SGflC$v0e*bd-G0d*k`1{IdupH})(W0!AZ2w6 z2?I#moqB%EVl0Kl0m*uO82x|12Gds&KTEMOocQDg2p1&;3M?`3;6N8EZs35i0)~r- z2PH;~xT3_0g(z@rl!3z|$d4a?c*MaGN5+RLO^U>E1I0@iH!>=$N%Nw@6(VJjG+DA` z$(JZc()dyIs8NzKK=#ZSVg*5iHlb2YX*K23r&qV4=(_YPjjv+I%9v4hsoAh&Nrt82 z*6oZRGiV%{8#k_78EV_!HCl8j%9J~Svh>NaBx3)=hX>1GSeWGvhc%;4R1sjGK7aoH z{liuO!@z|NBS&^Ax#GmsQ7KAoy*e}u(@_YgMkljc>JzvTN7pdg~%VGNUdHyYb`8pYh(V zT)H!c+#TZfI$nAeHJ4gsEd>{VN?|n^gO54*V1p30^w4Cq8Dtqf`sl+MXVeH_feSKi z6q!sV&a|3^uN6dDY6*#^Qi>8Km(fWmfu!AYN&&VJa973V5s$wKnbLAOS;bU#j4`+Z zPju1XpIlJ>m7_=9ad#1xKFU0ns-P(Y?P99J{F^cz$EAyrgTKaix< zvH#5w#eVwvm8@F44)^PD#W9(quz!6O>$YOWXJ(o~AOS>sY^pWansvb^)(t5PmV&M2 z0lNacd-lmBlLGP-7jHAsm}GAb_O@uEh5clbWV4ZaDQ5n_Fn|C91P}lRqA|3Pr!=)j zs){XAMvy|Wp;S>p6V()gks{}|n^^xk?nIuD9qsmIb7C^q@=-_V<=>NZ83m=W$?|u^ z&ra&c-?Ij~?5Lr*FHbaFri6i|Qx2|A4! z8)*=Ayt%2Je{8Ws3AqL`PSb+vlgoFC#O1$v>)3MD#|nCsen-Gr*AR0$N%T>?B{krj z{0YAwf${k_=MTsOFRa-h(XQ@$+UBhu4)A_8gL~w9J#L&}i~VNwX8*~$jUu_5LUS^Y zHKuw<_N40F0S6u(-iJQsC`E;$LtMg}t1a10>~EUVfkfP#%c?IhK7W?M1h1)YL*D0bsf3vi!B2} z+FYQwLLk7YT>G&aSlV*CW>xJu&$(Kk@8p>iBub9Gb+nWfB_%yfB`Tg0MJlHQZ50N0zJmS*BIo1D1wo~I+vvj z{)RD1nOc8X7(I32aw$lwU7LidEgd-$c>&T6)wrdjg{6E3-SS3yxsF-} zbGF}z;A*Fwk6G^GEkJ$cUVB>RRV-qa&TVWWD0^da&Irvl5-u^-l%~P5X_}gtY#`iJ zfeK2{fJ*pd2nh&HX)b0GjNL$;f!rA4O!kl)L{SNK zw#BZhMeTenWFsq;*-*uBH1%6_?8i5QHHvWA6xHYw7lR(yKp-C&KokrR0i+&Ksid)u zQybXMK!z+iB?!S|!!uR)J!^M7?BNks7~HdZ%P7!;Q}{5sL5p^aOCMpK7qwWcTQ-L} zX^9UF zEv8_KiWT3m`L~@m7GyR^0u_3o0@F5tfJn^^O#G<11`5)1D#BP*En+0m7}0mK8>K_l zbyn-}>stR~;TKz<%MrhV#BF=cQ}w1JCg>8@TOtK%T_jqkxzxa1V5v)Bu=1VnU<9`p zX)k*P!rt<}7qH>AV0yLx#?ch`sU;&Mo(VFe&`V-x`fkT6YiSDKIe&4lQ;ykd2C+ z0_;|hQ=as;O-hN+o0hArjg(2H6!+ zoTV*w;DRm8;mknk5ALHl7e5(SHo;KbIfAqyj+203vu!ed4g4$iKPFw zSl#6+Q_Dhct*VBSOwpsnSE}-rmhXkq4J+iOjP1^lt#m=}u#H}s#<5)_dIBY8ja=g@ z&|i4U&WF#P?Fms1J-g$gi#)dOiA#9J)qe33$k|slITn{k9=MM)1t_#K`QIZ|l%-Y( z&n-tV%vrVf17f(!xju+hYcCjsEhT+vTeJojqPChD`v*1iKLEOfNARC9LEEHgnRwj> zmSosKpo{}ziAxdL1HRXHSVf=!NHBpEeK^tfgx$DoU)s4}d{Nw;wMyJ6(`3O`@wLW$ zO`k+WnF|yU8A-}%xWM=Ampz49$nDMiJVwy`O}3GYwW$F5FUH5wOodgrUhBQx*+m8Hy%hWvoWdR41NI2|9Ee24 zM_Fhf2ln0vLfUWGO%tx2tx23kSkScy+`=&*AM)X;P#I+H-4}M5#|2IgW)=^Qgd~1h zQq-LAshkTe-p$S2r?>=b4AT2y-BPs26$lsA+(|z%$)Y8bF>#?53ZV;76)eW$@sZ(6 z#olb-*4s5;$?T$%MV2o9;z3-=)1l(m$p;Oj%bK`_&fp=DbWFmX3Jm_C=S7Of%tkLt zMcozR3eH^yQ43YvAmPoA-%KLvkQw3Cl$hyH%^i-)sTukWpFntuIkErWsKl0$j3N?b z0T_UR7i2*dtdO>p6u#I?dH4z!#v)ZMWGvzrLpEeXQWj4wAR78&1X@)YA{=}@TtSFO zccBGx%~iU*1)lT>J>6qFZ6hN3;cS5+|J_7mNnZ)HAUEP2R4Jmy1X~RK7s&-1;4sC= z6~+%n3jEZfL>$=5p@wQqRT5&@v>^^)$v|*Dfj{;mK+efnj1QnV9n4$>LN+8rhGika zB1~1q7{VS@IG`=62u2Q^Wa(le(h~{{78NcY+7zS^i%DRjj12#T%Qai6AmIU(CN{>F zJY@u0T)`7Offx8=79asd(TkoGiyaC9M9LgdcPNRhzga+u{!h~WaFB`*S^1JzeY z0v6L5h!VlaTeyW^z8hdx$^#kSV{QeMSw#uP7q+S3+7TRS>R}1EPWH8x$qi0)=ub(o zC!`EzV35pd9#}kXPKY=Le%9bqhQ`7*hg`5GKZ+ezqT*gX9eNCj^*!W_2q%LAr-Q=e zgMJ~AR7G1JT*WaTucS@;xQQ?A1)yvRc#t7BDyP~>9AQqJ#VH^Jrl2fF-)v=&NdO1& zbz|LWM352LWHusUkYi?@=X)9+;q=T9_}^)~5kVATrRe`zVeru(@ej2nUs+7$6I6lN zs~ zMpbN#ZPA)Q^jAi}Alnh*1s!0rl>lY>j!Y#g8lld4nvsCz4iMTz`LP@oh^DiZCJh>? zVD6#?E`{%bpf<%!JpU5(Hdc=XvF!E&0=Z zjMaFUs&J5{%0b*07KHO%=-a7kG@|H=#Nto%$YbJJ+9e-VSkP?ZO8tJ5feVq9H zz>u1mZ#Eo#HXAgOW@&ERo>p0LswTOJOI}8&sBZsIsTv)uIw22j#p{6Lsu_VMUV0}s7aw>-0dEa!L|lb^l;RYX5O$eGovaR` zp$B>(M+G)qm}Z;4!oXAbtJDq&top0GrANG61(e-d145my*3(5*iArGYje6!}Eaki@ zW_*d~Fhbvy-JL@8k*Zu=N_e55CK5uNYzLK5mrRp~`5N{G&CAM$x)KBtR;m$-PTM`3 zY0{ZY3}4$~6_X4B6Pe3=%*QBANlBnb-30&1kque0s)Ua??bFuQ)E-B0r0$j=Ey0G4 zATeLaE?q}71*+moWRgxTMqcU2gvdfw^GVh=8sG0~*@Ot{RnZ6-#b-CR)5>CBX8z={ zdEa~LsDOd1{(wq}oGr`M=V->_lA?r(Oc)aaVj-1(Mri;ei zk5p_T>K4RWTt}jvp^oGC<>25^Q#w-SdnS+moY@y%2$Di>oPBR_1f~O@>E!y-xNu-H zI!j(eQs?fIeuuhcrMmZEOMLGCafCbl(1YgQpK57 zgbIXa;(81j;b$M)T+e!NPDH8pOp)$@6w++&MmR*1DlLCvRqVQM-(Mo~opx**s2$<=JVJ9cvkLqY4;YYNRC_Z)9643>YgMBbJ#6 zHwYw#uxC}TnHdhMo-I<^>^e;Z=CK5*oNWVHGLy&!_e>EI@a0?RP9#lQ?TT(I&r`_S z#C8^$Qv~auwvDU)73#X1|FZw?dB99gKv_20YQCZ}s&d4=N^%Wa$Qw=22D9C<=2`>~ zrrI5>-APsjwW~QpmK-~(PnM&{d0z;pXUno9r68uXQL3dn)w2~~%;KDxX34%}voGn6 z?qpI)!T|po>105lN|C89lP^>#?fkM0a+vB5e<$lA?L$a0+^KDD{U%MIDpI?2OlJ;- z(1xT6w1~>&0FUM~zu#2DG5NNdk+5EJ25v=sNFWN_kdN}mov<6x z<4#Q(AlsQv@EoNGVJ`2sDlsyF%ny)I_p6O4g|(UnTd}-U;Cb=%GOez2ulIx+nT5XS zNm`zEdv}p`cX!toCI>lO+EN;89=wt~BVDp-EO}^ztjM~zo!741@oNJJ-IYT%R54KfU z_;W+|mNYSj*Yw)nqJ)CCOOHmvxZO?HgaqLt0+LbCP}NnH_dTDti+?tYSOmhbH{w|+ zYX@a#nPZZaW3l!4kCNa1`0<)K^SMH%PSoIt>Nb`%2MuVA_Bhd8ypL{7c?@URusYj= zp5-Zn>6QermW-{K$GGg_GJTo(nSc0j-!z42&Lyhoi$DdW*v=1(ktv%R*QWUR?m6QM zb+)xzvi)N7w$=0mWf~y{qU+SF_|dAg*+(+&HnVb3bOwY$9V?+^RyyN6*1n{rlxZe`7QsJQv7vBt~_hGQ7ZOC(PfRKXE6 zl~iNr%DL0D+1(5GG`59`@;+7wPc)+jp0;+MvE@(yEfr~y-{Dw$`kgCNW$(z(sZ|-u zw=Z(I*9yxcu#dRqJtB^SqmC5H|zY$yE)(4M1%FV;naY&whtED+1sOmA+8EYB#Xyc`?0y? zBP=$fboz=rge?_y`(_&>Cri1Cse_TFvAW+UKd^pzPs%w##5%oJ zB0WApxNuRz#fpIiSFDgh0|}6ZK!D6RLW71387OdIl!0Rf#}zO>3LI$AMF@c_N|Z22 zu!Kp1CQT;%C^Ljin;I?txQHSlPn$n`&J_BSCk&n_Zu~5$a_K>q15<`bi4tVNgH{c~ zKxno9Ajqm*Np}441ILdeOMak0F$BqxA1j?kC1@}qQ_b&;S z8*$)3oLF&V$BZFIX1rK3VzP;EM7~kAgbEu}k|en?HseRVf|K45X;SOelPVL+kmwL1 z5E?Tm?!<`khR@9*ONyLpl_|=>bWh6kO0;KV8aj0zU7>dR^rP2-W*=&^sms23Q-*(u zoFv$;V7+2(jXd#Kvu!!znD{pMUY00<7IY~-JXHR1{p0U6!u*5EF!37ONWsStqfD{~ zA&f9H#(*Oz2`WIlVTd8fLZP*%tV)e5tv);sHj8F>2qKAc%g9BfIO+`~l1k9ZIOJ6S zy6+{dl$*{g?9@U*K^lgrNHObzyy&8gcB)RhCymRoq#X$qa4zJo3JXiRmJ2B?fhPQc zh9ltns4N*i>@UY2$+I%RfmHZUKsdqs&Oj>>q)g8U`Rwz-%qGNarOs@)P&D^4va=x7 z7;UXJ;pkFHgd1ji=!h9`qamZ+5=+RVnS>Kg$C0dxsUxH~O_fQFa1v9e($qR~$t8F5 z3Ce^Jl2R%=r2@~&8wCqau&Tm>!K7dK zzw8r_&T%J$%t1fVl@N!`7~5r26F0$Pg+(<~pguFq)CV5IhA%%qO$YU99O=#hf^ukZdsdRMHrSgV5 zkGx}vRaV)u#5}?+4@3AWFO;TA$SCdxozu;uyZsig1C!pC&vVfQblrAkkb1(*4u(wM zgFefUv_$O%5#Jr~byTprRuBQj6p@f9QzK&yayy@GH1;D@5nc|4%qn(Fqr_N+RXU(- zt&U`y9L^7D<95|`$^yd`FiIK;5?1K)x&jr;z|2f+Oif`}*-e}B#ueOdPscOzZc(@L zz|K-?Jwl8mvwCXVr^bx3K-XP?Y^@(Hp-@9d8_QsDufjJmviXhJUm1S?pbdyiTa4AH zPk&l(Ul;&~th%DAb8%CSQr+$Qpt2J|CXchL$^D;ddMRZp;{qD0D7VehDI53Q_sg01 z7pb#>L@^zTlD5PJ8==hTTfTCjnp!8G15HqZsw)A6XxFaXZSZQC@gVMU2PhcqKx?={ zi3)lE8hLd{E66Ls4M->i%mfB2l2V9lXv3n4SjuFD6SxJ-f`S%@WQff*=q$VFwSbs^C25;iH4Qy%BC6?Iq`IhMr?9Q1Q95p z2&(RNuPcEa5hOnG84PPv>qrMf7@s~4Qg@p%q}Egem(X}^EEj42p`Gyc0}ic=bBTPO z3@+6W>Q$sxMqwgQvDc*K&y&tz1*)%zT_CQ~}DS3rZ^7Ar@P8+g$L z*#gmBxWcfnaOEKQD+o_|Ri^~@=ZnM)m$t?QJOAL(g4N{D9x1f0-4GH+yupk&|G2lk z;q7)VP#z9sh^)b&P=&?Q6i=pAxy(qCEUHnKY7{6W*|cqwJj}>ApC}W~`L8@*I!;q| z^rSAi5|*u8CE{G!u!Fozephi}8=rgh$D$PN&_Ak-=sJKAw(~W(O1J zvi61q+E-Y zRZNHoOt6S}VB;;-x~vp~L(z$%3_xLHX=e(eS94PCaW$$OFq1{bQaME`q5F3BqIB#A+pKt&E)xbvUs-#{tav!6ZFnyPE>k#D(71k%)kQ1NMRcIs0WXJ;@9N*(zCMl`Amq;=GO|Mzv09Scl_B z#Sn;~2BjzmJyMLXDUXtIbBRfGr+Zjs+6k{+%-5IgWKgTtS<@1Q2}^cmC!-ZvrSauU zy=+Qq0a>=EcT?z3kTnx41Mnp5l3Kf%XUQA?q!2Vz_yH0)1Xcc_I)OGDmc%+mz999+ zt42InDZkX${d#3N@yQ63DFn0^|2Zb^J3$=E5$<@TSO|J{@rdgKXzCVwPQHAXrFR+c zs+#u2!eSVwgLUq~z~Ix=k`l3Fmufl1#h2c4q0-VzuGHYI#Vpoah>T^(y`Dsz655*~ z@g?RY{2&TfK*FnfI?PF<#>ecsu)%bymZ1qHgOD*+ez|Aab#ae($#xP;sTkWq&j1_Y zE=M7#k!X$rQKHX;iOO=`5~X7lFxaJ`yz!+`b)q>i_cqVwV79XTl(cenS*F3yYj7Ix zg@*C`x@UhgqpxCpQJ^L2x|sLcz5VY0K{Uq<*>=F|Bt!uTSGa;(5R;ky4aAD~$`}8b zh`pUsriaRzCq2IwwG3)(xHT-cDPD9j_YI zYY(J;0~^ennBJiwc{UxY1gA`YhCN#TCT*Xl#q*=Y1USIcPM)M3)gXxs(Y{0!sLN=L zPejd3u6v;Oj{Ds$ml5IM^_Q=)D4MtYl?g{S@+ve&Y^u`-?XrA=?+}M4sylkL>eJQ% zAPm6n6^gRAcsBFJHDx_gtgSj7`&m>1Pn4 z@<{5W$c`(<&VeAuw9E+iD2K@Z5X6v>BQ=i7`d~0JBxyrva6=HK?(}7}45Flq&kfYlkW(7Ln|AY>r zBFbS1kgXo7lwRnyCUEx5NuzoI>M+M%Y|lLCkV|C7W;7<-5TaWBu5P*DDiuMlx+YCPT;$*F{Z2Gel{ zFEJIbfD-?X5-AZAU2bd42ux6m!2IqCI7BDRu`F-~5|5AyBJl|J5ecj2*`6eE_RzyN zrxrm3TtI@BF%mD429Yoou^<(ZFbSieDYYO9 zw91w}Ac z#UVu?DEIkJ8oSoD?nJSW
        @@ -160,7 +169,7 @@

      - +
      @@ -188,6 +197,12 @@ +
      + +
      @@ -216,10 +231,10 @@ *[type="polygon"] - only polygon objects
      car[occluded="true"] - only occluded cars
      *[lock!="true"] - only unlocked tracks
      - person[attr/age>="25" and attr/age<="35"] - persons with age (number) beetween [25,40] years
      + person[attr/age>="25" and attr/age<="35"] - persons with age (number) between [25,40] years
      car[attr/parked="true"] - only parked cars
      person[attr/race="asian"] | car[attr/model="bmw"] - asians and BMW cars
      - face[attr/glass="sunglass" or attr/glass="no"] - faces with sunglass or without glass
      + face[attr/glass="sunglasses" or attr/glass="no"] - faces with sunglasses or without glass
      *[attr/*="__undefined__"] - any tracks with any unlabeled attributes
      *[width<300 or height<300] - shapes with height or width less than 300px
      person[width>300 and height<200] - person shapes with width > 300px and height < 200px
      @@ -319,6 +334,10 @@ + + + +
      @@ -338,6 +357,7 @@ + @@ -377,13 +397,14 @@
      - +
      + @@ -398,6 +419,8 @@ + + @@ -425,10 +448,12 @@
      diff --git a/cvat/apps/engine/tests/test_model.py b/cvat/apps/engine/tests/test_model.py new file mode 100644 index 00000000000..34454c0b1f5 --- /dev/null +++ b/cvat/apps/engine/tests/test_model.py @@ -0,0 +1,25 @@ +# Copyright (C) 2018 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import os.path as osp + +from django.test import TestCase +from cvat.apps.engine.models import Task + + +class TaskModelTest(TestCase): + def test_frame_id_path_conversions(self): + task_id = 1 + task = Task(task_id) + + for i in [10 ** p for p in range(6)]: + src_path_expected = osp.join( + str(i // 10000), str(i // 100), '%s.jpg' % i) + src_path = task.get_frame_path(i) + + dst_frame = task.get_image_frame(src_path) + + self.assertTrue(src_path.endswith(src_path_expected), + '%s vs. %s' % (src_path, src_path_expected)) + self.assertEqual(i, dst_frame) diff --git a/cvat/apps/engine/tests/test_rest_api.py b/cvat/apps/engine/tests/test_rest_api.py index d352a4f7b4a..d959f71c5b5 100644 --- a/cvat/apps/engine/tests/test_rest_api.py +++ b/cvat/apps/engine/tests/test_rest_api.py @@ -12,12 +12,15 @@ from django.conf import settings from django.contrib.auth.models import User, Group from cvat.apps.engine.models import (Task, Segment, Job, StatusChoice, - AttributeType) + AttributeType, Project) from cvat.apps.annotation.models import AnnotationFormat from unittest import mock import io import xml.etree.ElementTree as ET from collections import defaultdict +import zipfile +from pycocotools import coco as coco_loader +import tempfile def create_db_users(cls): (group_admin, _) = Group.objects.get_or_create(name="admin") @@ -40,11 +43,11 @@ def create_db_users(cls): user_dummy.groups.add(group_user) cls.admin = user_admin - cls.owner = user_owner - cls.assignee = user_assignee - cls.annotator = user_annotator - cls.observer = user_observer - cls.user = user_dummy + cls.owner = cls.user1 = user_owner + cls.assignee = cls.user2 = user_assignee + cls.annotator = cls.user3 = user_annotator + cls.observer = cls.user4 = user_observer + cls.user = cls.user5 = user_dummy def create_db_task(data): db_task = Task.objects.create(**data) @@ -68,7 +71,7 @@ def create_db_task(data): return db_task -def create_dummy_db_tasks(obj): +def create_dummy_db_tasks(obj, project=None): tasks = [] data = { @@ -79,7 +82,8 @@ def create_dummy_db_tasks(obj): "segment_size": 100, "z_order": False, "image_quality": 75, - "size": 100 + "size": 100, + "project": project } db_task = create_db_task(data) tasks.append(db_task) @@ -91,7 +95,8 @@ def create_dummy_db_tasks(obj): "segment_size": 100, "z_order": True, "image_quality": 50, - "size": 200 + "size": 200, + "project": project } db_task = create_db_task(data) tasks.append(db_task) @@ -104,7 +109,8 @@ def create_dummy_db_tasks(obj): "segment_size": 100, "z_order": False, "image_quality": 75, - "size": 100 + "size": 100, + "project": project } db_task = create_db_task(data) tasks.append(db_task) @@ -116,13 +122,61 @@ def create_dummy_db_tasks(obj): "segment_size": 50, "z_order": False, "image_quality": 95, - "size": 50 + "size": 50, + "project": project } db_task = create_db_task(data) tasks.append(db_task) return tasks +def create_dummy_db_projects(obj): + projects = [] + + data = { + "name": "my empty project", + "owner": obj.owner, + "assignee": obj.assignee, + } + db_project = Project.objects.create(**data) + projects.append(db_project) + + data = { + "name": "my project without assignee", + "owner": obj.user, + } + db_project = Project.objects.create(**data) + create_dummy_db_tasks(obj, db_project) + projects.append(db_project) + + data = { + "name": "my big project", + "owner": obj.owner, + "assignee": obj.assignee, + } + db_project = Project.objects.create(**data) + create_dummy_db_tasks(obj, db_project) + projects.append(db_project) + + data = { + "name": "public project", + } + db_project = Project.objects.create(**data) + create_dummy_db_tasks(obj, db_project) + projects.append(db_project) + + data = { + "name": "super project", + "owner": obj.admin, + "assignee": obj.assignee, + } + db_project = Project.objects.create(**data) + create_dummy_db_tasks(obj, db_project) + projects.append(db_project) + + return projects + + class ForceLogin: def __init__(self, user, client): self.user = user @@ -411,53 +465,69 @@ def test_api_v1_server_logs_no_auth(self): self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) -class UserListAPITestCase(APITestCase): +class UserAPITestCase(APITestCase): def setUp(self): self.client = APIClient() + create_db_users(self) - @classmethod - def setUpTestData(cls): - create_db_users(cls) - + def _check_response(self, user, response, is_full=True): + self.assertEqual(response.status_code, status.HTTP_200_OK) + self._check_data(user, response.data, is_full) + + def _check_data(self, user, data, is_full): + self.assertEqual(data["id"], user.id) + self.assertEqual(data["username"], user.username) + self.assertEqual(data["first_name"], user.first_name) + self.assertEqual(data["last_name"], user.last_name) + self.assertEqual(data["email"], user.email) + extra_check = self.assertIn if is_full else self.assertNotIn + extra_check("groups", data) + extra_check("is_staff", data) + extra_check("is_superuser", data) + extra_check("is_active", data) + extra_check("last_login", data) + extra_check("date_joined", data) + +class UserListAPITestCase(UserAPITestCase): def _run_api_v1_users(self, user): with ForceLogin(user, self.client): response = self.client.get('/api/v1/users') return response + def _check_response(self, user, response, is_full): + self.assertEqual(response.status_code, status.HTTP_200_OK) + for user_info in response.data['results']: + db_user = getattr(self, user_info['username']) + self._check_data(db_user, user_info, is_full) + def test_api_v1_users_admin(self): response = self._run_api_v1_users(self.admin) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertListEqual( - ["admin", "user1", "user2", "user3", "user4", "user5"], - [res["username"] for res in response.data["results"]]) + self._check_response(self.admin, response, True) def test_api_v1_users_user(self): response = self._run_api_v1_users(self.user) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self._check_response(self.user, response, False) + + def test_api_v1_users_annotator(self): + response = self._run_api_v1_users(self.annotator) + self._check_response(self.annotator, response, False) + + def test_api_v1_users_observer(self): + response = self._run_api_v1_users(self.observer) + self._check_response(self.observer, response, False) def test_api_v1_users_no_auth(self): response = self._run_api_v1_users(None) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) -class UserSelfAPITestCase(APITestCase): - def setUp(self): - self.client = APIClient() - - @classmethod - def setUpTestData(cls): - create_db_users(cls) - +class UserSelfAPITestCase(UserAPITestCase): def _run_api_v1_users_self(self, user): with ForceLogin(user, self.client): response = self.client.get('/api/v1/users/self') return response - def _check_response(self, user, response): - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.data["username"], user.username) - def test_api_v1_users_self_admin(self): response = self._run_api_v1_users_self(self.admin) self._check_response(self.admin, response) @@ -470,123 +540,458 @@ def test_api_v1_users_self_annotator(self): response = self._run_api_v1_users_self(self.annotator) self._check_response(self.annotator, response) + def test_api_v1_users_self_observer(self): + response = self._run_api_v1_users_self(self.observer) + self._check_response(self.observer, response) def test_api_v1_users_self_no_auth(self): response = self._run_api_v1_users_self(None) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) -class UserGetAPITestCase(APITestCase): - def setUp(self): - self.client = APIClient() - - @classmethod - def setUpTestData(cls): - create_db_users(cls) - +class UserGetAPITestCase(UserAPITestCase): def _run_api_v1_users_id(self, user, user_id): with ForceLogin(user, self.client): response = self.client.get('/api/v1/users/{}'.format(user_id)) return response - def _check_response(self, user, response): - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.data["id"], user.id) - self.assertEqual(response.data["username"], user.username) - def test_api_v1_users_id_admin(self): response = self._run_api_v1_users_id(self.admin, self.user.id) - self._check_response(self.user, response) + self._check_response(self.user, response, True) response = self._run_api_v1_users_id(self.admin, self.admin.id) - self._check_response(self.admin, response) + self._check_response(self.admin, response, True) response = self._run_api_v1_users_id(self.admin, self.owner.id) - self._check_response(self.owner, response) + self._check_response(self.owner, response, True) def test_api_v1_users_id_user(self): response = self._run_api_v1_users_id(self.user, self.user.id) - self._check_response(self.user, response) + self._check_response(self.user, response, True) response = self._run_api_v1_users_id(self.user, self.owner.id) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self._check_response(self.owner, response, False) def test_api_v1_users_id_annotator(self): response = self._run_api_v1_users_id(self.annotator, self.annotator.id) - self._check_response(self.annotator, response) + self._check_response(self.annotator, response, True) response = self._run_api_v1_users_id(self.annotator, self.user.id) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self._check_response(self.user, response, False) + + def test_api_v1_users_id_observer(self): + response = self._run_api_v1_users_id(self.observer, self.observer.id) + self._check_response(self.observer, response, True) + + response = self._run_api_v1_users_id(self.observer, self.user.id) + self._check_response(self.user, response, False) def test_api_v1_users_id_no_auth(self): response = self._run_api_v1_users_id(None, self.user.id) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) -class UserUpdateAPITestCase(APITestCase): - def setUp(self): - self.client = APIClient() - create_db_users(self) - +class UserPartialUpdateAPITestCase(UserAPITestCase): def _run_api_v1_users_id(self, user, user_id, data): with ForceLogin(user, self.client): - response = self.client.put('/api/v1/users/{}'.format(user_id), data=data) + response = self.client.patch('/api/v1/users/{}'.format(user_id), data=data) return response - def test_api_v1_users_id_admin(self): - data = {"username": "user09", "groups": ["user", "admin"], - "first_name": "my name"} + def _check_response_with_data(self, user, response, data, is_full): + # refresh information about the user from DB + user = User.objects.get(id=user.id) + for k,v in data.items(): + self.assertEqual(response.data[k], v) + self._check_response(user, response, is_full) + + def test_api_v1_users_id_admin_partial(self): + data = {"username": "user09", "last_name": "my last name"} response = self._run_api_v1_users_id(self.admin, self.user.id, data) - self.assertEqual(response.status_code, status.HTTP_200_OK) - user09 = User.objects.get(id=self.user.id) - self.assertEqual(user09.username, data["username"]) - self.assertEqual(user09.first_name, data["first_name"]) + self._check_response_with_data(self.user, response, data, True) - def test_api_v1_users_id_user(self): - data = {"username": "user10", "groups": ["user", "annotator"], - "first_name": "my name"} + def test_api_v1_users_id_user_partial(self): + data = {"username": "user10", "first_name": "my name"} response = self._run_api_v1_users_id(self.user, self.user.id, data) + self._check_response_with_data(self.user, response, data, False) + + data = {"is_staff": True} + response = self._run_api_v1_users_id(self.user, self.user.id, data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + data = {"username": "admin", "is_superuser": True} + response = self._run_api_v1_users_id(self.user, self.user.id, data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + data = {"username": "non_active", "is_active": False} + response = self._run_api_v1_users_id(self.user, self.user.id, data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + data = {"username": "annotator01", "first_name": "slave"} + response = self._run_api_v1_users_id(self.user, self.annotator.id, data) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + def test_api_v1_users_id_no_auth_partial(self): + data = {"username": "user12"} + response = self._run_api_v1_users_id(None, self.user.id, data) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + +class UserDeleteAPITestCase(UserAPITestCase): + def _run_api_v1_users_id(self, user, user_id): + with ForceLogin(user, self.client): + response = self.client.delete('/api/v1/users/{}'.format(user_id)) + + return response + + def test_api_v1_users_id_admin(self): + response = self._run_api_v1_users_id(self.admin, self.user.id) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + + response = self._run_api_v1_users_id(self.admin, self.admin.id) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + + def test_api_v1_users_id_user(self): + response = self._run_api_v1_users_id(self.user, self.owner.id) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + response = self._run_api_v1_users_id(self.user, self.user.id) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + def test_api_v1_users_id_annotator(self): - data = {"username": "user11", "groups": ["annotator"], - "first_name": "my name"} - response = self._run_api_v1_users_id(self.annotator, self.user.id, data) + response = self._run_api_v1_users_id(self.annotator, self.user.id) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + response = self._run_api_v1_users_id(self.annotator, self.annotator.id) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + + def test_api_v1_users_id_observer(self): + response = self._run_api_v1_users_id(self.observer, self.user.id) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + response = self._run_api_v1_users_id(self.observer, self.observer.id) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + def test_api_v1_users_id_no_auth(self): - data = {"username": "user12", "groups": ["user", "observer"], - "first_name": "my name"} - response = self._run_api_v1_users_id(None, self.user.id, data) + response = self._run_api_v1_users_id(None, self.user.id) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) -class UserPartialUpdateAPITestCase(UserUpdateAPITestCase): - def _run_api_v1_users_id(self, user, user_id, data): +class ProjectListAPITestCase(APITestCase): + def setUp(self): + self.client = APIClient() + + @classmethod + def setUpTestData(cls): + create_db_users(cls) + cls.projects = create_dummy_db_projects(cls) + + def _run_api_v1_projects(self, user, params=""): with ForceLogin(user, self.client): - response = self.client.patch('/api/v1/users/{}'.format(user_id), data=data) + response = self.client.get('/api/v1/projects{}'.format(params)) return response - def test_api_v1_users_id_admin_partial(self): - data = {"username": "user09", "last_name": "my last name"} - response = self._run_api_v1_users_id(self.admin, self.user.id, data) + def test_api_v1_projects_admin(self): + response = self._run_api_v1_projects(self.admin) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertListEqual( + sorted([project.name for project in self.projects]), + sorted([res["name"] for res in response.data["results"]])) + def test_api_v1_projects_user(self): + response = self._run_api_v1_projects(self.user) self.assertEqual(response.status_code, status.HTTP_200_OK) - user09 = User.objects.get(id=self.user.id) - self.assertEqual(user09.username, data["username"]) - self.assertEqual(user09.last_name, data["last_name"]) + self.assertListEqual( + sorted([project.name for project in self.projects + if 'my empty project' != project.name]), + sorted([res["name"] for res in response.data["results"]])) - def test_api_v1_users_id_user_partial(self): - data = {"username": "user10", "first_name": "my name"} - response = self._run_api_v1_users_id(self.user, self.user.id, data) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + def test_api_v1_projects_observer(self): + response = self._run_api_v1_projects(self.observer) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertListEqual( + sorted([project.name for project in self.projects]), + sorted([res["name"] for res in response.data["results"]])) - def test_api_v1_users_id_no_auth_partial(self): - data = {"username": "user12"} - response = self._run_api_v1_users_id(None, self.user.id, data) + def test_api_v1_projects_no_auth(self): + response = self._run_api_v1_projects(None) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + +class ProjectGetAPITestCase(APITestCase): + def setUp(self): + self.client = APIClient() + + @classmethod + def setUpTestData(cls): + create_db_users(cls) + cls.projects = create_dummy_db_projects(cls) + + def _run_api_v1_projects_id(self, pid, user): + with ForceLogin(user, self.client): + response = self.client.get('/api/v1/projects/{}'.format(pid)) + + return response + + def _check_response(self, response, db_project): + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data["name"], db_project.name) + owner = db_project.owner.id if db_project.owner else None + self.assertEqual(response.data["owner"], owner) + assignee = db_project.assignee.id if db_project.assignee else None + self.assertEqual(response.data["assignee"], assignee) + self.assertEqual(response.data["status"], db_project.status) + + def _check_api_v1_projects_id(self, user): + for db_project in self.projects: + response = self._run_api_v1_projects_id(db_project.id, user) + if user and user.has_perm("engine.project.access", db_project): + self._check_response(response, db_project) + elif user: + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + else: + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + def test_api_v1_projects_id_admin(self): + self._check_api_v1_projects_id(self.admin) + + def test_api_v1_projects_id_user(self): + self._check_api_v1_projects_id(self.user) + + def test_api_v1_projects_id_observer(self): + self._check_api_v1_projects_id(self.observer) + + def test_api_v1_projects_id_no_auth(self): + self._check_api_v1_projects_id(None) + +class ProjectDeleteAPITestCase(APITestCase): + def setUp(self): + self.client = APIClient() + + @classmethod + def setUpTestData(cls): + create_db_users(cls) + cls.projects = create_dummy_db_projects(cls) + + def _run_api_v1_projects_id(self, pid, user): + with ForceLogin(user, self.client): + response = self.client.delete('/api/v1/projects/{}'.format(pid), format="json") + + return response + + def _check_api_v1_projects_id(self, user): + for db_project in self.projects: + response = self._run_api_v1_projects_id(db_project.id, user) + if user and user.has_perm("engine.project.delete", db_project): + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + elif user: + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + else: + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + def test_api_v1_projects_id_admin(self): + self._check_api_v1_projects_id(self.admin) + + def test_api_v1_projects_id_user(self): + self._check_api_v1_projects_id(self.user) + + def test_api_v1_projects_id_observer(self): + self._check_api_v1_projects_id(self.observer) + + def test_api_v1_projects_id_no_auth(self): + self._check_api_v1_projects_id(None) + +class ProjectCreateAPITestCase(APITestCase): + def setUp(self): + self.client = APIClient() + + @classmethod + def setUpTestData(cls): + create_db_users(cls) + + def _run_api_v1_projects(self, user, data): + with ForceLogin(user, self.client): + response = self.client.post('/api/v1/projects', data=data, format="json") + + return response + + def _check_response(self, response, user, data): + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(response.data["name"], data["name"]) + self.assertEqual(response.data["owner"], data.get("owner", user.id)) + self.assertEqual(response.data["assignee"], data.get("assignee")) + self.assertEqual(response.data["bug_tracker"], data.get("bug_tracker", "")) + self.assertEqual(response.data["status"], StatusChoice.ANNOTATION) + + def _check_api_v1_projects(self, user, data): + response = self._run_api_v1_projects(user, data) + if user and user.has_perm("engine.project.create"): + self._check_response(response, user, data) + elif user: + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + else: + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + def test_api_v1_projects_admin(self): + data = { + "name": "new name for the project", + "bug_tracker": "http://example.com" + } + self._check_api_v1_projects(self.admin, data) + + data = { + "owner": self.owner.id, + "assignee": self.assignee.id, + "name": "new name for the project" + } + self._check_api_v1_projects(self.admin, data) + + data = { + "owner": self.admin.id, + "name": "2" + } + self._check_api_v1_projects(self.admin, data) + + + def test_api_v1_projects_user(self): + data = { + "name": "Dummy name", + "bug_tracker": "it is just text" + } + self._check_api_v1_projects(self.user, data) + + data = { + "owner": self.owner.id, + "assignee": self.assignee.id, + "name": "My import project with data" + } + self._check_api_v1_projects(self.user, data) + + + def test_api_v1_projects_observer(self): + data = { + "name": "My Project #1", + "owner": self.owner.id, + "assignee": self.assignee.id + } + self._check_api_v1_projects(self.observer, data) + + def test_api_v1_projects_no_auth(self): + data = { + "name": "My Project #2", + "owner": self.admin.id, + } + self._check_api_v1_projects(None, data) + +class ProjectPartialUpdateAPITestCase(APITestCase): + def setUp(self): + self.client = APIClient() + + @classmethod + def setUpTestData(cls): + create_db_users(cls) + cls.projects = create_dummy_db_projects(cls) + + def _run_api_v1_projects_id(self, pid, user, data): + with ForceLogin(user, self.client): + response = self.client.patch('/api/v1/projects/{}'.format(pid), + data=data, format="json") + + return response + + def _check_response(self, response, db_project, data): + self.assertEqual(response.status_code, status.HTTP_200_OK) + name = data.get("name", db_project.name) + self.assertEqual(response.data["name"], name) + owner = db_project.owner.id if db_project.owner else None + owner = data.get("owner", owner) + self.assertEqual(response.data["owner"], owner) + assignee = db_project.assignee.id if db_project.assignee else None + assignee = data.get("assignee", assignee) + self.assertEqual(response.data["assignee"], assignee) + self.assertEqual(response.data["status"], db_project.status) + + def _check_api_v1_projects_id(self, user, data): + for db_project in self.projects: + response = self._run_api_v1_projects_id(db_project.id, user, data) + if user and user.has_perm("engine.project.change", db_project): + self._check_response(response, db_project, data) + elif user: + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + else: + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + def test_api_v1_projects_id_admin(self): + data = { + "name": "new name for the project", + "owner": self.owner.id, + } + self._check_api_v1_projects_id(self.admin, data) + + def test_api_v1_projects_id_user(self): + data = { + "name": "new name for the project", + "owner": self.assignee.id, + } + self._check_api_v1_projects_id(self.user, data) + + def test_api_v1_projects_id_observer(self): + data = { + "name": "new name for the project", + } + self._check_api_v1_projects_id(self.observer, data) + + def test_api_v1_projects_id_no_auth(self): + data = { + "name": "new name for the project", + } + self._check_api_v1_projects_id(None, data) + +class ProjectListOfTasksAPITestCase(APITestCase): + def setUp(self): + self.client = APIClient() + + @classmethod + def setUpTestData(cls): + create_db_users(cls) + cls.projects = create_dummy_db_projects(cls) + + def _run_api_v1_projects_id_tasks(self, user, pid): + with ForceLogin(user, self.client): + response = self.client.get('/api/v1/projects/{}/tasks'.format(pid)) + + return response + + def test_api_v1_projects_id_tasks_admin(self): + project = self.projects[1] + response = self._run_api_v1_projects_id_tasks(self.admin, project.id) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertListEqual( + sorted([task.name for task in project.tasks.all()]), + sorted([res["name"] for res in response.data["results"]])) + + def test_api_v1_projects_id_tasks_user(self): + project = self.projects[1] + response = self._run_api_v1_projects_id_tasks(self.user, project.id) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertListEqual( + sorted([task.name for task in project.tasks.all() + if task.owner in [None, self.user] or + task.assignee in [None, self.user]]), + sorted([res["name"] for res in response.data["results"]])) + + def test_api_v1_projects_id_tasks_observer(self): + project = self.projects[1] + response = self._run_api_v1_projects_id_tasks(self.observer, project.id) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertListEqual( + sorted([task.name for task in project.tasks.all()]), + sorted([res["name"] for res in response.data["results"]])) + + def test_api_v1_projects_id_tasks_no_auth(self): + project = self.projects[1] + response = self._run_api_v1_projects_id_tasks(None, project.id) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + class TaskListAPITestCase(APITestCase): def setUp(self): self.client = APIClient() @@ -1138,7 +1543,7 @@ def test_api_v1_tasks_id_data_no_auth(self): response = self._create_task(None, data) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) -def compare_objects(self, obj1, obj2, ignore_keys): +def compare_objects(self, obj1, obj2, ignore_keys, fp_tolerance=.001): if isinstance(obj1, dict): self.assertTrue(isinstance(obj2, dict), "{} != {}".format(obj1, obj2)) for k in obj1.keys(): @@ -1151,7 +1556,10 @@ def compare_objects(self, obj1, obj2, ignore_keys): for v1, v2 in zip(obj1, obj2): compare_objects(self, v1, v2, ignore_keys) else: - self.assertEqual(obj1, obj2) + if isinstance(obj1, float) or isinstance(obj2, float): + self.assertAlmostEqual(obj1, obj2, delta=fp_tolerance) + else: + self.assertEqual(obj1, obj2) class JobAnnotationAPITestCase(APITestCase): def setUp(self): @@ -1653,6 +2061,29 @@ def test_api_v1_jobs_id_annotations_user(self): self._run_api_v1_jobs_id_annotations(self.user, self.assignee, self.assignee) + def test_api_v1_jobs_id_annotations_observer(self): + _, jobs = self._create_task(self.user, self.assignee) + job = jobs[0] + data = { + "version": 0, + "tags": [], + "shapes": [], + "tracks": [] + } + + response = self._get_api_v1_jobs_id_data(job["id"], self.observer) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response = self._put_api_v1_jobs_id_data(job["id"], self.observer, data) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + response = self._patch_api_v1_jobs_id_data(job["id"], self.observer, "create", data) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + response = self._delete_api_v1_jobs_id_data(job["id"], self.observer) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_api_v1_jobs_id_annotations_no_auth(self): self._run_api_v1_jobs_id_annotations(self.user, self.assignee, None) @@ -1692,25 +2123,38 @@ def _patch_api_v1_tasks_id_annotations(self, pk, user, action, data): return response + def _upload_api_v1_tasks_id_annotations(self, pk, user, data, query_params=""): + with ForceLogin(user, self.client): + response = self.client.put( + path="/api/v1/tasks/{0}/annotations?{1}".format(pk, query_params), + data=data, + format="multipart", + ) + + return response + + def _get_annotation_formats(self, user): + with ForceLogin(user, self.client): + response = self.client.get( + path="/api/v1/server/annotation/formats" + ) + return response + def _check_response(self, response, data): if not response.status_code in [ status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN]: compare_objects(self, data, response.data, ignore_keys=["id"]) def _run_api_v1_tasks_id_annotations(self, owner, assignee, annotator): - task, jobs = self._create_task(owner, assignee) + task, _ = self._create_task(owner, assignee) if annotator: HTTP_200_OK = status.HTTP_200_OK HTTP_204_NO_CONTENT = status.HTTP_204_NO_CONTENT HTTP_400_BAD_REQUEST = status.HTTP_400_BAD_REQUEST - HTTP_202_ACCEPTED = status.HTTP_202_ACCEPTED - HTTP_201_CREATED = status.HTTP_201_CREATED else: HTTP_200_OK = status.HTTP_401_UNAUTHORIZED HTTP_204_NO_CONTENT = status.HTTP_401_UNAUTHORIZED HTTP_400_BAD_REQUEST = status.HTTP_401_UNAUTHORIZED - HTTP_202_ACCEPTED = status.HTTP_401_UNAUTHORIZED - HTTP_201_CREATED = status.HTTP_401_UNAUTHORIZED data = { "version": 0, @@ -2078,51 +2522,391 @@ def _run_api_v1_tasks_id_annotations(self, owner, assignee, annotator): "create", data) self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) - cvat_format = AnnotationFormat.objects.get(name="CVAT") - for annotation_handler in cvat_format.annotationdumper_set.all(): - response = self._dump_api_v1_tasks_id_annotations(task["id"], annotator, - "format={}".format(annotation_handler.display_name)) - self.assertEqual(response.status_code, HTTP_202_ACCEPTED) - - response = self._dump_api_v1_tasks_id_annotations(task["id"], annotator, - "format={}".format(annotation_handler.display_name)) - self.assertEqual(response.status_code, HTTP_201_CREATED) - - response = self._dump_api_v1_tasks_id_annotations(task["id"], annotator, - "action=download&format={}".format(annotation_handler.display_name)) - self.assertEqual(response.status_code, HTTP_200_OK) - self._check_dump_response(response, task, jobs, data) - - def _check_dump_response(self, response, task, jobs, data): - if response.status_code == status.HTTP_200_OK: - def etree_to_dict(t): - d = {t.tag: {} if t.attrib else None} - children = list(t) - if children: - dd = defaultdict(list) - for dc in map(etree_to_dict, children): - for k, v in dc.items(): - dd[k].append(v) - d = {t.tag: {k: v[0] if len(v) == 1 else v - for k, v in dd.items()}} - if t.attrib: - d[t.tag].update(('@' + k, v) for k, v in t.attrib.items()) - if t.text: - text = t.text.strip() - if not (children or t.attrib): - d[t.tag] = text - return d - - self.assertTrue(response.streaming) - content = io.BytesIO(b''.join(response.streaming_content)) - xmldump = ET.fromstring(content.read()) + def _run_api_v1_tasks_id_annotations_dump_load(self, owner, assignee, annotator): + if annotator: + HTTP_200_OK = status.HTTP_200_OK + HTTP_204_NO_CONTENT = status.HTTP_204_NO_CONTENT + HTTP_202_ACCEPTED = status.HTTP_202_ACCEPTED + HTTP_201_CREATED = status.HTTP_201_CREATED + else: + HTTP_200_OK = status.HTTP_401_UNAUTHORIZED + HTTP_204_NO_CONTENT = status.HTTP_401_UNAUTHORIZED + HTTP_202_ACCEPTED = status.HTTP_401_UNAUTHORIZED + HTTP_201_CREATED = status.HTTP_401_UNAUTHORIZED + + def _get_initial_annotation(annotation_format): + rectangle_tracks_with_attrs = [{ + "frame": 0, + "label_id": task["labels"][0]["id"], + "group": 0, + "attributes": [ + { + "spec_id": task["labels"][0]["attributes"][0]["id"], + "value": task["labels"][0]["attributes"][0]["values"][0] + }, + ], + "shapes": [ + { + "frame": 0, + "points": [1.0, 2.1, 50.1, 30.22], + "type": "rectangle", + "occluded": False, + "outside": False, + "attributes": [ + { + "spec_id": task["labels"][0]["attributes"][1]["id"], + "value": task["labels"][0]["attributes"][1]["default_value"] + } + ] + }, + { + "frame": 1, + "points": [2.0, 2.1, 77.2, 36.22], + "type": "rectangle", + "occluded": True, + "outside": True, + "attributes": [ + { + "spec_id": task["labels"][0]["attributes"][1]["id"], + "value": task["labels"][0]["attributes"][1]["default_value"] + } + ] + }, + ] + }] + rectangle_tracks_wo_attrs = [{ + "frame": 1, + "label_id": task["labels"][1]["id"], + "group": 0, + "attributes": [], + "shapes": [ + { + "frame": 1, + "attributes": [], + "points": [1.0, 2.1, 50.2, 36.6], + "type": "rectangle", + "occluded": False, + "outside": False + }, + { + "frame": 2, + "attributes": [], + "points": [1.0, 2.1, 51, 36.6], + "type": "rectangle", + "occluded": False, + "outside": True + } + ] + }] + + rectangle_shapes_with_attrs = [{ + "frame": 0, + "label_id": task["labels"][0]["id"], + "group": 0, + "attributes": [ + { + "spec_id": task["labels"][0]["attributes"][0]["id"], + "value": task["labels"][0]["attributes"][0]["values"][0] + }, + { + "spec_id": task["labels"][0]["attributes"][1]["id"], + "value": task["labels"][0]["attributes"][1]["default_value"] + } + ], + "points": [1.0, 2.1, 10.6, 53.22], + "type": "rectangle", + "occluded": False + }] + rectangle_shapes_wo_attrs = [{ + "frame": 1, + "label_id": task["labels"][1]["id"], + "group": 0, + "attributes": [], + "points": [2.0, 2.1, 40, 50.7], + "type": "rectangle", + "occluded": False + }] + + polygon_shapes_wo_attrs = [{ + "frame": 1, + "label_id": task["labels"][1]["id"], + "group": 0, + "attributes": [], + "points": [2.0, 2.1, 100, 30.22, 40, 77, 1, 3], + "type": "polygon", + "occluded": False + }] + + polygon_shapes_with_attrs = [{ + "frame": 2, + "label_id": task["labels"][0]["id"], + "group": 1, + "attributes": [ + { + "spec_id": task["labels"][0]["attributes"][0]["id"], + "value": task["labels"][0]["attributes"][0]["values"][1] + }, + { + "spec_id": task["labels"][0]["attributes"][1]["id"], + "value": task["labels"][0]["attributes"][1]["default_value"] + } + ], + "points": [20.0, 0.1, 10, 3.22, 4, 7, 10, 30, 1, 2, 4.44, 5.55], + "type": "polygon", + "occluded": True + }] + + tags_wo_attrs = [{ + "frame": 2, + "label_id": task["labels"][1]["id"], + "group": 0, + "attributes": [] + }] + tags_with_attrs = [{ + "frame": 1, + "label_id": task["labels"][0]["id"], + "group": 3, + "attributes": [ + { + "spec_id": task["labels"][0]["attributes"][0]["id"], + "value": task["labels"][0]["attributes"][0]["values"][1] + }, + { + "spec_id": task["labels"][0]["attributes"][1]["id"], + "value": task["labels"][0]["attributes"][1]["default_value"] + } + ], + }] + + annotations = { + "version": 0, + "tags": [], + "shapes": [], + "tracks": [], + } + if annotation_format == "CVAT XML 1.1 for videos": + annotations["tracks"] = rectangle_tracks_with_attrs + rectangle_tracks_wo_attrs + + elif annotation_format == "CVAT XML 1.1 for images": + annotations["shapes"] = rectangle_shapes_with_attrs + rectangle_shapes_wo_attrs \ + + polygon_shapes_wo_attrs + polygon_shapes_with_attrs + annotations["tags"] = tags_with_attrs + tags_wo_attrs + + elif annotation_format == "PASCAL VOC ZIP 1.1": + annotations["shapes"] = rectangle_shapes_wo_attrs + annotations["tags"] = tags_wo_attrs + + elif annotation_format == "YOLO ZIP 1.1" or \ + annotation_format == "TFRecord ZIP 1.0": + annotations["shapes"] = rectangle_shapes_wo_attrs + + elif annotation_format == "COCO JSON 1.0": + annotations["shapes"] = polygon_shapes_wo_attrs + + elif annotation_format == "MASK ZIP 1.1": + annotations["shapes"] = rectangle_shapes_wo_attrs + polygon_shapes_wo_attrs + annotations["tracks"] = rectangle_tracks_wo_attrs + + elif annotation_format == "MOT CSV 1.0": + annotations["tracks"] = rectangle_tracks_wo_attrs + + return annotations + + response = self._get_annotation_formats(annotator) + self.assertEqual(response.status_code, HTTP_200_OK) + + + if annotator is not None: + supported_formats = response.data + else: + supported_formats = [{ + "name": "CVAT", + "dumpers": [{ + "display_name": "CVAT XML 1.1 for images" + }], + "loaders": [{ + "display_name": "CVAT XML 1.1" + }] + }] + + self.assertTrue(isinstance(supported_formats, list) and supported_formats) + + for annotation_format in supported_formats: + for dumper in annotation_format["dumpers"]: + # 1. create task + task, jobs = self._create_task(owner, assignee) + + # 2. add annotation + data = _get_initial_annotation(dumper["display_name"]) + response = self._put_api_v1_tasks_id_annotations(task["id"], annotator, data) + data["version"] += 1 + + self.assertEqual(response.status_code, HTTP_200_OK) + self._check_response(response, data) + + # 3. download annotation + response = self._dump_api_v1_tasks_id_annotations(task["id"], annotator, + "format={}".format(dumper["display_name"])) + self.assertEqual(response.status_code, HTTP_202_ACCEPTED) + + response = self._dump_api_v1_tasks_id_annotations(task["id"], annotator, + "format={}".format(dumper["display_name"])) + self.assertEqual(response.status_code, HTTP_201_CREATED) + + response = self._dump_api_v1_tasks_id_annotations(task["id"], annotator, + "action=download&format={}".format(dumper["display_name"])) + self.assertEqual(response.status_code, HTTP_200_OK) + + # 4. check downloaded data + if response.status_code == status.HTTP_200_OK: + self.assertTrue(response.streaming) + content = io.BytesIO(b"".join(response.streaming_content)) + self._check_dump_content(content, task, jobs, data, annotation_format["name"]) + content.seek(0) + + # 5. remove annotation form the task + response = self._delete_api_v1_tasks_id_annotations(task["id"], annotator) + data["version"] += 1 + self.assertEqual(response.status_code, HTTP_204_NO_CONTENT) + + # 6. upload annotation and check annotation + uploaded_data = { + "annotation_file": content, + } + + for loader in annotation_format["loaders"]: + if loader["display_name"] == "MASK ZIP 1.1": + continue # can't really predict the result and check + response = self._upload_api_v1_tasks_id_annotations(task["id"], annotator, uploaded_data, "format={}".format(loader["display_name"])) + self.assertEqual(response.status_code, HTTP_202_ACCEPTED) + + response = self._upload_api_v1_tasks_id_annotations(task["id"], annotator, {}, "format={}".format(loader["display_name"])) + self.assertEqual(response.status_code, HTTP_201_CREATED) + + response = self._get_api_v1_tasks_id_annotations(task["id"], annotator) + self.assertEqual(response.status_code, HTTP_200_OK) + data["version"] += 2 # upload is delete + put + self._check_response(response, data) + + def _check_dump_content(self, content, task, jobs, data, annotation_format_name): + def etree_to_dict(t): + d = {t.tag: {} if t.attrib else None} + children = list(t) + if children: + dd = defaultdict(list) + for dc in map(etree_to_dict, children): + for k, v in dc.items(): + dd[k].append(v) + d = {t.tag: {k: v[0] if len(v) == 1 else v + for k, v in dd.items()}} + if t.attrib: + d[t.tag].update(('@' + k, v) for k, v in t.attrib.items()) + if t.text: + text = t.text.strip() + if not (children or t.attrib): + d[t.tag] = text + return d + + if annotation_format_name == "CVAT": + xmldump = ET.fromstring(content.read()) self.assertEqual(xmldump.tag, "annotations") tags = xmldump.findall("./meta") self.assertEqual(len(tags), 1) meta = etree_to_dict(tags[0])["meta"] self.assertEqual(meta["task"]["name"], task["name"]) + elif annotation_format_name == "PASCAL VOC": + self.assertTrue(zipfile.is_zipfile(content)) + elif annotation_format_name == "YOLO": + self.assertTrue(zipfile.is_zipfile(content)) + elif annotation_format_name == "COCO": + with tempfile.NamedTemporaryFile() as tmp_file: + tmp_file.write(content.read()) + tmp_file.flush() + coco = coco_loader.COCO(tmp_file.name) + self.assertTrue(coco.getAnnIds()) + elif annotation_format_name == "TFRecord": + self.assertTrue(zipfile.is_zipfile(content)) + elif annotation_format_name == "MASK": + self.assertTrue(zipfile.is_zipfile(content)) + + + def _run_coco_annotation_upload_test(self, user): + def generate_coco_anno(): + return b"""{ + "categories": [ + { + "id": 1, + "name": "car", + "supercategory": "" + }, + { + "id": 2, + "name": "person", + "supercategory": "" + } + ], + "images": [ + { + "coco_url": "", + "date_captured": "", + "flickr_url": "", + "license": 0, + "id": 0, + "file_name": "test_1.jpg", + "height": 720, + "width": 1280 + } + ], + "annotations": [ + { + "category_id": 1, + "id": 1, + "image_id": 0, + "iscrowd": 0, + "segmentation": [ + [] + ], + "area": 17702.0, + "bbox": [ + 574.0, + 407.0, + 167.0, + 106.0 + ] + } + ] + }""" + + response = self._get_annotation_formats(user) + self.assertEqual(response.status_code, status.HTTP_200_OK) + supported_formats = response.data + self.assertTrue(isinstance(supported_formats, list) and supported_formats) + + coco_format = None + for f in response.data: + if f["name"] == "COCO": + coco_format = f + break + self.assertTrue(coco_format) + loader = coco_format["loaders"][0] + task, _ = self._create_task(user, user) + + content = io.BytesIO(generate_coco_anno()) + content.seek(0) + + uploaded_data = { + "annotation_file": content, + } + response = self._upload_api_v1_tasks_id_annotations(task["id"], user, uploaded_data, "format={}".format(loader["display_name"])) + self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) + + response = self._upload_api_v1_tasks_id_annotations(task["id"], user, {}, "format={}".format(loader["display_name"])) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + response = self._get_api_v1_tasks_id_annotations(task["id"], user) + self.assertEqual(response.status_code, status.HTTP_200_OK) def test_api_v1_tasks_id_annotations_admin(self): self._run_api_v1_tasks_id_annotations(self.admin, self.assignee, @@ -2135,7 +2919,19 @@ def test_api_v1_tasks_id_annotations_user(self): def test_api_v1_tasks_id_annotations_no_auth(self): self._run_api_v1_tasks_id_annotations(self.user, self.assignee, None) + def test_api_v1_tasks_id_annotations_dump_load_admin(self): + self._run_api_v1_tasks_id_annotations_dump_load(self.admin, self.assignee, + self.assignee) + + def test_api_v1_tasks_id_annotations_dump_load_user(self): + self._run_api_v1_tasks_id_annotations_dump_load(self.user, self.assignee, + self.assignee) + + def test_api_v1_tasks_id_annotations_dump_load_no_auth(self): + self._run_api_v1_tasks_id_annotations_dump_load(self.user, self.assignee, None) + def test_api_v1_tasks_id_annotations_upload_coco_user(self): + self._run_coco_annotation_upload_test(self.user) class ServerShareAPITestCase(APITestCase): def setUp(self): diff --git a/cvat/apps/engine/urls.py b/cvat/apps/engine/urls.py index 534d72b7b5e..eef0ab2e047 100644 --- a/cvat/apps/engine/urls.py +++ b/cvat/apps/engine/urls.py @@ -1,5 +1,5 @@ -# Copyright (C) 2018 Intel Corporation +# Copyright (C) 2018-2019 Intel Corporation # # SPDX-License-Identifier: MIT @@ -24,6 +24,7 @@ ) router = routers.DefaultRouter(trailing_slash=False) +router.register('projects', views.ProjectViewSet) router.register('tasks', views.TaskViewSet) router.register('jobs', views.JobViewSet) router.register('users', views.UserViewSet) @@ -33,11 +34,15 @@ urlpatterns = [ # Entry point for a client path('', views.dispatch_request), + path('dashboard/', views.dispatch_request), # documentation for API - path('api/swagger.$', schema_view.without_ui(cache_timeout=0), name='schema-json'), - path('api/swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), - path('api/docs/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'), + path('api/swagger', views.wrap_swagger( + schema_view.without_ui(cache_timeout=0)), name='schema-json'), + path('api/swagger/', views.wrap_swagger( + schema_view.with_ui('swagger', cache_timeout=0)), name='schema-swagger-ui'), + path('api/docs/', views.wrap_swagger( + schema_view.with_ui('redoc', cache_timeout=0)), name='schema-redoc'), # entry point for API path('api/v1/auth/', include('cvat.apps.authentication.api_urls')), diff --git a/cvat/apps/engine/views.py b/cvat/apps/engine/views.py index 415637535c2..8536f21afbd 100644 --- a/cvat/apps/engine/views.py +++ b/cvat/apps/engine/views.py @@ -1,8 +1,9 @@ -# Copyright (C) 2018 Intel Corporation +# Copyright (C) 2018-2019 Intel Corporation # # SPDX-License-Identifier: MIT import os +import os.path as osp import re import traceback from ast import literal_eval @@ -10,8 +11,9 @@ from datetime import datetime from tempfile import mkstemp -from django.http import HttpResponseBadRequest -from django.shortcuts import redirect, render +from django.views.generic import RedirectView +from django.http import HttpResponseBadRequest, HttpResponseNotFound +from django.shortcuts import render from django.conf import settings from sendfile import sendfile from rest_framework.permissions import IsAuthenticated @@ -25,38 +27,70 @@ from django_filters import rest_framework as filters import django_rq from django.db import IntegrityError +from django.utils import timezone from . import annotation, task, models from cvat.settings.base import JS_3RDPARTY, CSS_3RDPARTY from cvat.apps.authentication.decorators import login_required -import logging from .log import slogger, clogger from cvat.apps.engine.models import StatusChoice, Task, Job, Plugin from cvat.apps.engine.serializers import (TaskSerializer, UserSerializer, ExceptionSerializer, AboutSerializer, JobSerializer, ImageMetaSerializer, RqStatusSerializer, TaskDataSerializer, LabeledDataSerializer, - PluginSerializer, FileInfoSerializer, LogEventSerializer) -from cvat.apps.annotation.serializers import AnnotationFileSerializer + PluginSerializer, FileInfoSerializer, LogEventSerializer, + ProjectSerializer, BasicUserSerializer) +from cvat.apps.annotation.serializers import AnnotationFileSerializer, AnnotationFormatSerializer from django.contrib.auth.models import User from django.core.exceptions import ObjectDoesNotExist from cvat.apps.authentication import auth from rest_framework.permissions import SAFE_METHODS from cvat.apps.annotation.models import AnnotationDumper, AnnotationLoader from cvat.apps.annotation.format import get_annotation_formats +import cvat.apps.dataset_manager.task as DatumaroTask + +from drf_yasg.utils import swagger_auto_schema +from drf_yasg import openapi +from django.utils.decorators import method_decorator +from drf_yasg.inspectors import NotHandled, CoreAPICompatInspector +from django_filters.rest_framework import DjangoFilterBackend + +# drf-yasg component doesn't handle correctly URL_FORMAT_OVERRIDE and +# send requests with ?format=openapi suffix instead of ?scheme=openapi. +# We map the required paramater explicitly and add it into query arguments +# on the server side. +def wrap_swagger(view): + @login_required + def _map_format_to_schema(request, scheme=None): + if 'format' in request.GET: + request.GET = request.GET.copy() + format_alias = settings.REST_FRAMEWORK['URL_FORMAT_OVERRIDE'] + request.GET[format_alias] = request.GET['format'] + + return view(request, format=scheme) + + return _map_format_to_schema # Server REST API @login_required def dispatch_request(request): """An entry point to dispatch legacy requests""" - if request.method == 'GET' and 'id' in request.GET: + if 'dashboard' in request.path or (request.path == '/' and 'id' not in request.GET): + return RedirectView.as_view( + url=settings.UI_URL, + permanent=True, + query_string=True + )(request) + elif request.method == 'GET' and 'id' in request.GET and request.path == '/': return render(request, 'engine/annotation.html', { 'css_3rdparty': CSS_3RDPARTY.get('engine', []), 'js_3rdparty': JS_3RDPARTY.get('engine', []), - 'status_list': [str(i) for i in StatusChoice] + 'status_list': [str(i) for i in StatusChoice], + 'ui_url': settings.UI_URL }) else: - return redirect('/dashboard/') + return HttpResponseNotFound() + class ServerViewSet(viewsets.ViewSet): serializer_class = None @@ -67,6 +101,8 @@ def get_serializer(self, *args, **kwargs): pass @staticmethod + @swagger_auto_schema(method='get', operation_summary='Method provides basic CVAT information', + responses={'200': AboutSerializer}) @action(detail=False, methods=['GET'], serializer_class=AboutSerializer) def about(request): from cvat import __version__ as cvat_version @@ -86,8 +122,14 @@ def about(request): return Response(data=serializer.data) @staticmethod + @swagger_auto_schema(method='post', request_body=ExceptionSerializer) @action(detail=False, methods=['POST'], serializer_class=ExceptionSerializer) def exception(request): + """ + Saves an exception from a client on the server + + Sends logs to the ELK if it is connected + """ serializer = ExceptionSerializer(data=request.data) if serializer.is_valid(raise_exception=True): additional_info = { @@ -107,8 +149,14 @@ def exception(request): return Response(serializer.data, status=status.HTTP_201_CREATED) @staticmethod + @swagger_auto_schema(method='post', request_body=LogEventSerializer(many=True)) @action(detail=False, methods=['POST'], serializer_class=LogEventSerializer) def logs(request): + """ + Saves logs from a client on the server + + Sends logs to the ELK if it is connected + """ serializer = LogEventSerializer(many=True, data=request.data) if serializer.is_valid(raise_exception=True): user = { "username": request.user.username } @@ -125,6 +173,11 @@ def logs(request): return Response(serializer.data, status=status.HTTP_201_CREATED) @staticmethod + @swagger_auto_schema( + method='get', operation_summary='Returns all files and folders that are on the server along specified path', + manual_parameters=[openapi.Parameter('directory', openapi.IN_QUERY, type=openapi.TYPE_STRING, description='Directory to browse')], + responses={'200' : FileInfoSerializer(many=True)} + ) @action(detail=False, methods=['GET'], serializer_class=FileInfoSerializer) def share(request): param = request.query_params.get('directory', '/') @@ -153,12 +206,98 @@ def share(request): status=status.HTTP_400_BAD_REQUEST) @staticmethod + @swagger_auto_schema(method='get', operation_summary='Method provides the list of available annotations formats supported by the server', + responses={'200': AnnotationFormatSerializer(many=True)}) @action(detail=False, methods=['GET'], url_path='annotation/formats') - def formats(request): + def annotation_formats(request): data = get_annotation_formats() return Response(data) + @staticmethod + @action(detail=False, methods=['GET'], url_path='dataset/formats') + def dataset_formats(request): + data = DatumaroTask.get_export_formats() + data = JSONRenderer().render(data) + return Response(data) + +class ProjectFilter(filters.FilterSet): + name = filters.CharFilter(field_name="name", lookup_expr="icontains") + owner = filters.CharFilter(field_name="owner__username", lookup_expr="icontains") + status = filters.CharFilter(field_name="status", lookup_expr="icontains") + assignee = filters.CharFilter(field_name="assignee__username", lookup_expr="icontains") + + class Meta: + model = models.Project + fields = ("id", "name", "owner", "status", "assignee") + +@method_decorator(name='list', decorator=swagger_auto_schema( + operation_summary='Returns a paginated list of projects according to query parameters (10 projects per page)', + manual_parameters=[ + openapi.Parameter('id', openapi.IN_QUERY, description="A unique number value identifying this project", + type=openapi.TYPE_NUMBER), + openapi.Parameter('name', openapi.IN_QUERY, description="Find all projects where name contains a parameter value", + type=openapi.TYPE_STRING), + openapi.Parameter('owner', openapi.IN_QUERY, description="Find all project where owner name contains a parameter value", + type=openapi.TYPE_STRING), + openapi.Parameter('status', openapi.IN_QUERY, description="Find all projects with a specific status", + type=openapi.TYPE_STRING, enum=[str(i) for i in StatusChoice]), + openapi.Parameter('assignee', openapi.IN_QUERY, description="Find all projects where assignee name contains a parameter value", + type=openapi.TYPE_STRING)])) +@method_decorator(name='create', decorator=swagger_auto_schema(operation_summary='Method creates a new project')) +@method_decorator(name='retrieve', decorator=swagger_auto_schema(operation_summary='Method returns details of a specific project')) +@method_decorator(name='destroy', decorator=swagger_auto_schema(operation_summary='Method deletes a specific project')) +@method_decorator(name='partial_update', decorator=swagger_auto_schema(operation_summary='Methods does a partial update of chosen fields in a project')) +class ProjectViewSet(auth.ProjectGetQuerySetMixin, viewsets.ModelViewSet): + queryset = models.Project.objects.all().order_by('-id') + serializer_class = ProjectSerializer + search_fields = ("name", "owner__username", "assignee__username", "status") + filterset_class = ProjectFilter + ordering_fields = ("id", "name", "owner", "status", "assignee") + http_method_names = ['get', 'post', 'head', 'patch', 'delete'] + + def get_permissions(self): + http_method = self.request.method + permissions = [IsAuthenticated] + + if http_method in SAFE_METHODS: + permissions.append(auth.ProjectAccessPermission) + elif http_method in ["POST"]: + permissions.append(auth.ProjectCreatePermission) + elif http_method in ["PATCH"]: + permissions.append(auth.ProjectChangePermission) + elif http_method in ["DELETE"]: + permissions.append(auth.ProjectDeletePermission) + else: + permissions.append(auth.AdminRolePermission) + + return [perm() for perm in permissions] + + def perform_create(self, serializer): + if self.request.data.get('owner', None): + serializer.save() + else: + serializer.save(owner=self.request.user) + + @swagger_auto_schema(method='get', operation_summary='Returns information of the tasks of the project with the selected id', + responses={'200': TaskSerializer(many=True)}) + @action(detail=True, methods=['GET'], serializer_class=TaskSerializer) + def tasks(self, request, pk): + self.get_object() # force to call check_object_permissions + queryset = Task.objects.filter(project_id=pk).order_by('-id') + queryset = auth.filter_task_queryset(queryset, request.user) + + page = self.paginate_queryset(queryset) + if page is not None: + serializer = self.get_serializer(page, many=True, + context={"request": request}) + return self.get_paginated_response(serializer.data) + + serializer = self.get_serializer(queryset, many=True, + context={"request": request}) + return Response(serializer.data) + class TaskFilter(filters.FilterSet): + project = filters.CharFilter(field_name="project__name", lookup_expr="icontains") name = filters.CharFilter(field_name="name", lookup_expr="icontains") owner = filters.CharFilter(field_name="owner__username", lookup_expr="icontains") mode = filters.CharFilter(field_name="mode", lookup_expr="icontains") @@ -167,8 +306,38 @@ class TaskFilter(filters.FilterSet): class Meta: model = Task - fields = ("id", "name", "owner", "mode", "status", "assignee") - + fields = ("id", "project_id", "project", "name", "owner", "mode", "status", + "assignee") + +class DjangoFilterInspector(CoreAPICompatInspector): + def get_filter_parameters(self, filter_backend): + if isinstance(filter_backend, DjangoFilterBackend): + result = super(DjangoFilterInspector, self).get_filter_parameters(filter_backend) + res = result.copy() + + for param in result: + if param.get('name') == 'project_id' or param.get('name') == 'project': + res.remove(param) + return res + + return NotHandled + +@method_decorator(name='list', decorator=swagger_auto_schema( + operation_summary='Returns a paginated list of tasks according to query parameters (10 tasks per page)', + manual_parameters=[ + openapi.Parameter('id',openapi.IN_QUERY,description="A unique number value identifying this task",type=openapi.TYPE_NUMBER), + openapi.Parameter('name', openapi.IN_QUERY, description="Find all tasks where name contains a parameter value", type=openapi.TYPE_STRING), + openapi.Parameter('owner', openapi.IN_QUERY, description="Find all tasks where owner name contains a parameter value", type=openapi.TYPE_STRING), + openapi.Parameter('mode', openapi.IN_QUERY, description="Find all tasks with a specific mode", type=openapi.TYPE_STRING, enum=['annotation', 'interpolation']), + openapi.Parameter('status', openapi.IN_QUERY, description="Find all tasks with a specific status", type=openapi.TYPE_STRING,enum=['annotation','validation','completed']), + openapi.Parameter('assignee', openapi.IN_QUERY, description="Find all tasks where assignee name contains a parameter value", type=openapi.TYPE_STRING) + ], + filter_inspectors=[DjangoFilterInspector])) +@method_decorator(name='create', decorator=swagger_auto_schema(operation_summary='Method creates a new task in a database without any attached images and videos')) +@method_decorator(name='retrieve', decorator=swagger_auto_schema(operation_summary='Method returns details of a specific task')) +@method_decorator(name='update', decorator=swagger_auto_schema(operation_summary='Method updates a task by id')) +@method_decorator(name='destroy', decorator=swagger_auto_schema(operation_summary='Method deletes a specific task, all attached jobs, annotations, and data')) +@method_decorator(name='partial_update', decorator=swagger_auto_schema(operation_summary='Methods does a partial update of chosen fields in a task')) class TaskViewSet(auth.TaskGetQuerySetMixin, viewsets.ModelViewSet): queryset = Task.objects.all().prefetch_related( "label_set__attributespec_set", @@ -187,7 +356,7 @@ def get_permissions(self): permissions.append(auth.TaskAccessPermission) elif http_method in ["POST"]: permissions.append(auth.TaskCreatePermission) - elif http_method in ["PATCH", "PUT"]: + elif self.action == 'annotations' or http_method in ["PATCH", "PUT"]: permissions.append(auth.TaskChangePermission) elif http_method in ["DELETE"]: permissions.append(auth.TaskDeletePermission) @@ -207,27 +376,40 @@ def perform_destroy(self, instance): super().perform_destroy(instance) shutil.rmtree(task_dirname, ignore_errors=True) - @staticmethod + @swagger_auto_schema(method='get', operation_summary='Returns a list of jobs for a specific task', + responses={'200': JobSerializer(many=True)}) @action(detail=True, methods=['GET'], serializer_class=JobSerializer) - def jobs(request, pk): + def jobs(self, request, pk): + self.get_object() # force to call check_object_permissions queryset = Job.objects.filter(segment__task_id=pk) serializer = JobSerializer(queryset, many=True, context={"request": request}) return Response(serializer.data) + @swagger_auto_schema(method='post', operation_summary='Method permanently attaches images or video to a task') @action(detail=True, methods=['POST'], serializer_class=TaskDataSerializer) def data(self, request, pk): - db_task = self.get_object() + """ + These data cannot be changed later + """ + db_task = self.get_object() # call check_object_permissions as well serializer = TaskDataSerializer(db_task, data=request.data) if serializer.is_valid(raise_exception=True): serializer.save() task.create(db_task.id, serializer.data) return Response(serializer.data, status=status.HTTP_202_ACCEPTED) + @swagger_auto_schema(method='get', operation_summary='Method returns annotations for a specific task') + @swagger_auto_schema(method='put', operation_summary='Method performs an update of all annotations in a specific task') + @swagger_auto_schema(method='patch', operation_summary='Method performs a partial update of annotations in a specific task', + manual_parameters=[openapi.Parameter('action', in_=openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING, + enum=['create', 'update', 'delete'])]) + @swagger_auto_schema(method='delete', operation_summary='Method deletes all annotations for a specific task') @action(detail=True, methods=['GET', 'DELETE', 'PUT', 'PATCH'], serializer_class=LabeledDataSerializer) def annotations(self, request, pk): + self.get_object() # force to call check_object_permissions if request.method == 'GET': data = annotation.get_task_data(pk, request.user) serializer = LabeledDataSerializer(data=data) @@ -262,12 +444,26 @@ def annotations(self, request, pk): return Response(data=str(e), status=status.HTTP_400_BAD_REQUEST) return Response(data) + @swagger_auto_schema(method='get', operation_summary='Method allows to download annotations as a file', + manual_parameters=[openapi.Parameter('filename', openapi.IN_PATH, description="A name of a file with annotations", + type=openapi.TYPE_STRING, required=True), + openapi.Parameter('format', openapi.IN_QUERY, description="A name of a dumper\nYou can get annotation dumpers from this API:\n/server/annotation/formats", + type=openapi.TYPE_STRING, required=True), + openapi.Parameter('action', in_=openapi.IN_QUERY, description='Used to start downloading process after annotation file had been created', + required=False, enum=['download'], type=openapi.TYPE_STRING)], + responses={'202': openapi.Response(description='Dump of annotations has been started'), + '201': openapi.Response(description='Annotations file is ready to download'), + '200': openapi.Response(description='Download of file started')}) @action(detail=True, methods=['GET'], serializer_class=None, url_path='annotations/(?P[^/]+)') def dump(self, request, pk, filename): + """ + Dump of annotations in common case is a long process which cannot be performed within one request. + First request starts dumping process. When the file is ready (code 201) you can get it with query parameter action=download. + """ filename = re.sub(r'[\\/*?:"<>|]', '_', filename) username = request.user.username - db_task = self.get_object() + db_task = self.get_object() # call check_object_permissions as well timestamp = datetime.now().strftime("%Y_%m_%d_%H_%M_%S") action = request.query_params.get("action") if action not in [None, "download"]: @@ -285,7 +481,7 @@ def dump(self, request, pk, filename): "{}.{}.{}.{}".format(filename, username, timestamp, db_dumper.format.lower())) queue = django_rq.get_queue("default") - rq_id = "{}@/api/v1/tasks/{}/annotations/{}".format(username, pk, filename) + rq_id = "{}@/api/v1/tasks/{}/annotations/{}/{}".format(username, pk, dump_format, filename) rq_job = queue.fetch_job(rq_id) if rq_job: @@ -323,8 +519,10 @@ def dump(self, request, pk, filename): return Response(status=status.HTTP_202_ACCEPTED) + @swagger_auto_schema(method='get', operation_summary='When task is being created the method returns information about a status of the creation process') @action(detail=True, methods=['GET'], serializer_class=RqStatusSerializer) def status(self, request, pk): + self.get_object() # force to call check_object_permissions response = self._get_rq_response(queue="default", job_id="/api/{}/tasks/{}".format(request.version, pk)) serializer = RqStatusSerializer(data=response) @@ -350,12 +548,13 @@ def _get_rq_response(queue, job_id): return response - @staticmethod + @swagger_auto_schema(method='get', operation_summary='Method provides a list of sizes (width, height) of media files which are related with the task', + responses={'200': ImageMetaSerializer(many=True)}) @action(detail=True, methods=['GET'], serializer_class=ImageMetaSerializer, url_path='frames/meta') - def data_info(request, pk): + def data_info(self, request, pk): try: - db_task = models.Task.objects.get(pk=pk) + db_task = self.get_object() # call check_object_permissions as well meta_cache_file = open(db_task.get_image_meta_cache_path()) except OSError: task.make_image_meta_cache(db_task) @@ -366,11 +565,13 @@ def data_info(request, pk): if serializer.is_valid(raise_exception=True): return Response(serializer.data) + @swagger_auto_schema(method='get', manual_parameters=[openapi.Parameter('frame', openapi.IN_PATH, required=True, + description="A unique integer value identifying this frame", type=openapi.TYPE_INTEGER)], + operation_summary='Method returns a specific frame for a specific task', + responses={'200': openapi.Response(description='frame')}) @action(detail=True, methods=['GET'], serializer_class=None, url_path='frames/(?P\d+)') def frame(self, request, pk, frame): - """Get a frame for the task""" - try: # Follow symbol links if the frame is a link on a real image otherwise # mimetype detection inside sendfile will work incorrectly. @@ -382,6 +583,81 @@ def frame(self, request, pk, frame): "cannot get frame #{}".format(frame), exc_info=True) return HttpResponseBadRequest(str(e)) + @swagger_auto_schema(method='get', operation_summary='Export task as a dataset in a specific format', + manual_parameters=[openapi.Parameter('action', in_=openapi.IN_QUERY, + required=False, type=openapi.TYPE_STRING, enum=['download']), + openapi.Parameter('format', in_=openapi.IN_QUERY, required=False, type=openapi.TYPE_STRING)], + responses={'202': openapi.Response(description='Dump of annotations has been started'), + '201': openapi.Response(description='Annotations file is ready to download'), + '200': openapi.Response(description='Download of file started')}) + @action(detail=True, methods=['GET'], serializer_class=None, + url_path='dataset') + def dataset_export(self, request, pk): + db_task = self.get_object() + + action = request.query_params.get("action", "") + action = action.lower() + if action not in ["", "download"]: + raise serializers.ValidationError( + "Unexpected parameter 'action' specified for the request") + + dst_format = request.query_params.get("format", "") + if not dst_format: + dst_format = DatumaroTask.DEFAULT_FORMAT + dst_format = dst_format.lower() + if dst_format not in [f['tag'] + for f in DatumaroTask.get_export_formats()]: + raise serializers.ValidationError( + "Unexpected parameter 'format' specified for the request") + + rq_id = "/api/v1/tasks/{}/dataset/{}".format(pk, dst_format) + queue = django_rq.get_queue("default") + + rq_job = queue.fetch_job(rq_id) + if rq_job: + last_task_update_time = timezone.localtime(db_task.updated_date) + request_time = rq_job.meta.get('request_time', None) + if request_time is None or request_time < last_task_update_time: + rq_job.cancel() + rq_job.delete() + else: + if rq_job.is_finished: + file_path = rq_job.return_value + if action == "download" and osp.exists(file_path): + rq_job.delete() + + timestamp = datetime.now().strftime("%Y_%m_%d_%H_%M_%S") + filename = "task_{}-{}-{}.zip".format( + db_task.name, timestamp, dst_format) + return sendfile(request, file_path, attachment=True, + attachment_filename=filename.lower()) + else: + if osp.exists(file_path): + return Response(status=status.HTTP_201_CREATED) + elif rq_job.is_failed: + exc_info = str(rq_job.exc_info) + rq_job.delete() + return Response(exc_info, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + else: + return Response(status=status.HTTP_202_ACCEPTED) + + try: + server_address = request.get_host() + except Exception: + server_address = None + + ttl = DatumaroTask.CACHE_TTL.total_seconds() + queue.enqueue_call(func=DatumaroTask.export_project, + args=(pk, request.user, dst_format, server_address), job_id=rq_id, + meta={ 'request_time': timezone.localtime() }, + result_ttl=ttl, failure_ttl=ttl) + return Response(status=status.HTTP_202_ACCEPTED) + +@method_decorator(name='retrieve', decorator=swagger_auto_schema(operation_summary='Method returns details of a job')) +@method_decorator(name='update', decorator=swagger_auto_schema(operation_summary='Method updates a job by id')) +@method_decorator(name='partial_update', decorator=swagger_auto_schema( + operation_summary='Methods does a partial update of chosen fields in a job')) class JobViewSet(viewsets.GenericViewSet, mixins.RetrieveModelMixin, mixins.UpdateModelMixin): queryset = Job.objects.all().order_by('id') @@ -400,10 +676,17 @@ def get_permissions(self): return [perm() for perm in permissions] - + @swagger_auto_schema(method='get', operation_summary='Method returns annotations for a specific job') + @swagger_auto_schema(method='put', operation_summary='Method performs an update of all annotations in a specific job') + @swagger_auto_schema(method='patch', manual_parameters=[ + openapi.Parameter('action', in_=openapi.IN_QUERY, type=openapi.TYPE_STRING, required=True, + enum=['create', 'update', 'delete'])], + operation_summary='Method performs a partial update of annotations in a specific job') + @swagger_auto_schema(method='delete', operation_summary='Method deletes all annotations for a specific job') @action(detail=True, methods=['GET', 'DELETE', 'PUT', 'PATCH'], serializer_class=LabeledDataSerializer) def annotations(self, request, pk): + self.get_object() # force to call check_object_permissions if request.method == 'GET': data = annotation.get_job_data(pk, request.user) return Response(data) @@ -440,24 +723,50 @@ def annotations(self, request, pk): return Response(data=str(e), status=status.HTTP_400_BAD_REQUEST) return Response(data) +@method_decorator(name='list', decorator=swagger_auto_schema( + operation_summary='Method provides a paginated list of users registered on the server')) +@method_decorator(name='retrieve', decorator=swagger_auto_schema( + operation_summary='Method provides information of a specific user')) +@method_decorator(name='partial_update', decorator=swagger_auto_schema( + operation_summary='Method updates chosen fields of a user')) +@method_decorator(name='destroy', decorator=swagger_auto_schema( + operation_summary='Method deletes a specific user from the server')) class UserViewSet(viewsets.GenericViewSet, mixins.ListModelMixin, - mixins.RetrieveModelMixin, mixins.UpdateModelMixin): + mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin): queryset = User.objects.all().order_by('id') - serializer_class = UserSerializer + http_method_names = ['get', 'post', 'head', 'patch', 'delete'] + + def get_serializer_class(self): + user = self.request.user + if user.is_staff: + return UserSerializer + else: + is_self = int(self.kwargs.get("pk", 0)) == user.id or \ + self.action == "self" + if is_self and self.request.method in SAFE_METHODS: + return UserSerializer + else: + return BasicUserSerializer def get_permissions(self): permissions = [IsAuthenticated] - if not self.action in ["self"]: - user = self.request.user - if self.action != "retrieve" or int(self.kwargs.get("pk", 0)) != user.id: + user = self.request.user + + if not self.request.method in SAFE_METHODS: + is_self = int(self.kwargs.get("pk", 0)) == user.id + if not is_self: permissions.append(auth.AdminRolePermission) return [perm() for perm in permissions] - @staticmethod - @action(detail=False, methods=['GET'], serializer_class=UserSerializer) - def self(request): - serializer = UserSerializer(request.user, context={ "request": request }) + @swagger_auto_schema(method='get', operation_summary='Method returns an instance of a user who is currently authorized') + @action(detail=False, methods=['GET']) + def self(self, request): + """ + Method returns an instance of a user who is currently authorized + """ + serializer_class = self.get_serializer_class() + serializer = serializer_class(request.user, context={ "request": request }) return Response(serializer.data) class PluginViewSet(viewsets.ModelViewSet): @@ -496,6 +805,15 @@ def rq_handler(job, exc_type, exc_value, tb): return True +# TODO: Method should be reimplemented as a separated view +# @swagger_auto_schema(method='put', manual_parameters=[openapi.Parameter('format', in_=openapi.IN_QUERY, +# description='A name of a loader\nYou can get annotation loaders from this API:\n/server/annotation/formats', +# required=True, type=openapi.TYPE_STRING)], +# operation_summary='Method allows to upload annotations', +# responses={'202': openapi.Response(description='Load of annotations has been started'), +# '201': openapi.Response(description='Annotations have been uploaded')}, +# tags=['tasks']) +# @api_view(['PUT']) def load_data_proxy(request, rq_id, rq_func, pk): queue = django_rq.get_queue("default") rq_job = queue.fetch_job(rq_id) diff --git a/cvat/apps/git/__init__.py b/cvat/apps/git/__init__.py index a7027503b5d..411e7c9d00c 100644 --- a/cvat/apps/git/__init__.py +++ b/cvat/apps/git/__init__.py @@ -1,7 +1,3 @@ -# Copyright (C) 2018 Intel Corporation +# Copyright (C) 2018-2019 Intel Corporation # # SPDX-License-Identifier: MIT - -from cvat.settings.base import JS_3RDPARTY - -JS_3RDPARTY['dashboard'] = JS_3RDPARTY.get('dashboard', []) + ['git/js/dashboardPlugin.js'] diff --git a/cvat/apps/git/git.py b/cvat/apps/git/git.py index b9ef5136f95..1874b25ce5f 100644 --- a/cvat/apps/git/git.py +++ b/cvat/apps/git/git.py @@ -76,8 +76,13 @@ def __init__(self, db_git, tid, user): # HTTP/HTTPS: [http://]github.com/proj/repos[.git] def _parse_url(self): try: - http_pattern = "([https|http]+)*[://]*([a-zA-Z0-9._-]+.[a-zA-Z]+)/([a-zA-Z0-9._-]+)/([a-zA-Z0-9._-]+)" - ssh_pattern = "([a-zA-Z0-9._-]+)@([a-zA-Z0-9._-]+):([a-zA-Z0-9._-]+)/([a-zA-Z0-9._-]+)" + # Almost STD66 (RFC3986), but schema can include a leading digit + # Reference on URL formats accepted by Git: + # https://github.com/git/git/blob/77bd3ea9f54f1584147b594abc04c26ca516d987/url.c + + host_pattern = r"((?:(?:(?:\d{1,3}\.){3}\d{1,3})|(?:[a-zA-Z0-9._-]+.[a-zA-Z]+))(?::\d+)?)" + http_pattern = r"(?:http[s]?://)?" + host_pattern + r"((?:/[a-zA-Z0-9._-]+){2})" + ssh_pattern = r"([a-zA-Z0-9._-]+)@" + host_pattern + r":([a-zA-Z0-9._-]+)/([a-zA-Z0-9._-]+)" http_match = re.match(http_pattern, self._url) ssh_match = re.match(ssh_pattern, self._url) @@ -87,21 +92,21 @@ def _parse_url(self): repos = None if http_match: - host = http_match.group(2) - repos = "{}/{}".format(http_match.group(3), http_match.group(4)) + host = http_match.group(1) + repos = http_match.group(2)[1:] elif ssh_match: user = ssh_match.group(1) host = ssh_match.group(2) repos = "{}/{}".format(ssh_match.group(3), ssh_match.group(4)) else: - raise Exception("Got URL doesn't sutisfy for regular expression") + raise Exception("Git repository URL does not satisfy pattern") if not repos.endswith(".git"): repos += ".git" return user, host, repos except Exception as ex: - slogger.glob.exception('URL parsing errors occured', exc_info = True) + slogger.glob.exception('URL parsing errors occurred', exc_info = True) raise ex @@ -428,11 +433,12 @@ def get(tid, user): response['status']['value'] = str(db_git.status) except git.exc.GitCommandError as ex: _have_no_access_exception(ex) + db_git.save() except Exception as ex: db_git.status = GitStatusChoice.NON_SYNCED + db_git.save() response['status']['error'] = str(ex) - db_git.save() return response def update_states(): diff --git a/cvat/apps/git/static/git/js/dashboardPlugin.js b/cvat/apps/git/static/git/js/dashboardPlugin.js deleted file mode 100644 index bdfc3cc013e..00000000000 --- a/cvat/apps/git/static/git/js/dashboardPlugin.js +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright (C) 2018 Intel Corporation - * - * SPDX-License-Identifier: MIT - */ - -/* global - showMessage:false -*/ - -// GIT ENTRYPOINT -window.addEventListener('dashboardReady', () => { - const reposWindowId = 'gitReposWindow'; - const closeReposWindowButtonId = 'closeGitReposButton'; - const reposURLTextId = 'gitReposURLText'; - const reposSyncButtonId = 'gitReposSyncButton'; - const labelStatusId = 'gitReposLabelStatus'; - const labelMessageId = 'gitReposLabelMessage'; - - const reposWindowTemplate = ` -
      Label Boxes Polygons Polylines Points Cuboids Manually Interpolated Total T S T S T
      - - - - - - - - - -
      - - - -
      - - -
      - - -
      -
      - -
      -
      -
      - -
      - - `; - - $.get('/git/repository/meta/get').done((gitData) => { - const dashboardItems = $('.dashboardItem'); - dashboardItems.each(function setupDashboardItem() { - const tid = +this.getAttribute('tid'); - if (tid in gitData) { - if (['sync', 'syncing'].includes(gitData[tid])) { - this.style.background = 'floralwhite'; - } else if (gitData[tid] === 'merged') { - this.style.background = 'azure'; - } else { - this.style.background = 'mistyrose'; - } - - $('').addClass('regular dashboardButtonUI').on('click', () => { - $(`#${reposWindowId}`).remove(); - const gitWindow = $(reposWindowTemplate).appendTo('body'); - const closeReposWindowButton = $(`#${closeReposWindowButtonId}`); - const reposSyncButton = $(`#${reposSyncButtonId}`); - const gitLabelMessage = $(`#${labelMessageId}`); - const gitLabelStatus = $(`#${labelStatusId}`); - const reposURLText = $(`#${reposURLTextId}`); - - function updateState() { - reposURLText.attr('placeholder', 'Waiting for server response..'); - reposURLText.prop('value', ''); - gitLabelMessage.css('color', '#cccc00').text('Waiting for server response..'); - gitLabelStatus.css('color', '#cccc00').text('\u25cc'); - reposSyncButton.attr('disabled', true); - - $.get(`/git/repository/get/${tid}`).done((data) => { - reposURLText.attr('placeholder', ''); - reposURLText.prop('value', data.url.value); - - if (!data.status.value) { - gitLabelStatus.css('color', 'red').text('\u26a0'); - gitLabelMessage.css('color', 'red').text(data.status.error); - reposSyncButton.attr('disabled', false); - return; - } - - if (data.status.value === '!sync') { - gitLabelStatus.css('color', 'red').text('\u2606'); - gitLabelMessage.css('color', 'red').text('Repository is not synchronized'); - reposSyncButton.attr('disabled', false); - } else if (data.status.value === 'sync') { - gitLabelStatus.css('color', '#cccc00').text('\u2605'); - gitLabelMessage.css('color', 'black').text('Synchronized (merge required)'); - } else if (data.status.value === 'merged') { - gitLabelStatus.css('color', 'darkgreen').text('\u2605'); - gitLabelMessage.css('color', 'darkgreen').text('Synchronized'); - } else if (data.status.value === 'syncing') { - gitLabelMessage.css('color', '#cccc00').text('Synchronization..'); - gitLabelStatus.css('color', '#cccc00').text('\u25cc'); - } else { - const message = `Got unknown repository status: ${data.status.value}`; - gitLabelStatus.css('color', 'red').text('\u26a0'); - gitLabelMessage.css('color', 'red').text(message); - } - }).fail((data) => { - gitWindow.remove(); - const message = 'Error occured during get an repos status. ' - + `Code: ${data.status}, text: ${data.responseText || data.statusText}`; - showMessage(message); - }); - } - - closeReposWindowButton.on('click', () => { - gitWindow.remove(); - }); - - reposSyncButton.on('click', () => { - function badResponse(message) { - try { - showMessage(message); - throw Error(message); - } finally { - gitWindow.remove(); - } - } - - gitLabelMessage.css('color', '#cccc00').text('Synchronization..'); - gitLabelStatus.css('color', '#cccc00').text('\u25cc'); - reposSyncButton.attr('disabled', true); - - $.get(`/git/repository/push/${tid}`).done((rqData) => { - function checkCallback() { - $.get(`/git/repository/check/${rqData.rq_id}`).done((statusData) => { - if (['queued', 'started'].includes(statusData.status)) { - setTimeout(checkCallback, 1000); - } else if (statusData.status === 'finished') { - updateState(); - } else if (statusData.status === 'failed') { - const message = `Can not push to remote repository. Message: ${statusData.stderr}`; - badResponse(message); - } else { - const message = `Check returned status "${statusData.status}".`; - badResponse(message); - } - }).fail((errorData) => { - const message = 'Errors occured during pushing an repos entry. ' - + `Code: ${errorData.status}, text: ${errorData.responseText || errorData.statusText}`; - badResponse(message); - }); - } - - setTimeout(checkCallback, 1000); - }).fail((errorData) => { - const message = 'Errors occured during pushing an repos entry. ' - + `Code: ${errorData.status}, text: ${errorData.responseText || errorData.statusText}`; - badResponse(message); - }); - }); - - updateState(); - }).appendTo($(this).find('div.dashboardButtonsUI')[0]); - } - }); - }).fail((errorData) => { - const message = `Can not get repository meta information. Code: ${errorData.status}. ` - + `Message: ${errorData.responseText || errorData.statusText}`; - showMessage(message); - }); -}); - -window.addEventListener('DOMContentLoaded', () => { - const createURLInputTextId = 'gitCreateURLInputText'; - const lfsCheckboxId = 'gitLFSCheckbox'; - - // Setup the "Create task" dialog - const title = 'Field for a repository URL and a relative path inside the repository. \n' - + 'Default repository path is `annotation/.zip`. \n' - + 'There are .zip or .xml extenstions are supported.'; - const placeh = 'github.com/user/repos [annotation/.zip]'; - - $(` - - - - - - - - - - `).insertAfter($('#dashboardBugTrackerInput').parent().parent()); - - async function cloneRepos(_, createdTask) { - async function wait(rqId) { - return new Promise((resolve, reject) => { - async function checkCallback() { - let response = null; - try { - response = await $.get(`/git/repository/check/${rqId}`); - } catch (errorData) { - const message = `Can not sent a request to clone the repository. Code: ${errorData.status}. ` - + `Message: ${errorData.responseText || errorData.statusText}`; - reject(new Error(message)); - } - - if (['queued', 'started'].includes(response.status)) { - setTimeout(checkCallback, 1000); - } else if (response.status === 'finished') { - resolve(); - } else if (response.status === 'failed') { - let message = 'Repository status check failed. '; - if (response.stderr) { - message += response.stderr; - } - - reject(new Error(message)); - } else { - const message = `Repository status check returned the status "${response.status}"`; - reject(new Error(message)); - } - } - - setTimeout(checkCallback, 1000); - }); - } - - const taskMessage = $('#dashboardCreateTaskMessage'); - - const path = $(`#${createURLInputTextId}`).prop('value').replace(/\s/g, ''); - const lfs = $(`#${lfsCheckboxId}`).prop('checked'); - - let response = null; - if (path.length) { - taskMessage.text('Git repository is being cloned..'); - - try { - response = await $.ajax({ - url: `/git/repository/create/${createdTask.id}`, - type: 'POST', - data: JSON.stringify({ - path, - lfs, - tid: createdTask.id, - }), - contentType: 'application/json', - }); - } catch (errorData) { - createdTask.delete(); - const message = `Can not send a request to clone the repository. Code: ${errorData.status}. ` - + `Message: ${errorData.responseText || errorData.statusText}`; - throw new Error(message); - } - - try { - await wait(response.rq_id); - } catch (exception) { - createdTask.delete(); - throw exception; - } - taskMessage.text('Git repository has been cloned..'); - } - } - - const gitPlugin = { - name: 'Git Plugin', - description: 'Plugin allows you to attach a repository to a task', - cvat: { - classes: { - Task: { - prototype: { - save: { - leave: cloneRepos, - }, - }, - }, - }, - }, - }; - - window.cvat.plugins.register(gitPlugin); -}); diff --git a/cvat/apps/git/tests.py b/cvat/apps/git/tests.py index 3517aa28fb2..5d4d2c116fd 100644 --- a/cvat/apps/git/tests.py +++ b/cvat/apps/git/tests.py @@ -2,6 +2,58 @@ # # SPDX-License-Identifier: MIT +from itertools import product + from django.test import TestCase # Create your tests here. + +from cvat.apps.git.git import Git + + +class GitUrlTest(TestCase): + class FakeGit: + def __init__(self, url): + self._url = url + + def _check_correct_urls(self, samples): + for i, (expected, url) in enumerate(samples): + git = GitUrlTest.FakeGit(url) + try: + actual = Git._parse_url(git) + self.assertEqual(expected, actual, "URL #%s: '%s'" % (i, url)) + except Exception: + self.assertFalse(True, "URL #%s: '%s'" % (i, url)) + + def test_correct_urls_can_be_parsed(self): + hosts = ['host.zone', '1.2.3.4'] + ports = ['', ':42'] + repo_groups = ['repo', 'r4p0'] + repo_repos = ['nkjl23', 'hewj'] + git_suffixes = ['', '.git'] + + samples = [] + + # http samples + protocols = ['', 'http://', 'https://'] + for protocol, host, port, repo_group, repo, git in product( + protocols, hosts, ports, repo_groups, repo_repos, git_suffixes): + url = '{protocol}{host}{port}/{repo_group}/{repo}{git}'.format( + protocol=protocol, host=host, port=port, + repo_group=repo_group, repo=repo, git=git + ) + expected = ('git', host + port, '%s/%s.git' % (repo_group, repo)) + samples.append((expected, url)) + + # git samples + users = ['user', 'u123_.'] + for user, host, port, repo_group, repo, git in product( + users, hosts, ports, repo_groups, repo_repos, git_suffixes): + url = '{user}@{host}{port}:{repo_group}/{repo}{git}'.format( + user=user, host=host, port=port, + repo_group=repo_group, repo=repo, git=git + ) + expected = (user, host + port, '%s/%s.git' % (repo_group, repo)) + samples.append((expected, url)) + + self._check_correct_urls(samples) \ No newline at end of file diff --git a/cvat/apps/log_viewer/__init__.py b/cvat/apps/log_viewer/__init__.py index 4c96c2ab030..411e7c9d00c 100644 --- a/cvat/apps/log_viewer/__init__.py +++ b/cvat/apps/log_viewer/__init__.py @@ -1,7 +1,3 @@ -# Copyright (C) 2018 Intel Corporation +# Copyright (C) 2018-2019 Intel Corporation # # SPDX-License-Identifier: MIT - -from cvat.settings.base import JS_3RDPARTY - -JS_3RDPARTY['dashboard'] = JS_3RDPARTY.get('dashboard', []) + ['log_viewer/js/dashboardPlugin.js'] diff --git a/cvat/apps/log_viewer/static/log_viewer/js/dashboardPlugin.js b/cvat/apps/log_viewer/static/log_viewer/js/dashboardPlugin.js deleted file mode 100644 index e40e279283d..00000000000 --- a/cvat/apps/log_viewer/static/log_viewer/js/dashboardPlugin.js +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright (C) 2018 Intel Corporation - * - * SPDX-License-Identifier: MIT - */ - -window.addEventListener('DOMContentLoaded', () => { - $('').on('click', () => { - window.open('/analytics/app/kibana'); - }).appendTo('#dashboardManageButtons'); -}); diff --git a/cvat/apps/tf_annotation/__init__.py b/cvat/apps/tf_annotation/__init__.py index 7c9219c0983..a0fca4cb39e 100644 --- a/cvat/apps/tf_annotation/__init__.py +++ b/cvat/apps/tf_annotation/__init__.py @@ -1,9 +1,4 @@ -# Copyright (C) 2018 Intel Corporation +# Copyright (C) 2018-2019 Intel Corporation # # SPDX-License-Identifier: MIT - -from cvat.settings.base import JS_3RDPARTY - -JS_3RDPARTY['dashboard'] = JS_3RDPARTY.get('dashboard', []) + ['tf_annotation/js/dashboardPlugin.js'] - diff --git a/cvat/apps/tf_annotation/static/tf_annotation/js/dashboardPlugin.js b/cvat/apps/tf_annotation/static/tf_annotation/js/dashboardPlugin.js deleted file mode 100644 index b0f319a349a..00000000000 --- a/cvat/apps/tf_annotation/static/tf_annotation/js/dashboardPlugin.js +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2018 Intel Corporation - * - * SPDX-License-Identifier: MIT - */ - -/* global - userConfirm:false - showMessage:false -*/ - -window.addEventListener('dashboardReady', () => { - function checkProcess(tid, button) { - function checkCallback() { - $.get(`/tensorflow/annotation/check/task/${tid}`).done((statusData) => { - if (['started', 'queued'].includes(statusData.status)) { - const progress = Math.round(statusData.progress) || '0'; - button.text(`Cancel TF Annotation (${progress}%)`); - setTimeout(checkCallback, 5000); - } else { - button.text('Run TF Annotation'); - button.removeClass('tfAnnotationProcess'); - button.prop('disabled', false); - - if (statusData.status === 'failed') { - const message = `Tensorflow annotation failed. Error: ${statusData.stderr}`; - showMessage(message); - } else if (statusData.status !== 'finished') { - const message = `Tensorflow annotation check request returned status "${statusData.status}"`; - showMessage(message); - } - } - }).fail((errorData) => { - const message = `Can not sent tensorflow annotation check request. Code: ${errorData.status}. ` - + `Message: ${errorData.responseText || errorData.statusText}`; - showMessage(message); - }); - } - - setTimeout(checkCallback, 5000); - } - - - function runProcess(tid, button) { - $.get(`/tensorflow/annotation/create/task/${tid}`).done(() => { - showMessage('Process has started'); - button.text('Cancel TF Annotation (0%)'); - button.addClass('tfAnnotationProcess'); - checkProcess(tid, button); - }).fail((errorData) => { - const message = `Can not run tf annotation. Code: ${errorData.status}. ` - + `Message: ${errorData.responseText || errorData.statusText}`; - showMessage(message); - }); - } - - - function cancelProcess(tid, button) { - $.get(`/tensorflow/annotation/cancel/task/${tid}`).done(() => { - button.prop('disabled', true); - }).fail((errorData) => { - const message = `Can not cancel tf annotation. Code: ${errorData.status}. ` - + `Message: ${errorData.responseText || errorData.statusText}`; - showMessage(message); - }); - } - - - function setupDashboardItem(item, metaData) { - const tid = +item.attr('tid'); - const button = $(''); - - button.on('click', () => { - if (button.hasClass('tfAnnotationProcess')) { - userConfirm('The process will be canceled. Continue?', () => { - cancelProcess(tid, button); - }); - } else { - userConfirm('The current annotation will be lost. Are you sure?', () => { - runProcess(tid, button); - }); - } - }); - - button.addClass('dashboardTFAnnotationButton regular dashboardButtonUI'); - button.appendTo(item.find('div.dashboardButtonsUI')); - - if ((tid in metaData) && (metaData[tid].active)) { - button.text('Cancel TF Annotation'); - button.addClass('tfAnnotationProcess'); - checkProcess(tid, button); - } - } - - const elements = $('.dashboardItem'); - const tids = Array.from(elements, el => +el.getAttribute('tid')); - - $.ajax({ - type: 'POST', - url: '/tensorflow/annotation/meta/get', - data: JSON.stringify(tids), - contentType: 'application/json; charset=utf-8', - }).done((metaData) => { - elements.each(function setupDashboardItemWrapper() { - setupDashboardItem($(this), metaData); - }); - }).fail((errorData) => { - const message = `Can not get tf annotation meta info. Code: ${errorData.status}. ` - + `Message: ${errorData.responseText || errorData.statusText}`; - showMessage(message); - }); -}); diff --git a/cvat/apps/tf_annotation/views.py b/cvat/apps/tf_annotation/views.py index c98c56765e6..2a70c9db323 100644 --- a/cvat/apps/tf_annotation/views.py +++ b/cvat/apps/tf_annotation/views.py @@ -1,21 +1,18 @@ -# Copyright (C) 2018 Intel Corporation +# Copyright (C) 2018-2019 Intel Corporation # # SPDX-License-Identifier: MIT -from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest, QueryDict -from django.core.exceptions import ObjectDoesNotExist -from django.shortcuts import render +from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest +from rest_framework.decorators import api_view from rules.contrib.views import permission_required, objectgetter from cvat.apps.authentication.decorators import login_required from cvat.apps.engine.models import Task as TaskModel -from cvat.apps.engine import annotation, task from cvat.apps.engine.serializers import LabeledDataSerializer from cvat.apps.engine.annotation import put_task_data import django_rq import fnmatch -import logging import json import os import rq @@ -223,14 +220,15 @@ def create_thread(tid, labels_mapping, user): try: slogger.task[tid].exception('exception was occured during tf annotation of the task', exc_info=True) except: - slogger.glob.exception('exception was occured during tf annotation of the task {}'.format(tid), exc_into=True) + slogger.glob.exception('exception was occured during tf annotation of the task {}'.format(tid), exc_info=True) raise ex +@api_view(['POST']) @login_required def get_meta_info(request): try: queue = django_rq.get_queue('low') - tids = json.loads(request.body.decode('utf-8')) + tids = request.data result = {} for tid in tids: job = queue.fetch_job('tf_annotation.create/{}'.format(tid)) @@ -242,7 +240,7 @@ def get_meta_info(request): return JsonResponse(result) except Exception as ex: - slogger.glob.exception('exception was occured during tf meta request', exc_into=True) + slogger.glob.exception('exception was occured during tf meta request', exc_info=True) return HttpResponseBadRequest(str(ex)) diff --git a/cvat/requirements/base.txt b/cvat/requirements/base.txt index 2f8ecbcd7f6..15cf69229b7 100644 --- a/cvat/requirements/base.txt +++ b/cvat/requirements/base.txt @@ -1,5 +1,5 @@ click==6.7 -Django==2.2.4 +Django==2.2.10 django-appconf==1.0.2 django-auth-ldap==1.4.0 django-cacheops==4.0.6 @@ -7,9 +7,8 @@ django-compressor==2.2 django-rq==2.0.0 EasyProcess==0.2.3 ffmpy==0.2.2 -Pillow==5.1.0 +Pillow==6.2.0 numpy==1.16.2 -patool==1.12 python-ldap==3.0.0 pytz==2018.3 pyunpack==0.1.2 @@ -18,6 +17,7 @@ redis==3.2.0 requests==2.20.0 rjsmin==1.0.12 rq==1.0.0 +rq-scheduler==0.9.1 scipy==1.2.1 sqlparse==0.2.4 django-sendfile==0.3.11 @@ -25,13 +25,13 @@ dj-pagination==2.4.0 python-logstash==0.4.6 django-revproxy==0.9.15 rules==2.0 -GitPython==2.1.11 +GitPython==3.0.8 coreapi==2.3.3 django-filter==2.0.0 Markdown==3.0.1 -djangorestframework==3.9.1 +djangorestframework==3.9.3 Pygments==2.3.1 -drf-yasg==1.16.0 +drf-yasg==1.17.0 Shapely==1.6.4.post2 pdf2image==1.6.0 pascal_voc_writer==0.1.4 @@ -39,6 +39,13 @@ django-rest-auth[with_social]==0.9.5 cython==0.29.13 matplotlib==3.0.3 scikit-image==0.15.0 -tensorflow==1.12.3 -django-cors-headers==3.0.2 +tensorflow==1.15.2 +keras==2.2.5 +opencv-python==4.1.0.25 +h5py==2.9.0 +imgaug==0.2.9 +django-cors-headers==3.2.0 furl==2.0.0 +# The package is used by pyunpack as a command line tool to support multiple +# archives. Don't use as a python module because it has GPL license. +patool==1.12 diff --git a/cvat/requirements/testing.txt b/cvat/requirements/testing.txt index b6468d78a44..f5f61eec145 100644 --- a/cvat/requirements/testing.txt +++ b/cvat/requirements/testing.txt @@ -1,2 +1,5 @@ -r development.txt -fakeredis==1.0.3 \ No newline at end of file +fakeredis==1.1.0 +# Fix dependencies for fakeredis 1.1.0 +# Pip will not reinstall six package if it is installed already +six==1.12.0 diff --git a/cvat/settings/base.py b/cvat/settings/base.py index 061fe17522b..e5f577d448b 100644 --- a/cvat/settings/base.py +++ b/cvat/settings/base.py @@ -1,4 +1,4 @@ -# Copyright (C) 2018 Intel Corporation +# Copyright (C) 2018-2019 Intel Corporation # # SPDX-License-Identifier: MIT @@ -30,8 +30,9 @@ try: sys.path.append(BASE_DIR) - from keys.secret_key import SECRET_KEY + from keys.secret_key import SECRET_KEY # pylint: disable=unused-import except ImportError: + from django.utils.crypto import get_random_string keys_dir = os.path.join(BASE_DIR, 'keys') if not os.path.isdir(keys_dir): @@ -91,10 +92,10 @@ def generate_ssh_keys(): 'django.contrib.messages', 'django.contrib.staticfiles', 'cvat.apps.engine', - 'cvat.apps.dashboard', 'cvat.apps.authentication', 'cvat.apps.documentation', 'cvat.apps.git', + 'cvat.apps.dataset_manager', 'cvat.apps.annotation', 'django_rq', 'compressor', @@ -123,7 +124,7 @@ def generate_ssh_keys(): 'rest_framework.permissions.IsAuthenticated', ], 'DEFAULT_AUTHENTICATION_CLASSES': [ - 'rest_framework.authentication.TokenAuthentication', + 'cvat.apps.authentication.auth.TokenAuthentication', 'cvat.apps.authentication.auth.SignatureAuthentication', 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication' @@ -136,7 +137,7 @@ def generate_ssh_keys(): # Need to add 'api-docs' here as a workaround for include_docs_urls. 'ALLOWED_VERSIONS': ('v1', 'api-docs'), 'DEFAULT_PAGINATION_CLASS': - 'rest_framework.pagination.PageNumberPagination', + 'cvat.apps.engine.pagination.CustomPagination', 'PAGE_SIZE': 10, 'DEFAULT_FILTER_BACKENDS': ( 'rest_framework.filters.SearchFilter', @@ -144,7 +145,7 @@ def generate_ssh_keys(): 'rest_framework.filters.OrderingFilter'), # Disable default handling of the 'format' query parameter by REST framework - 'URL_FORMAT_OVERRIDE': None, + 'URL_FORMAT_OVERRIDE': 'scheme', } REST_AUTH_REGISTER_SERIALIZERS = { @@ -157,7 +158,7 @@ def generate_ssh_keys(): if 'yes' == os.environ.get('OPENVINO_TOOLKIT', 'no'): INSTALLED_APPS += ['cvat.apps.auto_annotation'] -if 'yes' == os.environ.get('OPENVINO_TOOLKIT', 'no'): +if 'yes' == os.environ.get('OPENVINO_TOOLKIT', 'no') and os.environ.get('REID_MODEL_DIR', ''): INSTALLED_APPS += ['cvat.apps.reid'] if 'yes' == os.environ.get('WITH_DEXTR', 'no'): @@ -166,16 +167,22 @@ def generate_ssh_keys(): if os.getenv('DJANGO_LOG_VIEWER_HOST'): INSTALLED_APPS += ['cvat.apps.log_viewer'] +# new feature by Mohammad +if 'yes' == os.environ.get('AUTO_SEGMENTATION', 'no'): + INSTALLED_APPS += ['cvat.apps.auto_segmentation'] + MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', + 'corsheaders.middleware.CorsMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', + # FIXME + # 'corsheaders.middleware.CorsPostCsrfMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'dj_pagination.middleware.PaginationMiddleware', - 'corsheaders.middleware.CorsMiddleware', ] # Cross-Origin Resource Sharing settings for CVAT UI @@ -184,8 +191,13 @@ def generate_ssh_keys(): UI_PORT = os.environ.get('UI_PORT', '3000') CORS_ALLOW_CREDENTIALS = True CSRF_TRUSTED_ORIGINS = [UI_HOST] -UI_URL = '{}://{}:{}'.format(UI_SCHEME, UI_HOST, UI_PORT) +UI_URL = '{}://{}'.format(UI_SCHEME, UI_HOST) + +if UI_PORT and UI_PORT != '80': + UI_URL += ':{}'.format(UI_PORT) + CORS_ORIGIN_WHITELIST = [UI_URL] +CORS_REPLACE_HTTPS_REFERER = True STATICFILES_FINDERS = [ 'django.contrib.staticfiles.finders.FileSystemFinder', diff --git a/cvat/urls.py b/cvat/urls.py index e35ea21566c..6ae59f6b03a 100644 --- a/cvat/urls.py +++ b/cvat/urls.py @@ -1,5 +1,5 @@ -# Copyright (C) 2018 Intel Corporation +# Copyright (C) 2018-2019 Intel Corporation # # SPDX-License-Identifier: MIT @@ -18,17 +18,14 @@ 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ + from django.contrib import admin from django.urls import path, include -from django.conf import settings -from django.conf.urls.static import static from django.apps import apps -import os urlpatterns = [ path('admin/', admin.site.urls), path('', include('cvat.apps.engine.urls')), - path('dashboard/', include('cvat.apps.dashboard.urls')), path('django-rq/', include('django_rq.urls')), path('auth/', include('cvat.apps.authentication.urls')), path('documentation/', include('cvat.apps.documentation.urls')), @@ -54,3 +51,7 @@ if apps.is_installed('silk'): urlpatterns.append(path('profiler/', include('silk.urls'))) + +# new feature by Mohammad +if apps.is_installed('cvat.apps.auto_segmentation'): + urlpatterns.append(path('tensorflow/segmentation/', include('cvat.apps.auto_segmentation.urls'))) diff --git a/cvat_proxy/conf.d/cvat.conf.template b/cvat_proxy/conf.d/cvat.conf.template new file mode 100644 index 00000000000..c208417426d --- /dev/null +++ b/cvat_proxy/conf.d/cvat.conf.template @@ -0,0 +1,37 @@ +server { + listen 80; + server_name _ default; + return 404; +} + +server { + listen 80; + server_name ${CVAT_HOST}; + + location ~* /api/.*|git/.*|tensorflow/.*|auto_annotation/.*|analytics/.*|static/.*|admin|admin/.*|documentation/.*|dextr/.*|reid/.* { + proxy_pass http://cvat:8080; + proxy_pass_header X-CSRFToken; + proxy_set_header Host $http_host; + proxy_pass_header Set-Cookie; + } + + location / { + # workaround for match location by arguments + error_page 418 = @annotation_ui; + + if ( $query_string ~ "^id=\d+.*" ) { return 418; } + + proxy_pass http://cvat_ui; + proxy_pass_header X-CSRFToken; + proxy_set_header Host $http_host; + proxy_pass_header Set-Cookie; + } + + # old annotation ui, will be removed in the future. + location @annotation_ui { + proxy_pass http://cvat:8080; + proxy_pass_header X-CSRFToken; + proxy_set_header Host $http_host; + proxy_pass_header Set-Cookie; + } +} diff --git a/cvat_proxy/nginx.conf b/cvat_proxy/nginx.conf new file mode 100644 index 00000000000..289b0f6995f --- /dev/null +++ b/cvat_proxy/nginx.conf @@ -0,0 +1,16 @@ +worker_processes 2; + + +events { + worker_connections 1024; +} + +http { + include mime.types; + default_type application/octet-stream; + sendfile on; + keepalive_timeout 65; + + include /etc/nginx/conf.d/*.conf; + client_max_body_size 0; +} diff --git a/datumaro/CONTRIBUTING.md b/datumaro/CONTRIBUTING.md new file mode 100644 index 00000000000..b26152987c2 --- /dev/null +++ b/datumaro/CONTRIBUTING.md @@ -0,0 +1,195 @@ +## Table of Contents + +- [Installation](#installation) +- [Usage](#usage) +- [Testing](#testing) +- [Design](#design-and-code-structure) + +## Installation + +### Prerequisites + +- Python (3.5+) +- OpenVINO (optional) + +``` bash +git clone https://github.com/opencv/cvat +``` + +Optionally, install a virtual environment: + +``` bash +python -m pip install virtualenv +python -m virtualenv venv +. venv/bin/activate +``` + +Then install all dependencies: + +``` bash +while read -r p; do pip install $p; done < requirements.txt +``` + +If you're working inside CVAT environment: +``` bash +. .env/bin/activate +while read -r p; do pip install $p; done < datumaro/requirements.txt +``` + +## Usage + +> The directory containing Datumaro should be in the `PYTHONPATH` +> environment variable or `cvat/datumaro/` should be the current directory. + +``` bash +datum --help +python -m datumaro --help +python datumaro/ --help +python datum.py --help +``` + +``` python +import datumaro +``` + +## Testing + +It is expected that all Datumaro functionality is covered and checked by +unit tests. Tests are placed in `tests/` directory. + +To run tests use: + +``` bash +python -m unittest discover -s tests +``` + +If you're working inside CVAT environment, you can also use: + +``` bash +python manage.py test datumaro/ +``` + +## Design and code structure + +- [Design document](docs/design.md) + +### Command-line + +Use [Docker](https://www.docker.com/) as an example. Basically, +the interface is divided on contexts and single commands. +Contexts are semantically grouped commands, +related to a single topic or target. Single commands are handy shorter +alternatives for the most used commands and also special commands, +which are hard to be put into any specific context. + +![cli-design-image](docs/images/cli_design.png) + +- The diagram above was created with [FreeMind](http://freemind.sourceforge.net/wiki/index.php/Main_Page) + +Model-View-ViewModel (MVVM) UI pattern is used. + +![mvvm-image](docs/images/mvvm.png) + +### Datumaro project and environment structure + + +``` +├── [datumaro module] +└── [project folder] + ├── .datumaro/ + | ├── config.yml + │   ├── .git/ + │   ├── models/ + │   └── plugins/ + │   ├── plugin1/ + │   | ├── file1.py + │   | └── file2.py + │   ├── plugin2.py + │   ├── custom_extractor1.py + │   └── ... + ├── dataset/ + └── sources/ + ├── source1 + └── ... +``` + + +### Plugins + +Plugins are optional components, which extend the project. In Datumaro there are +several types of plugins, which include: +- `extractor` - produces dataset items from data source +- `importer` - recognizes dataset type and creates project +- `converter` - exports dataset to a specific format +- `transformation` - modifies dataset items or other properties +- `launcher` - executes models + +Plugins reside in plugin directories: +- `datumaro/plugins` for builtin components +- `/.datumaro/plugins` for project-specific components + +A plugin is a python file or package with any name, which exports some symbols. +To export a symbol put it to `exports` list of the module like this: + +``` python +class MyComponent1: ... +class MyComponent2: ... +exports = [MyComponent1, MyComponent2] +``` + +or inherit it from one of special classes: +``` python +from datumaro.components.extractor import Importer, SourceExtractor, Transform +from datumaro.components.launcher import Launcher +from datumaro.components.converter import Converter +``` + +There is an additional class to modify plugin appearance at command line: + +``` python +from datumaro.components.cli_plugin import CliPlugin +``` + +Plugin example: + + + +``` +datumaro/plugins/ +- my_plugin1/file1.py +- my_plugin1/file2.py +- my_plugin2.py +``` + + + +`my_plugin1/file2.py` contents: + +``` python +from datumaro.components.extractor import Transform, CliPlugin +from .file1 import something, useful + +class MyTransform(Transform, CliPlugin): + NAME = "custom_name" + """ + Some description. + """ + @classmethod + def build_cmdline_parser(cls, **kwargs): + parser = super().build_cmdline_parser(**kwargs) + parser.add_argument('-q', help="Some help") + return parser + ... +``` + +`my_plugin2.py` contents: + +``` python +from datumaro.components.extractor import SourceExtractor + +class MyFormat: ... +class MyFormatExtractor(SourceExtractor): ... + +exports = [MyFormat] # explicit exports declaration +# MyFormatExtractor won't be exported +``` diff --git a/datumaro/README.md b/datumaro/README.md new file mode 100644 index 00000000000..f7f1c46ec26 --- /dev/null +++ b/datumaro/README.md @@ -0,0 +1,176 @@ +# Dataset Framework (Datumaro) + +A framework to build, transform, and analyze datasets. + + +``` +CVAT annotations -- ---> Annotation tool +... \ / +COCO-like dataset -----> Datumaro ---> dataset ------> Model training +... / \ +VOC-like dataset -- ---> Publication etc. +``` + + +## Contents + +- [Documentation](#documentation) +- [Features](#features) +- [Installation](#installation) +- [Usage](#usage) +- [Examples](#examples) +- [Contributing](#contributing) + +## Documentation + +- [User manual](docs/user_manual.md) +- [Design document](docs/design.md) +- [Contributing](CONTRIBUTING.md) + +## Features + +- Dataset format conversions: + - COCO (`image_info`, `instances`, `person_keypoints`, `captions`, `labels`*) + - [Format specification](http://cocodataset.org/#format-data) + - `labels` are our extension - like `instances` with only `category_id` + - PASCAL VOC (`classification`, `detection`, `segmentation` (class, instances), `action_classification`, `person_layout`) + - [Format specification](http://host.robots.ox.ac.uk/pascal/VOC/voc2012/htmldoc/index.html) + - YOLO (`bboxes`) + - [Format specification](https://github.com/AlexeyAB/darknet#how-to-train-pascal-voc-data) + - TF Detection API (`bboxes`, `masks`) + - Format specifications: [bboxes](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/using_your_own_dataset.md), [masks](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/instance_segmentation.md) + - CVAT + - [Format specification](https://github.com/opencv/cvat/blob/develop/cvat/apps/documentation/xml_format.md) +- Dataset building operations: + - Merging multiple datasets into one + - Dataset filtering with custom conditions, for instance: + - remove all annotations except polygons of a certain class + - remove images without a specific class + - remove occluded annotations from images + - keep only vertically-oriented images + - remove small area bounding boxes from annotations + - Annotation conversions, for instance + - polygons to instance masks and vise-versa + - apply a custom colormap for mask annotations + - remap dataset labels +- Dataset comparison +- Model integration: + - Inference (OpenVINO and custom models) + - Explainable AI ([RISE algorithm](https://arxiv.org/abs/1806.07421)) + +> Check the [design document](docs/design.md) for a full list of features + +## Installation + +Optionally, create a virtual environment: + +``` bash +python -m pip install virtualenv +python -m virtualenv venv +. venv/bin/activate +``` + +Install Datumaro package: + +``` bash +pip install 'git+https://github.com/opencv/cvat#egg=datumaro&subdirectory=datumaro' +``` + +## Usage + +There are several options available: +- [A standalone command-line tool](#standalone-tool) +- [A python module](#python-module) + +### Standalone tool + + +``` + User + | + v ++------------------+ +| CVAT | ++--------v---------+ +------------------+ +--------------+ +| Datumaro module | ----> | Datumaro project | <---> | Datumaro CLI | <--- User ++------------------+ +------------------+ +--------------+ +``` + + +``` bash +datum --help +python -m datumaro --help +``` + +### Python module + +Datumaro can be used in custom scripts as a library in the following way: + +``` python +from datumaro.components.project import Project # project-related things +import datumaro.components.extractor # annotations and high-level interfaces +# etc. +project = Project.load('directory') +``` + +## Examples + + + + +- Convert [PASCAL VOC](http://host.robots.ox.ac.uk/pascal/VOC/voc2012/index.html#data) to COCO, keep only images with `cat` class presented: + ```bash + # Download VOC dataset: + # http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar + datum project import --format voc --input-path + datum project export --format coco --filter '/item[annotation/label="cat"]' + ``` + +- Convert only non-occluded annotations from a CVAT-annotated project to TFrecord: + ```bash + # export Datumaro dataset in CVAT UI, extract somewhere, go to the project dir + datum project extract --filter '/item/annotation[occluded="False"]' \ + --mode items+anno --output-dir not_occluded + datum project export --project not_occluded \ + --format tf_detection_api -- --save-images + ``` + +- Annotate COCO, extract image subset, re-annotate it in CVAT, update old dataset: + ```bash + # Download COCO dataset http://cocodataset.org/#download + # Put images to coco/images/ and annotations to coco/annotations/ + datum project import --format coco --input-path + datum project export --filter '/image[images_I_dont_like]' --format cvat \ + --output-dir reannotation + # import dataset and images to CVAT, re-annotate + # export Datumaro project, extract to 'reannotation-upd' + datum project project merge reannotation-upd + datum project export --format coco + ``` + +- Annotate instance polygons in CVAT, export as masks in COCO: + ```bash + datum project import --format cvat --input-path + datum project export --format coco -- --segmentation-mode masks + ``` + +- Apply an OpenVINO detection model to some COCO-like dataset, + then compare annotations with ground truth and visualize in TensorBoard: + ```bash + datum project import --format coco --input-path + # create model results interpretation script + datum model add mymodel openvino \ + --weights model.bin --description model.xml \ + --interpretation-script parse_results.py + datum model run --model mymodel --output-dir mymodel_inference/ + datum project diff mymodel_inference/ --format tensorboard --output-dir diff + ``` + + + + +## Contributing + +Feel free to [open an Issue](https://github.com/opencv/cvat/issues/new) if you +think something needs to be changed. You are welcome to participate in development, +development instructions are available in our [developer manual](CONTRIBUTING.md). diff --git a/datumaro/datum.py b/datumaro/datum.py new file mode 100755 index 00000000000..12c150bd167 --- /dev/null +++ b/datumaro/datum.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python +import sys + +from datumaro.cli.__main__ import main + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/datumaro/datumaro/__init__.py b/datumaro/datumaro/__init__.py new file mode 100644 index 00000000000..cd825f5686e --- /dev/null +++ b/datumaro/datumaro/__init__.py @@ -0,0 +1,4 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT diff --git a/datumaro/datumaro/__main__.py b/datumaro/datumaro/__main__.py new file mode 100644 index 00000000000..271483567e3 --- /dev/null +++ b/datumaro/datumaro/__main__.py @@ -0,0 +1,12 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import sys + +from datumaro.cli.__main__ import main + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/datumaro/datumaro/cli/__init__.py b/datumaro/datumaro/cli/__init__.py new file mode 100644 index 00000000000..cd825f5686e --- /dev/null +++ b/datumaro/datumaro/cli/__init__.py @@ -0,0 +1,4 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT diff --git a/datumaro/datumaro/cli/__main__.py b/datumaro/datumaro/cli/__main__.py new file mode 100644 index 00000000000..b68de226740 --- /dev/null +++ b/datumaro/datumaro/cli/__main__.py @@ -0,0 +1,166 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import argparse +import logging as log +import logging.handlers +import os +import sys + +from . import contexts, commands +from .util import CliException, add_subparser +from ..version import VERSION + + +_log_levels = { + 'debug': log.DEBUG, + 'info': log.INFO, + 'warning': log.WARNING, + 'error': log.ERROR, + 'critical': log.CRITICAL +} + +def loglevel(name): + return _log_levels[name] + +def _make_subcommands_help(commands, help_line_start=0): + desc = "" + for command_name, _, command_help in commands: + desc += (" %-" + str(max(0, help_line_start - 2 - 1)) + "s%s\n") % \ + (command_name, command_help) + return desc + +def make_parser(): + parser = argparse.ArgumentParser(prog="datumaro", + description="Dataset Framework", + formatter_class=argparse.RawDescriptionHelpFormatter) + + parser.add_argument('--version', action='version', version=VERSION) + parser.add_argument('--loglevel', type=loglevel, default='info', + help="Logging level (options: %s; default: %s)" % \ + (', '.join(_log_levels.keys()), "%(default)s")) + + known_contexts = [ + ('project', contexts.project, "Actions on projects (datasets)"), + ('source', contexts.source, "Actions on data sources"), + ('model', contexts.model, "Actions on models"), + ] + known_commands = [ + ('create', commands.create, "Create project"), + ('add', commands.add, "Add source to project"), + ('remove', commands.remove, "Remove source from project"), + ('export', commands.export, "Export project"), + ('explain', commands.explain, "Run Explainable AI algorithm for model"), + ] + + # Argparse doesn't support subparser groups: + # https://stackoverflow.com/questions/32017020/grouping-argparse-subparser-arguments + help_line_start = max((len(e[0]) for e in known_contexts + known_commands), + default=0) + help_line_start = max((2 + help_line_start) // 4 + 1, 6) * 4 # align to tabs + subcommands_desc = "" + if known_contexts: + subcommands_desc += "Contexts:\n" + subcommands_desc += _make_subcommands_help(known_contexts, + help_line_start) + if known_commands: + if subcommands_desc: + subcommands_desc += "\n" + subcommands_desc += "Commands:\n" + subcommands_desc += _make_subcommands_help(known_commands, + help_line_start) + if subcommands_desc: + subcommands_desc += \ + "\nRun '%s COMMAND --help' for more information on a command." % \ + parser.prog + + subcommands = parser.add_subparsers(title=subcommands_desc, + description="", help=argparse.SUPPRESS) + for command_name, command, _ in known_contexts + known_commands: + add_subparser(subcommands, command_name, command.build_parser) + + return parser + +class _LogManager: + _LOGLEVEL_ENV_NAME = '_DATUMARO_INIT_LOGLEVEL' + _BUFFER_SIZE = 1000 + _root = None + _init_handler = None + _default_handler = None + + @classmethod + def init_basic_logger(cls): + base_loglevel = os.getenv(cls._LOGLEVEL_ENV_NAME, 'info') + base_loglevel = loglevel(base_loglevel) + root = log.getLogger() + root.setLevel(base_loglevel) + + # NOTE: defer use of this handler until the logger + # is properly initialized, but keep logging enabled before this. + # Store messages obtained during initialization and print them after + # if necessary. + default_handler = log.StreamHandler() + default_handler.setFormatter( + log.Formatter('%(asctime)s %(levelname)s: %(message)s')) + + init_handler = logging.handlers.MemoryHandler(cls._BUFFER_SIZE, + target=default_handler) + root.addHandler(init_handler) + + cls._root = root + cls._init_handler = init_handler + cls._default_handler = default_handler + + @classmethod + def set_up_logger(cls, level): + log.getLogger().setLevel(level) + + if cls._init_handler: + # NOTE: Handlers are not capable of filtering with loglevel + # despite a level can be set for a handler. The level is checked + # by Logger. However, handler filters are checked at handler level. + class LevelFilter: + def __init__(self, level): + super().__init__() + self.level = level + + def filter(self, record): + return record.levelno >= self.level + filt = LevelFilter(level) + cls._default_handler.addFilter(filt) + + cls._root.removeHandler(cls._init_handler) + cls._init_handler.close() + del cls._init_handler + cls._init_handler = None + + cls._default_handler.removeFilter(filt) + + cls._root.addHandler(cls._default_handler) + +def main(args=None): + _LogManager.init_basic_logger() + + parser = make_parser() + args = parser.parse_args(args) + + _LogManager.set_up_logger(args.loglevel) + + if 'command' not in args: + parser.print_help() + return 1 + + try: + return args.command(args) + except CliException as e: + log.error(e) + return 1 + except Exception as e: + log.error(e) + raise + + +if __name__ == '__main__': + sys.exit(main()) \ No newline at end of file diff --git a/datumaro/datumaro/cli/commands/__init__.py b/datumaro/datumaro/cli/commands/__init__.py new file mode 100644 index 00000000000..7656b7ef075 --- /dev/null +++ b/datumaro/datumaro/cli/commands/__init__.py @@ -0,0 +1,6 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from . import add, create, explain, export, remove \ No newline at end of file diff --git a/datumaro/datumaro/cli/commands/add.py b/datumaro/datumaro/cli/commands/add.py new file mode 100644 index 00000000000..b2864039b69 --- /dev/null +++ b/datumaro/datumaro/cli/commands/add.py @@ -0,0 +1,8 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +# pylint: disable=unused-import + +from ..contexts.source import build_add_parser as build_parser diff --git a/datumaro/datumaro/cli/commands/create.py b/datumaro/datumaro/cli/commands/create.py new file mode 100644 index 00000000000..16f6737c657 --- /dev/null +++ b/datumaro/datumaro/cli/commands/create.py @@ -0,0 +1,8 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +# pylint: disable=unused-import + +from ..contexts.project import build_create_parser as build_parser \ No newline at end of file diff --git a/datumaro/datumaro/cli/commands/explain.py b/datumaro/datumaro/cli/commands/explain.py new file mode 100644 index 00000000000..9b5a6432d65 --- /dev/null +++ b/datumaro/datumaro/cli/commands/explain.py @@ -0,0 +1,190 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import argparse +import logging as log +import os +import os.path as osp + +from datumaro.components.project import Project +from datumaro.util.command_targets import (TargetKinds, target_selector, + ProjectTarget, SourceTarget, ImageTarget, is_project_path) +from datumaro.util.image import load_image, save_image +from ..util import MultilineFormatter +from ..util.project import load_project + + +def build_parser(parser_ctor=argparse.ArgumentParser): + parser = parser_ctor(help="Run Explainable AI algorithm", + description="Runs an explainable AI algorithm for a model.") + + parser.add_argument('-m', '--model', required=True, + help="Model to use for inference") + parser.add_argument('-t', '--target', default=None, + help="Inference target - image, source, project " + "(default: current dir)") + parser.add_argument('-o', '--output-dir', dest='save_dir', default=None, + help="Directory to save output (default: display only)") + + method_sp = parser.add_subparsers(dest='algorithm') + + rise_parser = method_sp.add_parser('rise', + description=""" + RISE: Randomized Input Sampling for + Explanation of Black-box Models algorithm|n + |n + See explanations at: https://arxiv.org/pdf/1806.07421.pdf + """, + formatter_class=MultilineFormatter) + rise_parser.add_argument('-s', '--max-samples', default=None, type=int, + help="Number of algorithm iterations (default: mask size ^ 2)") + rise_parser.add_argument('--mw', '--mask-width', + dest='mask_width', default=7, type=int, + help="Mask width (default: %(default)s)") + rise_parser.add_argument('--mh', '--mask-height', + dest='mask_height', default=7, type=int, + help="Mask height (default: %(default)s)") + rise_parser.add_argument('--prob', default=0.5, type=float, + help="Mask pixel inclusion probability (default: %(default)s)") + rise_parser.add_argument('--iou', '--iou-thresh', + dest='iou_thresh', default=0.9, type=float, + help="IoU match threshold for detections (default: %(default)s)") + rise_parser.add_argument('--nms', '--nms-iou-thresh', + dest='nms_iou_thresh', default=0.0, type=float, + help="IoU match threshold in Non-maxima suppression (default: no NMS)") + rise_parser.add_argument('--conf', '--det-conf-thresh', + dest='det_conf_thresh', default=0.0, type=float, + help="Confidence threshold for detections (default: include all)") + rise_parser.add_argument('-b', '--batch-size', default=1, type=int, + help="Inference batch size (default: %(default)s)") + rise_parser.add_argument('--progressive', action='store_true', + help="Visualize results during computations") + + parser.add_argument('-p', '--project', dest='project_dir', default='.', + help="Directory of the project to operate on (default: current dir)") + parser.set_defaults(command=explain_command) + + return parser + +def explain_command(args): + project_path = args.project_dir + if is_project_path(project_path): + project = Project.load(project_path) + else: + project = None + args.target = target_selector( + ProjectTarget(is_default=True, project=project), + SourceTarget(project=project), + ImageTarget() + )(args.target) + if args.target[0] == TargetKinds.project: + if is_project_path(args.target[1]): + args.project_dir = osp.dirname(osp.abspath(args.target[1])) + + + import cv2 + from matplotlib import cm + + project = load_project(args.project_dir) + + model = project.make_executable_model(args.model) + + if str(args.algorithm).lower() != 'rise': + raise NotImplementedError() + + from datumaro.components.algorithms.rise import RISE + rise = RISE(model, + max_samples=args.max_samples, + mask_width=args.mask_width, + mask_height=args.mask_height, + prob=args.prob, + iou_thresh=args.iou_thresh, + nms_thresh=args.nms_iou_thresh, + det_conf_thresh=args.det_conf_thresh, + batch_size=args.batch_size) + + if args.target[0] == TargetKinds.image: + image_path = args.target[1] + image = load_image(image_path) + if model.preferred_input_size() is not None: + h, w = model.preferred_input_size() + image = cv2.resize(image, (w, h)) + + log.info("Running inference explanation for '%s'" % image_path) + heatmap_iter = rise.apply(image, progressive=args.progressive) + + image = image / 255.0 + file_name = osp.splitext(osp.basename(image_path))[0] + if args.progressive: + for i, heatmaps in enumerate(heatmap_iter): + for j, heatmap in enumerate(heatmaps): + hm_painted = cm.jet(heatmap)[:, :, 2::-1] + disp = (image + hm_painted) / 2 + cv2.imshow('heatmap-%s' % j, hm_painted) + cv2.imshow(file_name + '-heatmap-%s' % j, disp) + cv2.waitKey(10) + print("Iter", i, "of", args.max_samples, end='\r') + else: + heatmaps = next(heatmap_iter) + + if args.save_dir is not None: + log.info("Saving inference heatmaps at '%s'" % args.save_dir) + os.makedirs(args.save_dir, exist_ok=True) + + for j, heatmap in enumerate(heatmaps): + save_path = osp.join(args.save_dir, + file_name + '-heatmap-%s.png' % j) + save_image(save_path, heatmap * 255.0) + else: + for j, heatmap in enumerate(heatmaps): + disp = (image + cm.jet(heatmap)[:, :, 2::-1]) / 2 + cv2.imshow(file_name + '-heatmap-%s' % j, disp) + cv2.waitKey(0) + elif args.target[0] == TargetKinds.source or \ + args.target[0] == TargetKinds.project: + if args.target[0] == TargetKinds.source: + source_name = args.target[1] + dataset = project.make_source_project(source_name).make_dataset() + log.info("Running inference explanation for '%s'" % source_name) + else: + project_name = project.config.project_name + dataset = project.make_dataset() + log.info("Running inference explanation for '%s'" % project_name) + + for item in dataset: + image = item.image + if image is None: + log.warn( + "Dataset item %s does not have image data. Skipping." % \ + (item.id)) + continue + + if model.preferred_input_size() is not None: + h, w = model.preferred_input_size() + image = cv2.resize(image, (w, h)) + heatmap_iter = rise.apply(image) + + image = image / 255.0 + file_name = osp.splitext(osp.basename(image_path))[0] + heatmaps = next(heatmap_iter) + + if args.save_dir is not None: + log.info("Saving inference heatmaps at '%s'" % args.save_dir) + os.makedirs(args.save_dir, exist_ok=True) + + for j, heatmap in enumerate(heatmaps): + save_path = osp.join(args.save_dir, + file_name + '-heatmap-%s.png' % j) + save_image(save_path, heatmap * 255.0) + + if args.progressive: + for j, heatmap in enumerate(heatmaps): + disp = (image + cm.jet(heatmap)[:, :, 2::-1]) / 2 + cv2.imshow(file_name + '-heatmap-%s' % j, disp) + cv2.waitKey(0) + else: + raise NotImplementedError() + + return 0 diff --git a/datumaro/datumaro/cli/commands/export.py b/datumaro/datumaro/cli/commands/export.py new file mode 100644 index 00000000000..afeb73cddd8 --- /dev/null +++ b/datumaro/datumaro/cli/commands/export.py @@ -0,0 +1,8 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +# pylint: disable=unused-import + +from ..contexts.project import build_export_parser as build_parser \ No newline at end of file diff --git a/datumaro/datumaro/cli/commands/remove.py b/datumaro/datumaro/cli/commands/remove.py new file mode 100644 index 00000000000..0e0d076fd83 --- /dev/null +++ b/datumaro/datumaro/cli/commands/remove.py @@ -0,0 +1,8 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +# pylint: disable=unused-import + +from ..contexts.source import build_remove_parser as build_parser \ No newline at end of file diff --git a/datumaro/datumaro/cli/contexts/__init__.py b/datumaro/datumaro/cli/contexts/__init__.py new file mode 100644 index 00000000000..95019b7b4fb --- /dev/null +++ b/datumaro/datumaro/cli/contexts/__init__.py @@ -0,0 +1,6 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from . import project, source, model, item \ No newline at end of file diff --git a/datumaro/datumaro/cli/contexts/item/__init__.py b/datumaro/datumaro/cli/contexts/item/__init__.py new file mode 100644 index 00000000000..1df66809986 --- /dev/null +++ b/datumaro/datumaro/cli/contexts/item/__init__.py @@ -0,0 +1,36 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import argparse + +from ...util import add_subparser + + +def build_export_parser(parser_ctor=argparse.ArgumentParser): + parser = parser_ctor() + return parser + +def build_stats_parser(parser_ctor=argparse.ArgumentParser): + parser = parser_ctor() + return parser + +def build_diff_parser(parser_ctor=argparse.ArgumentParser): + parser = parser_ctor() + return parser + +def build_edit_parser(parser_ctor=argparse.ArgumentParser): + parser = parser_ctor() + return parser + +def build_parser(parser_ctor=argparse.ArgumentParser): + parser = parser_ctor() + + subparsers = parser.add_subparsers() + add_subparser(subparsers, 'export', build_export_parser) + add_subparser(subparsers, 'stats', build_stats_parser) + add_subparser(subparsers, 'diff', build_diff_parser) + add_subparser(subparsers, 'edit', build_edit_parser) + + return parser diff --git a/datumaro/datumaro/cli/contexts/model/__init__.py b/datumaro/datumaro/cli/contexts/model/__init__.py new file mode 100644 index 00000000000..5f40bd38057 --- /dev/null +++ b/datumaro/datumaro/cli/contexts/model/__init__.py @@ -0,0 +1,152 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import argparse +import logging as log +import os +import os.path as osp +import shutil + +from datumaro.components.config import DEFAULT_FORMAT +from ...util import add_subparser +from ...util.project import load_project + + +def build_openvino_add_parser(parser=argparse.ArgumentParser()): + parser.add_argument('-d', '--description', required=True, + help="Path to the model description file (.xml)") + parser.add_argument('-w', '--weights', required=True, + help="Path to the model weights file (.bin)") + parser.add_argument('-i', '--interpretation-script', required=True, + help="Path to the network output interpretation script (.py)") + parser.add_argument('--plugins-path', default=None, + help="Path to the custom Inference Engine plugins directory") + parser.add_argument('--copy', action='store_true', + help="Copy the model data to the project") + + return parser + +def openvino_args_extractor(args): + my_args = argparse.Namespace() + my_args.description = args.description + my_args.weights = args.weights + my_args.interpretation_script = args.interpretation_script + my_args.plugins_path = args.plugins_path + return my_args + +def build_add_parser(parser_ctor=argparse.ArgumentParser): + parser = parser_ctor() + + parser.add_argument('name', + help="Name of the model to be added") + launchers_sp = parser.add_subparsers(dest='launcher') + + build_openvino_add_parser(launchers_sp.add_parser('openvino')) \ + .set_defaults(launcher_args_extractor=openvino_args_extractor) + + parser.add_argument('-p', '--project', dest='project_dir', default='.', + help="Directory of the project to operate on (default: current dir)") + parser.set_defaults(command=add_command) + + return parser + +def add_command(args): + project = load_project(args.project_dir) + + log.info("Adding '%s' model to '%s' project" % \ + (args.launcher, project.config.project_name)) + + options = args.launcher_args_extractor(args) + + if args.launcher == 'openvino' and args.copy: + config = project.config + env_config = project.env.config + + model_dir_rel = osp.join( + config.env_dir, env_config.models_dir, args.name) + model_dir = osp.join( + config.project_dir, model_dir_rel) + + os.makedirs(model_dir, exist_ok=True) + + shutil.copy(options.description, + osp.join(model_dir, osp.basename(options.description))) + options.description = \ + osp.join(model_dir_rel, osp.basename(options.description)) + + shutil.copy(options.weights, + osp.join(model_dir, osp.basename(options.weights))) + options.weights = \ + osp.join(model_dir_rel, osp.basename(options.weights)) + + shutil.copy(options.interpretation_script, + osp.join(model_dir, osp.basename(options.interpretation_script))) + options.interpretation_script = \ + osp.join(model_dir_rel, osp.basename(options.interpretation_script)) + + project.add_model(args.name, { + 'launcher': args.launcher, + 'options': vars(options), + }) + + project.save() + + return 0 + +def build_remove_parser(parser_ctor=argparse.ArgumentParser): + parser = parser_ctor() + + parser.add_argument('name', + help="Name of the model to be removed") + parser.add_argument('-p', '--project', dest='project_dir', default='.', + help="Directory of the project to operate on (default: current dir)") + parser.set_defaults(command=remove_command) + + return parser + +def remove_command(args): + project = load_project(args.project_dir) + + project.remove_model(args.name) + project.save() + + return 0 + +def build_run_parser(parser_ctor=argparse.ArgumentParser): + parser = parser_ctor() + + parser.add_argument('-o', '--output-dir', dest='dst_dir', required=True, + help="Directory to save output") + parser.add_argument('-m', '--model', dest='model_name', required=True, + help="Model to apply to the project") + parser.add_argument('-p', '--project', dest='project_dir', default='.', + help="Directory of the project to operate on (default: current dir)") + parser.set_defaults(command=run_command) + + return parser + +def run_command(args): + project = load_project(args.project_dir) + + dst_dir = osp.abspath(args.dst_dir) + os.makedirs(dst_dir, exist_ok=False) + project.make_dataset().apply_model( + save_dir=dst_dir, + model_name=args.model_name) + + log.info("Inference results have been saved to '%s'" % dst_dir) + + return 0 + + +def build_parser(parser_ctor=argparse.ArgumentParser): + parser = parser_ctor() + + subparsers = parser.add_subparsers() + add_subparser(subparsers, 'add', build_add_parser) + add_subparser(subparsers, 'remove', build_remove_parser) + add_subparser(subparsers, 'run', build_run_parser) + + return parser diff --git a/datumaro/datumaro/cli/contexts/project/__init__.py b/datumaro/datumaro/cli/contexts/project/__init__.py new file mode 100644 index 00000000000..61bb77ad433 --- /dev/null +++ b/datumaro/datumaro/cli/contexts/project/__init__.py @@ -0,0 +1,691 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import argparse +from enum import Enum +import logging as log +import os +import os.path as osp +import shutil + +from datumaro.components.project import Project, Environment, \ + PROJECT_DEFAULT_CONFIG as DEFAULT_CONFIG +from datumaro.components.comparator import Comparator +from datumaro.components.dataset_filter import DatasetItemEncoder +from datumaro.components.extractor import AnnotationType +from datumaro.components.cli_plugin import CliPlugin +from .diff import DiffVisualizer +from ...util import add_subparser, CliException, MultilineFormatter, \ + make_file_name +from ...util.project import load_project, generate_next_dir_name + + +def build_create_parser(parser_ctor=argparse.ArgumentParser): + parser = parser_ctor(help="Create empty project", + description=""" + Create a new empty project.|n + |n + Examples:|n + - Create a project in the current directory:|n + |s|screate -n myproject|n + |n + - Create a project in other directory:|n + |s|screate -o path/I/like/ + """, + formatter_class=MultilineFormatter) + + parser.add_argument('-o', '--output-dir', default='.', dest='dst_dir', + help="Save directory for the new project (default: current dir") + parser.add_argument('-n', '--name', default=None, + help="Name of the new project (default: same as project dir)") + parser.add_argument('--overwrite', action='store_true', + help="Overwrite existing files in the save directory") + parser.set_defaults(command=create_command) + + return parser + +def create_command(args): + project_dir = osp.abspath(args.dst_dir) + + project_env_dir = osp.join(project_dir, DEFAULT_CONFIG.env_dir) + if osp.isdir(project_env_dir) and os.listdir(project_env_dir): + if not args.overwrite: + raise CliException("Directory '%s' already exists " + "(pass --overwrite to force creation)" % project_env_dir) + else: + shutil.rmtree(project_env_dir, ignore_errors=True) + + own_dataset_dir = osp.join(project_dir, DEFAULT_CONFIG.dataset_dir) + if osp.isdir(own_dataset_dir) and os.listdir(own_dataset_dir): + if not args.overwrite: + raise CliException("Directory '%s' already exists " + "(pass --overwrite to force creation)" % own_dataset_dir) + else: + # NOTE: remove the dir to avoid using data from previous project + shutil.rmtree(own_dataset_dir) + + project_name = args.name + if project_name is None: + project_name = osp.basename(project_dir) + + log.info("Creating project at '%s'" % project_dir) + + Project.generate(project_dir, { + 'project_name': project_name, + }) + + log.info("Project has been created at '%s'" % project_dir) + + return 0 + +def build_import_parser(parser_ctor=argparse.ArgumentParser): + builtins = sorted(Environment().importers.items) + + parser = parser_ctor(help="Create project from existing dataset", + description=""" + Creates a project from an existing dataset. The source can be:|n + - a dataset in a supported format (check 'formats' section below)|n + - a Datumaro project|n + |n + Formats:|n + Datasets come in a wide variety of formats. Each dataset + format defines its own data structure and rules on how to + interpret the data. For example, the following data structure + is used in COCO format:|n + /dataset/|n + - /images/.jpg|n + - /annotations/|n + |n + In Datumaro dataset formats are supported by + Extractor-s and Importer-s. + An Extractor produces a list of dataset items corresponding + to the dataset. An Importer creates a project from the + data source location. + It is possible to add a custom Extractor and Importer. + To do this, you need to put an Extractor and + Importer implementation scripts to + /.datumaro/extractors + and /.datumaro/importers.|n + |n + List of builtin dataset formats: %s|n + |n + Examples:|n + - Create a project from VOC dataset in the current directory:|n + |s|simport -f voc -i path/to/voc|n + |n + - Create a project from COCO dataset in other directory:|n + |s|simport -f coco -i path/to/coco -o path/I/like/ + """ % ', '.join(builtins), + formatter_class=MultilineFormatter) + + parser.add_argument('-o', '--output-dir', default='.', dest='dst_dir', + help="Directory to save the new project to (default: current dir)") + parser.add_argument('-n', '--name', default=None, + help="Name of the new project (default: same as project dir)") + parser.add_argument('--copy', action='store_true', + help="Copy the dataset instead of saving source links") + parser.add_argument('--skip-check', action='store_true', + help="Skip source checking") + parser.add_argument('--overwrite', action='store_true', + help="Overwrite existing files in the save directory") + parser.add_argument('-i', '--input-path', required=True, dest='source', + help="Path to import project from") + parser.add_argument('-f', '--format', required=True, + help="Source project format") + parser.add_argument('extra_args', nargs=argparse.REMAINDER, + help="Additional arguments for importer (pass '-- -h' for help)") + parser.set_defaults(command=import_command) + + return parser + +def import_command(args): + project_dir = osp.abspath(args.dst_dir) + + project_env_dir = osp.join(project_dir, DEFAULT_CONFIG.env_dir) + if osp.isdir(project_env_dir) and os.listdir(project_env_dir): + if not args.overwrite: + raise CliException("Directory '%s' already exists " + "(pass --overwrite to force creation)" % project_env_dir) + else: + shutil.rmtree(project_env_dir, ignore_errors=True) + + own_dataset_dir = osp.join(project_dir, DEFAULT_CONFIG.dataset_dir) + if osp.isdir(own_dataset_dir) and os.listdir(own_dataset_dir): + if not args.overwrite: + raise CliException("Directory '%s' already exists " + "(pass --overwrite to force creation)" % own_dataset_dir) + else: + # NOTE: remove the dir to avoid using data from previous project + shutil.rmtree(own_dataset_dir) + + project_name = args.name + if project_name is None: + project_name = osp.basename(project_dir) + + try: + env = Environment() + importer = env.make_importer(args.format) + except KeyError: + raise CliException("Importer for format '%s' is not found" % \ + args.format) + + extra_args = {} + if hasattr(importer, 'from_cmdline'): + extra_args = importer.from_cmdline(args.extra_args) + + log.info("Importing project from '%s' as '%s'" % \ + (args.source, args.format)) + + source = osp.abspath(args.source) + project = importer(source, **extra_args) + project.config.project_name = project_name + project.config.project_dir = project_dir + + if not args.skip_check or args.copy: + log.info("Checking the dataset...") + dataset = project.make_dataset() + if args.copy: + log.info("Cloning data...") + dataset.save(merge=True, save_images=True) + else: + project.save() + + log.info("Project has been created at '%s'" % project_dir) + + return 0 + + +class FilterModes(Enum): + # primary + items = 1 + annotations = 2 + items_annotations = 3 + + # shortcuts + i = 1 + a = 2 + i_a = 3 + a_i = 3 + annotations_items = 3 + + @staticmethod + def parse(s): + s = s.lower() + s = s.replace('+', '_') + return FilterModes[s] + + @classmethod + def make_filter_args(cls, mode): + if mode == cls.items: + return {} + elif mode == cls.annotations: + return { + 'filter_annotations': True + } + elif mode == cls.items_annotations: + return { + 'filter_annotations': True, + 'remove_empty': True, + } + else: + raise NotImplementedError() + + @classmethod + def list_options(cls): + return [m.name.replace('_', '+') for m in cls] + +def build_export_parser(parser_ctor=argparse.ArgumentParser): + builtins = sorted(Environment().converters.items) + + parser = parser_ctor(help="Export project", + description=""" + Exports the project dataset in some format. Optionally, a filter + can be passed, check 'extract' command description for more info. + Each dataset format has its own options, which + are passed after '--' separator (see examples), pass '-- -h' + for more info. If not stated otherwise, by default + only annotations are exported, to include images pass + '--save-images' parameter.|n + |n + Formats:|n + In Datumaro dataset formats are supported by Converter-s. + A Converter produces a dataset of a specific format + from dataset items. It is possible to add a custom Converter. + To do this, you need to put a Converter + definition script to /.datumaro/converters.|n + |n + List of builtin dataset formats: %s|n + |n + Examples:|n + - Export project as a VOC-like dataset, include images:|n + |s|sexport -f voc -- --save-images|n + |n + - Export project as a COCO-like dataset in other directory:|n + |s|sexport -f coco -o path/I/like/ + """ % ', '.join(builtins), + formatter_class=MultilineFormatter) + + parser.add_argument('-e', '--filter', default=None, + help="Filter expression for dataset items") + parser.add_argument('--filter-mode', default=FilterModes.i.name, + type=FilterModes.parse, + help="Filter mode (options: %s; default: %s)" % \ + (', '.join(FilterModes.list_options()) , '%(default)s')) + parser.add_argument('-o', '--output-dir', dest='dst_dir', default=None, + help="Directory to save output (default: a subdir in the current one)") + parser.add_argument('--overwrite', action='store_true', + help="Overwrite existing files in the save directory") + parser.add_argument('-p', '--project', dest='project_dir', default='.', + help="Directory of the project to operate on (default: current dir)") + parser.add_argument('-f', '--format', required=True, + help="Output format") + parser.add_argument('extra_args', nargs=argparse.REMAINDER, default=None, + help="Additional arguments for converter (pass '-- -h' for help)") + parser.set_defaults(command=export_command) + + return parser + +def export_command(args): + project = load_project(args.project_dir) + + dst_dir = args.dst_dir + if dst_dir: + if not args.overwrite and osp.isdir(dst_dir) and os.listdir(dst_dir): + raise CliException("Directory '%s' already exists " + "(pass --overwrite to force creation)" % dst_dir) + else: + dst_dir = generate_next_dir_name('%s-%s' % \ + (project.config.project_name, make_file_name(args.format))) + dst_dir = osp.abspath(dst_dir) + + try: + converter = project.env.converters.get(args.format) + except KeyError: + raise CliException("Converter for format '%s' is not found" % \ + args.format) + + if hasattr(converter, 'from_cmdline'): + extra_args = converter.from_cmdline(args.extra_args) + converter = converter(**extra_args) + + filter_args = FilterModes.make_filter_args(args.filter_mode) + + log.info("Loading the project...") + dataset = project.make_dataset() + + log.info("Exporting the project...") + dataset.export_project( + save_dir=dst_dir, + converter=converter, + filter_expr=args.filter, + **filter_args) + log.info("Project exported to '%s' as '%s'" % \ + (dst_dir, args.format)) + + return 0 + +def build_extract_parser(parser_ctor=argparse.ArgumentParser): + parser = parser_ctor(help="Extract subproject", + description=""" + Extracts a subproject that contains only items matching filter. + A filter is an XPath expression, which is applied to XML + representation of a dataset item. Check '--dry-run' parameter + to see XML representations of the dataset items.|n + |n + To filter annotations use the mode ('-m') parameter.|n + Supported modes:|n + - 'i', 'items'|n + - 'a', 'annotations'|n + - 'i+a', 'a+i', 'items+annotations', 'annotations+items'|n + When filtering annotations, use the 'items+annotations' + mode to point that annotation-less dataset items should be + removed. To select an annotation, write an XPath that + returns 'annotation' elements (see examples).|n + |n + Examples:|n + - Filter images with width < height:|n + |s|sextract -e '/item[image/width < image/height]'|n + |n + - Filter images with large-area bboxes:|n + |s|sextract -e '/item[annotation/type="bbox" and + annotation/area>2000]'|n + |n + - Filter out all irrelevant annotations from items:|n + |s|sextract -m a -e '/item/annotation[label = "person"]'|n + |n + - Filter out all irrelevant annotations from items:|n + |s|sextract -m a -e '/item/annotation[label="cat" and + area > 99.5]'|n + |n + - Filter occluded annotations and items, if no annotations left:|n + |s|sextract -m i+a -e '/item/annotation[occluded="True"]' + """, + formatter_class=MultilineFormatter) + + parser.add_argument('-e', '--filter', default=None, + help="XML XPath filter expression for dataset items") + parser.add_argument('-m', '--mode', default=FilterModes.i.name, + type=FilterModes.parse, + help="Filter mode (options: %s; default: %s)" % \ + (', '.join(FilterModes.list_options()) , '%(default)s')) + parser.add_argument('--dry-run', action='store_true', + help="Print XML representations to be filtered and exit") + parser.add_argument('-o', '--output-dir', dest='dst_dir', default=None, + help="Output directory (default: update current project)") + parser.add_argument('--overwrite', action='store_true', + help="Overwrite existing files in the save directory") + parser.add_argument('-p', '--project', dest='project_dir', default='.', + help="Directory of the project to operate on (default: current dir)") + parser.set_defaults(command=extract_command) + + return parser + +def extract_command(args): + project = load_project(args.project_dir) + + if not args.dry_run: + dst_dir = args.dst_dir + if dst_dir: + if not args.overwrite and osp.isdir(dst_dir) and os.listdir(dst_dir): + raise CliException("Directory '%s' already exists " + "(pass --overwrite to force creation)" % dst_dir) + else: + dst_dir = generate_next_dir_name('%s-filter' % \ + project.config.project_name) + dst_dir = osp.abspath(dst_dir) + + dataset = project.make_dataset() + + filter_args = FilterModes.make_filter_args(args.mode) + + if args.dry_run: + dataset = dataset.extract(filter_expr=args.filter, **filter_args) + for item in dataset: + encoded_item = DatasetItemEncoder.encode(item, dataset.categories()) + xml_item = DatasetItemEncoder.to_string(encoded_item) + print(xml_item) + return 0 + + if not args.filter: + raise CliException("Expected a filter expression ('-e' argument)") + + os.makedirs(dst_dir, exist_ok=False) + dataset.extract_project(save_dir=dst_dir, filter_expr=args.filter, + **filter_args) + + log.info("Subproject has been extracted to '%s'" % dst_dir) + + return 0 + +def build_merge_parser(parser_ctor=argparse.ArgumentParser): + parser = parser_ctor(help="Merge projects", + description=""" + Updates items of the current project with items + from the other project.|n + |n + Examples:|n + - Update a project with items from other project:|n + |s|smerge -p path/to/first/project path/to/other/project + """, + formatter_class=MultilineFormatter) + + parser.add_argument('other_project_dir', + help="Directory of the project to get data updates from") + parser.add_argument('-o', '--output-dir', dest='dst_dir', default=None, + help="Output directory (default: current project's dir)") + parser.add_argument('--overwrite', action='store_true', + help="Overwrite existing files in the save directory") + parser.add_argument('-p', '--project', dest='project_dir', default='.', + help="Directory of the project to operate on (default: current dir)") + parser.set_defaults(command=merge_command) + + return parser + +def merge_command(args): + first_project = load_project(args.project_dir) + second_project = load_project(args.other_project_dir) + + dst_dir = args.dst_dir + if dst_dir: + if not args.overwrite and osp.isdir(dst_dir) and os.listdir(dst_dir): + raise CliException("Directory '%s' already exists " + "(pass --overwrite to force creation)" % dst_dir) + + first_dataset = first_project.make_dataset() + first_dataset.update(second_project.make_dataset()) + + first_dataset.save(save_dir=dst_dir) + + if dst_dir is None: + dst_dir = first_project.config.project_dir + dst_dir = osp.abspath(dst_dir) + log.info("Merge results have been saved to '%s'" % dst_dir) + + return 0 + +def build_diff_parser(parser_ctor=argparse.ArgumentParser): + parser = parser_ctor(help="Compare projects", + description=""" + Compares two projects.|n + |n + Examples:|n + - Compare two projects, consider bboxes matching if their IoU > 0.7,|n + |s|s|s|sprint results to Tensorboard: + |s|sdiff path/to/other/project -o diff/ -f tensorboard --iou-thresh 0.7 + """, + formatter_class=MultilineFormatter) + + parser.add_argument('other_project_dir', + help="Directory of the second project to be compared") + parser.add_argument('-o', '--output-dir', dest='dst_dir', default=None, + help="Directory to save comparison results (default: do not save)") + parser.add_argument('-f', '--format', + default=DiffVisualizer.DEFAULT_FORMAT, + choices=[f.name for f in DiffVisualizer.Format], + help="Output format (default: %(default)s)") + parser.add_argument('--iou-thresh', default=0.5, type=float, + help="IoU match threshold for detections (default: %(default)s)") + parser.add_argument('--conf-thresh', default=0.5, type=float, + help="Confidence threshold for detections (default: %(default)s)") + parser.add_argument('--overwrite', action='store_true', + help="Overwrite existing files in the save directory") + parser.add_argument('-p', '--project', dest='project_dir', default='.', + help="Directory of the first project to be compared (default: current dir)") + parser.set_defaults(command=diff_command) + + return parser + +def diff_command(args): + first_project = load_project(args.project_dir) + second_project = load_project(args.other_project_dir) + + comparator = Comparator( + iou_threshold=args.iou_thresh, + conf_threshold=args.conf_thresh) + + dst_dir = args.dst_dir + if dst_dir: + if not args.overwrite and osp.isdir(dst_dir) and os.listdir(dst_dir): + raise CliException("Directory '%s' already exists " + "(pass --overwrite to force creation)" % dst_dir) + else: + dst_dir = generate_next_dir_name('%s-%s-diff' % ( + first_project.config.project_name, + second_project.config.project_name) + ) + dst_dir = osp.abspath(dst_dir) + log.info("Saving diff to '%s'" % dst_dir) + + visualizer = DiffVisualizer(save_dir=dst_dir, comparator=comparator, + output_format=args.format) + visualizer.save_dataset_diff( + first_project.make_dataset(), + second_project.make_dataset()) + + return 0 + +def build_transform_parser(parser_ctor=argparse.ArgumentParser): + builtins = sorted(Environment().transforms.items) + + parser = parser_ctor(help="Transform project", + description=""" + Applies some operation to dataset items in the project + and produces a new project.|n + |n + Builtin transforms: %s|n + |n + Examples:|n + - Convert instance polygons to masks:|n + |s|stransform -n polygons_to_masks + """ % ', '.join(builtins), + formatter_class=MultilineFormatter) + + parser.add_argument('-t', '--transform', required=True, + help="Transform to apply to the project") + parser.add_argument('-o', '--output-dir', dest='dst_dir', default=None, + help="Directory to save output (default: current dir)") + parser.add_argument('--overwrite', action='store_true', + help="Overwrite existing files in the save directory") + parser.add_argument('-p', '--project', dest='project_dir', default='.', + help="Directory of the project to operate on (default: current dir)") + parser.add_argument('extra_args', nargs=argparse.REMAINDER, default=None, + help="Additional arguments for transformation (pass '-- -h' for help)") + parser.set_defaults(command=transform_command) + + return parser + +def transform_command(args): + project = load_project(args.project_dir) + + dst_dir = args.dst_dir + if dst_dir: + if not args.overwrite and osp.isdir(dst_dir) and os.listdir(dst_dir): + raise CliException("Directory '%s' already exists " + "(pass --overwrite to force creation)" % dst_dir) + else: + dst_dir = generate_next_dir_name('%s-%s' % \ + (project.config.project_name, make_file_name(args.transform))) + dst_dir = osp.abspath(dst_dir) + + try: + transform = project.env.transforms.get(args.transform) + except KeyError: + raise CliException("Transform '%s' is not found" % args.transform) + + extra_args = {} + if hasattr(transform, 'from_cmdline'): + extra_args = transform.from_cmdline(args.extra_args) + + log.info("Loading the project...") + dataset = project.make_dataset() + + log.info("Transforming the project...") + dataset.transform_project( + method=transform, + save_dir=dst_dir, + **extra_args + ) + + log.info("Transform results have been saved to '%s'" % dst_dir) + + return 0 + +def build_info_parser(parser_ctor=argparse.ArgumentParser): + parser = parser_ctor(help="Get project info", + description=""" + Outputs project info. + """, + formatter_class=MultilineFormatter) + + parser.add_argument('--all', action='store_true', + help="Print all information") + parser.add_argument('-p', '--project', dest='project_dir', default='.', + help="Directory of the project to operate on (default: current dir)") + parser.set_defaults(command=info_command) + + return parser + +def info_command(args): + project = load_project(args.project_dir) + config = project.config + env = project.env + dataset = project.make_dataset() + + print("Project:") + print(" name:", config.project_name) + print(" location:", config.project_dir) + print("Plugins:") + print(" importers:", ', '.join(env.importers.items)) + print(" extractors:", ', '.join(env.extractors.items)) + print(" converters:", ', '.join(env.converters.items)) + print(" launchers:", ', '.join(env.launchers.items)) + + print("Sources:") + for source_name, source in config.sources.items(): + print(" source '%s':" % source_name) + print(" format:", source.format) + print(" url:", source.url) + print(" location:", project.local_source_dir(source_name)) + + def print_extractor_info(extractor, indent=''): + print("%slength:" % indent, len(extractor)) + + categories = extractor.categories() + print("%scategories:" % indent, ', '.join(c.name for c in categories)) + + for cat_type, cat in categories.items(): + print("%s %s:" % (indent, cat_type.name)) + if cat_type == AnnotationType.label: + print("%s count:" % indent, len(cat.items)) + + count_threshold = 10 + if args.all: + count_threshold = len(cat.items) + labels = ', '.join(c.name for c in cat.items[:count_threshold]) + if count_threshold < len(cat.items): + labels += " (and %s more)" % ( + len(cat.items) - count_threshold) + print("%s labels:" % indent, labels) + + print("Dataset:") + print_extractor_info(dataset, indent=" ") + + subsets = dataset.subsets() + print(" subsets:", ', '.join(subsets)) + for subset_name in subsets: + subset = dataset.get_subset(subset_name) + print(" subset '%s':" % subset_name) + print_extractor_info(subset, indent=" ") + + print("Models:") + for model_name, model in config.models.items(): + print(" model '%s':" % model_name) + print(" type:", model.launcher) + + return 0 + + +def build_parser(parser_ctor=argparse.ArgumentParser): + parser = parser_ctor( + description=""" + Manipulate projects.|n + |n + By default, the project to be operated on is searched for + in the current directory. An additional '-p' argument can be + passed to specify project location. + """, + formatter_class=MultilineFormatter) + + subparsers = parser.add_subparsers() + add_subparser(subparsers, 'create', build_create_parser) + add_subparser(subparsers, 'import', build_import_parser) + add_subparser(subparsers, 'export', build_export_parser) + add_subparser(subparsers, 'extract', build_extract_parser) + add_subparser(subparsers, 'merge', build_merge_parser) + add_subparser(subparsers, 'diff', build_diff_parser) + add_subparser(subparsers, 'transform', build_transform_parser) + add_subparser(subparsers, 'info', build_info_parser) + + return parser diff --git a/datumaro/datumaro/cli/contexts/project/diff.py b/datumaro/datumaro/cli/contexts/project/diff.py new file mode 100644 index 00000000000..78fdcd51a1f --- /dev/null +++ b/datumaro/datumaro/cli/contexts/project/diff.py @@ -0,0 +1,281 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from collections import Counter +from enum import Enum +import numpy as np +import os +import os.path as osp + +_formats = ['simple'] + +import warnings +with warnings.catch_warnings(): + warnings.simplefilter("ignore") + import tensorboardX as tb + _formats.append('tensorboard') + +from datumaro.components.extractor import AnnotationType +from datumaro.util.image import save_image + + +Format = Enum('Formats', _formats) + +class DiffVisualizer: + Format = Format + DEFAULT_FORMAT = Format.simple + + _UNMATCHED_LABEL = -1 + + + def __init__(self, comparator, save_dir, output_format=DEFAULT_FORMAT): + self.comparator = comparator + + if isinstance(output_format, str): + output_format = Format[output_format] + assert output_format in Format + self.output_format = output_format + + self.save_dir = save_dir + if output_format is Format.tensorboard: + logdir = osp.join(self.save_dir, 'logs', 'diff') + self.file_writer = tb.SummaryWriter(logdir) + if output_format is Format.simple: + self.label_diff_writer = None + + self.categories = {} + + self.label_confusion_matrix = Counter() + self.bbox_confusion_matrix = Counter() + + def save_dataset_diff(self, extractor_a, extractor_b): + if self.save_dir: + os.makedirs(self.save_dir, exist_ok=True) + + if len(extractor_a) != len(extractor_b): + print("Datasets have different lengths: %s vs %s" % \ + (len(extractor_a), len(extractor_b))) + + self.categories = {} + + label_mismatch = self.comparator. \ + compare_dataset_labels(extractor_a, extractor_b) + if label_mismatch is None: + print("Datasets have no label information") + elif len(label_mismatch) != 0: + print("Datasets have mismatching labels:") + for a_label, b_label in label_mismatch: + if a_label is None: + print(" > %s" % b_label.name) + elif b_label is None: + print(" < %s" % a_label.name) + else: + print(" %s != %s" % (a_label.name, b_label.name)) + else: + self.categories.update(extractor_a.categories()) + self.categories.update(extractor_b.categories()) + + self.label_confusion_matrix = Counter() + self.bbox_confusion_matrix = Counter() + + if self.output_format is Format.tensorboard: + self.file_writer.reopen() + + for i, (item_a, item_b) in enumerate(zip(extractor_a, extractor_b)): + if item_a.id != item_b.id or not item_a.id or not item_b.id: + print("Dataset items #%s '%s' '%s' do not match" % \ + (i + 1, item_a.id, item_b.id)) + continue + + label_diff = self.comparator.compare_item_labels(item_a, item_b) + self.update_label_confusion(label_diff) + + bbox_diff = self.comparator.compare_item_bboxes(item_a, item_b) + self.update_bbox_confusion(bbox_diff) + + self.save_item_label_diff(item_a, item_b, label_diff) + self.save_item_bbox_diff(item_a, item_b, bbox_diff) + + if len(self.label_confusion_matrix) != 0: + self.save_conf_matrix(self.label_confusion_matrix, + 'labels_confusion.png') + if len(self.bbox_confusion_matrix) != 0: + self.save_conf_matrix(self.bbox_confusion_matrix, + 'bbox_confusion.png') + + if self.output_format is Format.tensorboard: + self.file_writer.flush() + self.file_writer.close() + elif self.output_format is Format.simple: + if self.label_diff_writer: + self.label_diff_writer.flush() + self.label_diff_writer.close() + + def update_label_confusion(self, label_diff): + matches, a_unmatched, b_unmatched = label_diff + for label in matches: + self.label_confusion_matrix[(label, label)] += 1 + for a_label in a_unmatched: + self.label_confusion_matrix[(a_label, self._UNMATCHED_LABEL)] += 1 + for b_label in b_unmatched: + self.label_confusion_matrix[(self._UNMATCHED_LABEL, b_label)] += 1 + + def update_bbox_confusion(self, bbox_diff): + matches, mispred, a_unmatched, b_unmatched = bbox_diff + for a_bbox, b_bbox in matches: + self.bbox_confusion_matrix[(a_bbox.label, b_bbox.label)] += 1 + for a_bbox, b_bbox in mispred: + self.bbox_confusion_matrix[(a_bbox.label, b_bbox.label)] += 1 + for a_bbox in a_unmatched: + self.bbox_confusion_matrix[(a_bbox.label, self._UNMATCHED_LABEL)] += 1 + for b_bbox in b_unmatched: + self.bbox_confusion_matrix[(self._UNMATCHED_LABEL, b_bbox.label)] += 1 + + @classmethod + def draw_text_with_background(cls, frame, text, origin, + font=None, scale=1.0, + color=(0, 0, 0), thickness=1, bgcolor=(1, 1, 1)): + import cv2 + + if not font: + font = cv2.FONT_HERSHEY_SIMPLEX + + text_size, baseline = cv2.getTextSize(text, font, scale, thickness) + cv2.rectangle(frame, + tuple((origin + (0, baseline)).astype(int)), + tuple((origin + (text_size[0], -text_size[1])).astype(int)), + bgcolor, cv2.FILLED) + cv2.putText(frame, text, + tuple(origin.astype(int)), + font, scale, color, thickness) + return text_size, baseline + + def draw_detection_roi(self, frame, x, y, w, h, label, conf, color): + import cv2 + + cv2.rectangle(frame, (x, y), (x + w, y + h), color, 2) + + text = '%s %.2f%%' % (label, 100.0 * conf) + text_scale = 0.5 + font = cv2.FONT_HERSHEY_SIMPLEX + text_size = cv2.getTextSize(text, font, text_scale, 1) + line_height = np.array([0, text_size[0][1]]) + self.draw_text_with_background(frame, text, + np.array([x, y]) - line_height * 0.5, + font, scale=text_scale, color=[255 - c for c in color]) + + def get_label(self, label_id): + cat = self.categories.get(AnnotationType.label) + if cat is None: + return str(label_id) + return cat.items[label_id].name + + def draw_bbox(self, img, shape, color): + x, y, w, h = shape.get_bbox() + self.draw_detection_roi(img, int(x), int(y), int(w), int(h), + self.get_label(shape.label), shape.attributes.get('score', 1), + color) + + def get_label_diff_file(self): + if self.label_diff_writer is None: + self.label_diff_writer = \ + open(osp.join(self.save_dir, 'label_diff.txt'), 'w') + return self.label_diff_writer + + def save_item_label_diff(self, item_a, item_b, diff): + _, a_unmatched, b_unmatched = diff + + if 0 < len(a_unmatched) + len(b_unmatched): + if self.output_format is Format.simple: + f = self.get_label_diff_file() + f.write(item_a.id + '\n') + for a_label in a_unmatched: + f.write(' >%s\n' % self.get_label(a_label)) + for b_label in b_unmatched: + f.write(' <%s\n' % self.get_label(b_label)) + elif self.output_format is Format.tensorboard: + tag = item_a.id + for a_label in a_unmatched: + self.file_writer.add_text(tag, + '>%s\n' % self.get_label(a_label)) + for b_label in b_unmatched: + self.file_writer.add_text(tag, + '<%s\n' % self.get_label(b_label)) + + def save_item_bbox_diff(self, item_a, item_b, diff): + _, mispred, a_unmatched, b_unmatched = diff + + if 0 < len(a_unmatched) + len(b_unmatched) + len(mispred): + img_a = item_a.image.copy() + img_b = img_a.copy() + for a_bbox, b_bbox in mispred: + self.draw_bbox(img_a, a_bbox, (0, 255, 0)) + self.draw_bbox(img_b, b_bbox, (0, 0, 255)) + for a_bbox in a_unmatched: + self.draw_bbox(img_a, a_bbox, (255, 255, 0)) + for b_bbox in b_unmatched: + self.draw_bbox(img_b, b_bbox, (255, 255, 0)) + + img = np.hstack([img_a, img_b]) + + path = osp.join(self.save_dir, 'diff_%s' % item_a.id) + + if self.output_format is Format.simple: + save_image(path + '.png', img) + elif self.output_format is Format.tensorboard: + self.save_as_tensorboard(img, path) + + def save_as_tensorboard(self, img, name): + img = img[:, :, ::-1] # to RGB + img = np.transpose(img, (2, 0, 1)) # to (C, H, W) + img = img.astype(dtype=np.uint8) + self.file_writer.add_image(name, img) + + def save_conf_matrix(self, conf_matrix, filename): + import matplotlib.pyplot as plt + + classes = None + label_categories = self.categories.get(AnnotationType.label) + if label_categories is not None: + classes = { id: c.name for id, c in enumerate(label_categories.items) } + if classes is None: + classes = { c: 'label_%s' % c for c, _ in conf_matrix } + classes[self._UNMATCHED_LABEL] = 'unmatched' + + class_idx = { id: i for i, id in enumerate(classes.keys()) } + matrix = np.zeros((len(classes), len(classes)), dtype=int) + for idx_pair in conf_matrix: + index = (class_idx[idx_pair[0]], class_idx[idx_pair[1]]) + matrix[index] = conf_matrix[idx_pair] + + labels = [label for id, label in classes.items()] + + fig = plt.figure() + fig.add_subplot(111) + table = plt.table( + cellText=matrix, + colLabels=labels, + rowLabels=labels, + loc ='center') + table.auto_set_font_size(False) + table.set_fontsize(8) + table.scale(3, 3) + # Removing ticks and spines enables you to get the figure only with table + plt.tick_params(axis='x', which='both', bottom=False, top=False, labelbottom=False) + plt.tick_params(axis='y', which='both', right=False, left=False, labelleft=False) + for pos in ['right','top','bottom','left']: + plt.gca().spines[pos].set_visible(False) + + for idx_pair in conf_matrix: + i = class_idx[idx_pair[0]] + j = class_idx[idx_pair[1]] + if conf_matrix[idx_pair] != 0: + if i != j: + table._cells[(i + 1, j)].set_facecolor('#FF0000') + else: + table._cells[(i + 1, j)].set_facecolor('#00FF00') + + plt.savefig(osp.join(self.save_dir, filename), + bbox_inches='tight', pad_inches=0.05) diff --git a/datumaro/datumaro/cli/contexts/source/__init__.py b/datumaro/datumaro/cli/contexts/source/__init__.py new file mode 100644 index 00000000000..94734265e7d --- /dev/null +++ b/datumaro/datumaro/cli/contexts/source/__init__.py @@ -0,0 +1,247 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import argparse +import logging as log +import os +import os.path as osp +import shutil + +from datumaro.components.project import Environment +from ...util import add_subparser, CliException, MultilineFormatter +from ...util.project import load_project + + +def build_add_parser(parser_ctor=argparse.ArgumentParser): + builtins = sorted(Environment().extractors.items) + + base_parser = argparse.ArgumentParser(add_help=False) + base_parser.add_argument('-n', '--name', default=None, + help="Name of the new source") + base_parser.add_argument('-f', '--format', required=True, + help="Source dataset format") + base_parser.add_argument('--skip-check', action='store_true', + help="Skip source checking") + base_parser.add_argument('-p', '--project', dest='project_dir', default='.', + help="Directory of the project to operate on (default: current dir)") + + parser = parser_ctor(help="Add data source to project", + description=""" + Adds a data source to a project. The source can be:|n + - a dataset in a supported format (check 'formats' section below)|n + - a Datumaro project|n + |n + The source can be either a local directory or a remote + git repository. Each source type has its own parameters, which can + be checked by:|n + '%s'.|n + |n + Formats:|n + Datasets come in a wide variety of formats. Each dataset + format defines its own data structure and rules on how to + interpret the data. For example, the following data structure + is used in COCO format:|n + /dataset/|n + - /images/.jpg|n + - /annotations/|n + |n + In Datumaro dataset formats are supported by Extractor-s. + An Extractor produces a list of dataset items corresponding + to the dataset. It is possible to add a custom Extractor. + To do this, you need to put an Extractor + definition script to /.datumaro/extractors.|n + |n + List of builtin source formats: %s|n + |n + Examples:|n + - Add a local directory with VOC-like dataset:|n + |s|sadd path path/to/voc -f voc_detection|n + - Add a local file with CVAT annotations, call it 'mysource'|n + |s|s|s|sto the project somewhere else:|n + |s|sadd path path/to/cvat.xml -f cvat -n mysource -p somewhere/else/ + """ % ('%(prog)s SOURCE_TYPE --help', ', '.join(builtins)), + formatter_class=MultilineFormatter, + add_help=False) + parser.set_defaults(command=add_command) + + sp = parser.add_subparsers(dest='source_type', metavar='SOURCE_TYPE', + help="The type of the data source " + "(call '%s SOURCE_TYPE --help' for more info)" % parser.prog) + + dir_parser = sp.add_parser('path', help="Add local path as source", + parents=[base_parser]) + dir_parser.add_argument('url', + help="Path to the source") + dir_parser.add_argument('--copy', action='store_true', + help="Copy the dataset instead of saving source links") + + repo_parser = sp.add_parser('git', help="Add git repository as source", + parents=[base_parser]) + repo_parser.add_argument('url', + help="URL of the source git repository") + repo_parser.add_argument('-b', '--branch', default='master', + help="Branch of the source repository (default: %(default)s)") + repo_parser.add_argument('--checkout', action='store_true', + help="Do branch checkout") + + # NOTE: add common parameters to the parent help output + # the other way could be to use parse_known_args() + display_parser = argparse.ArgumentParser( + parents=[base_parser, parser], + prog=parser.prog, usage="%(prog)s [-h] SOURCE_TYPE ...", + description=parser.description, formatter_class=MultilineFormatter) + class HelpAction(argparse._HelpAction): + def __call__(self, parser, namespace, values, option_string=None): + display_parser.print_help() + parser.exit() + + parser.add_argument('-h', '--help', action=HelpAction, + help='show this help message and exit') + + # TODO: needed distinction on how to add an extractor or a remote source + + return parser + +def add_command(args): + project = load_project(args.project_dir) + + if args.source_type == 'git': + name = args.name + if name is None: + name = osp.splitext(osp.basename(args.url))[0] + + if project.env.git.has_submodule(name): + raise CliException("Git submodule '%s' already exists" % name) + + try: + project.get_source(name) + raise CliException("Source '%s' already exists" % name) + except KeyError: + pass + + rel_local_dir = project.local_source_dir(name) + local_dir = osp.join(project.config.project_dir, rel_local_dir) + url = args.url + project.env.git.create_submodule(name, local_dir, + url=url, branch=args.branch, no_checkout=not args.checkout) + elif args.source_type == 'path': + url = osp.abspath(args.url) + if not osp.exists(url): + raise CliException("Source path '%s' does not exist" % url) + + name = args.name + if name is None: + name = osp.splitext(osp.basename(url))[0] + + if project.env.git.has_submodule(name): + raise CliException("Git submodule '%s' already exists" % name) + + try: + project.get_source(name) + raise CliException("Source '%s' already exists" % name) + except KeyError: + pass + + rel_local_dir = project.local_source_dir(name) + local_dir = osp.join(project.config.project_dir, rel_local_dir) + + if args.copy: + log.info("Copying from '%s' to '%s'" % (url, local_dir)) + if osp.isdir(url): + # copytree requires destination dir not to exist + shutil.copytree(url, local_dir) + url = rel_local_dir + elif osp.isfile(url): + os.makedirs(local_dir) + shutil.copy2(url, local_dir) + url = osp.join(rel_local_dir, osp.basename(url)) + else: + raise Exception("Expected file or directory") + else: + os.makedirs(local_dir) + + project.add_source(name, { 'url': url, 'format': args.format }) + + if not args.skip_check: + log.info("Checking the source...") + try: + project.make_source_project(name).make_dataset() + except Exception: + shutil.rmtree(local_dir, ignore_errors=True) + raise + + project.save() + + log.info("Source '%s' has been added to the project, location: '%s'" \ + % (name, rel_local_dir)) + + return 0 + +def build_remove_parser(parser_ctor=argparse.ArgumentParser): + parser = parser_ctor(help="Remove source from project", + description="Remove a source from a project.") + + parser.add_argument('-n', '--name', required=True, + help="Name of the source to be removed") + parser.add_argument('--force', action='store_true', + help="Ignore possible errors during removal") + parser.add_argument('--keep-data', action='store_true', + help="Do not remove source data") + parser.add_argument('-p', '--project', dest='project_dir', default='.', + help="Directory of the project to operate on (default: current dir)") + parser.set_defaults(command=remove_command) + + return parser + +def remove_command(args): + project = load_project(args.project_dir) + + name = args.name + if not name: + raise CliException("Expected source name") + try: + project.get_source(name) + except KeyError: + if not args.force: + raise CliException("Source '%s' does not exist" % name) + + if project.env.git.has_submodule(name): + if args.force: + log.warning("Forcefully removing the '%s' source..." % name) + + project.env.git.remove_submodule(name, force=args.force) + + source_dir = osp.join(project.config.project_dir, + project.local_source_dir(name)) + project.remove_source(name) + project.save() + + if not args.keep_data: + shutil.rmtree(source_dir, ignore_errors=True) + + log.info("Source '%s' has been removed from the project" % name) + + return 0 + +def build_parser(parser_ctor=argparse.ArgumentParser): + parser = parser_ctor(description=""" + Manipulate data sources inside of a project.|n + |n + A data source is a source of data for a project. + The project combines multiple data sources into one dataset. + The role of a data source is to provide dataset items - images + and/or annotations.|n + |n + By default, the project to be operated on is searched for + in the current directory. An additional '-p' argument can be + passed to specify project location. + """, + formatter_class=MultilineFormatter) + + subparsers = parser.add_subparsers() + add_subparser(subparsers, 'add', build_add_parser) + add_subparser(subparsers, 'remove', build_remove_parser) + + return parser diff --git a/datumaro/datumaro/cli/util/__init__.py b/datumaro/datumaro/cli/util/__init__.py new file mode 100644 index 00000000000..2d04a6936b7 --- /dev/null +++ b/datumaro/datumaro/cli/util/__init__.py @@ -0,0 +1,52 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import argparse +import textwrap + + +class CliException(Exception): pass + +def add_subparser(subparsers, name, builder): + return builder(lambda **kwargs: subparsers.add_parser(name, **kwargs)) + +class MultilineFormatter(argparse.HelpFormatter): + """ + Keeps line breaks introduced with '|n' separator + and spaces introduced with '|s'. + """ + + def __init__(self, keep_natural=False, **kwargs): + super().__init__(**kwargs) + self._keep_natural = keep_natural + + def _fill_text(self, text, width, indent): + text = self._whitespace_matcher.sub(' ', text).strip() + text = text.replace('|s', ' ') + + paragraphs = text.split('|n ') + if self._keep_natural: + paragraphs = sum((p.split('\n ') for p in paragraphs), []) + + multiline_text = '' + for paragraph in paragraphs: + formatted_paragraph = textwrap.fill(paragraph, width, + initial_indent=indent, subsequent_indent=indent) + '\n' + multiline_text += formatted_paragraph + return multiline_text + +def make_file_name(s): + # adapted from + # https://docs.djangoproject.com/en/2.1/_modules/django/utils/text/#slugify + """ + Normalizes string, converts to lowercase, removes non-alpha characters, + and converts spaces to hyphens. + """ + import unicodedata, re + s = unicodedata.normalize('NFKD', s).encode('ascii', 'ignore') + s = s.decode() + s = re.sub(r'[^\w\s-]', '', s).strip().lower() + s = re.sub(r'[-\s]+', '-', s) + return s \ No newline at end of file diff --git a/datumaro/datumaro/cli/util/project.py b/datumaro/datumaro/cli/util/project.py new file mode 100644 index 00000000000..af92458bcd1 --- /dev/null +++ b/datumaro/datumaro/cli/util/project.py @@ -0,0 +1,34 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import os + +from datumaro.components.project import Project + + +def load_project(project_dir): + return Project.load(project_dir) + +def generate_next_dir_name(dirname, basedir='.', sep='.'): + """ + If basedir does not contain dirname, returns dirname itself, + else generates a dirname by appending separator to the dirname + and the number, next to the last used number in the basedir for + files with dirname prefix. + """ + + def _to_int(s): + try: + return int(s) + except Exception: + return 0 + sep_count = dirname.count(sep) + 2 + + files = [e for e in os.listdir(basedir) if e.startswith(dirname)] + if files: + files = [e.split(sep) for e in files] + files = [_to_int(e[-1]) for e in files if len(e) == sep_count] + dirname += '%s%s' % (sep, max(files, default=0) + 1) + return dirname \ No newline at end of file diff --git a/datumaro/datumaro/components/__init__.py b/datumaro/datumaro/components/__init__.py new file mode 100644 index 00000000000..a9773073830 --- /dev/null +++ b/datumaro/datumaro/components/__init__.py @@ -0,0 +1,5 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + diff --git a/datumaro/datumaro/components/algorithms/__init__.py b/datumaro/datumaro/components/algorithms/__init__.py new file mode 100644 index 00000000000..a9773073830 --- /dev/null +++ b/datumaro/datumaro/components/algorithms/__init__.py @@ -0,0 +1,5 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + diff --git a/datumaro/datumaro/components/algorithms/rise.py b/datumaro/datumaro/components/algorithms/rise.py new file mode 100644 index 00000000000..277bedd2d3b --- /dev/null +++ b/datumaro/datumaro/components/algorithms/rise.py @@ -0,0 +1,220 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +# pylint: disable=unused-variable + +import numpy as np +from math import ceil + +from datumaro.components.extractor import AnnotationType + + +def flatmatvec(mat): + return np.reshape(mat, (len(mat), -1)) + +def expand(array, axis=None): + if axis is None: + axis = len(array.shape) + return np.expand_dims(array, axis=axis) + +class RISE: + """ + Implements RISE: Randomized Input Sampling for + Explanation of Black-box Models algorithm + See explanations at: https://arxiv.org/pdf/1806.07421.pdf + """ + + def __init__(self, model, + max_samples=None, mask_width=7, mask_height=7, prob=0.5, + iou_thresh=0.9, nms_thresh=0.0, det_conf_thresh=0.0, + batch_size=1): + self.model = model + self.max_samples = max_samples + self.mask_height = mask_height + self.mask_width = mask_width + self.prob = prob + self.iou_thresh = iou_thresh + self.nms_thresh = nms_thresh + self.det_conf_thresh = det_conf_thresh + self.batch_size = batch_size + + @staticmethod + def split_outputs(annotations): + labels = [] + bboxes = [] + for r in annotations: + if r.type is AnnotationType.label: + labels.append(r) + elif r.type is AnnotationType.bbox: + bboxes.append(r) + return labels, bboxes + + @staticmethod + def nms(boxes, iou_thresh=0.5): + indices = np.argsort([b.attributes['score'] for b in boxes]) + ious = np.array([[a.iou(b) for b in boxes] for a in boxes]) + + predictions = [] + while len(indices) != 0: + i = len(indices) - 1 + pred_idx = indices[i] + to_remove = [i] + predictions.append(boxes[pred_idx]) + for i, box_idx in enumerate(indices[:i]): + if iou_thresh < ious[pred_idx, box_idx]: + to_remove.append(i) + indices = np.delete(indices, to_remove) + + return predictions + + def normalize_hmaps(self, heatmaps, counts): + eps = np.finfo(heatmaps.dtype).eps + mhmaps = flatmatvec(heatmaps) + mhmaps /= expand(counts * self.prob + eps) + mhmaps -= expand(np.min(mhmaps, axis=1)) + mhmaps /= expand(np.max(mhmaps, axis=1) + eps) + return np.reshape(mhmaps, heatmaps.shape) + + def apply(self, image, progressive=False): + import cv2 + + assert len(image.shape) in [2, 3], \ + "Expected an input image in (H, W, C) format" + if len(image.shape) == 3: + assert image.shape[2] in [3, 4], "Expected BGR or BGRA input" + image = image[:, :, :3].astype(np.float32) + + model = self.model + iou_thresh = self.iou_thresh + + image_size = np.array((image.shape[:2])) + mask_size = np.array((self.mask_height, self.mask_width)) + cell_size = np.ceil(image_size / mask_size) + upsampled_size = np.ceil((mask_size + 1) * cell_size) + + rng = lambda shape=None: np.random.rand(*shape) + samples = np.prod(image_size) + if self.max_samples is not None: + samples = min(self.max_samples, samples) + batch_size = self.batch_size + + result = next(iter(model.launch(expand(image, 0)))) + result_labels, result_bboxes = self.split_outputs(result) + if 0 < self.det_conf_thresh: + result_bboxes = [b for b in result_bboxes \ + if self.det_conf_thresh <= b.attributes['score']] + if 0 < self.nms_thresh: + result_bboxes = self.nms(result_bboxes, self.nms_thresh) + + predicted_labels = set() + if len(result_labels) != 0: + predicted_label = max(result_labels, + key=lambda r: r.attributes['score']).label + predicted_labels.add(predicted_label) + if len(result_bboxes) != 0: + for bbox in result_bboxes: + predicted_labels.add(bbox.label) + predicted_labels = { label: idx \ + for idx, label in enumerate(predicted_labels) } + + predicted_bboxes = result_bboxes + + heatmaps_count = len(predicted_labels) + len(predicted_bboxes) + heatmaps = np.zeros((heatmaps_count, *image_size), dtype=np.float32) + total_counts = np.zeros(heatmaps_count, dtype=np.int32) + confs = np.zeros(heatmaps_count, dtype=np.float32) + + heatmap_id = 0 + + label_heatmaps = None + label_total_counts = None + label_confs = None + if len(predicted_labels) != 0: + step = len(predicted_labels) + label_heatmaps = heatmaps[heatmap_id : heatmap_id + step] + label_total_counts = total_counts[heatmap_id : heatmap_id + step] + label_confs = confs[heatmap_id : heatmap_id + step] + heatmap_id += step + + bbox_heatmaps = None + bbox_total_counts = None + bbox_confs = None + if len(predicted_bboxes) != 0: + step = len(predicted_bboxes) + bbox_heatmaps = heatmaps[heatmap_id : heatmap_id + step] + bbox_total_counts = total_counts[heatmap_id : heatmap_id + step] + bbox_confs = confs[heatmap_id : heatmap_id + step] + heatmap_id += step + + ups_mask = np.empty(upsampled_size.astype(int), dtype=np.float32) + masks = np.empty((batch_size, *image_size), dtype=np.float32) + + full_batch_inputs = np.empty((batch_size, *image.shape), dtype=np.float32) + current_heatmaps = np.empty_like(heatmaps) + for b in range(ceil(samples / batch_size)): + batch_pos = b * batch_size + current_batch_size = min(samples - batch_pos, batch_size) + + batch_masks = masks[: current_batch_size] + for i in range(current_batch_size): + mask = (rng(mask_size) < self.prob).astype(np.float32) + cv2.resize(mask, (int(upsampled_size[1]), int(upsampled_size[0])), + ups_mask) + + offsets = np.round(rng((2,)) * cell_size) + mask = ups_mask[ + int(offsets[0]):int(image_size[0] + offsets[0]), + int(offsets[1]):int(image_size[1] + offsets[1]) ] + batch_masks[i] = mask + + batch_inputs = full_batch_inputs[:current_batch_size] + np.multiply(expand(batch_masks), expand(image, 0), out=batch_inputs) + + results = model.launch(batch_inputs) + for mask, result in zip(batch_masks, results): + result_labels, result_bboxes = self.split_outputs(result) + + confs.fill(0) + if len(predicted_labels) != 0: + for r in result_labels: + idx = predicted_labels.get(r.label, None) + if idx is not None: + label_total_counts[idx] += 1 + label_confs[idx] += r.attributes['score'] + for r in result_bboxes: + idx = predicted_labels.get(r.label, None) + if idx is not None: + label_total_counts[idx] += 1 + label_confs[idx] += r.attributes['score'] + + if len(predicted_bboxes) != 0 and len(result_bboxes) != 0: + if 0 < self.det_conf_thresh: + result_bboxes = [b for b in result_bboxes \ + if self.det_conf_thresh <= b.attributes['score']] + if 0 < self.nms_thresh: + result_bboxes = self.nms(result_bboxes, self.nms_thresh) + + for detection in result_bboxes: + for pred_idx, pred in enumerate(predicted_bboxes): + if pred.label != detection.label: + continue + + iou = pred.iou(detection) + assert 0 <= iou and iou <= 1 + if iou < iou_thresh: + continue + + bbox_total_counts[pred_idx] += 1 + + conf = detection.attributes['score'] + bbox_confs[pred_idx] += conf + + np.multiply.outer(confs, mask, out=current_heatmaps) + heatmaps += current_heatmaps + + if progressive: + yield self.normalize_hmaps(heatmaps.copy(), total_counts) + + yield self.normalize_hmaps(heatmaps, total_counts) \ No newline at end of file diff --git a/datumaro/datumaro/components/cli_plugin.py b/datumaro/datumaro/components/cli_plugin.py new file mode 100644 index 00000000000..08a7f3834cc --- /dev/null +++ b/datumaro/datumaro/components/cli_plugin.py @@ -0,0 +1,56 @@ + +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import argparse + +from datumaro.cli.util import MultilineFormatter + + +class CliPlugin: + @staticmethod + def _get_name(cls): + return getattr(cls, 'NAME', + remove_plugin_type(to_snake_case(cls.__name__))) + + @staticmethod + def _get_doc(cls): + return getattr(cls, '__doc__', "") + + @classmethod + def build_cmdline_parser(cls, **kwargs): + args = { + 'prog': cls._get_name(cls), + 'description': cls._get_doc(cls), + 'formatter_class': MultilineFormatter, + } + args.update(kwargs) + + return argparse.ArgumentParser(**args) + + @classmethod + def from_cmdline(cls, args=None): + if args and args[0] == '--': + args = args[1:] + parser = cls.build_cmdline_parser() + args = parser.parse_args(args) + return vars(args) + +def remove_plugin_type(s): + for t in {'transform', 'extractor', 'converter', 'launcher', 'importer'}: + s = s.replace('_' + t, '') + return s + +def to_snake_case(s): + if not s: + return '' + + name = [s[0].lower()] + for char in s[1:]: + if char.isalpha() and char.isupper(): + name.append('_') + name.append(char.lower()) + else: + name.append(char) + return ''.join(name) \ No newline at end of file diff --git a/datumaro/datumaro/components/comparator.py b/datumaro/datumaro/components/comparator.py new file mode 100644 index 00000000000..842a3963a98 --- /dev/null +++ b/datumaro/datumaro/components/comparator.py @@ -0,0 +1,113 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from itertools import zip_longest +import numpy as np + +from datumaro.components.extractor import AnnotationType, LabelCategories + + +class Comparator: + def __init__(self, + iou_threshold=0.5, conf_threshold=0.9): + self.iou_threshold = iou_threshold + self.conf_threshold = conf_threshold + + @staticmethod + def iou(box_a, box_b): + return box_a.iou(box_b) + + # pylint: disable=no-self-use + def compare_dataset_labels(self, extractor_a, extractor_b): + a_label_cat = extractor_a.categories().get(AnnotationType.label) + b_label_cat = extractor_b.categories().get(AnnotationType.label) + if not a_label_cat and not b_label_cat: + return None + if not a_label_cat: + a_label_cat = LabelCategories() + if not b_label_cat: + b_label_cat = LabelCategories() + + mismatches = [] + for a_label, b_label in zip_longest(a_label_cat.items, b_label_cat.items): + if a_label != b_label: + mismatches.append((a_label, b_label)) + return mismatches + # pylint: enable=no-self-use + + def compare_item_labels(self, item_a, item_b): + conf_threshold = self.conf_threshold + + a_labels = set([ann.label for ann in item_a.annotations \ + if ann.type is AnnotationType.label and \ + conf_threshold < ann.attributes.get('score', 1)]) + b_labels = set([ann.label for ann in item_b.annotations \ + if ann.type is AnnotationType.label and \ + conf_threshold < ann.attributes.get('score', 1)]) + + a_unmatched = a_labels - b_labels + b_unmatched = b_labels - a_labels + matches = a_labels & b_labels + + return matches, a_unmatched, b_unmatched + + def compare_item_bboxes(self, item_a, item_b): + iou_threshold = self.iou_threshold + conf_threshold = self.conf_threshold + + a_boxes = [ann for ann in item_a.annotations \ + if ann.type is AnnotationType.bbox and \ + conf_threshold < ann.attributes.get('score', 1)] + b_boxes = [ann for ann in item_b.annotations \ + if ann.type is AnnotationType.bbox and \ + conf_threshold < ann.attributes.get('score', 1)] + a_boxes.sort(key=lambda ann: 1 - ann.attributes.get('score', 1)) + b_boxes.sort(key=lambda ann: 1 - ann.attributes.get('score', 1)) + + # a_matches: indices of b_boxes matched to a bboxes + # b_matches: indices of a_boxes matched to b bboxes + a_matches = -np.ones(len(a_boxes), dtype=int) + b_matches = -np.ones(len(b_boxes), dtype=int) + + iou_matrix = np.array([ + [self.iou(a, b) for b in b_boxes] for a in a_boxes + ]) + + # matches: boxes we succeeded to match completely + # mispred: boxes we succeeded to match, having label mismatch + matches = [] + mispred = [] + + for a_idx, a_bbox in enumerate(a_boxes): + if len(b_boxes) == 0: + break + matched_b = a_matches[a_idx] + iou_max = max(iou_matrix[a_idx, matched_b], iou_threshold) + for b_idx, b_bbox in enumerate(b_boxes): + if 0 <= b_matches[b_idx]: # assign a_bbox with max conf + continue + iou = iou_matrix[a_idx, b_idx] + if iou < iou_max: + continue + iou_max = iou + matched_b = b_idx + + if matched_b < 0: + continue + a_matches[a_idx] = matched_b + b_matches[matched_b] = a_idx + + b_bbox = b_boxes[matched_b] + + if a_bbox.label == b_bbox.label: + matches.append( (a_bbox, b_bbox) ) + else: + mispred.append( (a_bbox, b_bbox) ) + + # *_umatched: boxes of (*) we failed to match + a_unmatched = [a_boxes[i] for i, m in enumerate(a_matches) if m < 0] + b_unmatched = [b_boxes[i] for i, m in enumerate(b_matches) if m < 0] + + return matches, mispred, a_unmatched, b_unmatched diff --git a/datumaro/datumaro/components/config.py b/datumaro/datumaro/components/config.py new file mode 100644 index 00000000000..520c6e70bd5 --- /dev/null +++ b/datumaro/datumaro/components/config.py @@ -0,0 +1,237 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import yaml + + +class Schema: + class Item: + def __init__(self, ctor, internal=False): + self.ctor = ctor + self.internal = internal + + def __call__(self, *args, **kwargs): + return self.ctor(*args, **kwargs) + + def __init__(self, items=None, fallback=None): + self._items = {} + if items is not None: + self._items.update(items) + self._fallback = fallback + + def _get_items(self, allow_fallback=True): + all_items = {} + + if allow_fallback and self._fallback is not None: + all_items.update(self._fallback) + all_items.update(self._items) + + return all_items + + def items(self, allow_fallback=True): + return self._get_items(allow_fallback=allow_fallback).items() + + def keys(self, allow_fallback=True): + return self._get_items(allow_fallback=allow_fallback).keys() + + def values(self, allow_fallback=True): + return self._get_items(allow_fallback=allow_fallback).values() + + def __contains__(self, key): + return key in self.keys() + + def __len__(self): + return len(self._get_items()) + + def __iter__(self): + return iter(self._get_items()) + + def __getitem__(self, key): + default = object() + value = self.get(key, default=default) + if value is default: + raise KeyError('Key "%s" does not exist' % (key)) + return value + + def get(self, key, default=None): + found = self._items.get(key, default) + if found is not default: + return found + + if self._fallback is not None: + return self._fallback.get(key, default) + +class SchemaBuilder: + def __init__(self): + self._items = {} + + def add(self, name, ctor=str, internal=False): + if name in self._items: + raise KeyError('Key "%s" already exists' % (name)) + + self._items[name] = Schema.Item(ctor, internal=internal) + return self + + def build(self): + return Schema(self._items) + +class Config: + def __init__(self, config=None, fallback=None, schema=None, mutable=True): + # schema should be established first + self.__dict__['_schema'] = schema + self.__dict__['_mutable'] = True + + self.__dict__['_config'] = {} + if fallback is not None: + for k, v in fallback.items(allow_fallback=False): + self.set(k, v) + if config is not None: + self.update(config) + + self.__dict__['_mutable'] = mutable + + def _items(self, allow_fallback=True, allow_internal=True): + all_config = {} + if allow_fallback and self._schema is not None: + for key, item in self._schema.items(): + all_config[key] = item() + all_config.update(self._config) + + if not allow_internal and self._schema is not None: + for key, item in self._schema.items(): + if item.internal: + all_config.pop(key) + return all_config + + def items(self, allow_fallback=True, allow_internal=True): + return self._items( + allow_fallback=allow_fallback, + allow_internal=allow_internal + ).items() + + def keys(self, allow_fallback=True, allow_internal=True): + return self._items( + allow_fallback=allow_fallback, + allow_internal=allow_internal + ).keys() + + def values(self, allow_fallback=True, allow_internal=True): + return self._items( + allow_fallback=allow_fallback, + allow_internal=allow_internal + ).values() + + def __contains__(self, key): + return key in self.keys() + + def __len__(self): + return len(self.items()) + + def __iter__(self): + return iter(zip(self.keys(), self.values())) + + def __getitem__(self, key): + default = object() + value = self.get(key, default=default) + if value is default: + raise KeyError('Key "%s" does not exist' % (key)) + return value + + def __setitem__(self, key, value): + return self.set(key, value) + + def __getattr__(self, key): + return self.get(key) + + def __setattr__(self, key, value): + return self.set(key, value) + + def __eq__(self, other): + try: + for k, my_v in self.items(allow_internal=False): + other_v = other[k] + if my_v != other_v: + return False + return True + except Exception: + return False + + def update(self, other): + for k, v in other.items(): + self.set(k, v) + + def remove(self, key): + if not self._mutable: + raise Exception("Cannot set value of immutable object") + + self._config.pop(key, None) + + def get(self, key, default=None): + found = self._config.get(key, default) + if found is not default: + return found + + if self._schema is not None: + found = self._schema.get(key, default) + if found is not default: + # ignore mutability + found = found() + self._config[key] = found + return found + + return found + + def set(self, key, value): + if not self._mutable: + raise Exception("Cannot set value of immutable object") + + if self._schema is not None: + if key not in self._schema: + raise Exception("Can not set key '%s' - schema mismatch" % (key)) + + schema_entry = self._schema[key] + schema_entry_instance = schema_entry() + if not isinstance(value, type(schema_entry_instance)): + if isinstance(value, dict) and \ + isinstance(schema_entry_instance, Config): + schema_entry_instance.update(value) + value = schema_entry_instance + else: + raise Exception("Can not set key '%s' - schema mismatch" % (key)) + + self._config[key] = value + return value + + @staticmethod + def parse(path): + with open(path, 'r') as f: + return Config(yaml.safe_load(f)) + + @staticmethod + def yaml_representer(dumper, value): + return dumper.represent_data( + value._items(allow_internal=False, allow_fallback=False)) + + def dump(self, path): + with open(path, 'w+') as f: + yaml.dump(self, f) + +yaml.add_multi_representer(Config, Config.yaml_representer) + + +class DefaultConfig(Config): + def __init__(self, default=None): + super().__init__() + self.__dict__['_default'] = default + + def set(self, key, value): + if key not in self.keys(allow_fallback=False): + value = self._default(value) + return super().set(key, value) + else: + return super().set(key, value) + + +DEFAULT_FORMAT = 'datumaro' \ No newline at end of file diff --git a/datumaro/datumaro/components/config_model.py b/datumaro/datumaro/components/config_model.py new file mode 100644 index 00000000000..9bce725ebd7 --- /dev/null +++ b/datumaro/datumaro/components/config_model.py @@ -0,0 +1,64 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from datumaro.components.config import Config, \ + DefaultConfig as _DefaultConfig, \ + SchemaBuilder as _SchemaBuilder + + +SOURCE_SCHEMA = _SchemaBuilder() \ + .add('url', str) \ + .add('format', str) \ + .add('options', dict) \ + .build() + +class Source(Config): + def __init__(self, config=None): + super().__init__(config, schema=SOURCE_SCHEMA) + + +MODEL_SCHEMA = _SchemaBuilder() \ + .add('launcher', str) \ + .add('model_dir', str, internal=True) \ + .add('options', dict) \ + .build() + +class Model(Config): + def __init__(self, config=None): + super().__init__(config, schema=MODEL_SCHEMA) + + +PROJECT_SCHEMA = _SchemaBuilder() \ + .add('project_name', str) \ + .add('format_version', int) \ + \ + .add('subsets', list) \ + .add('sources', lambda: _DefaultConfig( + lambda v=None: Source(v))) \ + .add('models', lambda: _DefaultConfig( + lambda v=None: Model(v))) \ + \ + .add('models_dir', str, internal=True) \ + .add('plugins_dir', str, internal=True) \ + .add('sources_dir', str, internal=True) \ + .add('dataset_dir', str, internal=True) \ + .add('project_filename', str, internal=True) \ + .add('project_dir', str, internal=True) \ + .add('env_dir', str, internal=True) \ + .build() + +PROJECT_DEFAULT_CONFIG = Config({ + 'project_name': 'undefined', + 'format_version': 1, + + 'sources_dir': 'sources', + 'dataset_dir': 'dataset', + 'models_dir': 'models', + 'plugins_dir': 'plugins', + + 'project_filename': 'config.yaml', + 'project_dir': '', + 'env_dir': '.datumaro', +}, mutable=False, schema=PROJECT_SCHEMA) diff --git a/datumaro/datumaro/components/converter.py b/datumaro/datumaro/components/converter.py new file mode 100644 index 00000000000..9ea404d962d --- /dev/null +++ b/datumaro/datumaro/components/converter.py @@ -0,0 +1,19 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +class Converter: + def __init__(self, cmdline_args=None): + pass + + def __call__(self, extractor, save_dir): + raise NotImplementedError() + + def _parse_cmdline(self, cmdline): + parser = self.build_cmdline_parser() + + if len(cmdline) != 0 and cmdline[0] == '--': + cmdline = cmdline[1:] + args = parser.parse_args(cmdline) + return vars(args) \ No newline at end of file diff --git a/datumaro/datumaro/components/dataset_filter.py b/datumaro/datumaro/components/dataset_filter.py new file mode 100644 index 00000000000..f5d923bef79 --- /dev/null +++ b/datumaro/datumaro/components/dataset_filter.py @@ -0,0 +1,247 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from lxml import etree as ET # NOTE: lxml has proper XPath implementation +from datumaro.components.extractor import (Transform, + Annotation, AnnotationType, + Label, Mask, Points, Polygon, PolyLine, Bbox, Caption, +) + + +class DatasetItemEncoder: + @classmethod + def encode(cls, item, categories=None): + item_elem = ET.Element('item') + ET.SubElement(item_elem, 'id').text = str(item.id) + ET.SubElement(item_elem, 'subset').text = str(item.subset) + ET.SubElement(item_elem, 'path').text = str('/'.join(item.path)) + + image = item.image + if image is not None: + item_elem.append(cls.encode_image(image)) + + for ann in item.annotations: + item_elem.append(cls.encode_annotation(ann, categories)) + + return item_elem + + @classmethod + def encode_image(cls, image): + image_elem = ET.Element('image') + + h, w = image.size + ET.SubElement(image_elem, 'width').text = str(w) + ET.SubElement(image_elem, 'height').text = str(h) + + ET.SubElement(image_elem, 'has_data').text = '%d' % int(image.has_data) + ET.SubElement(image_elem, 'path').text = image.path + + return image_elem + + @classmethod + def encode_annotation_base(cls, annotation): + assert isinstance(annotation, Annotation) + ann_elem = ET.Element('annotation') + ET.SubElement(ann_elem, 'id').text = str(annotation.id) + ET.SubElement(ann_elem, 'type').text = str(annotation.type.name) + + for k, v in annotation.attributes.items(): + ET.SubElement(ann_elem, k).text = str(v) + + ET.SubElement(ann_elem, 'group').text = str(annotation.group) + + return ann_elem + + @staticmethod + def _get_label(label_id, categories): + label = '' + if label_id is None: + return '' + if categories is not None: + label_cat = categories.get(AnnotationType.label) + if label_cat is not None: + label = label_cat.items[label_id].name + return label + + @classmethod + def encode_label_object(cls, obj, categories): + ann_elem = cls.encode_annotation_base(obj) + + ET.SubElement(ann_elem, 'label').text = \ + str(cls._get_label(obj.label, categories)) + ET.SubElement(ann_elem, 'label_id').text = str(obj.label) + + return ann_elem + + @classmethod + def encode_mask_object(cls, obj, categories): + ann_elem = cls.encode_annotation_base(obj) + + ET.SubElement(ann_elem, 'label').text = \ + str(cls._get_label(obj.label, categories)) + ET.SubElement(ann_elem, 'label_id').text = str(obj.label) + + return ann_elem + + @classmethod + def encode_bbox_object(cls, obj, categories): + ann_elem = cls.encode_annotation_base(obj) + + ET.SubElement(ann_elem, 'label').text = \ + str(cls._get_label(obj.label, categories)) + ET.SubElement(ann_elem, 'label_id').text = str(obj.label) + ET.SubElement(ann_elem, 'x').text = str(obj.x) + ET.SubElement(ann_elem, 'y').text = str(obj.y) + ET.SubElement(ann_elem, 'w').text = str(obj.w) + ET.SubElement(ann_elem, 'h').text = str(obj.h) + ET.SubElement(ann_elem, 'area').text = str(obj.get_area()) + + return ann_elem + + @classmethod + def encode_points_object(cls, obj, categories): + ann_elem = cls.encode_annotation_base(obj) + + ET.SubElement(ann_elem, 'label').text = \ + str(cls._get_label(obj.label, categories)) + ET.SubElement(ann_elem, 'label_id').text = str(obj.label) + + x, y, w, h = obj.get_bbox() + area = w * h + bbox_elem = ET.SubElement(ann_elem, 'bbox') + ET.SubElement(bbox_elem, 'x').text = str(x) + ET.SubElement(bbox_elem, 'y').text = str(y) + ET.SubElement(bbox_elem, 'w').text = str(w) + ET.SubElement(bbox_elem, 'h').text = str(h) + ET.SubElement(bbox_elem, 'area').text = str(area) + + points = obj.points + for i in range(0, len(points), 2): + point_elem = ET.SubElement(ann_elem, 'point') + ET.SubElement(point_elem, 'x').text = str(points[i]) + ET.SubElement(point_elem, 'y').text = str(points[i + 1]) + ET.SubElement(point_elem, 'visible').text = \ + str(obj.visibility[i // 2].name) + + return ann_elem + + @classmethod + def encode_polygon_object(cls, obj, categories): + ann_elem = cls.encode_annotation_base(obj) + + ET.SubElement(ann_elem, 'label').text = \ + str(cls._get_label(obj.label, categories)) + ET.SubElement(ann_elem, 'label_id').text = str(obj.label) + + x, y, w, h = obj.get_bbox() + area = w * h + bbox_elem = ET.SubElement(ann_elem, 'bbox') + ET.SubElement(bbox_elem, 'x').text = str(x) + ET.SubElement(bbox_elem, 'y').text = str(y) + ET.SubElement(bbox_elem, 'w').text = str(w) + ET.SubElement(bbox_elem, 'h').text = str(h) + ET.SubElement(bbox_elem, 'area').text = str(area) + + points = obj.points + for i in range(0, len(points), 2): + point_elem = ET.SubElement(ann_elem, 'point') + ET.SubElement(point_elem, 'x').text = str(points[i]) + ET.SubElement(point_elem, 'y').text = str(points[i + 1]) + + return ann_elem + + @classmethod + def encode_polyline_object(cls, obj, categories): + ann_elem = cls.encode_annotation_base(obj) + + ET.SubElement(ann_elem, 'label').text = \ + str(cls._get_label(obj.label, categories)) + ET.SubElement(ann_elem, 'label_id').text = str(obj.label) + + x, y, w, h = obj.get_bbox() + area = w * h + bbox_elem = ET.SubElement(ann_elem, 'bbox') + ET.SubElement(bbox_elem, 'x').text = str(x) + ET.SubElement(bbox_elem, 'y').text = str(y) + ET.SubElement(bbox_elem, 'w').text = str(w) + ET.SubElement(bbox_elem, 'h').text = str(h) + ET.SubElement(bbox_elem, 'area').text = str(area) + + points = obj.points + for i in range(0, len(points), 2): + point_elem = ET.SubElement(ann_elem, 'point') + ET.SubElement(point_elem, 'x').text = str(points[i]) + ET.SubElement(point_elem, 'y').text = str(points[i + 1]) + + return ann_elem + + @classmethod + def encode_caption_object(cls, obj): + ann_elem = cls.encode_annotation_base(obj) + + ET.SubElement(ann_elem, 'caption').text = str(obj.caption) + + return ann_elem + + @classmethod + def encode_annotation(cls, o, categories=None): + if isinstance(o, Label): + return cls.encode_label_object(o, categories) + if isinstance(o, Mask): + return cls.encode_mask_object(o, categories) + if isinstance(o, Bbox): + return cls.encode_bbox_object(o, categories) + if isinstance(o, Points): + return cls.encode_points_object(o, categories) + if isinstance(o, PolyLine): + return cls.encode_polyline_object(o, categories) + if isinstance(o, Polygon): + return cls.encode_polygon_object(o, categories) + if isinstance(o, Caption): + return cls.encode_caption_object(o) + raise NotImplementedError("Unexpected annotation object passed: %s" % o) + + @staticmethod + def to_string(encoded_item): + return ET.tostring(encoded_item, encoding='unicode', pretty_print=True) + +def XPathDatasetFilter(extractor, xpath=None): + if xpath is None: + return extractor + xpath = ET.XPath(xpath) + f = lambda item: bool(xpath( + DatasetItemEncoder.encode(item, extractor.categories()))) + return extractor.select(f) + +class XPathAnnotationsFilter(Transform): + def __init__(self, extractor, xpath=None, remove_empty=False): + super().__init__(extractor) + + if xpath is not None: + xpath = ET.XPath(xpath) + self._filter = xpath + + self._remove_empty = remove_empty + + def __iter__(self): + for item in self._extractor: + item = self.transform_item(item) + if item is not None: + yield item + + def transform_item(self, item): + if self._filter is None: + return item + + encoded = DatasetItemEncoder.encode(item, self._extractor.categories()) + filtered = self._filter(encoded) + filtered = [elem for elem in filtered if elem.tag == 'annotation'] + + encoded = encoded.findall('annotation') + annotations = [item.annotations[encoded.index(e)] for e in filtered] + + if self._remove_empty and len(annotations) == 0: + return None + return self.wrap_item(item, annotations=annotations) \ No newline at end of file diff --git a/datumaro/datumaro/components/extractor.py b/datumaro/datumaro/components/extractor.py new file mode 100644 index 00000000000..bd5b39fa962 --- /dev/null +++ b/datumaro/datumaro/components/extractor.py @@ -0,0 +1,767 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from collections import namedtuple +from enum import Enum +import numpy as np + +from datumaro.util.image import Image + +AnnotationType = Enum('AnnotationType', + [ + 'label', + 'mask', + 'points', + 'polygon', + 'polyline', + 'bbox', + 'caption', + ]) + +class Annotation: + # pylint: disable=redefined-builtin + def __init__(self, id=None, type=None, attributes=None, group=None): + if id is not None: + id = int(id) + self.id = id + + assert type in AnnotationType + self.type = type + + if attributes is None: + attributes = {} + else: + attributes = dict(attributes) + self.attributes = attributes + + if group is None: + group = 0 + else: + group = int(group) + self.group = group + # pylint: enable=redefined-builtin + + def __eq__(self, other): + if not isinstance(other, Annotation): + return False + return \ + (self.id == other.id) and \ + (self.type == other.type) and \ + (self.attributes == other.attributes) and \ + (self.group == other.group) + +class Categories: + def __init__(self, attributes=None): + if attributes is None: + attributes = set() + else: + if not isinstance(attributes, set): + attributes = set(attributes) + for attr in attributes: + assert isinstance(attr, str) + self.attributes = attributes + + def __eq__(self, other): + if not isinstance(other, Categories): + return False + return \ + (self.attributes == other.attributes) + +class LabelCategories(Categories): + Category = namedtuple('Category', ['name', 'parent', 'attributes']) + + def __init__(self, items=None, attributes=None): + super().__init__(attributes=attributes) + + if items is None: + items = [] + self.items = items + + self._indices = {} + self._reindex() + + def _reindex(self): + indices = {} + for index, item in enumerate(self.items): + assert item.name not in self._indices + indices[item.name] = index + self._indices = indices + + def add(self, name, parent=None, attributes=None): + assert name not in self._indices + if attributes is None: + attributes = set() + else: + if not isinstance(attributes, set): + attributes = set(attributes) + for attr in attributes: + assert isinstance(attr, str) + if parent is None: + parent = '' + + index = len(self.items) + self.items.append(self.Category(name, parent, attributes)) + self._indices[name] = index + + def find(self, name): + index = self._indices.get(name) + if index: + return index, self.items[index] + return index, None + + def __eq__(self, other): + if not super().__eq__(other): + return False + return \ + (self.items == other.items) + +class Label(Annotation): + # pylint: disable=redefined-builtin + def __init__(self, label=None, + id=None, attributes=None, group=None): + super().__init__(id=id, type=AnnotationType.label, + attributes=attributes, group=group) + + if label is not None: + label = int(label) + self.label = label + # pylint: enable=redefined-builtin + + def __eq__(self, other): + if not super().__eq__(other): + return False + return \ + (self.label == other.label) + +class MaskCategories(Categories): + def __init__(self, colormap=None, inverse_colormap=None, attributes=None): + super().__init__(attributes=attributes) + + # colormap: label id -> color + if colormap is None: + colormap = {} + self.colormap = colormap + self._inverse_colormap = inverse_colormap + + @property + def inverse_colormap(self): + from datumaro.util.mask_tools import invert_colormap + if self._inverse_colormap is None: + if self.colormap is not None: + try: + self._inverse_colormap = invert_colormap(self.colormap) + except Exception: + pass + return self._inverse_colormap + + def __eq__(self, other): + if not super().__eq__(other): + return False + for label_id, my_color in self.colormap.items(): + other_color = other.colormap.get(label_id) + if not np.array_equal(my_color, other_color): + return False + return True + +class Mask(Annotation): + # pylint: disable=redefined-builtin + def __init__(self, image=None, label=None, z_order=None, + id=None, attributes=None, group=None): + super().__init__(type=AnnotationType.mask, + id=id, attributes=attributes, group=group) + + self._image = image + + if label is not None: + label = int(label) + self._label = label + + if z_order is None: + z_order = 0 + else: + z_order = int(z_order) + self._z_order = z_order + # pylint: enable=redefined-builtin + + @property + def image(self): + if callable(self._image): + return self._image() + return self._image + + @property + def label(self): + return self._label + + @property + def z_order(self): + return self._z_order + + def as_class_mask(self, label_id=None): + if label_id is None: + label_id = self.label + return self.image * label_id + + def as_instance_mask(self, instance_id): + return self.image * instance_id + + def get_area(self): + return np.count_nonzero(self.image) + + def get_bbox(self): + from datumaro.util.mask_tools import find_mask_bbox + return find_mask_bbox(self.image) + + def paint(self, colormap): + from datumaro.util.mask_tools import paint_mask + return paint_mask(self.as_class_mask(), colormap) + + def __eq__(self, other): + if not super().__eq__(other): + return False + return \ + (self.label == other.label) and \ + (self.z_order == other.z_order) and \ + (self.image is not None and other.image is not None and \ + np.array_equal(self.image, other.image)) + +class RleMask(Mask): + # pylint: disable=redefined-builtin + def __init__(self, rle=None, label=None, z_order=None, + id=None, attributes=None, group=None): + lazy_decode = self._lazy_decode(rle) + super().__init__(image=lazy_decode, label=label, z_order=z_order, + id=id, attributes=attributes, group=group) + + self._rle = rle + # pylint: enable=redefined-builtin + + @staticmethod + def _lazy_decode(rle): + from pycocotools import mask as mask_utils + return lambda: mask_utils.decode(rle).astype(np.bool) + + def get_area(self): + from pycocotools import mask as mask_utils + return mask_utils.area(self._rle) + + def get_bbox(self): + from pycocotools import mask as mask_utils + return mask_utils.toBbox(self._rle) + + @property + def rle(self): + return self._rle + + def __eq__(self, other): + if not isinstance(other, __class__): + return super().__eq__(other) + return self._rle == other._rle + +class CompiledMask: + @staticmethod + def from_instance_masks(instance_masks, + instance_ids=None, instance_labels=None): + from datumaro.util.mask_tools import merge_masks + + if instance_ids is not None: + assert len(instance_ids) == len(instance_masks) + else: + instance_ids = [1 + i for i in range(len(instance_masks))] + + if instance_labels is not None: + assert len(instance_labels) == len(instance_masks) + else: + instance_labels = [None] * len(instance_masks) + + instance_masks = sorted(instance_masks, key=lambda m: m.z_order) + + instance_mask = [m.as_instance_mask(id) for m, id in + zip(instance_masks, instance_ids)] + instance_mask = merge_masks(instance_mask) + + cls_mask = [m.as_class_mask(c) for m, c in + zip(instance_masks, instance_labels)] + cls_mask = merge_masks(cls_mask) + return __class__(class_mask=cls_mask, instance_mask=instance_mask) + + def __init__(self, class_mask=None, instance_mask=None): + self._class_mask = class_mask + self._instance_mask = instance_mask + + @staticmethod + def _get_image(image): + if callable(image): + return image() + return image + + @property + def class_mask(self): + return self._get_image(self._class_mask) + + @property + def instance_mask(self): + return self._get_image(self._instance_mask) + + @property + def instance_count(self): + return int(self.instance_mask.max()) + + def get_instance_labels(self, class_count=None): + if class_count is None: + class_count = np.max(self.class_mask) + 1 + + m = self.class_mask * class_count + self.instance_mask + m = m.astype(int) + keys = np.unique(m) + instance_labels = {k % class_count: k // class_count + for k in keys if k % class_count != 0 + } + return instance_labels + + def extract(self, instance_id): + return self.instance_mask == instance_id + + def lazy_extract(self, instance_id): + return lambda: self.extract(instance_id) + +def compute_iou(bbox_a, bbox_b): + aX, aY, aW, aH = bbox_a + bX, bY, bW, bH = bbox_b + in_right = min(aX + aW, bX + bW) + in_left = max(aX, bX) + in_top = max(aY, bY) + in_bottom = min(aY + aH, bY + bH) + + in_w = max(0, in_right - in_left) + in_h = max(0, in_bottom - in_top) + intersection = in_w * in_h + + a_area = aW * aH + b_area = bW * bH + union = a_area + b_area - intersection + + return intersection / max(1.0, union) + +class _Shape(Annotation): + # pylint: disable=redefined-builtin + def __init__(self, type, points=None, label=None, z_order=None, + id=None, attributes=None, group=None): + super().__init__(id=id, type=type, + attributes=attributes, group=group) + self._points = points + + if label is not None: + label = int(label) + self._label = label + + if z_order is None: + z_order = 0 + else: + z_order = int(z_order) + self._z_order = z_order + # pylint: enable=redefined-builtin + + @property + def points(self): + return self._points + + @property + def label(self): + return self._label + + @property + def z_order(self): + return self._z_order + + def get_area(self): + raise NotImplementedError() + + def get_bbox(self): + points = self.points + if not points: + return None + + xs = [p for p in points[0::2]] + ys = [p for p in points[1::2]] + x0 = min(xs) + x1 = max(xs) + y0 = min(ys) + y1 = max(ys) + return [x0, y0, x1 - x0, y1 - y0] + + def __eq__(self, other): + if not super().__eq__(other): + return False + return \ + (np.array_equal(self.points, other.points)) and \ + (self.z_order == other.z_order) and \ + (self.label == other.label) + +class PolyLine(_Shape): + # pylint: disable=redefined-builtin + def __init__(self, points=None, label=None, z_order=None, + id=None, attributes=None, group=None): + super().__init__(type=AnnotationType.polyline, + points=points, label=label, z_order=z_order, + id=id, attributes=attributes, group=group) + # pylint: enable=redefined-builtin + + def as_polygon(self): + return self.points[:] + + def get_area(self): + return 0 + +class Polygon(_Shape): + # pylint: disable=redefined-builtin + def __init__(self, points=None, label=None, + z_order=None, id=None, attributes=None, group=None): + if points is not None: + # keep the message on the single line to produce + # informative output + assert len(points) % 2 == 0 and 3 <= len(points) // 2, "Wrong polygon points: %s" % points + super().__init__(type=AnnotationType.polygon, + points=points, label=label, z_order=z_order, + id=id, attributes=attributes, group=group) + # pylint: enable=redefined-builtin + + def get_area(self): + import pycocotools.mask as mask_utils + + _, _, w, h = self.get_bbox() + rle = mask_utils.frPyObjects([self.points], h, w) + area = mask_utils.area(rle)[0] + return area + +class Bbox(_Shape): + # pylint: disable=redefined-builtin + def __init__(self, x=0, y=0, w=0, h=0, label=None, z_order=None, + id=None, attributes=None, group=None): + super().__init__(type=AnnotationType.bbox, + points=[x, y, x + w, y + h], label=label, z_order=z_order, + id=id, attributes=attributes, group=group) + # pylint: enable=redefined-builtin + + @property + def x(self): + return self.points[0] + + @property + def y(self): + return self.points[1] + + @property + def w(self): + return self.points[2] - self.points[0] + + @property + def h(self): + return self.points[3] - self.points[1] + + def get_area(self): + return self.w * self.h + + def get_bbox(self): + return [self.x, self.y, self.w, self.h] + + def as_polygon(self): + x, y, w, h = self.get_bbox() + return [ + x, y, + x + w, y, + x + w, y + h, + x, y + h + ] + + def iou(self, other): + return compute_iou(self.get_bbox(), other.get_bbox()) + +class PointsCategories(Categories): + Category = namedtuple('Category', ['labels', 'adjacent']) + + def __init__(self, items=None, attributes=None): + super().__init__(attributes=attributes) + + if items is None: + items = {} + self.items = items + + def add(self, label_id, labels=None, adjacent=None): + if labels is None: + labels = [] + if adjacent is None: + adjacent = [] + self.items[label_id] = self.Category(labels, set(adjacent)) + + def __eq__(self, other): + if not super().__eq__(other): + return False + return \ + (self.items == other.items) + +class Points(_Shape): + Visibility = Enum('Visibility', [ + ('absent', 0), + ('hidden', 1), + ('visible', 2), + ]) + + # pylint: disable=redefined-builtin + def __init__(self, points=None, visibility=None, label=None, z_order=None, + id=None, attributes=None, group=None): + if points is not None: + assert len(points) % 2 == 0 + + if visibility is not None: + assert len(visibility) == len(points) // 2 + for i, v in enumerate(visibility): + if not isinstance(v, self.Visibility): + visibility[i] = self.Visibility(v) + else: + visibility = [] + for _ in range(len(points) // 2): + visibility.append(self.Visibility.visible) + + super().__init__(type=AnnotationType.points, + points=points, label=label, z_order=z_order, + id=id, attributes=attributes, group=group) + + self.visibility = visibility + # pylint: enable=redefined-builtin + + def get_area(self): + return 0 + + def get_bbox(self): + xs = [p for p, v in zip(self.points[0::2], self.visibility) + if v != __class__.Visibility.absent] + ys = [p for p, v in zip(self.points[1::2], self.visibility) + if v != __class__.Visibility.absent] + x0 = min(xs, default=0) + x1 = max(xs, default=0) + y0 = min(ys, default=0) + y1 = max(ys, default=0) + return [x0, y0, x1 - x0, y1 - y0] + + def __eq__(self, other): + if not super().__eq__(other): + return False + return \ + (self.visibility == other.visibility) + +class Caption(Annotation): + # pylint: disable=redefined-builtin + def __init__(self, caption=None, + id=None, attributes=None, group=None): + super().__init__(id=id, type=AnnotationType.caption, + attributes=attributes, group=group) + + if caption is None: + caption = '' + else: + caption = str(caption) + self.caption = caption + # pylint: enable=redefined-builtin + + def __eq__(self, other): + if not super().__eq__(other): + return False + return \ + (self.caption == other.caption) + +class DatasetItem: + # pylint: disable=redefined-builtin + def __init__(self, id=None, annotations=None, + subset=None, path=None, image=None): + assert id is not None + self._id = str(id) + + if subset is None: + subset = '' + else: + subset = str(subset) + self._subset = subset + + if path is None: + path = [] + else: + path = list(path) + self._path = path + + if annotations is None: + annotations = [] + else: + annotations = list(annotations) + self._annotations = annotations + + if callable(image) or isinstance(image, np.ndarray): + image = Image(data=image) + elif isinstance(image, str): + image = Image(path=image) + assert image is None or isinstance(image, Image) + self._image = image + # pylint: enable=redefined-builtin + + @property + def id(self): + return self._id + + @property + def subset(self): + return self._subset + + @property + def path(self): + return self._path + + @property + def annotations(self): + return self._annotations + + @property + def image(self): + return self._image + + @property + def has_image(self): + return self._image is not None + + def __eq__(self, other): + if not isinstance(other, __class__): + return False + return \ + (self.id == other.id) and \ + (self.subset == other.subset) and \ + (self.path == other.path) and \ + (self.annotations == other.annotations) and \ + (self.image == other.image) + + def wrap(item, **kwargs): + expected_args = {'id', 'annotations', 'subset', 'path', 'image'} + for k in expected_args: + if k not in kwargs: + kwargs[k] = getattr(item, k) + return DatasetItem(**kwargs) + +class IExtractor: + def __iter__(self): + raise NotImplementedError() + + def __len__(self): + raise NotImplementedError() + + def subsets(self): + raise NotImplementedError() + + def get_subset(self, name): + raise NotImplementedError() + + def categories(self): + raise NotImplementedError() + + def select(self, pred): + raise NotImplementedError() + +class _DatasetFilter: + def __init__(self, iterable, predicate): + self.iterable = iterable + self.predicate = predicate + + def __iter__(self): + return filter(self.predicate, self.iterable) + +class _ExtractorBase(IExtractor): + def __init__(self, length=None, subsets=None): + self._length = length + self._subsets = subsets + + def _init_cache(self): + subsets = set() + length = -1 + for length, item in enumerate(self): + subsets.add(item.subset) + length += 1 + + if self._length is None: + self._length = length + if self._subsets is None: + self._subsets = subsets + + def __len__(self): + if self._length is None: + self._init_cache() + return self._length + + def subsets(self): + if self._subsets is None: + self._init_cache() + return list(self._subsets) + + def get_subset(self, name): + if name in self.subsets(): + return self.select(lambda item: item.subset == name) + else: + raise Exception("Unknown subset '%s' requested" % name) + + def transform(self, method, *args, **kwargs): + return method(self, *args, **kwargs) + +class DatasetIteratorWrapper(_ExtractorBase): + def __init__(self, iterable, categories, subsets=None): + super().__init__(length=None, subsets=subsets) + self._iterable = iterable + self._categories = categories + + def __iter__(self): + return iter(self._iterable) + + def categories(self): + return self._categories + + def select(self, pred): + return DatasetIteratorWrapper( + _DatasetFilter(self, pred), self.categories(), self.subsets()) + +class Extractor(_ExtractorBase): + def __init__(self, length=None): + super().__init__(length=None) + + def categories(self): + return {} + + def select(self, pred): + return DatasetIteratorWrapper( + _DatasetFilter(self, pred), self.categories(), self.subsets()) + +DEFAULT_SUBSET_NAME = 'default' + + +class SourceExtractor(Extractor): + pass + +class Importer: + def __call__(self, path, **extra_params): + raise NotImplementedError() + +class Transform(Extractor): + @staticmethod + def wrap_item(item, **kwargs): + return item.wrap(**kwargs) + + def __init__(self, extractor): + super().__init__() + + self._extractor = extractor + + def __iter__(self): + for item in self._extractor: + yield self.transform_item(item) + + def categories(self): + return self._extractor.categories() + + def transform_item(self, item): + raise NotImplementedError() \ No newline at end of file diff --git a/datumaro/datumaro/components/launcher.py b/datumaro/datumaro/components/launcher.py new file mode 100644 index 00000000000..eb36295584d --- /dev/null +++ b/datumaro/datumaro/components/launcher.py @@ -0,0 +1,65 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import numpy as np + +from datumaro.components.extractor import Transform + + +# pylint: disable=no-self-use +class Launcher: + def __init__(self, model_dir=None): + pass + + def launch(self, inputs): + raise NotImplementedError() + + def preferred_input_size(self): + return None + + def get_categories(self): + return None +# pylint: enable=no-self-use + +class InferenceWrapper(Transform): + def __init__(self, extractor, launcher, batch_size=1): + super().__init__(extractor) + self._launcher = launcher + self._batch_size = batch_size + + def __iter__(self): + stop = False + data_iter = iter(self._extractor) + while not stop: + batch_items = [] + try: + for _ in range(self._batch_size): + item = next(data_iter) + batch_items.append(item) + except StopIteration: + stop = True + if len(batch_items) == 0: + break + + inputs = np.array([item.image.data for item in batch_items]) + inference = self._launcher.launch(inputs) + + for item, annotations in zip(batch_items, inference): + yield self.wrap_item(item, annotations=annotations) + + def get_subset(self, name): + subset = self._extractor.get_subset(name) + return InferenceWrapper(subset, self._launcher, self._batch_size) + + def categories(self): + launcher_override = self._launcher.get_categories() + if launcher_override is not None: + return launcher_override + return self._extractor.categories() + + def transform_item(self, item): + inputs = np.expand_dims(item.image, axis=0) + annotations = self._launcher.launch(inputs)[0] + return self.wrap_item(item, annotations=annotations) \ No newline at end of file diff --git a/datumaro/datumaro/components/project.py b/datumaro/datumaro/components/project.py new file mode 100644 index 00000000000..ea184083a46 --- /dev/null +++ b/datumaro/datumaro/components/project.py @@ -0,0 +1,832 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from collections import OrderedDict, defaultdict +from functools import reduce +import git +from glob import glob +import importlib +import inspect +import logging as log +import os +import os.path as osp +import shutil +import sys + +from datumaro.components.config import Config, DEFAULT_FORMAT +from datumaro.components.config_model import (Model, Source, + PROJECT_DEFAULT_CONFIG, PROJECT_SCHEMA) +from datumaro.components.extractor import Extractor +from datumaro.components.launcher import InferenceWrapper +from datumaro.components.dataset_filter import \ + XPathDatasetFilter, XPathAnnotationsFilter + + +def import_foreign_module(name, path, package=None): + module = None + default_path = sys.path.copy() + try: + sys.path = [ osp.abspath(path), ] + default_path + sys.modules.pop(name, None) # remove from cache + module = importlib.import_module(name, package=package) + sys.modules.pop(name) # remove from cache + except Exception: + raise + finally: + sys.path = default_path + return module + + +class Registry: + def __init__(self, config=None, item_type=None): + self.item_type = item_type + + self.items = {} + + if config is not None: + self.load(config) + + def load(self, config): + pass + + def register(self, name, value): + if self.item_type: + value = self.item_type(value) + self.items[name] = value + return value + + def unregister(self, name): + return self.items.pop(name, None) + + def get(self, key): + return self.items[key] # returns a class / ctor + + +class ModelRegistry(Registry): + def __init__(self, config=None): + super().__init__(config, item_type=Model) + + def load(self, config): + # TODO: list default dir, insert values + if 'models' in config: + for name, model in config.models.items(): + self.register(name, model) + + +class SourceRegistry(Registry): + def __init__(self, config=None): + super().__init__(config, item_type=Source) + + def load(self, config): + # TODO: list default dir, insert values + if 'sources' in config: + for name, source in config.sources.items(): + self.register(name, source) + +class PluginRegistry(Registry): + def __init__(self, config=None, builtin=None, local=None): + super().__init__(config) + + from datumaro.components.cli_plugin import CliPlugin + + if builtin is not None: + for v in builtin: + k = CliPlugin._get_name(v) + self.register(k, v) + if local is not None: + for v in local: + k = CliPlugin._get_name(v) + self.register(k, v) + +class GitWrapper: + def __init__(self, config=None): + self.repo = None + + if config is not None and osp.isdir(config.project_dir): + self.init(config.project_dir) + + @staticmethod + def _git_dir(base_path): + return osp.join(base_path, '.git') + + @classmethod + def spawn(cls, path): + spawn = not osp.isdir(cls._git_dir(path)) + repo = git.Repo.init(path=path) + if spawn: + author = git.Actor("Nobody", "nobody@example.com") + repo.index.commit('Initial commit', author=author) + return repo + + def init(self, path): + self.repo = self.spawn(path) + return self.repo + + def is_initialized(self): + return self.repo is not None + + def create_submodule(self, name, dst_dir, **kwargs): + self.repo.create_submodule(name, dst_dir, **kwargs) + + def has_submodule(self, name): + return name in [submodule.name for submodule in self.repo.submodules] + + def remove_submodule(self, name, **kwargs): + return self.repo.submodule(name).remove(**kwargs) + +def load_project_as_dataset(url): + # symbol forward declaration + raise NotImplementedError() + +class Environment: + _builtin_plugins = None + PROJECT_EXTRACTOR_NAME = 'project' + + def __init__(self, config=None): + config = Config(config, + fallback=PROJECT_DEFAULT_CONFIG, schema=PROJECT_SCHEMA) + + self.models = ModelRegistry(config) + self.sources = SourceRegistry(config) + + self.git = GitWrapper(config) + + env_dir = osp.join(config.project_dir, config.env_dir) + builtin = self._load_builtin_plugins() + custom = self._load_plugins2(osp.join(env_dir, config.plugins_dir)) + select = lambda seq, t: [e for e in seq if issubclass(e, t)] + from datumaro.components.extractor import Transform + from datumaro.components.extractor import SourceExtractor + from datumaro.components.extractor import Importer + from datumaro.components.converter import Converter + from datumaro.components.launcher import Launcher + self.extractors = PluginRegistry( + builtin=select(builtin, SourceExtractor), + local=select(custom, SourceExtractor) + ) + self.extractors.register(self.PROJECT_EXTRACTOR_NAME, + load_project_as_dataset) + + self.importers = PluginRegistry( + builtin=select(builtin, Importer), + local=select(custom, Importer) + ) + self.launchers = PluginRegistry( + builtin=select(builtin, Launcher), + local=select(custom, Launcher) + ) + self.converters = PluginRegistry( + builtin=select(builtin, Converter), + local=select(custom, Converter) + ) + self.transforms = PluginRegistry( + builtin=select(builtin, Transform), + local=select(custom, Transform) + ) + + @staticmethod + def _find_plugins(plugins_dir): + plugins = [] + if not osp.exists(plugins_dir): + return plugins + + for plugin_name in os.listdir(plugins_dir): + p = osp.join(plugins_dir, plugin_name) + if osp.isfile(p) and p.endswith('.py'): + plugins.append((plugins_dir, plugin_name, None)) + elif osp.isdir(p): + plugins += [(plugins_dir, + osp.splitext(plugin_name)[0] + '.' + osp.basename(p), + osp.splitext(plugin_name)[0] + ) + for p in glob(osp.join(p, '*.py'))] + return plugins + + @classmethod + def _import_module(cls, module_dir, module_name, types, package=None): + module = import_foreign_module(osp.splitext(module_name)[0], module_dir, + package=package) + + exports = [] + if hasattr(module, 'exports'): + exports = module.exports + else: + for symbol in dir(module): + if symbol.startswith('_'): + continue + exports.append(getattr(module, symbol)) + + exports = [s for s in exports + if inspect.isclass(s) and issubclass(s, types) and not s in types] + + return exports + + @classmethod + def _load_plugins(cls, plugins_dir, types): + types = tuple(types) + + plugins = cls._find_plugins(plugins_dir) + + all_exports = [] + for module_dir, module_name, package in plugins: + try: + exports = cls._import_module(module_dir, module_name, types, + package) + except ImportError as e: + log.debug("Failed to import module '%s': %s" % (module_name, e)) + continue + + log.debug("Imported the following symbols from %s: %s" % \ + ( + module_name, + ', '.join(s.__name__ for s in exports) + ) + ) + all_exports.extend(exports) + + return all_exports + + @classmethod + def _load_builtin_plugins(cls): + if not cls._builtin_plugins: + plugins_dir = osp.join( + __file__[: __file__.rfind(osp.join('datumaro', 'components'))], + osp.join('datumaro', 'plugins') + ) + assert osp.isdir(plugins_dir), plugins_dir + cls._builtin_plugins = cls._load_plugins2(plugins_dir) + return cls._builtin_plugins + + @classmethod + def _load_plugins2(cls, plugins_dir): + from datumaro.components.extractor import Transform + from datumaro.components.extractor import SourceExtractor + from datumaro.components.extractor import Importer + from datumaro.components.converter import Converter + from datumaro.components.launcher import Launcher + types = [SourceExtractor, Converter, Importer, Launcher, Transform] + + return cls._load_plugins(plugins_dir, types) + + def make_extractor(self, name, *args, **kwargs): + return self.extractors.get(name)(*args, **kwargs) + + def make_importer(self, name, *args, **kwargs): + return self.importers.get(name)(*args, **kwargs) + + def make_launcher(self, name, *args, **kwargs): + return self.launchers.get(name)(*args, **kwargs) + + def make_converter(self, name, *args, **kwargs): + return self.converters.get(name)(*args, **kwargs) + + def register_model(self, name, model): + self.models.register(name, model) + + def unregister_model(self, name): + self.models.unregister(name) + + +class Subset(Extractor): + def __init__(self, parent): + self._parent = parent + self.items = OrderedDict() + + def __iter__(self): + for item in self.items.values(): + yield item + + def __len__(self): + return len(self.items) + + def categories(self): + return self._parent.categories() + +class Dataset(Extractor): + @classmethod + def from_extractors(cls, *sources): + # merge categories + # TODO: implement properly with merging and annotations remapping + categories = {} + for source in sources: + categories.update(source.categories()) + for source in sources: + for cat_type, source_cat in source.categories().items(): + if not categories[cat_type] == source_cat: + raise NotImplementedError( + "Merging different categories is not implemented yet") + dataset = Dataset(categories=categories) + + # merge items + subsets = defaultdict(lambda: Subset(dataset)) + for source in sources: + for item in source: + existing_item = subsets[item.subset].items.get(item.id) + if existing_item is not None: + path = existing_item.path + if item.path != path: + path = None + item = cls._merge_items(existing_item, item, path=path) + + subsets[item.subset].items[item.id] = item + + dataset._subsets = dict(subsets) + return dataset + + def __init__(self, categories=None): + super().__init__() + + self._subsets = {} + + if not categories: + categories = {} + self._categories = categories + + def __iter__(self): + for subset in self._subsets.values(): + for item in subset: + yield item + + def __len__(self): + if self._length is None: + self._length = reduce(lambda s, x: s + len(x), + self._subsets.values(), 0) + return self._length + + def get_subset(self, name): + return self._subsets[name] + + def subsets(self): + return list(self._subsets) + + def categories(self): + return self._categories + + def get(self, item_id, subset=None, path=None): + if path: + raise KeyError("Requested dataset item path is not found") + return self._subsets[subset].items[item_id] + + def put(self, item, item_id=None, subset=None, path=None): + if path: + raise KeyError("Requested dataset item path is not found") + + if item_id is None: + item_id = item.id + if subset is None: + subset = item.subset + + item = item.wrap(path=None, annotations=item.annotations) + if item.subset not in self._subsets: + self._subsets[item.subset] = Subset(self) + self._subsets[subset].items[item_id] = item + self._length = None + + return item + + def extract(self, filter_expr, filter_annotations=False, remove_empty=False): + if filter_annotations: + return self.transform(XPathAnnotationsFilter, filter_expr, + remove_empty) + else: + return self.transform(XPathDatasetFilter, filter_expr) + + def update(self, items): + for item in items: + self.put(item) + return self + + def define_categories(self, categories): + assert not self._categories + self._categories = categories + + @staticmethod + def _lazy_image(item): + # NOTE: avoid https://docs.python.org/3/faq/programming.html#why-do-lambdas-defined-in-a-loop-with-different-values-all-return-the-same-result + return lambda: item.image + + @classmethod + def _merge_items(cls, existing_item, current_item, path=None): + image = None + if existing_item.has_image and current_item.has_image: + if existing_item.image.has_data: + image = existing_item.image + else: + image = current_item.image + + if existing_item.image.path != current_item.image.path: + if not existing_item.image.path: + image._path = current_item.image.path + + if all([existing_item.image._size, current_item.image._size]): + assert existing_item.image._size == current_item.image._size, "Image info differs for item '%s'" % existing_item.id + elif existing_item.image._size: + image._size = existing_item.image._size + else: + image._size = current_item.image._size + elif existing_item.has_image: + image = existing_item.image + else: + image = current_item.image + + return existing_item.wrap(path=path, + image=image, annotations=cls._merge_anno( + existing_item.annotations, current_item.annotations)) + + @staticmethod + def _merge_anno(a, b): + from itertools import chain + merged = [] + for item in chain(a, b): + found = False + for elem in merged: + if elem == item: + found = True + break + if not found: + merged.append(item) + + return merged + +class ProjectDataset(Dataset): + def __init__(self, project): + super().__init__() + + self._project = project + config = self.config + env = self.env + + sources = {} + for s_name, source in config.sources.items(): + s_format = source.format + if not s_format: + s_format = env.PROJECT_EXTRACTOR_NAME + options = {} + options.update(source.options) + + url = source.url + if not source.url: + url = osp.join(config.project_dir, config.sources_dir, s_name) + sources[s_name] = env.make_extractor(s_format, + url, **options) + self._sources = sources + + own_source = None + own_source_dir = osp.join(config.project_dir, config.dataset_dir) + if config.project_dir and osp.isdir(own_source_dir): + log.disable(log.INFO) + own_source = env.make_importer(DEFAULT_FORMAT)(own_source_dir) \ + .make_dataset() + log.disable(log.NOTSET) + + # merge categories + # TODO: implement properly with merging and annotations remapping + categories = {} + for source in self._sources.values(): + categories.update(source.categories()) + for source in self._sources.values(): + for cat_type, source_cat in source.categories().items(): + if not categories[cat_type] == source_cat: + raise NotImplementedError( + "Merging different categories is not implemented yet") + if own_source is not None and len(own_source) != 0: + categories.update(own_source.categories()) + self._categories = categories + + # merge items + subsets = defaultdict(lambda: Subset(self)) + for source_name, source in self._sources.items(): + log.debug("Loading '%s' source contents..." % source_name) + for item in source: + existing_item = subsets[item.subset].items.get(item.id) + if existing_item is not None: + path = existing_item.path + if item.path != path: + path = None # NOTE: move to our own dataset + item = self._merge_items(existing_item, item, path=path) + else: + s_config = config.sources[source_name] + if s_config and \ + s_config.format != env.PROJECT_EXTRACTOR_NAME: + # NOTE: consider imported sources as our own dataset + path = None + else: + path = item.path + if path is None: + path = [] + path = [source_name] + path + item = item.wrap(path=path, annotations=item.annotations) + + subsets[item.subset].items[item.id] = item + + # override with our items, fallback to existing images + if own_source is not None: + log.debug("Loading own dataset...") + for item in own_source: + if not item.has_image: + existing_item = subsets[item.subset].items.get(item.id) + if existing_item is not None: + image = None + if existing_item.has_image: + # TODO: think of image comparison + image = self._lazy_image(existing_item) + item = item.wrap(path=None, + annotations=item.annotations, image=image) + + subsets[item.subset].items[item.id] = item + + # TODO: implement subset remapping when needed + subsets_filter = config.subsets + if len(subsets_filter) != 0: + subsets = { k: v for k, v in subsets.items() if k in subsets_filter} + self._subsets = dict(subsets) + + self._length = None + + def iterate_own(self): + return self.select(lambda item: not item.path) + + def get(self, item_id, subset=None, path=None): + if path: + source = path[0] + rest_path = path[1:] + return self._sources[source].get( + item_id=item_id, subset=subset, path=rest_path) + return self._subsets[subset].items[item_id] + + def put(self, item, item_id=None, subset=None, path=None): + if path is None: + path = item.path + if path: + source = path[0] + rest_path = path[1:] + # TODO: reverse remapping + self._sources[source].put(item, + item_id=item_id, subset=subset, path=rest_path) + + if item_id is None: + item_id = item.id + if subset is None: + subset = item.subset + + item = item.wrap(path=path, annotations=item.annotations) + if item.subset not in self._subsets: + self._subsets[item.subset] = Subset(self) + self._subsets[subset].items[item_id] = item + self._length = None + + return item + + def save(self, save_dir=None, merge=False, recursive=True, + save_images=False): + if save_dir is None: + assert self.config.project_dir + save_dir = self.config.project_dir + project = self._project + else: + merge = True + + if merge: + project = Project(Config(self.config)) + project.config.remove('sources') + + save_dir = osp.abspath(save_dir) + os.makedirs(save_dir, exist_ok=True) + + dataset_save_dir = osp.join(save_dir, project.config.dataset_dir) + os.makedirs(dataset_save_dir, exist_ok=True) + + converter_kwargs = { + 'save_images': save_images, + } + + if merge: + # merge and save the resulting dataset + converter = self.env.make_converter( + DEFAULT_FORMAT, **converter_kwargs) + converter(self, dataset_save_dir) + else: + if recursive: + # children items should already be updated + # so we just save them recursively + for source in self._sources.values(): + if isinstance(source, ProjectDataset): + source.save(**converter_kwargs) + + converter = self.env.make_converter( + DEFAULT_FORMAT, **converter_kwargs) + converter(self.iterate_own(), dataset_save_dir) + + project.save(save_dir) + + @property + def env(self): + return self._project.env + + @property + def config(self): + return self._project.config + + @property + def sources(self): + return self._sources + + def _save_branch_project(self, extractor, save_dir=None): + extractor = Dataset.from_extractors(extractor) # apply lazy transforms + + # NOTE: probably this function should be in the ViewModel layer + save_dir = osp.abspath(save_dir) + if save_dir: + dst_project = Project() + else: + if not self.config.project_dir: + raise Exception("Either a save directory or a project " + "directory should be specified") + save_dir = self.config.project_dir + + dst_project = Project(Config(self.config)) + dst_project.config.remove('project_dir') + dst_project.config.remove('sources') + dst_project.config.project_name = osp.basename(save_dir) + + dst_dataset = dst_project.make_dataset() + dst_dataset.define_categories(extractor.categories()) + dst_dataset.update(extractor) + + dst_dataset.save(save_dir=save_dir, merge=True) + + def transform_project(self, method, save_dir=None, **method_kwargs): + # NOTE: probably this function should be in the ViewModel layer + if isinstance(method, str): + method = self.env.make_transform(method) + + transformed = self.transform(method, **method_kwargs) + self._save_branch_project(transformed, save_dir=save_dir) + + def apply_model(self, model, save_dir=None, batch_size=1): + # NOTE: probably this function should be in the ViewModel layer + if isinstance(model, str): + launcher = self._project.make_executable_model(model) + + self.transform_project(InferenceWrapper, launcher=launcher, + save_dir=save_dir, batch_size=batch_size) + + def export_project(self, save_dir, converter, + filter_expr=None, filter_annotations=False, remove_empty=False): + # NOTE: probably this function should be in the ViewModel layer + dataset = self + if filter_expr: + dataset = dataset.extract(filter_expr, + filter_annotations=filter_annotations, + remove_empty=remove_empty) + + save_dir = osp.abspath(save_dir) + save_dir_existed = osp.exists(save_dir) + try: + os.makedirs(save_dir, exist_ok=True) + converter(dataset, save_dir) + except Exception: + if not save_dir_existed: + shutil.rmtree(save_dir) + raise + + def extract_project(self, filter_expr, filter_annotations=False, + save_dir=None, remove_empty=False): + # NOTE: probably this function should be in the ViewModel layer + filtered = self + if filter_expr: + filtered = self.extract(filter_expr, + filter_annotations=filter_annotations, + remove_empty=remove_empty) + self._save_branch_project(filtered, save_dir=save_dir) + +class Project: + @classmethod + def load(cls, path): + path = osp.abspath(path) + config_path = osp.join(path, PROJECT_DEFAULT_CONFIG.env_dir, + PROJECT_DEFAULT_CONFIG.project_filename) + config = Config.parse(config_path) + config.project_dir = path + config.project_filename = osp.basename(config_path) + return Project(config) + + def save(self, save_dir=None): + config = self.config + + if save_dir is None: + assert config.project_dir + project_dir = config.project_dir + else: + project_dir = save_dir + + env_dir = osp.join(project_dir, config.env_dir) + save_dir = osp.abspath(env_dir) + + project_dir_existed = osp.exists(project_dir) + env_dir_existed = osp.exists(env_dir) + try: + os.makedirs(save_dir, exist_ok=True) + + config_path = osp.join(save_dir, config.project_filename) + config.dump(config_path) + except Exception: + if not env_dir_existed: + shutil.rmtree(save_dir, ignore_errors=True) + if not project_dir_existed: + shutil.rmtree(project_dir, ignore_errors=True) + raise + + @staticmethod + def generate(save_dir, config=None): + project = Project(config) + project.save(save_dir) + project.config.project_dir = save_dir + return project + + @staticmethod + def import_from(path, dataset_format, env=None, **kwargs): + if env is None: + env = Environment() + importer = env.make_importer(dataset_format) + return importer(path, **kwargs) + + def __init__(self, config=None): + self.config = Config(config, + fallback=PROJECT_DEFAULT_CONFIG, schema=PROJECT_SCHEMA) + self.env = Environment(self.config) + + def make_dataset(self): + return ProjectDataset(self) + + def add_source(self, name, value=None): + if value is None or isinstance(value, (dict, Config)): + value = Source(value) + self.config.sources[name] = value + self.env.sources.register(name, value) + + def remove_source(self, name): + self.config.sources.remove(name) + self.env.sources.unregister(name) + + def get_source(self, name): + try: + return self.config.sources[name] + except KeyError: + raise KeyError("Source '%s' is not found" % name) + + def get_subsets(self): + return self.config.subsets + + def set_subsets(self, value): + if not value: + self.config.remove('subsets') + else: + self.config.subsets = value + + def add_model(self, name, value=None): + if value is None or isinstance(value, (dict, Config)): + value = Model(value) + self.env.register_model(name, value) + self.config.models[name] = value + + def get_model(self, name): + try: + return self.env.models.get(name) + except KeyError: + raise KeyError("Model '%s' is not found" % name) + + def remove_model(self, name): + self.config.models.remove(name) + self.env.unregister_model(name) + + def make_executable_model(self, name): + model = self.get_model(name) + model.model_dir = self.local_model_dir(name) + return self.env.make_launcher(model.launcher, + **model.options, model_dir=model.model_dir) + + def make_source_project(self, name): + source = self.get_source(name) + + config = Config(self.config) + config.remove('sources') + config.remove('subsets') + project = Project(config) + project.add_source(name, source) + return project + + def local_model_dir(self, model_name): + return osp.join( + self.config.env_dir, self.config.models_dir, model_name) + + def local_source_dir(self, source_name): + return osp.join(self.config.sources_dir, source_name) + +# pylint: disable=function-redefined +def load_project_as_dataset(url): + # implement the function declared above + return Project.load(url).make_dataset() +# pylint: enable=function-redefined diff --git a/cvat-ui/src/components/modals/task-update/task-update.scss b/datumaro/datumaro/plugins/__init__.py similarity index 100% rename from cvat-ui/src/components/modals/task-update/task-update.scss rename to datumaro/datumaro/plugins/__init__.py diff --git a/cvat-ui/src/components/modals/task-update/task-update.test.tsx b/datumaro/datumaro/plugins/coco_format/__init__.py similarity index 100% rename from cvat-ui/src/components/modals/task-update/task-update.test.tsx rename to datumaro/datumaro/plugins/coco_format/__init__.py diff --git a/datumaro/datumaro/plugins/coco_format/converter.py b/datumaro/datumaro/plugins/coco_format/converter.py new file mode 100644 index 00000000000..39fe7b15402 --- /dev/null +++ b/datumaro/datumaro/plugins/coco_format/converter.py @@ -0,0 +1,624 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from enum import Enum +from itertools import groupby +import json +import logging as log +import os +import os.path as osp + +import pycocotools.mask as mask_utils + +from datumaro.components.converter import Converter +from datumaro.components.extractor import (DEFAULT_SUBSET_NAME, + AnnotationType, Points +) +from datumaro.components.cli_plugin import CliPlugin +from datumaro.util import find +from datumaro.util.image import save_image +import datumaro.util.mask_tools as mask_tools +import datumaro.util.annotation_tools as anno_tools + +from .format import CocoTask, CocoPath + + +def _cast(value, type_conv, default=None): + if value is None: + return default + try: + return type_conv(value) + except Exception: + return default + + +SegmentationMode = Enum('SegmentationMode', ['guess', 'polygons', 'mask']) + +class _TaskConverter: + def __init__(self, context): + self._min_ann_id = 1 + self._context = context + + data = { + 'licenses': [], + 'info': {}, + 'categories': [], + 'images': [], + 'annotations': [] + } + + data['licenses'].append({ + 'name': '', + 'id': 0, + 'url': '' + }) + + data['info'] = { + 'contributor': '', + 'date_created': '', + 'description': '', + 'url': '', + 'version': '', + 'year': '' + } + self._data = data + + def is_empty(self): + return len(self._data['annotations']) == 0 + + def _get_image_id(self, item): + return self._context._get_image_id(item) + + def save_image_info(self, item, filename): + if item.has_image: + h, w = item.image.size + else: + h = 0 + w = 0 + + self._data['images'].append({ + 'id': self._get_image_id(item), + 'width': int(w), + 'height': int(h), + 'file_name': _cast(filename, str, ''), + 'license': 0, + 'flickr_url': '', + 'coco_url': '', + 'date_captured': 0, + }) + + def save_categories(self, dataset): + raise NotImplementedError() + + def save_annotations(self, item): + raise NotImplementedError() + + def write(self, path): + next_id = self._min_ann_id + for ann in self.annotations: + if ann['id'] is None: + ann['id'] = next_id + next_id += 1 + + with open(path, 'w') as outfile: + json.dump(self._data, outfile) + + @property + def annotations(self): + return self._data['annotations'] + + @property + def categories(self): + return self._data['categories'] + + def _get_ann_id(self, annotation): + ann_id = annotation.id + if ann_id: + self._min_ann_id = max(ann_id, self._min_ann_id) + return ann_id + +class _ImageInfoConverter(_TaskConverter): + def is_empty(self): + return len(self._data['images']) == 0 + + def save_categories(self, dataset): + pass + + def save_annotations(self, item): + pass + +class _CaptionsConverter(_TaskConverter): + def save_categories(self, dataset): + pass + + def save_annotations(self, item): + for ann_idx, ann in enumerate(item.annotations): + if ann.type != AnnotationType.caption: + continue + + elem = { + 'id': self._get_ann_id(ann), + 'image_id': self._get_image_id(item), + 'category_id': 0, # NOTE: workaround for a bug in cocoapi + 'caption': ann.caption, + } + if 'score' in ann.attributes: + try: + elem['score'] = float(ann.attributes['score']) + except Exception as e: + log.warning("Item '%s', ann #%s: failed to convert " + "attribute 'score': %e" % (item.id, ann_idx, e)) + + self.annotations.append(elem) + +class _InstancesConverter(_TaskConverter): + def save_categories(self, dataset): + label_categories = dataset.categories().get(AnnotationType.label) + if label_categories is None: + return + + for idx, cat in enumerate(label_categories.items): + self.categories.append({ + 'id': 1 + idx, + 'name': _cast(cat.name, str, ''), + 'supercategory': _cast(cat.parent, str, ''), + }) + + @classmethod + def crop_segments(cls, instances, img_width, img_height): + instances = sorted(instances, key=lambda x: x[0].z_order) + + segment_map = [] + segments = [] + for inst_idx, (_, polygons, mask, _) in enumerate(instances): + if polygons: + segment_map.extend(inst_idx for p in polygons) + segments.extend(polygons) + elif mask is not None: + segment_map.append(inst_idx) + segments.append(mask) + + segments = mask_tools.crop_covered_segments( + segments, img_width, img_height) + + for inst_idx, inst in enumerate(instances): + new_segments = [s for si_id, s in zip(segment_map, segments) + if si_id == inst_idx] + + if not new_segments: + inst[1] = [] + inst[2] = None + continue + + if inst[1]: + inst[1] = sum(new_segments, []) + else: + mask = mask_tools.merge_masks(new_segments) + inst[2] = mask_tools.mask_to_rle(mask) + + return instances + + def find_instance_parts(self, group, img_width, img_height): + boxes = [a for a in group if a.type == AnnotationType.bbox] + polygons = [a for a in group if a.type == AnnotationType.polygon] + masks = [a for a in group if a.type == AnnotationType.mask] + + anns = boxes + polygons + masks + leader = anno_tools.find_group_leader(anns) + bbox = anno_tools.compute_bbox(anns) + mask = None + polygons = [p.points for p in polygons] + + if self._context._segmentation_mode == SegmentationMode.guess: + use_masks = True == leader.attributes.get('is_crowd', + find(masks, lambda x: x.label == leader.label) is not None) + elif self._context._segmentation_mode == SegmentationMode.polygons: + use_masks = False + elif self._context._segmentation_mode == SegmentationMode.mask: + use_masks = True + else: + raise NotImplementedError("Unexpected segmentation mode '%s'" % \ + self._context._segmentation_mode) + + if use_masks: + if polygons: + mask = mask_tools.rles_to_mask(polygons, img_width, img_height) + + if masks: + if mask is not None: + masks += [mask] + mask = mask_tools.merge_masks([m.image for m in masks]) + + if mask is not None: + mask = mask_tools.mask_to_rle(mask) + polygons = [] + else: + if masks: + mask = mask_tools.merge_masks([m.image for m in masks]) + polygons += mask_tools.mask_to_polygons(mask) + mask = None + + return [leader, polygons, mask, bbox] + + @staticmethod + def find_instance_anns(annotations): + return [a for a in annotations + if a.type in { AnnotationType.bbox, + AnnotationType.polygon, AnnotationType.mask } + ] + + @classmethod + def find_instances(cls, annotations): + return anno_tools.find_instances(cls.find_instance_anns(annotations)) + + def save_annotations(self, item): + instances = self.find_instances(item.annotations) + if not instances: + return + + if not item.has_image: + log.warn("Item '%s': skipping writing instances " + "since no image info available" % item.id) + return + h, w = item.image.size + instances = [self.find_instance_parts(i, w, h) for i in instances] + + if self._context._crop_covered: + instances = self.crop_segments(instances, w, h) + + for instance in instances: + elem = self.convert_instance(instance, item) + if elem: + self.annotations.append(elem) + + def convert_instance(self, instance, item): + ann, polygons, mask, bbox = instance + + is_crowd = mask is not None + if is_crowd: + segmentation = { + 'counts': list(int(c) for c in mask['counts']), + 'size': list(int(c) for c in mask['size']) + } + else: + segmentation = [list(map(float, p)) for p in polygons] + + area = 0 + if segmentation: + if item.has_image: + h, w = item.image.size + else: + # NOTE: here we can guess the image size as + # it is only needed for the area computation + w = bbox[0] + bbox[2] + h = bbox[1] + bbox[3] + + rles = mask_utils.frPyObjects(segmentation, h, w) + if is_crowd: + rles = [rles] + else: + rles = mask_utils.merge(rles) + area = mask_utils.area(rles) + else: + x, y, w, h = bbox + segmentation = [[x, y, x + w, y, x + w, y + h, x, y + h]] + area = w * h + + elem = { + 'id': self._get_ann_id(ann), + 'image_id': self._get_image_id(item), + 'category_id': _cast(ann.label, int, -1) + 1, + 'segmentation': segmentation, + 'area': float(area), + 'bbox': list(map(float, bbox)), + 'iscrowd': int(is_crowd), + } + if 'score' in ann.attributes: + try: + elem['score'] = float(ann.attributes['score']) + except Exception as e: + log.warning("Item '%s': failed to convert attribute " + "'score': %e" % (item.id, e)) + + return elem + +class _KeypointsConverter(_InstancesConverter): + def save_categories(self, dataset): + label_categories = dataset.categories().get(AnnotationType.label) + if label_categories is None: + return + points_categories = dataset.categories().get(AnnotationType.points) + if points_categories is None: + return + + for idx, kp_cat in points_categories.items.items(): + label_cat = label_categories.items[idx] + + cat = { + 'id': 1 + idx, + 'name': _cast(label_cat.name, str, ''), + 'supercategory': _cast(label_cat.parent, str, ''), + 'keypoints': [str(l) for l in kp_cat.labels], + 'skeleton': [int(i) for i in kp_cat.adjacent], + } + self.categories.append(cat) + + def save_annotations(self, item): + point_annotations = [a for a in item.annotations + if a.type == AnnotationType.points] + if not point_annotations: + return + + # Create annotations for solitary keypoints annotations + for points in self.find_solitary_points(item.annotations): + instance = [points, [], None, points.get_bbox()] + elem = super().convert_instance(instance, item) + elem.update(self.convert_points_object(points)) + if elem: + self.annotations.append(elem) + + # Create annotations for complete instance + keypoints annotations + super().save_annotations(item) + + @classmethod + def find_solitary_points(cls, annotations): + annotations = sorted(annotations, key=lambda a: a.group) + solitary_points = [] + + for g_id, group in groupby(annotations, lambda a: a.group): + if g_id and not cls.find_instance_anns(group): + group = [a for a in group if a.type == AnnotationType.points] + solitary_points.extend(group) + + return solitary_points + + @staticmethod + def convert_points_object(ann): + keypoints = [] + points = ann.points + visibility = ann.visibility + for index in range(0, len(points), 2): + kp = points[index : index + 2] + state = visibility[index // 2].value + keypoints.extend([*kp, state]) + + num_annotated = len([v for v in visibility \ + if v != Points.Visibility.absent]) + + return { + 'keypoints': keypoints, + 'num_keypoints': num_annotated, + } + + def convert_instance(self, instance, item): + points_ann = find(item.annotations, lambda x: \ + x.type == AnnotationType.points and x.group == instance[0].group) + if not points_ann: + return None + + elem = super().convert_instance(instance, item) + elem.update(self.convert_points_object(points_ann)) + + return elem + +class _LabelsConverter(_TaskConverter): + def save_categories(self, dataset): + label_categories = dataset.categories().get(AnnotationType.label) + if label_categories is None: + return + + for idx, cat in enumerate(label_categories.items): + self.categories.append({ + 'id': 1 + idx, + 'name': _cast(cat.name, str, ''), + 'supercategory': _cast(cat.parent, str, ''), + }) + + def save_annotations(self, item): + for ann in item.annotations: + if ann.type != AnnotationType.label: + continue + + elem = { + 'id': self._get_ann_id(ann), + 'image_id': self._get_image_id(item), + 'category_id': int(ann.label) + 1, + } + if 'score' in ann.attributes: + try: + elem['score'] = float(ann.attributes['score']) + except Exception as e: + log.warning("Item '%s': failed to convert attribute " + "'score': %e" % (item.id, e)) + + self.annotations.append(elem) + +class _Converter: + _TASK_CONVERTER = { + CocoTask.image_info: _ImageInfoConverter, + CocoTask.instances: _InstancesConverter, + CocoTask.person_keypoints: _KeypointsConverter, + CocoTask.captions: _CaptionsConverter, + CocoTask.labels: _LabelsConverter, + } + + def __init__(self, extractor, save_dir, + tasks=None, save_images=False, segmentation_mode=None, + crop_covered=False): + assert tasks is None or isinstance(tasks, (CocoTask, list)) + if tasks is None: + tasks = list(self._TASK_CONVERTER) + elif isinstance(tasks, CocoTask): + tasks = [tasks] + else: + for t in tasks: + assert t in CocoTask + self._tasks = tasks + + self._extractor = extractor + self._save_dir = save_dir + + self._save_images = save_images + + assert segmentation_mode is None or \ + segmentation_mode in SegmentationMode or \ + isinstance(segmentation_mode, str) + if segmentation_mode is None: + segmentation_mode = SegmentationMode.guess + if isinstance(segmentation_mode, str): + segmentation_mode = SegmentationMode[segmentation_mode] + self._segmentation_mode = segmentation_mode + + self._crop_covered = crop_covered + + self._image_ids = {} + + def _make_dirs(self): + self._images_dir = osp.join(self._save_dir, CocoPath.IMAGES_DIR) + os.makedirs(self._images_dir, exist_ok=True) + + self._ann_dir = osp.join(self._save_dir, CocoPath.ANNOTATIONS_DIR) + os.makedirs(self._ann_dir, exist_ok=True) + + def _make_task_converter(self, task): + if task not in self._TASK_CONVERTER: + raise NotImplementedError() + return self._TASK_CONVERTER[task](self) + + def _make_task_converters(self): + return { + task: self._make_task_converter(task) for task in self._tasks + } + + def _get_image_id(self, item): + image_id = self._image_ids.get(item.id) + if image_id is None: + image_id = _cast(item.id, int, len(self._image_ids) + 1) + self._image_ids[item.id] = image_id + return image_id + + def _save_image(self, item): + image = item.image.data + if image is None: + log.warning("Item '%s' has no image" % item.id) + return '' + + filename = item.image.filename + if filename: + filename = osp.splitext(filename)[0] + else: + filename = item.id + filename += CocoPath.IMAGE_EXT + path = osp.join(self._images_dir, filename) + save_image(path, image) + return filename + + def convert(self): + self._make_dirs() + + subsets = self._extractor.subsets() + if len(subsets) == 0: + subsets = [ None ] + + for subset_name in subsets: + if subset_name: + subset = self._extractor.get_subset(subset_name) + else: + subset_name = DEFAULT_SUBSET_NAME + subset = self._extractor + + task_converters = self._make_task_converters() + for task_conv in task_converters.values(): + task_conv.save_categories(subset) + for item in subset: + filename = '' + if item.has_image: + filename = item.image.filename + if self._save_images: + if item.has_image: + filename = self._save_image(item) + else: + log.debug("Item '%s' has no image info" % item.id) + for task_conv in task_converters.values(): + task_conv.save_image_info(item, filename) + task_conv.save_annotations(item) + + for task, task_conv in task_converters.items(): + if not task_conv.is_empty(): + task_conv.write(osp.join(self._ann_dir, + '%s_%s.json' % (task.name, subset_name))) + +class CocoConverter(Converter, CliPlugin): + @staticmethod + def _split_tasks_string(s): + return [CocoTask[i.strip()] for i in s.split(',')] + + @classmethod + def build_cmdline_parser(cls, **kwargs): + parser = super().build_cmdline_parser(**kwargs) + parser.add_argument('--save-images', action='store_true', + help="Save images (default: %(default)s)") + parser.add_argument('--segmentation-mode', + choices=[m.name for m in SegmentationMode], + default=SegmentationMode.guess.name, + help=""" + Save mode for instance segmentation:|n + - '{sm.guess.name}': guess the mode for each instance,|n + |s|suse 'is_crowd' attribute as hint|n + - '{sm.polygons.name}': save polygons,|n + |s|smerge and convert masks, prefer polygons|n + - '{sm.mask.name}': save masks,|n + |s|smerge and convert polygons, prefer masks|n + Default: %(default)s. + """.format(sm=SegmentationMode)) + parser.add_argument('--crop-covered', action='store_true', + help="Crop covered segments so that background objects' " + "segmentation was more accurate (default: %(default)s)") + parser.add_argument('--tasks', type=cls._split_tasks_string, + default=None, + help="COCO task filter, comma-separated list of {%s} " + "(default: all)" % ', '.join([t.name for t in CocoTask])) + return parser + + def __init__(self, + tasks=None, save_images=False, segmentation_mode=None, + crop_covered=False): + super().__init__() + + self._options = { + 'tasks': tasks, + 'save_images': save_images, + 'segmentation_mode': segmentation_mode, + 'crop_covered': crop_covered, + } + + def __call__(self, extractor, save_dir): + converter = _Converter(extractor, save_dir, **self._options) + converter.convert() + +class CocoInstancesConverter(CocoConverter): + def __init__(self, **kwargs): + kwargs['tasks'] = CocoTask.instances + super().__init__(**kwargs) + +class CocoImageInfoConverter(CocoConverter): + def __init__(self, **kwargs): + kwargs['tasks'] = CocoTask.image_info + super().__init__(**kwargs) + +class CocoPersonKeypointsConverter(CocoConverter): + def __init__(self, **kwargs): + kwargs['tasks'] = CocoTask.person_keypoints + super().__init__(**kwargs) + +class CocoCaptionsConverter(CocoConverter): + def __init__(self, **kwargs): + kwargs['tasks'] = CocoTask.captions + super().__init__(**kwargs) + +class CocoLabelsConverter(CocoConverter): + def __init__(self, **kwargs): + kwargs['tasks'] = CocoTask.labels + super().__init__(**kwargs) diff --git a/datumaro/datumaro/plugins/coco_format/extractor.py b/datumaro/datumaro/plugins/coco_format/extractor.py new file mode 100644 index 00000000000..730c38350dd --- /dev/null +++ b/datumaro/datumaro/plugins/coco_format/extractor.py @@ -0,0 +1,264 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from collections import OrderedDict +import logging as log +import os.path as osp + +from pycocotools.coco import COCO +import pycocotools.mask as mask_utils + +from datumaro.components.extractor import (SourceExtractor, + DEFAULT_SUBSET_NAME, DatasetItem, + AnnotationType, Label, RleMask, Points, Polygon, Bbox, Caption, + LabelCategories, PointsCategories +) +from datumaro.util.image import Image + +from .format import CocoTask, CocoPath + + +class _CocoExtractor(SourceExtractor): + def __init__(self, path, task, merge_instance_polygons=False): + super().__init__() + + assert osp.isfile(path) + rootpath = path.rsplit(CocoPath.ANNOTATIONS_DIR, maxsplit=1)[0] + self._path = rootpath + self._task = task + + subset = osp.splitext(osp.basename(path))[0] \ + .rsplit('_', maxsplit=1)[1] + if subset == DEFAULT_SUBSET_NAME: + subset = None + self._subset = subset + + self._merge_instance_polygons = merge_instance_polygons + + loader = self._make_subset_loader(path) + self._load_categories(loader) + self._items = self._load_items(loader) + + def categories(self): + return self._categories + + def __iter__(self): + for item in self._items.values(): + yield item + + def __len__(self): + return len(self._items) + + def subsets(self): + if self._subset: + return [self._subset] + return None + + def get_subset(self, name): + if name != self._subset: + return None + return self + + @staticmethod + def _make_subset_loader(path): + # COCO API has an 'unclosed file' warning + coco_api = COCO() + with open(path, 'r') as f: + import json + dataset = json.load(f) + + coco_api.dataset = dataset + coco_api.createIndex() + return coco_api + + def _load_categories(self, loader): + self._categories = {} + + if self._task in [CocoTask.instances, CocoTask.labels, + CocoTask.person_keypoints, CocoTask.stuff, CocoTask.panoptic]: + label_categories, label_map = self._load_label_categories(loader) + self._categories[AnnotationType.label] = label_categories + self._label_map = label_map + + if self._task == CocoTask.person_keypoints: + person_kp_categories = self._load_person_kp_categories(loader) + self._categories[AnnotationType.points] = person_kp_categories + + # pylint: disable=no-self-use + def _load_label_categories(self, loader): + catIds = loader.getCatIds() + cats = loader.loadCats(catIds) + + categories = LabelCategories() + label_map = {} + for idx, cat in enumerate(cats): + label_map[cat['id']] = idx + categories.add(name=cat['name'], parent=cat['supercategory']) + + return categories, label_map + # pylint: enable=no-self-use + + def _load_person_kp_categories(self, loader): + catIds = loader.getCatIds() + cats = loader.loadCats(catIds) + + categories = PointsCategories() + for cat in cats: + label_id = self._label_map[cat['id']] + categories.add(label_id=label_id, + labels=cat['keypoints'], adjacent=cat['skeleton']) + + return categories + + def _load_items(self, loader): + items = OrderedDict() + + for img_id in loader.getImgIds(): + image_info = loader.loadImgs(img_id)[0] + image_path = self._find_image(image_info['file_name']) + if not image_path: + image_path = image_info['file_name'] + image_size = (image_info.get('height'), image_info.get('width')) + if all(image_size): + image_size = (int(image_size[0]), int(image_size[1])) + else: + image_size = None + image = Image(path=image_path, size=image_size) + + anns = loader.getAnnIds(imgIds=img_id) + anns = loader.loadAnns(anns) + anns = sum((self._load_annotations(a, image_info) for a in anns), []) + + items[img_id] = DatasetItem(id=img_id, subset=self._subset, + image=image, annotations=anns) + + return items + + def _get_label_id(self, ann): + cat_id = ann.get('category_id') + if cat_id in [0, None]: + return None + return self._label_map[cat_id] + + def _load_annotations(self, ann, image_info=None): + parsed_annotations = [] + + ann_id = ann.get('id') + + attributes = {} + if 'score' in ann: + attributes['score'] = ann['score'] + + group = ann_id # make sure all tasks' annotations are merged + + if self._task in [CocoTask.instances, CocoTask.person_keypoints]: + x, y, w, h = ann['bbox'] + label_id = self._get_label_id(ann) + + is_crowd = bool(ann['iscrowd']) + attributes['is_crowd'] = is_crowd + + if self._task is CocoTask.person_keypoints: + keypoints = ann['keypoints'] + points = [p for i, p in enumerate(keypoints) if i % 3 != 2] + visibility = keypoints[2::3] + parsed_annotations.append( + Points(points, visibility, label=label_id, + id=ann_id, attributes=attributes, group=group) + ) + + segmentation = ann.get('segmentation') + if segmentation and segmentation != [[]]: + rle = None + + if isinstance(segmentation, list): + if not self._merge_instance_polygons: + # polygon - a single object can consist of multiple parts + for polygon_points in segmentation: + parsed_annotations.append(Polygon( + points=polygon_points, label=label_id, + id=ann_id, attributes=attributes, group=group + )) + else: + # merge all parts into a single mask RLE + img_h = image_info['height'] + img_w = image_info['width'] + rles = mask_utils.frPyObjects(segmentation, img_h, img_w) + rle = mask_utils.merge(rles) + elif isinstance(segmentation['counts'], list): + # uncompressed RLE + img_h = image_info['height'] + img_w = image_info['width'] + mask_h, mask_w = segmentation['size'] + if img_h == mask_h and img_w == mask_w: + rle = mask_utils.frPyObjects( + [segmentation], mask_h, mask_w)[0] + else: + log.warning("item #%s: mask #%s " + "does not match image size: %s vs. %s. " + "Skipping this annotation.", + image_info['id'], ann_id, + (mask_h, mask_w), (img_h, img_w) + ) + else: + # compressed RLE + rle = segmentation + + if rle is not None: + parsed_annotations.append(RleMask(rle=rle, label=label_id, + id=ann_id, attributes=attributes, group=group + )) + else: + parsed_annotations.append( + Bbox(x, y, w, h, label=label_id, + id=ann_id, attributes=attributes, group=group) + ) + elif self._task is CocoTask.labels: + label_id = self._get_label_id(ann) + parsed_annotations.append( + Label(label=label_id, + id=ann_id, attributes=attributes, group=group) + ) + elif self._task is CocoTask.captions: + caption = ann['caption'] + parsed_annotations.append( + Caption(caption, + id=ann_id, attributes=attributes, group=group) + ) + else: + raise NotImplementedError() + + return parsed_annotations + + def _find_image(self, file_name): + images_dir = osp.join(self._path, CocoPath.IMAGES_DIR) + search_paths = [ + osp.join(images_dir, file_name), + osp.join(images_dir, self._subset or DEFAULT_SUBSET_NAME, file_name), + ] + for image_path in search_paths: + if osp.exists(image_path): + return image_path + return None + +class CocoImageInfoExtractor(_CocoExtractor): + def __init__(self, path, **kwargs): + super().__init__(path, task=CocoTask.image_info, **kwargs) + +class CocoCaptionsExtractor(_CocoExtractor): + def __init__(self, path, **kwargs): + super().__init__(path, task=CocoTask.captions, **kwargs) + +class CocoInstancesExtractor(_CocoExtractor): + def __init__(self, path, **kwargs): + super().__init__(path, task=CocoTask.instances, **kwargs) + +class CocoPersonKeypointsExtractor(_CocoExtractor): + def __init__(self, path, **kwargs): + super().__init__(path, task=CocoTask.person_keypoints, **kwargs) + +class CocoLabelsExtractor(_CocoExtractor): + def __init__(self, path, **kwargs): + super().__init__(path, task=CocoTask.labels, **kwargs) \ No newline at end of file diff --git a/datumaro/datumaro/plugins/coco_format/format.py b/datumaro/datumaro/plugins/coco_format/format.py new file mode 100644 index 00000000000..2a9cddc2c1b --- /dev/null +++ b/datumaro/datumaro/plugins/coco_format/format.py @@ -0,0 +1,23 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from enum import Enum + + +CocoTask = Enum('CocoTask', [ + 'instances', + 'person_keypoints', + 'captions', + 'labels', # extension, does not exist in the original COCO format + 'image_info', + 'panoptic', + 'stuff', +]) + +class CocoPath: + IMAGES_DIR = 'images' + ANNOTATIONS_DIR = 'annotations' + + IMAGE_EXT = '.jpg' \ No newline at end of file diff --git a/datumaro/datumaro/plugins/coco_format/importer.py b/datumaro/datumaro/plugins/coco_format/importer.py new file mode 100644 index 00000000000..bb129d7aed5 --- /dev/null +++ b/datumaro/datumaro/plugins/coco_format/importer.py @@ -0,0 +1,73 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from collections import defaultdict +from glob import glob +import logging as log +import os.path as osp + +from datumaro.components.extractor import Importer + +from .format import CocoTask, CocoPath + + +class CocoImporter(Importer): + _COCO_EXTRACTORS = { + CocoTask.instances: 'coco_instances', + CocoTask.person_keypoints: 'coco_person_keypoints', + CocoTask.captions: 'coco_captions', + CocoTask.labels: 'coco_labels', + CocoTask.image_info: 'coco_image_info', + } + + def __call__(self, path, **extra_params): + from datumaro.components.project import Project # cyclic import + project = Project() + + subsets = self.find_subsets(path) + + if len(subsets) == 0: + raise Exception("Failed to find 'coco' dataset at '%s'" % path) + + for ann_files in subsets.values(): + for ann_type, ann_file in ann_files.items(): + log.info("Found a dataset at '%s'" % ann_file) + + source_name = osp.splitext(osp.basename(ann_file))[0] + project.add_source(source_name, { + 'url': ann_file, + 'format': self._COCO_EXTRACTORS[ann_type], + 'options': dict(extra_params), + }) + + return project + + @staticmethod + def find_subsets(path): + if path.endswith('.json') and osp.isfile(path): + subset_paths = [path] + else: + subset_paths = glob(osp.join(path, '*_*.json')) + + if osp.basename(osp.normpath(path)) != CocoPath.ANNOTATIONS_DIR: + path = osp.join(path, CocoPath.ANNOTATIONS_DIR) + subset_paths += glob(osp.join(path, '*_*.json')) + + subsets = defaultdict(dict) + for subset_path in subset_paths: + name_parts = osp.splitext(osp.basename(subset_path))[0] \ + .rsplit('_', maxsplit=1) + + ann_type = name_parts[0] + try: + ann_type = CocoTask[ann_type] + except KeyError: + log.warn("Skipping '%s': unknown subset " + "type '%s', the only known are: %s" % \ + (subset_path, ann_type, + ', '.join([e.name for e in CocoTask]))) + subset_name = name_parts[1] + subsets[subset_name][ann_type] = subset_path + return dict(subsets) \ No newline at end of file diff --git a/datumaro/datumaro/plugins/cvat_format/__init__.py b/datumaro/datumaro/plugins/cvat_format/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/datumaro/datumaro/plugins/cvat_format/converter.py b/datumaro/datumaro/plugins/cvat_format/converter.py new file mode 100644 index 00000000000..a64addad02b --- /dev/null +++ b/datumaro/datumaro/plugins/cvat_format/converter.py @@ -0,0 +1,387 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from collections import OrderedDict +import logging as log +import os +import os.path as osp +from xml.sax.saxutils import XMLGenerator + +from datumaro.components.cli_plugin import CliPlugin +from datumaro.components.converter import Converter +from datumaro.components.extractor import DEFAULT_SUBSET_NAME, AnnotationType +from datumaro.util.image import save_image + +from .format import CvatPath + + +def _cast(value, type_conv, default=None): + if value is None: + return default + try: + return type_conv(value) + except Exception: + return default + +def pairwise(iterable): + a = iter(iterable) + return zip(a, a) + +class XmlAnnotationWriter: + VERSION = '1.1' + + def __init__(self, f): + self.xmlgen = XMLGenerator(f, 'utf-8') + self._level = 0 + + def _indent(self, newline = True): + if newline: + self.xmlgen.ignorableWhitespace('\n') + self.xmlgen.ignorableWhitespace(' ' * self._level) + + def _add_version(self): + self._indent() + self.xmlgen.startElement('version', {}) + self.xmlgen.characters(self.VERSION) + self.xmlgen.endElement('version') + + def open_root(self): + self.xmlgen.startDocument() + self.xmlgen.startElement('annotations', {}) + self._level += 1 + self._add_version() + + def _add_meta(self, meta): + self._level += 1 + for k, v in meta.items(): + if isinstance(v, OrderedDict): + self._indent() + self.xmlgen.startElement(k, {}) + self._add_meta(v) + self._indent() + self.xmlgen.endElement(k) + elif isinstance(v, list): + self._indent() + self.xmlgen.startElement(k, {}) + for tup in v: + self._add_meta(OrderedDict([tup])) + self._indent() + self.xmlgen.endElement(k) + else: + self._indent() + self.xmlgen.startElement(k, {}) + self.xmlgen.characters(v) + self.xmlgen.endElement(k) + self._level -= 1 + + def write_meta(self, meta): + self._indent() + self.xmlgen.startElement('meta', {}) + self._add_meta(meta) + self._indent() + self.xmlgen.endElement('meta') + + def open_track(self, track): + self._indent() + self.xmlgen.startElement('track', track) + self._level += 1 + + def open_image(self, image): + self._indent() + self.xmlgen.startElement('image', image) + self._level += 1 + + def open_box(self, box): + self._indent() + self.xmlgen.startElement('box', box) + self._level += 1 + + def open_polygon(self, polygon): + self._indent() + self.xmlgen.startElement('polygon', polygon) + self._level += 1 + + def open_polyline(self, polyline): + self._indent() + self.xmlgen.startElement('polyline', polyline) + self._level += 1 + + def open_points(self, points): + self._indent() + self.xmlgen.startElement('points', points) + self._level += 1 + + def open_tag(self, tag): + self._indent() + self.xmlgen.startElement("tag", tag) + self._level += 1 + + def add_attribute(self, attribute): + self._indent() + self.xmlgen.startElement('attribute', {'name': attribute['name']}) + self.xmlgen.characters(attribute['value']) + self.xmlgen.endElement('attribute') + + def _close_element(self, element): + self._level -= 1 + self._indent() + self.xmlgen.endElement(element) + + def close_box(self): + self._close_element('box') + + def close_polygon(self): + self._close_element('polygon') + + def close_polyline(self): + self._close_element('polyline') + + def close_points(self): + self._close_element('points') + + def close_tag(self): + self._close_element('tag') + + def close_image(self): + self._close_element('image') + + def close_track(self): + self._close_element('track') + + def close_root(self): + self._close_element('annotations') + self.xmlgen.endDocument() + +class _SubsetWriter: + def __init__(self, file, name, extractor, context): + self._writer = XmlAnnotationWriter(file) + self._name = name + self._extractor = extractor + self._context = context + + def write(self): + self._writer.open_root() + self._write_meta() + + for index, item in enumerate(self._extractor): + self._write_item(item, index) + + self._writer.close_root() + + def _save_image(self, item): + image = item.image.data + if image is None: + log.warning("Item '%s' has no image" % item.id) + return '' + + filename = item.image.filename + if filename: + filename = osp.splitext(filename)[0] + else: + filename = item.id + filename += CvatPath.IMAGE_EXT + image_path = osp.join(self._context._images_dir, filename) + save_image(image_path, image) + return filename + + def _write_item(self, item, index): + image_info = OrderedDict([ + ("id", str(_cast(item.id, int, index))), + ]) + if item.has_image: + size = item.image.size + if size: + h, w = size + image_info["width"] = str(w) + image_info["height"] = str(h) + + filename = item.image.filename + if self._context._save_images: + filename = self._save_image(item) + image_info["name"] = filename + else: + log.debug("Item '%s' has no image info" % item.id) + self._writer.open_image(image_info) + + for ann in item.annotations: + if ann.type in {AnnotationType.points, AnnotationType.polyline, + AnnotationType.polygon, AnnotationType.bbox}: + self._write_shape(ann) + elif ann.type == AnnotationType.label: + self._write_tag(ann) + else: + continue + + self._writer.close_image() + + def _write_meta(self): + label_cat = self._extractor.categories()[AnnotationType.label] + meta = OrderedDict([ + ("task", OrderedDict([ + ("id", ""), + ("name", self._name), + ("size", str(len(self._extractor))), + ("mode", "annotation"), + ("overlap", ""), + ("start_frame", "0"), + ("stop_frame", str(len(self._extractor))), + ("frame_filter", ""), + ("z_order", "True"), + + ("labels", [ + ("label", OrderedDict([ + ("name", label.name), + ("attributes", [ + ("attribute", OrderedDict([ + ("name", attr), + ("mutable", "True"), + ("input_type", "text"), + ("default_value", ""), + ("values", ""), + ])) for attr in label.attributes + ]) + ])) for label in label_cat.items + ]), + ])), + ]) + self._writer.write_meta(meta) + + def _get_label(self, label_id): + label_cat = self._extractor.categories()[AnnotationType.label] + return label_cat.items[label_id] + + def _write_shape(self, shape): + if shape.label is None: + return + + shape_data = OrderedDict([ + ("label", self._get_label(shape.label).name), + ("occluded", str(int(shape.attributes.get('occluded', False)))), + ]) + + if shape.type == AnnotationType.bbox: + shape_data.update(OrderedDict([ + ("xtl", "{:.2f}".format(shape.points[0])), + ("ytl", "{:.2f}".format(shape.points[1])), + ("xbr", "{:.2f}".format(shape.points[2])), + ("ybr", "{:.2f}".format(shape.points[3])) + ])) + else: + shape_data.update(OrderedDict([ + ("points", ';'.join(( + ','.join(( + "{:.2f}".format(x), + "{:.2f}".format(y) + )) for x, y in pairwise(shape.points)) + )), + ])) + + shape_data['z_order'] = str(int(shape.attributes.get('z_order', 0))) + if shape.group: + shape_data['group_id'] = str(shape.group) + + if shape.type == AnnotationType.bbox: + self._writer.open_box(shape_data) + elif shape.type == AnnotationType.polygon: + self._writer.open_polygon(shape_data) + elif shape.type == AnnotationType.polyline: + self._writer.open_polyline(shape_data) + elif shape.type == AnnotationType.points: + self._writer.open_points(shape_data) + else: + raise NotImplementedError("unknown shape type") + + for attr_name, attr_value in shape.attributes.items(): + if isinstance(attr_value, bool): + attr_value = 'true' if attr_value else 'false' + if attr_name in self._get_label(shape.label).attributes: + self._writer.add_attribute(OrderedDict([ + ("name", str(attr_name)), + ("value", str(attr_value)), + ])) + + if shape.type == AnnotationType.bbox: + self._writer.close_box() + elif shape.type == AnnotationType.polygon: + self._writer.close_polygon() + elif shape.type == AnnotationType.polyline: + self._writer.close_polyline() + elif shape.type == AnnotationType.points: + self._writer.close_points() + else: + raise NotImplementedError("unknown shape type") + + def _write_tag(self, label): + if label.label is None: + return + + tag_data = OrderedDict([ + ('label', self._get_label(label.label).name), + ]) + if label.group: + tag_data['group_id'] = str(label.group) + self._writer.open_tag(tag_data) + + for attr_name, attr_value in label.attributes.items(): + if isinstance(attr_value, bool): + attr_value = 'true' if attr_value else 'false' + if attr_name in self._get_label(label.label).attributes: + self._writer.add_attribute(OrderedDict([ + ("name", str(attr_name)), + ("value", str(attr_value)), + ])) + + self._writer.close_tag() + +class _Converter: + def __init__(self, extractor, save_dir, save_images=False): + self._extractor = extractor + self._save_dir = save_dir + self._save_images = save_images + + def convert(self): + os.makedirs(self._save_dir, exist_ok=True) + + images_dir = osp.join(self._save_dir, CvatPath.IMAGES_DIR) + os.makedirs(images_dir, exist_ok=True) + self._images_dir = images_dir + + annotations_dir = osp.join(self._save_dir, CvatPath.ANNOTATIONS_DIR) + os.makedirs(annotations_dir, exist_ok=True) + self._annotations_dir = annotations_dir + + subsets = self._extractor.subsets() + if len(subsets) == 0: + subsets = [ None ] + + for subset_name in subsets: + if subset_name: + subset = self._extractor.get_subset(subset_name) + else: + subset_name = DEFAULT_SUBSET_NAME + subset = self._extractor + + with open(osp.join(annotations_dir, '%s.xml' % subset_name), 'w') as f: + writer = _SubsetWriter(f, subset_name, subset, self) + writer.write() + +class CvatConverter(Converter, CliPlugin): + @classmethod + def build_cmdline_parser(cls, **kwargs): + parser = super().__init__(**kwargs) + parser.add_argument('--save-images', action='store_true', + help="Save images (default: %(default)s)") + return parser + + def __init__(self, save_images=False): + super().__init__() + + self._options = { + 'save_images': save_images, + } + + def __call__(self, extractor, save_dir): + converter = _Converter(extractor, save_dir, **self._options) + converter.convert() \ No newline at end of file diff --git a/datumaro/datumaro/plugins/cvat_format/extractor.py b/datumaro/datumaro/plugins/cvat_format/extractor.py new file mode 100644 index 00000000000..407c551d014 --- /dev/null +++ b/datumaro/datumaro/plugins/cvat_format/extractor.py @@ -0,0 +1,356 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from collections import OrderedDict +import os.path as osp +import xml.etree as ET + +from datumaro.components.extractor import (SourceExtractor, + DEFAULT_SUBSET_NAME, DatasetItem, + AnnotationType, Points, Polygon, PolyLine, Bbox, Label, + LabelCategories +) +from datumaro.util.image import Image + +from .format import CvatPath + + +class CvatExtractor(SourceExtractor): + _SUPPORTED_SHAPES = ('box', 'polygon', 'polyline', 'points') + + def __init__(self, path): + super().__init__() + + assert osp.isfile(path) + rootpath = '' + if path.endswith(osp.join(CvatPath.ANNOTATIONS_DIR, osp.basename(path))): + rootpath = path.rsplit(CvatPath.ANNOTATIONS_DIR, maxsplit=1)[0] + images_dir = '' + if rootpath and osp.isdir(osp.join(rootpath, CvatPath.IMAGES_DIR)): + images_dir = osp.join(rootpath, CvatPath.IMAGES_DIR) + self._images_dir = images_dir + self._path = path + + subset = osp.splitext(osp.basename(path))[0] + if subset == DEFAULT_SUBSET_NAME: + subset = None + self._subset = subset + + items, categories = self._parse(path) + self._items = self._load_items(items) + self._categories = categories + + def categories(self): + return self._categories + + def __iter__(self): + for item in self._items.values(): + yield item + + def __len__(self): + return len(self._items) + + def subsets(self): + if self._subset: + return [self._subset] + return None + + def get_subset(self, name): + if name != self._subset: + return None + return self + + @classmethod + def _parse(cls, path): + context = ET.ElementTree.iterparse(path, events=("start", "end")) + context = iter(context) + + categories, frame_size = cls._parse_meta(context) + + items = OrderedDict() + + track = None + shape = None + tag = None + attributes = None + image = None + for ev, el in context: + if ev == 'start': + if el.tag == 'track': + track = { + 'id': el.attrib.get('id'), + 'label': el.attrib.get('label'), + 'group': int(el.attrib.get('group_id', 0)), + 'height': frame_size[0], + 'width': frame_size[1], + } + elif el.tag == 'image': + image = { + 'name': el.attrib.get('name'), + 'frame': el.attrib['id'], + 'width': el.attrib.get('width'), + 'height': el.attrib.get('height'), + } + elif el.tag in cls._SUPPORTED_SHAPES and (track or image): + attributes = {} + shape = { + 'type': None, + 'attributes': attributes, + } + if track: + shape.update(track) + if image: + shape.update(image) + elif el.tag == 'tag' and image: + attributes = {} + tag = { + 'frame': image['frame'], + 'attributes': attributes, + 'group': int(el.attrib.get('group_id', 0)), + 'label': el.attrib['label'], + } + elif ev == 'end': + if el.tag == 'attribute' and attributes is not None: + attr_value = el.text + if el.text in ['true', 'false']: + attr_value = attr_value == 'true' + else: + try: + attr_value = float(attr_value) + except Exception: + pass + attributes[el.attrib['name']] = attr_value + elif el.tag in cls._SUPPORTED_SHAPES: + if track is not None: + shape['frame'] = el.attrib['frame'] + shape['outside'] = (el.attrib.get('outside') == '1') + shape['keyframe'] = (el.attrib.get('keyframe') == '1') + if image is not None: + shape['label'] = el.attrib.get('label') + shape['group'] = int(el.attrib.get('group_id', 0)) + + shape['type'] = el.tag + shape['occluded'] = (el.attrib.get('occluded') == '1') + shape['z_order'] = int(el.attrib.get('z_order', 0)) + + if el.tag == 'box': + shape['points'] = list(map(float, [ + el.attrib['xtl'], el.attrib['ytl'], + el.attrib['xbr'], el.attrib['ybr'], + ])) + else: + shape['points'] = [] + for pair in el.attrib['points'].split(';'): + shape['points'].extend(map(float, pair.split(','))) + + frame_desc = items.get(shape['frame'], {'annotations': []}) + frame_desc['annotations'].append( + cls._parse_shape_ann(shape, categories)) + items[shape['frame']] = frame_desc + shape = None + + elif el.tag == 'tag': + frame_desc = items.get(tag['frame'], {'annotations': []}) + frame_desc['annotations'].append( + cls._parse_tag_ann(tag, categories)) + items[tag['frame']] = frame_desc + tag = None + elif el.tag == 'track': + track = None + elif el.tag == 'image': + frame_desc = items.get(image['frame'], {'annotations': []}) + frame_desc.update({ + 'name': image.get('name'), + 'height': image.get('height'), + 'width': image.get('width'), + }) + items[image['frame']] = frame_desc + image = None + el.clear() + + return items, categories + + @staticmethod + def _parse_meta(context): + ev, el = next(context) + if not (ev == 'start' and el.tag == 'annotations'): + raise Exception("Unexpected token ") + + categories = {} + + frame_size = None + has_z_order = False + mode = 'annotation' + labels = OrderedDict() + label = None + + # Recursive descent parser + el = None + states = ['annotations'] + def accepted(expected_state, tag, next_state=None): + state = states[-1] + if state == expected_state and el is not None and el.tag == tag: + if not next_state: + next_state = tag + states.append(next_state) + return True + return False + def consumed(expected_state, tag): + state = states[-1] + if state == expected_state and el is not None and el.tag == tag: + states.pop() + return True + return False + + for ev, el in context: + if ev == 'start': + if accepted('annotations', 'meta'): pass + elif accepted('meta', 'task'): pass + elif accepted('task', 'z_order'): pass + elif accepted('task', 'original_size'): + frame_size = [None, None] + elif accepted('original_size', 'height', next_state='frame_height'): pass + elif accepted('original_size', 'width', next_state='frame_width'): pass + elif accepted('task', 'labels'): pass + elif accepted('labels', 'label'): + label = { 'name': None, 'attributes': set() } + elif accepted('label', 'name', next_state='label_name'): pass + elif accepted('label', 'attributes'): pass + elif accepted('attributes', 'attribute'): pass + elif accepted('attribute', 'name', next_state='attr_name'): pass + elif accepted('annotations', 'image') or \ + accepted('annotations', 'track') or \ + accepted('annotations', 'tag'): + break + else: + pass + elif ev == 'end': + if consumed('meta', 'meta'): + break + elif consumed('task', 'task'): pass + elif consumed('z_order', 'z_order'): + has_z_order = (el.text == 'True') + elif consumed('original_size', 'original_size'): pass + elif consumed('frame_height', 'height'): + frame_size[0] = int(el.text) + elif consumed('frame_width', 'width'): + frame_size[1] = int(el.text) + elif consumed('label_name', 'name'): + label['name'] = el.text + elif consumed('attr_name', 'name'): + label['attributes'].add(el.text) + elif consumed('attribute', 'attribute'): pass + elif consumed('attributes', 'attributes'): pass + elif consumed('label', 'label'): + labels[label['name']] = label['attributes'] + label = None + elif consumed('labels', 'labels'): pass + else: + pass + + assert len(states) == 1 and states[0] == 'annotations', \ + "Expected 'meta' section in the annotation file, path: %s" % states + + common_attrs = ['occluded'] + if has_z_order: + common_attrs.append('z_order') + if mode == 'interpolation': + common_attrs.append('keyframe') + common_attrs.append('outside') + + label_cat = LabelCategories(attributes=common_attrs) + for label, attrs in labels.items(): + label_cat.add(label, attributes=attrs) + + categories[AnnotationType.label] = label_cat + + return categories, frame_size + + @classmethod + def _parse_shape_ann(cls, ann, categories): + ann_id = ann.get('id') + ann_type = ann['type'] + + attributes = ann.get('attributes', {}) + if 'occluded' in categories[AnnotationType.label].attributes: + attributes['occluded'] = ann.get('occluded', False) + if 'z_order' in categories[AnnotationType.label].attributes: + attributes['z_order'] = ann.get('z_order', 0) + if 'outside' in categories[AnnotationType.label].attributes: + attributes['outside'] = ann.get('outside', False) + if 'keyframe' in categories[AnnotationType.label].attributes: + attributes['keyframe'] = ann.get('keyframe', False) + + group = ann.get('group') + + label = ann.get('label') + label_id = categories[AnnotationType.label].find(label)[0] + + points = ann.get('points', []) + + if ann_type == 'polyline': + return PolyLine(points, label=label_id, + id=ann_id, attributes=attributes, group=group) + + elif ann_type == 'polygon': + return Polygon(points, label=label_id, + id=ann_id, attributes=attributes, group=group) + + elif ann_type == 'points': + return Points(points, label=label_id, + id=ann_id, attributes=attributes, group=group) + + elif ann_type == 'box': + x, y = points[0], points[1] + w, h = points[2] - x, points[3] - y + return Bbox(x, y, w, h, label=label_id, + id=ann_id, attributes=attributes, group=group) + + else: + raise NotImplementedError("Unknown annotation type '%s'" % ann_type) + + @classmethod + def _parse_tag_ann(cls, ann, categories): + label = ann.get('label') + label_id = categories[AnnotationType.label].find(label)[0] + group = ann.get('group') + attributes = ann.get('attributes') + return Label(label_id, attributes=attributes, group=group) + + def _load_items(self, parsed): + for frame_id, item_desc in parsed.items(): + filename = item_desc.get('name') + if filename: + filename = self._find_image(filename) + if not filename: + filename = item_desc.get('name') + image_size = (item_desc.get('height'), item_desc.get('width')) + if all(image_size): + image_size = (int(image_size[0]), int(image_size[1])) + else: + image_size = None + image = None + if filename: + image = Image(path=filename, size=image_size) + + parsed[frame_id] = DatasetItem(id=frame_id, subset=self._subset, + image=image, annotations=item_desc.get('annotations')) + return parsed + + def _find_image(self, file_name): + search_paths = [] + if self._images_dir: + search_paths += [ + osp.join(self._images_dir, file_name), + osp.join(self._images_dir, self._subset or DEFAULT_SUBSET_NAME, + file_name), + ] + search_paths += [ + osp.join(osp.dirname(self._path), file_name) + ] + for image_path in search_paths: + if osp.isfile(image_path): + return image_path + return None diff --git a/datumaro/datumaro/plugins/cvat_format/format.py b/datumaro/datumaro/plugins/cvat_format/format.py new file mode 100644 index 00000000000..e0c7a10476a --- /dev/null +++ b/datumaro/datumaro/plugins/cvat_format/format.py @@ -0,0 +1,10 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +class CvatPath: + IMAGES_DIR = 'images' + ANNOTATIONS_DIR = 'annotations' + + IMAGE_EXT = '.jpg' diff --git a/datumaro/datumaro/plugins/cvat_format/importer.py b/datumaro/datumaro/plugins/cvat_format/importer.py new file mode 100644 index 00000000000..a81b5cb38ce --- /dev/null +++ b/datumaro/datumaro/plugins/cvat_format/importer.py @@ -0,0 +1,48 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from glob import glob +import logging as log +import os.path as osp + +from datumaro.components.extractor import Importer + +from .format import CvatPath + + +class CvatImporter(Importer): + EXTRACTOR_NAME = 'cvat' + + def __call__(self, path, **extra_params): + from datumaro.components.project import Project # cyclic import + project = Project() + + if path.endswith('.xml') and osp.isfile(path): + subset_paths = [path] + else: + subset_paths = glob(osp.join(path, '*.xml')) + + if osp.basename(osp.normpath(path)) != CvatPath.ANNOTATIONS_DIR: + path = osp.join(path, CvatPath.ANNOTATIONS_DIR) + subset_paths += glob(osp.join(path, '*.xml')) + + if len(subset_paths) == 0: + raise Exception("Failed to find 'cvat' dataset at '%s'" % path) + + for subset_path in subset_paths: + if not osp.isfile(subset_path): + continue + + log.info("Found a dataset at '%s'" % subset_path) + + subset_name = osp.splitext(osp.basename(subset_path))[0] + + project.add_source(subset_name, { + 'url': subset_path, + 'format': self.EXTRACTOR_NAME, + 'options': dict(extra_params), + }) + + return project diff --git a/datumaro/datumaro/plugins/datumaro_format/__init__.py b/datumaro/datumaro/plugins/datumaro_format/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/datumaro/datumaro/plugins/datumaro_format/converter.py b/datumaro/datumaro/plugins/datumaro_format/converter.py new file mode 100644 index 00000000000..cc860cbad3d --- /dev/null +++ b/datumaro/datumaro/plugins/datumaro_format/converter.py @@ -0,0 +1,295 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +# pylint: disable=no-self-use + +import json +import numpy as np +import os +import os.path as osp + +from datumaro.components.converter import Converter +from datumaro.components.extractor import ( + DEFAULT_SUBSET_NAME, Annotation, + Label, Mask, RleMask, Points, Polygon, PolyLine, Bbox, Caption, + LabelCategories, MaskCategories, PointsCategories +) +from datumaro.util.image import save_image +import pycocotools.mask as mask_utils +from datumaro.components.cli_plugin import CliPlugin + +from .format import DatumaroPath + + +def _cast(value, type_conv, default=None): + if value is None: + return default + try: + return type_conv(value) + except Exception: + return default + +class _SubsetWriter: + def __init__(self, name, context): + self._name = name + self._context = context + + self._data = { + 'info': {}, + 'categories': {}, + 'items': [], + } + + @property + def categories(self): + return self._data['categories'] + + @property + def items(self): + return self._data['items'] + + def write_item(self, item): + annotations = [] + item_desc = { + 'id': item.id, + 'annotations': annotations, + } + if item.path: + item_desc['path'] = item.path + if item.has_image: + path = item.image.path + if self._context._save_images: + path = self._context._save_image(item) + + item_desc['image'] = { + 'size': item.image.size, + 'path': path, + } + self.items.append(item_desc) + + for ann in item.annotations: + if isinstance(ann, Label): + converted_ann = self._convert_label_object(ann) + elif isinstance(ann, Mask): + converted_ann = self._convert_mask_object(ann) + elif isinstance(ann, Points): + converted_ann = self._convert_points_object(ann) + elif isinstance(ann, PolyLine): + converted_ann = self._convert_polyline_object(ann) + elif isinstance(ann, Polygon): + converted_ann = self._convert_polygon_object(ann) + elif isinstance(ann, Bbox): + converted_ann = self._convert_bbox_object(ann) + elif isinstance(ann, Caption): + converted_ann = self._convert_caption_object(ann) + else: + raise NotImplementedError() + annotations.append(converted_ann) + + def write_categories(self, categories): + for ann_type, desc in categories.items(): + if isinstance(desc, LabelCategories): + converted_desc = self._convert_label_categories(desc) + elif isinstance(desc, MaskCategories): + converted_desc = self._convert_mask_categories(desc) + elif isinstance(desc, PointsCategories): + converted_desc = self._convert_points_categories(desc) + else: + raise NotImplementedError() + self.categories[ann_type.name] = converted_desc + + def write(self, save_dir): + with open(osp.join(save_dir, '%s.json' % (self._name)), 'w') as f: + json.dump(self._data, f) + + def _convert_annotation(self, obj): + assert isinstance(obj, Annotation) + + ann_json = { + 'id': _cast(obj.id, int), + 'type': _cast(obj.type.name, str), + 'attributes': obj.attributes, + 'group': _cast(obj.group, int, 0), + } + return ann_json + + def _convert_label_object(self, obj): + converted = self._convert_annotation(obj) + + converted.update({ + 'label_id': _cast(obj.label, int), + }) + return converted + + def _convert_mask_object(self, obj): + converted = self._convert_annotation(obj) + + if isinstance(obj, RleMask): + rle = obj.rle + else: + rle = mask_utils.encode( + np.require(obj.image, dtype=np.uint8, requirements='F')) + + converted.update({ + 'label_id': _cast(obj.label, int), + 'rle': { + # serialize as compressed COCO mask + 'counts': rle['counts'].decode('ascii'), + 'size': list(int(c) for c in rle['size']), + } + }) + return converted + + def _convert_polyline_object(self, obj): + converted = self._convert_annotation(obj) + + converted.update({ + 'label_id': _cast(obj.label, int), + 'points': [float(p) for p in obj.points], + }) + return converted + + def _convert_polygon_object(self, obj): + converted = self._convert_annotation(obj) + + converted.update({ + 'label_id': _cast(obj.label, int), + 'points': [float(p) for p in obj.points], + }) + return converted + + def _convert_bbox_object(self, obj): + converted = self._convert_annotation(obj) + + converted.update({ + 'label_id': _cast(obj.label, int), + 'bbox': [float(p) for p in obj.get_bbox()], + }) + return converted + + def _convert_points_object(self, obj): + converted = self._convert_annotation(obj) + + converted.update({ + 'label_id': _cast(obj.label, int), + 'points': [float(p) for p in obj.points], + 'visibility': [int(v.value) for v in obj.visibility], + }) + return converted + + def _convert_caption_object(self, obj): + converted = self._convert_annotation(obj) + + converted.update({ + 'caption': _cast(obj.caption, str), + }) + return converted + + def _convert_label_categories(self, obj): + converted = { + 'labels': [], + } + for label in obj.items: + converted['labels'].append({ + 'name': _cast(label.name, str), + 'parent': _cast(label.parent, str), + }) + return converted + + def _convert_mask_categories(self, obj): + converted = { + 'colormap': [], + } + for label_id, color in obj.colormap.items(): + converted['colormap'].append({ + 'label_id': int(label_id), + 'r': int(color[0]), + 'g': int(color[1]), + 'b': int(color[2]), + }) + return converted + + def _convert_points_categories(self, obj): + converted = { + 'items': [], + } + for label_id, item in obj.items.items(): + converted['items'].append({ + 'label_id': int(label_id), + 'labels': [_cast(label, str) for label in item.labels], + 'adjacent': [int(v) for v in item.adjacent], + }) + return converted + +class _Converter: + def __init__(self, extractor, save_dir, save_images=False): + self._extractor = extractor + self._save_dir = save_dir + self._save_images = save_images + + def convert(self): + os.makedirs(self._save_dir, exist_ok=True) + + images_dir = osp.join(self._save_dir, DatumaroPath.IMAGES_DIR) + os.makedirs(images_dir, exist_ok=True) + self._images_dir = images_dir + + annotations_dir = osp.join(self._save_dir, DatumaroPath.ANNOTATIONS_DIR) + os.makedirs(annotations_dir, exist_ok=True) + self._annotations_dir = annotations_dir + + subsets = self._extractor.subsets() + if len(subsets) == 0: + subsets = [ None ] + subsets = [n if n else DEFAULT_SUBSET_NAME for n in subsets] + subsets = { name: _SubsetWriter(name, self) for name in subsets } + + for subset, writer in subsets.items(): + writer.write_categories(self._extractor.categories()) + + for item in self._extractor: + subset = item.subset + if not subset: + subset = DEFAULT_SUBSET_NAME + writer = subsets[subset] + + writer.write_item(item) + + for subset, writer in subsets.items(): + writer.write(annotations_dir) + + def _save_image(self, item): + image = item.image.data + if image is None: + return '' + + filename = item.image.filename + if filename: + filename = osp.splitext(filename)[0] + else: + filename = item.id + filename += DatumaroPath.IMAGE_EXT + image_path = osp.join(self._images_dir, filename) + save_image(image_path, image) + return filename + +class DatumaroConverter(Converter, CliPlugin): + @classmethod + def build_cmdline_parser(cls, **kwargs): + parser = super().build_cmdline_parser(**kwargs) + parser.add_argument('--save-images', action='store_true', + help="Save images (default: %(default)s)") + return parser + + def __init__(self, save_images=False): + super().__init__() + + self._options = { + 'save_images': save_images, + } + + def __call__(self, extractor, save_dir): + converter = _Converter(extractor, save_dir, **self._options) + converter.convert() diff --git a/datumaro/datumaro/plugins/datumaro_format/extractor.py b/datumaro/datumaro/plugins/datumaro_format/extractor.py new file mode 100644 index 00000000000..4be7a778779 --- /dev/null +++ b/datumaro/datumaro/plugins/datumaro_format/extractor.py @@ -0,0 +1,165 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import json +import os.path as osp + +from datumaro.components.extractor import (SourceExtractor, + DEFAULT_SUBSET_NAME, DatasetItem, + AnnotationType, Label, RleMask, Points, Polygon, PolyLine, Bbox, Caption, + LabelCategories, MaskCategories, PointsCategories +) +from datumaro.util.image import Image + +from .format import DatumaroPath + + +class DatumaroExtractor(SourceExtractor): + def __init__(self, path): + super().__init__() + + assert osp.isfile(path) + rootpath = path.rsplit(DatumaroPath.ANNOTATIONS_DIR, maxsplit=1)[0] + self._path = rootpath + + subset_name = osp.splitext(osp.basename(path))[0] + if subset_name == DEFAULT_SUBSET_NAME: + subset_name = None + self._subset_name = subset_name + + with open(path, 'r') as f: + parsed_anns = json.load(f) + self._categories = self._load_categories(parsed_anns) + self._items = self._load_items(parsed_anns) + + def categories(self): + return self._categories + + def __iter__(self): + for item in self._items: + yield item + + def __len__(self): + return len(self._items) + + def subsets(self): + if self._subset_name: + return [self._subset_name] + return None + + def get_subset(self, name): + if name != self._subset_name: + return None + return self + + @staticmethod + def _load_categories(parsed): + categories = {} + + parsed_label_cat = parsed['categories'].get(AnnotationType.label.name) + if parsed_label_cat: + label_categories = LabelCategories() + for item in parsed_label_cat['labels']: + label_categories.add(item['name'], parent=item['parent']) + + categories[AnnotationType.label] = label_categories + + parsed_mask_cat = parsed['categories'].get(AnnotationType.mask.name) + if parsed_mask_cat: + colormap = {} + for item in parsed_mask_cat['colormap']: + colormap[int(item['label_id'])] = \ + (item['r'], item['g'], item['b']) + + mask_categories = MaskCategories(colormap=colormap) + categories[AnnotationType.mask] = mask_categories + + parsed_points_cat = parsed['categories'].get(AnnotationType.points.name) + if parsed_points_cat: + point_categories = PointsCategories() + for item in parsed_points_cat['items']: + point_categories.add(int(item['label_id']), + item['labels'], adjacent=item['adjacent']) + + categories[AnnotationType.points] = point_categories + + return categories + + def _load_items(self, parsed): + items = [] + for item_desc in parsed['items']: + item_id = item_desc['id'] + + image = None + image_info = item_desc.get('image', {}) + if image_info: + image_path = osp.join(self._path, DatumaroPath.IMAGES_DIR, + image_info.get('path', '')) # relative or absolute fits + image = Image(path=image_path, size=image_info.get('size')) + + annotations = self._load_annotations(item_desc) + + item = DatasetItem(id=item_id, subset=self._subset_name, + annotations=annotations, image=image) + + items.append(item) + + return items + + def _load_annotations(self, item): + parsed = item['annotations'] + loaded = [] + + for ann in parsed: + ann_id = ann.get('id') + ann_type = AnnotationType[ann['type']] + attributes = ann.get('attributes') + group = ann.get('group') + + if ann_type == AnnotationType.label: + label_id = ann.get('label_id') + loaded.append(Label(label=label_id, + id=ann_id, attributes=attributes, group=group)) + + elif ann_type == AnnotationType.mask: + label_id = ann.get('label_id') + rle = ann['rle'] + rle['counts'] = rle['counts'].encode('ascii') + loaded.append(RleMask(rle=rle, label=label_id, + id=ann_id, attributes=attributes, group=group)) + + elif ann_type == AnnotationType.polyline: + label_id = ann.get('label_id') + points = ann.get('points') + loaded.append(PolyLine(points, label=label_id, + id=ann_id, attributes=attributes, group=group)) + + elif ann_type == AnnotationType.polygon: + label_id = ann.get('label_id') + points = ann.get('points') + loaded.append(Polygon(points, label=label_id, + id=ann_id, attributes=attributes, group=group)) + + elif ann_type == AnnotationType.bbox: + label_id = ann.get('label_id') + x, y, w, h = ann.get('bbox') + loaded.append(Bbox(x, y, w, h, label=label_id, + id=ann_id, attributes=attributes, group=group)) + + elif ann_type == AnnotationType.points: + label_id = ann.get('label_id') + points = ann.get('points') + loaded.append(Points(points, label=label_id, + id=ann_id, attributes=attributes, group=group)) + + elif ann_type == AnnotationType.caption: + caption = ann.get('caption') + loaded.append(Caption(caption, + id=ann_id, attributes=attributes, group=group)) + + else: + raise NotImplementedError() + + return loaded diff --git a/datumaro/datumaro/plugins/datumaro_format/format.py b/datumaro/datumaro/plugins/datumaro_format/format.py new file mode 100644 index 00000000000..ef587b9bbbe --- /dev/null +++ b/datumaro/datumaro/plugins/datumaro_format/format.py @@ -0,0 +1,12 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +class DatumaroPath: + IMAGES_DIR = 'images' + ANNOTATIONS_DIR = 'annotations' + MASKS_DIR = 'masks' + + IMAGE_EXT = '.jpg' + MASK_EXT = '.png' diff --git a/datumaro/datumaro/plugins/datumaro_format/importer.py b/datumaro/datumaro/plugins/datumaro_format/importer.py new file mode 100644 index 00000000000..0184ef9040f --- /dev/null +++ b/datumaro/datumaro/plugins/datumaro_format/importer.py @@ -0,0 +1,48 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from glob import glob +import logging as log +import os.path as osp + +from datumaro.components.extractor import Importer + +from .format import DatumaroPath + + +class DatumaroImporter(Importer): + EXTRACTOR_NAME = 'datumaro' + + def __call__(self, path, **extra_params): + from datumaro.components.project import Project # cyclic import + project = Project() + + if path.endswith('.json') and osp.isfile(path): + subset_paths = [path] + else: + subset_paths = glob(osp.join(path, '*.json')) + + if osp.basename(osp.normpath(path)) != DatumaroPath.ANNOTATIONS_DIR: + path = osp.join(path, DatumaroPath.ANNOTATIONS_DIR) + subset_paths += glob(osp.join(path, '*.json')) + + if len(subset_paths) == 0: + raise Exception("Failed to find 'datumaro' dataset at '%s'" % path) + + for subset_path in subset_paths: + if not osp.isfile(subset_path): + continue + + log.info("Found a dataset at '%s'" % subset_path) + + subset_name = osp.splitext(osp.basename(subset_path))[0] + + project.add_source(subset_name, { + 'url': subset_path, + 'format': self.EXTRACTOR_NAME, + 'options': dict(extra_params), + }) + + return project diff --git a/datumaro/datumaro/plugins/image_dir.py b/datumaro/datumaro/plugins/image_dir.py new file mode 100644 index 00000000000..c719c546f52 --- /dev/null +++ b/datumaro/datumaro/plugins/image_dir.py @@ -0,0 +1,91 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from collections import OrderedDict +import os +import os.path as osp + +from datumaro.components.extractor import DatasetItem, SourceExtractor, Importer +from datumaro.components.converter import Converter +from datumaro.util.image import save_image + + +class ImageDirImporter(Importer): + EXTRACTOR_NAME = 'image_dir' + + def __call__(self, path, **extra_params): + from datumaro.components.project import Project # cyclic import + project = Project() + + if not osp.isdir(path): + raise Exception("Can't find a directory at '%s'" % path) + + source_name = osp.basename(osp.normpath(path)) + project.add_source(source_name, { + 'url': source_name, + 'format': self.EXTRACTOR_NAME, + 'options': dict(extra_params), + }) + + return project + + +class ImageDirExtractor(SourceExtractor): + _SUPPORTED_FORMATS = ['.png', '.jpg'] + + def __init__(self, url): + super().__init__() + + assert osp.isdir(url) + + items = [] + for name in os.listdir(url): + path = osp.join(url, name) + if self._is_image(path): + item_id = osp.splitext(name)[0] + item = DatasetItem(id=item_id, image=path) + items.append((item.id, item)) + + items = sorted(items, key=lambda e: e[0]) + items = OrderedDict(items) + self._items = items + + self._subsets = None + + def __iter__(self): + for item in self._items.values(): + yield item + + def __len__(self): + return len(self._items) + + def subsets(self): + return self._subsets + + def get(self, item_id, subset=None, path=None): + if path or subset: + raise KeyError() + return self._items[item_id] + + def _is_image(self, path): + for ext in self._SUPPORTED_FORMATS: + if osp.isfile(path) and path.endswith(ext): + return True + return False + + +class ImageDirConverter(Converter): + def __call__(self, extractor, save_dir): + os.makedirs(save_dir, exist_ok=True) + + for item in extractor: + if item.has_image and item.image.has_data: + filename = item.image.filename + if filename: + filename = osp.splitext(filename)[0] + else: + filename = item.id + filename += '.jpg' + save_image(osp.join(save_dir, filename), item.image.data) \ No newline at end of file diff --git a/datumaro/datumaro/plugins/openvino_launcher.py b/datumaro/datumaro/plugins/openvino_launcher.py new file mode 100644 index 00000000000..b0e8360d0c1 --- /dev/null +++ b/datumaro/datumaro/plugins/openvino_launcher.py @@ -0,0 +1,189 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +# pylint: disable=exec-used + +import cv2 +import numpy as np +import os +import os.path as osp +import platform +import subprocess + +from openvino.inference_engine import IENetwork, IEPlugin + +from datumaro.components.launcher import Launcher + + +class InterpreterScript: + def __init__(self, path): + with open(path, 'r') as f: + script = f.read() + + context = {} + exec(script, context, context) + + process_outputs = context['process_outputs'] + assert callable(process_outputs) + self.__dict__['process_outputs'] = process_outputs + + get_categories = context.get('get_categories') + assert callable(get_categories) or get_categories is None + self.__dict__['get_categories'] = get_categories + + @staticmethod + def get_categories(): + return None + + @staticmethod + def process_outputs(inputs, outputs): + return [] + +class OpenVinoLauncher(Launcher): + _DEFAULT_IE_PLUGINS_PATH = "/opt/intel/openvino_2019.1.144/deployment_tools/inference_engine/lib/intel64" + _IE_PLUGINS_PATH = os.getenv("IE_PLUGINS_PATH", _DEFAULT_IE_PLUGINS_PATH) + + @staticmethod + def _check_instruction_set(instruction): + return instruction == str.strip( + subprocess.check_output( + 'lscpu | grep -o "{}" | head -1'.format(instruction), shell=True + ).decode('utf-8') + ) + + @staticmethod + def make_plugin(device='cpu', plugins_path=_IE_PLUGINS_PATH): + if plugins_path is None or not osp.isdir(plugins_path): + raise Exception('Inference engine plugins directory "%s" not found' % \ + (plugins_path)) + + plugin = IEPlugin(device='CPU', plugin_dirs=[plugins_path]) + if (OpenVinoLauncher._check_instruction_set('avx2')): + plugin.add_cpu_extension(os.path.join(plugins_path, + 'libcpu_extension_avx2.so')) + elif (OpenVinoLauncher._check_instruction_set('sse4')): + plugin.add_cpu_extension(os.path.join(plugins_path, + 'libcpu_extension_sse4.so')) + elif platform.system() == 'Darwin': + plugin.add_cpu_extension(os.path.join(plugins_path, + 'libcpu_extension.dylib')) + else: + raise Exception('Inference engine requires support of avx2 or sse4') + + return plugin + + @staticmethod + def make_network(model, weights): + return IENetwork.from_ir(model=model, weights=weights) + + def __init__(self, description, weights, interpretation_script, + plugins_path=None, model_dir=None, **kwargs): + if model_dir is None: + model_dir = '' + if not osp.isfile(description): + description = osp.join(model_dir, description) + if not osp.isfile(description): + raise Exception('Failed to open model description file "%s"' % \ + (description)) + + if not osp.isfile(weights): + weights = osp.join(model_dir, weights) + if not osp.isfile(weights): + raise Exception('Failed to open model weights file "%s"' % \ + (weights)) + + if not osp.isfile(interpretation_script): + interpretation_script = \ + osp.join(model_dir, interpretation_script) + if not osp.isfile(interpretation_script): + raise Exception('Failed to open model interpretation script file "%s"' % \ + (interpretation_script)) + + self._interpreter_script = InterpreterScript(interpretation_script) + + if plugins_path is None: + plugins_path = OpenVinoLauncher._IE_PLUGINS_PATH + + plugin = OpenVinoLauncher.make_plugin(plugins_path=plugins_path) + network = OpenVinoLauncher.make_network(description, weights) + self._network = network + self._plugin = plugin + self._load_executable_net() + + def _load_executable_net(self, batch_size=1): + network = self._network + plugin = self._plugin + + supported_layers = plugin.get_supported_layers(network) + not_supported_layers = [l for l in network.layers.keys() if l not in supported_layers] + if len(not_supported_layers) != 0: + raise Exception('Following layers are not supported by the plugin' + ' for the specified device {}:\n {}'. format( \ + plugin.device, ", ".join(not_supported_layers))) + + iter_inputs = iter(network.inputs) + self._input_blob_name = next(iter_inputs) + self._output_blob_name = next(iter(network.outputs)) + + # NOTE: handling for the inclusion of `image_info` in OpenVino2019 + self._require_image_info = 'image_info' in network.inputs + if self._input_blob_name == 'image_info': + self._input_blob_name = next(iter_inputs) + + input_type = network.inputs[self._input_blob_name] + self._input_layout = input_type if isinstance(input_type, list) else input_type.shape + + self._input_layout[0] = batch_size + network.reshape({self._input_blob_name: self._input_layout}) + self._batch_size = batch_size + + self._net = plugin.load(network=network, num_requests=1) + + def infer(self, inputs): + assert len(inputs.shape) == 4, \ + "Expected an input image in (N, H, W, C) format, got %s" % \ + (inputs.shape) + assert inputs.shape[3] == 3, \ + "Expected BGR input" + + n, c, h, w = self._input_layout + if inputs.shape[1:3] != (h, w): + resized_inputs = np.empty((n, h, w, c), dtype=inputs.dtype) + for inp, resized_input in zip(inputs, resized_inputs): + cv2.resize(inp, (w, h), resized_input) + inputs = resized_inputs + inputs = inputs.transpose((0, 3, 1, 2)) # NHWC to NCHW + inputs = {self._input_blob_name: inputs} + if self._require_image_info: + info = np.zeros([1, 3]) + info[0, 0] = h + info[0, 1] = w + info[0, 2] = 1.0 # scale + inputs['image_info'] = info + + results = self._net.infer(inputs) + if len(results) == 1: + return results[self._output_blob_name] + else: + return results + + def launch(self, inputs): + batch_size = len(inputs) + if self._batch_size < batch_size: + self._load_executable_net(batch_size) + + outputs = self.infer(inputs) + results = self.process_outputs(inputs, outputs) + return results + + def get_categories(self): + return self._interpreter_script.get_categories() + + def process_outputs(self, inputs, outputs): + return self._interpreter_script.process_outputs(inputs, outputs) + + def preferred_input_size(self): + _, _, h, w = self._input_layout + return (h, w) diff --git a/datumaro/datumaro/plugins/tf_detection_api_format/__init__.py b/datumaro/datumaro/plugins/tf_detection_api_format/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/datumaro/datumaro/plugins/tf_detection_api_format/converter.py b/datumaro/datumaro/plugins/tf_detection_api_format/converter.py new file mode 100644 index 00000000000..01e2bd0ea45 --- /dev/null +++ b/datumaro/datumaro/plugins/tf_detection_api_format/converter.py @@ -0,0 +1,208 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import codecs +from collections import OrderedDict +import logging as log +import os +import os.path as osp +import string + +from datumaro.components.extractor import (AnnotationType, DEFAULT_SUBSET_NAME, + LabelCategories +) +from datumaro.components.converter import Converter +from datumaro.components.cli_plugin import CliPlugin +from datumaro.util.image import encode_image +from datumaro.util.mask_tools import merge_masks +from datumaro.util.annotation_tools import (compute_bbox, + find_group_leader, find_instances) +from datumaro.util.tf_util import import_tf as _import_tf + +from .format import DetectionApiPath +tf = _import_tf() + + +# filter out non-ASCII characters, otherwise training will crash +_printable = set(string.printable) +def _make_printable(s): + return ''.join(filter(lambda x: x in _printable, s)) + +def int64_feature(value): + return tf.train.Feature(int64_list=tf.train.Int64List(value=[value])) + +def int64_list_feature(value): + return tf.train.Feature(int64_list=tf.train.Int64List(value=value)) + +def bytes_feature(value): + return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value])) + +def bytes_list_feature(value): + return tf.train.Feature(bytes_list=tf.train.BytesList(value=value)) + +def float_list_feature(value): + return tf.train.Feature(float_list=tf.train.FloatList(value=value)) + +class TfDetectionApiConverter(Converter, CliPlugin): + @classmethod + def build_cmdline_parser(cls, **kwargs): + parser = super().build_cmdline_parser(**kwargs) + parser.add_argument('--save-images', action='store_true', + help="Save images (default: %(default)s)") + parser.add_argument('--save-masks', action='store_true', + help="Include instance masks (default: %(default)s)") + return parser + + def __init__(self, save_images=False, save_masks=False): + super().__init__() + + self._save_images = save_images + self._save_masks = save_masks + + def __call__(self, extractor, save_dir): + os.makedirs(save_dir, exist_ok=True) + + label_categories = extractor.categories().get(AnnotationType.label, + LabelCategories()) + get_label = lambda label_id: label_categories.items[label_id].name \ + if label_id is not None else '' + label_ids = OrderedDict((label.name, 1 + idx) + for idx, label in enumerate(label_categories.items)) + map_label_id = lambda label_id: label_ids.get(get_label(label_id), 0) + self._get_label = get_label + self._get_label_id = map_label_id + + subsets = extractor.subsets() + if len(subsets) == 0: + subsets = [ None ] + + for subset_name in subsets: + if subset_name: + subset = extractor.get_subset(subset_name) + else: + subset_name = DEFAULT_SUBSET_NAME + subset = extractor + + labelmap_path = osp.join(save_dir, DetectionApiPath.LABELMAP_FILE) + with codecs.open(labelmap_path, 'w', encoding='utf8') as f: + for label, idx in label_ids.items(): + f.write( + 'item {\n' + + ('\tid: %s\n' % (idx)) + + ("\tname: '%s'\n" % (label)) + + '}\n\n' + ) + + anno_path = osp.join(save_dir, '%s.tfrecord' % (subset_name)) + with tf.io.TFRecordWriter(anno_path) as writer: + for item in subset: + tf_example = self._make_tf_example(item) + writer.write(tf_example.SerializeToString()) + + @staticmethod + def _find_instances(annotations): + return find_instances(a for a in annotations + if a.type in { AnnotationType.bbox, AnnotationType.mask }) + + def _find_instance_parts(self, group, img_width, img_height): + boxes = [a for a in group if a.type == AnnotationType.bbox] + masks = [a for a in group if a.type == AnnotationType.mask] + + anns = boxes + masks + leader = find_group_leader(anns) + bbox = compute_bbox(anns) + + mask = None + if self._save_masks: + mask = merge_masks([m.image for m in masks]) + + return [leader, mask, bbox] + + def _export_instances(self, instances, width, height): + xmins = [] # List of normalized left x coordinates of bounding boxes (1 per box) + xmaxs = [] # List of normalized right x coordinates of bounding boxes (1 per box) + ymins = [] # List of normalized top y coordinates of bounding boxes (1 per box) + ymaxs = [] # List of normalized bottom y coordinates of bounding boxes (1 per box) + classes_text = [] # List of class names of bounding boxes (1 per box) + classes = [] # List of class ids of bounding boxes (1 per box) + masks = [] # List of PNG-encoded instance masks (1 per box) + + for leader, mask, box in instances: + label = _make_printable(self._get_label(leader.label)) + classes_text.append(label.encode('utf-8')) + classes.append(self._get_label_id(leader.label)) + + xmins.append(box[0] / width) + xmaxs.append((box[0] + box[2]) / width) + ymins.append(box[1] / height) + ymaxs.append((box[1] + box[3]) / height) + + if self._save_masks: + if mask is not None: + mask = encode_image(mask, '.png') + else: + mask = b'' + masks.append(mask) + + result = {} + if classes: + result = { + 'image/object/bbox/xmin': float_list_feature(xmins), + 'image/object/bbox/xmax': float_list_feature(xmaxs), + 'image/object/bbox/ymin': float_list_feature(ymins), + 'image/object/bbox/ymax': float_list_feature(ymaxs), + 'image/object/class/text': bytes_list_feature(classes_text), + 'image/object/class/label': int64_list_feature(classes), + } + if masks: + result['image/object/mask'] = bytes_list_feature(masks) + return result + + def _make_tf_example(self, item): + features = { + 'image/source_id': bytes_feature(str(item.id).encode('utf-8')), + } + + filename = '' + if item.has_image: + filename = item.image.filename + if not filename: + filename = item.id + DetectionApiPath.IMAGE_EXT + features['image/filename'] = bytes_feature(filename.encode('utf-8')) + + if not item.has_image: + raise Exception("Failed to export dataset item '%s': " + "item has no image info" % item.id) + height, width = item.image.size + + features.update({ + 'image/height': int64_feature(height), + 'image/width': int64_feature(width), + }) + + features.update({ + 'image/encoded': bytes_feature(b''), + 'image/format': bytes_feature(b'') + }) + if self._save_images: + if item.has_image and item.image.has_data: + fmt = DetectionApiPath.IMAGE_FORMAT + buffer = encode_image(item.image.data, DetectionApiPath.IMAGE_EXT) + + features.update({ + 'image/encoded': bytes_feature(buffer), + 'image/format': bytes_feature(fmt.encode('utf-8')), + }) + else: + log.warning("Item '%s' has no image" % item.id) + + instances = self._find_instances(item.annotations) + instances = [self._find_instance_parts(i, width, height) for i in instances] + features.update(self._export_instances(instances, width, height)) + + tf_example = tf.train.Example( + features=tf.train.Features(feature=features)) + + return tf_example diff --git a/datumaro/datumaro/plugins/tf_detection_api_format/extractor.py b/datumaro/datumaro/plugins/tf_detection_api_format/extractor.py new file mode 100644 index 00000000000..567392ddf8e --- /dev/null +++ b/datumaro/datumaro/plugins/tf_detection_api_format/extractor.py @@ -0,0 +1,209 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from collections import OrderedDict +import numpy as np +import os.path as osp +import re + +from datumaro.components.extractor import (SourceExtractor, + DEFAULT_SUBSET_NAME, DatasetItem, + AnnotationType, Bbox, Mask, LabelCategories +) +from datumaro.util.image import Image, decode_image, lazy_image +from datumaro.util.tf_util import import_tf as _import_tf + +from .format import DetectionApiPath +tf = _import_tf() + + +def clamp(value, _min, _max): + return max(min(_max, value), _min) + +class TfDetectionApiExtractor(SourceExtractor): + def __init__(self, path): + super().__init__() + + assert osp.isfile(path) + images_dir = '' + root_dir = osp.dirname(osp.abspath(path)) + if osp.basename(root_dir) == DetectionApiPath.ANNOTATIONS_DIR: + root_dir = osp.dirname(root_dir) + images_dir = osp.join(root_dir, DetectionApiPath.IMAGES_DIR) + if not osp.isdir(images_dir): + images_dir = '' + + subset_name = osp.splitext(osp.basename(path))[0] + if subset_name == DEFAULT_SUBSET_NAME: + subset_name = None + self._subset_name = subset_name + + items, labels = self._parse_tfrecord_file(path, subset_name, images_dir) + self._items = items + self._categories = self._load_categories(labels) + + def categories(self): + return self._categories + + def __iter__(self): + for item in self._items: + yield item + + def __len__(self): + return len(self._items) + + def subsets(self): + if self._subset_name: + return [self._subset_name] + return None + + def get_subset(self, name): + if name != self._subset_name: + return None + return self + + @staticmethod + def _load_categories(labels): + label_categories = LabelCategories() + labels = sorted(labels.items(), key=lambda item: item[1]) + for label, _ in labels: + label_categories.add(label) + return { + AnnotationType.label: label_categories + } + + @classmethod + def _parse_labelmap(cls, text): + id_pattern = r'(?:id\s*:\s*(?P\d+))' + name_pattern = r'(?:name\s*:\s*[\'\"](?P.*?)[\'\"])' + entry_pattern = r'(\{(?:[\s\n]*(?:%(id)s|%(name)s)[\s\n]*){2}\})+' % \ + {'id': id_pattern, 'name': name_pattern} + matches = re.finditer(entry_pattern, text) + + labelmap = {} + for match in matches: + label_id = match.group('id') + label_name = match.group('name') + if label_id is not None and label_name is not None: + labelmap[label_name] = int(label_id) + + return labelmap + + @classmethod + def _parse_tfrecord_file(cls, filepath, subset_name, images_dir): + dataset = tf.data.TFRecordDataset(filepath) + features = { + 'image/filename': tf.io.FixedLenFeature([], tf.string), + 'image/source_id': tf.io.FixedLenFeature([], tf.string), + 'image/height': tf.io.FixedLenFeature([], tf.int64), + 'image/width': tf.io.FixedLenFeature([], tf.int64), + 'image/encoded': tf.io.FixedLenFeature([], tf.string), + 'image/format': tf.io.FixedLenFeature([], tf.string), + # Object boxes and classes. + 'image/object/bbox/xmin': tf.io.VarLenFeature(tf.float32), + 'image/object/bbox/xmax': tf.io.VarLenFeature(tf.float32), + 'image/object/bbox/ymin': tf.io.VarLenFeature(tf.float32), + 'image/object/bbox/ymax': tf.io.VarLenFeature(tf.float32), + 'image/object/class/label': tf.io.VarLenFeature(tf.int64), + 'image/object/class/text': tf.io.VarLenFeature(tf.string), + 'image/object/mask': tf.io.VarLenFeature(tf.string), + } + + dataset_labels = OrderedDict() + labelmap_path = osp.join(osp.dirname(filepath), + DetectionApiPath.LABELMAP_FILE) + if osp.exists(labelmap_path): + with open(labelmap_path, 'r', encoding='utf-8') as f: + labelmap_text = f.read() + dataset_labels.update({ label: id - 1 + for label, id in cls._parse_labelmap(labelmap_text).items() + }) + + dataset_items = [] + + for record in dataset: + parsed_record = tf.io.parse_single_example(record, features) + frame_id = parsed_record['image/source_id'].numpy().decode('utf-8') + frame_filename = \ + parsed_record['image/filename'].numpy().decode('utf-8') + frame_height = tf.cast( + parsed_record['image/height'], tf.int64).numpy().item() + frame_width = tf.cast( + parsed_record['image/width'], tf.int64).numpy().item() + frame_image = parsed_record['image/encoded'].numpy() + frame_format = parsed_record['image/format'].numpy().decode('utf-8') + xmins = tf.sparse.to_dense( + parsed_record['image/object/bbox/xmin']).numpy() + ymins = tf.sparse.to_dense( + parsed_record['image/object/bbox/ymin']).numpy() + xmaxs = tf.sparse.to_dense( + parsed_record['image/object/bbox/xmax']).numpy() + ymaxs = tf.sparse.to_dense( + parsed_record['image/object/bbox/ymax']).numpy() + label_ids = tf.sparse.to_dense( + parsed_record['image/object/class/label']).numpy() + labels = tf.sparse.to_dense( + parsed_record['image/object/class/text'], + default_value=b'').numpy() + masks = tf.sparse.to_dense( + parsed_record['image/object/mask'], + default_value=b'').numpy() + + for label, label_id in zip(labels, label_ids): + label = label.decode('utf-8') + if not label: + continue + if label_id <= 0: + continue + if label in dataset_labels: + continue + dataset_labels[label] = label_id - 1 + + item_id = frame_id + if not item_id: + item_id = osp.splitext(frame_filename)[0] + + annotations = [] + for shape_id, shape in enumerate( + np.dstack((labels, xmins, ymins, xmaxs, ymaxs))[0]): + label = shape[0].decode('utf-8') + + mask = None + if len(masks) != 0: + mask = masks[shape_id] + + if mask is not None: + if isinstance(mask, bytes): + mask = lazy_image(mask, decode_image) + annotations.append(Mask(image=mask, + label=dataset_labels.get(label) + )) + else: + x = clamp(shape[1] * frame_width, 0, frame_width) + y = clamp(shape[2] * frame_height, 0, frame_height) + w = clamp(shape[3] * frame_width, 0, frame_width) - x + h = clamp(shape[4] * frame_height, 0, frame_height) - y + annotations.append(Bbox(x, y, w, h, + label=dataset_labels.get(label) + )) + + image_size = None + if frame_height and frame_width: + image_size = (frame_height, frame_width) + + image_params = {} + if frame_image and frame_format: + image_params['data'] = lazy_image(frame_image, decode_image) + if frame_filename: + image_params['path'] = osp.join(images_dir, frame_filename) + + image = None + if image_params: + image = Image(**image_params, size=image_size) + + dataset_items.append(DatasetItem(id=item_id, subset=subset_name, + image=image, annotations=annotations)) + + return dataset_items, dataset_labels diff --git a/datumaro/datumaro/plugins/tf_detection_api_format/format.py b/datumaro/datumaro/plugins/tf_detection_api_format/format.py new file mode 100644 index 00000000000..9e31212e89c --- /dev/null +++ b/datumaro/datumaro/plugins/tf_detection_api_format/format.py @@ -0,0 +1,13 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +class DetectionApiPath: + IMAGES_DIR = 'images' + ANNOTATIONS_DIR = 'annotations' + + IMAGE_EXT = '.jpg' + IMAGE_FORMAT = 'jpeg' + + LABELMAP_FILE = 'label_map.pbtxt' \ No newline at end of file diff --git a/datumaro/datumaro/plugins/tf_detection_api_format/importer.py b/datumaro/datumaro/plugins/tf_detection_api_format/importer.py new file mode 100644 index 00000000000..3000c888163 --- /dev/null +++ b/datumaro/datumaro/plugins/tf_detection_api_format/importer.py @@ -0,0 +1,44 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from glob import glob +import logging as log +import os.path as osp + +from datumaro.components.extractor import Importer + + +class TfDetectionApiImporter(Importer): + EXTRACTOR_NAME = 'tf_detection_api' + + def __call__(self, path, **extra_params): + from datumaro.components.project import Project # cyclic import + project = Project() + + if path.endswith('.tfrecord') and osp.isfile(path): + subset_paths = [path] + else: + subset_paths = glob(osp.join(path, '*.tfrecord')) + + if len(subset_paths) == 0: + raise Exception( + "Failed to find 'tf_detection_api' dataset at '%s'" % path) + + for subset_path in subset_paths: + if not osp.isfile(subset_path): + continue + + log.info("Found a dataset at '%s'" % subset_path) + + subset_name = osp.splitext(osp.basename(subset_path))[0] + + project.add_source(subset_name, { + 'url': subset_path, + 'format': self.EXTRACTOR_NAME, + 'options': dict(extra_params), + }) + + return project + diff --git a/datumaro/datumaro/plugins/transforms.py b/datumaro/datumaro/plugins/transforms.py new file mode 100644 index 00000000000..78d9ecf36aa --- /dev/null +++ b/datumaro/datumaro/plugins/transforms.py @@ -0,0 +1,483 @@ + +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from enum import Enum +import logging as log +import os.path as osp +import random + +import pycocotools.mask as mask_utils + +from datumaro.components.extractor import (Transform, AnnotationType, + RleMask, Polygon, Bbox, + LabelCategories, MaskCategories, PointsCategories +) +from datumaro.components.cli_plugin import CliPlugin +import datumaro.util.mask_tools as mask_tools +from datumaro.util.annotation_tools import find_group_leader, find_instances + + +class CropCoveredSegments(Transform, CliPlugin): + def transform_item(self, item): + annotations = [] + segments = [] + for ann in item.annotations: + if ann.type in {AnnotationType.polygon, AnnotationType.mask}: + segments.append(ann) + else: + annotations.append(ann) + if not segments: + return item + + if not item.has_image: + raise Exception("Image info is required for this transform") + h, w = item.image.size + segments = self.crop_segments(segments, w, h) + + annotations += segments + return self.wrap_item(item, annotations=annotations) + + @classmethod + def crop_segments(cls, segment_anns, img_width, img_height): + segment_anns = sorted(segment_anns, key=lambda x: x.z_order) + + segments = [] + for s in segment_anns: + if s.type == AnnotationType.polygon: + segments.append(s.points) + elif s.type == AnnotationType.mask: + if isinstance(s, RleMask): + rle = s.rle + else: + rle = mask_tools.mask_to_rle(s.image) + segments.append(rle) + + segments = mask_tools.crop_covered_segments( + segments, img_width, img_height) + + new_anns = [] + for ann, new_segment in zip(segment_anns, segments): + fields = {'z_order': ann.z_order, 'label': ann.label, + 'id': ann.id, 'group': ann.group, 'attributes': ann.attributes + } + if ann.type == AnnotationType.polygon: + if fields['group'] is None: + fields['group'] = cls._make_group_id( + segment_anns + new_anns, fields['id']) + for polygon in new_segment: + new_anns.append(Polygon(points=polygon, **fields)) + else: + rle = mask_tools.mask_to_rle(new_segment) + rle = mask_utils.frPyObjects(rle, *rle['size']) + new_anns.append(RleMask(rle=rle, **fields)) + + return new_anns + + @staticmethod + def _make_group_id(anns, ann_id): + if ann_id: + return ann_id + max_gid = max(anns, default=0, key=lambda x: x.group) + return max_gid + 1 + +class MergeInstanceSegments(Transform, CliPlugin): + """ + Replaces instance masks and, optionally, polygons with a single mask. + """ + + @classmethod + def build_cmdline_parser(cls, **kwargs): + parser = super().build_cmdline_parser(**kwargs) + parser.add_argument('--include-polygons', action='store_true', + help="Include polygons") + return parser + + def __init__(self, extractor, include_polygons=False): + super().__init__(extractor) + + self._include_polygons = include_polygons + + def transform_item(self, item): + annotations = [] + segments = [] + for ann in item.annotations: + if ann.type in {AnnotationType.polygon, AnnotationType.mask}: + segments.append(ann) + else: + annotations.append(ann) + if not segments: + return item + + if not item.has_image: + raise Exception("Image info is required for this transform") + h, w = item.image.size + instances = self.find_instances(segments) + segments = [self.merge_segments(i, w, h, self._include_polygons) + for i in instances] + segments = sum(segments, []) + + annotations += segments + return self.wrap_item(item, annotations=annotations) + + @classmethod + def merge_segments(cls, instance, img_width, img_height, + include_polygons=False): + polygons = [a for a in instance if a.type == AnnotationType.polygon] + masks = [a for a in instance if a.type == AnnotationType.mask] + if not polygons and not masks: + return [] + + leader = find_group_leader(polygons + masks) + instance = [] + + # Build the resulting mask + mask = None + + if include_polygons and polygons: + polygons = [p.points for p in polygons] + mask = mask_tools.rles_to_mask(polygons, img_width, img_height) + else: + instance += polygons # keep unused polygons + + if masks: + masks = [m.image for m in masks] + if mask is not None: + masks += [mask] + mask = mask_tools.merge_masks(masks) + + if mask is None: + return instance + + mask = mask_tools.mask_to_rle(mask) + mask = mask_utils.frPyObjects(mask, *mask['size']) + instance.append( + RleMask(rle=mask, label=leader.label, z_order=leader.z_order, + id=leader.id, attributes=leader.attributes, group=leader.group + ) + ) + return instance + + @staticmethod + def find_instances(annotations): + return find_instances(a for a in annotations + if a.type in {AnnotationType.polygon, AnnotationType.mask}) + +class PolygonsToMasks(Transform, CliPlugin): + def transform_item(self, item): + annotations = [] + for ann in item.annotations: + if ann.type == AnnotationType.polygon: + if not item.has_image: + raise Exception("Image info is required for this transform") + h, w = item.image.size + annotations.append(self.convert_polygon(ann, h, w)) + else: + annotations.append(ann) + + return self.wrap_item(item, annotations=annotations) + + @staticmethod + def convert_polygon(polygon, img_h, img_w): + rle = mask_utils.frPyObjects([polygon.points], img_h, img_w)[0] + + return RleMask(rle=rle, label=polygon.label, z_order=polygon.z_order, + id=polygon.id, attributes=polygon.attributes, group=polygon.group) + +class BoxesToMasks(Transform, CliPlugin): + def transform_item(self, item): + annotations = [] + for ann in item.annotations: + if ann.type == AnnotationType.bbox: + if not item.has_image: + raise Exception("Image info is required for this transform") + h, w = item.image.size + annotations.append(self.convert_bbox(ann, h, w)) + else: + annotations.append(ann) + + return self.wrap_item(item, annotations=annotations) + + @staticmethod + def convert_bbox(bbox, img_h, img_w): + rle = mask_utils.frPyObjects([bbox.as_polygon()], img_h, img_w)[0] + + return RleMask(rle=rle, label=bbox.label, z_order=bbox.z_order, + id=bbox.id, attributes=bbox.attributes, group=bbox.group) + +class MasksToPolygons(Transform, CliPlugin): + def transform_item(self, item): + annotations = [] + for ann in item.annotations: + if ann.type == AnnotationType.mask: + polygons = self.convert_mask(ann) + if not polygons: + log.debug("[%s]: item %s: " + "Mask conversion to polygons resulted in too " + "small polygons, which were discarded" % \ + (self.NAME, item.id)) + annotations.extend(polygons) + else: + annotations.append(ann) + + return self.wrap_item(item, annotations=annotations) + + @staticmethod + def convert_mask(mask): + polygons = mask_tools.mask_to_polygons(mask.image) + + return [ + Polygon(points=p, label=mask.label, z_order=mask.z_order, + id=mask.id, attributes=mask.attributes, group=mask.group) + for p in polygons + ] + +class ShapesToBoxes(Transform, CliPlugin): + def transform_item(self, item): + annotations = [] + for ann in item.annotations: + if ann.type in { AnnotationType.mask, AnnotationType.polygon, + AnnotationType.polyline, AnnotationType.points, + }: + annotations.append(self.convert_shape(ann)) + else: + annotations.append(ann) + + return self.wrap_item(item, annotations=annotations) + + @staticmethod + def convert_shape(shape): + bbox = shape.get_bbox() + return Bbox(*bbox, label=shape.label, z_order=shape.z_order, + id=shape.id, attributes=shape.attributes, group=shape.group) + +class Reindex(Transform, CliPlugin): + @classmethod + def build_cmdline_parser(cls, **kwargs): + parser = super().build_cmdline_parser(**kwargs) + parser.add_argument('-s', '--start', type=int, default=1, + help="Start value for item ids") + return parser + + def __init__(self, extractor, start=1): + super().__init__(extractor) + + self._start = start + + def __iter__(self): + for i, item in enumerate(self._extractor): + yield self.wrap_item(item, id=i + self._start) + +class MapSubsets(Transform, CliPlugin): + @staticmethod + def _mapping_arg(s): + parts = s.split(':') + if len(parts) != 2: + import argparse + raise argparse.ArgumentTypeError() + return parts + + @classmethod + def build_cmdline_parser(cls, **kwargs): + parser = super().build_cmdline_parser(**kwargs) + parser.add_argument('-s', '--subset', action='append', + type=cls._mapping_arg, dest='mapping', + help="Subset mapping of the form: 'src:dst' (repeatable)") + return parser + + def __init__(self, extractor, mapping=None): + super().__init__(extractor) + + if mapping is None: + mapping = {} + elif not isinstance(mapping, dict): + mapping = dict(tuple(m) for m in mapping) + self._mapping = mapping + + def transform_item(self, item): + return self.wrap_item(item, + subset=self._mapping.get(item.subset, item.subset)) + +class RandomSplit(Transform, CliPlugin): + """ + Joins all subsets into one and splits the result into few parts. + It is expected that item ids are unique and subset ratios sum up to 1.|n + |n + Example:|n + |s|s%(prog)s --subset train:.67 --subset test:.33 + """ + + @staticmethod + def _split_arg(s): + parts = s.split(':') + if len(parts) != 2: + import argparse + raise argparse.ArgumentTypeError() + return (parts[0], float(parts[1])) + + @classmethod + def build_cmdline_parser(cls, **kwargs): + parser = super().build_cmdline_parser(**kwargs) + parser.add_argument('-s', '--subset', action='append', + type=cls._split_arg, dest='splits', + help="Subsets in the form of: ':' (repeatable)") + parser.add_argument('--seed', type=int, help="Random seed") + return parser + + def __init__(self, extractor, splits, seed=None): + super().__init__(extractor) + + assert 0 < len(splits), "Expected at least one split" + assert all(0.0 <= r and r <= 1.0 for _, r in splits), \ + "Ratios are expected to be in the range [0; 1], but got %s" % splits + + total_ratio = sum(s[1] for s in splits) + if not abs(total_ratio - 1.0) <= 1e-7: + raise Exception( + "Sum of ratios is expected to be 1, got %s, which is %s" % + (splits, total_ratio)) + + dataset_size = len(extractor) + indices = list(range(dataset_size)) + + random.seed(seed) + random.shuffle(indices) + parts = [] + s = 0 + for subset, ratio in splits: + s += ratio + boundary = int(s * dataset_size) + parts.append((boundary, subset)) + + self._parts = parts + + def _find_split(self, index): + for boundary, subset in self._parts: + if index < boundary: + return subset + return subset # all the possible remainder goes to the last split + + def __iter__(self): + for i, item in enumerate(self._extractor): + yield self.wrap_item(item, subset=self._find_split(i)) + +class IdFromImageName(Transform, CliPlugin): + def transform_item(self, item): + name = item.id + if item.has_image and item.image.filename: + name = osp.splitext(item.image.filename)[0] + return self.wrap_item(item, id=name) + +class RemapLabels(Transform, CliPlugin): + DefaultAction = Enum('DefaultAction', ['keep', 'delete']) + + @staticmethod + def _split_arg(s): + parts = s.split(':') + if len(parts) != 2: + import argparse + raise argparse.ArgumentTypeError() + return (parts[0], parts[1]) + + @classmethod + def build_cmdline_parser(cls, **kwargs): + parser = super().build_cmdline_parser(**kwargs) + parser.add_argument('-l', '--label', action='append', + type=cls._split_arg, dest='mapping', + help="Label in the form of: ':' (repeatable)") + parser.add_argument('--default', + choices=[a.name for a in cls.DefaultAction], + default=cls.DefaultAction.keep.name, + help="Action for unspecified labels") + return parser + + def __init__(self, extractor, mapping, default=None): + super().__init__(extractor) + + assert isinstance(default, (str, self.DefaultAction)) + if isinstance(default, str): + default = self.DefaultAction[default] + + assert isinstance(mapping, (dict, list)) + if isinstance(mapping, list): + mapping = dict(mapping) + + self._categories = {} + + src_label_cat = self._extractor.categories().get(AnnotationType.label) + if src_label_cat is not None: + self._make_label_id_map(src_label_cat, mapping, default) + + src_mask_cat = self._extractor.categories().get(AnnotationType.mask) + if src_mask_cat is not None: + assert src_label_cat is not None + dst_mask_cat = MaskCategories(attributes=src_mask_cat.attributes) + dst_mask_cat.colormap = { + id: src_mask_cat.colormap[id] + for id, _ in enumerate(src_label_cat.items) + if self._map_id(id) or id == 0 + } + self._categories[AnnotationType.mask] = dst_mask_cat + + src_points_cat = self._extractor.categories().get(AnnotationType.points) + if src_points_cat is not None: + assert src_label_cat is not None + dst_points_cat = PointsCategories(attributes=src_points_cat.attributes) + dst_points_cat.items = { + id: src_points_cat.items[id] + for id, item in enumerate(src_label_cat.items) + if self._map_id(id) or id == 0 + } + self._categories[AnnotationType.points] = dst_points_cat + + def _make_label_id_map(self, src_label_cat, label_mapping, default_action): + dst_label_cat = LabelCategories(attributes=src_label_cat.attributes) + id_mapping = {} + for src_index, src_label in enumerate(src_label_cat.items): + dst_label = label_mapping.get(src_label.name) + if not dst_label and default_action == self.DefaultAction.keep: + dst_label = src_label.name # keep unspecified as is + if not dst_label: + continue + + dst_index = dst_label_cat.find(dst_label)[0] + if dst_index is None: + dst_label_cat.add(dst_label, + src_label.parent, src_label.attributes) + dst_index = dst_label_cat.find(dst_label)[0] + id_mapping[src_index] = dst_index + + if log.getLogger().isEnabledFor(log.DEBUG): + log.debug("Label mapping:") + for src_id, src_label in enumerate(src_label_cat.items): + if id_mapping.get(src_id): + log.debug("#%s '%s' -> #%s '%s'", + src_id, src_label.name, id_mapping[src_id], + dst_label_cat.items[id_mapping[src_id]].name + ) + else: + log.debug("#%s '%s' -> ", src_id, src_label.name) + + self._map_id = lambda src_id: id_mapping.get(src_id, None) + self._categories[AnnotationType.label] = dst_label_cat + + def categories(self): + return self._categories + + def transform_item(self, item): + # TODO: provide non-inplace version + annotations = [] + for ann in item.annotations: + if ann.type in { AnnotationType.label, AnnotationType.mask, + AnnotationType.points, AnnotationType.polygon, + AnnotationType.polyline, AnnotationType.bbox + } and ann.label is not None: + conv_label = self._map_id(ann.label) + if conv_label is not None: + ann._label = conv_label + annotations.append(ann) + else: + annotations.append(ann) + item._annotations = annotations + return item \ No newline at end of file diff --git a/datumaro/datumaro/plugins/voc_format/__init__.py b/datumaro/datumaro/plugins/voc_format/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/datumaro/datumaro/plugins/voc_format/converter.py b/datumaro/datumaro/plugins/voc_format/converter.py new file mode 100644 index 00000000000..5467e52ffb8 --- /dev/null +++ b/datumaro/datumaro/plugins/voc_format/converter.py @@ -0,0 +1,588 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from collections import OrderedDict, defaultdict +from enum import Enum +from itertools import chain +import logging as log +from lxml import etree as ET +import os +import os.path as osp + +from datumaro.components.cli_plugin import CliPlugin +from datumaro.components.converter import Converter +from datumaro.components.extractor import (DEFAULT_SUBSET_NAME, AnnotationType, + LabelCategories, CompiledMask, +) +from datumaro.util.image import save_image +from datumaro.util.mask_tools import paint_mask, remap_mask + +from .format import (VocTask, VocPath, + VocInstColormap, VocPose, + parse_label_map, make_voc_label_map, make_voc_categories, write_label_map +) + + +def _convert_attr(name, attributes, type_conv, default=None, warn=True): + d = object() + value = attributes.get(name, d) + if value is d: + return default + + try: + return type_conv(value) + except Exception as e: + log.warning("Failed to convert attribute '%s'='%s': %s" % \ + (name, value, e)) + return default + +def _write_xml_bbox(bbox, parent_elem): + x, y, w, h = bbox + bbox_elem = ET.SubElement(parent_elem, 'bndbox') + ET.SubElement(bbox_elem, 'xmin').text = str(x) + ET.SubElement(bbox_elem, 'ymin').text = str(y) + ET.SubElement(bbox_elem, 'xmax').text = str(x + w) + ET.SubElement(bbox_elem, 'ymax').text = str(y + h) + return bbox_elem + + +LabelmapType = Enum('LabelmapType', ['voc', 'source', 'guess']) + +class _Converter: + def __init__(self, extractor, save_dir, + tasks=None, apply_colormap=True, save_images=False, label_map=None): + assert tasks is None or isinstance(tasks, (VocTask, list, set)) + if tasks is None: + tasks = set(VocTask) + elif isinstance(tasks, VocTask): + tasks = {tasks} + else: + tasks = set(t if t in VocTask else VocTask[t] for t in tasks) + self._tasks = tasks + + self._extractor = extractor + self._save_dir = save_dir + self._apply_colormap = apply_colormap + self._save_images = save_images + + self._load_categories(label_map) + + def convert(self): + self.init_dirs() + self.save_subsets() + self.save_label_map() + + def init_dirs(self): + save_dir = self._save_dir + subsets_dir = osp.join(save_dir, VocPath.SUBSETS_DIR) + cls_subsets_dir = osp.join(subsets_dir, + VocPath.TASK_DIR[VocTask.classification]) + action_subsets_dir = osp.join(subsets_dir, + VocPath.TASK_DIR[VocTask.action_classification]) + layout_subsets_dir = osp.join(subsets_dir, + VocPath.TASK_DIR[VocTask.person_layout]) + segm_subsets_dir = osp.join(subsets_dir, + VocPath.TASK_DIR[VocTask.segmentation]) + ann_dir = osp.join(save_dir, VocPath.ANNOTATIONS_DIR) + img_dir = osp.join(save_dir, VocPath.IMAGES_DIR) + segm_dir = osp.join(save_dir, VocPath.SEGMENTATION_DIR) + inst_dir = osp.join(save_dir, VocPath.INSTANCES_DIR) + images_dir = osp.join(save_dir, VocPath.IMAGES_DIR) + + os.makedirs(subsets_dir, exist_ok=True) + os.makedirs(ann_dir, exist_ok=True) + os.makedirs(img_dir, exist_ok=True) + os.makedirs(segm_dir, exist_ok=True) + os.makedirs(inst_dir, exist_ok=True) + os.makedirs(images_dir, exist_ok=True) + + self._subsets_dir = subsets_dir + self._cls_subsets_dir = cls_subsets_dir + self._action_subsets_dir = action_subsets_dir + self._layout_subsets_dir = layout_subsets_dir + self._segm_subsets_dir = segm_subsets_dir + self._ann_dir = ann_dir + self._img_dir = img_dir + self._segm_dir = segm_dir + self._inst_dir = inst_dir + self._images_dir = images_dir + + def get_label(self, label_id): + return self._extractor.categories()[AnnotationType.label] \ + .items[label_id].name + + def save_subsets(self): + subsets = self._extractor.subsets() + if len(subsets) == 0: + subsets = [ None ] + + for subset_name in subsets: + if subset_name: + subset = self._extractor.get_subset(subset_name) + else: + subset_name = DEFAULT_SUBSET_NAME + subset = self._extractor + + class_lists = OrderedDict() + clsdet_list = OrderedDict() + action_list = OrderedDict() + layout_list = OrderedDict() + segm_list = OrderedDict() + + for item in subset: + log.debug("Converting item '%s'", item.id) + + image_filename = '' + if item.has_image: + image_filename = item.image.filename + if self._save_images: + if item.has_image and item.image.has_data: + if image_filename: + image_filename = osp.splitext(image_filename)[0] + else: + image_filename = item.id + image_filename += VocPath.IMAGE_EXT + save_image(osp.join(self._images_dir, image_filename), + item.image.data) + else: + log.debug("Item '%s' has no image" % item.id) + + labels = [] + bboxes = [] + masks = [] + for a in item.annotations: + if a.type == AnnotationType.label: + labels.append(a) + elif a.type == AnnotationType.bbox: + bboxes.append(a) + elif a.type == AnnotationType.mask: + masks.append(a) + + if len(bboxes) != 0: + root_elem = ET.Element('annotation') + if '_' in item.id: + folder = item.id[ : item.id.find('_')] + else: + folder = '' + ET.SubElement(root_elem, 'folder').text = folder + ET.SubElement(root_elem, 'filename').text = image_filename + + source_elem = ET.SubElement(root_elem, 'source') + ET.SubElement(source_elem, 'database').text = 'Unknown' + ET.SubElement(source_elem, 'annotation').text = 'Unknown' + ET.SubElement(source_elem, 'image').text = 'Unknown' + + if item.has_image: + h, w = item.image.size + if item.image.has_data: + image_shape = item.image.data.shape + c = 1 if len(image_shape) == 2 else image_shape[2] + else: + c = 3 + size_elem = ET.SubElement(root_elem, 'size') + ET.SubElement(size_elem, 'width').text = str(w) + ET.SubElement(size_elem, 'height').text = str(h) + ET.SubElement(size_elem, 'depth').text = str(c) + + item_segmented = 0 < len(masks) + ET.SubElement(root_elem, 'segmented').text = \ + str(int(item_segmented)) + + objects_with_parts = [] + objects_with_actions = defaultdict(dict) + + main_bboxes = [] + layout_bboxes = [] + for bbox in bboxes: + label = self.get_label(bbox.label) + if self._is_part(label): + layout_bboxes.append(bbox) + elif self._is_label(label): + main_bboxes.append(bbox) + + for new_obj_id, obj in enumerate(main_bboxes): + attr = obj.attributes + + obj_elem = ET.SubElement(root_elem, 'object') + + obj_label = self.get_label(obj.label) + ET.SubElement(obj_elem, 'name').text = obj_label + + if 'pose' in attr: + pose = _convert_attr('pose', attr, + lambda v: VocPose[v], VocPose.Unspecified) + ET.SubElement(obj_elem, 'pose').text = pose.name + + if 'truncated' in attr: + truncated = _convert_attr('truncated', attr, int, 0) + ET.SubElement(obj_elem, 'truncated').text = \ + '%d' % truncated + + if 'difficult' in attr: + difficult = _convert_attr('difficult', attr, int, 0) + ET.SubElement(obj_elem, 'difficult').text = \ + '%d' % difficult + + if 'occluded' in attr: + occluded = _convert_attr('occluded', attr, int, 0) + ET.SubElement(obj_elem, 'occluded').text = \ + '%d' % occluded + + bbox = obj.get_bbox() + if bbox is not None: + _write_xml_bbox(bbox, obj_elem) + + for part_bbox in filter( + lambda x: obj.group and obj.group == x.group, + layout_bboxes): + part_elem = ET.SubElement(obj_elem, 'part') + ET.SubElement(part_elem, 'name').text = \ + self.get_label(part_bbox.label) + _write_xml_bbox(part_bbox.get_bbox(), part_elem) + + objects_with_parts.append(new_obj_id) + + label_actions = self._get_actions(obj_label) + actions_elem = ET.Element('actions') + for action in label_actions: + present = 0 + if action in attr: + present = _convert_attr(action, attr, + lambda v: int(v == True), 0) + ET.SubElement(actions_elem, action).text = \ + '%d' % present + + objects_with_actions[new_obj_id][action] = present + if len(actions_elem) != 0: + obj_elem.append(actions_elem) + + if self._tasks & {None, + VocTask.detection, + VocTask.person_layout, + VocTask.action_classification}: + with open(osp.join(self._ann_dir, item.id + '.xml'), 'w') as f: + f.write(ET.tostring(root_elem, + encoding='unicode', pretty_print=True)) + + clsdet_list[item.id] = True + layout_list[item.id] = objects_with_parts + action_list[item.id] = objects_with_actions + + for label_ann in labels: + label = self.get_label(label_ann.label) + if not self._is_label(label): + continue + class_list = class_lists.get(item.id, set()) + class_list.add(label_ann.label) + class_lists[item.id] = class_list + + clsdet_list[item.id] = True + + if masks: + compiled_mask = CompiledMask.from_instance_masks(masks, + instance_labels=[self._label_id_mapping(m.label) + for m in masks]) + + self.save_segm( + osp.join(self._segm_dir, item.id + VocPath.SEGM_EXT), + compiled_mask.class_mask) + self.save_segm( + osp.join(self._inst_dir, item.id + VocPath.SEGM_EXT), + compiled_mask.instance_mask, + colormap=VocInstColormap) + + segm_list[item.id] = True + + if len(item.annotations) == 0: + clsdet_list[item.id] = None + layout_list[item.id] = None + action_list[item.id] = None + segm_list[item.id] = None + + if self._tasks & {None, + VocTask.classification, + VocTask.detection, + VocTask.action_classification, + VocTask.person_layout}: + self.save_clsdet_lists(subset_name, clsdet_list) + if self._tasks & {None, VocTask.classification}: + self.save_class_lists(subset_name, class_lists) + if self._tasks & {None, VocTask.action_classification}: + self.save_action_lists(subset_name, action_list) + if self._tasks & {None, VocTask.person_layout}: + self.save_layout_lists(subset_name, layout_list) + if self._tasks & {None, VocTask.segmentation}: + self.save_segm_lists(subset_name, segm_list) + + def save_action_lists(self, subset_name, action_list): + os.makedirs(self._action_subsets_dir, exist_ok=True) + + ann_file = osp.join(self._action_subsets_dir, subset_name + '.txt') + with open(ann_file, 'w') as f: + for item in action_list: + f.write('%s\n' % item) + + if len(action_list) == 0: + return + + all_actions = set(chain(*(self._get_actions(l) + for l in self._label_map))) + for action in all_actions: + ann_file = osp.join(self._action_subsets_dir, + '%s_%s.txt' % (action, subset_name)) + with open(ann_file, 'w') as f: + for item, objs in action_list.items(): + if not objs: + continue + for obj_id, obj_actions in objs.items(): + presented = obj_actions[action] + f.write('%s %s % d\n' % \ + (item, 1 + obj_id, 1 if presented else -1)) + + def save_class_lists(self, subset_name, class_lists): + os.makedirs(self._cls_subsets_dir, exist_ok=True) + + if len(class_lists) == 0: + return + + for label in self._label_map: + ann_file = osp.join(self._cls_subsets_dir, + '%s_%s.txt' % (label, subset_name)) + with open(ann_file, 'w') as f: + for item, item_labels in class_lists.items(): + if not item_labels: + continue + item_labels = [self._strip_label(self.get_label(l)) + for l in item_labels] + presented = label in item_labels + f.write('%s % d\n' % (item, 1 if presented else -1)) + + def save_clsdet_lists(self, subset_name, clsdet_list): + os.makedirs(self._cls_subsets_dir, exist_ok=True) + + ann_file = osp.join(self._cls_subsets_dir, subset_name + '.txt') + with open(ann_file, 'w') as f: + for item in clsdet_list: + f.write('%s\n' % item) + + def save_segm_lists(self, subset_name, segm_list): + os.makedirs(self._segm_subsets_dir, exist_ok=True) + + ann_file = osp.join(self._segm_subsets_dir, subset_name + '.txt') + with open(ann_file, 'w') as f: + for item in segm_list: + f.write('%s\n' % item) + + def save_layout_lists(self, subset_name, layout_list): + os.makedirs(self._layout_subsets_dir, exist_ok=True) + + ann_file = osp.join(self._layout_subsets_dir, subset_name + '.txt') + with open(ann_file, 'w') as f: + for item, item_layouts in layout_list.items(): + if item_layouts: + for obj_id in item_layouts: + f.write('%s % d\n' % (item, 1 + obj_id)) + else: + f.write('%s\n' % (item)) + + def save_segm(self, path, mask, colormap=None): + if self._apply_colormap: + if colormap is None: + colormap = self._categories[AnnotationType.mask].colormap + mask = paint_mask(mask, colormap) + save_image(path, mask) + + def save_label_map(self): + path = osp.join(self._save_dir, VocPath.LABELMAP_FILE) + write_label_map(path, self._label_map) + + @staticmethod + def _strip_label(label): + return label.lower().strip() + + def _load_categories(self, label_map_source=None): + if label_map_source == LabelmapType.voc.name: + # strictly use VOC default labelmap + label_map = make_voc_label_map() + + elif label_map_source == LabelmapType.source.name: + # generate colormap from the input dataset + labels = self._extractor.categories() \ + .get(AnnotationType.label, LabelCategories()) + label_map = OrderedDict() + label_map['background'] = [None, [], []] + for item in labels.items: + label_map[item.name] = [None, [], []] + + elif label_map_source in [LabelmapType.guess.name, None]: + # generate colormap for union of VOC and input dataset labels + label_map = make_voc_label_map() + + rebuild_colormap = False + source_labels = self._extractor.categories() \ + .get(AnnotationType.label, LabelCategories()) + for label in source_labels.items: + label_name = self._strip_label(label.name) + if label_name not in label_map: + rebuild_colormap = True + if label.attributes or label_name not in label_map: + label_map[label_name] = [None, [], label.attributes] + + if rebuild_colormap: + for item in label_map.values(): + item[0] = None + + elif isinstance(label_map_source, dict): + label_map = label_map_source + + elif isinstance(label_map_source, str) and osp.isfile(label_map_source): + label_map = parse_label_map(label_map_source) + + else: + raise Exception("Wrong labelmap specified, " + "expected one of %s or a file path" % \ + ', '.join(t.name for t in LabelmapType)) + + self._categories = make_voc_categories(label_map) + + self._label_map = label_map + colormap = self._categories[AnnotationType.mask].colormap + for label_id, color in colormap.items(): + label_desc = label_map[ + self._categories[AnnotationType.label].items[label_id].name] + label_desc[0] = color + + self._label_id_mapping = self._make_label_id_map() + + def _is_label(self, s): + return self._label_map.get(self._strip_label(s)) is not None + + def _is_part(self, s): + s = self._strip_label(s) + for label_desc in self._label_map.values(): + if s in label_desc[1]: + return True + return False + + def _is_action(self, label, s): + return self._strip_label(s) in self._get_actions(label) + + def _get_actions(self, label): + label_desc = self._label_map.get(self._strip_label(label)) + if not label_desc: + return [] + return label_desc[2] + + def _make_label_id_map(self): + source_labels = { + id: label.name for id, label in + enumerate(self._extractor.categories().get( + AnnotationType.label, LabelCategories()).items) + } + target_labels = { + label.name: id for id, label in + enumerate(self._categories[AnnotationType.label].items) + } + id_mapping = { + src_id: target_labels.get(src_label, 0) + for src_id, src_label in source_labels.items() + } + + void_labels = [src_label for src_id, src_label in source_labels.items() + if src_label not in target_labels] + if void_labels: + log.warning("The following labels are remapped to background: %s" % + ', '.join(void_labels)) + log.debug("Saving segmentations with the following label mapping: \n%s" % + '\n'.join(["#%s '%s' -> #%s '%s'" % + ( + src_id, src_label, id_mapping[src_id], + self._categories[AnnotationType.label] \ + .items[id_mapping[src_id]].name + ) + for src_id, src_label in source_labels.items() + ]) + ) + + def map_id(src_id): + return id_mapping.get(src_id, 0) + return map_id + + def _remap_mask(self, mask): + return remap_mask(mask, self._label_id_mapping) + +class VocConverter(Converter, CliPlugin): + @staticmethod + def _split_tasks_string(s): + return [VocTask[i.strip()] for i in s.split(',')] + + @staticmethod + def _get_labelmap(s): + if osp.isfile(s): + return s + try: + return LabelmapType[s].name + except KeyError: + import argparse + raise argparse.ArgumentTypeError() + + @classmethod + def build_cmdline_parser(cls, **kwargs): + parser = super().build_cmdline_parser(**kwargs) + + parser.add_argument('--save-images', action='store_true', + help="Save images (default: %(default)s)") + parser.add_argument('--apply-colormap', type=bool, default=True, + help="Use colormap for class and instance masks " + "(default: %(default)s)") + parser.add_argument('--label-map', type=cls._get_labelmap, default=None, + help="Labelmap file path or one of %s" % \ + ', '.join(t.name for t in LabelmapType)) + parser.add_argument('--tasks', type=cls._split_tasks_string, + default=None, + help="VOC task filter, comma-separated list of {%s} " + "(default: all)" % ', '.join([t.name for t in VocTask])) + + return parser + + def __init__(self, tasks=None, save_images=False, + apply_colormap=False, label_map=None): + super().__init__() + + self._options = { + 'tasks': tasks, + 'save_images': save_images, + 'apply_colormap': apply_colormap, + 'label_map': label_map, + } + + def __call__(self, extractor, save_dir): + converter = _Converter(extractor, save_dir, **self._options) + converter.convert() + +class VocClassificationConverter(VocConverter): + def __init__(self, **kwargs): + kwargs['tasks'] = VocTask.classification + super().__init__(**kwargs) + +class VocDetectionConverter(VocConverter): + def __init__(self, **kwargs): + kwargs['tasks'] = VocTask.detection + super().__init__(**kwargs) + +class VocLayoutConverter(VocConverter): + def __init__(self, **kwargs): + kwargs['tasks'] = VocTask.person_layout + super().__init__(**kwargs) + +class VocActionConverter(VocConverter): + def __init__(self, **kwargs): + kwargs['tasks'] = VocTask.action_classification + super().__init__(**kwargs) + +class VocSegmentationConverter(VocConverter): + def __init__(self, **kwargs): + kwargs['tasks'] = VocTask.segmentation + super().__init__(**kwargs) diff --git a/datumaro/datumaro/plugins/voc_format/extractor.py b/datumaro/datumaro/plugins/voc_format/extractor.py new file mode 100644 index 00000000000..31047e8532e --- /dev/null +++ b/datumaro/datumaro/plugins/voc_format/extractor.py @@ -0,0 +1,740 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from collections import defaultdict +import logging as log +import os +import os.path as osp +from xml.etree import ElementTree as ET + +from datumaro.components.extractor import (SourceExtractor, Extractor, + DEFAULT_SUBSET_NAME, DatasetItem, + AnnotationType, Label, Mask, Bbox, CompiledMask +) +from datumaro.util import dir_items +from datumaro.util.image import lazy_image, Image +from datumaro.util.mask_tools import lazy_mask, invert_colormap + +from .format import ( + VocTask, VocPath, VocInstColormap, parse_label_map, make_voc_categories +) + + +_inverse_inst_colormap = invert_colormap(VocInstColormap) + +class VocExtractor(SourceExtractor): + class Subset(Extractor): + def __init__(self, name, parent): + super().__init__() + self._parent = parent + self._name = name + self.items = [] + + def __iter__(self): + for item_id in self.items: + yield self._parent._get(item_id, self._name) + + def __len__(self): + return len(self.items) + + def categories(self): + return self._parent.categories() + + def _load_subsets(self, subsets_dir): + dir_files = dir_items(subsets_dir, '.txt', truncate_ext=True) + subset_names = [s for s in dir_files if '_' not in s] + + subsets = {} + for subset_name in subset_names: + subset_file_name = subset_name + if subset_name == DEFAULT_SUBSET_NAME: + subset_name = None + subset = __class__.Subset(subset_name, self) + + subset.items = [] + with open(osp.join(subsets_dir, subset_file_name + '.txt'), 'r') as f: + for line in f: + line = line.split()[0].strip() + if line: + subset.items.append(line) + + subsets[subset_name] = subset + return subsets + + def _load_cls_annotations(self, subsets_dir, subset_names): + subset_file_names = [n if n else DEFAULT_SUBSET_NAME + for n in subset_names] + dir_files = dir_items(subsets_dir, '.txt', truncate_ext=True) + + label_annotations = defaultdict(list) + label_anno_files = [s for s in dir_files \ + if '_' in s and s[s.rfind('_') + 1:] in subset_file_names] + for ann_filename in label_anno_files: + with open(osp.join(subsets_dir, ann_filename + '.txt'), 'r') as f: + label = ann_filename[:ann_filename.rfind('_')] + label_id = self._get_label_id(label) + for line in f: + item, present = line.split() + if present == '1': + label_annotations[item].append(label_id) + + self._annotations[VocTask.classification] = dict(label_annotations) + + def _load_det_annotations(self): + det_anno_dir = osp.join(self._path, VocPath.ANNOTATIONS_DIR) + det_anno_items = dir_items(det_anno_dir, '.xml', truncate_ext=True) + det_annotations = dict() + for ann_item in det_anno_items: + with open(osp.join(det_anno_dir, ann_item + '.xml'), 'r') as f: + ann_file_data = f.read() + det_annotations[ann_item] = ann_file_data + + self._annotations[VocTask.detection] = det_annotations + + def _load_categories(self): + label_map = None + label_map_path = osp.join(self._path, VocPath.LABELMAP_FILE) + if osp.isfile(label_map_path): + label_map = parse_label_map(label_map_path) + self._categories = make_voc_categories(label_map) + + def __init__(self, path, task): + super().__init__() + + self._path = path + self._subsets = {} + self._categories = {} + self._annotations = {} + self._task = task + + self._load_categories() + + def __len__(self): + length = 0 + for subset in self._subsets.values(): + length += len(subset) + return length + + def subsets(self): + return list(self._subsets) + + def get_subset(self, name): + return self._subsets[name] + + def categories(self): + return self._categories + + def __iter__(self): + for subset in self._subsets.values(): + for item in subset: + yield item + + def _get(self, item_id, subset_name): + image = osp.join(self._path, VocPath.IMAGES_DIR, + item_id + VocPath.IMAGE_EXT) + det_annotations = self._annotations.get(VocTask.detection) + if det_annotations is not None: + det_annotations = det_annotations.get(item_id) + if det_annotations is not None: + root_elem = ET.fromstring(det_annotations) + height = root_elem.find('size/height') + if height is not None: + height = int(height.text) + width = root_elem.find('size/width') + if width is not None: + width = int(width.text) + if height and width: + image = Image(path=image, size=(height, width)) + + annotations = self._get_annotations(item_id) + + return DatasetItem(annotations=annotations, + id=item_id, subset=subset_name, image=image) + + def _get_label_id(self, label): + label_id, _ = self._categories[AnnotationType.label].find(label) + if label_id is None: + log.debug("Unknown label '%s'. Loaded labels: %s", + label, + ', '.join("'%s'" % s.name + for s in self._categories[AnnotationType.label].items)) + raise Exception("Unknown label '%s'" % label) + return label_id + + @staticmethod + def _lazy_extract_mask(mask, c): + return lambda: mask == c + + def _get_annotations(self, item_id): + item_annotations = [] + + if self._task is VocTask.segmentation: + class_mask = None + segm_path = osp.join(self._path, VocPath.SEGMENTATION_DIR, + item_id + VocPath.SEGM_EXT) + if osp.isfile(segm_path): + inverse_cls_colormap = \ + self._categories[AnnotationType.mask].inverse_colormap + class_mask = lazy_mask(segm_path, inverse_cls_colormap) + + instances_mask = None + inst_path = osp.join(self._path, VocPath.INSTANCES_DIR, + item_id + VocPath.SEGM_EXT) + if osp.isfile(inst_path): + instances_mask = lazy_mask(inst_path, _inverse_inst_colormap) + + if instances_mask is not None: + compiled_mask = CompiledMask(class_mask, instances_mask) + + if class_mask is not None: + label_cat = self._categories[AnnotationType.label] + instance_labels = compiled_mask.get_instance_labels( + class_count=len(label_cat.items)) + else: + instance_labels = {i: None + for i in range(compiled_mask.instance_count)} + + for instance_id, label_id in instance_labels.items(): + image = compiled_mask.lazy_extract(instance_id) + + attributes = dict() + if label_id is not None: + actions = {a: False + for a in label_cat.items[label_id].attributes + } + attributes.update(actions) + + item_annotations.append(Mask( + image=image, label=label_id, + attributes=attributes, group=instance_id + )) + elif class_mask is not None: + log.warn("item '%s': has only class segmentation, " + "instance masks will not be available" % item_id) + classes = class_mask.image.unique() + for label_id in classes: + image = self._lazy_extract_mask(class_mask, label_id) + item_annotations.append(Mask(image=image, label=label_id)) + + cls_annotations = self._annotations.get(VocTask.classification) + if cls_annotations is not None and \ + self._task is VocTask.classification: + item_labels = cls_annotations.get(item_id) + if item_labels is not None: + for label_id in item_labels: + item_annotations.append(Label(label_id)) + + det_annotations = self._annotations.get(VocTask.detection) + if det_annotations is not None: + det_annotations = det_annotations.get(item_id) + if det_annotations is not None: + root_elem = ET.fromstring(det_annotations) + + for obj_id, object_elem in enumerate(root_elem.findall('object')): + obj_id += 1 + attributes = {} + group = obj_id + + obj_label_id = None + label_elem = object_elem.find('name') + if label_elem is not None: + obj_label_id = self._get_label_id(label_elem.text) + + obj_bbox = self._parse_bbox(object_elem) + + if obj_label_id is None or obj_bbox is None: + continue + + difficult_elem = object_elem.find('difficult') + attributes['difficult'] = difficult_elem is not None and \ + difficult_elem.text == '1' + + truncated_elem = object_elem.find('truncated') + attributes['truncated'] = truncated_elem is not None and \ + truncated_elem.text == '1' + + occluded_elem = object_elem.find('occluded') + attributes['occluded'] = occluded_elem is not None and \ + occluded_elem.text == '1' + + pose_elem = object_elem.find('pose') + if pose_elem is not None: + attributes['pose'] = pose_elem.text + + point_elem = object_elem.find('point') + if point_elem is not None: + point_x = point_elem.find('x') + point_y = point_elem.find('y') + point = [float(point_x.text), float(point_y.text)] + attributes['point'] = point + + actions_elem = object_elem.find('actions') + actions = {a: False + for a in self._categories[AnnotationType.label] \ + .items[obj_label_id].attributes} + if actions_elem is not None: + for action_elem in actions_elem: + actions[action_elem.tag] = (action_elem.text == '1') + for action, present in actions.items(): + attributes[action] = present + + has_parts = False + for part_elem in object_elem.findall('part'): + part = part_elem.find('name').text + part_label_id = self._get_label_id(part) + part_bbox = self._parse_bbox(part_elem) + + if self._task is not VocTask.person_layout: + break + if part_bbox is None: + continue + has_parts = True + item_annotations.append(Bbox(*part_bbox, label=part_label_id, + group=group)) + + if self._task is VocTask.person_layout and not has_parts: + continue + if self._task is VocTask.action_classification and not actions: + continue + + item_annotations.append(Bbox(*obj_bbox, label=obj_label_id, + attributes=attributes, id=obj_id, group=group)) + + return item_annotations + + @staticmethod + def _parse_bbox(object_elem): + bbox_elem = object_elem.find('bndbox') + if bbox_elem is None: + return None + + xmin = float(bbox_elem.find('xmin').text) + xmax = float(bbox_elem.find('xmax').text) + ymin = float(bbox_elem.find('ymin').text) + ymax = float(bbox_elem.find('ymax').text) + return [xmin, ymin, xmax - xmin, ymax - ymin] + +class VocClassificationExtractor(VocExtractor): + def __init__(self, path): + super().__init__(path, task=VocTask.classification) + + subsets_dir = osp.join(path, VocPath.SUBSETS_DIR, 'Main') + subsets = self._load_subsets(subsets_dir) + self._subsets = subsets + + self._load_cls_annotations(subsets_dir, subsets) + +class VocDetectionExtractor(VocExtractor): + def __init__(self, path): + super().__init__(path, task=VocTask.detection) + + subsets_dir = osp.join(path, VocPath.SUBSETS_DIR, 'Main') + subsets = self._load_subsets(subsets_dir) + self._subsets = subsets + + self._load_det_annotations() + +class VocSegmentationExtractor(VocExtractor): + def __init__(self, path): + super().__init__(path, task=VocTask.segmentation) + + subsets_dir = osp.join(path, VocPath.SUBSETS_DIR, 'Segmentation') + subsets = self._load_subsets(subsets_dir) + self._subsets = subsets + +class VocLayoutExtractor(VocExtractor): + def __init__(self, path): + super().__init__(path, task=VocTask.person_layout) + + subsets_dir = osp.join(path, VocPath.SUBSETS_DIR, 'Layout') + subsets = self._load_subsets(subsets_dir) + self._subsets = subsets + + self._load_det_annotations() + +class VocActionExtractor(VocExtractor): + def __init__(self, path): + super().__init__(path, task=VocTask.action_classification) + + subsets_dir = osp.join(path, VocPath.SUBSETS_DIR, 'Action') + subsets = self._load_subsets(subsets_dir) + self._subsets = subsets + + self._load_det_annotations() + + +class VocResultsExtractor(Extractor): + class Subset(Extractor): + def __init__(self, name, parent): + super().__init__() + self._parent = parent + self._name = name + self.items = [] + + def __iter__(self): + for item in self.items: + yield self._parent._get(item, self._name) + + def __len__(self): + return len(self.items) + + def categories(self): + return self._parent.categories() + + _SUPPORTED_TASKS = { + VocTask.classification: { + 'dir': 'Main', + 'mark': 'cls', + 'ext': '.txt', + 'path' : ['%(comp)s_cls_%(subset)s_%(label)s.txt'], + 'comp': ['comp1', 'comp2'], + }, + VocTask.detection: { + 'dir': 'Main', + 'mark': 'det', + 'ext': '.txt', + 'path': ['%(comp)s_det_%(subset)s_%(label)s.txt'], + 'comp': ['comp3', 'comp4'], + }, + VocTask.segmentation: { + 'dir': 'Segmentation', + 'mark': ['cls', 'inst'], + 'ext': '.png', + 'path': ['%(comp)s_%(subset)s_cls', '%(item)s.png'], + 'comp': ['comp5', 'comp6'], + }, + VocTask.person_layout: { + 'dir': 'Layout', + 'mark': 'layout', + 'ext': '.xml', + 'path': ['%(comp)s_layout_%(subset)s.xml'], + 'comp': ['comp7', 'comp8'], + }, + VocTask.action_classification: { + 'dir': 'Action', + 'mark': 'action', + 'ext': '.txt', + 'path': ['%(comp)s_action_%(subset)s_%(label)s.txt'], + 'comp': ['comp9', 'comp10'], + }, + } + + def _parse_txt_ann(self, path, subsets, annotations, task): + task_desc = self._SUPPORTED_TASKS[task] + task_dir = osp.join(path, task_desc['dir']) + ann_ext = task_desc['ext'] + if not osp.isdir(task_dir): + return + + ann_files = dir_items(task_dir, ann_ext, truncate_ext=True) + + for ann_file in ann_files: + ann_parts = filter(None, ann_file.strip().split('_')) + if len(ann_parts) != 4: + continue + _, mark, subset_name, label = ann_parts + if mark != task_desc['mark']: + continue + + label_id = self._get_label_id(label) + anns = defaultdict(list) + with open(osp.join(task_dir, ann_file + ann_ext), 'r') as f: + for line in f: + line_parts = line.split() + item = line_parts[0] + anns[item].append((label_id, *line_parts[1:])) + + subset = VocResultsExtractor.Subset(subset_name, self) + subset.items = list(anns) + + subsets[subset_name] = subset + annotations[subset_name] = dict(anns) + + def _parse_classification(self, path, subsets, annotations): + self._parse_txt_ann(path, subsets, annotations, + VocTask.classification) + + def _parse_detection(self, path, subsets, annotations): + self._parse_txt_ann(path, subsets, annotations, + VocTask.detection) + + def _parse_action(self, path, subsets, annotations): + self._parse_txt_ann(path, subsets, annotations, + VocTask.action_classification) + + def _load_categories(self): + label_map = None + label_map_path = osp.join(self._path, VocPath.LABELMAP_FILE) + if osp.isfile(label_map_path): + label_map = parse_label_map(label_map_path) + self._categories = make_voc_categories(label_map) + + def _get_label_id(self, label): + label_id = self._categories[AnnotationType.label].find(label) + assert label_id is not None + return label_id + + def __init__(self, path): + super().__init__() + + self._path = path + self._subsets = {} + self._annotations = {} + + self._load_categories() + + def __len__(self): + length = 0 + for subset in self._subsets.values(): + length += len(subset) + return length + + def subsets(self): + return list(self._subsets) + + def get_subset(self, name): + return self._subsets[name] + + def categories(self): + return self._categories + + def __iter__(self): + for subset in self._subsets.values(): + for item in subset: + yield item + + def _get(self, item, subset_name): + image = None + image_path = osp.join(self._path, VocPath.IMAGES_DIR, + item + VocPath.IMAGE_EXT) + if osp.isfile(image_path): + image = lazy_image(image_path) + + annotations = self._get_annotations(item, subset_name) + + return DatasetItem(annotations=annotations, + id=item, subset=subset_name, image=image) + + def _get_annotations(self, item, subset_name): + raise NotImplementedError() + +class VocComp_1_2_Extractor(VocResultsExtractor): + def __init__(self, path): + super().__init__(path) + + subsets = {} + annotations = defaultdict(dict) + + self._parse_classification(path, subsets, annotations) + + self._subsets = subsets + self._annotations = dict(annotations) + + def _get_annotations(self, item, subset_name): + annotations = [] + + cls_ann = self._annotations[subset_name].get(item) + if cls_ann is not None: + for desc in cls_ann: + label_id, conf = desc + annotations.append(Label( + int(label_id), + attributes={ 'score': float(conf) } + )) + + return annotations + +class VocComp_3_4_Extractor(VocResultsExtractor): + def __init__(self, path): + super().__init__(path) + + subsets = {} + annotations = defaultdict(dict) + + self._parse_detection(path, subsets, annotations) + + self._subsets = subsets + self._annotations = dict(annotations) + + def _get_annotations(self, item, subset_name): + annotations = [] + + det_ann = self._annotations[subset_name].get(item) + if det_ann is not None: + for desc in det_ann: + label_id, conf, left, top, right, bottom = desc + annotations.append(Bbox( + x=float(left), y=float(top), + w=float(right) - float(left), h=float(bottom) - float(top), + label=int(label_id), + attributes={ 'score': float(conf) } + )) + + return annotations + +class VocComp_5_6_Extractor(VocResultsExtractor): + def __init__(self, path): + super().__init__(path) + + subsets = {} + annotations = defaultdict(dict) + + task_dir = osp.join(path, 'Segmentation') + if not osp.isdir(task_dir): + return + + ann_files = os.listdir(task_dir) + + for ann_dir in ann_files: + ann_parts = filter(None, ann_dir.strip().split('_')) + if len(ann_parts) != 4: + continue + _, subset_name, mark = ann_parts + if mark not in ['cls', 'inst']: + continue + + item_dir = osp.join(task_dir, ann_dir) + items = dir_items(item_dir, '.png', truncate_ext=True) + items = { name: osp.join(item_dir, item + '.png') \ + for name, item in items } + + subset = VocResultsExtractor.Subset(subset_name, self) + subset.items = list(items) + + subsets[subset_name] = subset + annotations[subset_name][mark] = items + + self._subsets = subsets + self._annotations = dict(annotations) + + def _get_annotations(self, item, subset_name): + annotations = [] + + segm_ann = self._annotations[subset_name] + cls_image_path = segm_ann.get(item) + if cls_image_path and osp.isfile(cls_image_path): + inverse_cls_colormap = \ + self._categories[AnnotationType.mask].inverse_colormap + annotations.append(Mask( + image=lazy_mask(cls_image_path, inverse_cls_colormap), + attributes={ 'class': True } + )) + + inst_ann = self._annotations[subset_name] + inst_image_path = inst_ann.get(item) + if inst_image_path and osp.isfile(inst_image_path): + annotations.append(Mask( + image=lazy_mask(inst_image_path, _inverse_inst_colormap), + attributes={ 'instances': True } + )) + + return annotations + +class VocComp_7_8_Extractor(VocResultsExtractor): + def __init__(self, path): + super().__init__(path) + + subsets = {} + annotations = defaultdict(dict) + + task = VocTask.person_layout + task_desc = self._SUPPORTED_TASKS[task] + task_dir = osp.join(path, task_desc['dir']) + if not osp.isdir(task_dir): + return + + ann_ext = task_desc['ext'] + ann_files = dir_items(task_dir, ann_ext, truncate_ext=True) + + for ann_file in ann_files: + ann_parts = filter(None, ann_file.strip().split('_')) + if len(ann_parts) != 4: + continue + _, mark, subset_name, _ = ann_parts + if mark != task_desc['mark']: + continue + + layouts = {} + root = ET.parse(osp.join(task_dir, ann_file + ann_ext)) + root_elem = root.getroot() + for layout_elem in root_elem.findall('layout'): + item = layout_elem.find('image').text + obj_id = int(layout_elem.find('object').text) + conf = float(layout_elem.find('confidence').text) + parts = [] + for part_elem in layout_elem.findall('part'): + label_id = self._get_label_id(part_elem.find('class').text) + bbox_elem = part_elem.find('bndbox') + xmin = float(bbox_elem.find('xmin').text) + xmax = float(bbox_elem.find('xmax').text) + ymin = float(bbox_elem.find('ymin').text) + ymax = float(bbox_elem.find('ymax').text) + bbox = [xmin, ymin, xmax - xmin, ymax - ymin] + parts.append((label_id, bbox)) + layouts[item] = [obj_id, conf, parts] + + subset = VocResultsExtractor.Subset(subset_name, self) + subset.items = list(layouts) + + subsets[subset_name] = subset + annotations[subset_name] = layouts + + self._subsets = subsets + self._annotations = dict(annotations) + + def _get_annotations(self, item, subset_name): + annotations = [] + + layout_ann = self._annotations[subset_name].get(item) + if layout_ann is not None: + for desc in layout_ann: + obj_id, conf, parts = desc + attributes = { + 'score': conf, + 'object_id': obj_id, + } + + for part in parts: + label_id, bbox = part + annotations.append(Bbox( + *bbox, label=label_id, + attributes=attributes)) + + return annotations + +class VocComp_9_10_Extractor(VocResultsExtractor): + def __init__(self, path): + super().__init__(path) + + subsets = {} + annotations = defaultdict(dict) + + self._parse_action(path, subsets, annotations) + + self._subsets = subsets + self._annotations = dict(annotations) + + def _load_categories(self): + from collections import OrderedDict + from .format import VocAction + label_map = OrderedDict((a.name, [[], [], []]) for a in VocAction) + self._categories = make_voc_categories(label_map) + + def _get_annotations(self, item, subset_name): + annotations = [] + + action_ann = self._annotations[subset_name].get(item) + if action_ann is not None: + for desc in action_ann: + action_id, obj_id, conf = desc + annotations.append(Label( + action_id, + attributes={ + 'score': conf, + 'object_id': int(obj_id), + } + )) + + return annotations \ No newline at end of file diff --git a/datumaro/datumaro/plugins/voc_format/format.py b/datumaro/datumaro/plugins/voc_format/format.py new file mode 100644 index 00000000000..5af79f2d9f9 --- /dev/null +++ b/datumaro/datumaro/plugins/voc_format/format.py @@ -0,0 +1,205 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from collections import OrderedDict +from enum import Enum +from itertools import chain +import numpy as np + +from datumaro.components.extractor import (AnnotationType, + LabelCategories, MaskCategories +) + + +VocTask = Enum('VocTask', [ + 'classification', + 'detection', + 'segmentation', + 'action_classification', + 'person_layout', +]) + +VocLabel = Enum('VocLabel', [ + ('background', 0), + ('aeroplane', 1), + ('bicycle', 2), + ('bird', 3), + ('boat', 4), + ('bottle', 5), + ('bus', 6), + ('car', 7), + ('cat', 8), + ('chair', 9), + ('cow', 10), + ('diningtable', 11), + ('dog', 12), + ('horse', 13), + ('motorbike', 14), + ('person', 15), + ('pottedplant', 16), + ('sheep', 17), + ('sofa', 18), + ('train', 19), + ('tvmonitor', 20), + ('ignored', 255), +]) + +VocPose = Enum('VocPose', [ + 'Unspecified', + 'Left', + 'Right', + 'Frontal', + 'Rear', +]) + +VocBodyPart = Enum('VocBodyPart', [ + 'head', + 'hand', + 'foot', +]) + +VocAction = Enum('VocAction', [ + 'other', + 'jumping', + 'phoning', + 'playinginstrument', + 'reading', + 'ridingbike', + 'ridinghorse', + 'running', + 'takingphoto', + 'usingcomputer', + 'walking', +]) + +def generate_colormap(length=256): + def get_bit(number, index): + return (number >> index) & 1 + + colormap = np.zeros((length, 3), dtype=int) + indices = np.arange(length, dtype=int) + + for j in range(7, -1, -1): + for c in range(3): + colormap[:, c] |= get_bit(indices, c) << j + indices >>= 3 + + return OrderedDict( + (id, tuple(color)) for id, color in enumerate(colormap) + ) + +VocColormap = {id: color for id, color in generate_colormap(256).items() + if id in [l.value for l in VocLabel]} +VocInstColormap = generate_colormap(256) + +class VocPath: + IMAGES_DIR = 'JPEGImages' + ANNOTATIONS_DIR = 'Annotations' + SEGMENTATION_DIR = 'SegmentationClass' + INSTANCES_DIR = 'SegmentationObject' + SUBSETS_DIR = 'ImageSets' + IMAGE_EXT = '.jpg' + SEGM_EXT = '.png' + LABELMAP_FILE = 'labelmap.txt' + + TASK_DIR = { + VocTask.classification: 'Main', + VocTask.detection: 'Main', + VocTask.segmentation: 'Segmentation', + VocTask.action_classification: 'Action', + VocTask.person_layout: 'Layout', + } + + +def make_voc_label_map(): + labels = sorted(VocLabel, key=lambda l: l.value) + label_map = OrderedDict( + (label.name, [VocColormap[label.value], [], []]) for label in labels) + label_map[VocLabel.person.name][1] = [p.name for p in VocBodyPart] + label_map[VocLabel.person.name][2] = [a.name for a in VocAction] + return label_map + +def parse_label_map(path): + if not path: + return None + + label_map = OrderedDict() + with open(path, 'r') as f: + for line in f: + # skip empty and commented lines + line = line.strip() + if not line or line and line[0] == '#': + continue + + # name, color, parts, actions + label_desc = line.strip().split(':') + name = label_desc[0] + + if 1 < len(label_desc) and len(label_desc[1]) != 0: + color = label_desc[1].split(',') + assert len(color) == 3, \ + "Label '%s' has wrong color, expected 'r,g,b', got '%s'" % \ + (name, color) + color = tuple([int(c) for c in color]) + else: + color = None + + if 2 < len(label_desc) and len(label_desc[2]) != 0: + parts = label_desc[2].split(',') + else: + parts = [] + + if 3 < len(label_desc) and len(label_desc[3]) != 0: + actions = label_desc[3].split(',') + else: + actions = [] + + label_map[name] = [color, parts, actions] + return label_map + +def write_label_map(path, label_map): + with open(path, 'w') as f: + f.write('# label:color_rgb:parts:actions\n') + for label_name, label_desc in label_map.items(): + if label_desc[0]: + color_rgb = ','.join(str(c) for c in label_desc[0]) + else: + color_rgb = '' + + parts = ','.join(str(p) for p in label_desc[1]) + actions = ','.join(str(a) for a in label_desc[2]) + + f.write('%s\n' % ':'.join([label_name, color_rgb, parts, actions])) + +# pylint: disable=pointless-statement +def make_voc_categories(label_map=None): + if label_map is None: + label_map = make_voc_label_map() + + categories = {} + + label_categories = LabelCategories() + label_categories.attributes.update(['difficult', 'truncated', 'occluded']) + + for label, desc in label_map.items(): + label_categories.add(label, attributes=desc[2]) + for part in OrderedDict((k, None) for k in chain( + *(desc[1] for desc in label_map.values()))): + label_categories.add(part) + categories[AnnotationType.label] = label_categories + + has_colors = sum(v[0] is not None for v in label_map.values()) + if not has_colors: + colormap = generate_colormap(len(label_map)) + else: + label_id = lambda label: label_categories.find(label)[0] + colormap = { label_id(name): desc[0] + for name, desc in label_map.items() } + mask_categories = MaskCategories(colormap) + mask_categories.inverse_colormap # force init + categories[AnnotationType.mask] = mask_categories + + return categories +# pylint: enable=pointless-statement \ No newline at end of file diff --git a/datumaro/datumaro/plugins/voc_format/importer.py b/datumaro/datumaro/plugins/voc_format/importer.py new file mode 100644 index 00000000000..f3d7c5ef3b9 --- /dev/null +++ b/datumaro/datumaro/plugins/voc_format/importer.py @@ -0,0 +1,81 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import os +import os.path as osp + +from datumaro.components.extractor import Importer +from datumaro.util import find + +from .format import VocTask, VocPath + + +class VocImporter(Importer): + _TASKS = [ + (VocTask.classification, 'voc_classification', 'Main'), + (VocTask.detection, 'voc_detection', 'Main'), + (VocTask.segmentation, 'voc_segmentation', 'Segmentation'), + (VocTask.person_layout, 'voc_layout', 'Layout'), + (VocTask.action_classification, 'voc_action', 'Action'), + ] + + def __call__(self, path, **extra_params): + from datumaro.components.project import Project # cyclic import + project = Project() + + for task, extractor_type, task_dir in self._TASKS: + task_dir = osp.join(path, VocPath.SUBSETS_DIR, task_dir) + if not osp.isdir(task_dir): + continue + + project.add_source(task.name, { + 'url': path, + 'format': extractor_type, + 'options': dict(extra_params), + }) + + if len(project.config.sources) == 0: + raise Exception("Failed to find 'voc' dataset at '%s'" % path) + + return project + + +class VocResultsImporter: + _TASKS = [ + ('comp1', 'voc_comp_1_2', 'Main'), + ('comp2', 'voc_comp_1_2', 'Main'), + ('comp3', 'voc_comp_3_4', 'Main'), + ('comp4', 'voc_comp_3_4', 'Main'), + ('comp5', 'voc_comp_5_6', 'Segmentation'), + ('comp6', 'voc_comp_5_6', 'Segmentation'), + ('comp7', 'voc_comp_7_8', 'Layout'), + ('comp8', 'voc_comp_7_8', 'Layout'), + ('comp9', 'voc_comp_9_10', 'Action'), + ('comp10', 'voc_comp_9_10', 'Action'), + ] + + def __call__(self, path, **extra_params): + from datumaro.components.project import Project # cyclic import + project = Project() + + for task_name, extractor_type, task_dir in self._TASKS: + task_dir = osp.join(path, task_dir) + if not osp.isdir(task_dir): + continue + dir_items = os.listdir(task_dir) + if not find(dir_items, lambda x: x == task_name): + continue + + project.add_source(task_name, { + 'url': task_dir, + 'format': extractor_type, + 'options': dict(extra_params), + }) + + if len(project.config.sources) == 0: + raise Exception("Failed to find 'voc_results' dataset at '%s'" % \ + path) + + return project \ No newline at end of file diff --git a/datumaro/datumaro/plugins/yolo_format/__init__.py b/datumaro/datumaro/plugins/yolo_format/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/datumaro/datumaro/plugins/yolo_format/converter.py b/datumaro/datumaro/plugins/yolo_format/converter.py new file mode 100644 index 00000000000..de30d7d785e --- /dev/null +++ b/datumaro/datumaro/plugins/yolo_format/converter.py @@ -0,0 +1,125 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from collections import OrderedDict +import logging as log +import os +import os.path as osp + +from datumaro.components.converter import Converter +from datumaro.components.extractor import AnnotationType +from datumaro.components.cli_plugin import CliPlugin +from datumaro.util.image import save_image + +from .format import YoloPath + + +def _make_yolo_bbox(img_size, box): + # https://github.com/pjreddie/darknet/blob/master/scripts/voc_label.py + # - values relative to width and height of image + # - are center of rectangle + x = (box[0] + box[2]) / 2 / img_size[0] + y = (box[1] + box[3]) / 2 / img_size[1] + w = (box[2] - box[0]) / img_size[0] + h = (box[3] - box[1]) / img_size[1] + return x, y, w, h + +class YoloConverter(Converter, CliPlugin): + # https://github.com/AlexeyAB/darknet#how-to-train-to-detect-your-custom-objects + + @classmethod + def build_cmdline_parser(cls, **kwargs): + parser = super().build_cmdline_parser(**kwargs) + parser.add_argument('--save-images', action='store_true', + help="Save images (default: %(default)s)") + return parser + + def __init__(self, save_images=False): + super().__init__() + self._save_images = save_images + + def __call__(self, extractor, save_dir): + os.makedirs(save_dir, exist_ok=True) + + label_categories = extractor.categories()[AnnotationType.label] + label_ids = {label.name: idx + for idx, label in enumerate(label_categories.items)} + with open(osp.join(save_dir, 'obj.names'), 'w') as f: + f.writelines('%s\n' % l[0] + for l in sorted(label_ids.items(), key=lambda x: x[1])) + + subsets = extractor.subsets() + if len(subsets) == 0: + subsets = [ None ] + + subset_lists = OrderedDict() + + for subset_name in subsets: + if subset_name and subset_name in YoloPath.SUBSET_NAMES: + subset = extractor.get_subset(subset_name) + elif not subset_name: + subset_name = YoloPath.DEFAULT_SUBSET_NAME + subset = extractor + else: + log.warn("Skipping subset export '%s'. " + "If specified, the only valid names are %s" % \ + (subset_name, ', '.join( + "'%s'" % s for s in YoloPath.SUBSET_NAMES))) + continue + + subset_dir = osp.join(save_dir, 'obj_%s_data' % subset_name) + os.makedirs(subset_dir, exist_ok=True) + + image_paths = OrderedDict() + + for item in subset: + if not item.has_image: + raise Exception("Failed to export item '%s': " + "item has no image info" % item.id) + height, width = item.image.size + + image_name = item.image.filename + item_name = osp.splitext(item.image.filename)[0] + if self._save_images: + if item.has_image and item.image.has_data: + if not item_name: + item_name = item.id + image_name = item_name + '.jpg' + save_image(osp.join(subset_dir, image_name), + item.image.data) + else: + log.warning("Item '%s' has no image" % item.id) + image_paths[item.id] = osp.join('data', + osp.basename(subset_dir), image_name) + + yolo_annotation = '' + for bbox in item.annotations: + if bbox.type is not AnnotationType.bbox: + continue + if bbox.label is None: + continue + + yolo_bb = _make_yolo_bbox((width, height), bbox.points) + yolo_bb = ' '.join('%.6f' % p for p in yolo_bb) + yolo_annotation += '%s %s\n' % (bbox.label, yolo_bb) + + annotation_path = osp.join(subset_dir, '%s.txt' % item_name) + with open(annotation_path, 'w') as f: + f.write(yolo_annotation) + + subset_list_name = '%s.txt' % subset_name + subset_lists[subset_name] = subset_list_name + with open(osp.join(save_dir, subset_list_name), 'w') as f: + f.writelines('%s\n' % s for s in image_paths.values()) + + with open(osp.join(save_dir, 'obj.data'), 'w') as f: + f.write('classes = %s\n' % len(label_ids)) + + for subset_name, subset_list_name in subset_lists.items(): + f.write('%s = %s\n' % (subset_name, + osp.join('data', subset_list_name))) + + f.write('names = %s\n' % osp.join('data', 'obj.names')) + f.write('backup = backup/\n') \ No newline at end of file diff --git a/datumaro/datumaro/plugins/yolo_format/extractor.py b/datumaro/datumaro/plugins/yolo_format/extractor.py new file mode 100644 index 00000000000..7840b26c5ca --- /dev/null +++ b/datumaro/datumaro/plugins/yolo_format/extractor.py @@ -0,0 +1,180 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from collections import OrderedDict +import os.path as osp +import re + +from datumaro.components.extractor import (SourceExtractor, Extractor, + DatasetItem, AnnotationType, Bbox, LabelCategories +) +from datumaro.util.image import Image + +from .format import YoloPath + + +class YoloExtractor(SourceExtractor): + class Subset(Extractor): + def __init__(self, name, parent): + super().__init__() + self._name = name + self._parent = parent + self.items = OrderedDict() + + def __iter__(self): + for item_id in self.items: + yield self._parent._get(item_id, self._name) + + def __len__(self): + return len(self.items) + + def categories(self): + return self._parent.categories() + + def __init__(self, config_path, image_info=None): + super().__init__() + + if not osp.isfile(config_path): + raise Exception("Can't read dataset descriptor file '%s'" % + config_path) + + rootpath = osp.dirname(config_path) + self._path = rootpath + + assert image_info is None or isinstance(image_info, (str, dict)) + if image_info is None: + image_info = osp.join(rootpath, YoloPath.IMAGE_META_FILE) + if not osp.isfile(image_info): + image_info = {} + if isinstance(image_info, str): + if not osp.isfile(image_info): + raise Exception("Can't read image meta file '%s'" % image_info) + with open(image_info) as f: + image_info = {} + for line in f: + image_name, h, w = line.strip().split() + image_info[image_name] = (int(h), int(w)) + self._image_info = image_info + + with open(config_path, 'r') as f: + config_lines = f.readlines() + + subsets = OrderedDict() + names_path = None + + for line in config_lines: + match = re.match(r'(\w+)\s*=\s*(.+)$', line) + if not match: + continue + + key = match.group(1) + value = match.group(2) + if key == 'names': + names_path = value + elif key in YoloPath.SUBSET_NAMES: + subsets[key] = value + else: + continue + + if not names_path: + raise Exception("Failed to parse labels path from '%s'" % \ + config_path) + + for subset_name, list_path in subsets.items(): + list_path = self._make_local_path(list_path) + if not osp.isfile(list_path): + raise Exception("Not found '%s' subset list file" % subset_name) + + subset = YoloExtractor.Subset(subset_name, self) + with open(list_path, 'r') as f: + subset.items = OrderedDict( + (osp.splitext(osp.basename(p))[0], p.strip()) for p in f) + + for item_id, image_path in subset.items.items(): + image_path = self._make_local_path(image_path) + if not osp.isfile(image_path) and item_id not in image_info: + raise Exception("Can't find image '%s'" % item_id) + + subsets[subset_name] = subset + + self._subsets = subsets + + self._categories = { + AnnotationType.label: + self._load_categories(self._make_local_path(names_path)) + } + + def _make_local_path(self, path): + default_base = osp.join('data', '') + if path.startswith(default_base): # default path + path = path[len(default_base) : ] + return osp.join(self._path, path) # relative or absolute path + + def _get(self, item_id, subset_name): + subset = self._subsets[subset_name] + item = subset.items[item_id] + + if isinstance(item, str): + image_path = self._make_local_path(item) + image_size = self._image_info.get(item_id) + image = Image(path=image_path, size=image_size) + h, w = image.size + + anno_path = osp.splitext(image_path)[0] + '.txt' + annotations = self._parse_annotations(anno_path, w, h) + + item = DatasetItem(id=item_id, subset=subset_name, + image=image, annotations=annotations) + subset.items[item_id] = item + + return item + + @staticmethod + def _parse_annotations(anno_path, image_width, image_height): + with open(anno_path, 'r') as f: + annotations = [] + for line in f: + label_id, xc, yc, w, h = line.strip().split() + label_id = int(label_id) + w = float(w) + h = float(h) + x = float(xc) - w * 0.5 + y = float(yc) - h * 0.5 + annotations.append(Bbox( + round(x * image_width, 1), round(y * image_height, 1), + round(w * image_width, 1), round(h * image_height, 1), + label=label_id + )) + return annotations + + @staticmethod + def _load_categories(names_path): + label_categories = LabelCategories() + + with open(names_path, 'r') as f: + for label in f: + label_categories.add(label.strip()) + + return label_categories + + def categories(self): + return self._categories + + def __iter__(self): + for subset in self._subsets.values(): + for item in subset: + yield item + + def __len__(self): + length = 0 + for subset in self._subsets.values(): + length += len(subset) + return length + + def subsets(self): + return list(self._subsets) + + def get_subset(self, name): + return self._subsets[name] \ No newline at end of file diff --git a/datumaro/datumaro/plugins/yolo_format/format.py b/datumaro/datumaro/plugins/yolo_format/format.py new file mode 100644 index 00000000000..c88c99d442d --- /dev/null +++ b/datumaro/datumaro/plugins/yolo_format/format.py @@ -0,0 +1,11 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + + +class YoloPath: + DEFAULT_SUBSET_NAME = 'train' + SUBSET_NAMES = ['train', 'valid'] + + IMAGE_META_FILE = 'images.meta' \ No newline at end of file diff --git a/datumaro/datumaro/plugins/yolo_format/importer.py b/datumaro/datumaro/plugins/yolo_format/importer.py new file mode 100644 index 00000000000..fcee669dc6b --- /dev/null +++ b/datumaro/datumaro/plugins/yolo_format/importer.py @@ -0,0 +1,38 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from glob import glob +import logging as log +import os.path as osp + +from datumaro.components.extractor import Importer + + +class YoloImporter(Importer): + def __call__(self, path, **extra_params): + from datumaro.components.project import Project # cyclic import + project = Project() + + if path.endswith('.data') and osp.isfile(path): + config_paths = [path] + else: + config_paths = glob(osp.join(path, '*.data')) + + if not osp.exists(path) or not config_paths: + raise Exception("Failed to find 'yolo' dataset at '%s'" % path) + + for config_path in config_paths: + log.info("Found a dataset at '%s'" % config_path) + + source_name = '%s_%s' % ( + osp.basename(osp.dirname(config_path)), + osp.splitext(osp.basename(config_path))[0]) + project.add_source(source_name, { + 'url': config_path, + 'format': 'yolo', + 'options': dict(extra_params), + }) + + return project \ No newline at end of file diff --git a/datumaro/datumaro/util/__init__.py b/datumaro/datumaro/util/__init__.py new file mode 100644 index 00000000000..87c800fe515 --- /dev/null +++ b/datumaro/datumaro/util/__init__.py @@ -0,0 +1,20 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import os + + +def find(iterable, pred=lambda x: True, default=None): + return next((x for x in iterable if pred(x)), default) + +def dir_items(path, ext, truncate_ext=False): + items = [] + for f in os.listdir(path): + ext_pos = f.rfind(ext) + if ext_pos != -1: + if truncate_ext: + f = f[:ext_pos] + items.append(f) + return items \ No newline at end of file diff --git a/datumaro/datumaro/util/annotation_tools.py b/datumaro/datumaro/util/annotation_tools.py new file mode 100644 index 00000000000..d0fb1f64ab4 --- /dev/null +++ b/datumaro/datumaro/util/annotation_tools.py @@ -0,0 +1,29 @@ + +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from itertools import groupby + + +def find_instances(instance_anns): + instance_anns = sorted(instance_anns, key=lambda a: a.group) + ann_groups = [] + for g_id, group in groupby(instance_anns, lambda a: a.group): + if not g_id: + ann_groups.extend(([a] for a in group)) + else: + ann_groups.append(list(group)) + + return ann_groups + +def find_group_leader(group): + return max(group, key=lambda x: x.get_area()) + +def compute_bbox(annotations): + boxes = [ann.get_bbox() for ann in annotations] + x0 = min((b[0] for b in boxes), default=0) + y0 = min((b[1] for b in boxes), default=0) + x1 = max((b[0] + b[2] for b in boxes), default=0) + y1 = max((b[1] + b[3] for b in boxes), default=0) + return [x0, y0, x1 - x0, y1 - y0] \ No newline at end of file diff --git a/datumaro/datumaro/util/command_targets.py b/datumaro/datumaro/util/command_targets.py new file mode 100644 index 00000000000..d8035a23da3 --- /dev/null +++ b/datumaro/datumaro/util/command_targets.py @@ -0,0 +1,113 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import argparse +from enum import Enum + +from datumaro.components.project import Project +from datumaro.util.image import load_image + + +TargetKinds = Enum('TargetKinds', + ['project', 'source', 'external_dataset', 'inference', 'image']) + +def is_project_name(value, project): + return value == project.config.project_name + +def is_project_path(value): + if value: + try: + Project.load(value) + return True + except Exception: + pass + return False + +def is_project(value, project=None): + if is_project_path(value): + return True + elif project is not None: + return is_project_name(value, project) + + return False + +def is_source(value, project=None): + if project is not None: + try: + project.get_source(value) + return True + except KeyError: + pass + + return False + +def is_external_source(value): + return False + +def is_inference_path(value): + return False + +def is_image_path(value): + try: + return load_image(value) is not None + except Exception: + return False + + +class Target: + def __init__(self, kind, test, is_default=False, name=None): + self.kind = kind + self.test = test + self.is_default = is_default + self.name = name + + def _get_fields(self): + return [self.kind, self.test, self.is_default, self.name] + + def __str__(self): + return self.name or str(self.kind) + + def __len__(self): + return len(self._get_fields()) + + def __iter__(self): + return iter(self._get_fields()) + +def ProjectTarget(kind=TargetKinds.project, test=None, + is_default=False, name='project name or path', + project=None): + if test is None: + test = lambda v: is_project(v, project=project) + return Target(kind, test, is_default, name) + +def SourceTarget(kind=TargetKinds.source, test=None, + is_default=False, name='source name', + project=None): + if test is None: + test = lambda v: is_source(v, project=project) + return Target(kind, test, is_default, name) + +def ExternalDatasetTarget(kind=TargetKinds.external_dataset, + test=is_external_source, + is_default=False, name='external dataset path'): + return Target(kind, test, is_default, name) + +def InferenceTarget(kind=TargetKinds.inference, test=is_inference_path, + is_default=False, name='inference path'): + return Target(kind, test, is_default, name) + +def ImageTarget(kind=TargetKinds.image, test=is_image_path, + is_default=False, name='image path'): + return Target(kind, test, is_default, name) + + +def target_selector(*targets): + def selector(value): + for (kind, test, is_default, _) in targets: + if (is_default and (value == '' or value is None)) or test(value): + return (kind, value) + raise argparse.ArgumentTypeError('Value should be one of: %s' \ + % (', '.join([str(t) for t in targets]))) + return selector diff --git a/datumaro/datumaro/util/image.py b/datumaro/datumaro/util/image.py new file mode 100644 index 00000000000..2d465f71a4c --- /dev/null +++ b/datumaro/datumaro/util/image.py @@ -0,0 +1,221 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +# pylint: disable=unused-import + +from io import BytesIO +import numpy as np +import os.path as osp + +from enum import Enum +_IMAGE_BACKENDS = Enum('_IMAGE_BACKENDS', ['cv2', 'PIL']) +_IMAGE_BACKEND = None +try: + import cv2 + _IMAGE_BACKEND = _IMAGE_BACKENDS.cv2 +except ImportError: + import PIL + _IMAGE_BACKEND = _IMAGE_BACKENDS.PIL + +from datumaro.util.image_cache import ImageCache as _ImageCache + + +def load_image(path): + """ + Reads an image in the HWC Grayscale/BGR(A) float [0; 255] format. + """ + + if _IMAGE_BACKEND == _IMAGE_BACKENDS.cv2: + import cv2 + image = cv2.imread(path, cv2.IMREAD_UNCHANGED) + image = image.astype(np.float32) + elif _IMAGE_BACKEND == _IMAGE_BACKENDS.PIL: + from PIL import Image + image = Image.open(path) + image = np.asarray(image, dtype=np.float32) + if len(image.shape) == 3 and image.shape[2] in {3, 4}: + image[:, :, :3] = image[:, :, 2::-1] # RGB to BGR + else: + raise NotImplementedError() + + assert len(image.shape) in {2, 3} + if len(image.shape) == 3: + assert image.shape[2] in {3, 4} + return image + +def save_image(path, image, params=None): + if _IMAGE_BACKEND == _IMAGE_BACKENDS.cv2: + import cv2 + ext = path[-4:] + if ext.upper() == '.JPG': + params = [ int(cv2.IMWRITE_JPEG_QUALITY), 75 ] + + image = image.astype(np.uint8) + cv2.imwrite(path, image, params=params) + elif _IMAGE_BACKEND == _IMAGE_BACKENDS.PIL: + from PIL import Image + + if not params: + params = {} + + image = image.astype(np.uint8) + if len(image.shape) == 3 and image.shape[2] in {3, 4}: + image[:, :, :3] = image[:, :, 2::-1] # BGR to RGB + image = Image.fromarray(image) + image.save(path, **params) + else: + raise NotImplementedError() + +def encode_image(image, ext, params=None): + if _IMAGE_BACKEND == _IMAGE_BACKENDS.cv2: + import cv2 + + if not ext.startswith('.'): + ext = '.' + ext + + if ext.upper() == '.JPG': + params = [ int(cv2.IMWRITE_JPEG_QUALITY), 75 ] + + image = image.astype(np.uint8) + success, result = cv2.imencode(ext, image, params=params) + if not success: + raise Exception("Failed to encode image to '%s' format" % (ext)) + return result.tobytes() + elif _IMAGE_BACKEND == _IMAGE_BACKENDS.PIL: + from PIL import Image + + if ext.startswith('.'): + ext = ext[1:] + + if not params: + params = {} + + image = image.astype(np.uint8) + if len(image.shape) == 3 and image.shape[2] in {3, 4}: + image[:, :, :3] = image[:, :, 2::-1] # BGR to RGB + image = Image.fromarray(image) + with BytesIO() as buffer: + image.save(buffer, format=ext, **params) + return buffer.getvalue() + else: + raise NotImplementedError() + +def decode_image(image_bytes): + if _IMAGE_BACKEND == _IMAGE_BACKENDS.cv2: + import cv2 + image = np.frombuffer(image_bytes, dtype=np.uint8) + image = cv2.imdecode(image, cv2.IMREAD_UNCHANGED) + image = image.astype(np.float32) + elif _IMAGE_BACKEND == _IMAGE_BACKENDS.PIL: + from PIL import Image + image = Image.open(BytesIO(image_bytes)) + image = np.asarray(image, dtype=np.float32) + if len(image.shape) == 3 and image.shape[2] in {3, 4}: + image[:, :, :3] = image[:, :, 2::-1] # RGB to BGR + else: + raise NotImplementedError() + + assert len(image.shape) in {2, 3} + if len(image.shape) == 3: + assert image.shape[2] in {3, 4} + return image + + +class lazy_image: + def __init__(self, path, loader=None, cache=None): + if loader is None: + loader = load_image + self.path = path + self.loader = loader + + # Cache: + # - False: do not cache + # - None: use the global cache + # - object: an object to be used as cache + assert cache in {None, False} or isinstance(cache, object) + self.cache = cache + + def __call__(self): + image = None + image_id = hash(self) # path is not necessary hashable or a file path + + cache = self._get_cache(self.cache) + if cache is not None: + image = cache.get(image_id) + + if image is None: + image = self.loader(self.path) + if cache is not None: + cache.push(image_id, image) + return image + + @staticmethod + def _get_cache(cache): + if cache is None: + cache = _ImageCache.get_instance() + elif cache == False: + return None + return cache + + def __hash__(self): + return hash((id(self), self.path, self.loader)) + +class Image: + def __init__(self, data=None, path=None, loader=None, cache=None, + size=None): + assert size is None or len(size) == 2 + if size is not None: + assert len(size) == 2 and 0 < size[0] and 0 < size[1], size + size = tuple(size) + self._size = size # (H, W) + + assert path is None or isinstance(path, str) + if path is None: + path = '' + self._path = path + + assert data is not None or path, "Image can not be empty" + if data is None and path: + if osp.isfile(path): + data = lazy_image(path, loader=loader, cache=cache) + self._data = data + + @property + def path(self): + return self._path + + @property + def filename(self): + return osp.basename(self._path) + + @property + def data(self): + if callable(self._data): + return self._data() + return self._data + + @property + def has_data(self): + return self._data is not None + + @property + def size(self): + if self._size is None: + data = self.data + if data is not None: + self._size = data.shape[:2] + return self._size + + def __eq__(self, other): + if isinstance(other, np.ndarray): + return self.has_data and np.array_equal(self.data, other) + + if not isinstance(other, __class__): + return False + return \ + (np.array_equal(self.size, other.size)) and \ + (self.has_data == other.has_data) and \ + (self.has_data and np.array_equal(self.data, other.data) or \ + not self.has_data) \ No newline at end of file diff --git a/datumaro/datumaro/util/image_cache.py b/datumaro/datumaro/util/image_cache.py new file mode 100644 index 00000000000..fd1ad0d7863 --- /dev/null +++ b/datumaro/datumaro/util/image_cache.py @@ -0,0 +1,38 @@ +from collections import OrderedDict + + +_instance = None + +DEFAULT_CAPACITY = 2 + +class ImageCache: + @staticmethod + def get_instance(): + global _instance + if _instance is None: + _instance = ImageCache() + return _instance + + def __init__(self, capacity=DEFAULT_CAPACITY): + self.capacity = int(capacity) + self.items = OrderedDict() + + def push(self, item_id, image): + if self.capacity <= len(self.items): + self.items.popitem(last=True) + self.items[item_id] = image + + def get(self, item_id): + default = object() + item = self.items.get(item_id, default) + if item is default: + return None + + self.items.move_to_end(item_id, last=False) # naive splay tree + return item + + def size(self): + return len(self.items) + + def clear(self): + self.items.clear() \ No newline at end of file diff --git a/datumaro/datumaro/util/mask_tools.py b/datumaro/datumaro/util/mask_tools.py new file mode 100644 index 00000000000..dea22c8ea5f --- /dev/null +++ b/datumaro/datumaro/util/mask_tools.py @@ -0,0 +1,284 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from itertools import groupby +import numpy as np + +from datumaro.util.image import lazy_image, load_image + + +def generate_colormap(length=256): + def get_bit(number, index): + return (number >> index) & 1 + + colormap = np.zeros((length, 3), dtype=int) + indices = np.arange(length, dtype=int) + + for j in range(7, -1, -1): + for c in range(3): + colormap[:, c] |= get_bit(indices, c) << j + indices >>= 3 + + return { + id: tuple(color) for id, color in enumerate(colormap) + } + +def invert_colormap(colormap): + return { + tuple(a): index for index, a in colormap.items() + } + +def check_is_mask(mask): + assert len(mask.shape) in {2, 3} + if len(mask.shape) == 3: + assert mask.shape[2] == 1 + +_default_colormap = generate_colormap() +_default_unpaint_colormap = invert_colormap(_default_colormap) + +def unpaint_mask(painted_mask, inverse_colormap=None): + # Covert color mask to index mask + + # mask: HWC BGR [0; 255] + # colormap: (R, G, B) -> index + assert len(painted_mask.shape) == 3 + if inverse_colormap is None: + inverse_colormap = _default_unpaint_colormap + + if callable(inverse_colormap): + map_fn = lambda a: inverse_colormap( + (a >> 16) & 255, (a >> 8) & 255, a & 255 + ) + else: + map_fn = lambda a: inverse_colormap[( + (a >> 16) & 255, (a >> 8) & 255, a & 255 + )] + + painted_mask = painted_mask.astype(int) + painted_mask = painted_mask[:, :, 0] + \ + (painted_mask[:, :, 1] << 8) + \ + (painted_mask[:, :, 2] << 16) + uvals, unpainted_mask = np.unique(painted_mask, return_inverse=True) + palette = np.array([map_fn(v) for v in uvals], dtype=np.float32) + unpainted_mask = palette[unpainted_mask].reshape(painted_mask.shape[:2]) + + return unpainted_mask + +def paint_mask(mask, colormap=None): + # Applies colormap to index mask + + # mask: HW(C) [0; max_index] mask + # colormap: index -> (R, G, B) + check_is_mask(mask) + + if colormap is None: + colormap = _default_colormap + if callable(colormap): + map_fn = colormap + else: + map_fn = lambda c: colormap.get(c, (-1, -1, -1)) + palette = np.array([map_fn(c)[::-1] for c in range(256)], dtype=np.float32) + + mask = mask.astype(np.uint8) + painted_mask = palette[mask].reshape((*mask.shape[:2], 3)) + return painted_mask + +def remap_mask(mask, map_fn): + # Changes mask elements from one colormap to another + + # mask: HW(C) [0; max_index] mask + check_is_mask(mask) + + return np.array([map_fn(c) for c in range(256)], dtype=np.uint8)[mask] + +def make_index_mask(binary_mask, index): + return np.choose(binary_mask, np.array([0, index], dtype=np.uint8)) + +def make_binary_mask(mask): + return np.nonzero(mask) + + +def load_mask(path, inverse_colormap=None): + mask = load_image(path) + mask = mask.astype(np.uint8) + if inverse_colormap is not None: + if len(mask.shape) == 3 and mask.shape[2] != 1: + mask = unpaint_mask(mask, inverse_colormap) + return mask + +def lazy_mask(path, inverse_colormap=None): + return lazy_image(path, lambda path: load_mask(path, inverse_colormap)) + +def mask_to_rle(binary_mask): + # walk in row-major order as COCO format specifies + bounded = binary_mask.ravel(order='F') + + # add borders to sequence + # find boundary positions for sequences and compute their lengths + difs = np.diff(bounded, prepend=[1 - bounded[0]], append=[1 - bounded[-1]]) + counts, = np.where(difs != 0) + + # start RLE encoding from 0 as COCO format specifies + if bounded[0] != 0: + counts = np.diff(counts, prepend=[0]) + else: + counts = np.diff(counts) + + return { + 'counts': counts, + 'size': list(binary_mask.shape) + } + +def mask_to_polygons(mask, tolerance=1.0, area_threshold=1): + """ + Convert an instance mask to polygons + + Args: + mask: a 2d binary mask + tolerance: maximum distance from original points of + a polygon to the approximated ones + area_threshold: minimal area of generated polygons + + Returns: + A list of polygons like [[x1,y1, x2,y2 ...], [...]] + """ + from pycocotools import mask as mask_utils + from skimage import measure + + polygons = [] + + # pad mask with 0 around borders + padded_mask = np.pad(mask, pad_width=1, mode='constant', constant_values=0) + contours = measure.find_contours(padded_mask, 0.5) + # Fix coordinates after padding + contours = np.subtract(contours, 1) + + for contour in contours: + if not np.array_equal(contour[0], contour[-1]): + contour = np.vstack((contour, contour[0])) # make polygon closed + + contour = measure.approximate_polygon(contour, tolerance) + if len(contour) <= 2: + continue + + contour = np.flip(contour, axis=1).flatten().clip(0) # [x0, y0, ...] + + # Check if the polygon is big enough + rle = mask_utils.frPyObjects([contour], mask.shape[0], mask.shape[1]) + area = sum(mask_utils.area(rle)) + if area_threshold <= area: + polygons.append(contour) + return polygons + +def crop_covered_segments(segments, width, height, + iou_threshold=0.0, ratio_tolerance=0.001, area_threshold=1, + return_masks=False): + """ + Find all segments occluded by others and crop them to the visible part only. + Input segments are expected to be sorted from background to foreground. + + Args: + segments: 1d list of segment RLEs (in COCO format) + width: width of the image + height: height of the image + iou_threshold: IoU threshold for objects to be counted as intersected + By default is set to 0 to process any intersected objects + ratio_tolerance: an IoU "handicap" value for a situation + when an object is (almost) fully covered by another one and we + don't want make a "hole" in the background object + area_threshold: minimal area of included segments + + Returns: + A list of input segments' parts (in the same order as input): + [ + [[x1,y1, x2,y2 ...], ...], # input segment #0 parts + mask1, # input segment #1 mask (if source segment is mask) + [], # when source segment is too small + ... + ] + """ + from pycocotools import mask as mask_utils + + segments = [[s] for s in segments] + input_rles = [mask_utils.frPyObjects(s, height, width) for s in segments] + + for i, rle_bottom in enumerate(input_rles): + area_bottom = sum(mask_utils.area(rle_bottom)) + if area_bottom < area_threshold: + segments[i] = [] if not return_masks else None + continue + + rles_top = [] + for j in range(i + 1, len(input_rles)): + rle_top = input_rles[j] + iou = sum(mask_utils.iou(rle_bottom, rle_top, [0, 0]))[0] + + if iou <= iou_threshold: + continue + + area_top = sum(mask_utils.area(rle_top)) + area_ratio = area_top / area_bottom + + # If a segment is fully inside another one, skip this segment + if abs(area_ratio - iou) < ratio_tolerance: + continue + + # Check if the bottom segment is fully covered by the top one. + # There is a mistake in the annotation, keep the background one + if abs(1 / area_ratio - iou) < ratio_tolerance: + rles_top = [] + break + + rles_top += rle_top + + if not rles_top and not isinstance(segments[i][0], dict) \ + and not return_masks: + continue + + rle_bottom = rle_bottom[0] + bottom_mask = mask_utils.decode(rle_bottom).astype(np.uint8) + + if rles_top: + rle_top = mask_utils.merge(rles_top) + top_mask = mask_utils.decode(rle_top).astype(np.uint8) + + bottom_mask -= top_mask + bottom_mask[bottom_mask != 1] = 0 + + if not return_masks and not isinstance(segments[i][0], dict): + segments[i] = mask_to_polygons(bottom_mask, + area_threshold=area_threshold) + else: + segments[i] = bottom_mask + + return segments + +def rles_to_mask(rles, width, height): + from pycocotools import mask as mask_utils + + rles = mask_utils.frPyObjects(rles, height, width) + rles = mask_utils.merge(rles) + mask = mask_utils.decode(rles) + return mask + +def find_mask_bbox(mask): + cols = np.any(mask, axis=0) + rows = np.any(mask, axis=1) + x0, x1 = np.where(cols)[0][[0, -1]] + y0, y1 = np.where(rows)[0][[0, -1]] + return [x0, y0, x1 - x0, y1 - y0] + +def merge_masks(masks): + """ + Merges masks into one, mask order is responsible for z order. + """ + if not masks: + return None + + merged_mask = masks[0] + for m in masks[1:]: + merged_mask = np.where(m != 0, m, merged_mask) + + return merged_mask \ No newline at end of file diff --git a/datumaro/datumaro/util/test_utils.py b/datumaro/datumaro/util/test_utils.py new file mode 100644 index 00000000000..8600b62199e --- /dev/null +++ b/datumaro/datumaro/util/test_utils.py @@ -0,0 +1,100 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import inspect +import os +import os.path as osp +import shutil +import tempfile + +from datumaro.components.extractor import AnnotationType +from datumaro.util import find + + +def current_function_name(depth=1): + return inspect.getouterframes(inspect.currentframe())[depth].function + +class FileRemover: + def __init__(self, path, is_dir=False, ignore_errors=False): + self.path = path + self.is_dir = is_dir + self.ignore_errors = ignore_errors + + def __enter__(self): + return self.path + + # pylint: disable=redefined-builtin + def __exit__(self, type=None, value=None, traceback=None): + if self.is_dir: + shutil.rmtree(self.path, ignore_errors=self.ignore_errors) + else: + os.remove(self.path) + # pylint: enable=redefined-builtin + +class TestDir(FileRemover): + def __init__(self, path=None, ignore_errors=False): + if path is None: + path = osp.abspath('temp_%s-' % current_function_name(2)) + path = tempfile.mkdtemp(dir=os.getcwd(), prefix=path) + else: + os.makedirs(path, exist_ok=ignore_errors) + + super().__init__(path, is_dir=True, ignore_errors=ignore_errors) + +def ann_to_str(ann): + return vars(ann) + +def item_to_str(item): + return '\n'.join( + [ + '%s' % vars(item) + ] + [ + 'ann[%s]: %s' % (i, ann_to_str(a)) + for i, a in enumerate(item.annotations) + ] + ) + +def compare_categories(test, expected, actual): + test.assertEqual( + sorted(expected, key=lambda t: t.value), + sorted(actual, key=lambda t: t.value) + ) + + if AnnotationType.label in expected: + test.assertEqual( + expected[AnnotationType.label].items, + actual[AnnotationType.label].items, + ) + if AnnotationType.mask in expected: + test.assertEqual( + expected[AnnotationType.mask].colormap, + actual[AnnotationType.mask].colormap, + ) + if AnnotationType.points in expected: + test.assertEqual( + expected[AnnotationType.points].items, + actual[AnnotationType.points].items, + ) + +def compare_datasets(test, expected, actual): + compare_categories(test, expected.categories(), actual.categories()) + + test.assertEqual(sorted(expected.subsets()), sorted(actual.subsets())) + test.assertEqual(len(expected), len(actual)) + for item_a in expected: + item_b = find(actual, lambda x: x.id == item_a.id and \ + x.subset == item_a.subset) + test.assertFalse(item_b is None, item_a.id) + test.assertEqual(len(item_a.annotations), len(item_b.annotations)) + for ann_a in item_a.annotations: + # We might find few corresponding items, so check them all + ann_b_matches = [x for x in item_b.annotations + if x.id == ann_a.id and \ + x.type == ann_a.type and x.group == ann_a.group] + test.assertFalse(len(ann_b_matches) == 0, 'ann id: %s' % ann_a.id) + + ann_b = find(ann_b_matches, lambda x: x == ann_a) + test.assertEqual(ann_a, ann_b, 'ann: %s' % ann_to_str(ann_a)) + item_b.annotations.remove(ann_b) # avoid repeats \ No newline at end of file diff --git a/datumaro/datumaro/util/tf_util.py b/datumaro/datumaro/util/tf_util.py new file mode 100644 index 00000000000..beaa4fc74e4 --- /dev/null +++ b/datumaro/datumaro/util/tf_util.py @@ -0,0 +1,41 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +def import_tf(): + import sys + + tf = sys.modules.get('tensorflow', None) + if tf is not None: + return tf + + # Reduce output noise, https://stackoverflow.com/questions/38073432/how-to-suppress-verbose-tensorflow-logging + import os + os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' + + import tensorflow as tf + + try: + tf.get_logger().setLevel('WARNING') + except AttributeError: + pass + try: + tf.compat.v1.logging.set_verbosity(tf.compat.v1.logging.WARN) + except AttributeError: + pass + + # Enable eager execution in early versions to unlock dataset operations + eager_enabled = False + try: + tf.compat.v1.enable_eager_execution() + eager_enabled = True + except AttributeError: + pass + try: + if not eager_enabled: + tf.enable_eager_execution() + except AttributeError: + pass + + return tf \ No newline at end of file diff --git a/datumaro/datumaro/version.py b/datumaro/datumaro/version.py new file mode 100644 index 00000000000..8589c063873 --- /dev/null +++ b/datumaro/datumaro/version.py @@ -0,0 +1 @@ +VERSION = '0.1.0' \ No newline at end of file diff --git a/datumaro/docs/cli_design.mm b/datumaro/docs/cli_design.mm new file mode 100644 index 00000000000..0ff17cb2994 --- /dev/null +++ b/datumaro/docs/cli_design.mm @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/datumaro/docs/design.md b/datumaro/docs/design.md new file mode 100644 index 00000000000..7d89e8ebe38 --- /dev/null +++ b/datumaro/docs/design.md @@ -0,0 +1,183 @@ +# Datumaro + + + +## Table of contents + +- [Concept](#concept) +- [RC 1 vision](#rc-1-vision) + +## Concept + +Datumaro is: +- a tool to build composite datasets and iterate over them +- a tool to create and maintain datasets + - Version control of annotations and images + - Publication (with removal of sensitive information) + - Editing + - Joining and splitting + - Exporting, format changing + - Image preprocessing +- a dataset storage +- a tool to debug datasets + - A network can be used to generate + informative data subsets (e.g. with false-positives) + to be analyzed further + +### Requirements + +- User interfaces + - a library + - a console tool with visualization means +- Targets: single datasets, composite datasets, single images / videos +- Built-in support for well-known annotation formats and datasets: + CVAT, COCO, PASCAL VOC, Cityscapes, ImageNet +- Extensibility with user-provided components +- Lightweightness - it should be easy to start working with Datumaro + - Minimal dependency on environment and configuration + - It should be easier to use Datumaro than writing own code + for computation of statistics or dataset manipulations + +### Functionality and ideas + +- Blur sensitive areas on dataset images +- Dataset annotation filters, relabelling etc. +- Dataset augmentation +- Calculation of statistics: + - Mean & std, custom stats +- "Edit" command to modify annotations +- Versioning (for images, annotations, subsets, sources etc., comparison) +- Documentation generation +- Provision of iterators for user code +- Dataset building (export in a specific format, indexation, statistics, documentation) +- Dataset exporting to other formats +- Dataset debugging (run inference, generate dataset slices, compute statistics) +- "Explainable AI" - highlight network attention areas ([paper](https://arxiv.org/abs/1901.04592)) + - Black-box approach + - Classification, Detection, Segmentation, Captioning + - White-box approach + +### Research topics + +- exploration of network prediction uncertainty (aka Bayessian approach) + Use case: explanation of network "quality", "stability", "certainty" +- adversarial attacks on networks +- dataset minification / reduction + Use case: removal of redundant information to reach the same network quality with lesser training time +- dataset expansion and filtration of additions + Use case: add only important data +- guidance for key frame selection for tracking ([paper](https://arxiv.org/abs/1903.11779)) + Use case: more effective annotation, better predictions + +## RC 1 vision + +In the first version Datumaro should be a project manager for CVAT. +It should only consume data from CVAT. The collected dataset +can be downloaded by user to be operated on with Datumaro CLI. + + +``` + User + | + v + +------------------+ + | CVAT | + +--------v---------+ +------------------+ +--------------+ + | Datumaro module | ----> | Datumaro project | <---> | Datumaro CLI | <--- User + +------------------+ +------------------+ +--------------+ +``` + + +### Interfaces + +- [x] Python API for user code + - [x] Installation as a package +- [x] A command-line tool for dataset manipulations + +### Features + +- Dataset format support (reading, writing) + - [x] Own format + - [x] CVAT + - [x] COCO + - [x] PASCAL VOC + - [x] YOLO + - [x] TF Detection API + - [ ] Cityscapes + - [ ] ImageNet + +- Dataset visualization (`show`) + - [ ] Ability to visualize a dataset + - [ ] with TensorBoard + +- Calculation of statistics for datasets + - [ ] Pixel mean, std + - [ ] Object counts (detection scenario) + - [ ] Image-Class distribution (classification scenario) + - [ ] Pixel-Class distribution (segmentation scenario) + - [ ] Image clusters + - [ ] Custom statistics + +- Dataset building + - [x] Composite dataset building + - [ ] Annotation remapping + - [ ] Subset splitting + - [x] Dataset filtering (`extract`) + - [x] Dataset merging (`merge`) + - [ ] Dataset item editing (`edit`) + +- Dataset comparison (`diff`) + - [x] Annotation-annotation comparison + - [x] Annotation-inference comparison + - [ ] Annotation quality estimation (for CVAT) + - Provide a simple method to check + annotation quality with a model and generate summary + +- Dataset and model debugging + - [x] Inference explanation (`explain`) + - [x] Black-box approach ([RISE paper](https://arxiv.org/abs/1806.07421)) + - [x] Ability to run a model on a dataset and read the results + +- CVAT-integration features + - [x] Task export + - [x] Datumaro project export + - [x] Dataset export + - [ ] Original raw data (images, a video file) can be downloaded (exported) + together with annotations or just have links + on CVAT server (in the future support S3, etc) + - [x] Be able to use local files instead of remote links + - [ ] Specify cache directory + - [x] Use case "annotate for model training" + - create a task + - annotate + - export the task + - convert to a training format + - train a DL model + - [x] Use case "annotate - reannotate problematic images - merge" + - [ ] Use case "annotate and estimate quality" + - create a task + - annotate + - estimate quality of annotations + +### Optional features + +- Dataset publishing + - [ ] Versioning (for annotations, subsets, sources, etc.) + - [ ] Blur sensitive areas on images + - [ ] Tracking of legal information + - [ ] Documentation generation + +- Dataset building + - [ ] Dataset minification / Extraction of the most representative subset + - Use case: generate low-precision calibration dataset + +- Dataset and model debugging + - [ ] Training visualization + - [ ] Inference explanation (`explain`) + - [ ] White-box approach + +### Properties + +- Lightweightness +- Modularity +- Extensibility diff --git a/datumaro/docs/images/cli_design.png b/datumaro/docs/images/cli_design.png new file mode 100644 index 0000000000000000000000000000000000000000..f83b1430ec53a28546cd82d9c2d4b2031474d01d GIT binary patch literal 35845 zcma%j1yod9|27ROB_JUw(#;^I3Uactzp~H!8Ro zd#~S-POqExSgRMR#=D%ZdG21;6keJxoE~2xu=$Xo_>c)a)J4HW5XM9>A_4vq#*B4= zJ`h%1dwlmVSOU{Hbu@Y5a`U6E_FyTYW9A$FRB#bO?_2`hw|)3Db%OfijBmpgQHVzK z$%uL%6tI0h_df4*A56hQ-E+Sz3K!lCRw-b(qUa_xnm-)qj1-*F6jth{LK1hroh&h4&RIEW(4X2KEPnfV_K(Zih+ zHD={I8PkZr+c>6KK?8HA_i@G z{ni*3QY?L7Q5irwqL044I z4&}~N9aZSoZF4wVWgx+}?#4ov${5`CC+O#=7KhdM&flVJsek+?B7$G9Yq6rI$`Lau zLfl-IDhkZcSeMo}R4d~UW!I0B2O2IuO?F_Dt%V=`A2Rryi$YO_Hr{0I z!7;AkTHtE<)a>5!-zj%~ZNAiSxk?Fc8n_uUK#W9%kjmO2#x~Mae zu;i$DU_-InM9P7&7)-+W1IU`zA>@GYIpf6hI-&%%j>xwq$9pu92!&pq9}^ZXX!5rk zF8hf4M%*ul0`_5;1SvMdTKv77nIIO|yi@0IT@CFFF(dGBs?R&w+a<8y7M1CkS92Mh z=RbJO*kjc`9yg&rkj_}WPQNv(g_nwJbNUwwBrE5h_nX*(D;QaOG6@e2Ulckwe{XdMbTzfLNr{Ph zd3lB)uo(H{kT#KYmXTls6cE9Fl0Wo-b$TrLJiNS59{NjCNGm8LNN3qrYvw9f06*pB zrZ1kRU>jnG5(0Z@_m#fiFMU%}Q!A^&N<($vKNLnqU%x8mk5OR@tlV#{dLM#TbCq%^ zu%tXZuA7=*knT6v)5kw;U4(^&t*w~@;Go+s_>?v=DXFxyG)(;Yf1Ux(Vq|!@rly7k z2iUMty6cLb;XKq(S7$;0{cvc0o(VUE_4h*z@;GTiLc+fu7Nt*Y0lyv)7|4p#Stt6& zv*uZE1lxkOZ`;I>{(Ia-&jz6eVj9SNpe9q%w{NXnZ>yMjA%cXeOb06$g2xG*=&85z zzJeL0dU+uQ0*IYs`0H~~{%nYBBum^ZrX1oRKa;`S5L}8XJq)2K#1v_qVDz8i?IC8L78A43*0z4g($dR6M&P4ftJnqj{%a z+uJz7Vvit~9348_;kQ04+%u$qBEbm5RmT3FM(U%-@2^`XNINI0tQQyDOF zC#vhYAAJ-O=&;_{OdArk6Mz!z8Ra-3=k5@E~pp=jMUNE&U8w-A4_ z6f~UrKrm~+6NOyLQO%5)p=Sa4bzV0q>o7l{Wzl@Hv;UrC&Xbq#=7|XSRDE-Ela7uK zS5JIdmZ3?3!Ru+i*2Ch7y<>d2i}1}%p~;+5wO>wWX^fevn@Vxt;5~z9PU`$LPLB-| zgb@k^G-n5gOftno)mQydf;M#RlM5<$U`*l?g+C6LFVx5HCznnEEAqy%H9q%(W?5K` z0VNtOG#i~{y`_IxMW%E-nz%_&;Fm64Qq&)?5;p_Ry6v(fj89RrC-|z}HUq;P;CUW0 z&N8Yb(~Zg*QRQGLZtVC>VzN!hf(btwVTGwIzRvu1fpP)`5XBL7=@h99&!uPEG|mkGVqZsfotjiFB)k7&dtn z?;oa17S~V=v!0Y}*sU-wyTrDKk(sS1(j&fo+~nBKjIUu$3PUlk{Iv#&A2KlC*kXY@ zC8!0<)%kAI(_7>+rw0a^NY;q$5L4)fA?|y0K`)Z&&m{`5Uw2k@*Kg){Kr3vugXG)# zWE^wB@t?ocPcHBBq{T-wNb1P#dz8`3X4TA5K+?aG37ozH zd3T*^RCF|G>U3oARC!`ctm3$6eQU59wM{<9V&1q3OCSG3&#EmKQHUxZhvoYj&t}%< zHJJw*s*D&vBbe#VLgz}3jjd>`LQHE?Vax} zk9vm9kzCfR(2az*D&+uzXRGyoPP7f?XX5-IwbS(N^4SRb_L5Uyfn$l)@{a_cA(jf4 zAWaK`QD>@f_Uuw|6c(5;YefSgUDK!6TlEBui(l_5&Lf^I$ zO#TjETD{RFuoB$sc6dK8sM(pVO3!;pdg=lpzfNYBmMFgTy3s%M|58?F&jZ?XMb_82 zmDeNY!8-~e*SoIXQ!hg@Gpy1Xd27a|`%9l|JO1Y4oj2&%p=?xp7H{K%a8>W}Pc+^s zDuR#Q*oo5JVzg~M%TWo_Yn>dSFyuSw90$f5Q>Sn@=c}iK3*WxNQ+zpMu5RDcZUB#m z^Q9LMwU@mEnFvSV^=gl_2Qaggx|Fo_?FU7{PUio*Oz$NM>siFHFs+jqHIm&Xi#~iO zT@FtEDHFaZ7lj6#(17B;mCUuazG|0jxknCmt|+ zu?y!JT?e}}@Rx9df;t|3oOS;>Z4jZwk#tF?G_O`JMi%63)OhqP#FwP24w<7W!KFf; zNH%e;m+2>p8f@4;FAawP_6MveLQ#c_|Fr1IM-}Sh1U2INQ zy1qZCl_2SZF8hu~5dc4=933mf**2(~WTn>#CK^c!MYWizqX_g|17%ZT&h$`Oo4F!F zn4aRpSueE)V^hVLcGuPdGppq0_#N@4D|c}3WkkY;pi9MbRupmk^~XjeYH#fw9E^;N zmhj-c>arUcnYa5wkV2pRele)5tnBCd`U)QWJp>B_TW)txkB979?f)PNK4hKB09K<1 zwPRgCsNe__&#?2c@Zp64klPs2fGXt} zjL9>RB%6(lK{7fz3Vg0xTMdYjF)VZY$&)8$<*pA^amWE&r-2BdNVx+iKulL#QzPji zFOUAJZ*9%E*A*(vVqNxp4eKc|9KTvvTT_E=l$5aBk?+r5-=2rKYkGrSY)!4wS z_fX0Myg#7R1t2Ip8QMkEF7B$Tc;ZO+?{@_V#|CF#LVUc6s;a@;`*-UIr7LzCg3PN+ zOWFAOJ)E8KTcB$3bMGbGSjO8Ptl-Rs1`>ncPoZRGWl^M8W*>sVVCUNN=8K0*qCPnf z-<)p*un6{3z;l%sK;@-c6%G#0TkGWfr-<aE3QrXN-boa63QJt=P$%-@-5BQP%_8avuJvP>nJw&sAbkG@{ zpYuNk_Az8q@5KB9K)J@c7%oMF!MP(F0LvrZP1SkpHavSnCV)2Br+h1>L?0TV{kU`e zj8UteO_WuiBf=dvQVbhu^Fg<$pz#paRW@!uK zr#auF1nkg&sD5B^ZN*2?_*wI~*LTb(bLag@FUqiyncp>VRoL%aec4l2TaN##E5OWU zCi6vB#(exY#i@hDLyDOL_OC?@u*~}!*W2rlaC9)A#vEBgGS|=n)gGm`G@7e~G+khj(JzDBbqMJ zG&cy?&N__PhGTqBG8dst#84wo;Y|tp8D)RiRA_q3yH9O9#bjT*^@SrX-dlcWeU4Hg zl~g94Cqb#8y}IpR&^sAzsqkCcj#p2@Uak06aNJRtmWqh~Qw$&cb*(=xGBsw>FQu9z zwZ`gTNT-J>`WCo{9GD<%rw>N>e)22qYD&*mGm(EHJeu7?_G9mjfOsK%pF6a+WU1p@ z+fQQ_l`P1552X=PlFB{m7cgW8*&<`?og9b4-hjI8VPJ~D*b%4OzP~_GuyvX07EF9q zNbVdyPiVjoKQc{Nu3yuOqA-X|h%+k27F`u%p5Dz5>};CGvyGRj=-h~eESZrN40fUO zAN;`8%}q&3NsbCwn8P?$2`2r8s8eu_p`J3PLipdm1=+@>xuml6$+(MO>`r(Pqsdr@ zdRR5Yes>r<6uLRf^A%K|IYjlNWAyj(Z)Ri6cq0>Zjlh)9v-F;U(Zztj><<-907rr< zq@j#DO>9CkO$%c9>k6E_s{H%#d1M1V7+{KM5)y&Z>*>KJz=^+M2_eYr3#$D<70aK@ z3zbU7L@DwKVDQw`6gVb^&A)G|B>yDk`_LCoK*J@@F{#+W0stQQP7Yfg-_0{ zE!yFH9iMT2YN=w%;qCnm!3ssUWs-Yy07g?lI&yfv4{mbpDlh`3P#y)RiX`zX@pUg8 zK&fn|LhXOqb%XZ$@l{%MMKBAePjESWrNTX@RxEBxIzYGSEB)0A1$&`J80&X49Ncjs zbjj zT|Rk>Il6k#Bxnkq>-?A%GJD6UuXy74Rw?m5_Dn|iMcwIl*{mFz`n)5pMM`v8xwXGW zJQVkqKzpBbDZuog#nKgVG}kT4|~D3HSd*0|P* zDv5pk&>t*MBpQkM;D+ZJ@XNiO?!v+!G?`A(oc;;(u~pg1bQ&5OGt<*Ao`&^5D0#4p zbcfsxQhIuN+S=NpqVOv^!7l(J5S=9ifUhcr(4X#r>zzB`Y4aR6WFSp&@9KqH^qDjT z7CAUBE{=lG^KAc_as9oomIXS7Ts{>3R0P&s) zAchn38oP0X%Tnd%=eK+U4g`!|L(~5LelRw5=IdPcI0Xd-YIc0wyR)9h8{Dur=})F* zW~N^K@`YS>6|ZMvScZg;kDWKSTY=N>+=kCtf$8oT%n({y+Oe@Q@eEd4TGKq{T!_aj zdtLOOb}B}u@jPkjm24yf3b3~9;(;W|kM3xO00s%4q@3Iv8l1kqKCEB_c#vGN+E0++ zO1o^IyZ}tw{1d=8{@lwJUkpLZDeD^`uGx&JpPef7Jgv93#`DE-Ied!Uh*%V zb^A1&X(r;(U=-ZYalWC^hbtMX2#4IBGOByMilLJ{BVGwLc6}o^bx#OSrEcxHJasLF z|7jUu7{Kdtzn6PRH#G0i16KvRl!UIcm(H%6pTn!qVQv(x@x|x7Zt*K)Fo;p~GwR>H z>%r9Tzvh*YeN(opD<6`ZDj4rBAjo*?aUU&z+m&r$P1^hkf{u~4q>M}taEpT`W0D&x zEAfmxv04a95IqU5+R(}n11^& z3wwayfn{%$*bshx8MJvk#SfOP(|2*@^0^W8cCc}nK zZ4bo7&oizy&o5yg?xZon6TSa+OXEE{|2gU%Kofbcg1b_qg(erQwK(gL2X6IJ!+T4? zo3;S30z4dRb89OmIvSwuA3uJ~9*4IBy{H85UKv6V=Dc80ZqfJIYrlmL3Uca5Pu7^Z zka@Y6H&v-V{%(@}- z)XZL9)DB}h?Of>QDL@YC2q*68=`lAqPg2LQ=o(Bmu7Qw4Y81Q^1k942Q4ikcQ^yYj zP^K6A$xqn&a>}@ zVp%zoJJzcYd>*W?t@Mmt)FG0dr(3jKt~icpiPZEE^p;}qM^MDw1~ith8Uy@9M`vFN@KhMS z69CPr#k#E9L0W(GF|IJa2;^qIk-XXIcV;8^?s0VglqYa~0(r(4PDZg=F1j$Ab6s8p zT1|N)gWwaSdLl!ro=;S>8J!lffNxq2x4c@_%Y(EBCVba${Z7;wCV>>|xP7Tvkf;#q zNPp3XI?@+ehZe`!5^jHr;Q-@*EAHwX^eB_E4v`LH?})%z#-8N$W>YR>rB^^P&1fEv zhK`QMZcSENx;xfQS3Y2kW8|grX_bEtAMAb-ug2;?6D_tp0kEW=7j|}ab@lf4_VuNR zlcIJF5}DL=ibT4McpX|$Z@qmb6$-JMrdbn2pTuW>tl#HnHc04#pX zfb)!K+sl{! z>IEF&{X7e(K0R+kcy&6GF{t#GJWy;7Fu;Qgdkjl1E?lF}1l-Rs*|hF4CyDGLB|6&n z@TXMGzKM}h)R9LwMw{pzvPB^Z19&U7$_4^iKn zP`U0QmN&!~xJ7_<0Gwp7*zoA6U;6!tQ0G(;AG;|8!7nrW<+yv*N2uINy0EBW)lqjm zCAHkyVNBEdxz~Un=c+N@K{grCp5{o5ciEwX!h7t6H7&R6YP8co(0H=oVQOGCk~S`9 zaa+y4ceF8LGLUL%VIeCg*EZ9gQ{`34hd`o}?+YzIe2F}-N-`o=pxF%G%t`vbF5z3? z=`_X|W;1zl5S&cvlwn_{X0AnW^+w?5X#1>n7 z^ytw66_0TMP)_JaI-7~aGl2*&KpHHmp?DJ5OSqGUv|=k_aeRbeql0eFX1>%yG?~&f zbNz(#hF;zsy>)fp;{ zE@SwZkF}#!87jtjS5YpzsE|Q6AooEusAqLr9j7f-yVct4XIHp{s94N$nz6;CKv7je zbj|C#SB><*+D4x(aq0OrdLPrO5WLcD@02@wsI%@&mQ2EHB_$bY>5|e?8&0DyCfH|Q z*0jp!E#@4EAc_ZM_eftrxi>xLQ{c(lq?TnI`Ne+6sfd!VgT}!#@OEn0-~~Vhq8*x; zi^nn6nfxcWP9^IbGI(e)Pt^KH72Q5q^B*2Edjqzu89xi%(WHfOYWNWu7==(oi zfBgaCbT6WNLNV^W4RdoK4B(S*HeCp!aLEsq0huZX9l`Ay`k5dLd4fOK%BMac1#s2d zAs4Y;p?GwmtlJ|hc@Zq)|5_gT(n>1B|UZlYnE9AFmE*i z(*n}GN1^m_H>_NSps!E8+`H6WsE|EDHFy_4fBrl=IyyW&1o)w;-1l0B!Iu}mB8#Q4 zwY99e0w#9q2RDVkaP(YNKa8goW%TOcygjt~~`wY|`-sKk^e7?JWVmO-jCe%>EFcN(}M89*r!pxwM7kx6kNzLJ| zsZ`G`+&aVfQMRDrgcN|8g5$L0E{rM2*zog-li50JUW+N!+ZA332!b0$k>)#BE|uev zq%`ft8Ug!-`FSgTR%YCg92feO8R!*D4osp_pMPIZvxM5#Jx;zeVwM%A=z*O!b8PwK zdTrL@`YbRzhyuPGyi=Qfj4!3!IEo&uTr_@P>D|$*+W4Y^zn>zMM&Su{tVM+;Xvf(JgwsuHCZ1YeOWrZr#Qt~RqgMqMFHoDzp@kHd3gK-$=Qp@SSxmf+8MOrN z&FlkMWD!r2O30=k0!}Z}R>yssu?8$%2{x^nUWQH9ZPlfdsMh@2UV`=a<<*7>=VZ z+XTzaP@lJ_Qax*O95C|yXW@ap^j=`=R%)0LxQuk{7=V4+hOZLHsSjAAC%|3>ylY=TYZXv){e0%vI+1 zwAIyJms^7YyaML^Mx)4l0iScU02Vq74x16gB58mQk!W%S^Aq(Re1~glrOZX8YV#G> zO}hE1eARGjb%TtjfTuy`YPV0q3TR6fNJBg>`bQ!_msd$iY}zn9j*XhC74_QwcO{`< z#>IXsXhbL?OM*iJ>A+byDk(zKt=Ex8vdpPqwK-(9h)S~jTOj7A+CwsxKa2Wp@=#Ig zfk?jT@9*CcK}tP!HC1ZD{N(Dr{Je0UybIE()ijT*U-Io09B042n0T+wH0H|@j7xtSIkw`@w#S@I#`nk2(je z{dsmVazlw9!1N%S>wH?buf#tHo|r!5d@oapJkTIXOTWzj*@!%aTV0wdsRw?hFf? zeQH8s+}d93RkDqu%EpxX^zK<$yn#Gg?mn)JF#JJ zD7+GetbP}BKvZju1V{fj<$#rQXKEC2E#2`Q!L3iS*|W2n`7d1A#WM~7LW0Mn`MJ?2 z{ws0Uwv@V#rlu^GPV^cU_^8H$;cw-P3p{*iIU_`Jjh>!9_~AtW^ZEHXlm+0}kiNVl zZhaKI6uET+6hY%Av4^!)tGe-hEn(OH3C9 z&O?AKd)Qi;nVSzJbKC9Ca;NdB+;i$$;aufmQnD3;7o40q+1Z}g#}i>;VFDYAF^Ld2 z9j(W`k6ayF!prhH<}aS8OInMkC9mRw|}m5jcB1@i7`mVI~*+9B(c#iZg!WxEgbr1Uy)XMv9< zh6Sijs#sRb} zJrk3>loYtWE(}A6HO?ey2j*Dz*tg^SdudsD#Fq1n8k!-0}J?>m&QG=C-XlB z+9il=AG`63$J|q*4GYKAzh(9cMWuHT8yt52AV}T*=HXjNRLn%*93E3n?Bf4Hazfqw zH4=c*9XQPaD7b}u{OIoK8V&fM5C|I^Th4)ZW9n9*L9>)Ce$9i@+0%<&rvu2C#!DJit5?u`@EcEU?hnODQ9edf(oH_Y2yc5ah%u+fDkOF+f{ zIb7-vhm+u=0kht0F6WA#EOYew;%Y$KhzhaMEbUFz`8Sj_&WjH#6HD-KBjmeTnv#3Sm3*D zqWz{c)~>$viKw3z(b;Ipjwj`fo?f0k9~DKoG3DTle`RWF2jJFqgAs8zd01_{;Xdb`CT zZLy30Cuy+%QE=zl4Uogb!&CgA?5@PmmY|AB;j;1J($Lb<($*I6ClN@DB-U0F0$Fni z{t*-0IqegSCz&E5Xk9^kX(eF0Q!HcZ^j-`I%#Pbyo*$}WXI6v^3)=TL|(zGZe@wm&X;$*#D3ZMoU>pT_ zgn*Y3ET#{VSI-Svb8I6~H`;#?w4~P#M&6@+sK7sRoH4eaZ zc6D{ND_wPEBHwR4-P|;rHGj1Ru3Yu6cNy|_gYzNNW`MM{wV|P*g_yWLB_sr_iOsDJ zEB>vW9RQN@zI{t}myfu!WSA{N#aEWA&-$eSRRaJLzb_{~Dgcx&E-ru({>cgQKjMl( z10Mtd!3YwBoSj_$!hfbNZ+O@qMu>h4Fp94JeyT)uB_+{#I5Deri(}Dof|Qh$hyEfI zSXntaGFh*yHRZFj%gf{5ei?daFCQtlpA-O`3}_E2nmKQ5^CdgK*z?ayehaf!XU6?y6V+>Fk%IX#UB$8zZGc&rVH?vC6q=HVo z3t+x%jTgM+5~cW%`h#=y*&VV!iUR5qVdAAICcl6TM~-|7=xJD0DU>QS&_aOo4Ed?W z-fw7ZoXYDIE(YVI0vxK9)2JaYPns!#Ssk1pNde&Ne||wtO-;#7FbRVUH>60bHUOg$ zYQ?xKt?}~m0$vi{4(elYQC=R9qiari0-WuEFu3Tc(NKS+ELE+?CC9u{rBI`w_A!}9 z-r)$Eo@cVZ3B-rJm(YpgZL6MG-J3N#TU%TR;ybamClA|z0l*zo@?7_edeIZ&G7#R| zs<0_?V_80RLMv3*&ZXwN-o##`XUFiBZFvrd2aLwXw{6Bjg)@Om15wJUt>vYsH)kDC z1m>%QL%AdcKqXp;Y^Vsxm!nhA_AArd4!7EoKjUUbD6v^A$r5z)0e`MWqk2|>icpA4 zz_$TJa@id^K2n)B9 zg?mi~z(+gI=zsk)$Ph4wr37DLGbR2f_?F3kyRBp$(hZRN1)FYdRA5?b&q781mPH-% zui(VOenfMlwF{G2(=mpQ7nx4WX7&i?pTUJvKLMum$^St!_{UR+Sj3&$C3J}sh92WR z)8FcLP*xMyOM1t{KY`KV7vn);e|apWg$X!N0QuIyOQIT?djMuU;5GXCBUvFYmgzllkK zUg&%^{(lF7Kms}l0Te&ZA74Xe_Yh)))O!eu6@lOT@hk2v%f^r+h&XAiiufPf=_Go^AFKJK zT72f@iXZXYeKmwmH3GqsUHQU*BuxaMjNgxw#7_eO)~Fwf22hD3PfOq3-Ret&q?Ge@n45%O_JWs`pP@0?)MI?=1+HJ+wTbXp`+m zgl(TlP-e1DT?1%Rq*D*0fn;6PgOt0b1q-_)+$Qdtx@MpOWBepuCHw?NU~agOys50kCT@|MKa>AbQ~ZdE5VpcNl&#G;LNpCX zvr9zsr5ZxGMAwF#hSVQbTFoVHdmw8ke(byRE8ZtxbX_*lvP9@iHH_OnJAl%RBlAa- zn<)(cjy71<{#;+-6jx_b^yFqL4SxpFyT2obZLj~HQ{azsX_uoSQs};`7*$v+{+XUQ zt;I}ADy#U@&e)bmnd-I(D6VDOu$dBR9gkaoM2zm9=_Xmu%AKt9AbpGaiMeIp2{3Zf;?4oj=H+G0Q)EAp z^c`+*fOg05bS`D(;zJ<-07V9f*ck44%ib>_F*w8Y@&#BH;0A#5!^ger*s_juaX%wY z86JM^FQRw(bz}pW8`dE=T0RXEUq_fiaC!!nb9k6aZs?&H87lcwdEI9@ zfYSJ42N*wM5L{99$oP2i*Czm_z`~AsK-vk&YM|br5UpI-8Y-`rHyD&GG5+5Ic?JGkHr3%PK+II=YWPId@4<7=G>#iLl$f?Ku z)vKu2xvjxAi{k|vKqLVl8T$5GBLPZEU|p@Wat%&SO5zILKOJIp^TQVzNI-GX_uD%< z0^I5T$ie(Uzdj6UNU=A2GI zcyu~?PNLw+g;&Y(B9ailhGTaYL%~=<&P0?K5HGcsS5W>Cv_J*bh{%$8uA8H@?k{Kw z?==|P{Em<0o_+4K{gc&zLh5Yl>Dq)!QOP`XzI}Xz&ZOC&=;Yu$2mljD%Up(m7o6!)j#U=Nw!PouF@9}`FhO#{)NcSG!P~Z`<>>0q#6ZM%)zI;R|B}i zpX^odQi>{Q_w5cnLH98T%tj+9r4CsPcBuqbTTHU2+0>PY{GJaiRcan941$8nnrS7W zHY^ucZlB=2*e?F135^uSFW;Vn{6={yoq?nSkOVJIH^&QyliWdj+V!RB*q(Db-vLG@ zxo;9f@tw6D0H>a9P!kYW=?x$2UObRw0g4s0Jy$+*dljsPkUd%8b87p^ggP@hIREUC zOR+Y2=Yk|6F+g`kkCSRzJkI`~Xal(QRC#TR6S!_H7?R)c8NfF&vQr`O$w&;k2u(omxL?f%x<5<9KLJBKiGw^0}I4!>x?;alVFd;S~oNfyUWtx2Ij-<#B-f+|al6&@h z9Yy$1j};f#(46(>v8mk5T6l+fEnj0=)ViyhzaS_N^WPIvJUn~y4kYl6{?$hAV}k)8 zOaPZu4h`l@{Mr?AudYe^axr$7srxj#_SzxjpK+jZu@+u=qzxz+&9--UF{1_AFSXJGyhaV`l4qTj^LQP%9lDBOcYu zYG`S|68Y#K>R6OxfTJt~jX>+O+@RvMzk+te!^iIvZj44!nY{+(?P~hgI)gnE`CYqOLWxsadOrL9LD6G%+~+wG{?aVE($Q1kX-zy&9C89Ij(0AdcR^MDfu zq@E2G@CdVY!q8J!mkb!7h*PMZ48rJkx3eo1BmZG41bv+}_8chYYgAe-y#S5`eVI>c z^&pJ>4qlQ#X#kaG@h)ljwlMkdhupkafD> zJfy+-xV)^N3~v|<=)p&G1K|u5;8RC%a^kgxHG6XN`=dgjO}b^G&h1MkAs#tO35sDu zS=kYgGT=gPNT5{kPgj5~DTTe^@B=j}mYjp*T+BZ_9ep8fAhgd2;Qm=d?<0Q^cNX;S zy14(yy_bzD8S58IpY{Q{_v_;HC0!za@3FF_xTd``*?_gt(YV+~n(PXT_1Qla8|WFV z!(1;cE@4?oE90vl?%MB10H1BHn_4Mu1N`)R?90O>laTIQh59^+J(WD zH@ntww|k8KbY9K{9oYtd1j+05@)ywGtK<@7(r8`InLild#%QU~`&aXHJIZ(VjFxvT zi#7t=43_2Lwa_N$xo*BY*LHt0f%5O!a-#yGf40gQB>%ASdSg^fCRtI(`Sn(5aeaoo zhMZPJBo94(2N(n718FVebJ zF;va(ZK9la7gCOm=xjINwVzO@Uth;t5s)WbLeJ`ZE?fiW(3&1Ds~Oj~WBT0W`vTuQ zEKZcyfE$Xj*wyMl<$c>)NYk=Xo1p!nkq!tnwN4l{eE7rhu5qrPYpFz>TP zq0WH}uQQazO+Q2XUXqFn8=9Rt25-y$=f#$1|ItC*!S)L(3O{2!Re}o$u(2)923eC9 ztVgV;(@$zGE~HD&d*tB(6xli1;{UObzcR%)v=D=-sLw&{!|AF;v5LN1i^e?V#U%H& z3SpnsSK@hG&tl^Y^rR|{1o-*u^Yb53>&A~5u5H^5s~vuOP3z4`m8`lu^U$YzDB}^e z%ijvP8R&l&a918X{3{7}0o6Vyb%_N8#Nvd%>$Is~j`D_`J|u8Cg43Z*+LG+1*FcZ9 zx7=FT#)t1(u0chWVbl0rCGoo>fL&X2E7O9WPaQ=tK>5|+7 z?;X|H6!@0sE$}Jb^fS*ELq7~c&`-5(Bp+lGtc z`lrX@TLp0^soN9jPoy1!_B-oB6l>l8zgF-d<{rLRaCJ9UJecPcuhjvik(QR0kiZ$C zW(#1V)KoB~S@}~P1)vpw*&JleFE8^#gDU0;F7C&+Wgu8KvorRAcln8H8a%Ne$LLU4SlIv6->(elKL`4)>g$P14FP~MY&T5(^>D}`S}Yr=?AAY{ zvjPsQTnA{c($Wz}=aZ$Dz{~&|o^yJjGQJBmrvU|4z-z6sS!ow>Ml9Hf2!*^{BK>*c zpMqW8o-;;e|OwfiIrhlCb`O2+5(XJ|c}vRW+!QgrR4 zGyTbYZ)EUnq;N(5_Uw^f&W*wsa9u7yA}Ab~7#Z7C@6eZx86u_y7_UL{G43h9YSar- z)i1bMSQObmveQ%gyVU&@c#d4?WS08rtLNMtX@0x*w{x-nh}u`v2Yz2t%f{n==&br$ z8QtgEw&h4f1rgigq-T&rvxUdzNIT{Uq_0a}?5-UPB85F~4-ZIes*;||-2RrI-vhYc zJyf$g0Ii55h+w-Mzh|TgUg-Gmw7+hMGI0w*rZi zojuCP?=6Gp{^E~h*CIx`yveI`%H4S%;^9k!jk5+KmT3*t_Eo>CIF}1vQJ~qiy581_ z$@)}v_N<8OfEzQR1u`fU(inY*~Thr4Ph`sm88PE^6(;oV7h`%&SeK^VKthgWjB^Q2GO$BH)k$yJB{> z@ve2$QxcNGuV2Z|w`Mip#VgWU?T+J1gnC5y+i)8&kGtC@;W?FlYmo$p=G72>zdV`b z6*ae85-T+ud;#J+n;Ccm#{vE`a7E;1Li4Xw)<(L&ez9}EiaL~E?E#Cz>C1uG#`;ip zdwY9X*^B7effJ$IYA6oz1-K|rRqlfjC&+^;uKIZ?qy3QXgHuM6QMa{2Wc3`hS>9H0p$m^i$$xCy+D&s zLph4D`|CC9xf)8W;BQWq1I$musCaobfCAoHb#7r{gUt%kZU#wr^b=3rvqUF_*u_WK z#%gOO2E^Xxj}X9GVCpHhSK}W(d_dG!b84azy)U&_3$SLh(Kuc~niWZuYc>V7G z=5$>aOJaZaFdqM_DH60a8N6k@EdZKanFVrcY*Y|2Wy$3#7RcJg#YJ**avK{dPl|l5 zGFAeREQ0iOL@o%Cxc$EFmufWl`{J$;lY2AG;Qpfm;le<71G)$0;fvKI z*U5y2{A>uGoC059fI%@eA37v~7vs~|i2{NMMo<{ZDk$i5Do9g51>!42bfg0Al42<| zuE0D5X8Yq~ApQg>nz?GbjthGaIMbDvdxDg>W-k)Q$5oc`Y^2GffRxlm2r2(qQpc!c zVTnuk{@HGp>SED~lPSZq$7J_qdNtVneKw27vI68`fM#U*Tg(vAJkE&R9nG#Q znmdM9fQ%&o|J89KwfHJMqsKr&YS;qi2U_WQ01QWH{f8h+lo=cJJF-c_p-Y(Xi_@~r zK7k3c7Cd!jlmk1f_HOC9cm9cjs>R%Y8!!6F<|@aqU9!_&>zlX#dY;6O?m>9>F!i2*lME!4odVI5L5#q5Ug`00>Ka(;W4MH-BGdZi6o=^{E0Jm=sQr_V>fI}lga z5hnwJ{`O@+mdM2$EWjnGwg);;W+s;16z~Z*Q9=IvMjuTLt|i@JDU#NN{7c$A9S4Ji z>)ES&2gC?u=9PcO*Qs*LBLtpruq$RaIEv~c2^4sY00yT?M0dM6AI$M}yL))>5Amfd zejR#;J1`3b*<~SzqJaczFu6NKU3Jyg$omC>PXacX-49)rgS`J=Wp5o9<=S=)kAZ@y z2r3}bNJ+P}bT^U`N+TUZcZo<#3rLGdx5NsV*a0aMbEJJN4dB<|m+L^hE*a+=&XbttTf4Qt>pYCp2+H??o}T;@isEytW^ z85y5<7gn(AqRl*s1*-yIp{$F^2#ycwLd?ZrPyvGjv&8@C5eEy(a=t0RF5J<>DbRym zxB9t`*z0%~d)`9|ri|$Y0Jz?d3Q7n>d~9s%APzhU@660LrX_tYCzo^JuB&f8N{;Fmro*d%XFBcv-46#TU23k5P6)0DTZ6$DVp@IaJ%$ z8qi(SMMPv~Vev^iE5tq!1etc_yD=zdwS*7dU{#Nfjj>%?CLWJF+hIlovFg2gMH$zH zxAhD=i<-Mml-%WHHw~+10!aLZhehF9RMFTbEpJ)&cz`icd-_eU@7RVPSPKnYZpoA0|P#0W}4jyU}YpJtYFB*&!7Cg8&GZlXLsb8IUq@6g8>Y`w^zR8 z;RjLjg+$folpEX&W?(7^&~f4!1Kh>CsSd!>ti;FoF+7QptSj6i>8IO41F5nMS0t2MF5$z5c!P7xI@ zF0Shd1O>?76e2XzEgXx^w0ScdbGbV&6195|3u{poB?f3$HB2Fm>k=a$5au;EXT}Gy zfPjF&CIx)xf#Eu<0+^e5&cLAaA~upk!qd~!$cR!N2gPg1s>6Z1f+7v5_OwZ3U#Y4} zKEtV{$3*>@@k@A5hPo}#)v3LD)#FCY6x{)oe&fV_K`6+lJ<}-V<)>HlxOlj@y<_(# zTA~}9Vmd)>lOWrAnK+q{36z3gzK{a4KT>eZYL8}meB;6OKQNG++9x|7 zShc%3>(y_?fSaFuDfo(7ad60Z%b>x@%Fgaw-k;$07XWoKC{)FB^YbZl`Q$tJ?sR`% zOTP&I;Fl=zSL+*Ypo-3| zuC6XBia2d45Pbz^V>-MkQy^??G1l*qHv(Jw#qvj?Y-I&UF1Yab4 z>vUkYUyY48XyRC zHJ8$~uw?$j_n*;xD=as>y&H}d<`Ve~WzbiCsZ)c;c-Nj^!r}(E^G_-SYW_m_R@SL1 z<%YN3!Za)hoaIMbbOZ4R7SWt$p{n81DYc!B)abt5k=;}MrtG5dU}0)X%E%?qFKW)k zj?T`??|Ysy*tpiE43_~EwW4BlWJD@!5CD}?HK{y~KqHfrmBsBOPq4DI^y|B9!oQ8N zuq$1jX=UL<5eihI=K-TZ?IrM83A&^?%FhH@t`ZRrx8yRz_s8Lx9oP`%fX+TwQWLTUmWke z`T3gNfNNYIat4Kc}F8G3dS=)sU~pk9ze7Wv4c_A`+>Q1T5w;c;uwn&5pz!YJ%z$d`8ah z$Afgc>dP7zI8jtMRj(O?nv3I`OGj_t=no^h$6C8pltp9K2j_V6`U(&FxthP5CfXu7 zdJw&iNJZU3xJv_C!uhlO_buX?dKpGs++I87ibVlWyQl~!d)N&f!tf~Vqe)4j2 zC(!Uq?P2~V6@dZ@pF|C4pKEPWey%K|&_AAV!@N|w1QMf6yC>(Bhp#tHARkvOfCj&& z8~)e$f~Meu@-KoSk@@zxh13olvBYU1jfNPX1`v$Zee&t!cI6Vzt~KxU@|7Xj?v! z4@%OKhq7NUsip4{pALuJ&G!!stlHgvH7!R?C*4$({B?#D+2Q9t zKChx)a;slSag(ohlAXNK-qj`2+#NBBDWR)#Y~%mY?M+W*_#@0zl?106-c%~A3xk7S zvq=0Bb;M9USu4l*jGEWw%gvLo%^0*3dIO$8Z??;>4zaHH!3`dc2;z4iVM}<0eAt+- z6_w1j9E5(L+*ZPG%*>`lh_q!b^(Zf{%3e3D-Z3thQi_Xv+6 zj^1mb#Us_DO^%ogk7%~aTuS5QgUPWG*Tek?37)!glVgjIM9Enfvc4s6rkjtmV71U( z|F}4t1>yi~S#vGc9q8OB;S|nO+8FUxD{d%~lgYBj%bFm+rPJhCAprwhvC;o}0NW2e zRuY@W>mOQqiN4Fac+9X?yKiNS%Ej%7LLg~mh~Y*rZgEY`gb9n@_VzD!DW5;npYV9v z+S=;q5KS82suga}aV<@@<)#-@cYnZ*ysP_D;zPh~E3hRjeP%@^LAN9N$Ih;Re3vPIy~*Gb*A)XQprIi@_%%WzWH8fD zF-h{#+THCq4IvB(D>c6vHEG^CI!Aa^)rUaqF9Iy^R3Sy2IBS;1@t^1XE1em>xz z`x3pKk7E^(?us;z^xXH*UOCn2>Zz(4R!-$s=a9Ix?QCrUb1i@=;-8+ zm?QT0ht12Svy@$JuX~@}zTAeOD<|X_8X7La;krK@01{A;lM|FO*Z?9@aU*!f2)~h= zrhV}}>Na&-bKsfeDI3ZjFa>D5Sx%2Wb6I6&Qjc*S9-H=~C*X+QcesPXL`bM{xhpO= zJcsiFj7O+KIy*am;x+?Y$ZRcN+=VZv0MvU0#w0x0wx0Eb!~tbUMr>VrdXpjRo9}24 z2q_|3QH+VeFIlXwx9J^d>geeoee9F}*&OJ2AOJ3e$wws}9l0I+!yXS051S5fA$%p_ zxoSE;9M;Z}2mtEY@g#53^W}{IMDsZm;}ypwgGP7H(9~2?Ow21qMepPL;3GH7yG!%N zm6dX=Qa<21W_|aki;-rz2Rs15L`E51(Tl0q z^ksklgBbVXHpF&uWd#^n#GAJ_bHgWQLc29p z*p}m3&7!)0P(b0N4RfI%9&Br~fI7oSM$Jn8o6{8??|klD`w|@&^%Yaxd$FW44n}FJ z&<|6IndSZ7kqYQia48A|8CmMb_r{IGIn+PfL?z?C2YYcyA@cMSa&!WN?|9lf*mo;; z494_bv9z`4)tDN!+!7oQUxjVq%}&&EN(&IHyqJUB;iJSf=)GJ~>sM~Abv{V))OGfD zK^A)o{kWshhV}`o%KSzVadnK66GZpu&?$XuuyD`Px)K8Jglz~ElPuO2i2pLibndujQ+Jt9&PC_4=6^GMT zJlrpyT%Svp?YrQJud_05qH&5r?&TiA5ont+Ln!9YZ(lKC7 z0F9|xRii$-VFtmZ*w=yr-3Hdaf@^$F_mb8>!+R!seYz1Ri&xW6umt7b2adfeZprQi84CjET%NIo0@5>@G7+@s8{OptLG&`oI;^xt4DBCAou>9z17GX zY&ju^2?5BGQcbdkX6~Mh@?=oc8D!d_Ar+vc^Zdf*=Ph`Ao>Qfr!JCmTHzMN(mf9ITrtzedLA56n1gS$aKEa=XSGbDekjI8mkb84L7FX7s?n~)nOxBZ zJs}k$w@9lDQ>1xcoj#yr7dd|&M5 zL_zg{S9meOJC&5TH}uFehdF)cY=0*?yuQA^94i1Hl!i3eX!nB5o2C3jlAJLvM3?uB z-#qses(`QQ%2#|pm4ocv^l(ZSc`e>J6P018pah>F$7hwj!jco?AKYZ`#>!BYNl_PM zj%g3``a52Uzlyn{rn*-9Al(lku`;oOdkgh_^`$qzYgBTyRs0l}&WbG74%4aUPbfq9 zDWRQtbKPP3EM~nm!T;1if|`~u`iq54&##J*xHNUdT#_0C2Bscz^OBZr>q4EAIT^Rt z=?ANI_r%raDt%~>-i!3&ZlG3>9^fqapCYd31c$(5jj2%Qd1F66MCci!uCDG*fbTj_ zj073n3Lw3dQ20koFM{BM=u{MdUn;5K1 zl{s=JuAmNiW+(-WX$}t$OQ*y77LTon^BFW81Sv}gCu{ua8~JbQlYuH~0aQ_6zkmP! z?He$y+-!nwl5=ktQy=^Ev6;BtElmhpQHPiE&zx7~_H>ZX$pz{yEasdu%ox^OW(&nSQ)Sg7vq05xC zy?zPgz?p!a=jMKW9v6d87vEONSJmh46xSdS|BCKX?b6RLY(d3VT)GQJNUQlLz9yv> z@PZ0NmDEh)&5`}i3bvLfH2JDUL{E>zas!M6<*Ug|>Ex>oK~+(O=M$efCkOx{9eIc@ zG;;)3VdwjpyqMv3rw3+gnYm0%w6qCUi3eJ)k7N8NzB90eYvhk=*0Yd2AKyAkNg+NZ z0>&_)+#(1KJVGA0)-~#zT3d4^^{6qeQl5yQ{Vu4?7uFPj#ufX*cExyJiMmZbJLA4} z>rR-Hg9DhR!F}|oKhu532~V|O;7%Rc^mKE;mMs8GOdSEnwmL@YKZ%a2M16$$-$_eL zgIWVjKHJ*vmA@OCHaP1BSXwdm`L^21jc@Ft4d5bG;npaPLTE0n3`5C=u;2jGfo{HF#SDlAD~DH%aRi;5Bu4 zd68ks27OknQzIiSRw^|`Y0%G;4(q>i8&uAS2NAcy4R{HsPWv`GKJI*au;$})4T?*A zFVm{Nt}Z$B!HJzSCPeE3*pTI`g%nbN+A@F?vFYcsz6d(HYE5T$|G-jg;OS~o9bsuI zlWdG91qME!Nb+qPwrJjSC0- ze0!HWFhDalHU_}C2uIu>-WlG2BOLfXx`34uJAWL2Qt0+q1f z)p@B_{`UI%0w+lAyCQb)h8k+d!Q4ak#mocZ>j48S2h`?7eO~m7#-?4Z+ zAPvINE+3S0fw`-Kl+-+vkGhglBg082LqLu5FI=QvgWYoD`8R{FOB-B`f)ArCp*Jm0 zA>J)C2rx1RFoDf>cPNzevE%XhqRS=1EI_!?O0h=rSK$`yg$}Kur!ht+`Ws@yP+|x0 zAv7AqcU$$m5g^qbZYrSyjh~81U%lQ+%J`D;9{VlcqQ+NVk?rm?i%VO2)biasK`o^U z=9~XnUWQjvrEl%z@{S2UV$`YI^MdY$!BsI5gurs*oLNX6pgvt4$yDY2q#-8IZP@xlYKjNoZmpV-G&1fVz4 zg->uFeIEy`JP=rv%%4}TRJYjUVu8kFdegsU0lt;6Pr2qK9v-MFqy8v#LfquYu4E(W z>BarH{2D55U&5jiTcWi{!=&t%1^v^|a+6UckWtzcvseDBO5CKBxSAAaGQ1V4opO!KuedDb zJ$=Ihhp9UZNJb|-=EfmIk7^8jIhJ#FGDgo2JVYOM3l|x+;9P_Fdv~ia4+xuX zV|$<|d8+&BRZAWJj;{OS5eToP3C=X@*KaUe+EXBW`{2CLs30b`?ih}eVIj_?7i5VY z8;KYbEyP4rnGJe7LbC9dLHCe|%sd{0oucr!2r6+c3{5z1BQ5g2HC1?++PWT$tnhA~ zFfoD03nMV>+h!;8EYpT{T}=4C9qq6j!^=uzJ%9JMII63Qoc}y%$7|D;#q>9|)#$N* zx9T-M_qcjeh)WCm_V6m56m-~3SpFJYdP;N%_3=rnK-wZQU`JO%@7Fzq>*LWi#OZ`1 zC7%Hc#f*zlU-g{)TGR0=sDj+E**S`86j|%ucOOKgNbW zfm_ubLb)1gt<7F->XArqnbpP>8)b64FM8Fhn51rNt@fdqy~8k(+k1R`^`d+X$ zMO(McD!;hS7OT{TEnhU)1Zx4xHag zN=t>qQe-zP;N!95lDX`x(5~XH$_se=`Qm|j$=bPGD&<|8o+n+k8u@jDSTqDXs$Y%7 zU;s-~jBtS#PXX!=%(6g>67L;5dwll%6cttCd}IbXcHjWI2^A>)v0S7mLj`!%Ky0Bz9+l}`;Nkz)bKCSb8m*VwBb z<{lvoz*Gq%r;|)f%8?r@E9+_xMv5{hG0V%!RMpkrm1|Afn3rp1Dbv=yxYqF62UWeR z!*f3>J=D|G>?qJljf!gGkIGqS)H{j^1pEX;O0YBGro0ER^UuumU(cJcFdoBBl-Y#x zZut+yc6M~6QKsdKPstRHJ-r(UW`(q#<>4LRoI75FG`x*-fhKy`+q2bV^H)lSO}!h@ zJ{}G_4!O6UW;O$``L9cTb?Vtf^phLu3}R=5Y`Q9Rh@fut4#$1?A-k0Z-PZ#4rbn>HK)nYaH+bybV;lW2kEh zx@5;30tKvbm7@#*2?1+%>*H|a8>pH>1P*4azyhecr-7s!=i=l9S0()92{0^L@8JP& zAoVv&(Sp|L_SMnofyCbX!%3l5ZG~ke>9P0mG0tX23fnojc-T(}+Db+cJ zTPk*W@%06`fy6-Qztev^m^pqlLLxDMzP?(woZ8dTpWdQlKgTJ1LtNA-C09Y=eRyyL zqvpda^C(bkBof#k30A(%H8krEk$|nO1XSudn;<4>kj~2gs3XX2GX7b&aLo-K9S*IlH9MeJTHU|^#|{$nvSOH+W}rf_2ZSHO-vkyzU&Gv& zlo}JGbh27e5LE0o`92$z@wIE_dbXwe)%1KcYf0%(W`Z6YquY_mgh%b|33Z=0PvPRQ zW}}S)sW8&p-z};r_@gX_$}|XNVZ}$?Jg`c2dI1>oTNh*8%;O(_cV2DUVua_}q zk$5UA@X9#_di^AaaPy!}(;~1!|3dE(*|q6U+Wr=J(a1`4sUP3ij_#kW_CJzK(IH`McbCExcc5%{H+OKEWP?u^VncEwv zlov`^IeZp~5e#pV5alIQNiV+LwR$fH_>cf~`2e}p#I9*t$_K0jypTSoj~cq3)*-2= z!ku*|3HZCErIA_T5REE)w84iIAS_2n(23 znN=_9i^t*CuH|p*@^oqxm^>(vQ-s4(_|6h{;K+g5BL_q<@xUxarg)|!qbr)Stz&Q{ zT+D0cdU5Sfs}Kq3+c@xa8_r?@V-zsTc|HM^&WgG+t{tzcbF&Cbk7 z*rer}c@xi@z0WUtgje&62%m?M(mIyF^6uZclz3n}Ih@RmLvV)R?Q6do%fl6{pJCzU zH015jp3qCTk0TmM`;VTU;E-iiV9ysnNjm}bQkTqTg|i_wlpEDNT{P6wDdclkiV)o! zSaWb^!24|72e=U(Ma5smryitMp4ZM`HFHMWQ}_>QL*ILNwzWOn>0$dP?{jL0_?ysD z*#(4FBP9)@_47qvm^`oZNdN^wNc%m98tZpqmcgN)q$vz432WT7Nhkjet}H4O{3nNn z_jEY!a(^-lCo;{==R}t!0D)7(pgR%#_%Ujc2~>l=Yy|W&_Z>IUQG-0<8S`y>b^D+? zC_XN!s`t4{8c{b<+j%riX^3%o4sus=laGk5(W#U0_Q#DRl1=-m@eg3>joMh0qExB=|1s?@_aTHqjwOLi>seaRU zi}q~vbTlSIi2hh#MnI=Uz=!Br71Cyt?^I7%z{_eu3MdE@+|tq4)50>nqq;WH0`30+ zf-5PaQ5ckzV2XoZQcCKLdM%LMAe?n{bB2%}kR6*BfFuHX7hw+tSFKB+rTK#x?D%Qr zXY=D{;ZeVg@JR)i!BE$`vr~GsMn#GZ|L%8vVC{|YKmulky#!v2hJk(D*y z+nVbi9GsN}8-_5z#4~_)WMt#;AnCM6SvQNP&s9{lh3hObzXx)SCP4%Pe)Gfo+I+y zl!%B3kQt_6kwHj58KxwT0pB$4Wcj3?)_jMliHQz#K@x(uoJj-DsIO>muT82HNd_^L zs$Z!ir>QSIduR=G=6_zL>8Mr9v$P>M1$KEOW(s2oT_c{~GzS^UH{!Ltt% zR_#E4e*@UKhN!A78*YmUx9L}yV0edm4UoFltORM(PXH4Eiiw|-DEz9GFUI{Sfj{;9{2UY*>|}qwKmlMZYI!64jyh9`EPQX3LrwAp zcP&&4b&T+!95?QwBJKIPilSlzql-3h9?{e5yY<16Q^CkH692Tu(c~Cb#3N0BY!4(d z8>u8i0Qee$E|mcEL&l-JkI#U=SbY}>K7Kr&h~SmoU>uH^?$vtR_8WLXOC}TFquU8V z-8UdFB|F_^WH9r#&&BM(!BYCFY^?mkxTx(4HoNB{YU(mq19u%sIT^t&DWdF3olPis ziXG#UEx=DEKqdUcQ0_cWDp9X48YftPfXXs7BuXk{p@Pbcf&B4`$(ZMSa?iVrrA4v| zSkCtAE5#~@!`2(oW|JF(PHlzgSeSa{^ZF^jto+^{+SQ!FxPNdP=Nhhcu3W_%EIk&X z{~h;*#P#%2m2(Zl0UBQ|W>@Q!X8>?Em;OM{StCzgvQXjqNCgrGt94=rW{UeEm(yA$ z)!MRfvpXakJ^JC|_S+Y!1X_xmqHeh))p7_NlCB0)!SiLrKQc}XoSo`(s zdu@Wg=6(|+qaJ<1N=4hyDbeZLn!~0@^)Y@zGK$6apN6wDYM*MYyG~z4?Y23&ALRFQ z)(c~2LzzI{JEB8S)Pf{8+rhei0?cfm(bHR$bq_XdEWaXN;04$FCG#aI;KJG2+57!K zeR@go!nbR__)}b*8_bqV&UcK1U@W5Q>Gk;?oPDKcxh|sHY?IB^s2oRW_`CI_N*Vct zZteC1DJlCkt+rL`UGby`xJI82~PjYV$M76cPH|Y zYoiYq2{x_r1ieT{Ynb02mRyNRAAL&NX6`03n6O&l`s?B(_89m61X8||!huq-#Zb{A zauy8h8=IKeW3HeHqgS{7|Hx`HmmsSNeMx;oN{k)a^~*}s^9i0Dlkg9Kw2QBgSZ zWA|>|IF1ZQ9(^9nQ+-aukBWAgtbO%&`iQyM*;QY?vL#V6VXBb7AJk(E=4as0dW>79 z!10amTV7tdmZh$43P?*H*dQ%6+_>Sj1;U_vQNx^dJ=i1)%=%u@IwE{GmUh!FywA_w z1B8`8Yh!83+cv%hozw#r2Pm7$o)Ls*S5&C|-O&Nz!J)648!91b6ag+KDF9GjEzY5Be@6oK!b0L%OyQucpE~MHAZ?FB*RJ2 zOrs*LwYm9%@w^LPN>0BDROmJMC&WLc8?Z*8R^*4n_;-l4w`VC(R~SM@f%vqvWVjt! zE1b3)uLDB(WXL!wPedR|h4R(xqn9`c?s?g|9St5uW{33)Y_N$8nNs;I zPhEZz{~lxnh<|2xWm%aG=B#G^DA16EqXR}2lpUsG;5E?iq)58~(Ah~EHNe+E;TxW8 z(a;UUO?T@8?YVdNoFI@!R0qrh6dvIE68>N`0W_5CnR?r!$un0?>ojId6sWzI=K?`; z0LlAuen-ND86P-wO56-jHuquvuTBestG?xYP zk+Uz^j=e6NddDIUl|INT8n!5&6ug+%O>kd^FI_o?#Cib&G3tqKo#3;j=tysApoEEVVe(*20Ja?`A7&>e z*06!qQ!>eI=hFYz9*e&Azq$U4&e{XX5xJyje#cCSD9c3L5Wh99(G*Rt*{T1M5P*&( zhXRTbTCt#_Q<(guzfQb8nojU6r^T}nmO`B8@d>6O4V|L0E7_tNT!6^@K4OJ&OOxs$ zUSQA5!viUYckX{pIgjL10E_>3#`#%u2L1eCHaf{z7>ZG0p#zEQ zH*MW}NypDAM=XJS?&a~w#n|0+e#}JD!Q)F7STpUl5KwvmN@dd)B;#Mv&gOfso{u=> zOfwPpRm%PBsnKR4ba9+%#%5&=la+eWH^}M*KS>f+l0O(` z_={6*)#-5ol1B7DBEpQ&Hx{i?T<_V<+&hF(2x!2qp>Y)c8PZYvFMzbcZN;Enw@0Be z5PwYHN&5nMquA3(cM}K3K&R$;oIK<2zK=mxvr~103DoQ_;b_1TZ@J=|eYji|nBm6x zsHvi|SggmuN76R4!+LD_G5C}CPXXBCV(>9MArVW@jb4iYAn#{0^GqJq$l1wKH_q0u zURq}=p$XDm8_{P?mOaG`#gWQphMTblany68TwaGc_gG{oOIEAqNEpt=+;{bhSeFkS zyO#FLlW;huyB-}A^bxtzGHNkuUj7U@6FBQ#s5EL}+?y&%Uv}aZ#NW#D_;nBm7BJ`z zqe1SmR9LaAesm{bh<4XG9+mcuOI?gvvHDQ)ja3i}Y{$A!J&1>g_w??q#(2;=s;==@ ze7~m%pGSf>yo9e@3qM}f($@B{5SkhvU#-&sI3-vrSjvDa1xBaT{3S!Vs?R-Y1tfT{ z0jOYn@D_O4P~hg5A1Ppv1B;fCaeMWFj!xhmz8ECvr_AJPvFyOS8JKbUj|2n;Af=h) zy-9DuK*7>p%M4l%6hdhRMl%3Nvm*m0eZOvk*&Jn`y94mFnMYdhz&2*l(O^!1oj}gg zGGDh4SSy15-MDmOy+wF^0vrP94C6a5Yr&5!Qw0#zDQ_Kp>b6Qldht=>S0WlmrC1S`+vPibS2- zf@UeNVaJ1=b=U!e`TIlKa80F3t^D(dQ9=zj#3Vebl!gQ0L4BUpxYfV;K zi`egjol_37L?g#wr^RmZ)$dr=FtJ(~?s8!z+y-PJBjQoHR*#ok3MDml+hW4S+fnKv zf_=%-h!(D~yrZ9mVG#ignwJN?4m0n+cC#I>VeGMB4^qBc(32ZHf389CTMR}YLm%q0 zx5{-01U2c*9k1|@k#Zqoi>!#KFIz|tt^AEd4yRV+!hwtHXlwI&U zuABxnW#w@Z>pcM{&noy12sJ@|E(O!l zr!yVKd+p~?VS<1ric|ru>0$m=eQ`qEi|TSi`^xz4p+^FvNkLCqCv7kGBPGz@i9%xt zZa*>Z*Z2|A-Zx%tgK9`Psoi+omBpd4_~IE){;+q%Pq<6tqIKq`SDmUXW;i;qml~Y) zdLoIXD$KO#?`O|xxEUwUP0@S~<5#JFcG0^!5MjRdfxK0{!{bqm9tAodILa%1aAWA{ z=?~Bys$T=Bd1&slvI9C3=JNu4X+33uBj1B=P`WGiwJ}O)Ej}k9rkP1K|Uv%;M9yErc{yP zi|xJO8nOqMjASGn(`D1P%o!e;Nwe$QujE_p`28X*eOa##mnO#Cx%$G7DpoF}4&EOT zllkhf{nkqia2GFH!}pE7{L=p}py%eAil$B#+g+UjZ- zErnIQ?`UP8gYUSNDtdMGzM$Val?iGiDS-V$4!tJw54HKzspANjzf?={e{Zgbz#$MoNsAWX*WKXZkSg`(bR*Sk1r(No#$ zYyHsaLC(Eur0IC{+!>7vMC?!-glbTxQh)7MDL%fxv$_g|{+P_z8Vw0v*2Y=(P8gav ziIl&AkoQP%NOr?8x{OV`KlD$ouBQ6QOIU74Z~E8$h-ZAO{ebih`cD_D0<;Pn)-uQ)V#H zoT25VC7MTfLd8We3&%DfYj*7))T~P@Dm^ zc~OTUjvFy5P}AQ@a0+J#a)JdeqyB7nw;2GZUq$#k`GFu$bU#QsOPCJSY!FOE_zZp} zDx2aKbpvHPFsx4}10O=EFywRC=;$!-0VOO+tA2}oA5q+&X@C|cJjBQFxz$Mx!SeqRE!QzUff*|&o3D1Pp?5V zhFQHpf=iaajkS{D-(6I#S(?xeODv3PM}LFs;B==MJU;E`eoa954~O5H4?nBD(okS{ zDM$v}v&3C%{D*Gi@+Y)j*27iUUDn^BBk@E%njLo9F3F76GzVySWr@X=7-% z64(6&S&dtJ3DbiEZo?RmZ8gj8>}bhd%vz&J)W-!h&F=Gf4RW8KYvWSH_1(9#cyovqU-#{$7R=BWK+hph#hR@#mcg45{vS=NwC$&n)Hz`7gXCK z+UlKZ3NB+eFJihjHafsQT9;I{GVK!gxEP!WyI19#xGrXKi%-fb=W#wA5X5ap}W&~Cpr58qH9r^xtA2;1M;Zm<54)A;AWl|2%>dd{wu>YEpH{1&?_m`eKD ztpVTpFHYu;?o)mC?cucdq*EMRr#mca29}*F^$+(_BM%=$K5w0zNqLNYiJS?xP3B+D z=Dlm*rdl7=e!0M1fVe}Jy50kAT`oF*y(g48H${$nMJX(8Ninkov;C rj=+3JsZ0Kh26*rlt@(q4@TR^ZVSgS#0=*XfJLF4=$%_^U>-+v6kV}f+ literal 0 HcmV?d00001 diff --git a/datumaro/docs/images/mvvm.png b/datumaro/docs/images/mvvm.png new file mode 100644 index 0000000000000000000000000000000000000000..88257123ac7db353ec1919203576b3b29d37ee91 GIT binary patch literal 30318 zcmXt91yGyK)5o1sXmE;@0zq4xQV8x)f(0qXU5mQ}Demr2v`BHcKp|Lhr?_izw=ey_ z^JONJ$vt;_zm?nD+uJ7${7D8MhXMx)2?<|LR#FuS2}KME33&|*1JPo6VzrF;L30w9 zQ^!Jlys=C|5&yBj%4$0yA-(AN_eD-(eL;z6BzKn5a#pi9b9OUyG(~cAbK|hIvvx8z z{A$W!?`WR!SBL@$i55vtQcT@F^DxV;iOO{TS%irx>Lro*F7&1NF>O0DOMp5d`p55; z{{A8!gv36!TF6p_FNx6$n6bivDB`hth1W0gW`h8 zMAyRRN~$5LQZV!&s5zYK7Fx~f=Bli+lQs1Ff$Zn+%H$7y1WEgM2J)SLT{eFh?eUb3^Lb7~C=R8P*INxuJt0I`3&O z6X+tdF;=ksM&>}d!b~1efC-X?0kZVzG__>uT4-+A-U~62BVdqCiho1CryKgD%B+R9 zh(?7RIjR6-9E$;bcLM9Go_TemP)V4xc>O~_9t2w5fK3J^`5%w90Ir3|@x7_t{VyC+ z(YZf2T}aHcE6GmdyGQDJ9+&vjd4Ll1=s>jXlvBlqYGO+h*c9 zR7?yR8f$cv;aH;&0a@hDVe==0?WC!Ya{}Vt_R9a9t z8r*(;5}L09PbAOGQrXseAp0aW@BELnr9_9U;Qb!k)ZCPMA9(ekL|xRGjTE(b6=wNw zuq|0wVwMSf7W83oioHC>Grl+Gs2y)vy5}EhB{P)0bP-aNp>1aM6iz_%s~uG#xINPG zup02+*pzu{_p}L?QOs>-a7r!uiBRu`FikV=;ULuQlXTQdqe`GP|VpP$0jWX}DNmp{!2?Ysf z4gN_nz55A<63)NWnftATQ6&1iI^msZ$NE2*OMR}1W=vm#h;v=n{FrTLhB^JR5hBe+KY+{9Xj*1&J{`W{Qhr$0NaM{3K4BA}uC=j=P%!4~etM7mSmpZa7 z9?n0rqY~a=sr(H^Q{|zZ8Z~+3KWq{oVkg8yKX0)_{Rnb^Z2!ef!$?*x_d<5B z`{twZ>s~JB`5I}GqU!h(`Z12x326?!sRj9-7r}&QKm<_d)zf^yDLmKt7?WA)_NQ|i zawdk2+E&x)F*$qj%(Vu~8$Byp9aqcMr%viW$La>osMod#PMq1jRN9~;Xb{VFtzWTc z+<^;;Ib)8PRT9A)Ze>JM%9hu+uPB@B|H+(nub8K3GnO&BUeNImkNj=ey9g*Q*&@nB zh5SpHG~m9K9c{+$0b#=}p!4I6yjPn_Gp9;#K0Rk~IDRbFQ` znyB@l83`$>1Ww^x_ly|i6$x$C_I^~DT#37p$cfUb(|=}A$NVU37|QmlmCrJ>0{l9I z4R?eAhN)t3Y;b}62c;8z`Ng4qmKbPdIGNV}6lN1XlUqM%Z}-oB7%{% zUP1h`bI-K0Uk)KNsg)XBv=E`4)XtF&*Bt&^Jg*gK!^TRg& z+1l&AG=P142(RI@0?NC%j?+;zh;_GnE?IG0pv@$!{FrpLgC{R|k<54@oX!K+0LgUE zr{u(w`m66NxB!wCbJY_jtX7GPz<}_5qul*p9%@k%esJjn{KKG8Bqs)|*im;hKyA8G zjGdWcSPbe?>yT^5+AMJ8nX_2%9!`evuU4d>mtH=WudYPM=h&*AVq^)qD%RrX4$NAX z5rR?oPs+VoU8e-7a9%AOaU-Ud`8Rb90)N$riH1$~{J*ik zkzUtE_N+N1hDT z^zVgU8fUyu{t?-a&&Q<6jF4L2KdIFkRn~Gf?;Cm?*D%(L6kUt_ss2b zeDNzt5hw?K(ykholUi^U=Mdpw;{b#LG|SQIF=yd=$KwT@)aWDl*at zVhmRI%I6+7=D|t)f}_nmgHgn1Sq@rFfu#)Da1-B9;w<>0xaU`~iO+nW1|LMdh6)eZ z{i^0T(MogR5U-YS(8J7Ty+$uxRa~8gUgFB~moE`Fms2Q-oV=7On``LTF+*TaMd>Wq zO~+npq8y&ZY7HJ_KQEiR!Z+9wGr?;9MbZ2KG+uCmMd<#f@(Xw(&pL@}-LzV4qrCXYo4^M(Q8(LwPOr zTVhUEK;DLPD^Z=F%`?H+jX)cNzlOgCbdCIv4GI;$O}duf%daMZTb3BDbhjy$P9m5n z`jww4j1^}MYQ68PiB5Rl6KSjS6}ub}-+{sB9k5pDaDPX20M{V3^eMY0svqVyM8KGI;atg%3q9a1~@Q-2F_0{{4X-UF53XSV{6UK(Z0CroCC>*?y8 zR+T^TCTyvkMKY%*W4>*84aiE2pK+XH|04ws#e4gXI;4N(f zNS1Ln-wmVE+89aXsYE!=*caefn;x82%F5i}#aCw4MG|880&ef1>#)1H$=GbjO!eh;K^Y%6F`%Qp}|reaS`VfiFyBKIB)8 zQet6d#h`R3{c!XEuW=^!ZjW%; z{yEDFNsZ87V^J6;DmNc{rOS%i$zNoOjVoQMIivneX@|ChZv%w==b^UCVh-RWv_gB# zNZQ5j4bxS5z@)RJVJBf3t-{o?zwO{*C&6X+9~AA>Y>q=G>9nJj2wm7Yl=8=6lWowV zt1(!5z0-e(^(GTxgM8a;h|SO|Hn_h|q_gm*RX7~rFa6cSFegOwwwX;~EWcnWEskNA z2=I%?e?4a1ySB=gwV)t~O^g+}G5!T8=fxputOKC;m{i|40x%fMV8Q0d$tc22K>Va= zq=4|@&M$|M-hne0pCbe3|787QW+$%t5_t6yjhPJP_Pw1TWCZfLD{$7)kP$k$Oa|G zgf8LwDdV)cfli!Og?B%;2Xifaqm3AO=($q!?^B^n6HqBj0@p5SLaqG?@&ocmljEkH znet;9e7p^{NqPs^zqtDe^qvGT;69HQrT%`|TQQ8eEXg}oMEWFMx%6C#TSYWQ{y>;^ zA0y2lajwFDBVC}Iai@)oP8##>!H;|EzGk}nclQcI9BYZjtCOsfPt)emS1P#|8o;lD z5d*=UF&N9A{S(uVVlxiK!vD7SabI1NSu$8WcCT#U+$$0hAL?SWiY@iKUr;KGhub|I zOR7uO&9{9kPf;8Hz*k=-6j&ZB7rb0Y2MG`id6po31Od0#N3{s8FlWRdA;2$>*d|bm z3p9aOyvbi)@Rr$XAS=c4?xLA=k*nQwQqkl1Nz7ZHUeXi!tqjhA>Od_6?a**n#<2Y_ z!+34xf+2A*j1tD_ZzXeE(z4CN8C$qRB+zRT+O0>i)u-+iObb0V8$?)Zm}vHkO>+2y z5~v)tP4@KKoho&#=aeJ2p{dH#C;$mkFlS~$AytUp%_*h{n6b^E8F z|2_AFF~KR;NIIi2`MsA@>}XWJ1h2=TMAm%s=_uJ0!|{|la8R+X+sIT=lQUCQ$^!YM zP?up}X*ufm+W7Ga+1h$=l!;e(c)!6QWYI^xk_fHlNeKPnZ8fL>&8M@9W2(`Xy&$yb zv(2GACpX)GBOn8(FDW;Nct&HxwT@`Yw>(t0Vuvn8by;4%ETv?6*r7xk(#$7Sggy6q z#^bk)Sl-U}#Era?@qI?1|FrM!w$ppT?eU;Xfpl2<^hefpGk}c`*QWHbr*Rl1qOlk>5vb%u_AN2? zhO;wxKgNTXBlE3<%EFydI7x}Z*znVG_g$}rc$7sD12Cf8C`_NN|C9vhP^6Id0I3vB z_)Ybr*SL};2JRW5?o1tb z$<$(zjk=stCb=|V;+!9)3^tGNNYm{0g|Wg!#_csCq+AuIW)BDp`hSaerF1eb6(2rY zI4*yveoBGOhq+jjHarR@Id~i1o1z}K(`Z0_iKkmy2sAQAN^{%s2Zb`ibfZW~H*bv$ zBd1Yvv?+;AyxZQxhf2U3VFkwV(J~GiMYoZXa#Vf=pk8x2RhAKqj&E$T!;!7lfM4L3 z*Gy33@%ks6OJ+#|dd*tmH^xMV{&qa$y)|UQ#4#M#bZUe?{g1pE6JfWzjx?qIlQ{P} zLFMFrVs@XN&59D{k$M~3KH2i$Y4?Bkp~8vPE2>}T&q^AX7?a!ef=84=tmy>PPkx1P z3J>f9`QG^E356;46!Ae&e->S&b|@}%7!s+eK3g14kidmsDvGt;{`(c77q0-{;uxCUV{#u7qrdcp5&O$ZX z(?x3N7J&PPFTfY@AdqsgdBs;Fpd{7RnZf^6*O;OUE*n?L7xnnTd2)sFGbo`vOq}XT z&Rq}KC=^DWoov-S1+OEgRJAc6XSb(&Rr@a1<}UWfouz!tk~QED4^Mg_*x#{Pbg&sP zIp`O*m{WoC#KZWBrY1;Fo>QPxwRh*O^WOy{74rO8g%c_sKg|tQA#9CNz9Mt-=o*~w z6s+TCZ%#aPsp$s1+mOJ zi`vP2S^L}z??)#|F{D%N=KCdIHW0D~e0*SLX+7l-XTZObAXhCqEe~3}R0cPT3!#*Io)hwEj-nY$<+b!tLod*8JC(0uqc5o ziFeHX?bi&RAQPB}K{)XcB9S^W?RYAE3(xc=6{+Rey$elYXOv#iNe|iVKo#AIaRHKko)kLRPKoYqGkNdOZQJh@VknKOwq-ysY(<6M z%-Az|7!3Q8`2Ds1vRKDv`>r?tZ;bw<6g=1HpP`pd z@k7r1$AvXK*HXJs%U16m9#b;tb}eQJo7pbUKZKcbuyH7<2-K0$7Jy2UxwxLe zG~uXnSGfPr(IIBPtaj+){81u7QiEfD;I7ua+i+t?6Mbh6w$El65o1b!>yu?o z7UHgR&kHhnAFb3S_Ja`(k(@5?ac&>=CHciCKSzr3*LT%GT?Z6ICtcZ~R;9gVGfXM` zDvEK4NEYo+{uL$Z#DjEIk+3`ImX{AZl5c>WC;aU^R#Vg&&c$(D(h4G3lO5O3azTL; z&AT*&oJip6Lor9kzo%F{7Goqdq#gVhd8S`M)Hw1-vXTn={17-Zd5>Ber8ECw^8N#m zwLevU9z{F8cH~DIOvW)`gv_|qgv_(si4lWP37Td9?A zS(2iISsrJR@!U&|yAJ9k%J*3V(I(fAl%nLqLpzLFdyH2_-dR8V3>5M?ivr04%Ec<_ z4q|gQ1tVheREe~ecbMY*>_sNOC`uOc*8uYFasMEa0DbSt>&cqQFMYU=lxXBlM;w)Z z_m~nYo+A?6hip>242yH~WRr%Hw#0f8{Bvu~^2Rk}(DeP}p*d|~1h4N-MM_9bc7?og zd2M*%snJHJtH&@O*!G7ZBYAD2rg$6lT^#kdyGLATwyFx^L3aQZ=MzoXl%+PDpR!NaUc9_yR zks>7=g027A-JbBg_85Rp#6 zHFI zXOXqmt0s~rY>_>N`EOrI|4P9+5GWP7_b@r=%CVNv@J5w+5EBXc0Ht?%O^6-ym9+?` zwQ-iPIx_Qh;dKEju!^A3XEn#fAw*gp{?-8JD?ycZ$M?;_uQeaLpV<&7EE!nekZSD; z3cMGoM)2ivk>@F1nR_SRZgB^SI_dFaXoyc+HZq>V5=i81(1>;eN1Ed@ z*VY->!DbbW$!DyGUr^A{XHuO|y$Ah|x4lEmwjj?q_Ox2IVKXHIlDz?6zA7=q_yqpG zSH1!_Oh5U}BKI8Obfn`6FfXU%LhVSTzl_2I?=V2=|DdBv_zz*Tiid3d(_CGoFgWF6 z(!X%f=9@ygk@AL|4%X`o$^U`u)}0a-^)Jd_}l3$}8wzL1mWAZkzi*7nKnQRko~0XY8ru%j4z>y zg%C%UJvmS6s!tJH?~5pJKkOY?(^WZSiz5u~<9oatq?6By%zXX<32xToHSTZU<%%V($1^=jd~uO zo=gpQd+I^iyc0LQwRTGwWy!@g51`T@au4)V{AZH@!T0Xyh|lk$Ill}?!|Ch zMcRThy>B-RKnoTS>$A~8)OstBb2%1 zt5s$?wNQ%~_C6{ElTW-Wj55FFjdi}kz^8TFRl`-}i$D-qtDK5_GJ6rtCP&NdW0lfB zxw6^>>l(tW*v<~=VJF2w%&c;EgHOQB{TK<&Ed`X_bNPe(FDx4 zz>}#utMhPNGc_Z0>aq&D$X`*_GMlNQF;?7gf5)<(Y+|n7*H1Pn^n`JkpmmMK^$kzG zCBaoQ;4U_JX9?B-djadg0rC4UDvg4j`IVyDeUXWEh5{LYEjx^=&f`ibGbqWG1~d_I zy}R4pj>3+`|Jnbu#6!Z|ApBuuDezttTg>n?jbQ1k&W-ntOARGTOD#U%5` z$=&g+Vif?{ryYM#_6c0Pej(kr$~f4F9hzK>^XE9STm_!GT}`+aS(W9OIFPEh@vfk< z>);Gf0)e1Mqy3b&D=w#eL|OghT>GrG=F|>*Y2dSp3#0Mx16IBqM&b}>ZuzNZq`2`R zG<|h&Pq?e6c2E_=XUf{o4fE(&2+iY=-K>=sA&S>Z3oTdpU)RWhne)?PlVt*31 zz!-zrKzBg}x^z@st+^SJH^%$s#N7ne_gjb+DLhzTP}$qL&7wm74TVN}T4RT(!?F`6 zGm1_4@5|30UP0e|uUl-yqWNP9v)_HmKA+0BxM-ubh}W;8;SW>v#9rz556GAVfR&qAk?y*H^DB!iv`?8kIm5+_2)xE=v?qBG*2=) zy$l-LdfKu)^~+#vv28kqpt6z%Odnv_$J*kUl84Cxb{ zkL@SR>D3|BPU*+$cxcngubE(1GoMwrf-k0%V5-^B!x)N5wTFO3<)xVZz$UI?#yg&E)RiH7|e zs^b}QnRf*cDdM4D=UV+bl~Jaxh;fD1fWgQ7K%?rpombr81rFY81&dx~3c{ZNEb$~e z5DN1bIf_kl3#Y{1uFR`du)E2)&t)VHGU3WX%WLTBt7n50*OrRKj*Nq%VNMjK!={mz z1j*tIZ1J95+pwi5G6S9(qPG#0)MKGp=I>FIKyL9fC#BkEd5I58CF~5(a2Uu>eZvih zkmZI4yk^d1;_bR5XiD*IJzzbUos?d)_~1jDo}qwes;r*=$1Pw2C!=CXjurzvNKBj5 zg(K$phD{x-TH71VJ^ci~BYVqXbwk~a{#c3uXe2z<&)adV;P5+T4<96Xx8RAb8Om4m z`W9>N;PQjjx%XpAdn^OP!rGsH;VHIclo1ARWBW}<{v`9Cmo-Fa=_fJ8Jdj=N0$TFP z;fJ`3dKV+aIx?=&3iw(abY!)(CIeLR@k5zwvg54PrtUpSRMT4)d%e>ctRr$5y&l9T zQ_V($-T}qoW8|KVA7UgPoIa2T_vn=@xbeWtfV|_Wb_F?AtTtu;_O-+y@iLTlen^jOo?`&N8rSG$T-h#nWcQj8|on53Q3mxf|&-7mtKLarvOuxKlAAvil z->Z(a&M?obsUa4iF^e8wE%_-!fwme-3D(Z}^V{J!{(pS~GolJL|nt@w$cY>PCl zy%(;shpTVWS3(ahy7|w{$6tBU&xe2S9gqhfu@NlNP#!ZZbP^uOhR>rqjW<8yC+i$PinXG;* zrsi4KG%zZ^Id%3_DJ<*eY?OcJz(vgCFICglog1v^((H3J=fkxedB$=zk&G^gC!n$a~!{C`5&wv7KTeEhztpdb{q(c zDxlm%E*Z6mR$=8CWkUr&x?+6ujCIvV!+Z9VaR{u%`ZGr`XAwkv)_S)E13>f2QhtX| z;X4u??$8jA$jeq88*GZqwZx(kCNK7_;WF8uI=Di7zYs+k%IaRs`@L>jM*4ILV-CT# zzJ#$`e}b<+#Z*M7O1~qpp&3Z1Lwl{Sb-e{MpOKi)9{@Qn8DZmmQRB80>0KsE7={*9 zZYZo%N+#!q?P3%0togR{o^gH)vMhzIjH6i#M%=DP>F2pny*iQSJ0HyQuL{XL{_ zG&Wco-gQS@PX$t<9yBnK1d^kp)5wpXoq3srS)w1mp-O$oK?I5vqo%-fD_r#j1Gmiv zOH4Cb(y=+17eU@{&e#ac6$o~4^r-X2l%t5CtB9D`qPC*AU7b0~u@dI8=)BUeG1T6I zZZBo@hMbUc-GiQ)OH}Lc8Be_AT#Ax^tE+(twHa8wbxVtmhysMm69CInjib#{O~1k! zmMrpKu?0E>(kH7tf9)gwzczVzMW>me<+!<8DXuC3)H2~+7zJe^ddi$+e%z&+0sB8_ zg$Qn5)kynbehD!rI7>6^KT9@EbI1I0C=o{l5Z(-81La-*LPTKfQ*cJfI^Q+g8rL^@ zAc@90T^wV@_Ql8==Z!;AMeK!mBWUXZwum-g1UUW3XsTk_nZr*4->olKtC(t>@^-6(${bZ++?38v8t3x|J@gsTuE9y7V|*!PP%V&!UnTxmY00yG zyh#_l8L*Mqq3HGCTVX`D$M+sruFsX+cp)d2s80LB2&<4+QRlIzL*8JCt>t{6CM8vZ zGuss4;(ToTzLPfIvy?M-{fkFTew<_jc?L_}b-H%>95y zUCBSkujhO*3ltfeG0>B&w^JG)l>CDARzT_08Yar zuVaQ!h-$>XJWvf^KIsQ8cyWXj%%<0)D*N=ZFAp<*h3E;N|2r?b(r2Z9C4HB3DPzH) zLa@rrO^wq}BHrYZOQp=Hu;BMXiq)_sqKX9Eu>>oIZWr`e$75F zW5>+6d@4Fn6k9av?%vajO_o*y<~5bBE>xnN3@t4Yf6vh`-)frl&_)me$Zc0YOEOtu zcV}?9te=$SA}WxBbwDOgm774@XH-J&r5mk+dWqJ$Iqsi#(#q^|(8VUd>939>oIq=C=C-s7X@y+#slC3w{S-sIb7F zuvUy}x0BjBwGtp$>KBPwGTsZlh1h91UT;B}e_^(1iR%JU7Vs6w+9znuQPA}xP*VDZ9_B1=DJjQYHp z^9c4>U>wUYJv-z#v`Rn{I*<30wjyi#RqAWrzH-+eJztC!z%G$71LLKhAK^R!?lf%{ z+CR`Y{sPDvXH2PaupSB5tXAQYir??q#YEmbzV5{Jbd1b?e-5%WsHRTTPAYp{AK5l zxjOtA-&eQ4RHK-52PNkIs33XMm8Wh;u1|=*cZ}v8IpK;-=mQk+eKVNoEK;|;?b1>^ zC6&n!Bsq!F!Y2C=G~p{y4~Q)b@xs#g|6H#XEsu36jl;}QPqd)PbZs0yn`WWW3QZdC z^=4tQ<44u@?m)J|7YgIbENYOYp7f~8XDFf26XUJw2&gdx$p{=GAu4qT*yD#%+tSi+ zI%CA)a} zDy@*v6ZsA@(<7~JGXvR#BEVIT;_@-SsGPHTRV{g!21I1_Wm*#-B{$}n-(MkJGF3!M z{f#bHtg&lEAwy-^i4(eZqYMam`u?rZTFvkF+3HrtU+yUu3DzPX&ITF8fVyNQ z#}PRoNikfB@0UF$ZhqM{(eoc!;#NUj)lgZzw~)7|IFDu6$1?Z;xK1u7vQ?A>5_fzt zTCg%@bc`}3W)~*>!Vh=m&O315+Ld-;WC_6d%2OIP$uffMD=XN4^L2C3f>b>C2*Soj zhyFuHyk1DK+HiwNb}jTqBJkRe-|F$}+x?7sK2_%$9K9DZV$a3&3S(M4YXnlDb#36) z{*mBw&Yv;+c^^Cp2jD1b%2)?^;1K#u`-~$q#i-HhOXv2(RCjyRL{5k$`Z-&&#HlOg zcaw~*SdA+UXFPj}BWE3r9tX9}QM&88M@6 z!B8DdeZl-oV%{wO?Vk)Pp*_x<>uu;u^n!WCOGLYkb1#}RQ^cHLWk9nFob;Sva+dYG z6;N#cn4F+LM>2%J?SbBq`v`^Q`8CVKElFXY0U=H}uIVrv)D9_Oe6sd^lQ_7~UU>tCApv--`Em`@c6*R2yzuGtn31TM z$+^t{=~y8@lChQ*Bc|?I2YTzK3b|@gJ8zHd1@V{Pa#v1Cy3dUrJurnA?Y{K6X=Tps zJceDnNf0Om4mi@U?XMCdisHek{Ra1Um@I-?tQ#t$KNaG>bI-Yta}j93D*ISDnX6Xh zBN(3EpUk%#9lwKax9t&oryN+=?W+XP zdK)h3T)j#nz?<^F7G{xvhBLaVP3DP@z0 zaePENP}yYK+U2JcQGv)eI5$CXyP=2gMy+{1L*TE<$KZR35m_@l<(&{<%x7A8_B^b9Y)2GdWTxBR$#-GYfP1lUp1tGSf8b zg7rCpLyT0Tcr1jm9(Z%(t&>7ba{5LU?~}O7GmXS!7h^uQ7mn(SbkpdZ$yJ|sP(W&2 z^=#gAAO6{sEYFWo3FS1W*!V}Y*UvIhW@TF*tHS+}oo@y0U1d1Z<4UgQkH-@(+bFK| z35GCc+7Us5PM2kWVW!DDmUnSA43378l`7vrtF+FQ^GSCHllWpJaWNXV&2hn_?a{g} zxz=Vl)1MjOxyCiT)Af1jqK~{gcVOvN3}CjaZ`OTTN>r?A@dD;3oorkdLFTkpT)^zv zO%o9h%?(lM+l7!jD_xm`^+4aqn)eM#<@%^D%}*$6MX}T1<5$zj2` z^75mrG=H37zqm68%iqRaXdhjKo=U)9$u)u@rWq#V;2N#h&D?$Hi0UIiC}kY66Pcsb zv6$A6IVxWE<+wY$#g*^s$T~q>;>VIWBVV20%?DmQ2;=o~{EQiy6k#yQ3b~_M4jDld z2zmA&@qJTG#_$as+0daT5`|>-`FpAS9;e)ex?32w;g1LQctS`2hO-Up|8O{zu3{T3 zie?q%hFXtZr$e_BT;kdW?1+7K#jdR6DOG0`c6DBSIe&=%{$p2TB1qX4$Bk6jewQw(k6qG7+%^(&sdEU4n4<{Q$%_JR%jn%@uSilTVGiPwBts%{e=Fc$Kh zFGM#Q-|%Eel6Rkj`z7K{hsl&V?|GZmDMqn-x3e2_4;a-~xq7Z-2XMb|C$ZFxxw~n# zA)g)q2bd2!ngHoE;(aG>8r~uDTFmdItAOXSfE^;+3xRnib?(&A@4Z7xk=(I}YSat$ zXX=92l~6$X>v6T`nAmkdIDhy%N)0-Fn)KM(t4lV|?neu;1&4}HEd6QRiu1QWy`MAqFVBAi-jt`Ar?eAfp+4xDv`6-U=%U#q}(qkH4i_+HW=oa z-}}}k=2n_V^3I`ltB~iE)$OjU_8VnCJcw@^l?4?L0YvPhOTW^XV}PF7dQ;&Z7B6Tb zeK&>W{vHJ2Y#}iqwYP>JZQx1GsyaR>l+N`Pz;+H)0={oyJ1hST%E17MB)}OzNoxDZ zkq4HChL1{7g2GTJKKF7ZYVuIj3n|$Kk4e)%J8*BN=>`gUFpGUux#JND{(As5qZk63 zY$I&vd_1TTY51c#owaI-D};MK;)2&1g4?(9A8-QmNDU4FPqOe<1>bE)z>o^_5ZyZo zM-jiDC+FJoV?Q+d-)B#RG;TVY5Z~+Y(@uWmX=pV0@GWokhbI<2npel%Apv6WpS=$GF`aM)o#KT||gilYB>DHiV#W7Np^vevo z_K_p8D#)6SfMM4sak>9zANs7K2b`XN4#{&OJEZ)(h%{uv#9OKi+cbt};jMmj7Rm;{ z0`?%qL4ql$R^Q;>t>mcd$O-B*T-^4N{SD7fN4TBi^j9$h5!8|_wR*($Df*Qf$79EM z(8v-h>%(CI%=|zjphIVEXTzJRDo2{pJhAH5&2j?j|vVU}-FXF?AuyFTy0DFyRQ4m}l|Ry(58wSZ~7D50Fy>!?0Z$ zrUfj3q7bSn&gQt!<7qdM@Q17Xxgygs&I~=Rbd`G(zF|#yO$jBkjjo8DT&G+O(-Wng z32fd#>nz2)Z#)F1chiG_33;&WI7Q@be=vfV#JwN=^)sP9yxr{| z(S*S<5;NJ3&~DsU&$Bwee&|`T(?0Ndrbc@av#^utGM_UGUx#Q~Mf*}qRXzi591WL> z<5+jWI+N6NJ#Q#J%jCwfi6^H@e>T=pQlC0B2fW)4uA8B-P-MlLjdAT@X%vN&5Te?cI%xv3dzO+$y|dzToNhdQ7906Hy92T$=51}H2!*D zL>yF(eQ6Tgz^#pbTf=rhSNJ2jDdsI5zh%8VcmiLY05MS^o>Um-)&*zYnXDc*e)A@suaBOR?y4#$`!=)?|1*18$R=u36;b@$EnNHWo3woYl|?7K)y zbMe1bcJ*k%D6u(of078piAxJny&3K;Oly>uy(6nooVibMo&B=DtaX6Gl5!o$XX&C>_TBPfo9!vM2QD9+@LKCc6aX(uT!!inmH)+me` zlWrA(kP7$y~^*0Y+r)s_*9rho2fn);G-6`C}?&$pECEok>@3o*u& zQ-3x;tddcQF1;uZ8>CW{`un@-%!##hr-sSVi(-;BNSE8gbdD!s^kp<@^Y`E8;w0Za zKQl)$X2O>cuVI-bj0z)lf^4e`A#R{+_J9hDa*C%wHBg z?h~7;FaexkCk|f=l#UXLFxyGZWB1xay?GzI*E~oU+H(1&OP8H}dZj?R-5^}-Xmdkff(Sa47?5n6H>E;FV(UQVlKc#WT zfr+bFZ*F?Q*}5xtVM#l1ubsUA78 zejA|_%#X%2Yb$AC7C%xle1y3~Xn5Kd{Iahq0*Z3c8lWY3pgUHU{%4l7#36>Io*TQdu#J=LOKQu9oD2MOz_rP(ip*& zvb;4{d5r3Ty^QJ5JIP@3vI_F?i1KfMEZn$&^LL38)XGjd5z)6#{VC61_R9_}75TZ? zPoJ?&PIsNJBuy2>$-6okTVo)ZoftE(MQt6_T4R5T3yaz(tznsVRgh`%lO4Y~)Zs2f z7+tbLoB{D_8LU0=_{kUdD@ql?pv|t!jAX*-F?gfHvF|TSf%BEl5x%k28fap6ya4g? zrJBK^XKQ~9x^LM2aL|>H$h=Qae55!>?I!5cPt_tq***Ec#S>Qw0~}}9DJwz=8mL@T zIuoWLunu3|e)A5>o8Jx^(H}Dp@68J@m)1wgg>J|Vd0L=e z>+@UZJ0CIkzs+(nrV$nMG^ZPyAW`=vbbB*IyK)=1D|cu*NRz=oj_pkwbqupsY;+>t zv$pE{51>v`F8&xkZ$X{p&^a)-fg&3J(46x`gxI$u$B1rev&K4Yw0DmL5fng2e;1sU zx2{?I9KG|YKubE4nW*{uS-HAL`donJ#U-wZ?-NqhXG1xRvkDC@(B~UJ&OymmnqCrN ze;tre;G$?2jO-$dkByBoKg_0^HiuGl~_*%Ehg+Kkn~M(A$rEtj^mlJTAd(Ahpd?J8Blce{n@UVhrNky7|pZOSfG>eyywy< z8D=rLY-Y9{;wPF2)s{vSJ{C6Xmwc3!>RJuVwa@6Xq4D6YaP@Q$$4PdYM)vvhxa)PA-L4KD4hd5n@%AOf zxM?{``fTXe7L#Hw(l48NxIRKsjcS|W6w2c|wTg!>`C6fD3UMyXclQ*9KbwDCKz@lD zFQk3a2w0Z>TU-n_{N)uFygJSp?ePjn>4xR2KoV)PHyXNDP6*p!`v?!<&pG=hNqNC0 z%nA6kkLQiYO6QC(f?D5d*^)#jOGptIt>nxVotp}60H}U_<2KJ`QlTBmrj9DRwx@Ca ze?@(DTvgBWH(a{m(wCA>0qO1#1f*0tq#FbQ>F$t{?(PoBD+&l)y8BYn0s_+T9Q=L0 zuZO=l=j_hT&hF06?!0Gi&5LXmn)Z^-*-b<~QofFa6R`Fg%AM*K_1F@4JRa2Gq4?!$ z8*^BO=9+cD{nbSWK3V1yMq`TAUO^8dBkTZ$djQnqcYq~W7c@E&a7;Sx0i-Nj zq3y#->{ZABG%F1eZX9^g(W-}Urt;<-sRMboG#Oy&#lqpM#UekCCr|*YnfK(}(JLwA ze^{>4dLp(Ol3W$iP3=#>4BS*=moD95djbHnXuW)0rug(fQ?!H$+n(Lu@4`b*$kpL* zMaAS&wLP|AZ1iz6SL5#i0Qs&~Mxwo=L)k7oRZA zW0WTFI%RJ3aEga10La~E_4Vd~e}uM>ifX6k0!{yq(5NOIoyJ)tjqiT-Y=C zlty3=EJJum^W@PbtQT9K7ndvp(7QF3Czc>K0}{aeEmMYUs!&Vh0O2ov3x1(!v5 zf;#0%ls5qbO2(OrzW_u~zZ-=yz4(MgWUfH$VAAIlcg~hcAnsFE7gx1r5aTi!U_mAR z%wuwH>(kSqyf7TPE@ED`1K3PkwJN^-Py13wyocd|FfRh&UG_8{q;B*&Nju5_ zfT+!~l-I^R;En=NOe)W^NFGoBVF4YjxF!jYWlTn%pnn5Gf#)Bu__+E0p&6ILabBTFO0R)XM*e@Os`b*M+8)6G#{WL8LtTrQH(Y_IG-?xz z-mbgu?0<8{j#AEx&Is8)5qNSF?qOPf?x*r^q-!Ve#FV{cx`m!te$PZOitc&~F^d@t zeEm7+Gbz&279bnLp|3@?t)u~rf1Y55UMaL?Li!)xcQ>Bo0JuH-Xlm}nxkb|dV<5W= z<6Xjo4?ucK9$OlZWYF%A)kFsX^O&jB#DD~lSHs`C{n=2SBs{N#P6880rPY~x9WB=P zU+s($Armcghie{ZurtaDSs&km?oaI;=7T~J=|QENou?rn<;L}lJ#W4^{wF_$aWs33 zLo^@a;+_YJlpcSyK7>w`P7GKHACt)oZsN3g+k9T3NN2=8oF{?c?z{`Bt-Scx6r>LV zJ%wWTG98cekXl5={FGkhXvooVVm5x4{UUp)rR?w9KN%t~{tX2=aIKWD|2dI2wGPCG zcV(%@gRAiNu=m|Rn&!wS_;Jm={pVy!fl~>CqQ4(9;;ZD3M_)HdlY^F3yf*yMbl3r` z?H*qoNga~6dMbYRsgB6fhB3L}plrla0#tMSbC3Svn1`3XOb9D)MqGNIeu5}|VIABZ}p zIicA~q~YN(%kvR)3o}#Es(%Wb(O}tu(`$&oKSVhF1nGzLzh!m7gFKIw2I|D7M5}_# zJ2INkDo4I%Q_TL+AEJ5_k5uTIJe=lkA=q2}qDG$66T=8N-`LO5SLNAr0E)g@1~Oa_^TW|YJ7 zHsWdJcQ>D4(xNYr+cHi*{u!j)28vfi*xoOPWKdA!#MEw_eczR6yhKhu{gSn8wO-NM zk8qb(2Rgs~Cq!jk3?bCw=GW3upDP(i7lNIuz86C00U}IJe`$Rj#Be!M3JppSW2B4E z2M{as>6|AXYdme*1F6*A=lHg68Z78DwJ;=)9;oKCrTm|sS9n2+)B(%bW+=o{`?=L9e z*weey=DgrG>Up-4)fdo9eYV&a*F30gI67P>tKa5{XEpl~mkN;{v9!~UaHk84Cd1;Y zjVuu^kx~noVf5-Er^+}I0{wDTk&}zYiaRT#SEdzuZ}XeL?oAvZiGNt}#C+&>$gx9@ zO$vUO(5DM}*Za3nRA^Y}BQu}LniGBW2q;P!lc-wEMM2}xkIKIbJJuyhRgtwH4+*b8 zxzmIGRnm(YZe&jy&4yOwQJJLaMj%hko_+X!4)*J+++d<%eLc>E7uDPEpzDDbtsv3- z8$H!h(&RjgKnKj>7He=V)3`0_2XXOmru&@1^052?6+U2jp-JOwM zV5;EBKeI*NQ=ab--4VM{?}%B&w&O{L8@Mve z=1rX;9JWjAo3qtb0a4@<;ZYR76))0bOC-L?eAyl6_e6RdqT*3UwU0t3GWIA41O-MZ z{WScDw;=d?E>My-X({UK%LUySX>!&clpNaIHZ}Qb?A4U$5nj$xxFJp=Ya!3ttu_Z({Qfn&9&MgxY@xF}RV-R4x?d-;1Yvk31tiC7QgSJJE^_iZO0jBDEwLXs>b_=cdl(G#i9y*j5>s6=Ln zRVMX4R@EN1fFhnz{@E3i_Q%y9Iw-2swA(vs+Jg(E{@(a2289KmPc1M-u2m&CLTggW zq<(!9s%s%!NIz7Wv(w#s<27d%IUIZ)A%v84`;b>raoVBBZE zNZH{VDsKm0^Jy`@D!8gPmn=g0*>2Tn;rQ{Ub5c7==lSBL$j9>jO`O!ofb4>iVBQ(uf^6dsS)471L96yL%obsLB*%Pi@Y*zZ zK%@btlfrqXfjz2-!S1;2>C`Vlkx(N67!W{s_%n7}}J8iRv*h zgQAZgKP^XBK*F@_txkz5j?Cm61a)af((@18X+;Eq(3asmsr#zi$=tFB#4h#)H1V!} zGx<3A5w)ezZPqoJ_XsF#0#OR=(i1M?RlTSB`N$gcYqVun`Ua!58+!cPIv6X{If|H{ zmvqV0YrmKN%t2D?6Qr7g011=cHIDXaIn&Ak0Zr<9*2gHx?^hhP3z7}Sw@xh!DU4=6 zuB+7JZ zsuL}CWz(fkgI#fsmc|Ycl`RZa$}a#eX$9HZ#fy+v-mP2Bfz+OL#Qo$jUtm+LOnIih z@VF)HB3?uO0&JnYiY6Au^tl)?SDQrX0~&!aDkC(B&zWu|`s7g!>W!lv3((cCN^=V5 zNd9@}a7x+RjQ$pedo!bGk!t2Sn$^4)fFFlWK>By$_~6yBv_xh&AZ5woj|`6af=)Mf z1sK~Omq&ZGU(;x&HF~je7Ip0yPU%(wmPiZeoQ^XYYd1(VS2J3mQd1&~;WZ-*L~<1Zcl44ah_b!?v7|fy(R6>^461Lwmc?1< z`~0MY_X>aEIX9|$S(x2h;t2iMBn6I}M=)^Fijk9++r+Zs4VshOaf1S@H2&KSRF>S( zlvXfu9ZIoH&)k%7cEfEMC&EV#Thmi?M(B%j4#8v8B(Xz zNh+l=U%Q!*Am;vjmlbuzut#&zIe)8&vMO1RxGA8Cu_#ejiwL)~keI?%#{aoUf>(F@ zF?ygjG~s9Ln@DgIXNFwKraMW=*BFICifu<7(n$)EcUqGAg_G>MJCpYCO+eCNRq-4w z6Nopq-5c7lnIivXPqw?LqTv7l0H5JRpOR-9vI+8g`-}Ie067pOw7HG3SnjT!s05XT z0@r3!@f+SHdG_6#0T`;OFfte9?O|WBScn0H@ICSgeZ!J5HUeHD(I+hv^kVr_P8iS! z-Poo-Cv@$ReP6TAmFk?b%>*?uKqzxemzUu@ORY&j<^(-?o=!xL)FjGnPr;~WJvpH` zD-@!#8_Ex#r~>R-(tPH_{_q=+AX%v|uJ)&82Pm>Ssftlu2CKNR^dX&Nb2Aycu>B&N zlsq{NJdz-p|2-)(;MfEvVk`HzOg~yu1!7ieiq2*1n7d`_6Sij}FALmBoC+Y4u@U6F zB}N6Fah};NTRb>@rNZp>t>odq>OSlY*_BCrRsu5Lye>eRVHLLzdlvfqS~fgtDwcst ztP4TsYuxD(?yE*fA4h5^OAeEh0)l^0+Tg(qVb2e< z&fN%=W(-)qa3F^0OKnOV(Tz6>gs-zIDQ;qeVX@wcxhow|kU-{YGNa4Zx7eAsg zR&yIUX6pEgUtdmWsed(DmgwCJYIc`%N(MI(^y#D&p&E0tv>F16fJ2L6Oj$NxRG5t?_xHjF_)Y=T2u{;kFkdH87uiA2{`9!8OE!BcHv{D31uiB6%n2R)> zz}JJnEIh?Sn{#{vzb5;|N`|?A7a_M*-BeGH^9+3`G9%P`xwh9u$qrr}JBq-4sP;s}$5CYiG6bF@0meRymd1=@)}F!bI{su*>|CgUe$|Qq?Lt zQ7Ogt1M8Dhqc?@0ssL!am-MIN_L0;O@R(S=}6^hx%P=?9LgtLXx$;R8i#Ru#%axk1W-n(-Vg=(4TBNybd|dl*7B>^ zyYoDo`^#{Q#)}I^2=OO2pp$Jey`;Ub+1u{d7?&Sv*fw4&2ic0Zv-;~bEF|UGMZRC` z35=f##SA%$^8S?W5Dqa()#F&OfIK<&y)4XuGq2^>gQ8nJ5WWOA} zu->@l;BQ*mwDy2cvH7%#fIa9*D_6wXQWX3^S}|?M#dXMpQt~IDz;4VMDxC8cvy|>~N@{)- z`I?3JbNM)^jw}Z0_1223Dj%>~zlh|Zb;lf}@h{sK(A*?L>y!WB*yj4NeO%U&7E=p~ zI*@V765pMA)yO<=nQckr_1ZUOXq7${;ax$<&+MN`M3$)z?v;3jH|-`mb$?d5K{(OgkjuS1cjdx3yei@gaj;YQ3H6mBb z(8W;Js&?+87LK_ABht4F2;JrntuS2%wT}&Lv$2j+f4Xi4ezop z+0tvG__~;@D7f$Lj9d3HYAnBcMvtYOq78Dz>9uYwwO&&`9#%knaSb4&iwY&3vA!-% zUCQtaOsZHf4lc;W+CP$dJ~!d8eEXG}kJnicLM}!kSg?Dw zIAUjrM8p_<7m4Wg4boCUi}DiEu?+Dq52Tjz({As8o&vgv9{I7E{W5CTIUX=ykCbTi@zkcC)s91y5SD6diTewb14_3_2^_Q8UA=O{A1jyQi93$Bb@)Z!R3Xvtdcn;GUYCM`@4 z?Yr=NKPwRi!uy=bNJI53&R8q0e#`;BdC$Fmnp1dHrcfG>d=w+mWkGZKv55%^gH;vB zO7YV*Ft>fQM%2;k&vW>E&3xqwxk~e95;MYE{FPk|xp)6&xN+Xdn%y@%bVW>foGM}Ww8TB5(Ouc)yk09(ynvMg1YUV;?_ zW=A&IZ0ICw?d>E&?B?n~HCp?~jQ9P7`4`YWxqH2;i;sx8()50(YZpmn<$3Apx&tEA zo+-dD>Wm?gxWpmJ23Uv|q2&MZifzk-Yf(>a(`o+e0&mJMd0NnL+8K zVH9Imk`Q)6{*A~fg;g`PAF;Y$f?$knnLkUVB)NnGr5R67Wf#x<7#3-ry=HkwJ5^
      @TC!IC6@~-#1I9p`Q+RVQ z&_Q&?RV{tRzW>Yd2pFVj1(NnxnrbV4kP=O+UHMcGP7b%xUShiah?rqyhvKW=z)Kwa z=Ma3WS23z7c!GXX3Ogm`1DRW9ThX0}o!8HL0B}K<2Je_*V6|Oa&xjnx z#FDkCi~CS{CX3Qy$ger++`0w_=`ez>^_XXZd*reX2WhKJkQFaClpnhTN(REhFEbE0HLWkx$HL_n65i zo&*Ky{4$bFqdQ{feoYv@Y5RyRv=y~2*uLn?l+P4^h0@qAiaUcLiXH2Gi6{}F@FiDj za9jkQ$i1Ep{1wx8oLo!6Q41EOL_@KMV8FsjK+VrC%pJSC1vT}bd@~{VR}2nB$;|h> zmZq1c!6J+UUg>UTql!iA;aMC7yO-c9cZNQ_i(aDA3E3~`i$(NH_7ehhfMu*8rYC{W z*PoBx(oH~#=4=V*2y{Ci=>@>ptEo?AHXojgV*tw)rZ;2?wJS3QR$RKSB3Zl_E;X?T z{uB32=SaU1NAaj4p1x#oqFHW)EdYdPrPWY!WS2Ic>WcKbY2&-hjR!u}F1FwR=06r4 zpq+r`{Gj%5@BcN24S$>Gv?~SFfK7gY7@<1=bf_$y5=FmfJ{7LUfcn5g{znDhHp)x- zzrt?(&V)7pSA0thZff%Ke?&O;v3r(2H47sk5Dz^4Mu6&nA9#p83Fo=oo;``cW&zWa z{L1rxU&z{MuGIc}m_%6yEeCi6J#`K3S`f}@VgeU>Vo&o-7tU(p_Fu6-h)u+P9sgsD z%jCr>rqdJa=H77N&LVJ=`$?E1=x`VKtN*(C${&sk5C7i-4V)DEh~SGf@E)l3B_leZ zCvRx{i8ZQq6}Y^~@PCxIh2S_QJO4c}>SOIf|JS6qHrDPBsLMa`T4JAI*PxjHYaijn z3V!?lTj>cfMl=jG`!}{D7SI*we*|Xq>ydZtpE?v(|BuFdF=yr8?ExgLfTKyA0a(-? zsR%+N6F(VQx=g#b z|G*ON{b)~^DqftSo_PH_nB+Fq>nDS|LW}E%-9e6sqX@sk(1&1&!Wa$iFL2YaRR$?V zK}%ppQQ7aIZIOv5vxZmTd{dn< zQ7#R|O{X7(d`K!doMR5OuvqRP76e_{{i@-C~rPFm?e3gy+I3iA==bdJl zDRXZYusP^5IK5(fZHoGu`foB1MT%XZa_785j~Utfm6o>`AoxV*W=clkZ%R^xokH__ z0h8w^Bq!;gpJ=HCRguS_;e!4$DSP(6bH25ZD};;@#yWDyVW@kE2RjhxBj^*V67-j1 zGHW|a4hIgX%bcS-)0}7;(mUYEs%vIbEqGISLfX*D;u7Zw(rX(7y+J>$Ek9);>!K$G z1_xeRcB`>&3aqev{Y2#XW!ZdSN+^X~!STa6rwJb4%zRJkl0_HpG8Sym$2VwHg>SPP zWT4XYw+c;F+)+Vt>YKIpE(f2yrKKuV>2t9u#F63DW>cVN_f>0k1Ww4Goqwvk+VJLU$!KW4bTte zZ(}7c<)aTb$379(QD2cR(*PRQK~G4`r^_l9| zA(pM^s`pehPx`qso|7+wH)(6H#sW%gZED}~{6n*wL%4!}4znkblLUMzd$n~(=kD0ATjG!}|>qMgd$j|h!dTD55*Bc{tD<7k&Dwq*vCAr@Jv@@;L= z$ri9o%o0;S*C8bHor{~U^n5#_gFImtFlK5;L789yp}(5ENZ!Vj`z8< zg?^+ky+j4Fj^7l=c>-Mr_55xh4sXz~;?~yki z)$(Jeh(OLwqUTNi#WA<4!%W(HcbwX{w(NXhH!e297wkH~#`N}`cLAZ7&#CKk6g=6o zotNXzaDA2u77zXVVdwzsa{k-MY>lpB9AYuYzd9fTl*2UVp5ThMp7#Tej5*(5u&`GC z>NZB;9Q?7Q!h{%4(D#)uawF7&0aMEqj( zbVgBDqVt*!gY8h6G*<>G5+|onm^9bJ5QY($W z8#F}HJURsp()0BB)Z@mJ^8h#GcP(-86ERBoEECa^V>r+WX?mVUcqdaCe=N#Fz-(>+ zIIl3k;u{_2!7J#t{KN6%czbU6c-aEex$6?CYV;N}YSKg*;4OOh#)%2(G12+j3+@5U z4YUgEW0|19jzMTab4UBDs4C-qE4qyABl-7OHUR0YM$6H3uocWHN-R|V^P`i@n?7Ud zF+{!=L>Ub{paqx^k4GjN~ITsNcfUl6Y6+3^^?sM=VS32z zEjf2n(Cs|pb2-1HUsM=F`|*iWS8HPt|75oC5V(uuL%!HGwO^qcgj$fK^WgCK(t`Q) z{-s)ovP1c(u}>Ennbx<>{)_z1w1afyF46UF$I_dq4J4b`iERz*QiH$3k39B(G5R*H ze9L`Yg4SN>qm~2M&9e81S{BQc{bDcVw!J3xAYQHQ;dQ0wvhoZsh6pWylGh6-)Bnb~ z#nip}0$2rG4|46y%T3xz$QRODM%S(0PAM-KR@S_tS|JO<>-9Zvw$0uZ-Xc)&<3DBw!!IR~)#vA_+*!n{_cdymcz5=*m7E4;gl>IF&e7X#?`Y z%o)bmBK7$a9o^UxpAmA3rp{V7YvPGR*Q~mO+{tlqNdZfpaUs=u-jBMv{;CN)qY}Q7 z;hd?*ZhhLC zJmvoU&OkP&+`vVjWIHanqBYKh%>^F&8~;dOCG6m7`A>|W^0nv98td#r>E=~ zW6QIf3Sb8#h;dTS8Pq_yijf-Sy|KaLqdl?#W)m{o`0W$Zs~@ccuTOV{4`uf*BTu0h z&{gO=2$>nZIONuE&O{?ET{n(~0B4GZK6V0-VfiRY~*c; zY1um`yH9m2Fpf8BK^ewA(SJLFpIS}KOB$zJd3)LsGTg%9l35fw)kW-P-&C|yC%>;T z&Do8ZZx}6;p-asyJ>ukec&k@Q+nRJcF?-lX3vEUEp;=wDS9iham^97HX+D{7PAO1C zEq}C1cz^vu|S2o$oA;m>o5nhn#CVOZ>qk3*JZH7r>(`z ziov2tdeTGG@8=h} zF^hbIVN$-L*Um%^oK>oR+2)J4k6`R*bsbn1`KuW)xbSQP^kAhtb9<=2kw;7JY|4+u z^)l`}=kk>&i^bBMm#ow`1c{&jd2Utj)^fa{mwb{P;jWaj82~f;HE_yTohs)O&+_2^ zZwZvmtX7H4n3zxlZXl#XTrqNq@uZt&yuJ!wa|S<}vP2nkWuzOvu^vgxFjU zBNiD{3hR)+QECqg50(a00%x(TPce$)$5u+ePNfQ8iRL@pg(r$tf1*CQr;8`ORDx&l z)TBB=zSd?Q1iaodw8F^<@{dZyIF!ngBQ!8 zXS7H>yp1wzv^Py&-f*4$>Msd-RY)3mRjU=`@4S+E!P>Q(JJw#cl)mbUH_9|-t(~`D z0e=#UyS?0?ZsphHUEyUg;I-gof6s8jG(qt<()4?Ir5A&A-d@=wUB#XLa6R_bTdw$z zUI|NYysALmYTJbk!Q;~-VVKnHk{InJYGdofdal)r9TSe7_TX0Ud1t_i7d)K^?}N?) zq%yr2rFFd#-X&!6eR1WGvkOv_u?taNKzW|m9lLRm7)4c!Bw6(LEsZ{cv0R~uYv4N# z&^tBoG@k0EVbCmjHP?rZ_XC^IITn8^4c@)izZWaWO~1cr#0U2LW;Y-|?D6oOw}3OY zt*uSyO0(AlIzRFzS&)t5~q)=0{UU-J2d z;|ur&GwnP(704oHZw`ZIxg4S)WpQ#Bk#7sOZZ(?NYg6V%VNwo~Qni8NFKa#l#*?Z! z-3iGA^L%A*nAz+H3DxttZ$UUUE76hC&vpf=Vzt0Z@fl`*1GeXhJ z7vx>fH~4vlaA)L(9!9oOg@98B33zRF3CSR0?o&FWaQbrSTB9=-1W-gCFTO^OH$wwH zBc}7Ga}hT8?Mfdh|G_fL0B(??Qtyuuf1~!J#s1{z1d@wO-#Kks4zc^2SXw^cI{x`Q ztCdWM2QbIhRiuw3H!#MrwTFNU^~G_GEyvt7c-ItGzW-|(z!<+E`J-ay$A?m zcm^!LhQ=o>pG;O|+Xv6y{xH5h3-AT?1080iK)4H7?#rXls4ZVd+~lzf@hkHkVEaIr zm#LoO%*%Tqg;w|$|N6dOZNQGcN}N(@eKEHfIv|4zSU)4CED++MV}S>T6T!Bc&&?(G z8poY?B{hNwg#WA~Rck>wHRfxGzdTmOh~76o_$5(yT<-S{THq8Cf`=HdC%@9+eO?WGCRV^MWk?uspBxYwyuTfo*UKU~XU}xw0>#=ib7zPW1VE7PRVGRzB zTLO1bu2A>D_f5Y9NrZ^sxEQ_qW4EUZ7cIL7)TXXhdWid$nCu^=Dl|_F)pV-J*5@AW zR1?~BM?@qCFzQzU?CKv~YlVR;z%O-zdOvPN`UndwfMcVLr?ee2S=he1A!4WU>0BQs zTu-~G(J4X-IGFF}b2+ovjwCb{3wygEy)Go&Vm{F;aIZtL?fM{GA(qqB&D>q~UqmW`J1y zb%(AT27_gUB|Wn%qJP*9X??}17XrZj{@_R`HN z0c}CtM$yLa%Hd!VV`<6n3O2Wage%~$Mk^4mBAv6lWa6+83^6Ih=Z7vtQ;=FJl4BgI zB=4%AxtCrNh>e$x@~|*GKqo5x78HKe{b@ z{40Maw6qXdn_GSOl?E0aGOX(+Va&VC5807uY8-}DSpauhmW@bX3&Gx+pB@{wS;+O$ zgh?#AyUA}oIvAAc10tazu({Juk2xTF-F071+Kf0@98^5qRtA_KILy+snEhZzxsLq3 zOjc{JYP|yu&2vbGp1#JGr|9t-MF4;L(wwZMiO`~jAUHN8cF4#0a6XY`fn4*uSuF!Z z-aCqAPgm!=Z$^4EHb84gAwhJck$1vvY(Dw@e1u45@a!N5*N3ikxr1EPgjIplH(P2H z{iV31#YDltO!)vdKeA#YsFCAf6Pv^eGx!Gy&%T literal 0 HcmV?d00001 diff --git a/datumaro/docs/user_manual.md b/datumaro/docs/user_manual.md new file mode 100644 index 00000000000..65284fa1166 --- /dev/null +++ b/datumaro/docs/user_manual.md @@ -0,0 +1,563 @@ +# Quick start guide + +## Contents + +- [Installation](#installation) +- [Interfaces](#interfaces) +- [Supported dataset formats and annotations](#formats-support) +- [Command line workflow](#command-line-workflow) + - [Create a project](#create-project) + - [Add and remove data](#add-and-remove-data) + - [Import a project](#import-project) + - [Extract a subproject](#extract-subproject) + - [Merge projects](#merge-project) + - [Export a project](#export-project) + - [Compare projects](#compare-projects) + - [Get project info](#get-project-info) + - [Register a model](#register-model) + - [Run inference](#run-inference) + - [Run inference explanation](#explain-inference) +- [Links](#links) + +## Installation + +### Prerequisites + +- Python (3.5+) +- OpenVINO (optional) + +### Installation steps + +Optionally, set up a virtual environment: + +``` bash +python -m pip install virtualenv +python -m virtualenv venv +. venv/bin/activate +``` + +Install Datumaro: +``` bash +pip install 'git+https://github.com/opencv/cvat#egg=datumaro&subdirectory=datumaro' +``` + +> You can change the installation branch with `.../cvat@#egg...` +> Also note `--force-reinstall` parameter in this case. + +## Interfaces + +As a standalone tool: + +``` bash +datum --help +``` + +As a python module: +> The directory containing Datumaro should be in the `PYTHONPATH` +> environment variable or `cvat/datumaro/` should be the current directory. + +``` bash +python -m datumaro --help +python datumaro/ --help +python datum.py --help +``` + +As a python library: + +``` python +import datumaro +``` + +## Formats support + +List of supported formats: +- COCO (`image_info`, `instances`, `person_keypoints`, `captions`, `labels`*) + - [Format specification](http://cocodataset.org/#format-data) + - `labels` are our extension - like `instances` with only `category_id` +- PASCAL VOC (`classification`, `detection`, `segmentation` (class, instances), `action_classification`, `person_layout`) + - [Format specification](http://host.robots.ox.ac.uk/pascal/VOC/voc2012/htmldoc/index.html) +- YOLO (`bboxes`) + - [Format specification](https://github.com/AlexeyAB/darknet#how-to-train-pascal-voc-data) +- TF Detection API (`bboxes`, `masks`) + - Format specifications: [bboxes](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/using_your_own_dataset.md), [masks](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/instance_segmentation.md) +- CVAT + - [Format specification](https://github.com/opencv/cvat/blob/develop/cvat/apps/documentation/xml_format.md) + +List of supported annotation types: +- Labels +- Bounding boxes +- Polygons +- Polylines +- (Key-)Points +- Captions +- Masks + +## Command line workflow + +> **Note**: command invocation syntax is subject to change, +> **always refer to command --help output** + +The key object is the Project. The Project is a combination of +a Project's own dataset, a number of external data sources and an environment. +An empty Project can be created by `project create` command, +an existing dataset can be imported with `project import` command. +A typical way to obtain projects is to export tasks in CVAT UI. + +Available CLI commands: +![CLI design doc](images/cli_design.png) + +If you want to interact with models, you need to add them to project first. + +### Import project + +This command creates a Project from an existing dataset. + +Supported formats are listed in the command help. +In Datumaro dataset formats are supported by Extractors and Importers. +An Extractor produces a list of dataset items corresponding +to the dataset. An Importer creates a Project from the +data source location. It is possible to add a custom Extractor and Importer. +To do this, you need to put an Extractor and Importer implementation scripts to +`/.datumaro/extractors` and `/.datumaro/importers`. + +Usage: + +``` bash +datum project import --help + +datum project import \ + -i \ + -o \ + -f +``` + +Example: create a project from COCO-like dataset + +``` bash +datum project import \ + -i /home/coco_dir \ + -o /home/project_dir \ + -f coco +``` + +An _MS COCO_-like dataset should have the following directory structure: + + +``` +COCO/ +├── annotations/ +│   ├── instances_val2017.json +│   ├── instances_train2017.json +├── images/ +│   ├── val2017 +│   ├── train2017 +``` + + +Everything after the last `_` is considered a subset name in the COCO format. + +### Create project + +The command creates an empty project. Once a Project is created, there are +a few options to interact with it. + +Usage: + +``` bash +datum project create --help + +datum project create \ + -o +``` + +Example: create an empty project `my_dataset` + +``` bash +datum project create -o my_dataset/ +``` + +### Add and remove data + +A Project can be attached to a number of external Data Sources. Each Source +describes a way to produce dataset items. A Project combines dataset items from +all the sources and its own dataset into one composite dataset. You can manage +project sources by commands in the `source` command line context. + +Datasets come in a wide variety of formats. Each dataset +format defines its own data structure and rules on how to +interpret the data. For example, the following data structure +is used in COCO format: + +``` +/dataset/ +- /images/.jpg +- /annotations/ +``` + + +In Datumaro dataset formats are supported by Extractors. +An Extractor produces a list of dataset items corresponding +to the dataset. It is possible to add a custom Extractor. +To do this, you need to put an Extractor +definition script to `/.datumaro/extractors`. + +Usage: + +``` bash +datum source add --help +datum source remove --help + +datum source add \ + path \ + -p \ + -n + +datum source remove \ + -p \ + -n +``` + +Example: create a project from a bunch of different annotations and images, +and generate TFrecord for TF Detection API for model training + +``` bash +datum project create +# 'default' is the name of the subset below +datum source add path -f coco_instances +datum source add path -f cvat +datum source add path -f voc_detection +datum source add path -f datumaro +datum source add path -f image_dir +datum project export -f tf_detection_api +``` + +### Extract subproject + +This command allows to create a sub-Project from a Project. The new project +includes only items satisfying some condition. [XPath](https://devhints.io/xpath) +is used as query format. + +There are several filtering modes available ('-m/--mode' parameter). +Supported modes: +- 'i', 'items' +- 'a', 'annotations' +- 'i+a', 'a+i', 'items+annotations', 'annotations+items' + +When filtering annotations, use the 'items+annotations' +mode to point that annotation-less dataset items should be +removed. To select an annotation, write an XPath that +returns 'annotation' elements (see examples). + +Usage: + +``` bash +datum project extract --help + +datum project extract \ + -p \ + -o \ + -e '' +``` + +Example: extract a dataset with only images which width < height + +``` bash +datum project extract \ + -p test_project \ + -o test_project-extract \ + -e '/item[image/width < image/height]' +``` + +Example: extract a dataset with only large annotations of class `cat` and any non-`persons` + +``` bash +datum project extract \ + -p test_project \ + -o test_project-extract \ + --mode annotations -e '/item/annotation[(label="cat" and area > 999.5) or label!="person"]' +``` + +Example: extract a dataset with only occluded annotations, remove empty images + +``` bash +datum project extract \ + -p test_project \ + -o test_project-extract \ + -m i+a -e '/item/annotation[occluded="True"]' +``` + +Item representations are available with `--dry-run` parameter: + +``` xml + + 290768 + minival2014 + + 612 + 612 + 3 + + + 80154 + bbox + 39 + 264.59 + 150.25 + 11.199999999999989 + 42.31 + 473.87199999999956 + + + 669839 + bbox + 41 + 163.58 + 191.75 + 76.98999999999998 + 73.63 + 5668.773699999998 + + ... + +``` + +### Merge projects + +This command combines multiple Projects into one. + +Usage: + +``` bash +datum project merge --help + +datum project merge \ + -p \ + -o \ + +``` + +Example: update annotations in the `first_project` with annotations +from the `second_project` and save the result as `merged_project` + +``` bash +datum project merge \ + -p first_project \ + -o merged_project \ + second_project +``` + +### Export project + +This command exports a Project in some format. + +Supported formats are listed in the command help. +In Datumaro dataset formats are supported by Converters. +A Converter produces a dataset of a specific format +from dataset items. It is possible to add a custom Converter. +To do this, you need to put a Converter +definition script to /.datumaro/converters. + +Usage: + +``` bash +datum project export --help + +datum project export \ + -p \ + -o \ + -f \ + [-- ] +``` + +Example: save project as VOC-like dataset, include images + +``` bash +datum project export \ + -p test_project \ + -o test_project-export \ + -f voc \ + -- --save-images +``` + +### Get project info + +This command outputs project status information. + +Usage: + +``` bash +datum project info --help + +datum project info \ + -p +``` + +Example: + +``` bash +datum project info -p /test_project + +Project: + name: test_project2 + location: /test_project +Sources: + source 'instances_minival2014': + format: coco_instances + url: /coco_like/annotations/instances_minival2014.json +Dataset: + length: 5000 + categories: label + label: + count: 80 + labels: person, bicycle, car, motorcycle (and 76 more) + subsets: minival2014 + subset 'minival2014': + length: 5000 + categories: label + label: + count: 80 + labels: person, bicycle, car, motorcycle (and 76 more) +``` + +### Register model + +Supported models: +- OpenVINO +- Custom models via custom `launchers` + +Usage: + +``` bash +datum model add --help +``` + +Example: register an OpenVINO model + +A model consists of a graph description and weights. There is also a script +used to convert model outputs to internal data structures. + +``` bash +datum project create +datum model add \ + -n openvino \ + -d -w -i +``` + +Interpretation script for an OpenVINO detection model (`convert.py`): + +``` python +from datumaro.components.extractor import * + +max_det = 10 +conf_thresh = 0.1 + +def process_outputs(inputs, outputs): + # inputs = model input, array or images, shape = (N, C, H, W) + # outputs = model output, shape = (N, 1, K, 7) + # results = conversion result, [ [ Annotation, ... ], ... ] + results = [] + for input, output in zip(inputs, outputs): + input_height, input_width = input.shape[:2] + detections = output[0] + image_results = [] + for i, det in enumerate(detections): + label = int(det[1]) + conf = det[2] + if conf <= conf_thresh: + continue + + x = max(int(det[3] * input_width), 0) + y = max(int(det[4] * input_height), 0) + w = min(int(det[5] * input_width - x), input_width) + h = min(int(det[6] * input_height - y), input_height) + image_results.append(Bbox(x, y, w, h, + label=label, attributes={'score': conf} )) + + results.append(image_results[:max_det]) + + return results + +def get_categories(): + # Optionally, provide output categories - label map etc. + # Example: + label_categories = LabelCategories() + label_categories.add('person') + label_categories.add('car') + return { AnnotationType.label: label_categories } +``` + +### Run model + +This command applies model to dataset images and produces a new project. + +Usage: + +``` bash +datum model run --help + +datum model run \ + -p \ + -m \ + -o +``` + +Example: launch inference on a dataset + +``` bash +datum project import <...> +datum model add mymodel <...> +datum model run -m mymodel -o inference +``` + +### Compare projects + +The command compares two datasets and saves the results in the +specified directory. The current project is considered to be +"ground truth". + +``` bash +datum project diff --help + +datum project diff -o +``` + +Example: compare a dataset with model inference + +``` bash +datum project import <...> +datum model add mymodel <...> +datum project transform <...> -o inference +datum project diff inference -o diff +``` + +### Explain inference + +Usage: + +``` bash +datum explain --help + +datum explain \ + -m \ + -o \ + -t \ + \ + +``` + +Example: run inference explanation on a single image with visualization + +``` bash +datum project create <...> +datum model add mymodel <...> +datum explain \ + -m mymodel \ + -t 'image.png' \ + rise \ + -s 1000 --progressive +``` + +## Links +- [TensorFlow detection model zoo](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/detection_model_zoo.md) +- [How to convert model to OpenVINO format](https://docs.openvinotoolkit.org/latest/_docs_MO_DG_prepare_model_convert_model_tf_specific_Convert_Object_Detection_API_Models.html) +- [Model conversion script example](https://github.com/opencv/cvat/blob/3e09503ba6c6daa6469a6c4d275a5a8b168dfa2c/components/tf_annotation/install.sh#L23) diff --git a/datumaro/requirements.txt b/datumaro/requirements.txt new file mode 100644 index 00000000000..5d458bd04eb --- /dev/null +++ b/datumaro/requirements.txt @@ -0,0 +1,10 @@ +Cython>=0.27.3 # include before pycocotools +GitPython>=3.0.8 +lxml>=4.4.1 +matplotlib<3.1 # 3.1+ requires python3.6, but we have 3.5 in cvat +opencv-python-headless>=4.1.0.25 +Pillow>=6.1.0 +pycocotools>=2.0.0 +PyYAML>=5.1.1 +scikit-image>=0.15.0 +tensorboardX>=1.8 diff --git a/datumaro/setup.py b/datumaro/setup.py new file mode 100644 index 00000000000..c39f38d2093 --- /dev/null +++ b/datumaro/setup.py @@ -0,0 +1,71 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import os.path as osp +import re +import setuptools + + +def find_version(file_path=None): + if not file_path: + file_path = osp.join(osp.dirname(osp.abspath(__file__)), + 'datumaro', 'version.py') + + with open(file_path, 'r') as version_file: + version_text = version_file.read() + + # PEP440: + # https://www.python.org/dev/peps/pep-0440/#appendix-b-parsing-version-strings-with-regular-expressions + pep_regex = r'([1-9]\d*!)?(0|[1-9]\d*)(\.(0|[1-9]\d*))*((a|b|rc)(0|[1-9]\d*))?(\.post(0|[1-9]\d*))?(\.dev(0|[1-9]\d*))?' + version_regex = r'VERSION\s*=\s*.(' + pep_regex + ').' + match = re.match(version_regex, version_text) + if not match: + raise RuntimeError("Failed to find version string in '%s'" % file_path) + + version = version_text[match.start(1) : match.end(1)] + return version + + +with open('README.md', 'r') as fh: + long_description = fh.read() + +setuptools.setup( + name="datumaro", + version=find_version(), + author="Intel", + author_email="maxim.zhiltsov@intel.com", + description="Dataset Framework", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/opencv/cvat/datumaro", + packages=setuptools.find_packages(exclude=['tests*']), + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ], + python_requires='>=3.5', + install_requires=[ + 'GitPython', + 'lxml', + 'matplotlib', + 'numpy', + 'opencv-python', + 'Pillow', + 'PyYAML', + 'pycocotools', + 'scikit-image', + 'tensorboardX', + ], + extras_require={ + 'tf': ['tensorflow'], + 'tf-gpu': ['tensorflow-gpu'], + }, + entry_points={ + 'console_scripts': [ + 'datum=datumaro.cli.__main__:main', + ], + }, +) diff --git a/datumaro/tests/__init__.py b/datumaro/tests/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/datumaro/tests/test_RISE.py b/datumaro/tests/test_RISE.py new file mode 100644 index 00000000000..04772287f43 --- /dev/null +++ b/datumaro/tests/test_RISE.py @@ -0,0 +1,231 @@ +from collections import namedtuple +import numpy as np + +from unittest import TestCase + +from datumaro.components.extractor import Label, Bbox +from datumaro.components.launcher import Launcher +from datumaro.components.algorithms.rise import RISE + + +class RiseTest(TestCase): + def test_rise_can_be_applied_to_classification_model(self): + class TestLauncher(Launcher): + def __init__(self, class_count, roi, **kwargs): + self.class_count = class_count + self.roi = roi + + def launch(self, inputs): + for inp in inputs: + yield self._process(inp) + + def _process(self, image): + roi = self.roi + roi_area = (roi[1] - roi[0]) * (roi[3] - roi[2]) + if 0.5 * roi_area < np.sum(image[roi[0]:roi[1], roi[2]:roi[3], 0]): + cls = 0 + else: + cls = 1 + + cls_conf = 0.5 + other_conf = (1.0 - cls_conf) / (self.class_count - 1) + + return [ + Label(i, attributes={ + 'score': cls_conf if cls == i else other_conf }) \ + for i in range(self.class_count) + ] + + roi = [70, 90, 7, 90] + model = TestLauncher(class_count=3, roi=roi) + + rise = RISE(model, max_samples=(7 * 7) ** 2, mask_width=7, mask_height=7) + + image = np.ones((100, 100, 3)) + heatmaps = next(rise.apply(image)) + + self.assertEqual(1, len(heatmaps)) + + heatmap = heatmaps[0] + self.assertEqual(image.shape[:2], heatmap.shape) + + h_sum = np.sum(heatmap) + h_area = np.prod(heatmap.shape) + roi_sum = np.sum(heatmap[roi[0]:roi[1], roi[2]:roi[3]]) + roi_area = (roi[1] - roi[0]) * (roi[3] - roi[2]) + roi_den = roi_sum / roi_area + hrest_den = (h_sum - roi_sum) / (h_area - roi_area) + self.assertLess(hrest_den, roi_den) + + def test_rise_can_be_applied_to_detection_model(self): + ROI = namedtuple('ROI', + ['threshold', 'x', 'y', 'w', 'h', 'label']) + + class TestLauncher(Launcher): + def __init__(self, rois, class_count, fp_count=4, pixel_jitter=20, **kwargs): + self.rois = rois + self.roi_base_sums = [None, ] * len(rois) + self.class_count = class_count + self.fp_count = fp_count + self.pixel_jitter = pixel_jitter + + @staticmethod + def roi_value(roi, image): + return np.sum( + image[roi.y:roi.y + roi.h, roi.x:roi.x + roi.w, :]) + + def launch(self, inputs): + for inp in inputs: + yield self._process(inp) + + def _process(self, image): + detections = [] + for i, roi in enumerate(self.rois): + roi_sum = self.roi_value(roi, image) + roi_base_sum = self.roi_base_sums[i] + first_run = roi_base_sum is None + if first_run: + roi_base_sum = roi_sum + self.roi_base_sums[i] = roi_base_sum + + cls_conf = roi_sum / roi_base_sum + + if roi.threshold < roi_sum / roi_base_sum: + cls = roi.label + detections.append( + Bbox(roi.x, roi.y, roi.w, roi.h, + label=cls, attributes={'score': cls_conf}) + ) + + if first_run: + continue + for j in range(self.fp_count): + if roi.threshold < cls_conf: + cls = roi.label + else: + cls = (i + j) % self.class_count + box = [roi.x, roi.y, roi.w, roi.h] + offset = (np.random.rand(4) - 0.5) * self.pixel_jitter + detections.append( + Bbox(*(box + offset), + label=cls, attributes={'score': cls_conf}) + ) + + return detections + + rois = [ + ROI(0.3, 10, 40, 30, 10, 0), + ROI(0.5, 70, 90, 7, 10, 0), + ROI(0.7, 5, 20, 40, 60, 2), + ROI(0.9, 30, 20, 10, 40, 1), + ] + model = model = TestLauncher(class_count=3, rois=rois) + + rise = RISE(model, max_samples=(7 * 7) ** 2, mask_width=7, mask_height=7) + + image = np.ones((100, 100, 3)) + heatmaps = next(rise.apply(image)) + heatmaps_class_count = len(set([roi.label for roi in rois])) + self.assertEqual(heatmaps_class_count + len(rois), len(heatmaps)) + + # import cv2 + # roi_image = image.copy() + # for i, roi in enumerate(rois): + # cv2.rectangle(roi_image, (roi.x, roi.y), (roi.x + roi.w, roi.y + roi.h), (32 * i) * 3) + # cv2.imshow('img', roi_image) + + for c in range(heatmaps_class_count): + class_roi = np.zeros(image.shape[:2]) + for i, roi in enumerate(rois): + if roi.label != c: + continue + class_roi[roi.y:roi.y + roi.h, roi.x:roi.x + roi.w] \ + += roi.threshold + + heatmap = heatmaps[c] + + roi_pixels = heatmap[class_roi != 0] + h_sum = np.sum(roi_pixels) + h_area = np.sum(roi_pixels != 0) + h_den = h_sum / h_area + + rest_pixels = heatmap[class_roi == 0] + r_sum = np.sum(rest_pixels) + r_area = np.sum(rest_pixels != 0) + r_den = r_sum / r_area + + # print(r_den, h_den) + # cv2.imshow('class %s' % c, heatmap) + self.assertLess(r_den, h_den) + + for i, roi in enumerate(rois): + heatmap = heatmaps[heatmaps_class_count + i] + h_sum = np.sum(heatmap) + h_area = np.prod(heatmap.shape) + roi_sum = np.sum(heatmap[roi.y:roi.y + roi.h, roi.x:roi.x + roi.w]) + roi_area = roi.h * roi.w + roi_den = roi_sum / roi_area + hrest_den = (h_sum - roi_sum) / (h_area - roi_area) + # print(hrest_den, h_den) + # cv2.imshow('roi %s' % i, heatmap) + self.assertLess(hrest_den, roi_den) + # cv2.waitKey(0) + + @staticmethod + def DISABLED_test_roi_nms(): + ROI = namedtuple('ROI', + ['conf', 'x', 'y', 'w', 'h', 'label']) + + class_count = 3 + noisy_count = 3 + rois = [ + ROI(0.3, 10, 40, 30, 10, 0), + ROI(0.5, 70, 90, 7, 10, 0), + ROI(0.7, 5, 20, 40, 60, 2), + ROI(0.9, 30, 20, 10, 40, 1), + ] + pixel_jitter = 10 + + detections = [] + for i, roi in enumerate(rois): + detections.append( + Bbox(roi.x, roi.y, roi.w, roi.h, + label=roi.label, attributes={'score': roi.conf}) + ) + + for j in range(noisy_count): + cls_conf = roi.conf * j / noisy_count + cls = (i + j) % class_count + box = [roi.x, roi.y, roi.w, roi.h] + offset = (np.random.rand(4) - 0.5) * pixel_jitter + detections.append( + Bbox(*(box + offset), + label=cls, attributes={'score': cls_conf}) + ) + + import cv2 + image = np.zeros((100, 100, 3)) + for i, det in enumerate(detections): + roi = ROI(det.attributes['score'], *det.get_bbox(), det.label) + p1 = (int(roi.x), int(roi.y)) + p2 = (int(roi.x + roi.w), int(roi.y + roi.h)) + c = (0, 1 * (i % (1 + noisy_count) == 0), 1) + cv2.rectangle(image, p1, p2, c) + cv2.putText(image, 'd%s-%s-%.2f' % (i, roi.label, roi.conf), + p1, cv2.FONT_HERSHEY_SIMPLEX, 0.25, c) + cv2.imshow('nms_image', image) + cv2.waitKey(0) + + nms_boxes = RISE.nms(detections, iou_thresh=0.25) + print(len(detections), len(nms_boxes)) + + for i, det in enumerate(nms_boxes): + roi = ROI(det.attributes['score'], *det.get_bbox(), det.label) + p1 = (int(roi.x), int(roi.y)) + p2 = (int(roi.x + roi.w), int(roi.y + roi.h)) + c = (0, 1, 0) + cv2.rectangle(image, p1, p2, c) + cv2.putText(image, 'p%s-%s-%.2f' % (i, roi.label, roi.conf), + p1, cv2.FONT_HERSHEY_SIMPLEX, 0.25, c) + cv2.imshow('nms_image', image) + cv2.waitKey(0) \ No newline at end of file diff --git a/datumaro/tests/test_coco_format.py b/datumaro/tests/test_coco_format.py new file mode 100644 index 00000000000..2caa03a7c09 --- /dev/null +++ b/datumaro/tests/test_coco_format.py @@ -0,0 +1,648 @@ +import json +import numpy as np +import os +import os.path as osp + +from unittest import TestCase + +from datumaro.components.project import Project +from datumaro.components.extractor import (Extractor, DatasetItem, + AnnotationType, Label, Mask, Points, Polygon, Bbox, Caption, + LabelCategories, PointsCategories +) +from datumaro.plugins.coco_format.converter import ( + CocoConverter, + CocoImageInfoConverter, + CocoCaptionsConverter, + CocoInstancesConverter, + CocoPersonKeypointsConverter, + CocoLabelsConverter, +) +from datumaro.plugins.coco_format.importer import CocoImporter +from datumaro.util.image import save_image, Image +from datumaro.util.test_utils import TestDir, compare_datasets + + +class CocoImporterTest(TestCase): + @staticmethod + def generate_annotation(): + annotation = { + 'licenses': [], + 'info': {}, + 'categories': [], + 'images': [], + 'annotations': [], + } + annotation['licenses'].append({ + 'name': '', + 'id': 0, + 'url': '', + }) + annotation['info'] = { + 'contributor': '', + 'date_created': '', + 'description': '', + 'url': '', + 'version': '', + 'year': '', + } + annotation['licenses'].append({ + 'name': '', + 'id': 0, + 'url': '', + }) + annotation['categories'].append({ + 'id': 1, + 'name': 'TEST', + 'supercategory': '', + }) + annotation['images'].append({ + "id": 1, + "width": 5, + "height": 10, + "file_name": '000000000001.jpg', + "license": 0, + "flickr_url": '', + "coco_url": '', + "date_captured": 0, + }) + annotation['annotations'].append({ + "id": 1, + "image_id": 1, + "category_id": 1, + "segmentation": [[0, 0, 1, 0, 1, 2, 0, 2]], + "area": 2, + "bbox": [0, 0, 1, 2], + "iscrowd": 0, + }) + annotation['annotations'].append({ + "id": 2, + "image_id": 1, + "category_id": 1, + "segmentation": { + "counts": [ + 0, 10, + 5, 5, + 5, 5, + 0, 10, + 10, 0], + "size": [10, 5]}, + "area": 30, + "bbox": [0, 0, 10, 4], + "iscrowd": 1, + }) + return annotation + + def COCO_dataset_generate(self, path): + img_dir = osp.join(path, 'images', 'val') + ann_dir = osp.join(path, 'annotations') + os.makedirs(img_dir) + os.makedirs(ann_dir) + + image = np.ones((10, 5, 3)) + save_image(osp.join(img_dir, '000000000001.jpg'), image) + + annotation = self.generate_annotation() + + with open(osp.join(ann_dir, 'instances_val.json'), 'w') as outfile: + json.dump(annotation, outfile) + + def test_can_import(self): + class DstExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, image=np.ones((10, 5, 3)), subset='val', + annotations=[ + Polygon([0, 0, 1, 0, 1, 2, 0, 2], label=0, + id=1, group=1, attributes={'is_crowd': False}), + Mask(np.array( + [[1, 0, 0, 1, 0]] * 5 + + [[1, 1, 1, 1, 0]] * 5 + ), label=0, + id=2, group=2, attributes={'is_crowd': True}), + ] + ), + ]) + + def categories(self): + label_cat = LabelCategories() + label_cat.add('TEST') + return { AnnotationType.label: label_cat } + + with TestDir() as test_dir: + self.COCO_dataset_generate(test_dir) + + dataset = Project.import_from(test_dir, 'coco').make_dataset() + + compare_datasets(self, DstExtractor(), dataset) + +class CocoConverterTest(TestCase): + def _test_save_and_load(self, source_dataset, converter, test_dir, + target_dataset=None, importer_args=None): + converter(source_dataset, test_dir) + + if importer_args is None: + importer_args = {} + parsed_dataset = CocoImporter()(test_dir, **importer_args).make_dataset() + + if target_dataset is None: + target_dataset = source_dataset + + compare_datasets(self, expected=target_dataset, actual=parsed_dataset) + + def test_can_save_and_load_captions(self): + class TestExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, subset='train', + annotations=[ + Caption('hello', id=1, group=1), + Caption('world', id=2, group=2), + ]), + DatasetItem(id=2, subset='train', + annotations=[ + Caption('test', id=3, group=3), + ]), + + DatasetItem(id=3, subset='val', + annotations=[ + Caption('word', id=1, group=1), + ] + ), + ]) + + with TestDir() as test_dir: + self._test_save_and_load(TestExtractor(), + CocoCaptionsConverter(), test_dir) + + def test_can_save_and_load_instances(self): + label_categories = LabelCategories() + for i in range(10): + label_categories.add(str(i)) + categories = { AnnotationType.label: label_categories } + + class TestExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, subset='train', image=np.ones((4, 4, 3)), + annotations=[ + # Bbox + single polygon + Bbox(0, 1, 2, 2, + label=2, group=1, id=1, + attributes={ 'is_crowd': False }), + Polygon([0, 1, 2, 1, 2, 3, 0, 3], + attributes={ 'is_crowd': False }, + label=2, group=1, id=1), + ]), + DatasetItem(id=2, subset='train', image=np.ones((4, 4, 3)), + annotations=[ + # Mask + bbox + Mask(np.array([ + [0, 1, 0, 0], + [0, 1, 0, 0], + [0, 1, 1, 1], + [0, 0, 0, 0]], + ), + attributes={ 'is_crowd': True }, + label=4, group=3, id=3), + Bbox(1, 0, 2, 2, label=4, group=3, id=3, + attributes={ 'is_crowd': True }), + ]), + + DatasetItem(id=3, subset='val', image=np.ones((4, 4, 3)), + annotations=[ + # Bbox + mask + Bbox(0, 1, 2, 2, label=4, group=3, id=3, + attributes={ 'is_crowd': True }), + Mask(np.array([ + [0, 0, 0, 0], + [1, 1, 1, 0], + [1, 1, 0, 0], + [0, 0, 0, 0]], + ), + attributes={ 'is_crowd': True }, + label=4, group=3, id=3), + ]), + ]) + + def categories(self): + return categories + + class DstExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, subset='train', image=np.ones((4, 4, 3)), + annotations=[ + Polygon([0, 1, 2, 1, 2, 3, 0, 3], + attributes={ 'is_crowd': False }, + label=2, group=1, id=1), + ]), + DatasetItem(id=2, subset='train', image=np.ones((4, 4, 3)), + annotations=[ + Mask(np.array([ + [0, 1, 0, 0], + [0, 1, 0, 0], + [0, 1, 1, 1], + [0, 0, 0, 0]], + ), + attributes={ 'is_crowd': True }, + label=4, group=3, id=3), + ]), + + DatasetItem(id=3, subset='val', image=np.ones((4, 4, 3)), + annotations=[ + Mask(np.array([ + [0, 0, 0, 0], + [1, 1, 1, 0], + [1, 1, 0, 0], + [0, 0, 0, 0]], + ), + attributes={ 'is_crowd': True }, + label=4, group=3, id=3), + ]), + ]) + + def categories(self): + return categories + + with TestDir() as test_dir: + self._test_save_and_load(TestExtractor(), + CocoInstancesConverter(), test_dir, + target_dataset=DstExtractor()) + + def test_can_merge_polygons_on_loading(self): + label_categories = LabelCategories() + for i in range(10): + label_categories.add(str(i)) + categories = { AnnotationType.label: label_categories } + + class TestExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, image=np.zeros((6, 10, 3)), + annotations=[ + Polygon([0, 0, 4, 0, 4, 4], + label=3, id=4, group=4), + Polygon([5, 0, 9, 0, 5, 5], + label=3, id=4, group=4), + ] + ), + ]) + + def categories(self): + return categories + + class TargetExtractor(TestExtractor): + def __iter__(self): + items = list(super().__iter__()) + items[0]._annotations = [ + Mask(np.array([ + [0, 1, 1, 1, 0, 1, 1, 1, 1, 0], + [0, 0, 1, 1, 0, 1, 1, 1, 0, 0], + [0, 0, 0, 1, 0, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], + # only internal fragment (without the border), + # but not everywhere... + ), + label=3, id=4, group=4, + attributes={ 'is_crowd': False }), + ] + return iter(items) + + with TestDir() as test_dir: + self._test_save_and_load(TestExtractor(), + CocoInstancesConverter(), test_dir, + importer_args={'merge_instance_polygons': True}, + target_dataset=TargetExtractor()) + + def test_can_crop_covered_segments(self): + label_categories = LabelCategories() + for i in range(10): + label_categories.add(str(i)) + + class SrcTestExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, image=np.zeros((5, 5, 3)), + annotations=[ + Mask(np.array([ + [0, 0, 1, 1, 1], + [0, 0, 1, 1, 1], + [1, 1, 0, 1, 1], + [1, 1, 1, 0, 0], + [1, 1, 1, 0, 0]], + ), + label=2, id=1, z_order=0), + Polygon([1, 1, 4, 1, 4, 4, 1, 4], + label=1, id=2, z_order=1), + ] + ), + ]) + + def categories(self): + return { AnnotationType.label: label_categories } + + class DstTestExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, image=np.zeros((5, 5, 3)), + annotations=[ + Mask(np.array([ + [0, 0, 1, 1, 1], + [0, 0, 0, 0, 1], + [1, 0, 0, 0, 1], + [1, 0, 0, 0, 0], + [1, 1, 1, 0, 0]], + ), + attributes={ 'is_crowd': True }, + label=2, id=1, group=1), + + Polygon([1, 1, 4, 1, 4, 4, 1, 4], + label=1, id=2, group=2, + attributes={ 'is_crowd': False }), + ] + ), + ]) + + def categories(self): + return { AnnotationType.label: label_categories } + + with TestDir() as test_dir: + self._test_save_and_load(SrcTestExtractor(), + CocoInstancesConverter(crop_covered=True), test_dir, + target_dataset=DstTestExtractor()) + + def test_can_convert_polygons_to_mask(self): + label_categories = LabelCategories() + for i in range(10): + label_categories.add(str(i)) + + class SrcTestExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, image=np.zeros((6, 10, 3)), + annotations=[ + Polygon([0, 0, 4, 0, 4, 4], + label=3, id=4, group=4), + Polygon([5, 0, 9, 0, 5, 5], + label=3, id=4, group=4), + ] + ), + ]) + + def categories(self): + return { AnnotationType.label: label_categories } + + class DstTestExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, image=np.zeros((6, 10, 3)), + annotations=[ + Mask(np.array([ + [0, 1, 1, 1, 0, 1, 1, 1, 1, 0], + [0, 0, 1, 1, 0, 1, 1, 1, 0, 0], + [0, 0, 0, 1, 0, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], + # only internal fragment (without the border), + # but not everywhere... + ), + attributes={ 'is_crowd': True }, + label=3, id=4, group=4), + ] + ), + ]) + + def categories(self): + return { AnnotationType.label: label_categories } + + with TestDir() as test_dir: + self._test_save_and_load(SrcTestExtractor(), + CocoInstancesConverter(segmentation_mode='mask'), test_dir, + target_dataset=DstTestExtractor()) + + def test_can_convert_masks_to_polygons(self): + label_categories = LabelCategories() + for i in range(10): + label_categories.add(str(i)) + + class SrcTestExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, image=np.zeros((5, 10, 3)), + annotations=[ + Mask(np.array([ + [0, 1, 1, 1, 0, 1, 1, 1, 1, 0], + [0, 0, 1, 1, 0, 1, 1, 1, 0, 0], + [0, 0, 0, 1, 0, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ]), + label=3, id=4, group=4), + ] + ), + ]) + + def categories(self): + return { AnnotationType.label: label_categories } + + class DstTestExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, image=np.zeros((5, 10, 3)), + annotations=[ + Polygon( + [3.0, 2.5, 1.0, 0.0, 3.5, 0.0, 3.0, 2.5], + label=3, id=4, group=4, + attributes={ 'is_crowd': False }), + Polygon( + [5.0, 3.5, 4.5, 0.0, 8.0, 0.0, 5.0, 3.5], + label=3, id=4, group=4, + attributes={ 'is_crowd': False }), + ] + ), + ]) + + def categories(self): + return { AnnotationType.label: label_categories } + + with TestDir() as test_dir: + self._test_save_and_load(SrcTestExtractor(), + CocoInstancesConverter(segmentation_mode='polygons'), test_dir, + target_dataset=DstTestExtractor()) + + def test_can_save_and_load_images(self): + class TestExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, subset='train'), + DatasetItem(id=2, subset='train'), + + DatasetItem(id=2, subset='val'), + DatasetItem(id=3, subset='val'), + DatasetItem(id=4, subset='val'), + + DatasetItem(id=5, subset='test'), + ]) + + with TestDir() as test_dir: + self._test_save_and_load(TestExtractor(), + CocoImageInfoConverter(), test_dir) + + def test_can_save_and_load_labels(self): + class TestExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, subset='train', + annotations=[ + Label(4, id=1, group=1), + Label(9, id=2, group=2), + ]), + DatasetItem(id=2, subset='train', + annotations=[ + Label(4, id=4, group=4), + ]), + + DatasetItem(id=3, subset='val', + annotations=[ + Label(2, id=1, group=1), + ]), + ]) + + def categories(self): + label_categories = LabelCategories() + for i in range(10): + label_categories.add(str(i)) + return { + AnnotationType.label: label_categories, + } + + with TestDir() as test_dir: + self._test_save_and_load(TestExtractor(), + CocoLabelsConverter(), test_dir) + + def test_can_save_and_load_keypoints(self): + label_categories = LabelCategories() + points_categories = PointsCategories() + for i in range(10): + label_categories.add(str(i)) + points_categories.add(i, []) + categories = { + AnnotationType.label: label_categories, + AnnotationType.points: points_categories, + } + + class TestExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, subset='train', image=np.zeros((5, 5, 3)), + annotations=[ + # Full instance annotations: polygon + keypoints + Points([0, 0, 0, 2, 4, 1], [0, 1, 2], + label=3, group=1, id=1), + Polygon([0, 0, 4, 0, 4, 4], + label=3, group=1, id=1), + + # Full instance annotations: bbox + keypoints + Points([1, 2, 3, 4, 2, 3], group=2, id=2), + Bbox(1, 2, 2, 2, group=2, id=2), + ]), + DatasetItem(id=2, subset='train', + annotations=[ + # Solitary keypoints + Points([1, 2, 0, 2, 4, 1], label=5, id=3), + ]), + + DatasetItem(id=3, subset='val', + annotations=[ + # Solitary keypoints with no label + Points([0, 0, 1, 2, 3, 4], [0, 1, 2], id=3), + ]), + ]) + + def categories(self): + return categories + + class DstTestExtractor(TestExtractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, subset='train', image=np.zeros((5, 5, 3)), + annotations=[ + Points([0, 0, 0, 2, 4, 1], [0, 1, 2], + label=3, group=1, id=1, + attributes={'is_crowd': False}), + Polygon([0, 0, 4, 0, 4, 4], + label=3, group=1, id=1, + attributes={'is_crowd': False}), + + Points([1, 2, 3, 4, 2, 3], + group=2, id=2, + attributes={'is_crowd': False}), + Polygon([1, 2, 3, 2, 3, 4, 1, 4], + group=2, id=2, + attributes={'is_crowd': False}), + ]), + DatasetItem(id=2, subset='train', + annotations=[ + Points([1, 2, 0, 2, 4, 1], + label=5, group=3, id=3, + attributes={'is_crowd': False}), + Polygon([0, 1, 4, 1, 4, 2, 0, 2], + label=5, group=3, id=3, + attributes={'is_crowd': False}), + ]), + + DatasetItem(id=3, subset='val', + annotations=[ + Points([0, 0, 1, 2, 3, 4], [0, 1, 2], + group=3, id=3, + attributes={'is_crowd': False}), + Polygon([1, 2, 3, 2, 3, 4, 1, 4], + group=3, id=3, + attributes={'is_crowd': False}), + ]), + ]) + + with TestDir() as test_dir: + self._test_save_and_load(TestExtractor(), + CocoPersonKeypointsConverter(), test_dir, + target_dataset=DstTestExtractor()) + + def test_can_save_dataset_with_no_subsets(self): + class TestExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, annotations=[ + Label(2, id=1, group=1), + ]), + + DatasetItem(id=2, annotations=[ + Label(3, id=2, group=2), + ]), + ]) + + def categories(self): + label_cat = LabelCategories() + for label in range(10): + label_cat.add('label_' + str(label)) + return { + AnnotationType.label: label_cat, + } + + with TestDir() as test_dir: + self._test_save_and_load(TestExtractor(), + CocoConverter(), test_dir) + + def test_can_save_dataset_with_image_info(self): + class TestExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, image=Image(path='1.jpg', size=(10, 15))), + ]) + + with TestDir() as test_dir: + self._test_save_and_load(TestExtractor(), + CocoConverter(), test_dir) \ No newline at end of file diff --git a/datumaro/tests/test_command_targets.py b/datumaro/tests/test_command_targets.py new file mode 100644 index 00000000000..5b8a69f3182 --- /dev/null +++ b/datumaro/tests/test_command_targets.py @@ -0,0 +1,128 @@ +import numpy as np +import os.path as osp + +from unittest import TestCase + +from datumaro.components.project import Project +from datumaro.util.command_targets import ProjectTarget, \ + ImageTarget, SourceTarget +from datumaro.util.image import save_image +from datumaro.util.test_utils import TestDir + + +class CommandTargetsTest(TestCase): + def test_image_false_when_no_file(self): + target = ImageTarget() + + status = target.test('somepath.jpg') + + self.assertFalse(status) + + def test_image_false_when_false(self): + with TestDir() as test_dir: + path = osp.join(test_dir, 'test.jpg') + with open(path, 'w+') as f: + f.write('qwerty123') + + target = ImageTarget() + + status = target.test(path) + + self.assertFalse(status) + + def test_image_true_when_true(self): + with TestDir() as test_dir: + path = osp.join(test_dir, 'test.jpg') + save_image(path, np.ones([10, 7, 3])) + + target = ImageTarget() + + status = target.test(path) + + self.assertTrue(status) + + def test_project_false_when_no_file(self): + target = ProjectTarget() + + status = target.test('somepath.jpg') + + self.assertFalse(status) + + def test_project_false_when_no_name(self): + target = ProjectTarget(project=Project()) + + status = target.test('') + + self.assertFalse(status) + + def test_project_true_when_project_file(self): + with TestDir() as test_dir: + path = osp.join(test_dir, 'test.jpg') + Project().save(path) + + target = ProjectTarget() + + status = target.test(path) + + self.assertTrue(status) + + def test_project_true_when_project_name(self): + project_name = 'qwerty' + project = Project({ + 'project_name': project_name + }) + target = ProjectTarget(project=project) + + status = target.test(project_name) + + self.assertTrue(status) + + def test_project_false_when_not_project_name(self): + project_name = 'qwerty' + project = Project({ + 'project_name': project_name + }) + target = ProjectTarget(project=project) + + status = target.test(project_name + '123') + + self.assertFalse(status) + + def test_project_false_when_not_project_file(self): + with TestDir() as test_dir: + path = osp.join(test_dir, 'test.jpg') + with open(path, 'w+') as f: + f.write('wqererw') + + target = ProjectTarget() + + status = target.test(path) + + self.assertFalse(status) + + def test_source_false_when_no_project(self): + target = SourceTarget() + + status = target.test('qwerty123') + + self.assertFalse(status) + + def test_source_true_when_source_exists(self): + source_name = 'qwerty' + project = Project() + project.add_source(source_name) + target = SourceTarget(project=project) + + status = target.test(source_name) + + self.assertTrue(status) + + def test_source_false_when_source_doesnt_exist(self): + source_name = 'qwerty' + project = Project() + project.add_source(source_name) + target = SourceTarget(project=project) + + status = target.test(source_name + '123') + + self.assertFalse(status) \ No newline at end of file diff --git a/datumaro/tests/test_cvat_format.py b/datumaro/tests/test_cvat_format.py new file mode 100644 index 00000000000..cc45bee921c --- /dev/null +++ b/datumaro/tests/test_cvat_format.py @@ -0,0 +1,253 @@ +import numpy as np +import os +import os.path as osp +from xml.etree import ElementTree as ET + +from unittest import TestCase + +from datumaro.components.extractor import (Extractor, DatasetItem, + AnnotationType, Points, Polygon, PolyLine, Bbox, Label, + LabelCategories, +) +from datumaro.plugins.cvat_format.importer import CvatImporter +from datumaro.plugins.cvat_format.converter import CvatConverter +from datumaro.plugins.cvat_format.format import CvatPath +from datumaro.util.image import save_image, Image +from datumaro.util.test_utils import TestDir, compare_datasets + + +class CvatExtractorTest(TestCase): + @staticmethod + def generate_dummy_cvat(path): + images_dir = osp.join(path, CvatPath.IMAGES_DIR) + anno_dir = osp.join(path, CvatPath.ANNOTATIONS_DIR) + + os.makedirs(images_dir) + os.makedirs(anno_dir) + + root_elem = ET.Element('annotations') + ET.SubElement(root_elem, 'version').text = '1.1' + + meta_elem = ET.SubElement(root_elem, 'meta') + task_elem = ET.SubElement(meta_elem, 'task') + ET.SubElement(task_elem, 'z_order').text = 'True' + ET.SubElement(task_elem, 'mode').text = 'interpolation' + + labels_elem = ET.SubElement(task_elem, 'labels') + + label1_elem = ET.SubElement(labels_elem, 'label') + ET.SubElement(label1_elem, 'name').text = 'label1' + label1_attrs_elem = ET.SubElement(label1_elem, 'attributes') + + label1_a1_elem = ET.SubElement(label1_attrs_elem, 'attribute') + ET.SubElement(label1_a1_elem, 'name').text = 'a1' + ET.SubElement(label1_a1_elem, 'input_type').text = 'checkbox' + ET.SubElement(label1_a1_elem, 'default_value').text = 'false' + ET.SubElement(label1_a1_elem, 'values').text = 'false\ntrue' + + label1_a2_elem = ET.SubElement(label1_attrs_elem, 'attribute') + ET.SubElement(label1_a2_elem, 'name').text = 'a2' + ET.SubElement(label1_a2_elem, 'input_type').text = 'radio' + ET.SubElement(label1_a2_elem, 'default_value').text = 'v1' + ET.SubElement(label1_a2_elem, 'values').text = 'v1\nv2\nv3' + + label2_elem = ET.SubElement(labels_elem, 'label') + ET.SubElement(label2_elem, 'name').text = 'label2' + + # item 1 + save_image(osp.join(images_dir, 'img0.jpg'), np.ones((8, 8, 3))) + item1_elem = ET.SubElement(root_elem, 'image') + item1_elem.attrib.update({ + 'id': '0', 'name': 'img0', 'width': '8', 'height': '8' + }) + + item1_ann1_elem = ET.SubElement(item1_elem, 'box') + item1_ann1_elem.attrib.update({ + 'label': 'label1', 'occluded': '1', 'z_order': '1', + 'xtl': '0', 'ytl': '2', 'xbr': '4', 'ybr': '4' + }) + item1_ann1_a1_elem = ET.SubElement(item1_ann1_elem, 'attribute') + item1_ann1_a1_elem.attrib['name'] = 'a1' + item1_ann1_a1_elem.text = 'true' + item1_ann1_a2_elem = ET.SubElement(item1_ann1_elem, 'attribute') + item1_ann1_a2_elem.attrib['name'] = 'a2' + item1_ann1_a2_elem.text = 'v3' + + item1_ann2_elem = ET.SubElement(item1_elem, 'polyline') + item1_ann2_elem.attrib.update({ + 'label': '', 'points': '1.0,2;3,4;5,6;7,8' + }) + + # item 2 + save_image(osp.join(images_dir, 'img1.jpg'), np.ones((10, 10, 3))) + item2_elem = ET.SubElement(root_elem, 'image') + item2_elem.attrib.update({ + 'id': '1', 'name': 'img1', 'width': '10', 'height': '10' + }) + + item2_ann1_elem = ET.SubElement(item2_elem, 'polygon') + item2_ann1_elem.attrib.update({ + 'label': '', 'points': '1,2;3,4;6,5', 'z_order': '1', + }) + + item2_ann2_elem = ET.SubElement(item2_elem, 'points') + item2_ann2_elem.attrib.update({ + 'label': 'label2', 'points': '1,2;3,4;5,6', 'z_order': '2', + }) + + with open(osp.join(anno_dir, 'train.xml'), 'w') as f: + f.write(ET.tostring(root_elem, encoding='unicode')) + + def test_can_load(self): + class TestExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=0, subset='train', image=np.ones((8, 8, 3)), + annotations=[ + Bbox(0, 2, 4, 2, label=0, + attributes={ + 'occluded': True, 'z_order': 1, + 'a1': True, 'a2': 'v3' + }), + PolyLine([1, 2, 3, 4, 5, 6, 7, 8], + attributes={'occluded': False, 'z_order': 0}), + ]), + DatasetItem(id=1, subset='train', image=np.ones((10, 10, 3)), + annotations=[ + Polygon([1, 2, 3, 4, 6, 5], + attributes={'occluded': False, 'z_order': 1}), + Points([1, 2, 3, 4, 5, 6], label=1, + attributes={'occluded': False, 'z_order': 2}), + ]), + ]) + + def categories(self): + label_categories = LabelCategories() + label_categories.add('label1', attributes={'a1', 'a2'}) + label_categories.add('label2') + return { + AnnotationType.label: label_categories, + } + + with TestDir() as test_dir: + self.generate_dummy_cvat(test_dir) + source_dataset = TestExtractor() + + parsed_dataset = CvatImporter()(test_dir).make_dataset() + + compare_datasets(self, source_dataset, parsed_dataset) + + +class CvatConverterTest(TestCase): + def _test_save_and_load(self, source_dataset, converter, test_dir, + target_dataset=None, importer_args=None): + converter(source_dataset, test_dir) + + if importer_args is None: + importer_args = {} + parsed_dataset = CvatImporter()(test_dir, **importer_args).make_dataset() + + if target_dataset is None: + target_dataset = source_dataset + + compare_datasets(self, expected=target_dataset, actual=parsed_dataset) + + def test_can_save_and_load(self): + label_categories = LabelCategories() + for i in range(10): + label_categories.add(str(i)) + label_categories.items[2].attributes.update(['a1', 'a2']) + label_categories.attributes.update(['z_order', 'occluded']) + + class SrcExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=0, subset='s1', image=np.zeros((5, 10, 3)), + annotations=[ + Polygon([0, 0, 4, 0, 4, 4], + label=1, group=4, + attributes={ 'occluded': True }), + Polygon([5, 0, 9, 0, 5, 5], + label=2, group=4, + attributes={ 'unknown': 'bar' }), + Points([1, 1, 3, 2, 2, 3], + label=2, + attributes={ 'a1': 'x', 'a2': 42 }), + Label(1), + Label(2, attributes={ 'a1': 'y', 'a2': 44 }), + ] + ), + DatasetItem(id=1, subset='s1', + annotations=[ + PolyLine([0, 0, 4, 0, 4, 4], + label=3, id=4, group=4), + Bbox(5, 0, 1, 9, + label=3, id=4, group=4), + ] + ), + + DatasetItem(id=2, subset='s2', image=np.ones((5, 10, 3)), + annotations=[ + Polygon([0, 0, 4, 0, 4, 4], + label=3, group=4, + attributes={ 'z_order': 1, 'occluded': False }), + PolyLine([5, 0, 9, 0, 5, 5]), # will be skipped as no label + ] + ), + + DatasetItem(id=3, subset='s3', image=Image( + path='3.jpg', size=(2, 4))), + ]) + + def categories(self): + return { AnnotationType.label: label_categories } + + class DstExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=0, subset='s1', image=np.zeros((5, 10, 3)), + annotations=[ + Polygon([0, 0, 4, 0, 4, 4], + label=1, group=4, + attributes={ 'z_order': 0, 'occluded': True }), + Polygon([5, 0, 9, 0, 5, 5], + label=2, group=4, + attributes={ 'z_order': 0, 'occluded': False }), + Points([1, 1, 3, 2, 2, 3], + label=2, + attributes={ 'z_order': 0, 'occluded': False, + 'a1': 'x', 'a2': 42 }), + Label(1), + Label(2, attributes={ 'a1': 'y', 'a2': 44 }), + ] + ), + DatasetItem(id=1, subset='s1', + annotations=[ + PolyLine([0, 0, 4, 0, 4, 4], + label=3, group=4, + attributes={ 'z_order': 0, 'occluded': False }), + Bbox(5, 0, 1, 9, + label=3, group=4, + attributes={ 'z_order': 0, 'occluded': False }), + ] + ), + + DatasetItem(id=2, subset='s2', image=np.ones((5, 10, 3)), + annotations=[ + Polygon([0, 0, 4, 0, 4, 4], + label=3, group=4, + attributes={ 'z_order': 1, 'occluded': False }), + ] + ), + + DatasetItem(id=3, subset='s3', image=Image( + path='3.jpg', size=(2, 4))), + ]) + + def categories(self): + return { AnnotationType.label: label_categories } + + with TestDir() as test_dir: + self._test_save_and_load(SrcExtractor(), + CvatConverter(save_images=True), test_dir, + target_dataset=DstExtractor()) diff --git a/datumaro/tests/test_datumaro_format.py b/datumaro/tests/test_datumaro_format.py new file mode 100644 index 00000000000..e17da2a7b95 --- /dev/null +++ b/datumaro/tests/test_datumaro_format.py @@ -0,0 +1,101 @@ +import numpy as np + +from unittest import TestCase + +from datumaro.components.project import Project +from datumaro.components.extractor import (Extractor, DatasetItem, + AnnotationType, Label, Mask, Points, Polygon, + PolyLine, Bbox, Caption, + LabelCategories, MaskCategories, PointsCategories +) +from datumaro.plugins.datumaro_format.converter import DatumaroConverter +from datumaro.util.mask_tools import generate_colormap +from datumaro.util.image import Image +from datumaro.util.test_utils import TestDir, item_to_str + + +class DatumaroConverterTest(TestCase): + class TestExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=100, subset='train', image=np.ones((10, 6, 3)), + annotations=[ + Caption('hello', id=1), + Caption('world', id=2, group=5), + Label(2, id=3, attributes={ + 'x': 1, + 'y': '2', + }), + Bbox(1, 2, 3, 4, label=4, id=4, attributes={ + 'score': 1.0, + }), + Bbox(5, 6, 7, 8, id=5, group=5), + Points([1, 2, 2, 0, 1, 1], label=0, id=5), + Mask(label=3, id=5, image=np.ones((2, 3))), + ]), + DatasetItem(id=21, subset='train', + annotations=[ + Caption('test'), + Label(2), + Bbox(1, 2, 3, 4, 5, id=42, group=42) + ]), + + DatasetItem(id=2, subset='val', + annotations=[ + PolyLine([1, 2, 3, 4, 5, 6, 7, 8], id=11), + Polygon([1, 2, 3, 4, 5, 6, 7, 8], id=12), + ]), + + DatasetItem(id=42, subset='test'), + + DatasetItem(id=42), + DatasetItem(id=43, image=Image(path='1/b/c.qq', size=(2, 4))), + ]) + + def categories(self): + label_categories = LabelCategories() + for i in range(5): + label_categories.add('cat' + str(i)) + + mask_categories = MaskCategories( + generate_colormap(len(label_categories.items))) + + points_categories = PointsCategories() + for index, _ in enumerate(label_categories.items): + points_categories.add(index, ['cat1', 'cat2'], adjacent=[0, 1]) + + return { + AnnotationType.label: label_categories, + AnnotationType.mask: mask_categories, + AnnotationType.points: points_categories, + } + + def test_can_save_and_load(self): + with TestDir() as test_dir: + source_dataset = self.TestExtractor() + + converter = DatumaroConverter(save_images=True) + converter(source_dataset, test_dir) + + project = Project.import_from(test_dir, 'datumaro') + parsed_dataset = project.make_dataset() + + self.assertListEqual( + sorted(source_dataset.subsets()), + sorted(parsed_dataset.subsets()), + ) + + self.assertEqual(len(source_dataset), len(parsed_dataset)) + + for subset_name in source_dataset.subsets(): + source_subset = source_dataset.get_subset(subset_name) + parsed_subset = parsed_dataset.get_subset(subset_name) + self.assertEqual(len(source_subset), len(parsed_subset)) + for idx, (item_a, item_b) in enumerate( + zip(source_subset, parsed_subset)): + self.assertEqual(item_a, item_b, '%s:\n%s\nvs.\n%s\n' % \ + (idx, item_to_str(item_a), item_to_str(item_b))) + + self.assertEqual( + source_dataset.categories(), + parsed_dataset.categories()) \ No newline at end of file diff --git a/datumaro/tests/test_diff.py b/datumaro/tests/test_diff.py new file mode 100644 index 00000000000..9ad9c1de6fd --- /dev/null +++ b/datumaro/tests/test_diff.py @@ -0,0 +1,142 @@ +from unittest import TestCase + +from datumaro.components.extractor import DatasetItem, Label, Bbox +from datumaro.components.comparator import Comparator + + +class DiffTest(TestCase): + def test_no_bbox_diff_with_same_item(self): + detections = 3 + anns = [ + Bbox(i * 10, 10, 10, 10, label=i, + attributes={'score': (1.0 + i) / detections}) \ + for i in range(detections) + ] + item = DatasetItem(id=0, annotations=anns) + + iou_thresh = 0.5 + conf_thresh = 0.5 + comp = Comparator( + iou_threshold=iou_thresh, conf_threshold=conf_thresh) + + result = comp.compare_item_bboxes(item, item) + + matches, mispred, a_greater, b_greater = result + self.assertEqual(0, len(mispred)) + self.assertEqual(0, len(a_greater)) + self.assertEqual(0, len(b_greater)) + self.assertEqual(len([it for it in item.annotations \ + if conf_thresh < it.attributes['score']]), + len(matches)) + for a_bbox, b_bbox in matches: + self.assertLess(iou_thresh, a_bbox.iou(b_bbox)) + self.assertEqual(a_bbox.label, b_bbox.label) + self.assertLess(conf_thresh, a_bbox.attributes['score']) + self.assertLess(conf_thresh, b_bbox.attributes['score']) + + def test_can_find_bbox_with_wrong_label(self): + detections = 3 + class_count = 2 + item1 = DatasetItem(id=1, annotations=[ + Bbox(i * 10, 10, 10, 10, label=i, + attributes={'score': (1.0 + i) / detections}) \ + for i in range(detections) + ]) + item2 = DatasetItem(id=2, annotations=[ + Bbox(i * 10, 10, 10, 10, label=(i + 1) % class_count, + attributes={'score': (1.0 + i) / detections}) \ + for i in range(detections) + ]) + + iou_thresh = 0.5 + conf_thresh = 0.5 + comp = Comparator( + iou_threshold=iou_thresh, conf_threshold=conf_thresh) + + result = comp.compare_item_bboxes(item1, item2) + + matches, mispred, a_greater, b_greater = result + self.assertEqual(len([it for it in item1.annotations \ + if conf_thresh < it.attributes['score']]), + len(mispred)) + self.assertEqual(0, len(a_greater)) + self.assertEqual(0, len(b_greater)) + self.assertEqual(0, len(matches)) + for a_bbox, b_bbox in mispred: + self.assertLess(iou_thresh, a_bbox.iou(b_bbox)) + self.assertEqual((a_bbox.label + 1) % class_count, b_bbox.label) + self.assertLess(conf_thresh, a_bbox.attributes['score']) + self.assertLess(conf_thresh, b_bbox.attributes['score']) + + def test_can_find_missing_boxes(self): + detections = 3 + class_count = 2 + item1 = DatasetItem(id=1, annotations=[ + Bbox(i * 10, 10, 10, 10, label=i, + attributes={'score': (1.0 + i) / detections}) \ + for i in range(detections) if i % 2 == 0 + ]) + item2 = DatasetItem(id=2, annotations=[ + Bbox(i * 10, 10, 10, 10, label=(i + 1) % class_count, + attributes={'score': (1.0 + i) / detections}) \ + for i in range(detections) if i % 2 == 1 + ]) + + iou_thresh = 0.5 + conf_thresh = 0.5 + comp = Comparator( + iou_threshold=iou_thresh, conf_threshold=conf_thresh) + + result = comp.compare_item_bboxes(item1, item2) + + matches, mispred, a_greater, b_greater = result + self.assertEqual(0, len(mispred)) + self.assertEqual(len([it for it in item1.annotations \ + if conf_thresh < it.attributes['score']]), + len(a_greater)) + self.assertEqual(len([it for it in item2.annotations \ + if conf_thresh < it.attributes['score']]), + len(b_greater)) + self.assertEqual(0, len(matches)) + + def test_no_label_diff_with_same_item(self): + detections = 3 + anns = [ + Label(i, attributes={'score': (1.0 + i) / detections}) \ + for i in range(detections) + ] + item = DatasetItem(id=1, annotations=anns) + + conf_thresh = 0.5 + comp = Comparator(conf_threshold=conf_thresh) + + result = comp.compare_item_labels(item, item) + + matches, a_greater, b_greater = result + self.assertEqual(0, len(a_greater)) + self.assertEqual(0, len(b_greater)) + self.assertEqual(len([it for it in item.annotations \ + if conf_thresh < it.attributes['score']]), + len(matches)) + + def test_can_find_wrong_label(self): + item1 = DatasetItem(id=1, annotations=[ + Label(0), + Label(1), + Label(2), + ]) + item2 = DatasetItem(id=2, annotations=[ + Label(2), + Label(3), + Label(4), + ]) + + conf_thresh = 0.5 + comp = Comparator(conf_threshold=conf_thresh) + + result = comp.compare_item_labels(item1, item2) + + matches, a_greater, b_greater = result + self.assertEqual(2, len(a_greater)) + self.assertEqual(2, len(b_greater)) + self.assertEqual(1, len(matches)) \ No newline at end of file diff --git a/datumaro/tests/test_image.py b/datumaro/tests/test_image.py new file mode 100644 index 00000000000..efb7aea2969 --- /dev/null +++ b/datumaro/tests/test_image.py @@ -0,0 +1,52 @@ +from itertools import product +import numpy as np +import os.path as osp + +from unittest import TestCase + +import datumaro.util.image as image_module +from datumaro.util.test_utils import TestDir + + +class ImageOperationsTest(TestCase): + def setUp(self): + self.default_backend = image_module._IMAGE_BACKEND + + def tearDown(self): + image_module._IMAGE_BACKEND = self.default_backend + + def test_save_and_load_backends(self): + backends = image_module._IMAGE_BACKENDS + for save_backend, load_backend, c in product(backends, backends, [1, 3]): + with TestDir() as test_dir: + if c == 1: + src_image = np.random.randint(0, 255 + 1, (2, 4)) + else: + src_image = np.random.randint(0, 255 + 1, (2, 4, c)) + path = osp.join(test_dir, 'img.png') # lossless + + image_module._IMAGE_BACKEND = save_backend + image_module.save_image(path, src_image) + + image_module._IMAGE_BACKEND = load_backend + dst_image = image_module.load_image(path) + + self.assertTrue(np.array_equal(src_image, dst_image), + 'save: %s, load: %s' % (save_backend, load_backend)) + + def test_encode_and_decode_backends(self): + backends = image_module._IMAGE_BACKENDS + for save_backend, load_backend, c in product(backends, backends, [1, 3]): + if c == 1: + src_image = np.random.randint(0, 255 + 1, (2, 4)) + else: + src_image = np.random.randint(0, 255 + 1, (2, 4, c)) + + image_module._IMAGE_BACKEND = save_backend + buffer = image_module.encode_image(src_image, '.png') # lossless + + image_module._IMAGE_BACKEND = load_backend + dst_image = image_module.decode_image(buffer) + + self.assertTrue(np.array_equal(src_image, dst_image), + 'save: %s, load: %s' % (save_backend, load_backend)) diff --git a/datumaro/tests/test_image_dir_format.py b/datumaro/tests/test_image_dir_format.py new file mode 100644 index 00000000000..30dd05b1c43 --- /dev/null +++ b/datumaro/tests/test_image_dir_format.py @@ -0,0 +1,28 @@ +import numpy as np + +from unittest import TestCase + +from datumaro.components.project import Project +from datumaro.components.extractor import Extractor, DatasetItem +from datumaro.plugins.image_dir import ImageDirConverter +from datumaro.util.test_utils import TestDir, compare_datasets + + +class ImageDirFormatTest(TestCase): + class TestExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, image=np.ones((10, 6, 3))), + DatasetItem(id=2, image=np.ones((5, 4, 3))), + ]) + + def test_can_load(self): + with TestDir() as test_dir: + source_dataset = self.TestExtractor() + + ImageDirConverter()(source_dataset, save_dir=test_dir) + + project = Project.import_from(test_dir, 'image_dir') + parsed_dataset = project.make_dataset() + + compare_datasets(self, source_dataset, parsed_dataset) diff --git a/datumaro/tests/test_images.py b/datumaro/tests/test_images.py new file mode 100644 index 00000000000..e77d73d4fac --- /dev/null +++ b/datumaro/tests/test_images.py @@ -0,0 +1,79 @@ +import numpy as np +import os.path as osp + +from unittest import TestCase + +from datumaro.util.test_utils import TestDir +from datumaro.util.image import lazy_image, load_image, save_image, Image +from datumaro.util.image_cache import ImageCache + + +class LazyImageTest(TestCase): + def test_cache_works(self): + with TestDir() as test_dir: + image = np.ones((100, 100, 3), dtype=np.uint8) + image_path = osp.join(test_dir, 'image.jpg') + save_image(image_path, image) + + caching_loader = lazy_image(image_path, cache=None) + self.assertTrue(caching_loader() is caching_loader()) + + non_caching_loader = lazy_image(image_path, cache=False) + self.assertFalse(non_caching_loader() is non_caching_loader()) + +class ImageCacheTest(TestCase): + def test_cache_fifo_displacement(self): + capacity = 2 + cache = ImageCache(capacity) + + loaders = [lazy_image(None, loader=lambda p: object(), cache=cache) + for _ in range(capacity + 1)] + + first_request = [loader() for loader in loaders[1 : ]] + loaders[0]() # pop something from the cache + + second_request = [loader() for loader in loaders[2 : ]] + second_request.insert(0, loaders[1]()) + + matches = sum([a is b for a, b in zip(first_request, second_request)]) + self.assertEqual(matches, len(first_request) - 1) + + def test_global_cache_is_accessible(self): + loader = lazy_image(None, loader=lambda p: object()) + + ImageCache.get_instance().clear() + self.assertTrue(loader() is loader()) + self.assertEqual(ImageCache.get_instance().size(), 1) + +class ImageTest(TestCase): + def test_lazy_image_shape(self): + data = np.ones((5, 6, 7)) + + image_lazy = Image(data=data, size=(2, 4)) + image_eager = Image(data=data) + + self.assertEqual((2, 4), image_lazy.size) + self.assertEqual((5, 6), image_eager.size) + + @staticmethod + def test_ctors(): + with TestDir() as test_dir: + path = osp.join(test_dir, 'path.png') + image = np.ones([2, 4, 3]) + save_image(path, image) + + for args in [ + { 'data': image }, + { 'data': image, 'path': path }, + { 'data': image, 'path': path, 'size': (2, 4) }, + { 'data': image, 'path': path, 'loader': load_image, 'size': (2, 4) }, + { 'path': path }, + { 'path': path, 'loader': load_image }, + { 'path': path, 'size': (2, 4) }, + ]: + img = Image(**args) + # pylint: disable=pointless-statement + if img.has_data: + img.data + img.size + # pylint: enable=pointless-statement diff --git a/datumaro/tests/test_masks.py b/datumaro/tests/test_masks.py new file mode 100644 index 00000000000..a4f540185ee --- /dev/null +++ b/datumaro/tests/test_masks.py @@ -0,0 +1,186 @@ +import numpy as np + +from unittest import TestCase + +import datumaro.util.mask_tools as mask_tools + + +class PolygonConversionsTest(TestCase): + def test_mask_can_be_converted_to_polygon(self): + mask = np.array([ + [0, 1, 1, 1, 0, 1, 1, 1, 1, 0], + [0, 0, 1, 1, 0, 1, 0, 1, 0, 0], + [0, 0, 0, 1, 0, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ]) + expected = [ + [1, 0, 3, 0, 3, 2, 1, 0], + [5, 0, 8, 0, 5, 3], + ] + + computed = mask_tools.mask_to_polygons(mask) + + self.assertEqual(len(expected), len(computed)) + + def test_can_crop_covered_segments(self): + image_size = [7, 7] + initial = [ + [1, 1, 6, 1, 6, 6, 1, 6], # rectangle + mask_tools.mask_to_rle(np.array([ + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 1, 1, 0], + [0, 1, 1, 0, 1, 1, 0], + [0, 0, 0, 0, 0, 1, 0], + [0, 1, 1, 0, 0, 1, 0], + [0, 1, 1, 1, 1, 1, 0], + [0, 0, 0, 0, 0, 0, 0], + ])), + [1, 1, 6, 6, 1, 6], # lower-left triangle + ] + expected = [ + np.array([ + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + ]), # half-covered + np.array([ + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 1, 1, 0], + [0, 0, 0, 0, 1, 1, 0], + [0, 0, 0, 0, 0, 1, 0], + [0, 0, 0, 0, 0, 1, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + ]), # half-covered + mask_tools.rles_to_mask([initial[2]], *image_size), # unchanged + ] + + computed = mask_tools.crop_covered_segments(initial, *image_size, + ratio_tolerance=0, return_masks=True) + + self.assertEqual(len(initial), len(computed)) + for i, (e_mask, c_mask) in enumerate(zip(expected, computed)): + self.assertTrue(np.array_equal(e_mask, c_mask), + '#%s: %s\n%s\n' % (i, e_mask, c_mask)) + + def _test_mask_to_rle(self, source_mask): + rle_uncompressed = mask_tools.mask_to_rle(source_mask) + + from pycocotools import mask as mask_utils + resulting_mask = mask_utils.frPyObjects( + rle_uncompressed, *rle_uncompressed['size']) + resulting_mask = mask_utils.decode(resulting_mask) + + self.assertTrue(np.array_equal(source_mask, resulting_mask), + '%s\n%s\n' % (source_mask, resulting_mask)) + + def test_mask_to_rle_multi(self): + cases = [ + np.array([ + [0, 1, 1, 1, 0, 1, 1, 1, 1, 0], + [0, 0, 1, 1, 0, 1, 0, 1, 0, 0], + [0, 0, 0, 1, 0, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ]), + + np.array([ + [0] + ]), + np.array([ + [1] + ]), + + np.array([ + [1, 0, 0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 1, 1, 1, 0, 0, 0, 0, 0], + [1, 0, 1, 0, 1, 1, 1, 0, 0, 0], + [1, 1, 0, 1, 0, 1, 1, 1, 1, 0], + [1, 0, 1, 0, 1, 0, 0, 0, 0, 0], + [1, 0, 0, 1, 0, 0, 0, 1, 0, 1], + [1, 1, 0, 0, 1, 1, 0, 0, 0, 1], + [0, 0, 1, 0, 0, 0, 1, 1, 1, 1], + [1, 1, 0, 0, 0, 0, 0, 1, 0, 0], + [1, 1, 1, 1, 1, 0, 1, 0, 1, 0], + [0, 1, 0, 1, 1, 1, 1, 1, 0, 0], + [0, 1, 0, 0, 0, 1, 0, 0, 1, 0], + [1, 1, 0, 1, 0, 0, 1, 1, 1, 1], + ]) + ] + + for case in cases: + self._test_mask_to_rle(case) + +class ColormapOperationsTest(TestCase): + def test_can_paint_mask(self): + mask = np.zeros((1, 3), dtype=np.uint8) + mask[:, 0] = 0 + mask[:, 1] = 1 + mask[:, 2] = 2 + + colormap = mask_tools.generate_colormap(3) + + expected = np.zeros((*mask.shape, 3), dtype=np.uint8) + expected[:, 0] = colormap[0][::-1] + expected[:, 1] = colormap[1][::-1] + expected[:, 2] = colormap[2][::-1] + + actual = mask_tools.paint_mask(mask, colormap) + + self.assertTrue(np.array_equal(expected, actual), + '%s\nvs.\n%s' % (expected, actual)) + + def test_can_unpaint_mask(self): + colormap = mask_tools.generate_colormap(3) + inverse_colormap = mask_tools.invert_colormap(colormap) + + mask = np.zeros((1, 3, 3), dtype=np.uint8) + mask[:, 0] = colormap[0][::-1] + mask[:, 1] = colormap[1][::-1] + mask[:, 2] = colormap[2][::-1] + + expected = np.zeros((1, 3), dtype=np.uint8) + expected[:, 0] = 0 + expected[:, 1] = 1 + expected[:, 2] = 2 + + actual = mask_tools.unpaint_mask(mask, inverse_colormap) + + self.assertTrue(np.array_equal(expected, actual), + '%s\nvs.\n%s' % (expected, actual)) + + def test_can_remap_mask(self): + class_count = 10 + remap_fn = lambda c: class_count - c + + src = np.empty((class_count, class_count), dtype=np.uint8) + for c in range(class_count): + src[c:, c:] = c + + expected = np.empty_like(src) + for c in range(class_count): + expected[c:, c:] = remap_fn(c) + + actual = mask_tools.remap_mask(src, remap_fn) + + self.assertTrue(np.array_equal(expected, actual), + '%s\nvs.\n%s' % (expected, actual)) + + def test_can_merge_masks(self): + masks = [ + np.array([0, 2, 4, 0, 0, 1]), + np.array([0, 1, 1, 0, 2, 0]), + np.array([0, 0, 2, 3, 0, 0]), + ] + expected = \ + np.array([0, 1, 2, 3, 2, 1]) + + actual = mask_tools.merge_masks(masks) + + self.assertTrue(np.array_equal(expected, actual), + '%s\nvs.\n%s' % (expected, actual)) \ No newline at end of file diff --git a/datumaro/tests/test_project.py b/datumaro/tests/test_project.py new file mode 100644 index 00000000000..75baf716e80 --- /dev/null +++ b/datumaro/tests/test_project.py @@ -0,0 +1,551 @@ +import numpy as np +import os +import os.path as osp + +from unittest import TestCase + +from datumaro.components.project import Project, Environment, Dataset +from datumaro.components.config_model import Source, Model +from datumaro.components.launcher import Launcher, InferenceWrapper +from datumaro.components.converter import Converter +from datumaro.components.extractor import (Extractor, DatasetItem, + Label, Mask, Points, Polygon, PolyLine, Bbox, Caption, +) +from datumaro.util.image import Image +from datumaro.components.config import Config, DefaultConfig, SchemaBuilder +from datumaro.components.dataset_filter import \ + XPathDatasetFilter, XPathAnnotationsFilter, DatasetItemEncoder +from datumaro.util.test_utils import TestDir, compare_datasets + + +class ProjectTest(TestCase): + def test_project_generate(self): + src_config = Config({ + 'project_name': 'test_project', + 'format_version': 1, + }) + + with TestDir() as test_dir: + project_path = test_dir + Project.generate(project_path, src_config) + + self.assertTrue(osp.isdir(project_path)) + + result_config = Project.load(project_path).config + self.assertEqual( + src_config.project_name, result_config.project_name) + self.assertEqual( + src_config.format_version, result_config.format_version) + + @staticmethod + def test_default_ctor_is_ok(): + Project() + + @staticmethod + def test_empty_config_is_ok(): + Project(Config()) + + def test_add_source(self): + source_name = 'source' + origin = Source({ + 'url': 'path', + 'format': 'ext' + }) + project = Project() + + project.add_source(source_name, origin) + + added = project.get_source(source_name) + self.assertIsNotNone(added) + self.assertEqual(added, origin) + + def test_added_source_can_be_saved(self): + source_name = 'source' + origin = Source({ + 'url': 'path', + }) + project = Project() + project.add_source(source_name, origin) + + saved = project.config + + self.assertEqual(origin, saved.sources[source_name]) + + def test_added_source_can_be_dumped(self): + source_name = 'source' + origin = Source({ + 'url': 'path', + }) + project = Project() + project.add_source(source_name, origin) + + with TestDir() as test_dir: + project.save(test_dir) + + loaded = Project.load(test_dir) + loaded = loaded.get_source(source_name) + self.assertEqual(origin, loaded) + + def test_can_import_with_custom_importer(self): + class TestImporter: + def __call__(self, path, subset=None): + return Project({ + 'project_filename': path, + 'subsets': [ subset ] + }) + + path = 'path' + importer_name = 'test_importer' + + env = Environment() + env.importers.register(importer_name, TestImporter) + + project = Project.import_from(path, importer_name, env, + subset='train') + + self.assertEqual(path, project.config.project_filename) + self.assertListEqual(['train'], project.config.subsets) + + def test_can_dump_added_model(self): + model_name = 'model' + + project = Project() + saved = Model({ 'launcher': 'name' }) + project.add_model(model_name, saved) + + with TestDir() as test_dir: + project.save(test_dir) + + loaded = Project.load(test_dir) + loaded = loaded.get_model(model_name) + self.assertEqual(saved, loaded) + + def test_can_have_project_source(self): + with TestDir() as test_dir: + Project.generate(test_dir) + + project2 = Project() + project2.add_source('project1', { + 'url': test_dir, + }) + dataset = project2.make_dataset() + + self.assertTrue('project1' in dataset.sources) + + def test_can_batch_launch_custom_model(self): + class TestExtractor(Extractor): + def __iter__(self): + for i in range(5): + yield DatasetItem(id=i, subset='train', image=np.array([i])) + + class TestLauncher(Launcher): + def launch(self, inputs): + for i, inp in enumerate(inputs): + yield [ Label(attributes={'idx': i, 'data': inp.item()}) ] + + model_name = 'model' + launcher_name = 'custom_launcher' + + project = Project() + project.env.launchers.register(launcher_name, TestLauncher) + project.add_model(model_name, { 'launcher': launcher_name }) + model = project.make_executable_model(model_name) + extractor = TestExtractor() + + batch_size = 3 + executor = InferenceWrapper(extractor, model, batch_size=batch_size) + + for item in executor: + self.assertEqual(1, len(item.annotations)) + self.assertEqual(int(item.id) % batch_size, + item.annotations[0].attributes['idx']) + self.assertEqual(int(item.id), + item.annotations[0].attributes['data']) + + def test_can_do_transform_with_custom_model(self): + class TestExtractorSrc(Extractor): + def __iter__(self): + for i in range(2): + yield DatasetItem(id=i, image=np.ones([2, 2, 3]) * i, + annotations=[Label(i)]) + + class TestLauncher(Launcher): + def launch(self, inputs): + for inp in inputs: + yield [ Label(inp[0, 0, 0]) ] + + class TestConverter(Converter): + def __call__(self, extractor, save_dir): + for item in extractor: + with open(osp.join(save_dir, '%s.txt' % item.id), 'w') as f: + f.write(str(item.annotations[0].label) + '\n') + + class TestExtractorDst(Extractor): + def __init__(self, url): + super().__init__() + self.items = [osp.join(url, p) for p in sorted(os.listdir(url))] + + def __iter__(self): + for path in self.items: + with open(path, 'r') as f: + index = osp.splitext(osp.basename(path))[0] + label = int(f.readline().strip()) + yield DatasetItem(id=index, annotations=[Label(label)]) + + model_name = 'model' + launcher_name = 'custom_launcher' + extractor_name = 'custom_extractor' + + project = Project() + project.env.launchers.register(launcher_name, TestLauncher) + project.env.extractors.register(extractor_name, TestExtractorSrc) + project.env.converters.register(extractor_name, TestConverter) + project.add_model(model_name, { 'launcher': launcher_name }) + project.add_source('source', { 'format': extractor_name }) + + with TestDir() as test_dir: + project.make_dataset().apply_model(model=model_name, + save_dir=test_dir) + + result = Project.load(test_dir) + result.env.extractors.register(extractor_name, TestExtractorDst) + it = iter(result.make_dataset()) + item1 = next(it) + item2 = next(it) + self.assertEqual(0, item1.annotations[0].label) + self.assertEqual(1, item2.annotations[0].label) + + def test_source_datasets_can_be_merged(self): + class TestExtractor(Extractor): + def __init__(self, url, n=0, s=0): + super().__init__(length=n) + self.n = n + self.s = s + + def __iter__(self): + for i in range(self.n): + yield DatasetItem(id=self.s + i, subset='train') + + e_name1 = 'e1' + e_name2 = 'e2' + n1 = 2 + n2 = 4 + + project = Project() + project.env.extractors.register(e_name1, lambda p: TestExtractor(p, n=n1)) + project.env.extractors.register(e_name2, lambda p: TestExtractor(p, n=n2, s=n1)) + project.add_source('source1', { 'format': e_name1 }) + project.add_source('source2', { 'format': e_name2 }) + + dataset = project.make_dataset() + + self.assertEqual(n1 + n2, len(dataset)) + + def test_project_filter_can_be_applied(self): + class TestExtractor(Extractor): + def __iter__(self): + for i in range(10): + yield DatasetItem(id=i, subset='train') + + e_type = 'type' + project = Project() + project.env.extractors.register(e_type, TestExtractor) + project.add_source('source', { 'format': e_type }) + + dataset = project.make_dataset().extract('/item[id < 5]') + + self.assertEqual(5, len(dataset)) + + def test_can_save_and_load_own_dataset(self): + with TestDir() as test_dir: + src_project = Project() + src_dataset = src_project.make_dataset() + item = DatasetItem(id=1) + src_dataset.put(item) + src_dataset.save(test_dir) + + loaded_project = Project.load(test_dir) + loaded_dataset = loaded_project.make_dataset() + + self.assertEqual(list(src_dataset), list(loaded_dataset)) + + def test_project_own_dataset_can_be_modified(self): + project = Project() + dataset = project.make_dataset() + + item = DatasetItem(id=1) + dataset.put(item) + + self.assertEqual(item, next(iter(dataset))) + + def test_project_compound_child_can_be_modified_recursively(self): + with TestDir() as test_dir: + child1 = Project({ + 'project_dir': osp.join(test_dir, 'child1'), + }) + child1.save() + + child2 = Project({ + 'project_dir': osp.join(test_dir, 'child2'), + }) + child2.save() + + parent = Project() + parent.add_source('child1', { + 'url': child1.config.project_dir + }) + parent.add_source('child2', { + 'url': child2.config.project_dir + }) + dataset = parent.make_dataset() + + item1 = DatasetItem(id='ch1', path=['child1']) + item2 = DatasetItem(id='ch2', path=['child2']) + dataset.put(item1) + dataset.put(item2) + + self.assertEqual(2, len(dataset)) + self.assertEqual(1, len(dataset.sources['child1'])) + self.assertEqual(1, len(dataset.sources['child2'])) + + def test_project_can_merge_item_annotations(self): + class TestExtractor1(Extractor): + def __iter__(self): + yield DatasetItem(id=1, subset='train', annotations=[ + Label(2, id=3), + Label(3, attributes={ 'x': 1 }), + ]) + + class TestExtractor2(Extractor): + def __iter__(self): + yield DatasetItem(id=1, subset='train', annotations=[ + Label(3, attributes={ 'x': 1 }), + Label(4, id=4), + ]) + + project = Project() + project.env.extractors.register('t1', TestExtractor1) + project.env.extractors.register('t2', TestExtractor2) + project.add_source('source1', { 'format': 't1' }) + project.add_source('source2', { 'format': 't2' }) + + merged = project.make_dataset() + + self.assertEqual(1, len(merged)) + + item = next(iter(merged)) + self.assertEqual(3, len(item.annotations)) + +class DatasetFilterTest(TestCase): + @staticmethod + def test_item_representations(): + item = DatasetItem(id=1, subset='subset', path=['a', 'b'], + image=np.ones((5, 4, 3)), + annotations=[ + Label(0, attributes={'a1': 1, 'a2': '2'}, id=1, group=2), + Caption('hello', id=1), + Caption('world', group=5), + Label(2, id=3, attributes={ 'x': 1, 'y': '2' }), + Bbox(1, 2, 3, 4, label=4, id=4, attributes={ 'a': 1.0 }), + Bbox(5, 6, 7, 8, id=5, group=5), + Points([1, 2, 2, 0, 1, 1], label=0, id=5), + Mask(id=5, image=np.ones((3, 2))), + Mask(label=3, id=5, image=np.ones((2, 3))), + PolyLine([1, 2, 3, 4, 5, 6, 7, 8], id=11), + Polygon([1, 2, 3, 4, 5, 6, 7, 8]), + ] + ) + + encoded = DatasetItemEncoder.encode(item) + DatasetItemEncoder.to_string(encoded) + + def test_item_filter_can_be_applied(self): + class TestExtractor(Extractor): + def __iter__(self): + for i in range(4): + yield DatasetItem(id=i, subset='train') + + extractor = TestExtractor() + + filtered = XPathDatasetFilter(extractor, '/item[id > 1]') + + self.assertEqual(2, len(filtered)) + + def test_annotations_filter_can_be_applied(self): + class SrcExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=0), + DatasetItem(id=1, annotations=[ + Label(0), + Label(1), + ]), + DatasetItem(id=2, annotations=[ + Label(0), + Label(2), + ]), + ]) + + class DstExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=0), + DatasetItem(id=1, annotations=[ + Label(0), + ]), + DatasetItem(id=2, annotations=[ + Label(0), + ]), + ]) + + extractor = SrcExtractor() + + filtered = XPathAnnotationsFilter(extractor, + '/item/annotation[label_id = 0]') + + self.assertListEqual(list(filtered), list(DstExtractor())) + + def test_annotations_filter_can_remove_empty_items(self): + class SrcExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=0), + DatasetItem(id=1, annotations=[ + Label(0), + Label(1), + ]), + DatasetItem(id=2, annotations=[ + Label(0), + Label(2), + ]), + ]) + + class DstExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=2, annotations=[ + Label(2), + ]), + ]) + + extractor = SrcExtractor() + + filtered = XPathAnnotationsFilter(extractor, + '/item/annotation[label_id = 2]', remove_empty=True) + + self.assertListEqual(list(filtered), list(DstExtractor())) + +class ConfigTest(TestCase): + def test_can_produce_multilayer_config_from_dict(self): + schema_low = SchemaBuilder() \ + .add('options', dict) \ + .build() + schema_mid = SchemaBuilder() \ + .add('desc', lambda: Config(schema=schema_low)) \ + .build() + schema_top = SchemaBuilder() \ + .add('container', lambda: DefaultConfig( + lambda v: Config(v, schema=schema_mid))) \ + .build() + + value = 1 + source = Config({ + 'container': { + 'elem': { + 'desc': { + 'options': { + 'k': value + } + } + } + } + }, schema=schema_top) + + self.assertEqual(value, source.container['elem'].desc.options['k']) + +class ExtractorTest(TestCase): + def test_custom_extractor_can_be_created(self): + class CustomExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=0, subset='train'), + DatasetItem(id=1, subset='train'), + DatasetItem(id=2, subset='train'), + + DatasetItem(id=3, subset='test'), + DatasetItem(id=4, subset='test'), + + DatasetItem(id=1), + DatasetItem(id=2), + DatasetItem(id=3), + ]) + + extractor_name = 'ext1' + project = Project() + project.env.extractors.register(extractor_name, CustomExtractor) + project.add_source('src1', { + 'url': 'path', + 'format': extractor_name, + }) + + dataset = project.make_dataset() + + compare_datasets(self, CustomExtractor(), dataset) + +class DatasetTest(TestCase): + def test_create_from_extractors(self): + class SrcExtractor1(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, subset='train', annotations=[ + Bbox(1, 2, 3, 4), + Label(4), + ]), + DatasetItem(id=1, subset='val', annotations=[ + Label(4), + ]), + ]) + + class SrcExtractor2(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, subset='val', annotations=[ + Label(5), + ]), + ]) + + class DstExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, subset='train', annotations=[ + Bbox(1, 2, 3, 4), + Label(4), + ]), + DatasetItem(id=1, subset='val', annotations=[ + Label(4), + Label(5), + ]), + ]) + + dataset = Dataset.from_extractors(SrcExtractor1(), SrcExtractor2()) + + compare_datasets(self, DstExtractor(), dataset) + + +class DatasetItemTest(TestCase): + def test_ctor_requires_id(self): + with self.assertRaises(Exception): + # pylint: disable=no-value-for-parameter + DatasetItem() + # pylint: enable=no-value-for-parameter + + @staticmethod + def test_ctors_with_image(): + for args in [ + { 'id': 0, 'image': None }, + { 'id': 0, 'image': 'path.jpg' }, + { 'id': 0, 'image': np.array([1, 2, 3]) }, + { 'id': 0, 'image': lambda f: np.array([1, 2, 3]) }, + { 'id': 0, 'image': Image(data=np.array([1, 2, 3])) }, + ]: + DatasetItem(**args) \ No newline at end of file diff --git a/datumaro/tests/test_tfrecord_format.py b/datumaro/tests/test_tfrecord_format.py new file mode 100644 index 00000000000..0bd29ae4179 --- /dev/null +++ b/datumaro/tests/test_tfrecord_format.py @@ -0,0 +1,172 @@ +import numpy as np + +from unittest import TestCase + +from datumaro.components.extractor import (Extractor, DatasetItem, + AnnotationType, Bbox, Mask, LabelCategories +) +from datumaro.plugins.tf_detection_api_format.importer import TfDetectionApiImporter +from datumaro.plugins.tf_detection_api_format.extractor import TfDetectionApiExtractor +from datumaro.plugins.tf_detection_api_format.converter import TfDetectionApiConverter +from datumaro.util.image import Image +from datumaro.util.test_utils import TestDir, compare_datasets + + +class TfrecordConverterTest(TestCase): + def _test_save_and_load(self, source_dataset, converter, test_dir, + target_dataset=None, importer_args=None): + converter(source_dataset, test_dir) + + if importer_args is None: + importer_args = {} + parsed_dataset = TfDetectionApiImporter()(test_dir, **importer_args) \ + .make_dataset() + + if target_dataset is None: + target_dataset = source_dataset + + compare_datasets(self, expected=target_dataset, actual=parsed_dataset) + + def test_can_save_bboxes(self): + class TestExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, subset='train', + image=np.ones((16, 16, 3)), + annotations=[ + Bbox(0, 4, 4, 8, label=2), + Bbox(0, 4, 4, 4, label=3), + Bbox(2, 4, 4, 4), + ] + ), + + DatasetItem(id=2, subset='val', + image=np.ones((8, 8, 3)), + annotations=[ + Bbox(1, 2, 4, 2, label=3), + ] + ), + + DatasetItem(id=3, subset='test', + image=np.ones((5, 4, 3)) * 3, + ), + ]) + + def categories(self): + label_cat = LabelCategories() + for label in range(10): + label_cat.add('label_' + str(label)) + return { + AnnotationType.label: label_cat, + } + + with TestDir() as test_dir: + self._test_save_and_load( + TestExtractor(), TfDetectionApiConverter(save_images=True), + test_dir) + + def test_can_save_masks(self): + class TestExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, subset='train', image=np.ones((4, 5, 3)), + annotations=[ + Mask(image=np.array([ + [1, 0, 0, 1], + [0, 1, 1, 0], + [0, 1, 1, 0], + [1, 0, 0, 1], + ]), label=1), + ] + ), + ]) + + def categories(self): + label_cat = LabelCategories() + for label in range(10): + label_cat.add('label_' + str(label)) + return { + AnnotationType.label: label_cat, + } + + with TestDir() as test_dir: + self._test_save_and_load( + TestExtractor(), TfDetectionApiConverter(save_masks=True), + test_dir) + + def test_can_save_dataset_with_no_subsets(self): + class TestExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, + image=np.ones((16, 16, 3)), + annotations=[ + Bbox(2, 1, 4, 4, label=2), + Bbox(4, 2, 8, 4, label=3), + ] + ), + + DatasetItem(id=2, + image=np.ones((8, 8, 3)) * 2, + annotations=[ + Bbox(4, 4, 4, 4, label=3), + ] + ), + + DatasetItem(id=3, + image=np.ones((8, 4, 3)) * 3, + ), + ]) + + def categories(self): + label_cat = LabelCategories() + for label in range(10): + label_cat.add('label_' + str(label)) + return { + AnnotationType.label: label_cat, + } + + with TestDir() as test_dir: + self._test_save_and_load( + TestExtractor(), TfDetectionApiConverter(save_images=True), + test_dir) + + def test_can_save_dataset_with_image_info(self): + class TestExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, image=Image(path='1/q.e', size=(10, 15))), + ]) + + def categories(self): + return { AnnotationType.label: LabelCategories() } + + with TestDir() as test_dir: + self._test_save_and_load(TestExtractor(), + TfDetectionApiConverter(), test_dir) + + def test_labelmap_parsing(self): + text = """ + { + id: 4 + name: 'qw1' + } + { + id: 5 name: 'qw2' + } + + { + name: 'qw3' + id: 6 + } + {name:'qw4' id:7} + """ + expected = { + 'qw1': 4, + 'qw2': 5, + 'qw3': 6, + 'qw4': 7, + } + parsed = TfDetectionApiExtractor._parse_labelmap(text) + + self.assertEqual(expected, parsed) diff --git a/datumaro/tests/test_transforms.py b/datumaro/tests/test_transforms.py new file mode 100644 index 00000000000..58c677a275d --- /dev/null +++ b/datumaro/tests/test_transforms.py @@ -0,0 +1,457 @@ +import numpy as np + +from unittest import TestCase + +from datumaro.components.extractor import (Extractor, DatasetItem, + Mask, Polygon, PolyLine, Points, Bbox, Label, + LabelCategories, MaskCategories, AnnotationType +) +import datumaro.util.mask_tools as mask_tools +import datumaro.plugins.transforms as transforms +from datumaro.util.test_utils import compare_datasets + + +class TransformsTest(TestCase): + def test_reindex(self): + class SrcExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=10), + DatasetItem(id=10, subset='train'), + DatasetItem(id='a', subset='val'), + ]) + + class DstExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=5), + DatasetItem(id=6, subset='train'), + DatasetItem(id=7, subset='val'), + ]) + + actual = transforms.Reindex(SrcExtractor(), start=5) + compare_datasets(self, DstExtractor(), actual) + + def test_mask_to_polygons(self): + class SrcExtractor(Extractor): + def __iter__(self): + items = [ + DatasetItem(id=1, image=np.zeros((5, 10, 3)), + annotations=[ + Mask(np.array([ + [0, 1, 1, 1, 0, 1, 1, 1, 1, 0], + [0, 0, 1, 1, 0, 1, 1, 1, 0, 0], + [0, 0, 0, 1, 0, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ]), + ), + ] + ), + ] + return iter(items) + + class DstExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, image=np.zeros((5, 10, 3)), + annotations=[ + Polygon([3.0, 2.5, 1.0, 0.0, 3.5, 0.0, 3.0, 2.5]), + Polygon([5.0, 3.5, 4.5, 0.0, 8.0, 0.0, 5.0, 3.5]), + ] + ), + ]) + + actual = transforms.MasksToPolygons(SrcExtractor()) + compare_datasets(self, DstExtractor(), actual) + + def test_polygons_to_masks(self): + class SrcExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, image=np.zeros((5, 10, 3)), + annotations=[ + Polygon([0, 0, 4, 0, 4, 4]), + Polygon([5, 0, 9, 0, 5, 5]), + ] + ), + ]) + + class DstExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, image=np.zeros((5, 10, 3)), + annotations=[ + Mask(np.array([ + [0, 0, 0, 0, 0, 1, 1, 1, 1, 0], + [0, 0, 0, 0, 0, 1, 1, 1, 0, 0], + [0, 0, 0, 0, 0, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ]), + ), + Mask(np.array([ + [0, 1, 1, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ]), + ), + ] + ), + ]) + + actual = transforms.PolygonsToMasks(SrcExtractor()) + compare_datasets(self, DstExtractor(), actual) + + def test_crop_covered_segments(self): + class SrcExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, image=np.zeros((5, 5, 3)), + annotations=[ + # The mask is partially covered by the polygon + Mask(np.array([ + [0, 0, 1, 1, 1], + [0, 0, 1, 1, 1], + [1, 1, 1, 1, 1], + [1, 1, 1, 0, 0], + [1, 1, 1, 0, 0]], + ), + z_order=0), + Polygon([1, 1, 4, 1, 4, 4, 1, 4], + z_order=1), + ] + ), + ]) + + class DstExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, image=np.zeros((5, 5, 3)), + annotations=[ + Mask(np.array([ + [0, 0, 1, 1, 1], + [0, 0, 0, 0, 1], + [1, 0, 0, 0, 1], + [1, 0, 0, 0, 0], + [1, 1, 1, 0, 0]], + ), + z_order=0), + Polygon([1, 1, 4, 1, 4, 4, 1, 4], + z_order=1), + ] + ), + ]) + + actual = transforms.CropCoveredSegments(SrcExtractor()) + compare_datasets(self, DstExtractor(), actual) + + def test_merge_instance_segments(self): + class SrcExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, image=np.zeros((5, 5, 3)), + annotations=[ + Mask(np.array([ + [0, 0, 1, 1, 1], + [0, 0, 0, 0, 1], + [1, 0, 0, 0, 1], + [1, 0, 0, 0, 0], + [1, 1, 1, 0, 0]], + ), + z_order=0, group=1), + Polygon([1, 1, 4, 1, 4, 4, 1, 4], + z_order=1, group=1), + Polygon([0, 0, 0, 2, 2, 2, 2, 0], + z_order=1), + ] + ), + ]) + + class DstExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, image=np.zeros((5, 5, 3)), + annotations=[ + Mask(np.array([ + [0, 0, 1, 1, 1], + [0, 1, 1, 1, 1], + [1, 1, 1, 1, 1], + [1, 1, 1, 1, 0], + [1, 1, 1, 0, 0]], + ), + z_order=0, group=1), + Mask(np.array([ + [1, 1, 0, 0, 0], + [1, 1, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0]], + ), + z_order=1), + ] + ), + ]) + + actual = transforms.MergeInstanceSegments(SrcExtractor(), + include_polygons=True) + compare_datasets(self, DstExtractor(), actual) + + def test_map_subsets(self): + class SrcExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, subset='a'), + DatasetItem(id=2, subset='b'), + DatasetItem(id=3, subset='c'), + ]) + + class DstExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, subset=''), + DatasetItem(id=2, subset='a'), + DatasetItem(id=3, subset='c'), + ]) + + actual = transforms.MapSubsets(SrcExtractor(), + { 'a': '', 'b': 'a' }) + compare_datasets(self, DstExtractor(), actual) + + def test_shapes_to_boxes(self): + class SrcExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, image=np.zeros((5, 5, 3)), + annotations=[ + Mask(np.array([ + [0, 0, 1, 1, 1], + [0, 0, 0, 0, 1], + [1, 0, 0, 0, 1], + [1, 0, 0, 0, 0], + [1, 1, 1, 0, 0]], + ), id=1), + Polygon([1, 1, 4, 1, 4, 4, 1, 4], id=2), + PolyLine([1, 1, 2, 1, 2, 2, 1, 2], id=3), + Points([2, 2, 4, 2, 4, 4, 2, 4], id=4), + ] + ), + ]) + + class DstExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, image=np.zeros((5, 5, 3)), + annotations=[ + Bbox(0, 0, 4, 4, id=1), + Bbox(1, 1, 3, 3, id=2), + Bbox(1, 1, 1, 1, id=3), + Bbox(2, 2, 2, 2, id=4), + ] + ), + ]) + + actual = transforms.ShapesToBoxes(SrcExtractor()) + compare_datasets(self, DstExtractor(), actual) + + def test_id_from_image(self): + class SrcExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, image='path.jpg'), + DatasetItem(id=2), + ]) + + class DstExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id='path', image='path.jpg'), + DatasetItem(id=2), + ]) + + actual = transforms.IdFromImageName(SrcExtractor()) + compare_datasets(self, DstExtractor(), actual) + + def test_boxes_to_masks(self): + class SrcExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, image=np.zeros((5, 5, 3)), + annotations=[ + Bbox(0, 0, 3, 3, z_order=1), + Bbox(0, 0, 3, 1, z_order=2), + Bbox(0, 2, 3, 1, z_order=3), + ] + ), + ]) + + class DstExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, image=np.zeros((5, 5, 3)), + annotations=[ + Mask(np.array([ + [1, 1, 1, 0, 0], + [1, 1, 1, 0, 0], + [1, 1, 1, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0]], + ), + z_order=1), + Mask(np.array([ + [1, 1, 1, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0]], + ), + z_order=2), + Mask(np.array([ + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [1, 1, 1, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0]], + ), + z_order=3), + ] + ), + ]) + + actual = transforms.BoxesToMasks(SrcExtractor()) + compare_datasets(self, DstExtractor(), actual) + + def test_random_split(self): + class SrcExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, subset="a"), + DatasetItem(id=2, subset="a"), + DatasetItem(id=3, subset="b"), + DatasetItem(id=4, subset="b"), + DatasetItem(id=5, subset="b"), + DatasetItem(id=6, subset=""), + DatasetItem(id=7, subset=""), + ]) + + actual = transforms.RandomSplit(SrcExtractor(), splits=[ + ('train', 4.0 / 7.0), + ('test', 3.0 / 7.0), + ]) + + self.assertEqual(4, len(actual.get_subset('train'))) + self.assertEqual(3, len(actual.get_subset('test'))) + + def test_random_split_gives_error_on_wrong_ratios(self): + class SrcExtractor(Extractor): + def __iter__(self): + return iter([DatasetItem(id=1)]) + + with self.assertRaises(Exception): + transforms.RandomSplit(SrcExtractor(), splits=[ + ('train', 0.5), + ('test', 0.7), + ]) + + with self.assertRaises(Exception): + transforms.RandomSplit(SrcExtractor(), splits=[]) + + with self.assertRaises(Exception): + transforms.RandomSplit(SrcExtractor(), splits=[ + ('train', -0.5), + ('test', 1.5), + ]) + + def test_remap_labels(self): + class SrcExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, annotations=[ + # Should be remapped + Label(1), + Bbox(1, 2, 3, 4, label=2), + Mask(image=np.array([1]), label=3), + + # Should be kept + Polygon([1, 1, 2, 2, 3, 4], label=4), + PolyLine([1, 3, 4, 2, 5, 6], label=None) + ]), + ]) + + def categories(self): + label_cat = LabelCategories() + label_cat.add('label0') + label_cat.add('label1') + label_cat.add('label2') + label_cat.add('label3') + label_cat.add('label4') + + mask_cat = MaskCategories( + colormap=mask_tools.generate_colormap(5)) + + return { + AnnotationType.label: label_cat, + AnnotationType.mask: mask_cat, + } + + class DstExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, annotations=[ + Label(1), + Bbox(1, 2, 3, 4, label=0), + Mask(image=np.array([1]), label=1), + + Polygon([1, 1, 2, 2, 3, 4], label=2), + PolyLine([1, 3, 4, 2, 5, 6], label=None) + ]), + ]) + + def categories(self): + label_cat = LabelCategories() + label_cat.add('label0') + label_cat.add('label9') + label_cat.add('label4') + + mask_cat = MaskCategories(colormap={ + k: v for k, v in mask_tools.generate_colormap(5).items() + if k in { 0, 1, 3, 4 } + }) + + return { + AnnotationType.label: label_cat, + AnnotationType.mask: mask_cat, + } + + actual = transforms.RemapLabels(SrcExtractor(), mapping={ + 'label1': 'label9', + 'label2': 'label0', + 'label3': 'label9', + }, default='keep') + + compare_datasets(self, DstExtractor(), actual) + + def test_remap_labels_delete_unspecified(self): + class SrcExtractor(Extractor): + def __iter__(self): + return iter([ DatasetItem(id=1, annotations=[ Label(0) ]) ]) + + def categories(self): + label_cat = LabelCategories() + label_cat.add('label0') + + return { AnnotationType.label: label_cat } + + class DstExtractor(Extractor): + def __iter__(self): + return iter([ DatasetItem(id=1, annotations=[]) ]) + + def categories(self): + return { AnnotationType.label: LabelCategories() } + + actual = transforms.RemapLabels(SrcExtractor(), + mapping={}, default='delete') + + compare_datasets(self, DstExtractor(), actual) diff --git a/datumaro/tests/test_voc_format.py b/datumaro/tests/test_voc_format.py new file mode 100644 index 00000000000..b91ee1a9325 --- /dev/null +++ b/datumaro/tests/test_voc_format.py @@ -0,0 +1,777 @@ +from collections import OrderedDict +import numpy as np +import os +import os.path as osp +from xml.etree import ElementTree as ET +import shutil + +from unittest import TestCase + +from datumaro.components.extractor import (Extractor, DatasetItem, + AnnotationType, Label, Bbox, Mask, LabelCategories, +) +import datumaro.plugins.voc_format.format as VOC +from datumaro.plugins.voc_format.extractor import ( + VocClassificationExtractor, + VocDetectionExtractor, + VocSegmentationExtractor, + VocLayoutExtractor, + VocActionExtractor, +) +from datumaro.plugins.voc_format.converter import ( + VocConverter, + VocClassificationConverter, + VocDetectionConverter, + VocLayoutConverter, + VocActionConverter, + VocSegmentationConverter, +) +from datumaro.plugins.voc_format.importer import VocImporter +from datumaro.components.project import Project +from datumaro.util.image import save_image, Image +from datumaro.util.test_utils import TestDir, compare_datasets + + +class VocTest(TestCase): + def test_colormap_generator(self): + reference = np.array([ + [ 0, 0, 0], + [128, 0, 0], + [ 0, 128, 0], + [128, 128, 0], + [ 0, 0, 128], + [128, 0, 128], + [ 0, 128, 128], + [128, 128, 128], + [ 64, 0, 0], + [192, 0, 0], + [ 64, 128, 0], + [192, 128, 0], + [ 64, 0, 128], + [192, 0, 128], + [ 64, 128, 128], + [192, 128, 128], + [ 0, 64, 0], + [128, 64, 0], + [ 0, 192, 0], + [128, 192, 0], + [ 0, 64, 128], + [224, 224, 192], # ignored + ]) + + self.assertTrue(np.array_equal(reference, list(VOC.VocColormap.values()))) + +def get_label(extractor, label_id): + return extractor.categories()[AnnotationType.label].items[label_id].name + +def generate_dummy_voc(path): + cls_subsets_dir = osp.join(path, 'ImageSets', 'Main') + action_subsets_dir = osp.join(path, 'ImageSets', 'Action') + layout_subsets_dir = osp.join(path, 'ImageSets', 'Layout') + segm_subsets_dir = osp.join(path, 'ImageSets', 'Segmentation') + ann_dir = osp.join(path, 'Annotations') + img_dir = osp.join(path, 'JPEGImages') + segm_dir = osp.join(path, 'SegmentationClass') + inst_dir = osp.join(path, 'SegmentationObject') + + os.makedirs(cls_subsets_dir) + os.makedirs(ann_dir) + os.makedirs(img_dir) + os.makedirs(segm_dir) + os.makedirs(inst_dir) + + subsets = { + 'train': ['2007_000001'], + 'test': ['2007_000002'], + } + + # Subsets + for subset_name, subset in subsets.items(): + for item in subset: + with open(osp.join(cls_subsets_dir, subset_name + '.txt'), 'w') as f: + for item in subset: + f.write('%s\n' % item) + shutil.copytree(cls_subsets_dir, action_subsets_dir) + shutil.copytree(cls_subsets_dir, layout_subsets_dir) + shutil.copytree(cls_subsets_dir, segm_subsets_dir) + + # Classification + subset_name = 'train' + subset = subsets[subset_name] + for label in VOC.VocLabel: + with open(osp.join(cls_subsets_dir, '%s_%s.txt' % \ + (label.name, subset_name)), 'w') as f: + for item in subset: + presence = label.value % 2 + f.write('%s %2d\n' % (item, 1 if presence else -1)) + + # Detection + Action + Layout + subset_name = 'train' + subset = subsets[subset_name] + for item in subset: + root_elem = ET.Element('annotation') + ET.SubElement(root_elem, 'folder').text = 'VOC' + item.split('_')[0] + ET.SubElement(root_elem, 'filename').text = item + '.jpg' + + size_elem = ET.SubElement(root_elem, 'size') + ET.SubElement(size_elem, 'width').text = '10' + ET.SubElement(size_elem, 'height').text = '20' + ET.SubElement(size_elem, 'depth').text = '3' + + ET.SubElement(root_elem, 'segmented').text = '1' + + obj1_elem = ET.SubElement(root_elem, 'object') + ET.SubElement(obj1_elem, 'name').text = 'cat' + ET.SubElement(obj1_elem, 'pose').text = VOC.VocPose(1).name + ET.SubElement(obj1_elem, 'truncated').text = '1' + ET.SubElement(obj1_elem, 'difficult').text = '0' + obj1bb_elem = ET.SubElement(obj1_elem, 'bndbox') + ET.SubElement(obj1bb_elem, 'xmin').text = '1' + ET.SubElement(obj1bb_elem, 'ymin').text = '2' + ET.SubElement(obj1bb_elem, 'xmax').text = '3' + ET.SubElement(obj1bb_elem, 'ymax').text = '4' + + obj2_elem = ET.SubElement(root_elem, 'object') + ET.SubElement(obj2_elem, 'name').text = 'person' + obj2bb_elem = ET.SubElement(obj2_elem, 'bndbox') + ET.SubElement(obj2bb_elem, 'xmin').text = '4' + ET.SubElement(obj2bb_elem, 'ymin').text = '5' + ET.SubElement(obj2bb_elem, 'xmax').text = '6' + ET.SubElement(obj2bb_elem, 'ymax').text = '7' + obj2head_elem = ET.SubElement(obj2_elem, 'part') + ET.SubElement(obj2head_elem, 'name').text = VOC.VocBodyPart(1).name + obj2headbb_elem = ET.SubElement(obj2head_elem, 'bndbox') + ET.SubElement(obj2headbb_elem, 'xmin').text = '5.5' + ET.SubElement(obj2headbb_elem, 'ymin').text = '6' + ET.SubElement(obj2headbb_elem, 'xmax').text = '7.5' + ET.SubElement(obj2headbb_elem, 'ymax').text = '8' + obj2act_elem = ET.SubElement(obj2_elem, 'actions') + for act in VOC.VocAction: + ET.SubElement(obj2act_elem, act.name).text = '%s' % (act.value % 2) + + with open(osp.join(ann_dir, item + '.xml'), 'w') as f: + f.write(ET.tostring(root_elem, encoding='unicode')) + + # Segmentation + Instances + subset_name = 'train' + subset = subsets[subset_name] + for item in subset: + save_image(osp.join(segm_dir, item + '.png'), + np.tile(VOC.VocColormap[2][::-1], (5, 10, 1)) + ) + save_image(osp.join(inst_dir, item + '.png'), + np.tile(1, (5, 10, 1))) + + # Test images + subset_name = 'test' + subset = subsets[subset_name] + for item in subset: + save_image(osp.join(img_dir, item + '.jpg'), + np.ones([10, 20, 3])) + + return subsets + +class TestExtractorBase(Extractor): + def _label(self, voc_label): + return self.categories()[AnnotationType.label].find(voc_label)[0] + + def categories(self): + return VOC.make_voc_categories() + +class VocExtractorTest(TestCase): + def test_can_load_voc_cls(self): + class DstExtractor(TestExtractorBase): + def __iter__(self): + return iter([ + DatasetItem(id='2007_000001', subset='train', + annotations=[ + Label(self._label(l.name)) + for l in VOC.VocLabel if l.value % 2 == 1 + ] + ), + + DatasetItem(id='2007_000002', subset='test') + ]) + + with TestDir() as test_dir: + generate_dummy_voc(test_dir) + parsed_dataset = VocClassificationExtractor(test_dir) + compare_datasets(self, DstExtractor(), parsed_dataset) + + def test_can_load_voc_det(self): + class DstExtractor(TestExtractorBase): + def __iter__(self): + return iter([ + DatasetItem(id='2007_000001', subset='train', + annotations=[ + Bbox(1, 2, 2, 2, label=self._label('cat'), + attributes={ + 'pose': VOC.VocPose(1).name, + 'truncated': True, + 'difficult': False, + 'occluded': False, + }, + id=1, group=1, + ), + Bbox(4, 5, 2, 2, label=self._label('person'), + attributes={ + 'truncated': False, + 'difficult': False, + 'occluded': False, + **{ + a.name: a.value % 2 == 1 + for a in VOC.VocAction + } + }, + id=2, group=2, + # TODO: Actions and group should be excluded + # as soon as correct merge is implemented + ), + ] + ), + + DatasetItem(id='2007_000002', subset='test') + ]) + + with TestDir() as test_dir: + generate_dummy_voc(test_dir) + parsed_dataset = VocDetectionExtractor(test_dir) + compare_datasets(self, DstExtractor(), parsed_dataset) + + def test_can_load_voc_segm(self): + class DstExtractor(TestExtractorBase): + def __iter__(self): + return iter([ + DatasetItem(id='2007_000001', subset='train', + annotations=[ + Mask(image=np.ones([5, 10]), + label=self._label(VOC.VocLabel(2).name), + group=1, + ), + ] + ), + + DatasetItem(id='2007_000002', subset='test') + ]) + + with TestDir() as test_dir: + generate_dummy_voc(test_dir) + parsed_dataset = VocSegmentationExtractor(test_dir) + compare_datasets(self, DstExtractor(), parsed_dataset) + + def test_can_load_voc_layout(self): + class DstExtractor(TestExtractorBase): + def __iter__(self): + return iter([ + DatasetItem(id='2007_000001', subset='train', + annotations=[ + Bbox(4, 5, 2, 2, label=self._label('person'), + attributes={ + 'truncated': False, + 'difficult': False, + 'occluded': False, + **{ + a.name: a.value % 2 == 1 + for a in VOC.VocAction + } + }, + id=2, group=2, + # TODO: Actions should be excluded + # as soon as correct merge is implemented + ), + Bbox(5.5, 6, 2, 2, label=self._label( + VOC.VocBodyPart(1).name), + group=2 + ) + ] + ), + + DatasetItem(id='2007_000002', subset='test') + ]) + + with TestDir() as test_dir: + generate_dummy_voc(test_dir) + parsed_dataset = VocLayoutExtractor(test_dir) + compare_datasets(self, DstExtractor(), parsed_dataset) + + def test_can_load_voc_action(self): + class DstExtractor(TestExtractorBase): + def __iter__(self): + return iter([ + DatasetItem(id='2007_000001', subset='train', + annotations=[ + Bbox(4, 5, 2, 2, label=self._label('person'), + attributes={ + 'truncated': False, + 'difficult': False, + 'occluded': False, + **{ + a.name: a.value % 2 == 1 + for a in VOC.VocAction + } + # TODO: group should be excluded + # as soon as correct merge is implemented + }, + id=2, group=2, + ), + ] + ), + + DatasetItem(id='2007_000002', subset='test') + ]) + + with TestDir() as test_dir: + generate_dummy_voc(test_dir) + parsed_dataset = VocActionExtractor(test_dir) + compare_datasets(self, DstExtractor(), parsed_dataset) + +class VocConverterTest(TestCase): + def _test_save_and_load(self, source_dataset, converter, test_dir, + target_dataset=None, importer_args=None): + converter(source_dataset, test_dir) + + if importer_args is None: + importer_args = {} + parsed_dataset = VocImporter()(test_dir, **importer_args).make_dataset() + + if target_dataset is None: + target_dataset = source_dataset + + compare_datasets(self, expected=target_dataset, actual=parsed_dataset) + + def test_can_save_voc_cls(self): + class TestExtractor(TestExtractorBase): + def __iter__(self): + return iter([ + DatasetItem(id=0, subset='a', annotations=[ + Label(1), + Label(2), + Label(3), + ]), + + DatasetItem(id=1, subset='b', annotations=[ + Label(4), + ]), + ]) + + with TestDir() as test_dir: + self._test_save_and_load(TestExtractor(), + VocClassificationConverter(label_map='voc'), test_dir) + + def test_can_save_voc_det(self): + class TestExtractor(TestExtractorBase): + def __iter__(self): + return iter([ + DatasetItem(id=1, subset='a', annotations=[ + Bbox(2, 3, 4, 5, label=2, + attributes={ 'occluded': True } + ), + Bbox(2, 3, 4, 5, label=3, + attributes={ 'truncated': True }, + ), + ]), + + DatasetItem(id=2, subset='b', annotations=[ + Bbox(5, 4, 6, 5, label=3, + attributes={ 'difficult': True }, + ), + ]), + ]) + + class DstExtractor(TestExtractorBase): + def __iter__(self): + return iter([ + DatasetItem(id=1, subset='a', annotations=[ + Bbox(2, 3, 4, 5, label=2, id=1, group=1, + attributes={ + 'truncated': False, + 'difficult': False, + 'occluded': True, + } + ), + Bbox(2, 3, 4, 5, label=3, id=2, group=2, + attributes={ + 'truncated': True, + 'difficult': False, + 'occluded': False, + }, + ), + ]), + + DatasetItem(id=2, subset='b', annotations=[ + Bbox(5, 4, 6, 5, label=3, id=1, group=1, + attributes={ + 'truncated': False, + 'difficult': True, + 'occluded': False, + }, + ), + ]), + ]) + + with TestDir() as test_dir: + self._test_save_and_load(TestExtractor(), + VocDetectionConverter(label_map='voc'), test_dir, + target_dataset=DstExtractor()) + + def test_can_save_voc_segm(self): + class TestExtractor(TestExtractorBase): + def __iter__(self): + return iter([ + DatasetItem(id=1, subset='a', annotations=[ + # overlapping masks, the first should be truncated + # the second and third are different instances + Mask(image=np.array([[0, 1, 1, 1, 0]]), label=4, + z_order=1), + Mask(image=np.array([[1, 1, 0, 0, 0]]), label=3, + z_order=2), + Mask(image=np.array([[0, 0, 0, 1, 0]]), label=3, + z_order=2), + ]), + ]) + + class DstExtractor(TestExtractorBase): + def __iter__(self): + return iter([ + DatasetItem(id=1, subset='a', annotations=[ + Mask(image=np.array([[0, 0, 1, 0, 0]]), label=4, + group=1), + Mask(image=np.array([[1, 1, 0, 0, 0]]), label=3, + group=2), + Mask(image=np.array([[0, 0, 0, 1, 0]]), label=3, + group=3), + ]), + ]) + + with TestDir() as test_dir: + self._test_save_and_load(TestExtractor(), + VocSegmentationConverter(label_map='voc'), test_dir, + target_dataset=DstExtractor()) + + def test_can_save_voc_layout(self): + class TestExtractor(TestExtractorBase): + def __iter__(self): + return iter([ + DatasetItem(id=1, subset='a', annotations=[ + Bbox(2, 3, 4, 5, label=2, id=1, group=1, + attributes={ + 'pose': VOC.VocPose(1).name, + 'truncated': True, + 'difficult': False, + 'occluded': False, + } + ), + Bbox(2, 3, 1, 1, label=self._label( + VOC.VocBodyPart(1).name), group=1), + Bbox(5, 4, 3, 2, label=self._label( + VOC.VocBodyPart(2).name), group=1), + ]), + ]) + + with TestDir() as test_dir: + self._test_save_and_load(TestExtractor(), + VocLayoutConverter(label_map='voc'), test_dir) + + def test_can_save_voc_action(self): + class TestExtractor(TestExtractorBase): + def __iter__(self): + return iter([ + DatasetItem(id=1, subset='a', annotations=[ + Bbox(2, 3, 4, 5, label=2, + attributes={ + 'truncated': True, + VOC.VocAction(1).name: True, + VOC.VocAction(2).name: True, + } + ), + Bbox(5, 4, 3, 2, label=self._label('person'), + attributes={ + 'truncated': True, + VOC.VocAction(1).name: True, + VOC.VocAction(2).name: True, + } + ), + ]), + ]) + + class DstExtractor(TestExtractorBase): + def __iter__(self): + return iter([ + DatasetItem(id=1, subset='a', annotations=[ + Bbox(2, 3, 4, 5, label=2, + id=1, group=1, attributes={ + 'truncated': True, + 'difficult': False, + 'occluded': False, + # no attributes here in the label categories + } + ), + Bbox(5, 4, 3, 2, label=self._label('person'), + id=2, group=2, attributes={ + 'truncated': True, + 'difficult': False, + 'occluded': False, + VOC.VocAction(1).name: True, + VOC.VocAction(2).name: True, + **{ + a.name: False for a in VOC.VocAction + if a.value not in {1, 2} + } + } + ), + ]), + ]) + + with TestDir() as test_dir: + self._test_save_and_load(TestExtractor(), + VocActionConverter(label_map='voc'), test_dir, + target_dataset=DstExtractor()) + + def test_can_save_dataset_with_no_subsets(self): + class TestExtractor(TestExtractorBase): + def __iter__(self): + return iter([ + DatasetItem(id=1, annotations=[ + Label(2), + Label(3), + ]), + + DatasetItem(id=2, annotations=[ + Label(3), + ]), + ]) + + with TestDir() as test_dir: + self._test_save_and_load(TestExtractor(), + VocConverter(label_map='voc'), test_dir) + + def test_can_save_dataset_with_images(self): + class TestExtractor(TestExtractorBase): + def __iter__(self): + return iter([ + DatasetItem(id=1, subset='a', image=np.ones([4, 5, 3])), + DatasetItem(id=2, subset='a', image=np.ones([5, 4, 3])), + + DatasetItem(id=3, subset='b', image=np.ones([2, 6, 3])), + ]) + + with TestDir() as test_dir: + self._test_save_and_load(TestExtractor(), + VocConverter(label_map='voc', save_images=True), test_dir) + + def test_dataset_with_voc_labelmap(self): + class SrcExtractor(TestExtractorBase): + def __iter__(self): + yield DatasetItem(id=1, annotations=[ + Bbox(2, 3, 4, 5, label=self._label('cat'), id=1), + Bbox(1, 2, 3, 4, label=self._label('non_voc_label'), id=2), + ]) + + def categories(self): + label_cat = LabelCategories() + label_cat.add(VOC.VocLabel.cat.name) + label_cat.add('non_voc_label') + return { + AnnotationType.label: label_cat, + } + + class DstExtractor(TestExtractorBase): + def __iter__(self): + yield DatasetItem(id=1, annotations=[ + # drop non voc label + Bbox(2, 3, 4, 5, label=self._label('cat'), id=1, group=1, + attributes={ + 'truncated': False, + 'difficult': False, + 'occluded': False, + } + ), + ]) + + def categories(self): + return VOC.make_voc_categories() + + with TestDir() as test_dir: + self._test_save_and_load( + SrcExtractor(), VocConverter(label_map='voc'), + test_dir, target_dataset=DstExtractor()) + + def test_dataset_with_guessed_labelmap(self): + class SrcExtractor(TestExtractorBase): + def __iter__(self): + yield DatasetItem(id=1, annotations=[ + Bbox(2, 3, 4, 5, label=0, id=1), + Bbox(1, 2, 3, 4, label=1, id=2), + ]) + + def categories(self): + label_cat = LabelCategories() + label_cat.add(VOC.VocLabel(1).name) + label_cat.add('non_voc_label') + return { + AnnotationType.label: label_cat, + } + + class DstExtractor(TestExtractorBase): + def __iter__(self): + yield DatasetItem(id=1, annotations=[ + Bbox(2, 3, 4, 5, label=self._label(VOC.VocLabel(1).name), + id=1, group=1, attributes={ + 'truncated': False, + 'difficult': False, + 'occluded': False, + } + ), + Bbox(1, 2, 3, 4, label=self._label('non_voc_label'), + id=2, group=2, attributes={ + 'truncated': False, + 'difficult': False, + 'occluded': False, + } + ), + ]) + + def categories(self): + label_map = VOC.make_voc_label_map() + label_map['non_voc_label'] = [None, [], []] + for label_desc in label_map.values(): + label_desc[0] = None # rebuild colormap + return VOC.make_voc_categories(label_map) + + with TestDir() as test_dir: + self._test_save_and_load( + SrcExtractor(), VocConverter(label_map='guess'), + test_dir, target_dataset=DstExtractor()) + + def test_dataset_with_source_labelmap(self): + class SrcExtractor(TestExtractorBase): + def __iter__(self): + yield DatasetItem(id=1, annotations=[ + Bbox(2, 3, 4, 5, label=0, id=1), + Bbox(1, 2, 3, 4, label=1, id=2), + ]) + + def categories(self): + label_cat = LabelCategories() + label_cat.add('label_1') + label_cat.add('label_2') + return { + AnnotationType.label: label_cat, + } + + class DstExtractor(TestExtractorBase): + def __iter__(self): + yield DatasetItem(id=1, annotations=[ + Bbox(2, 3, 4, 5, label=self._label('label_1'), + id=1, group=1, attributes={ + 'truncated': False, + 'difficult': False, + 'occluded': False, + } + ), + Bbox(1, 2, 3, 4, label=self._label('label_2'), + id=2, group=2, attributes={ + 'truncated': False, + 'difficult': False, + 'occluded': False, + } + ), + ]) + + def categories(self): + label_map = OrderedDict() + label_map['background'] = [None, [], []] + label_map['label_1'] = [None, [], []] + label_map['label_2'] = [None, [], []] + return VOC.make_voc_categories(label_map) + + with TestDir() as test_dir: + self._test_save_and_load( + SrcExtractor(), VocConverter(label_map='source'), + test_dir, target_dataset=DstExtractor()) + + def test_dataset_with_fixed_labelmap(self): + class SrcExtractor(TestExtractorBase): + def __iter__(self): + yield DatasetItem(id=1, annotations=[ + Bbox(2, 3, 4, 5, label=0, id=1), + Bbox(1, 2, 3, 4, label=1, id=2, group=2, + attributes={'act1': True}), + Bbox(2, 3, 4, 5, label=2, id=3, group=2), + Bbox(2, 3, 4, 6, label=3, id=4, group=2), + ]) + + def categories(self): + label_cat = LabelCategories() + label_cat.add('foreign_label') + label_cat.add('label', attributes=['act1', 'act2']) + label_cat.add('label_part1') + label_cat.add('label_part2') + return { + AnnotationType.label: label_cat, + } + + label_map = { + 'label': [None, ['label_part1', 'label_part2'], ['act1', 'act2']] + } + + class DstExtractor(TestExtractorBase): + def __iter__(self): + yield DatasetItem(id=1, annotations=[ + Bbox(1, 2, 3, 4, label=0, id=1, group=1, + attributes={ + 'act1': True, + 'act2': False, + 'truncated': False, + 'difficult': False, + 'occluded': False, + } + ), + Bbox(2, 3, 4, 5, label=1, group=1), + Bbox(2, 3, 4, 6, label=2, group=1), + ]) + + def categories(self): + return VOC.make_voc_categories(label_map) + + with TestDir() as test_dir: + self._test_save_and_load( + SrcExtractor(), VocConverter(label_map=label_map), + test_dir, target_dataset=DstExtractor()) + + def test_can_save_dataset_with_image_info(self): + class TestExtractor(TestExtractorBase): + def __iter__(self): + return iter([ + DatasetItem(id=1, image=Image(path='1.jpg', size=(10, 15))), + ]) + + with TestDir() as test_dir: + self._test_save_and_load(TestExtractor(), + VocConverter(label_map='voc'), test_dir) + +class VocImportTest(TestCase): + def test_can_import(self): + with TestDir() as test_dir: + subsets = generate_dummy_voc(test_dir) + + dataset = Project.import_from(test_dir, 'voc').make_dataset() + + self.assertEqual(len(VOC.VocTask), len(dataset.sources)) + self.assertEqual(set(subsets), set(dataset.subsets())) + self.assertEqual( + sum([len(s) for _, s in subsets.items()]), + len(dataset)) + +class VocFormatTest(TestCase): + def test_can_write_and_parse_labelmap(self): + src_label_map = VOC.make_voc_label_map() + src_label_map['qq'] = [None, ['part1', 'part2'], ['act1', 'act2']] + + with TestDir() as test_dir: + file_path = osp.join(test_dir, 'test.txt') + + VOC.write_label_map(file_path, src_label_map) + dst_label_map = VOC.parse_label_map(file_path) + + self.assertEqual(src_label_map, dst_label_map) \ No newline at end of file diff --git a/datumaro/tests/test_yolo_format.py b/datumaro/tests/test_yolo_format.py new file mode 100644 index 00000000000..e9a95108a9e --- /dev/null +++ b/datumaro/tests/test_yolo_format.py @@ -0,0 +1,116 @@ +import numpy as np +import os.path as osp + +from unittest import TestCase + +from datumaro.components.extractor import (Extractor, DatasetItem, + AnnotationType, Bbox, LabelCategories, +) +from datumaro.plugins.yolo_format.importer import YoloImporter +from datumaro.plugins.yolo_format.converter import YoloConverter +from datumaro.util.image import Image, save_image +from datumaro.util.test_utils import TestDir, compare_datasets + + +class YoloFormatTest(TestCase): + def test_can_save_and_load(self): + class TestExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, subset='train', image=np.ones((8, 8, 3)), + annotations=[ + Bbox(0, 2, 4, 2, label=2), + Bbox(0, 1, 2, 3, label=4), + ]), + DatasetItem(id=2, subset='train', image=np.ones((10, 10, 3)), + annotations=[ + Bbox(0, 2, 4, 2, label=2), + Bbox(3, 3, 2, 3, label=4), + Bbox(2, 1, 2, 3, label=4), + ]), + + DatasetItem(id=3, subset='valid', image=np.ones((8, 8, 3)), + annotations=[ + Bbox(0, 1, 5, 2, label=2), + Bbox(0, 2, 3, 2, label=5), + Bbox(0, 2, 4, 2, label=6), + Bbox(0, 7, 3, 2, label=7), + ]), + ]) + + def categories(self): + label_categories = LabelCategories() + for i in range(10): + label_categories.add('label_' + str(i)) + return { + AnnotationType.label: label_categories, + } + + with TestDir() as test_dir: + source_dataset = TestExtractor() + + YoloConverter(save_images=True)(source_dataset, test_dir) + parsed_dataset = YoloImporter()(test_dir).make_dataset() + + compare_datasets(self, source_dataset, parsed_dataset) + + def test_can_save_dataset_with_image_info(self): + class TestExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, subset='train', + image=Image(path='1.jpg', size=(10, 15)), + annotations=[ + Bbox(0, 2, 4, 2, label=2), + Bbox(3, 3, 2, 3, label=4), + ]), + ]) + + def categories(self): + label_categories = LabelCategories() + for i in range(10): + label_categories.add('label_' + str(i)) + return { + AnnotationType.label: label_categories, + } + + with TestDir() as test_dir: + source_dataset = TestExtractor() + + YoloConverter()(source_dataset, test_dir) + + save_image(osp.join(test_dir, 'obj_train_data', '1.jpg'), + np.ones((10, 15, 3))) # put the image for dataset + parsed_dataset = YoloImporter()(test_dir).make_dataset() + + compare_datasets(self, source_dataset, parsed_dataset) + + def test_can_load_dataset_with_exact_image_info(self): + class TestExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, subset='train', + image=Image(path='1.jpg', size=(10, 15)), + annotations=[ + Bbox(0, 2, 4, 2, label=2), + Bbox(3, 3, 2, 3, label=4), + ]), + ]) + + def categories(self): + label_categories = LabelCategories() + for i in range(10): + label_categories.add('label_' + str(i)) + return { + AnnotationType.label: label_categories, + } + + with TestDir() as test_dir: + source_dataset = TestExtractor() + + YoloConverter()(source_dataset, test_dir) + + parsed_dataset = YoloImporter()(test_dir, + image_info={'1': (10, 15)}).make_dataset() + + compare_datasets(self, source_dataset, parsed_dataset) diff --git a/docker-compose.ci.yml b/docker-compose.ci.yml index 81bd47a6756..f67559124c1 100644 --- a/docker-compose.ci.yml +++ b/docker-compose.ci.yml @@ -1,12 +1,14 @@ version: "2.3" services: - cvat: + cvat_ci: + image: cvat_ci build: - args: - DJANGO_CONFIGURATION: "testing" - WITH_TESTS: "yes" network: host + context: . + dockerfile: Dockerfile.ci + depends_on: + - cvat environment: COVERALLS_REPO_TOKEN: TRAVIS: diff --git a/docker-compose.yml b/docker-compose.yml index 163bbba54c4..3bb1eb70151 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,5 @@ # -# Copyright (C) 2018 Intel Corporation +# Copyright (C) 2018-2020 Intel Corporation # # SPDX-License-Identifier: MIT # @@ -8,7 +8,7 @@ version: "2.3" services: cvat_db: container_name: cvat_db - image: postgres:10.3-alpine + image: postgres:10-alpine networks: default: aliases: @@ -17,12 +17,13 @@ services: environment: POSTGRES_USER: root POSTGRES_DB: cvat + POSTGRES_HOST_AUTH_METHOD: trust volumes: - cvat_db:/var/lib/postgresql/data cvat_redis: container_name: cvat_redis - image: redis:4.0.5-alpine + image: redis:4.0-alpine networks: default: aliases: @@ -36,8 +37,6 @@ services: depends_on: - cvat_redis - cvat_db - ports: - - "8080:8080" build: context: . args: @@ -46,15 +45,14 @@ services: no_proxy: socks_proxy: TF_ANNOTATION: "no" + AUTO_SEGMENTATION: "no" USER: "django" DJANGO_CONFIGURATION: "production" - WITH_TESTS: "no" TZ: "Etc/UTC" OPENVINO_TOOLKIT: "no" environment: DJANGO_MODWSGI_EXTRA_ARGS: "" - UI_PORT: 9080 - + ALLOWED_HOSTS: '*' volumes: - cvat_data:/home/django/data - cvat_keys:/home/django/keys @@ -63,23 +61,38 @@ services: cvat_ui: container_name: cvat_ui - image: nginx + restart: always build: - context: cvat-ui + context: . args: http_proxy: https_proxy: no_proxy: socks_proxy: - dockerfile: Dockerfile + dockerfile: Dockerfile.ui + networks: default: aliases: - ui depends_on: - cvat + + cvat_proxy: + container_name: cvat_proxy + image: nginx:stable-alpine + restart: always + depends_on: + - cvat + - cvat_ui + environment: + CVAT_HOST: localhost ports: - - "9080:80" + - "8080:80" + volumes: + - ./cvat_proxy/nginx.conf:/etc/nginx/nginx.conf:ro + - ./cvat_proxy/conf.d/cvat.conf.template:/etc/nginx/conf.d/cvat.conf.template:ro + command: /bin/sh -c "envsubst '$$CVAT_HOST' < /etc/nginx/conf.d/cvat.conf.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'" volumes: cvat_db: diff --git a/package.json b/package.json index f98b89a4853..6499b0da0da 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ }, "dependencies": {}, "devDependencies": { - "eslint": "^6.1.0", + "eslint": "^6.8.0", "eslint-config-airbnb": "^18.0.1", "eslint-plugin-import": "^2.18.2", "eslint-plugin-jsx-a11y": "^6.2.3", diff --git a/supervisord.conf b/supervisord.conf index 8ed246da09b..d5e222d2a99 100644 --- a/supervisord.conf +++ b/supervisord.conf @@ -23,7 +23,7 @@ priority=1 autorestart=true [program:rqworker_default] -command=%(ENV_HOME)s/wait-for-it.sh cvat_redis:6379 -t 0 -- bash -ic \ +command=%(ENV_HOME)s/wait-for-it.sh redis:6379 -t 0 -- bash -ic \ "exec /usr/bin/python3 %(ENV_HOME)s/manage.py rqworker -v 3 default" environment=SSH_AUTH_SOCK="/tmp/ssh-agent.sock" numprocs=2 @@ -41,6 +41,12 @@ command=%(ENV_HOME)s/wait-for-it.sh redis:6379 -t 0 -- bash -ic \ environment=SSH_AUTH_SOCK="/tmp/ssh-agent.sock" numprocs=1 +[program:rqscheduler] +command=%(ENV_HOME)s/wait-for-it.sh redis:6379 -t 0 -- bash -ic \ + "/usr/bin/python3 /usr/local/bin/rqscheduler --host redis -i 30" +environment=SSH_AUTH_SOCK="/tmp/ssh-agent.sock" +numprocs=1 + [program:runserver] ; Here need to run a couple of commands to initialize DB and copy static files. ; We cannot initialize DB on build because the DB should be online. Also some diff --git a/utils/auto_annotation/README.md b/utils/auto_annotation/README.md index 5dd3ea31af4..4bc64654305 100644 --- a/utils/auto_annotation/README.md +++ b/utils/auto_annotation/README.md @@ -4,13 +4,29 @@ A small command line program to test and run AutoAnnotation Scripts. ## Instructions -Change in to the root of the project directory and run +There are two modes to run this script in. If you already have a model uploaded into the server, and you're having +issues with running it in production, you can pass in the model name and a task id that you want to test against. ```shell -$ python cvat/utils/auto_annotation/run_modely.py --py /path/to/python/interp.py \ - --xml /path/to/xml/file.xml \ - --bin /path/to/bin/file.bin \ - --json /path/to/json/mapping/mapping.json +# Note that this module can be found in cvat/utils/auto_annotation/run_model.py +$ python /path/to/run_model.py --model-name mymodel --task-id 4 +``` + +If you're running in docker, this can be useful way to debug your model. + +``` shell +$ docker exec -it cvat bash -ic 'python3 ~/cvat/apps/auto_annotation/run_model.py --model-name my-model --task-id 4 +``` + +If you are developing an auto annotation model or you can't get something uploaded into the server, +then you'll need to specify the individual inputs. + +```shell +# Note that this module can be found in cvat/utils/auto_annotation/run_model.py +$ python path/to/run_model.py --py /path/to/python/interp.py \ + --xml /path/to/xml/file.xml \ + --bin /path/to/bin/file.bin \ + --json /path/to/json/mapping/mapping.json ``` Some programs need to run unrestricted or as an administer. Use the `--unrestriced` flag to simulate. @@ -18,35 +34,62 @@ Some programs need to run unrestricted or as an administer. Use the `--unrestric You can pass image files in to fully simulate your findings. Images are passed in as a list ```shell -$ python cvat/utils/auto_annotation/run_modely.py --py /path/to/python/interp.py \ - --xml /path/to/xml/file.xml \ - --bin /path/to/bin/file.bin \ - --json /path/to/json/mapping/mapping.json \ - --image-files /path/to/img.jpg /path2/to/img2.png /path/to/img3.jpg +$ python /path/to/run_model.py --py /path/to/python/interp.py \ + --xml /path/to/xml/file.xml \ + --bin /path/to/bin/file.bin \ + --json /path/to/json/mapping/mapping.json \ + --image-files /path/to/img.jpg /path2/to/img2.png /path/to/img3.jpg ``` Additionally, it's sometimes useful to visualize your images. Use the `--show-images` flag to have each image with the annotations pop up. ```shell -$ python cvat/utils/auto_annotation/run_modely.py --py /path/to/python/interp.py \ - --xml /path/to/xml/file.xml \ - --bin /path/to/bin/file.bin \ - --json /path/to/json/mapping/mapping.json \ - --image-files /path/to/img.jpg /path2/to/img2.png /path/to/img3.jpg \ - --show-images +$ python /path/to/run_model.py --py /path/to/python/interp.py \ + --xml /path/to/xml/file.xml \ + --bin /path/to/bin/file.bin \ + --json /path/to/json/mapping/mapping.json \ + --image-files /path/to/img.jpg /path2/to/img2.png /path/to/img3.jpg \ + --show-images +``` + +If you'd like to see the labels printed on the image, use the `--show-labels` flag + +```shell +$ python /path/to/run_model.py --py /path/to/python/interp.py \ + --xml /path/to/xml/file.xml \ + --bin /path/to/bin/file.bin \ + --json /path/to/json/mapping/mapping.json \ + --image-files /path/to/img.jpg /path2/to/img2.png /path/to/img3.jpg \ + --show-images \ + --show-labels ``` There's a command that let's you scan quickly by setting the length of time (in milliseconds) to display each image. Use the `--show-image-delay` flag and set the appropriate time. +In this example, 2000 milliseconds is 2 seconds for each image. ```shell # Display each image in a window for 2 seconds -$ python cvat/utils/auto_annotation/run_modely.py --py /path/to/python/interp.py \ - --xml /path/to/xml/file.xml \ - --bin /path/to/bin/file.bin \ - --json /path/to/json/mapping/mapping.json \ - --image-files /path/to/img.jpg /path2/to/img2.png /path/to/img3.jpg \ - --show-images - --show-image-delay 2000 +$ python /path/to/run_model.py --py /path/to/python/interp.py \ + --xml /path/to/xml/file.xml \ + --bin /path/to/bin/file.bin \ + --json /path/to/json/mapping/mapping.json \ + --image-files /path/to/img.jpg /path2/to/img2.png /path/to/img3.jpg \ + --show-images \ + --show-image-delay 2000 +``` + +Visualization isn't always enough. +The CVAT has a serialization step that can throw errors on model upload even after successful visualization. +You must install the necessary packages installed, but then you can add the `--serialize` command to ensure that your +results will serialize correctly. + +```shell +$ python /path/to/run_model.py --py /path/to/python/interp.py \ + --xml /path/to/xml/file.xml \ + --bin /path/to/bin/file.bin \ + --json /path/to/json/mapping/mapping.json \ + --image-files /path/to/img.jpg /path2/to/img2.png /path/to/img3.jpg \ + --serialize ``` diff --git a/utils/auto_annotation/run_model.py b/utils/auto_annotation/run_model.py index 8aedf1c5cfc..111141fbc9c 100644 --- a/utils/auto_annotation/run_model.py +++ b/utils/auto_annotation/run_model.py @@ -4,6 +4,8 @@ import argparse import random import logging +import fnmatch +from operator import xor import numpy as np import cv2 @@ -18,16 +20,22 @@ def _get_kwargs(): parser = argparse.ArgumentParser() - parser.add_argument('--py', required=True, help='Path to the python interpt file') - parser.add_argument('--xml', required=True, help='Path to the xml file') - parser.add_argument('--bin', required=True, help='Path to the bin file') - parser.add_argument('--json', required=True, help='Path to the JSON mapping file') + parser.add_argument('--py', help='Path to the python interpt file') + parser.add_argument('--xml', help='Path to the xml file') + parser.add_argument('--bin', help='Path to the bin file') + parser.add_argument('--json', help='Path to the JSON mapping file') + + parser.add_argument('--model-name', help='Name of the model in the Model Manager') + parser.add_argument('--task-id', type=int, help='ID task used to test the model') + parser.add_argument('--restricted', dest='restricted', action='store_true') parser.add_argument('--unrestricted', dest='restricted', action='store_false') parser.add_argument('--image-files', nargs='*', help='Paths to image files you want to test') parser.add_argument('--show-images', action='store_true', help='Show the results of the annotation in a window') parser.add_argument('--show-image-delay', default=0, type=int, help='Displays the images for a set duration in milliseconds, default is until a key is pressed') + parser.add_argument('--serialize', default=False, action='store_true', help='Try to serialize the result') + parser.add_argument('--show-labels', action='store_true', help='Show the labels on the window') return vars(parser.parse_args()) @@ -44,14 +52,85 @@ def pairwise(iterable): result.append((iterable[i], iterable[i+1])) return np.array(result, dtype=np.int32) +def find_min_y(array): + min_ = sys.maxsize + index = None + for i, pair in enumerate(array): + if pair[1] < min_: + min_ = pair[1] + index = i + + return array[index] + +def _get_docker_files(model_name: str, task_id: int): + os.environ['DJANGO_SETTINGS_MODULE'] = 'cvat.settings.development' + + import django + django.setup() + + from cvat.apps.auto_annotation.models import AnnotationModel + from cvat.apps.engine.models import Task as TaskModel + + task = TaskModel(pk=task_id) + model = AnnotationModel.objects.get(name=model_name) + + images_dir = task.get_data_dirname() + + py_file = model.interpretation_file.name + mapping_file = model.labelmap_file.name + xml_file = model.model_file.name + bin_file = model.weights_file.name + + image_files = [] + for root, _, filenames in os.walk(images_dir): + for filename in fnmatch.filter(filenames, '*.jpg'): + image_files.append(os.path.join(root, filename)) + + return py_file, mapping_file, bin_file, xml_file, image_files + def main(): kwargs = _get_kwargs() - py_file = kwargs['py'] - bin_file = kwargs['bin'] - mapping_file = kwargs['json'] - xml_file = kwargs['xml'] + py_file = kwargs.get('py') + bin_file = kwargs.get('bin') + mapping_file = kwargs.get('json') + xml_file = kwargs.get('xml') + + model_name = kwargs.get('model_name') + task_id = kwargs.get('task_id') + + is_docker = model_name and task_id + + # xor is `exclusive or`. English is: if one or the other but not both + if xor(bool(model_name), bool(task_id)): + logging.critical('Must provide both `--model-name` and `--task-id` together!') + return + + if is_docker: + files = _get_docker_files(model_name, task_id) + py_file = files[0] + mapping_file = files[1] + bin_file = files[2] + xml_file = files[3] + image_files = files[4] + else: + return_ = False + if not py_file: + logging.critical('Must provide --py file!') + return_ = True + if not bin_file: + logging.critical('Must provide --bin file!') + return_ = True + if not xml_file: + logging.critical('Must provide --xml file!') + return_ = True + if not mapping_file: + logging.critical('Must provide --json file!') + return_ = True + + if return_: + return if not os.path.isfile(py_file): logging.critical('Py file not found! Check the path') @@ -70,7 +149,11 @@ def main(): return with open(mapping_file) as json_file: - mapping = json.load(json_file) + try: + mapping = json.load(json_file) + except json.decoder.JSONDecodeError: + logging.critical('JSON file not able to be parsed! Check file') + return try: mapping = mapping['label_map'] @@ -82,7 +165,9 @@ def main(): mapping = {int(k): v for k, v in mapping.items()} restricted = kwargs['restricted'] - image_files = kwargs.get('image_files') + + if not is_docker: + image_files = kwargs.get('image_files') if image_files: image_data = [cv2.imread(f) for f in image_files] @@ -99,7 +184,8 @@ def main(): py_file, restricted=restricted) - logging.warning('Program didn\'t have any errors.') + + logging.warning('Inference didn\'t have any errors.') show_images = kwargs.get('show_images', False) if show_images: @@ -114,23 +200,64 @@ def main(): return show_image_delay = kwargs['show_image_delay'] + show_labels = kwargs.get('show_labels') + for index, data in enumerate(image_data): for detection in results['shapes']: if not detection['frame'] == index: continue points = detection['points'] + label_str = detection['label_id'] + # Cv2 doesn't like floats for drawing points = [int(p) for p in points] color = random_color() + if detection['type'] == 'rectangle': cv2.rectangle(data, (points[0], points[1]), (points[2], points[3]), color, 3) + + if show_labels: + cv2.putText(data, label_str, (points[0], points[1] - 7), cv2.FONT_HERSHEY_COMPLEX, 0.6, color, 1) + elif detection['type'] in ('polygon', 'polyline'): # polylines is picky about datatypes points = pairwise(points) cv2.polylines(data, [points], 1, color) + + if show_labels: + min_point = find_min_y(points) + cv2.putText(data, label_str, (min_point[0], min_point[1] - 7), cv2.FONT_HERSHEY_COMPLEX, 0.6, color, 1) + cv2.imshow(str(index), data) cv2.waitKey(show_image_delay) cv2.destroyWindow(str(index)) + if kwargs['serialize']: + os.environ['DJANGO_SETTINGS_MODULE'] = 'cvat.settings.production' + import django + django.setup() + + from cvat.apps.engine.serializers import LabeledDataSerializer + + # NOTE: We're actually using `run_inference_engine_annotation` + # incorrectly here. The `mapping` dict is supposed to be a mapping + # of integers -> integers and represents the transition from model + # integers to the labels in the database. We're using a mapping of + # integers -> strings. For testing purposes, this shortcut is fine. + # We just want to make sure everything works. Until, that is.... + # we want to test using the label serializer. Then we have to transition + # back to integers, otherwise the serializer complains about have a string + # where an integer is expected. We'll just brute force that. + + for shape in results['shapes']: + # Change the english label to an integer for serialization validation + shape['label_id'] = 1 + + serializer = LabeledDataSerializer(data=results) + + if not serializer.is_valid(): + logging.critical('Data unable to be serialized correctly!') + serializer.is_valid(raise_exception=True) + if __name__ == '__main__': main() diff --git a/utils/cli/README.md b/utils/cli/README.md new file mode 100644 index 00000000000..ee637aa6a07 --- /dev/null +++ b/utils/cli/README.md @@ -0,0 +1,44 @@ +# Command line interface (CLI) +**Description** +A simple command line interface for working with CVAT tasks. At the moment it +implements a basic feature set but may serve as the starting point for a more +comprehensive CVAT administration tool in the future. + +Overview of functionality: + +- Create a new task (supports name, bug tracker, labels JSON, local/share/remote files) +- Delete tasks (supports deleting a list of task IDs) +- List all tasks (supports basic CSV or JSON output) +- Download JPEG frames (supports a list of frame IDs) +- Dump annotations (supports all formats via format string) + +**Usage** +```bash +usage: cli.py [-h] [--auth USER:[PASS]] [--server-host SERVER_HOST] + [--server-port SERVER_PORT] [--debug] + {create,delete,ls,frames,dump} ... + +Perform common operations related to CVAT tasks. + +positional arguments: + {create,delete,ls,frames,dump} + +optional arguments: + -h, --help show this help message and exit + --auth USER:[PASS] defaults to the current user and supports the PASS + environment variable or password prompt. + --server-host SERVER_HOST + host (default: localhost) + --server-port SERVER_PORT + port (default: 8080) + --debug show debug output +``` +**Examples** +- List all tasks +`cli.py ls` +- Create a task +`cli.py create "new task" --labels labels.json local file1.jpg file2.jpg` +- Delete some tasks +`cli.py delete 100 101 102` +- Dump annotations +`cli.py dump --format "CVAT XML 1.1 for images" 103 output.xml` diff --git a/utils/cli/cli.py b/utils/cli/cli.py new file mode 100755 index 00000000000..f22bf81520c --- /dev/null +++ b/utils/cli/cli.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +# +# SPDX-License-Identifier: MIT +import logging +import requests +import sys +from http.client import HTTPConnection +from core.core import CLI, CVAT_API_V1 +from core.definition import parser +log = logging.getLogger(__name__) + + +def config_log(level): + log = logging.getLogger('core') + log.addHandler(logging.StreamHandler(sys.stdout)) + log.setLevel(level) + if level <= logging.DEBUG: + HTTPConnection.debuglevel = 1 + + +def main(): + actions = {'create': CLI.tasks_create, + 'delete': CLI.tasks_delete, + 'ls': CLI.tasks_list, + 'frames': CLI.tasks_frame, + 'dump': CLI.tasks_dump, + 'upload': CLI.tasks_upload} + args = parser.parse_args() + config_log(args.loglevel) + with requests.Session() as session: + session.auth = args.auth + api = CVAT_API_V1(args.server_host, args.server_port) + cli = CLI(session, api) + try: + actions[args.action](cli, **args.__dict__) + except (requests.exceptions.HTTPError, + requests.exceptions.ConnectionError, + requests.exceptions.RequestException) as e: + log.critical(e) + + +if __name__ == '__main__': + main() diff --git a/utils/cli/core/__init__.py b/utils/cli/core/__init__.py new file mode 100644 index 00000000000..c5319fc62bc --- /dev/null +++ b/utils/cli/core/__init__.py @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: MIT +from .definition import parser, ResourceType # noqa +from .core import CLI, CVAT_API_V1 # noqa diff --git a/utils/cli/core/core.py b/utils/cli/core/core.py new file mode 100644 index 00000000000..f213044332d --- /dev/null +++ b/utils/cli/core/core.py @@ -0,0 +1,161 @@ +# SPDX-License-Identifier: MIT +import json +import logging +import os +import requests +from io import BytesIO +from PIL import Image +from .definition import ResourceType +log = logging.getLogger(__name__) + + +class CLI(): + + def __init__(self, session, api): + self.api = api + self.session = session + + def tasks_data(self, task_id, resource_type, resources): + """ Add local, remote, or shared files to an existing task. """ + url = self.api.tasks_id_data(task_id) + data = None + files = None + if resource_type == ResourceType.LOCAL: + files = {'client_files[{}]'.format(i): open(f, 'rb') for i, f in enumerate(resources)} + elif resource_type == ResourceType.REMOTE: + data = {'remote_files[{}]'.format(i): f for i, f in enumerate(resources)} + elif resource_type == ResourceType.SHARE: + data = {'server_files[{}]'.format(i): f for i, f in enumerate(resources)} + response = self.session.post(url, data=data, files=files) + response.raise_for_status() + + def tasks_list(self, use_json_output, **kwargs): + """ List all tasks in either basic or JSON format. """ + url = self.api.tasks + response = self.session.get(url) + response.raise_for_status() + page = 1 + while True: + response_json = response.json() + for r in response_json['results']: + if use_json_output: + log.info(json.dumps(r, indent=4)) + else: + log.info('{id},{name},{status}'.format(**r)) + if not response_json['next']: + return + page += 1 + url = self.api.tasks_page(page) + response = self.session.get(url) + response.raise_for_status() + + def tasks_create(self, name, labels, bug, resource_type, resources, **kwargs): + """ Create a new task with the given name and labels JSON and + add the files to it. """ + url = self.api.tasks + data = {'name': name, + 'labels': labels, + 'bug_tracker': bug, + 'image_quality': 50} + response = self.session.post(url, json=data) + response.raise_for_status() + response_json = response.json() + log.info('Created task ID: {id} NAME: {name}'.format(**response_json)) + self.tasks_data(response_json['id'], resource_type, resources) + + def tasks_delete(self, task_ids, **kwargs): + """ Delete a list of tasks, ignoring those which don't exist. """ + for task_id in task_ids: + url = self.api.tasks_id(task_id) + response = self.session.delete(url) + try: + response.raise_for_status() + log.info('Task ID {} deleted'.format(task_id)) + except requests.exceptions.HTTPError as e: + if response.status_code == 404: + log.info('Task ID {} not found'.format(task_id)) + else: + raise e + + def tasks_frame(self, task_id, frame_ids, outdir='', **kwargs): + """ Download the requested frame numbers for a task and save images as + task__frame_.jpg.""" + for frame_id in frame_ids: + url = self.api.tasks_id_frame_id(task_id, frame_id) + response = self.session.get(url) + response.raise_for_status() + im = Image.open(BytesIO(response.content)) + outfile = 'task_{}_frame_{:06d}.jpg'.format(task_id, frame_id) + im.save(os.path.join(outdir, outfile)) + + def tasks_dump(self, task_id, fileformat, filename, **kwargs): + """ Download annotations for a task in the specified format + (e.g. 'YOLO ZIP 1.0').""" + url = self.api.tasks_id(task_id) + response = self.session.get(url) + response.raise_for_status() + response_json = response.json() + + url = self.api.tasks_id_annotations_filename(task_id, + response_json['name'], + fileformat) + while True: + response = self.session.get(url) + response.raise_for_status() + log.info('STATUS {}'.format(response.status_code)) + if response.status_code == 201: + break + + response = self.session.get(url + '&action=download') + response.raise_for_status() + + with open(filename, 'wb') as fp: + fp.write(response.content) + + def tasks_upload(self, task_id, fileformat, filename, **kwargs): + """ Upload annotations for a task in the specified format + (e.g. 'YOLO ZIP 1.0').""" + url = self.api.tasks_id_annotations_format(task_id, fileformat) + while True: + response = self.session.put( + url, + files={'annotation_file':open(filename, 'rb')} + ) + response.raise_for_status() + if response.status_code == 201: + break + + logger_string = "Upload job for Task ID {} ".format(task_id) +\ + "with annotation file {} finished".format(filename) + log.info(logger_string) + + +class CVAT_API_V1(): + """ Build parameterized API URLs """ + + def __init__(self, host, port): + self.base = 'http://{}:{}/api/v1/'.format(host, port) + + @property + def tasks(self): + return self.base + 'tasks' + + def tasks_page(self, page_id): + return self.tasks + '?page={}'.format(page_id) + + def tasks_id(self, task_id): + return self.tasks + '/{}'.format(task_id) + + def tasks_id_data(self, task_id): + return self.tasks_id(task_id) + '/data' + + def tasks_id_frame_id(self, task_id, frame_id): + return self.tasks_id(task_id) + '/frames/{}'.format(frame_id) + + def tasks_id_annotations_format(self, task_id, fileformat): + return self.tasks_id(task_id) + '/annotations?format={}' \ + .format(fileformat) + + def tasks_id_annotations_filename(self, task_id, name, fileformat): + return self.tasks_id(task_id) + '/annotations/{}?format={}' \ + .format(name, fileformat) diff --git a/utils/cli/core/definition.py b/utils/cli/core/definition.py new file mode 100644 index 00000000000..e3d1a6222d5 --- /dev/null +++ b/utils/cli/core/definition.py @@ -0,0 +1,236 @@ +# SPDX-License-Identifier: MIT +import argparse +import getpass +import json +import logging +import os +from enum import Enum + + +def get_auth(s): + """ Parse USER[:PASS] strings and prompt for password if none was + supplied. """ + user, _, password = s.partition(':') + password = password or os.environ.get('PASS') or getpass.getpass() + return user, password + + +def parse_label_arg(s): + """ If s is a file load it as JSON, otherwise parse s as JSON.""" + if os.path.exists(s): + fp = open(s, 'r') + return json.load(fp) + else: + return json.loads(s) + + +class ResourceType(Enum): + + LOCAL = 0 + SHARE = 1 + REMOTE = 2 + + def __str__(self): + return self.name.lower() + + def __repr__(self): + return str(self) + + @staticmethod + def argparse(s): + try: + return ResourceType[s.upper()] + except KeyError: + return s + + +####################################################################### +# Command line interface definition +####################################################################### + +parser = argparse.ArgumentParser( + description='Perform common operations related to CVAT tasks.\n\n' +) +task_subparser = parser.add_subparsers(dest='action') + +####################################################################### +# Positional arguments +####################################################################### + +parser.add_argument( + '--auth', + type=get_auth, + metavar='USER:[PASS]', + default=getpass.getuser(), + help='''defaults to the current user and supports the PASS + environment variable or password prompt + (default user: %(default)s).''' +) +parser.add_argument( + '--server-host', + type=str, + default='localhost', + help='host (default: %(default)s)' +) +parser.add_argument( + '--server-port', + type=int, + default='8080', + help='port (default: %(default)s)' +) +parser.add_argument( + '--debug', + action='store_const', + dest='loglevel', + const=logging.DEBUG, + default=logging.INFO, + help='show debug output' +) + +####################################################################### +# Create +####################################################################### + +task_create_parser = task_subparser.add_parser( + 'create', + description='Create a new CVAT task.' +) +task_create_parser.add_argument( + 'name', + type=str, + help='name of the task' +) +task_create_parser.add_argument( + '--labels', + default='[]', + type=parse_label_arg, + help='string or file containing JSON labels specification' +) +task_create_parser.add_argument( + '--bug', + default='', + type=str, + help='bug tracker URL' +) +task_create_parser.add_argument( + 'resource_type', + default='local', + choices=list(ResourceType), + type=ResourceType.argparse, + help='type of files specified' +) +task_create_parser.add_argument( + 'resources', + type=str, + help='list of paths or URLs', + nargs='+' +) + +####################################################################### +# Delete +####################################################################### + +delete_parser = task_subparser.add_parser( + 'delete', + description='Delete a CVAT task.' +) +delete_parser.add_argument( + 'task_ids', + type=int, + help='list of task IDs', + nargs='+' +) + +####################################################################### +# List +####################################################################### + +ls_parser = task_subparser.add_parser( + 'ls', + description='List all CVAT tasks in simple or JSON format.' +) +ls_parser.add_argument( + '--json', + dest='use_json_output', + default=False, + action='store_true', + help='output JSON data' +) + +####################################################################### +# Frames +####################################################################### + +frames_parser = task_subparser.add_parser( + 'frames', + description='Download all frame images for a CVAT task.' +) +frames_parser.add_argument( + 'task_id', + type=int, + help='task ID' +) +frames_parser.add_argument( + 'frame_ids', + type=int, + help='list of frame IDs to download', + nargs='+' +) +frames_parser.add_argument( + '--outdir', + type=str, + default='', + help='directory to save images' +) + +####################################################################### +# Dump +####################################################################### + +dump_parser = task_subparser.add_parser( + 'dump', + description='Download annotations for a CVAT task.' +) +dump_parser.add_argument( + 'task_id', + type=int, + help='task ID' +) +dump_parser.add_argument( + 'filename', + type=str, + help='output file' +) +dump_parser.add_argument( + '--format', + dest='fileformat', + type=str, + default='CVAT XML 1.1 for images', + help='annotation format (default: %(default)s)' +) + +####################################################################### +# Upload Annotations +####################################################################### + +upload_parser = task_subparser.add_parser( + 'upload', + description='Upload annotations for a CVAT task.' +) +upload_parser.add_argument( + 'task_id', + type=int, + help='task ID' +) +upload_parser.add_argument( + 'filename', + type=str, + help='upload file' +) +upload_parser.add_argument( + '--format', + dest='fileformat', + type=str, + default='CVAT XML 1.1', + help='annotation format (default: %(default)s)' +) diff --git a/utils/cli/requirements.txt b/utils/cli/requirements.txt new file mode 100644 index 00000000000..14cc33a6268 --- /dev/null +++ b/utils/cli/requirements.txt @@ -0,0 +1,2 @@ +Pillow>=6.2.0 +requests>=2.20.1 diff --git a/utils/cli/tests/__init__.py b/utils/cli/tests/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/utils/cli/tests/test_cli.py b/utils/cli/tests/test_cli.py new file mode 100644 index 00000000000..cf1dcc82ed8 --- /dev/null +++ b/utils/cli/tests/test_cli.py @@ -0,0 +1,131 @@ +# SPDX-License-Identifier: MIT +import logging +import io +import os +import sys +import unittest +from django.conf import settings +from requests.auth import HTTPBasicAuth +from utils.cli.core import CLI, CVAT_API_V1, ResourceType +from rest_framework.test import APITestCase, RequestsClient +from cvat.apps.engine.tests.test_rest_api import create_db_users +from cvat.apps.engine.tests.test_rest_api import generate_image_file +from PIL import Image + + +class TestCLI(APITestCase): + + @unittest.mock.patch('sys.stdout', new_callable=io.StringIO) + def setUp(self, mock_stdout): + self.client = RequestsClient() + self.client.auth = HTTPBasicAuth('admin', 'admin') + self.api = CVAT_API_V1('testserver', '') + self.cli = CLI(self.client, self.api) + self.taskname = 'test_task' + self.cli.tasks_create(self.taskname, + [{'name' : 'car'}, {'name': 'person'}], + '', + ResourceType.LOCAL, + [self.img_file]) + # redirect logging to mocked stdout to test program output + self.mock_stdout = mock_stdout + log = logging.getLogger('utils.cli.core') + log.setLevel(logging.INFO) + log.addHandler(logging.StreamHandler(sys.stdout)) + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.img_file = os.path.join(settings.SHARE_ROOT, 'test_cli.jpg') + data = generate_image_file(cls.img_file) + with open(cls.img_file, 'wb') as image: + image.write(data.read()) + + @classmethod + def tearDownClass(cls): + super().tearDownClass() + os.remove(cls.img_file) + + @classmethod + def setUpTestData(cls): + create_db_users(cls) + + def test_tasks_list(self): + self.cli.tasks_list(False) + self.assertRegex(self.mock_stdout.getvalue(), '.*{}.*'.format(self.taskname)) + + def test_tasks_delete(self): + self.cli.tasks_delete([1]) + self.cli.tasks_list(False) + self.assertNotRegex(self.mock_stdout.getvalue(), '.*{}.*'.format(self.taskname)) + + def test_tasks_dump(self): + path = os.path.join(settings.SHARE_ROOT, 'test_cli.xml') + self.cli.tasks_dump(1, 'CVAT XML 1.1 for images', path) + self.assertTrue(os.path.exists(path)) + os.remove(path) + + def test_tasks_frame(self): + path = os.path.join(settings.SHARE_ROOT, 'task_1_frame_000000.jpg') + self.cli.tasks_frame(1, [0], outdir=settings.SHARE_ROOT) + self.assertTrue(os.path.exists(path)) + os.remove(path) + + def test_tasks_upload(self): + test_image = Image.open(self.img_file) + width, height = test_image.size + + # Using generate_coco_anno() from: + # https://github.com/opencv/cvat/blob/develop/cvat/apps/engine/tests/test_rest_api.py + def generate_coco_anno(): + return b"""{ + "categories": [ + { + "id": 1, + "name": "car", + "supercategory": "" + }, + { + "id": 2, + "name": "person", + "supercategory": "" + } + ], + "images": [ + { + "coco_url": "", + "date_captured": "", + "flickr_url": "", + "license": 0, + "id": 0, + "file_name": "test_cli.jpg", + "height": %d, + "width": %d + } + ], + "annotations": [ + { + "category_id": 1, + "id": 1, + "image_id": 0, + "iscrowd": 0, + "segmentation": [ + [] + ], + "area": 17702.0, + "bbox": [ + 574.0, + 407.0, + 167.0, + 106.0 + ] + } + ] + }""" + content = generate_coco_anno() % (height, width) + path = os.path.join(settings.SHARE_ROOT, 'test_cli.json') + with open(path, "wb") as coco: + coco.write(content) + self.cli.tasks_upload(1, 'COCO JSON 1.0', path) + self.assertRegex(self.mock_stdout.getvalue(), '.*{}.*'.format("annotation file")) + os.remove(path) diff --git a/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0004/mappings.json b/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0004/mappings.json new file mode 100644 index 00000000000..f6a0aa87964 --- /dev/null +++ b/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0004/mappings.json @@ -0,0 +1,5 @@ +{ + "label_map": { + "1": "text" + } +} diff --git a/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0004/pixel_link_mobilenet_v2.py b/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0004/pixel_link_mobilenet_v2.py new file mode 100644 index 00000000000..b0f105ec5a1 --- /dev/null +++ b/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0004/pixel_link_mobilenet_v2.py @@ -0,0 +1,197 @@ +# SPDX-License-Identifier: MIT` + +import cv2 +import numpy as np + + +class PixelLinkDecoder(): + def __init__(self): + four_neighbours = False + if four_neighbours: + self._get_neighbours = self._get_neighbours_4 + else: + self._get_neighbours = self._get_neighbours_8 + self.pixel_conf_threshold = 0.8 + self.link_conf_threshold = 0.8 + + def decode(self, height, width, detections: dict): + self.image_height = height + self.image_width = width + self.pixel_scores = self._set_pixel_scores(detections['model/segm_logits/add']) + self.link_scores = self._set_link_scores(detections['model/link_logits_/add']) + + self.pixel_mask = self.pixel_scores >= self.pixel_conf_threshold + self.link_mask = self.link_scores >= self.link_conf_threshold + self.points = list(zip(*np.where(self.pixel_mask))) + self.h, self.w = np.shape(self.pixel_mask) + self.group_mask = dict.fromkeys(self.points, -1) + self.bboxes = None + self.root_map = None + self.mask = None + + self._decode() + + def _softmax(self, x, axis=None): + return np.exp(x - self._logsumexp(x, axis=axis, keepdims=True)) + + # pylint: disable=no-self-use + def _logsumexp(self, a, axis=None, b=None, keepdims=False, return_sign=False): + if b is not None: + a, b = np.broadcast_arrays(a, b) + if np.any(b == 0): + a = a + 0. # promote to at least float + a[b == 0] = -np.inf + + a_max = np.amax(a, axis=axis, keepdims=True) + + if a_max.ndim > 0: + a_max[~np.isfinite(a_max)] = 0 + elif not np.isfinite(a_max): + a_max = 0 + + if b is not None: + b = np.asarray(b) + tmp = b * np.exp(a - a_max) + else: + tmp = np.exp(a - a_max) + + # suppress warnings about log of zero + with np.errstate(divide='ignore'): + s = np.sum(tmp, axis=axis, keepdims=keepdims) + if return_sign: + sgn = np.sign(s) + s *= sgn # /= makes more sense but we need zero -> zero + out = np.log(s) + + if not keepdims: + a_max = np.squeeze(a_max, axis=axis) + out += a_max + + if return_sign: + return out, sgn + else: + return out + + def _set_pixel_scores(self, pixel_scores): + "get softmaxed properly shaped pixel scores" + tmp = np.transpose(pixel_scores, (0, 2, 3, 1)) + return self._softmax(tmp, axis=-1)[0, :, :, 1] + + def _set_link_scores(self, link_scores): + "get softmaxed properly shaped links scores" + tmp = np.transpose(link_scores, (0, 2, 3, 1)) + tmp_reshaped = tmp.reshape(tmp.shape[:-1] + (8, 2)) + return self._softmax(tmp_reshaped, axis=-1)[0, :, :, :, 1] + + def _find_root(self, point): + root = point + update_parent = False + tmp = self.group_mask[root] + while tmp is not -1: + root = tmp + tmp = self.group_mask[root] + update_parent = True + if update_parent: + self.group_mask[point] = root + return root + + def _join(self, p1, p2): + root1 = self._find_root(p1) + root2 = self._find_root(p2) + if root1 != root2: + self.group_mask[root2] = root1 + + def _get_index(self, root): + if root not in self.root_map: + self.root_map[root] = len(self.root_map) + 1 + return self.root_map[root] + + def _get_all(self): + self.root_map = {} + self.mask = np.zeros_like(self.pixel_mask, dtype=np.int32) + + for point in self.points: + point_root = self._find_root(point) + bbox_idx = self._get_index(point_root) + self.mask[point] = bbox_idx + + def _get_neighbours_8(self, x, y): + w, h = self.w, self.h + tmp = [(0, x - 1, y - 1), (1, x, y - 1), + (2, x + 1, y - 1), (3, x - 1, y), + (4, x + 1, y), (5, x - 1, y + 1), + (6, x, y + 1), (7, x + 1, y + 1)] + + return [i for i in tmp if i[1] >= 0 and i[1] < w and i[2] >= 0 and i[2] < h] + + def _get_neighbours_4(self, x, y): + w, h = self.w, self.h + tmp = [(1, x, y - 1), + (3, x - 1, y), + (4, x + 1, y), + (6, x, y + 1)] + + return [i for i in tmp if i[1] >= 0 and i[1] < w and i[2] >= 0 and i[2] < h] + + def _mask_to_bboxes(self, min_area=300, min_height=10): + self.bboxes = [] + max_bbox_idx = self.mask.max() + mask_tmp = cv2.resize(self.mask, (self.image_width, self.image_height), interpolation=cv2.INTER_NEAREST) + + for bbox_idx in range(1, max_bbox_idx + 1): + bbox_mask = mask_tmp == bbox_idx + cnts, _ = cv2.findContours(bbox_mask.astype(np.uint8), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) + if len(cnts) == 0: + continue + cnt = cnts[0] + rect, w, h = self._min_area_rect(cnt) + if min(w, h) < min_height: + continue + if w * h < min_area: + continue + self.bboxes.append(self._order_points(rect)) + + # pylint: disable=no-self-use + def _min_area_rect(self, cnt): + rect = cv2.minAreaRect(cnt) + w, h = rect[1] + box = cv2.boxPoints(rect) + box = np.int0(box) + return box, w, h + + # pylint: disable=no-self-use + def _order_points(self, rect): + """ (x, y) + Order: TL, TR, BR, BL + """ + tmp = np.zeros_like(rect) + sums = rect.sum(axis=1) + tmp[0] = rect[np.argmin(sums)] + tmp[2] = rect[np.argmax(sums)] + diff = np.diff(rect, axis=1) + tmp[1] = rect[np.argmin(diff)] + tmp[3] = rect[np.argmax(diff)] + return tmp + + def _decode(self): + for point in self.points: + y, x = point + neighbours = self._get_neighbours(x, y) + for n_idx, nx, ny in neighbours: + link_value = self.link_mask[y, x, n_idx] + pixel_cls = self.pixel_mask[ny, nx] + if link_value and pixel_cls: + self._join(point, (ny, nx)) + + self._get_all() + self._mask_to_bboxes() + + +label = 1 +pcd = PixelLinkDecoder() +for detection in detections: + frame = detection['frame_id'] + pcd.decode(detection['frame_height'], detection['frame_width'], detection['detections']) + for box in pcd.bboxes: + box = [[int(b[0]), int(b[1])] for b in box] + results.add_polygon(box, label, frame) diff --git a/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0001/README.md b/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/README.md similarity index 100% rename from utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0001/README.md rename to utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/README.md diff --git a/utils/open_model_zoo/Transportation/semantic-segmentation-adas/interp.py b/utils/open_model_zoo/Transportation/semantic-segmentation-adas/interp.py new file mode 100644 index 00000000000..58a87fa35d1 --- /dev/null +++ b/utils/open_model_zoo/Transportation/semantic-segmentation-adas/interp.py @@ -0,0 +1,31 @@ +import numpy as np +from skimage.measure import approximate_polygon, find_contours + +import cv2 + + +for frame_results in detections: + frame_height = frame_results['frame_height'] + frame_width = frame_results['frame_width'] + frame_number = frame_results['frame_id'] + detection = frame_results['detections'] + detection = detection[0, 0, :, :] + width, height = detection.shape + + for i in range(21): + zero = np.zeros((width,height),dtype=np.uint8) + + f = float(i) + zero = ((detection == f) * 255).astype(np.float32) + zero = cv2.resize(zero, dsize=(frame_width, frame_height), interpolation=cv2.INTER_CUBIC) + + contours = find_contours(zero, 0.8) + + for contour in contours: + contour = np.flip(contour, axis=1) + contour = approximate_polygon(contour, tolerance=2.5) + segmentation = contour.tolist() + if len(segmentation) < 3: + continue + + results.add_polygon(segmentation, i, frame_number) diff --git a/utils/open_model_zoo/Transportation/semantic-segmentation-adas/mapping.json b/utils/open_model_zoo/Transportation/semantic-segmentation-adas/mapping.json new file mode 100644 index 00000000000..cbda289d3e2 --- /dev/null +++ b/utils/open_model_zoo/Transportation/semantic-segmentation-adas/mapping.json @@ -0,0 +1,25 @@ +{ + "label_map": { + "0": "road", + "1": "sidewalk", + "2": "building", + "3": "wall", + "4": "fence", + "5": "pole", + "6": "traffic light", + "7": "traffic sign", + "8": "vegetation", + "9": "terrain", + "10": "sky", + "11": "person", + "12": "rider", + "13": "car", + "14": "truck", + "15": "bus", + "16": "train", + "17": "motorcycle", + "18": "bicycle", + "19": "ego-vehicle", + "20": "background" + } +} diff --git a/utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/README.md b/utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/README.md new file mode 100644 index 00000000000..be7a82121bf --- /dev/null +++ b/utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/README.md @@ -0,0 +1,32 @@ +# mask_rcnn_inception_resnet_v2_atrous_coco + +## Use Case and High-Level Description + +Mask R-CNN Inception Resnet V2 Atrous is trained on COCO dataset and used for object instance segmentation. +For details, see a [paper](https://arxiv.org/pdf/1703.06870.pdf). + +## Specification + +| Metric | Value | +|---------------------------------|-------------------------------------------| +| Type | Instance segmentation | +| GFlops | 675.314 | +| MParams | 92.368 | +| Source framework | TensorFlow\* | + +## Legal Information + +[https://raw.githubusercontent.com/tensorflow/models/master/LICENSE]() + +## OpenVINO Conversion Notes + +In order to convert the code into the openvino format, please see the [following link](https://docs.openvinotoolkit.org/latest/_docs_MO_DG_prepare_model_convert_model_tf_specific_Convert_Object_Detection_API_Models.html#mask_r_cnn_topologies). + +The conversion command from the command line prompt will look something like the following. + +```shell +$ python /opt/intel/openvino/deployment_tools/model_optimizer/mo_tf.py \ + --input_model /path/to/frozen_inference_graph.pb \ + --tensorflow_use_custom_operations_config /opt/intel/openvino/deployment_tools/model_optimizer/extensions/front/tf/mask_rcnn_support.json \ + --tensorflow_object_detection_api_pipeline_config /path/to/pipeline.config +``` diff --git a/utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/interp.py b/utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/interp.py new file mode 100644 index 00000000000..6625a834933 --- /dev/null +++ b/utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/interp.py @@ -0,0 +1,64 @@ +import numpy as np +import cv2 +from skimage.measure import approximate_polygon, find_contours + + +MASK_THRESHOLD = .5 +PROBABILITY_THRESHOLD = 0.2 + + +# Ref: https://software.intel.com/en-us/forums/computer-vision/topic/804895 +def segm_postprocess(box: list, raw_cls_mask, im_h, im_w, threshold): + ymin, xmin, ymax, xmax = box + + width = int(abs(xmax - xmin)) + height = int(abs(ymax - ymin)) + + result = np.zeros((im_h, im_w), dtype=np.uint8) + resized_mask = cv2.resize(raw_cls_mask, dsize=(height, width), interpolation=cv2.INTER_CUBIC) + + # extract the ROI of the image + ymin = int(round(ymin)) + xmin = int(round(xmin)) + ymax = ymin + height + xmax = xmin + width + result[xmin:xmax, ymin:ymax] = (resized_mask>threshold).astype(np.uint8) * 255 + + return result + + +for detection in detections: + frame_number = detection['frame_id'] + height = detection['frame_height'] + width = detection['frame_width'] + detection = detection['detections'] + + masks = detection['masks'] + boxes = detection['reshape_do_2d'] + + for index, box in enumerate(boxes): + label = int(box[1]) + obj_value = box[2] + if obj_value >= PROBABILITY_THRESHOLD: + x = box[3] * width + y = box[4] * height + right = box[5] * width + bottom = box[6] * height + mask = masks[index][label - 1] + + mask = segm_postprocess((x, y, right, bottom), + mask, + height, + width, + MASK_THRESHOLD) + + contours = find_contours(mask, MASK_THRESHOLD) + contour = contours[0] + contour = np.flip(contour, axis=1) + contour = approximate_polygon(contour, tolerance=2.5) + segmentation = contour.tolist() + + + # NOTE: if you want to see the boxes, uncomment next line + # results.add_box(x, y, right, bottom, label, frame_number) + results.add_polygon(segmentation, label, frame_number) diff --git a/utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/mapping.json b/utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/mapping.json new file mode 100644 index 00000000000..3efdb307565 --- /dev/null +++ b/utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/mapping.json @@ -0,0 +1,84 @@ +{ + "label_map": { + "1": "person", + "2": "bicycle", + "3": "car", + "4": "motorcycle", + "5": "airplane", + "6": "bus", + "7": "train", + "8": "truck", + "9": "boat", + "10": "traffic_light", + "11": "fire_hydrant", + "13": "stop_sign", + "14": "parking_meter", + "15": "bench", + "16": "bird", + "17": "cat", + "18": "dog", + "19": "horse", + "20": "sheep", + "21": "cow", + "22": "elephant", + "23": "bear", + "24": "zebra", + "25": "giraffe", + "27": "backpack", + "28": "umbrella", + "31": "handbag", + "32": "tie", + "33": "suitcase", + "34": "frisbee", + "35": "skis", + "36": "snowboard", + "37": "sports_ball", + "38": "kite", + "39": "baseball_bat", + "40": "baseball_glove", + "41": "skateboard", + "42": "surfboard", + "43": "tennis_racket", + "44": "bottle", + "46": "wine_glass", + "47": "cup", + "48": "fork", + "49": "knife", + "50": "spoon", + "51": "bowl", + "52": "banana", + "53": "apple", + "54": "sandwich", + "55": "orange", + "56": "broccoli", + "57": "carrot", + "58": "hot_dog", + "59": "pizza", + "60": "donut", + "61": "cake", + "62": "chair", + "63": "couch", + "64": "potted_plant", + "65": "bed", + "67": "dining_table", + "70": "toilet", + "72": "tv", + "73": "laptop", + "74": "mouse", + "75": "remote", + "76": "keyboard", + "77": "cell_phone", + "78": "microwave", + "79": "oven", + "80": "toaster", + "81": "sink", + "83": "refrigerator", + "84": "book", + "85": "clock", + "86": "vase", + "87": "scissors", + "88": "teddy_bear", + "89": "hair_drier", + "90": "toothbrush" + } +} diff --git a/utils/open_model_zoo/yolov3/README.md b/utils/open_model_zoo/yolov3/README.md new file mode 100644 index 00000000000..2e47953cb3f --- /dev/null +++ b/utils/open_model_zoo/yolov3/README.md @@ -0,0 +1,22 @@ +# Object Detection YOLO V3 Python Demo, Async API Performance Showcase + +See [these instructions][1] for converting the yolo weights to the OpenVino format. + +As of OpenVINO 2019 R3, only tensorflow 1.13 and NetworkX 2.3. +These can be explicitly installed using the following command. + +```bash +python3 -m pip install tensorflow==1.13 networkx==2.3 +``` + + +Additionally, at the time of writing, the model optimizer required an input shape. + +``` bash +python3 mo_tf.py \ + --input_model /path/to/yolo_v3.pb \ + --tensorflow_use_custom_operations_config $MO_ROOT/extensions/front/tf/yolo_v3.json \ + --input_shape [1,416,416,3] +``` + +[1]: https://docs.openvinotoolkit.org/latest/_docs_MO_DG_prepare_model_convert_model_tf_specific_Convert_YOLO_From_Tensorflow.html diff --git a/utils/open_model_zoo/yolov3/interp.py b/utils/open_model_zoo/yolov3/interp.py new file mode 100644 index 00000000000..4c76c85448d --- /dev/null +++ b/utils/open_model_zoo/yolov3/interp.py @@ -0,0 +1,160 @@ +from math import exp + + +class Parser: + IOU_THRESHOLD = 0.4 + PROB_THRESHOLD = 0.5 + + def __init__(self): + self.objects = [] + + def scale_bbox(self, x, y, h, w, class_id, confidence, h_scale, w_scale): + xmin = int((x - w / 2) * w_scale) + ymin = int((y - h / 2) * h_scale) + xmax = int(xmin + w * w_scale) + ymax = int(ymin + h * h_scale) + + return dict(xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax, class_id=class_id, confidence=confidence) + + def entry_index(self, side, coord, classes, location, entry): + side_power_2 = side ** 2 + n = location // side_power_2 + loc = location % side_power_2 + return int(side_power_2 * (n * (coord + classes + 1) + entry) + loc) + + def intersection_over_union(self, box_1, box_2): + width_of_overlap_area = min(box_1['xmax'], box_2['xmax']) - max(box_1['xmin'], box_2['xmin']) + height_of_overlap_area = min(box_1['ymax'], box_2['ymax']) - max(box_1['ymin'], box_2['ymin']) + if width_of_overlap_area < 0 or height_of_overlap_area < 0: + area_of_overlap = 0 + else: + area_of_overlap = width_of_overlap_area * height_of_overlap_area + box_1_area = (box_1['ymax'] - box_1['ymin']) * (box_1['xmax'] - box_1['xmin']) + box_2_area = (box_2['ymax'] - box_2['ymin']) * (box_2['xmax'] - box_2['xmin']) + area_of_union = box_1_area + box_2_area - area_of_overlap + if area_of_union == 0: + return 0 + return area_of_overlap / area_of_union + + + def sort_objects(self): + self.objects = sorted(self.objects, key=lambda obj : obj['confidence'], reverse=True) + + for i in range(len(self.objects)): + if self.objects[i]['confidence'] == 0: + continue + for j in range(i + 1, len(self.objects)): + if self.intersection_over_union(self.objects[i], self.objects[j]) > self.IOU_THRESHOLD: + self.objects[j]['confidence'] = 0 + + def parse_yolo_region(self, blob: 'np.ndarray', original_shape: list, params: dict) -> list: + + # YOLO magic numbers + # See: https://github.com/opencv/open_model_zoo/blob/acf297c73db8cb3f68791ae1fad4a7cc4a6039e5/demos/python_demos/object_detection_demo_yolov3_async/object_detection_demo_yolov3_async.py#L61 + num = 3 + coords = 4 + classes = 80 + # ----------------- + + _, _, out_blob_h, out_blob_w = blob.shape + assert out_blob_w == out_blob_h, "Invalid size of output blob. It sould be in NCHW layout and height should " \ + "be equal to width. Current height = {}, current width = {}" \ + "".format(out_blob_h, out_blob_w) + + # ------ Extracting layer parameters -- + orig_im_h, orig_im_w = original_shape + predictions = blob.flatten() + side_square = params['side'] * params['side'] + + # ------ Parsing YOLO Region output -- + for i in range(side_square): + row = i // params['side'] + col = i % params['side'] + for n in range(num): + # -----entry index calcs------ + obj_index = self.entry_index(params['side'], coords, classes, n * side_square + i, coords) + scale = predictions[obj_index] + if scale < self.PROB_THRESHOLD: + continue + box_index = self.entry_index(params['side'], coords, classes, n * side_square + i, 0) + + # Network produces location predictions in absolute coordinates of feature maps. + # Scale it to relative coordinates. + x = (col + predictions[box_index + 0 * side_square]) / params['side'] * 416 + y = (row + predictions[box_index + 1 * side_square]) / params['side'] * 416 + # Value for exp is very big number in some cases so following construction is using here + try: + h_exp = exp(predictions[box_index + 3 * side_square]) + w_exp = exp(predictions[box_index + 2 * side_square]) + except OverflowError: + continue + + w = w_exp * params['anchors'][2 * n] + h = h_exp * params['anchors'][2 * n + 1] + + for j in range(classes): + class_index = self.entry_index(params['side'], coords, classes, n * side_square + i, + coords + 1 + j) + confidence = scale * predictions[class_index] + if confidence < self.PROB_THRESHOLD: + continue + + self.objects.append(self.scale_bbox(x=x, + y=y, + h=h, + w=w, + class_id=j, + confidence=confidence, + h_scale=(orig_im_h/416), + w_scale=(orig_im_w/416))) + + +for detection in detections: + frame_number = detection['frame_id'] + height = detection['frame_height'] + width = detection['frame_width'] + detection = detection['detections'] + + original_shape = (height, width) + + # https://github.com/opencv/open_model_zoo/blob/master/demos/python_demos/object_detection_demo_yolov3_async/object_detection_demo_yolov3_async.py#L72 + anchors = [10,13,16,30,33,23,30,61,62,45,59,119,116,90,156,198,373,326] + conv_6 = {'side': 13, 'mask': [6,7,8]} + conv_14 = {'side': 26, 'mask': [3,4,5]} + conv_22 = {'side': 52, 'mask': [0,1,2]} + + yolo_params = {'detector/yolo-v3/Conv_6/BiasAdd/YoloRegion': conv_6, + 'detector/yolo-v3/Conv_14/BiasAdd/YoloRegion': conv_14, + 'detector/yolo-v3/Conv_22/BiasAdd/YoloRegion': conv_22} + + for conv_net in yolo_params.values(): + mask = conv_net['mask'] + masked_anchors = [] + for idx in mask: + masked_anchors += [anchors[idx * 2], anchors[idx * 2 + 1]] + + conv_net['anchors'] = masked_anchors + + parser = Parser() + + for name, blob in detection.items(): + parser.parse_yolo_region(blob, original_shape, yolo_params[name]) + + parser.sort_objects() + + objects = [] + for obj in parser.objects: + if obj['confidence'] >= parser.PROB_THRESHOLD: + label = obj['class_id'] + xmin = obj['xmin'] + xmax = obj['xmax'] + ymin = obj['ymin'] + ymax = obj['ymax'] + + # Enforcing extra checks for bounding box coordinates + xmin = max(0,xmin) + ymin = max(0,ymin) + xmax = min(xmax,width) + ymax = min(ymax,height) + + results.add_box(xmin, ymin, xmax, ymax, label, frame_number) diff --git a/utils/open_model_zoo/yolov3/mapping.json b/utils/open_model_zoo/yolov3/mapping.json new file mode 100644 index 00000000000..bfb65a24cf0 --- /dev/null +++ b/utils/open_model_zoo/yolov3/mapping.json @@ -0,0 +1,84 @@ +{ + "label_map": { + "0": "person", + "1": "bicycle", + "2": "car", + "3": "motorbike", + "4": "aeroplane", + "5": "bus", + "6": "train", + "7": "truck", + "8": "boat", + "9": "traffic light", + "10": "fire hydrant", + "11": "stop sign", + "12": "parking meter", + "13": "bench", + "14": "bird", + "15": "cat", + "16": "dog", + "17": "horse", + "18": "sheep", + "19": "cow", + "20": "elephant", + "21": "bear", + "22": "zebra", + "23": "giraffe", + "24": "backpack", + "25": "umbrella", + "26": "handbag", + "27": "tie", + "28": "suitcase", + "29": "frisbee", + "30": "skis", + "31": "snowboard", + "32": "sports ball", + "33": "kite", + "34": "baseball bat", + "35": "baseball glove", + "36": "skateboard", + "37": "surfboard", + "38": "tennis racket", + "39": "bottle", + "40": "wine glass", + "41": "cup", + "42": "fork", + "43": "knife", + "44": "spoon", + "45": "bowl", + "46": "banana", + "47": "apple", + "48": "sandwich", + "49": "orange", + "50": "broccoli", + "51": "carrot", + "52": "hot dog", + "53": "pizza", + "54": "donut", + "55": "cake", + "56": "chair", + "57": "sofa", + "58": "pottedplant", + "59": "bed", + "60": "diningtable", + "61": "toilet", + "62": "tvmonitor", + "63": "laptop", + "64": "mouse", + "65": "remote", + "66": "keyboard", + "67": "cell phone", + "68": "microwave", + "69": "oven", + "70": "toaster", + "71": "sink", + "72": "refrigerator", + "73": "book", + "74": "clock", + "75": "vase", + "76": "scissors", + "77": "teddy bear", + "78": "hair drier", + "79": "toothbrush" + } +} diff --git a/utils/tfrecords/converter.md b/utils/tfrecords/converter.md index 96d56ffcb58..875f165f37d 100644 --- a/utils/tfrecords/converter.md +++ b/utils/tfrecords/converter.md @@ -23,7 +23,7 @@ sudo apt-get install -y --no-install-recommends python3-pip python3-dev ``` ``` bash -pip3 install -r requirements.txt +python3 -m pip install -r requirements.txt ``` ### 2. Install the tensorflow object detection API @@ -38,12 +38,12 @@ git clone https://github.com/tensorflow/models.git ``` ```bash # install some dependencies -pip3 install --user Cython -pip3 install --user contextlib2 -pip3 install --user pillow -pip3 install --user lxml -pip3 install --user jupyter -pip3 install --user matplotlib +python3 -m pip install --user Cython +python3 -m pip install --user contextlib2 +python3 -m pip install --user pillow +python3 -m pip install --user lxml +python3 -m pip install --user jupyter +python3 -m pip install --user matplotlib ``` ```bash # clone and compile the cocoapi diff --git a/utils/tfrecords/requirements.txt b/utils/tfrecords/requirements.txt index 616c04018e2..bb0070d81b1 100644 --- a/utils/tfrecords/requirements.txt +++ b/utils/tfrecords/requirements.txt @@ -1,3 +1,3 @@ argparse==1.1 -tensorflow==1.13.1 +tensorflow==1.15.2 pathlib==1.0.1 diff --git a/utils/voc/converter.py b/utils/voc/converter.py index 1764e819313..671e87804ca 100644 --- a/utils/voc/converter.py +++ b/utils/voc/converter.py @@ -124,11 +124,12 @@ def process_cvat_xml(xml_file, image_dir, output_dir): image_name = img_tag.get('name') width = img_tag.get('width') height = img_tag.get('height') + depth = img_tag.get('depth', 3) image_path = os.path.join(image_dir, image_name) if not os.path.exists(image_path): log.warn('{} image cannot be found. Is `{}` image directory correct?'. format(image_path, image_dir)) - writer = Writer(image_path, width, height) + writer = Writer(image_path, width, height, depth=depth) unknown_tags = {x.tag for x in img_tag.iter()}.difference(KNOWN_TAGS) if unknown_tags: From 4d0c22513ac2215686a11ec3e262fff3f74b8de8 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Mon, 16 Mar 2020 15:43:23 +0300 Subject: [PATCH 11/49] temp --- cvat-ui/src/actions/annotation-actions.ts | 9 ++++- .../canvas-point-context-menu.tsx | 34 +++++++++++++++++++ .../standard-workspace/canvas-wrapper.tsx | 27 ++++++++++++--- .../standard-workspace/styles.scss | 13 +++++++ .../canvas-context-menu.tsx | 7 +++- .../standard-workspace/canvas-wrapper.tsx | 20 +++++++++-- cvat-ui/src/reducers/annotation-reducer.ts | 4 +++ cvat-ui/src/reducers/interfaces.ts | 2 ++ 8 files changed, 107 insertions(+), 9 deletions(-) create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/canvas-point-context-menu.tsx diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 44aeb752ceb..916ddb7c80b 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -18,6 +18,7 @@ import { Task, FrameSpeed, Rotation, + ContextMenuType, } from 'reducers/interfaces'; import getCore from 'cvat-core'; @@ -270,13 +271,19 @@ ThunkAction, {}, {}, AnyAction> { }; } -export function updateCanvasContextMenu(visible: boolean, left: number, top: number): AnyAction { +export function updateCanvasContextMenu( + visible: boolean, + left: number, + top: number, + type?: ContextMenuType, +): AnyAction { return { type: AnnotationActionTypes.UPDATE_CANVAS_CONTEXT_MENU, payload: { visible, left, top, + type, }, }; } diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-point-context-menu.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-point-context-menu.tsx new file mode 100644 index 00000000000..4ce488f37f7 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-point-context-menu.tsx @@ -0,0 +1,34 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import ReactDOM from 'react-dom'; + + +interface Props { + activatedStateID: number | null; + visible: boolean; + left: number; + top: number; +} + +export default function CanvasPointContextMenu(props: Props): JSX.Element | null { + const { + activatedStateID, + visible, + left, + top, + } = props; + + if (!visible || activatedStateID === null) { + return null; + } + + return ReactDOM.createPortal( +
      + Haha +
      , + window.document.body, + ); +} diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx index aaa6645dc08..bbb0aab26af 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -13,7 +13,12 @@ import { } from 'antd'; import { SliderValue } from 'antd/lib//slider'; -import { ColorBy, GridColor, ObjectType } from 'reducers/interfaces'; +import { + ColorBy, + GridColor, + ObjectType, + ContextMenuType, +} from 'reducers/interfaces'; import { Canvas } from 'cvat-canvas'; import getCore from 'cvat-core'; @@ -48,6 +53,8 @@ interface Props { contrastLevel: number; saturationLevel: number; resetZoom: boolean; + contextVisible: boolean; + contextType: ContextMenuType; onSetupCanvas: () => void; onDragCanvas: (enabled: boolean) => void; onZoomCanvas: (enabled: boolean) => void; @@ -64,7 +71,7 @@ interface Props { onSplitAnnotations(sessionInstance: any, frame: number, state: any): void; onActivateObject(activatedStateID: number | null): void; onSelectObjects(selectedStatesID: number[]): void; - onUpdateContextMenu(visible: boolean, left: number, top: number): void; + onUpdateContextMenu(visible: boolean, left: number, top: number, type: ContextMenuType): void; onAddZLayer(): void; onSwitchZLayer(cur: number): void; onChangeBrightnessLevel(level: number): void; @@ -414,9 +421,14 @@ export default class CanvasWrapperComponent extends React.PureComponent { canvasInstance.html().addEventListener('contextmenu', (e: MouseEvent): void => { const { activatedStateID, + contextType, + contextVisible, } = this.props; - onUpdateContextMenu(activatedStateID !== null, e.clientX, e.clientY); + if (!(contextVisible && contextType === ContextMenuType.CANVAS_SHAPE_POINT)) { + onUpdateContextMenu(activatedStateID !== null, e.clientX, e.clientY, + ContextMenuType.CANVAS_SHAPE); + } }); canvasInstance.html().addEventListener('canvas.editstart', (): void => { @@ -518,7 +530,14 @@ export default class CanvasWrapperComponent extends React.PureComponent { canvasInstance.html().addEventListener('canvas.splitted', this.onTrackSplitted.bind(this)); canvasInstance.html().addEventListener('point.contextmenu', (event: any) => { - console.log(event); + // const { + // activatedStateID, + // } = this.props; + + // console.log(event); + + // onUpdateContextMenu(activatedStateID !== null, event.detail.mouseEvent.clientX, + // event.detail.mouseEvent.clientY, ContextMenuType.CANVAS_SHAPE_POINT); }); } diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss index ed07a9056f3..2ac89546e96 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss +++ b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss @@ -129,6 +129,19 @@ } } +.cvat-canvas-point-context-menu { + opacity: 0.6; + position: fixed; + width: 100px; + z-index: 10; + max-height: 50%; + overflow-y: auto; + + &:hover { + opacity: 1; + } +} + .cvat-canvas-z-axis-wrapper { position: absolute; background: $background-color-2; diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-context-menu.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-context-menu.tsx index fd2ff980568..7bd7b9fbc40 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-context-menu.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-context-menu.tsx @@ -5,15 +5,17 @@ import React from 'react'; import { connect } from 'react-redux'; -import { CombinedState } from 'reducers/interfaces'; +import { CombinedState, ContextMenuType } from 'reducers/interfaces'; import CanvasContextMenuComponent from 'components/annotation-page/standard-workspace/canvas-context-menu'; +import CanvasPointContextMenuComponent from 'components/annotation-page/standard-workspace/canvas-point-context-menu'; interface StateToProps { activatedStateID: number | null; visible: boolean; top: number; left: number; + type: ContextMenuType; collapsed: boolean | undefined; } @@ -29,6 +31,7 @@ function mapStateToProps(state: CombinedState): StateToProps { visible, top, left, + type, }, }, }, @@ -40,6 +43,7 @@ function mapStateToProps(state: CombinedState): StateToProps { visible, left, top, + type, }; } @@ -175,6 +179,7 @@ class CanvasContextMenuContainer extends React.PureComponent { const { visible, activatedStateID, + type, } = this.props; return ( diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx index 0625bece913..e52d999447f 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -39,6 +39,7 @@ import { GridColor, ObjectType, CombinedState, + ContextMenuType, } from 'reducers/interfaces'; import { Canvas } from 'cvat-canvas'; @@ -70,6 +71,8 @@ interface StateToProps { minZLayer: number; maxZLayer: number; curZLayer: number; + contextVisible: boolean; + contextType: ContextMenuType; } interface DispatchToProps { @@ -89,7 +92,7 @@ interface DispatchToProps { onSplitAnnotations(sessionInstance: any, frame: number, state: any): void; onActivateObject: (activatedStateID: number | null) => void; onSelectObjects: (selectedStatesID: number[]) => void; - onUpdateContextMenu(visible: boolean, left: number, top: number): void; + onUpdateContextMenu(visible: boolean, left: number, top: number, type: ContextMenuType): void; onAddZLayer(): void; onSwitchZLayer(cur: number): void; onChangeBrightnessLevel(level: number): void; @@ -104,6 +107,10 @@ function mapStateToProps(state: CombinedState): StateToProps { const { annotation: { canvas: { + contextMenu: { + visible: contextVisible, + type: contextType, + }, instance: canvasInstance, }, drawing: { @@ -179,6 +186,8 @@ function mapStateToProps(state: CombinedState): StateToProps { curZLayer, minZLayer, maxZLayer, + contextVisible, + contextType, }; } @@ -236,8 +245,13 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { onSelectObjects(selectedStatesID: number[]): void { dispatch(selectObjects(selectedStatesID)); }, - onUpdateContextMenu(visible: boolean, left: number, top: number): void { - dispatch(updateCanvasContextMenu(visible, left, top)); + onUpdateContextMenu( + visible: boolean, + left: number, + top: number, + type: ContextMenuType, + ): void { + dispatch(updateCanvasContextMenu(visible, left, top, type)); }, onAddZLayer(): void { dispatch(addZLayer()); diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index d5de5f6009c..550a61e4f19 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -12,6 +12,7 @@ import { ActiveControl, ShapeType, ObjectType, + ContextMenuType, } from './interfaces'; const defaultState: AnnotationState = { @@ -23,6 +24,7 @@ const defaultState: AnnotationState = { visible: false, left: 0, top: 0, + type: ContextMenuType.CANVAS_SHAPE, }, instance: new Canvas(), ready: false, @@ -923,6 +925,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { visible, left, top, + type, } = action.payload; return { @@ -934,6 +937,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { visible, left, top, + type, }, }, }; diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index 74e36d86c56..f7bf7131897 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -282,6 +282,7 @@ export enum StatesOrdering { export enum ContextMenuType { CANVAS = 'canvas', CANVAS_SHAPE = 'canvas_shape', + CANVAS_SHAPE_POINT = 'canvas_shape_point', } export enum Rotation { @@ -301,6 +302,7 @@ export interface AnnotationState { visible: boolean; top: number; left: number; + type: ContextMenuType; }; instance: Canvas; ready: boolean; From 936d3059cf72064f397ff72c289d18b6b8ca73c8 Mon Sep 17 00:00:00 2001 From: zhiltsov-max Date: Wed, 18 Mar 2020 14:45:44 +0300 Subject: [PATCH 12/49] [Datumaro] Fix frame matching in video annotations import (#1274) * Add extra frame matching way for videos * Add line to changelog --- CHANGELOG.md | 3 ++- cvat/apps/dataset_manager/bindings.py | 4 ++++ datumaro/datumaro/plugins/yolo_format/extractor.py | 4 +++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb69400edd8..3e62ba4511e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - ### Fixed -- +- Frame name matching for video annotations import - + allowed `frame_XXXXXX[.ext]` format ([#1274](https://github.com/opencv/cvat/pull/1274)) ### Security - Bump acorn from 6.3.0 to 6.4.1 in /cvat-ui ([#1270](https://github.com/opencv/cvat/pull/1270)) diff --git a/cvat/apps/dataset_manager/bindings.py b/cvat/apps/dataset_manager/bindings.py index da37a3048e6..6b531545c38 100644 --- a/cvat/apps/dataset_manager/bindings.py +++ b/cvat/apps/dataset_manager/bindings.py @@ -177,6 +177,8 @@ def __init__(self, url, db_task, user): def match_frame(item, cvat_task_anno): + is_video = cvat_task_anno.meta['task']['mode'] == 'interpolation' + frame_number = None if frame_number is None: try: @@ -193,6 +195,8 @@ def match_frame(item, cvat_task_anno): frame_number = int(item.id) except Exception: pass + if frame_number is None and is_video and item.id.startswith('frame_'): + frame_number = int(item.id[len('frame_'):]) if not frame_number in cvat_task_anno.frame_info: raise Exception("Could not match item id: '%s' with any task frame" % item.id) diff --git a/datumaro/datumaro/plugins/yolo_format/extractor.py b/datumaro/datumaro/plugins/yolo_format/extractor.py index 7840b26c5ca..11e829d4a5b 100644 --- a/datumaro/datumaro/plugins/yolo_format/extractor.py +++ b/datumaro/datumaro/plugins/yolo_format/extractor.py @@ -90,7 +90,9 @@ def __init__(self, config_path, image_info=None): subset = YoloExtractor.Subset(subset_name, self) with open(list_path, 'r') as f: subset.items = OrderedDict( - (osp.splitext(osp.basename(p))[0], p.strip()) for p in f) + (osp.splitext(osp.basename(p.strip()))[0], p.strip()) + for p in f + ) for item_id, image_path in subset.items.items(): image_path = self._make_local_path(image_path) From 95451320979978bb28ec7f4459294bc5cf6e3372 Mon Sep 17 00:00:00 2001 From: zhiltsov-max Date: Wed, 18 Mar 2020 14:54:44 +0300 Subject: [PATCH 13/49] [Datumaro] Allow empty COCO dataset export (#1272) * Allow empty dataset export in coco * Add line to changelog Co-authored-by: Nikita Manovich <40690625+nmanovic@users.noreply.github.com> --- CHANGELOG.md | 1 + .../datumaro/plugins/coco_format/converter.py | 36 +++++++++++-------- datumaro/tests/test_coco_format.py | 5 ++- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e62ba4511e..03542b30260 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - ### Fixed +- Annotation-less tasks now can be exported as empty datasets in COCO ([#1277](https://github.com/opencv/cvat/issues/1277)) - Frame name matching for video annotations import - allowed `frame_XXXXXX[.ext]` format ([#1274](https://github.com/opencv/cvat/pull/1274)) diff --git a/datumaro/datumaro/plugins/coco_format/converter.py b/datumaro/datumaro/plugins/coco_format/converter.py index 39fe7b15402..403a6a83eb6 100644 --- a/datumaro/datumaro/plugins/coco_format/converter.py +++ b/datumaro/datumaro/plugins/coco_format/converter.py @@ -329,20 +329,24 @@ def save_categories(self, dataset): label_categories = dataset.categories().get(AnnotationType.label) if label_categories is None: return - points_categories = dataset.categories().get(AnnotationType.points) - if points_categories is None: - return - - for idx, kp_cat in points_categories.items.items(): - label_cat = label_categories.items[idx] + point_categories = dataset.categories().get(AnnotationType.points) + for idx, label_cat in enumerate(label_categories.items): cat = { 'id': 1 + idx, 'name': _cast(label_cat.name, str, ''), 'supercategory': _cast(label_cat.parent, str, ''), - 'keypoints': [str(l) for l in kp_cat.labels], - 'skeleton': [int(i) for i in kp_cat.adjacent], + 'keypoints': [], + 'skeleton': [], } + + if point_categories is not None: + kp_cat = point_categories.items.get(idx) + if kp_cat is not None: + cat.update({ + 'keypoints': [str(l) for l in kp_cat.labels], + 'skeleton': [int(i) for i in kp_cat.adjacent], + }) self.categories.append(cat) def save_annotations(self, item): @@ -447,14 +451,19 @@ class _Converter: def __init__(self, extractor, save_dir, tasks=None, save_images=False, segmentation_mode=None, crop_covered=False): - assert tasks is None or isinstance(tasks, (CocoTask, list)) + assert tasks is None or isinstance(tasks, (CocoTask, list, str)) if tasks is None: tasks = list(self._TASK_CONVERTER) elif isinstance(tasks, CocoTask): tasks = [tasks] + elif isinstance(tasks, str): + tasks = [CocoTask[tasks]] else: - for t in tasks: - assert t in CocoTask + for i, t in enumerate(tasks): + if isinstance(t, str): + tasks[i] = CocoTask[t] + else: + assert t in CocoTask, t self._tasks = tasks self._extractor = extractor @@ -546,9 +555,8 @@ def convert(self): task_conv.save_annotations(item) for task, task_conv in task_converters.items(): - if not task_conv.is_empty(): - task_conv.write(osp.join(self._ann_dir, - '%s_%s.json' % (task.name, subset_name))) + task_conv.write(osp.join(self._ann_dir, + '%s_%s.json' % (task.name, subset_name))) class CocoConverter(Converter, CliPlugin): @staticmethod diff --git a/datumaro/tests/test_coco_format.py b/datumaro/tests/test_coco_format.py index 724fdc5a4af..f9340b659e8 100644 --- a/datumaro/tests/test_coco_format.py +++ b/datumaro/tests/test_coco_format.py @@ -632,10 +632,13 @@ def __iter__(self): def categories(self): label_cat = LabelCategories() + point_cat = PointsCategories() for label in range(10): label_cat.add('label_' + str(label)) + point_cat.add(label) return { AnnotationType.label: label_cat, + AnnotationType.points: point_cat, } with TestDir() as test_dir: @@ -651,4 +654,4 @@ def __iter__(self): with TestDir() as test_dir: self._test_save_and_load(TestExtractor(), - CocoConverter(), test_dir) \ No newline at end of file + CocoConverter(tasks='image_info'), test_dir) \ No newline at end of file From 08688b0c3e3e280a0f9a4973aab48a36589de399 Mon Sep 17 00:00:00 2001 From: zhiltsov-max Date: Wed, 18 Mar 2020 14:57:51 +0300 Subject: [PATCH 14/49] [Datumaro] Fix occluded and z_order attributes export (#1271) * Fix occluded and z_order attributes export * Add line to changelog Co-authored-by: Nikita Manovich <40690625+nmanovic@users.noreply.github.com> --- CHANGELOG.md | 1 + cvat/apps/dataset_manager/bindings.py | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03542b30260..0da60518f6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - ### Fixed +- `occluded` and `z_order` annotation attributes are now correctly passed to Datumaro ([#1271](https://github.com/opencv/cvat/pull/1271)) - Annotation-less tasks now can be exported as empty datasets in COCO ([#1277](https://github.com/opencv/cvat/issues/1277)) - Frame name matching for video annotations import - allowed `frame_XXXXXX[.ext]` format ([#1274](https://github.com/opencv/cvat/pull/1274)) diff --git a/cvat/apps/dataset_manager/bindings.py b/cvat/apps/dataset_manager/bindings.py index 6b531545c38..d1d98af279a 100644 --- a/cvat/apps/dataset_manager/bindings.py +++ b/cvat/apps/dataset_manager/bindings.py @@ -91,7 +91,9 @@ def categories(self): @staticmethod def _load_categories(cvat_anno): categories = {} - label_categories = datumaro.LabelCategories() + + label_categories = datumaro.LabelCategories( + attributes=['occluded', 'z_order']) for _, label in cvat_anno.meta['task']['labels']: label_categories.add(label['name']) @@ -144,6 +146,8 @@ def convert_attrs(label, cvat_attrs): anno_group = shape_obj.group anno_label = map_label(shape_obj.label) anno_attr = convert_attrs(shape_obj.label, shape_obj.attributes) + anno_attr['occluded'] = shape_obj.occluded + anno_attr['z_order'] = shape_obj.z_order anno_points = shape_obj.points if shape_obj.type == ShapeType.POINTS: @@ -238,7 +242,7 @@ def import_dm_annotations(dm_dataset, cvat_task_anno): frame=frame_number, label=label_cat.items[ann.label].name, points=ann.points, - occluded=False, + occluded=ann.attributes.get('occluded') == True, group=group_map.get(ann.group, 0), attributes=[cvat_task_anno.Attribute(name=n, value=str(v)) for n, v in ann.attributes.items()], From 731b8967c01847c999f9ec0986e575196513fa18 Mon Sep 17 00:00:00 2001 From: zhiltsov-max Date: Wed, 18 Mar 2020 15:16:26 +0300 Subject: [PATCH 15/49] Fix LabelMe format (#1260) * Fix labelme filenames * Change module path * Add tests for LabelMe * Update test * Fix test * Add line in changelog --- CHANGELOG.md | 1 + cvat/apps/annotation/labelme.py | 8 ++++---- cvat/apps/engine/tests/test_rest_api.py | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0da60518f6b..e50226d9110 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - ### Fixed +- File names in LabelMe format export are no longer truncated ([#1259](https://github.com/opencv/cvat/issues/1259)) - `occluded` and `z_order` annotation attributes are now correctly passed to Datumaro ([#1271](https://github.com/opencv/cvat/pull/1271)) - Annotation-less tasks now can be exported as empty datasets in COCO ([#1277](https://github.com/opencv/cvat/issues/1277)) - Frame name matching for video annotations import - diff --git a/cvat/apps/annotation/labelme.py b/cvat/apps/annotation/labelme.py index 0128ca73922..baacb388ef0 100644 --- a/cvat/apps/annotation/labelme.py +++ b/cvat/apps/annotation/labelme.py @@ -107,17 +107,17 @@ def dump_frame_anno(frame_annotation): return ET.tostring(root_elem, encoding='unicode', pretty_print=True) def dump_as_labelme_annotation(file_object, annotations): + import os.path as osp from zipfile import ZipFile, ZIP_DEFLATED with ZipFile(file_object, 'w', compression=ZIP_DEFLATED) as output_zip: for frame_annotation in annotations.group_by_frame(): xml_data = dump_frame_anno(frame_annotation) - filename = frame_annotation.name - filename = filename[ : filename.rfind('.')] + '.xml' + filename = osp.splitext(frame_annotation.name)[0] + '.xml' output_zip.writestr(filename, xml_data) def parse_xml_annotations(xml_data, annotations, input_zip): - from cvat.apps.annotation.coco import mask_to_polygon + from datumaro.util.mask_tools import mask_to_polygons from io import BytesIO from lxml import etree as ET import numpy as np @@ -229,7 +229,7 @@ def parse_attributes(attributes_string): mask = input_zip.read(osp.join(_MASKS_DIR, mask_file)) mask = np.asarray(Image.open(BytesIO(mask)).convert('L')) mask = (mask != 0) - polygons = mask_to_polygon(mask) + polygons = mask_to_polygons(mask) for polygon in polygons: ann_items.append(annotations.LabeledShape( diff --git a/cvat/apps/engine/tests/test_rest_api.py b/cvat/apps/engine/tests/test_rest_api.py index d959f71c5b5..f3da0410623 100644 --- a/cvat/apps/engine/tests/test_rest_api.py +++ b/cvat/apps/engine/tests/test_rest_api.py @@ -2655,6 +2655,15 @@ def _get_initial_annotation(annotation_format): "points": [20.0, 0.1, 10, 3.22, 4, 7, 10, 30, 1, 2, 4.44, 5.55], "type": "polygon", "occluded": True + }, + { + "frame": 2, + "label_id": task["labels"][1]["id"], + "group": 1, + "attributes": [], + "points": [4, 7, 10, 30, 4, 5.55], + "type": "polygon", + "occluded": False }] tags_wo_attrs = [{ @@ -2711,6 +2720,12 @@ def _get_initial_annotation(annotation_format): elif annotation_format == "MOT CSV 1.0": annotations["tracks"] = rectangle_tracks_wo_attrs + elif annotation_format == "LabelMe ZIP 3.0 for images": + annotations["shapes"] = rectangle_shapes_with_attrs + \ + rectangle_shapes_wo_attrs + \ + polygon_shapes_wo_attrs + \ + polygon_shapes_with_attrs + return annotations response = self._get_annotation_formats(annotator) From 845be3b48601cb72664a0db8ae9e66f1dfff16bb Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Thu, 19 Mar 2020 02:03:42 +0300 Subject: [PATCH 16/49] Added point deletion context menu --- cvat-ui/src/actions/annotation-actions.ts | 2 + .../canvas-point-context-menu.tsx | 9 +- .../standard-workspace/canvas-wrapper.tsx | 36 +++- .../standard-workspace/standard-workspace.tsx | 2 + .../standard-workspace/styles.scss | 4 +- .../canvas-context-menu.tsx | 17 +- .../canvas-point-context-menu.tsx | 180 ++++++++++++++++++ .../standard-workspace/canvas-wrapper.tsx | 13 +- cvat-ui/src/reducers/annotation-reducer.ts | 3 + cvat-ui/src/reducers/interfaces.ts | 1 + 10 files changed, 240 insertions(+), 27 deletions(-) create mode 100644 cvat-ui/src/containers/annotation-page/standard-workspace/canvas-point-context-menu.tsx diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 2652cee33ed..4041973dc31 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -318,6 +318,7 @@ export function updateCanvasContextMenu( left: number, top: number, type?: ContextMenuType, + pointID?: number, ): AnyAction { return { type: AnnotationActionTypes.UPDATE_CANVAS_CONTEXT_MENU, @@ -326,6 +327,7 @@ export function updateCanvasContextMenu( left, top, type, + pointID, }, }; } diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-point-context-menu.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-point-context-menu.tsx index 4ce488f37f7..b2cf338d263 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-point-context-menu.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-point-context-menu.tsx @@ -5,16 +5,21 @@ import React from 'react'; import ReactDOM from 'react-dom'; +import { + Button, +} from 'antd'; interface Props { activatedStateID: number | null; visible: boolean; left: number; top: number; + onPointDelete(): void; } export default function CanvasPointContextMenu(props: Props): JSX.Element | null { const { + onPointDelete, activatedStateID, visible, left, @@ -27,7 +32,9 @@ export default function CanvasPointContextMenu(props: Props): JSX.Element | null return ReactDOM.createPortal(
      - Haha +
      , window.document.body, ); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx index f74e2c2cb18..0aedbdb09d5 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -18,16 +18,10 @@ import { GridColor, ObjectType, ContextMenuType, - Workspace + Workspace, } from 'reducers/interfaces'; import { Canvas } from 'cvat-canvas'; import getCore from 'cvat-core'; -import { - ColorBy, - GridColor, - ObjectType, - Workspace, -} from 'reducers/interfaces'; const cvat = getCore(); @@ -81,7 +75,8 @@ interface Props { onSplitAnnotations(sessionInstance: any, frame: number, state: any): void; onActivateObject(activatedStateID: number | null): void; onSelectObjects(selectedStatesID: number[]): void; - onUpdateContextMenu(visible: boolean, left: number, top: number, type: ContextMenuType): void; + onUpdateContextMenu(visible: boolean, left: number, top: number, type: ContextMenuType, + pointID?: number): void; onAddZLayer(): void; onSwitchZLayer(cur: number): void; onChangeBrightnessLevel(level: number): void; @@ -333,8 +328,17 @@ export default class CanvasWrapperComponent extends React.PureComponent { }; private onCanvasContextMenu = (e: MouseEvent): void => { - const { activatedStateID, onUpdateContextMenu } = this.props; - onUpdateContextMenu(activatedStateID !== null, e.clientX, e.clientY); + const { + activatedStateID, + onUpdateContextMenu, + contextVisible, + contextType, + } = this.props; + + if (!contextVisible && contextType !== ContextMenuType.CANVAS_SHAPE_POINT) { + onUpdateContextMenu(activatedStateID !== null, e.clientX, e.clientY, + ContextMenuType.CANVAS_SHAPE); + } }; private onCanvasShapeClicked = (e: any): void => { @@ -460,6 +464,16 @@ export default class CanvasWrapperComponent extends React.PureComponent { } }; + private onCanvasPointContextMenu = (e: any): void => { + const { + activatedStateID, + onUpdateContextMenu, + } = this.props; + + onUpdateContextMenu(activatedStateID !== null, e.detail.mouseEvent.clientX, + e.detail.mouseEvent.clientY, ContextMenuType.CANVAS_SHAPE_POINT, e.detail.pointID); + }; + private activateOnCanvas(): void { const { activatedStateID, @@ -599,6 +613,8 @@ export default class CanvasWrapperComponent extends React.PureComponent { canvasInstance.html().addEventListener('canvas.merged', this.onCanvasObjectsMerged); canvasInstance.html().addEventListener('canvas.groupped', this.onCanvasObjectsGroupped); canvasInstance.html().addEventListener('canvas.splitted', this.onCanvasTrackSplitted); + + canvasInstance.html().addEventListener('point.contextmenu', this.onCanvasPointContextMenu); } public render(): JSX.Element { diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx index e9bfd6c4e00..5cfcba47bec 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx @@ -14,6 +14,7 @@ import ControlsSideBarContainer from 'containers/annotation-page/standard-worksp import ObjectSideBarContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/objects-side-bar'; import PropagateConfirmContainer from 'containers/annotation-page/standard-workspace/propagate-confirm'; import CanvasContextMenuContainer from 'containers/annotation-page/standard-workspace/canvas-context-menu'; +import CanvasPointContextMenuContainer from 'containers/annotation-page/standard-workspace/canvas-point-context-menu'; export default function StandardWorkspaceComponent(): JSX.Element { return ( @@ -23,6 +24,7 @@ export default function StandardWorkspaceComponent(): JSX.Element { + ); } diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss index 5f276eb50ef..1dd7d769200 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss +++ b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss @@ -136,10 +136,12 @@ .cvat-canvas-point-context-menu { opacity: 0.6; position: fixed; - width: 100px; + width: 135px; z-index: 10; max-height: 50%; overflow-y: auto; + background-color: #ffffff; + border-radius: 4px; &:hover { opacity: 1; diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-context-menu.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-context-menu.tsx index 7bd7b9fbc40..3d0a508c03c 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-context-menu.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-context-menu.tsx @@ -8,7 +8,6 @@ import { connect } from 'react-redux'; import { CombinedState, ContextMenuType } from 'reducers/interfaces'; import CanvasContextMenuComponent from 'components/annotation-page/standard-workspace/canvas-context-menu'; -import CanvasPointContextMenuComponent from 'components/annotation-page/standard-workspace/canvas-point-context-menu'; interface StateToProps { activatedStateID: number | null; @@ -183,12 +182,16 @@ class CanvasContextMenuContainer extends React.PureComponent { } = this.props; return ( - + <> + { type === ContextMenuType.CANVAS_SHAPE && ( + + )} + ); } } diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-point-context-menu.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-point-context-menu.tsx new file mode 100644 index 00000000000..74d07ff8326 --- /dev/null +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-point-context-menu.tsx @@ -0,0 +1,180 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { connect } from 'react-redux'; +import { CombinedState, ContextMenuType } from 'reducers/interfaces'; + +import { updateAnnotationsAsync, updateCanvasContextMenu } from 'actions/annotation-actions'; + +import CanvasPointContextMenuComponent from 'components/annotation-page/standard-workspace/canvas-point-context-menu'; + +interface StateToProps { + activatedStateID: number | null; + activetedPointID: number | null | undefined; + states: any[]; + visible: boolean; + top: number; + left: number; + type: ContextMenuType; +} + +function mapStateToProps(state: CombinedState): StateToProps { + const { + annotation: { + annotations: { + states, + activatedStateID, + }, + canvas: { + contextMenu: { + visible, + top, + left, + type, + pointID: activetedPointID, + }, + }, + }, + } = state; + + return { + activatedStateID, + activetedPointID, + states, + visible, + left, + top, + type, + }; +} + +interface DispatchToProps { + onUpdateAnnotations(states: any[]): void; + onCloseContextMenu(): void; +} + +function mapDispatchToProps(dispatch: any): DispatchToProps { + return { + onUpdateAnnotations(states: any[]): void { + dispatch(updateAnnotationsAsync(states)); + }, + onCloseContextMenu(): void { + dispatch(updateCanvasContextMenu(false, 0, 0)); + }, + }; +} + +type Props = StateToProps & DispatchToProps; + +interface State { + latestLeft: number; + latestTop: number; + left: number; + top: number; +} + +class CanvasContextMenuContainer extends React.PureComponent { + public constructor(props: Props) { + super(props); + + this.state = { + latestLeft: 0, + latestTop: 0, + left: 0, + top: 0, + }; + } + + static getDerivedStateFromProps(props: Props, state: State): State | null { + if (props.left === state.latestLeft + && props.top === state.latestTop) { + return null; + } + + return { + ...state, + latestLeft: props.left, + latestTop: props.top, + top: props.top, + left: props.left, + }; + } + + public componentDidUpdate(): void { + const { + top, + left, + } = this.state; + + const { + innerWidth, + innerHeight, + } = window; + + const [element] = window.document.getElementsByClassName('cvat-canvas-point-context-menu'); + if (element) { + const height = element.clientHeight; + const width = element.clientWidth; + + if (top + height > innerHeight || left + width > innerWidth) { + this.setState({ + top: top - Math.max(top + height - innerHeight, 0), + left: left - Math.max(left + width - innerWidth, 0), + }); + } + } + } + + private deletePoint(): void { + const { + activetedPointID, + activatedStateID, + states, + onUpdateAnnotations, + onCloseContextMenu, + } = this.props; + + const [objectState] = states.filter((e) => (e.clientID === activatedStateID)); + if (activetedPointID) { + objectState.points = objectState.points.slice(0, activetedPointID * 2) + .concat(objectState.points.slice(activetedPointID * 2 + 2)); + onUpdateAnnotations([objectState]); + onCloseContextMenu(); + } + } + + public render(): JSX.Element { + const { + visible, + activatedStateID, + type, + } = this.props; + + const { + top, + left, + } = this.state; + + return ( + <> + {type === ContextMenuType.CANVAS_SHAPE_POINT && ( + this.deletePoint()} + /> + )} + + ); + } +} + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(CanvasContextMenuContainer); diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx index 7d99f5926e4..89f0af7e4e2 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -96,7 +96,8 @@ interface DispatchToProps { onSplitAnnotations(sessionInstance: any, frame: number, state: any): void; onActivateObject: (activatedStateID: number | null) => void; onSelectObjects: (selectedStatesID: number[]) => void; - onUpdateContextMenu(visible: boolean, left: number, top: number, type: ContextMenuType): void; + onUpdateContextMenu(visible: boolean, left: number, top: number, type: ContextMenuType, + pointID?: number): void; onAddZLayer(): void; onSwitchZLayer(cur: number): void; onChangeBrightnessLevel(level: number): void; @@ -257,13 +258,9 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { onSelectObjects(selectedStatesID: number[]): void { dispatch(selectObjects(selectedStatesID)); }, - onUpdateContextMenu( - visible: boolean, - left: number, - top: number, - type: ContextMenuType, - ): void { - dispatch(updateCanvasContextMenu(visible, left, top, type)); + onUpdateContextMenu(visible: boolean, left: number, top: number, + type: ContextMenuType, pointID?: number): void { + dispatch(updateCanvasContextMenu(visible, left, top, type, pointID)); }, onAddZLayer(): void { dispatch(addZLayer()); diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index 70eaf594239..fcb791d1a1a 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -26,6 +26,7 @@ const defaultState: AnnotationState = { left: 0, top: 0, type: ContextMenuType.CANVAS_SHAPE, + pointID: null, }, instance: new Canvas(), ready: false, @@ -934,6 +935,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { left, top, type, + pointID, } = action.payload; return { @@ -946,6 +948,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { left, top, type, + pointID, }, }, }; diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index 4c308a7b0e8..1b8884059c2 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -303,6 +303,7 @@ export interface AnnotationState { top: number; left: number; type: ContextMenuType; + pointID: number | null | undefined; }; instance: Canvas; ready: boolean; From bfd300039ec68aaa601b2b2adb61449188886b1e Mon Sep 17 00:00:00 2001 From: Boris Sekachev <40690378+bsekachev@users.noreply.github.com> Date: Thu, 19 Mar 2020 19:23:49 +0300 Subject: [PATCH 17/49] React UI: Added logging (#1288) --- cvat-canvas/README.md | 21 +- cvat-canvas/src/typescript/canvasView.ts | 26 ++- cvat-canvas/src/typescript/drawHandler.ts | 21 +- cvat-canvas/src/typescript/mergeHandler.ts | 9 +- cvat-core/src/api.js | 73 ++++-- cvat-core/src/config.js | 3 - cvat-core/src/enums.js | 123 ++++++----- cvat-core/src/log.js | 209 ++++++++++++++++++ cvat-core/src/logger-storage.js | 169 ++++++++++++++ cvat-core/src/logging.js | 42 ---- cvat-core/src/server-proxy.js | 22 ++ cvat-core/src/session.js | 59 ++--- cvat-ui/src/actions/annotation-actions.ts | 135 ++++++++++- .../annotation-page/annotation-page.tsx | 10 +- .../attribute-annotation-sidebar.tsx | 12 + .../standard-workspace/canvas-wrapper.tsx | 59 +++-- cvat-ui/src/components/cvat-app.tsx | 22 +- .../annotation-page/annotation-page.tsx | 6 +- .../objects-side-bar/object-item.tsx | 15 +- cvat-ui/src/cvat-logger.ts | 14 ++ cvat-ui/src/reducers/interfaces.ts | 1 + cvat-ui/src/reducers/notifications-reducer.ts | 16 ++ cvat-ui/webpack.config.js | 4 +- 23 files changed, 867 insertions(+), 204 deletions(-) create mode 100644 cvat-core/src/log.js create mode 100644 cvat-core/src/logger-storage.js delete mode 100644 cvat-core/src/logging.js create mode 100644 cvat-ui/src/cvat-logger.ts diff --git a/cvat-canvas/README.md b/cvat-canvas/README.md index 2b9f9201ba7..7fe6049176e 100644 --- a/cvat-canvas/README.md +++ b/cvat-canvas/README.md @@ -37,6 +37,19 @@ Canvas itself handles: EXTREME_POINTS = 'By 4 points' } + enum Mode { + IDLE = 'idle', + DRAG = 'drag', + RESIZE = 'resize', + DRAW = 'draw', + EDIT = 'edit', + MERGE = 'merge', + SPLIT = 'split', + GROUP = 'group', + DRAG_CANVAS = 'drag_canvas', + ZOOM_CANVAS = 'zoom_canvas', + } + interface DrawData { enabled: boolean; shapeType?: string; @@ -70,6 +83,7 @@ Canvas itself handles: } interface Canvas { + mode(): Mode; html(): HTMLDivElement; setZLayer(zLayer: number | null): void; setup(frameData: any, objectStates: any[]): void; @@ -128,6 +142,10 @@ Standard JS events are used. - canvas.dragstop - canvas.zoomstart - canvas.zoomstop + - canvas.zoom + - canvas.fit + - canvas.dragshape => {id: number} + - canvas.resizeshape => {id: number} ``` ### WEB @@ -135,7 +153,8 @@ Standard JS events are used. // Create an instance of a canvas const canvas = new window.canvas.Canvas(); - console.log('Version', window.canvas.CanvasVersion); + console.log('Version ', window.canvas.CanvasVersion); + console.log('Current mode is ', window.canvas.mode()); // Put canvas to a html container htmlContainer.appendChild(canvas.html()); diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 65e272207e4..5db41045586 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -74,7 +74,7 @@ export class CanvasViewImpl implements CanvasView, Listener { return this.controller.mode; } - private onDrawDone(data: object, continueDraw?: boolean): void { + private onDrawDone(data: object | null, duration: number, continueDraw?: boolean): void { if (data) { const { zLayer } = this.controller; const event: CustomEvent = new CustomEvent('canvas.drawn', { @@ -87,6 +87,7 @@ export class CanvasViewImpl implements CanvasView, Listener { zOrder: zLayer || 0, }, continue: continueDraw, + duration, }, }); @@ -137,12 +138,13 @@ export class CanvasViewImpl implements CanvasView, Listener { this.mode = Mode.IDLE; } - private onMergeDone(objects: any[]): void { + private onMergeDone(objects: any[]| null, duration?: number): void { if (objects) { const event: CustomEvent = new CustomEvent('canvas.merged', { bubbles: false, cancelable: true, detail: { + duration, states: objects, }, }); @@ -701,6 +703,12 @@ export class CanvasViewImpl implements CanvasView, Listener { } else if ([UpdateReasons.IMAGE_ZOOMED, UpdateReasons.IMAGE_FITTED].includes(reason)) { this.moveCanvas(); this.transformCanvas(); + if (reason === UpdateReasons.IMAGE_FITTED) { + this.canvas.dispatchEvent(new CustomEvent('canvas.fit', { + bubbles: false, + cancelable: true, + })); + } } else if (reason === UpdateReasons.IMAGE_MOVED) { this.moveCanvas(); } else if ([UpdateReasons.OBJECTS_UPDATED, UpdateReasons.SET_Z_LAYER].includes(reason)) { @@ -1159,6 +1167,13 @@ export class CanvasViewImpl implements CanvasView, Listener { ).map((x: number): number => x - offset); this.drawnStates[state.clientID].points = points; + this.canvas.dispatchEvent(new CustomEvent('canvas.dragshape', { + bubbles: false, + cancelable: true, + detail: { + id: state.clientID, + }, + })); this.onEditDone(state, points); } }); @@ -1209,6 +1224,13 @@ export class CanvasViewImpl implements CanvasView, Listener { ).map((x: number): number => x - offset); this.drawnStates[state.clientID].points = points; + this.canvas.dispatchEvent(new CustomEvent('canvas.resizeshape', { + bubbles: false, + cancelable: true, + detail: { + id: state.clientID, + }, + })); this.onEditDone(state, points); } }); diff --git a/cvat-canvas/src/typescript/drawHandler.ts b/cvat-canvas/src/typescript/drawHandler.ts index 60745f9f3ec..4afe75ec259 100644 --- a/cvat-canvas/src/typescript/drawHandler.ts +++ b/cvat-canvas/src/typescript/drawHandler.ts @@ -31,7 +31,8 @@ export interface DrawHandler { export class DrawHandlerImpl implements DrawHandler { // callback is used to notify about creating new shape - private onDrawDone: (data: object, continueDraw?: boolean) => void; + private onDrawDone: (data: object | null, duration?: number, continueDraw?: boolean) => void; + private startTimestamp: number; private canvas: SVG.Container; private text: SVG.Container; private cursorPosition: { @@ -180,7 +181,7 @@ export class DrawHandlerImpl implements DrawHandler { this.onDrawDone({ shapeType, points: [xtl, ytl, xbr, ybr], - }); + }, Date.now() - this.startTimestamp); } }).on('drawupdate', (): void => { this.shapeSizeElement.update(this.drawInstance); @@ -213,7 +214,7 @@ export class DrawHandlerImpl implements DrawHandler { this.onDrawDone({ shapeType, points: [xtl, ytl, xbr, ybr], - }); + }, Date.now() - this.startTimestamp); } } }).on('undopoint', (): void => { @@ -300,7 +301,7 @@ export class DrawHandlerImpl implements DrawHandler { this.onDrawDone({ shapeType, points, - }); + }, Date.now() - this.startTimestamp); } else if (shapeType === 'polyline' && ((box.xbr - box.xtl) >= consts.SIZE_THRESHOLD || (box.ybr - box.ytl) >= consts.SIZE_THRESHOLD) @@ -308,13 +309,13 @@ export class DrawHandlerImpl implements DrawHandler { this.onDrawDone({ shapeType, points, - }); + }, Date.now() - this.startTimestamp); } else if (shapeType === 'points' && (e.target as any).getAttribute('points') !== '0,0') { this.onDrawDone({ shapeType, points, - }); + }, Date.now() - this.startTimestamp); } }); } @@ -365,7 +366,7 @@ export class DrawHandlerImpl implements DrawHandler { attributes: { ...this.drawData.initialState.attributes }, label: this.drawData.initialState.label, color: this.drawData.initialState.color, - }, e.detail.originalEvent.ctrlKey); + }, Date.now() - this.startTimestamp, e.detail.originalEvent.ctrlKey); }); } @@ -405,7 +406,7 @@ export class DrawHandlerImpl implements DrawHandler { attributes: { ...this.drawData.initialState.attributes }, label: this.drawData.initialState.label, color: this.drawData.initialState.color, - }, e.detail.originalEvent.ctrlKey); + }, Date.now() - this.startTimestamp, e.detail.originalEvent.ctrlKey); }); } @@ -583,14 +584,16 @@ export class DrawHandlerImpl implements DrawHandler { this.setupDrawEvents(); } + this.startTimestamp = Date.now(); this.initialized = true; } public constructor( - onDrawDone: (data: object, continueDraw?: boolean) => void, + onDrawDone: (data: object | null, duration?: number, continueDraw?: boolean) => void, canvas: SVG.Container, text: SVG.Container, ) { + this.startTimestamp = Date.now(); this.onDrawDone = onDrawDone; this.canvas = canvas; this.text = text; diff --git a/cvat-canvas/src/typescript/mergeHandler.ts b/cvat-canvas/src/typescript/mergeHandler.ts index efaa4ac09d7..cfb3f78c43d 100644 --- a/cvat-canvas/src/typescript/mergeHandler.ts +++ b/cvat-canvas/src/typescript/mergeHandler.ts @@ -15,8 +15,9 @@ export interface MergeHandler { export class MergeHandlerImpl implements MergeHandler { // callback is used to notify about merging end - private onMergeDone: (objects: any[]) => void; + private onMergeDone: (objects: any[] | null, duration?: number) => void; private onFindObject: (event: MouseEvent) => void; + private startTimestamp: number; private canvas: SVG.Container; private initialized: boolean; private statesToBeMerged: any[]; // are being merged @@ -57,6 +58,7 @@ export class MergeHandlerImpl implements MergeHandler { private initMerging(): void { this.canvas.node.addEventListener('click', this.onFindObject); + this.startTimestamp = Date.now(); this.initialized = true; } @@ -66,7 +68,7 @@ export class MergeHandlerImpl implements MergeHandler { this.release(); if (statesToBeMerged.length > 1) { - this.onMergeDone(statesToBeMerged); + this.onMergeDone(statesToBeMerged, Date.now() - this.startTimestamp); } else { this.onMergeDone(null); // here is a cycle @@ -77,12 +79,13 @@ export class MergeHandlerImpl implements MergeHandler { } public constructor( - onMergeDone: (objects: any[]) => void, + onMergeDone: (objects: any[] | null, duration?: number) => void, onFindObject: (event: MouseEvent) => void, canvas: SVG.Container, ) { this.onMergeDone = onMergeDone; this.onFindObject = onFindObject; + this.startTimestamp = Date.now(); this.canvas = canvas; this.statesToBeMerged = []; this.highlightedShapes = {}; diff --git a/cvat-core/src/api.js b/cvat-core/src/api.js index 7f9ad9c7448..4eb4e99af00 100644 --- a/cvat-core/src/api.js +++ b/cvat-core/src/api.js @@ -14,7 +14,8 @@ function build() { const PluginRegistry = require('./plugins'); - const User = require('./user'); + const loggerStorage = require('./logger-storage'); + const Log = require('./log'); const ObjectState = require('./object-state'); const Statistics = require('./statistics'); const { Job, Task } = require('./session'); @@ -41,6 +42,7 @@ function build() { ServerError, } = require('./exceptions'); + const User = require('./user'); const pjson = require('../package.json'); const config = require('./config'); @@ -419,6 +421,53 @@ function build() { return result; }, }, + /** + * Namespace to working with logs + * @namespace logger + * @memberof module:API.cvat + */ + /** + * Method to logger configuration + * @method configure + * @memberof module:API.cvat.logger + * @param {function} isActiveChecker - callback to know if logger + * should increase working time or not + * @param {object} userActivityCallback - container for a callback
      + * Logger put here a callback to update user activity timer
      + * You can call it outside + * @instance + * @async + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + + /** + * Append log to a log collection
      + * Durable logs will have been added after "close" method is called for them
      + * Ignore rules exist for some logs (e.g. zoomImage, changeAttribute)
      + * Payload of ignored logs are shallowly combined to previous logs of the same type + * @method log + * @memberof module:API.cvat.logger + * @param {module:API.cvat.enums.LogType | string} type - log type + * @param {Object} [payload = {}] - any other data that will be appended to the log + * @param {boolean} [wait = false] - specifies if log is durable + * @returns {module:API.cvat.classes.Log} + * @instance + * @async + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + + /** + * Save accumulated logs on a server + * @method save + * @memberof module:API.cvat.logger + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ServerError} + * @instance + * @async + */ + logger: loggerStorage, /** * Namespace contains some changeable configurations * @namespace config @@ -432,12 +481,6 @@ function build() { * @property {string} proxy Axios proxy settings. * For more details please read
      here * @memberof module:API.cvat.config - * @property {integer} taskID this value is displayed in a logs if available - * @memberof module:API.cvat.config - * @property {integer} jobID this value is displayed in a logs if available - * @memberof module:API.cvat.config - * @property {integer} clientID read only auto-generated - * value which is displayed in a logs * @memberof module:API.cvat.config */ get backendAPI() { @@ -452,21 +495,6 @@ function build() { set proxy(value) { config.proxy = value; }, - get taskID() { - return config.taskID; - }, - set taskID(value) { - config.taskID = value; - }, - get jobID() { - return config.jobID; - }, - set jobID(value) { - config.jobID = value; - }, - get clientID() { - return config.clientID; - }, }, /** * Namespace contains some library information e.g. api version @@ -524,6 +552,7 @@ function build() { Task, User, Job, + Log, Attribute, Label, Statistics, diff --git a/cvat-core/src/config.js b/cvat-core/src/config.js index e940a214c95..3b9eade8f34 100644 --- a/cvat-core/src/config.js +++ b/cvat-core/src/config.js @@ -6,7 +6,4 @@ module.exports = { backendAPI: 'http://localhost:7000/api/v1', proxy: false, - taskID: undefined, - jobID: undefined, - clientID: +Date.now().toString().substr(-6), }; diff --git a/cvat-core/src/enums.js b/cvat-core/src/enums.js index 2c34290876b..8b6c86fcaab 100644 --- a/cvat-core/src/enums.js +++ b/cvat-core/src/enums.js @@ -103,68 +103,74 @@ }); /** - * Event types - * @enum {number} + * Logger event types + * @enum {string} * @name LogType * @memberof module:API.cvat.enums - * @property {number} pasteObject 0 - * @property {number} changeAttribute 1 - * @property {number} dragObject 2 - * @property {number} deleteObject 3 - * @property {number} pressShortcut 4 - * @property {number} resizeObject 5 - * @property {number} sendLogs 6 - * @property {number} saveJob 7 - * @property {number} jumpFrame 8 - * @property {number} drawObject 9 - * @property {number} changeLabel 10 - * @property {number} sendTaskInfo 11 - * @property {number} loadJob 12 - * @property {number} moveImage 13 - * @property {number} zoomImage 14 - * @property {number} lockObject 15 - * @property {number} mergeObjects 16 - * @property {number} copyObject 17 - * @property {number} propagateObject 18 - * @property {number} undoAction 19 - * @property {number} redoAction 20 - * @property {number} sendUserActivity 21 - * @property {number} sendException 22 - * @property {number} changeFrame 23 - * @property {number} debugInfo 24 - * @property {number} fitImage 25 - * @property {number} rotateImage 26 + * @property {string} loadJob Load job + * @property {string} saveJob Save job + * @property {string} uploadAnnotations Upload annotations + * @property {string} sendUserActivity Send user activity + * @property {string} sendException Send exception + * @property {string} sendTaskInfo Send task info + + * @property {string} drawObject Draw object + * @property {string} pasteObject Paste object + * @property {string} copyObject Copy object + * @property {string} propagateObject Propagate object + * @property {string} dragObject Drag object + * @property {string} resizeObject Resize object + * @property {string} deleteObject Delete object + * @property {string} lockObject Lock object + * @property {string} mergeObjects Merge objects + * @property {string} changeAttribute Change attribute + * @property {string} changeLabel Change label + + * @property {string} changeFrame Change frame + * @property {string} moveImage Move image + * @property {string} zoomImage Zoom image + * @property {string} fitImage Fit image + * @property {string} rotateImage Rotate image + + * @property {string} undoAction Undo action + * @property {string} redoAction Redo action + + * @property {string} pressShortcut Press shortcut + * @property {string} debugInfo Debug info * @readonly */ - const LogType = { - pasteObject: 0, - changeAttribute: 1, - dragObject: 2, - deleteObject: 3, - pressShortcut: 4, - resizeObject: 5, - sendLogs: 6, - saveJob: 7, - jumpFrame: 8, - drawObject: 9, - changeLabel: 10, - sendTaskInfo: 11, - loadJob: 12, - moveImage: 13, - zoomImage: 14, - lockObject: 15, - mergeObjects: 16, - copyObject: 17, - propagateObject: 18, - undoAction: 19, - redoAction: 20, - sendUserActivity: 21, - sendException: 22, - changeFrame: 23, - debugInfo: 24, - fitImage: 25, - rotateImage: 26, - }; + const LogType = Object.freeze({ + loadJob: 'Load job', + saveJob: 'Save job', + uploadAnnotations: 'Upload annotations', + sendUserActivity: 'Send user activity', + sendException: 'Send exception', + sendTaskInfo: 'Send task info', + + drawObject: 'Draw object', + pasteObject: 'Paste object', + copyObject: 'Copy object', + propagateObject: 'Propagate object', + dragObject: 'Drag object', + resizeObject: 'Resize object', + deleteObject: 'Delete object', + lockObject: 'Lock object', + mergeObjects: 'Merge objects', + changeAttribute: 'Change attribute', + changeLabel: 'Change label', + + changeFrame: 'Change frame', + moveImage: 'Move image', + zoomImage: 'Zoom image', + fitImage: 'Fit image', + rotateImage: 'Rotate image', + + undoAction: 'Undo action', + redoAction: 'Redo action', + + pressShortcut: 'Press shortcut', + debugInfo: 'Debug info', + }); /** * Types of actions with annotations @@ -208,7 +214,6 @@ /** * Array of hex colors - * @type {module:API.cvat.classes.Loader[]} values * @name colors * @memberof module:API.cvat.enums * @type {string[]} diff --git a/cvat-core/src/log.js b/cvat-core/src/log.js new file mode 100644 index 00000000000..56d9592d4d9 --- /dev/null +++ b/cvat-core/src/log.js @@ -0,0 +1,209 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +/* global + require:false +*/ + +const PluginRegistry = require('./plugins'); +const { ArgumentError } = require('./exceptions'); +const { LogType } = require('./enums'); + +/** + * Class representing a single log + * @memberof module:API.cvat.classes + * @hideconstructor +*/ +class Log { + constructor(logType, payload) { + this.onCloseCallback = null; + + this.type = logType; + this.payload = { ...payload }; + this.time = new Date(); + } + + onClose(callback) { + this.onCloseCallback = callback; + } + + validatePayload() { + if (typeof (this.payload) !== 'object') { + throw new ArgumentError('Payload must be an object'); + } + + try { + JSON.stringify(this.payload); + } catch (error) { + const message = `Log payload must be JSON serializable. ${error.toString()}`; + throw new ArgumentError(message); + } + } + + dump() { + const payload = { ...this.payload }; + const body = { + name: this.type, + time: this.time.toISOString(), + }; + + for (const field of ['client_id', 'job_id', 'task_id', 'is_active']) { + if (field in payload) { + body[field] = payload[field]; + delete payload[field]; + } + } + + return { + ...body, + payload, + }; + } + + /** + * Method saves a durable log in a storage
      + * Note then you can call close() multiple times
      + * Log duration will be computed based on the latest call
      + * All payloads will be shallowly combined (all top level properties will exist) + * @method close + * @memberof module:API.cvat.classes.Log + * @param {object} [payload] part of payload can be added when close a log + * @readonly + * @instance + * @async + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + async close(payload = {}) { + const result = await PluginRegistry + .apiWrapper.call(this, Log.prototype.close, payload); + return result; + } +} + +Log.prototype.close.implementation = function (payload) { + this.payload.duration = Date.now() - this.time.getTime(); + this.payload = { ...this.payload, ...payload }; + + if (this.onCloseCallback) { + this.onCloseCallback(); + } +}; + +class LogWithCount extends Log { + validatePayload() { + Log.prototype.validatePayload.call(this); + if (!Number.isInteger(this.payload.count) || this.payload.count < 1) { + const message = `The field "count" is required for "${this.type}" log` + + 'It must be a positive integer'; + throw new ArgumentError(message); + } + } +} + +class LogWithObjectsInfo extends Log { + validatePayload() { + const generateError = (name, range) => { + const message = `The field "${name}" is required for "${this.type}" log. ${range}`; + throw new ArgumentError(message); + }; + + if (!Number.isInteger(this.payload['track count']) || this.payload['track count'] < 0) { + generateError('track count', 'It must be an integer not less than 0'); + } + + if (!Number.isInteger(this.payload['tag count']) || this.payload['tag count'] < 0) { + generateError('tag count', 'It must be an integer not less than 0'); + } + + if (!Number.isInteger(this.payload['object count']) || this.payload['object count'] < 0) { + generateError('object count', 'It must be an integer not less than 0'); + } + + if (!Number.isInteger(this.payload['frame count']) || this.payload['frame count'] < 1) { + generateError('frame count', 'It must be an integer not less than 1'); + } + + if (!Number.isInteger(this.payload['box count']) || this.payload['box count'] < 0) { + generateError('box count', 'It must be an integer not less than 0'); + } + + if (!Number.isInteger(this.payload['polygon count']) || this.payload['polygon count'] < 0) { + generateError('polygon count', 'It must be an integer not less than 0'); + } + + if (!Number.isInteger(this.payload['polyline count']) || this.payload['polyline count'] < 0) { + generateError('polyline count', 'It must be an integer not less than 0'); + } + + if (!Number.isInteger(this.payload['points count']) || this.payload['points count'] < 0) { + generateError('points count', 'It must be an integer not less than 0'); + } + } +} + +class LogWithWorkingTime extends Log { + validatePayload() { + Log.prototype.validatePayload.call(this); + + if (!('working_time' in this.payload) + || !typeof (this.payload.working_time) === 'number' + || this.payload.working_time < 0 + ) { + const message = `The field "working_time" is required for ${this.type} log. ` + + 'It must be a number not less than 0'; + throw new ArgumentError(message); + } + } +} + +class LogWithExceptionInfo extends Log { + validatePayload() { + Log.prototype.validatePayload.call(this); + + if (typeof (this.payload.message) !== 'string') { + const message = `The field "message" is required for ${this.type} log. ` + + 'It must be a string'; + throw new ArgumentError(message); + } + + if (typeof (this.payload.filename) !== 'string') { + const message = `The field "filename" is required for ${this.type} log. ` + + 'It must be a string'; + throw new ArgumentError(message); + } + + if (typeof (this.payload.line) !== 'number') { + const message = `The field "line" is required for ${this.type} log. ` + + 'It must be a number'; + throw new ArgumentError(message); + } + } +} + +function logFactory(logType, payload) { + const logsWithCount = [ + LogType.deleteObject, LogType.mergeObjects, LogType.copyObject, + LogType.undoAction, LogType.redoAction, + ]; + + if (logsWithCount.includes(logType)) { + return new LogWithCount(logType, payload); + } + if ([LogType.sendTaskInfo, LogType.loadJob, LogType.uploadAnnotations].includes(logType)) { + return new LogWithObjectsInfo(logType, payload); + } + + if (logType === LogType.sendUserActivity) { + return new LogWithWorkingTime(logType, payload); + } + + if (logType === LogType.sendException) { + return new LogWithExceptionInfo(logType, payload); + } + + return new Log(logType, payload); +} + +module.exports = logFactory; diff --git a/cvat-core/src/logger-storage.js b/cvat-core/src/logger-storage.js new file mode 100644 index 00000000000..e8e24d7d0ce --- /dev/null +++ b/cvat-core/src/logger-storage.js @@ -0,0 +1,169 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +/* global + require:false +*/ + +const PluginRegistry = require('./plugins'); +const server = require('./server-proxy'); +const logFactory = require('./log'); +const { ArgumentError } = require('./exceptions'); +const { LogType } = require('./enums'); + +const WORKING_TIME_THRESHOLD = 100000; // ms, 1.66 min + +class LoggerStorage { + constructor() { + this.clientID = Date.now().toString().substr(-6); + this.lastLogTime = Date.now(); + this.workingTime = 0; + this.collection = []; + this.ignoreRules = {}; // by event + this.isActiveChecker = null; + + this.ignoreRules[LogType.zoomImage] = { + lastLog: null, + timeThreshold: 1000, + ignore(previousLog) { + return Date.now() - previousLog.time < this.timeThreshold; + }, + }; + + this.ignoreRules[LogType.changeAttribute] = { + lastLog: null, + ignore(previousLog, currentPayload) { + return currentPayload.object_id === previousLog.payload.object_id + && currentPayload.id === previousLog.payload.id; + }, + }; + } + + updateWorkingTime() { + if (!this.isActiveChecker || this.isActiveChecker()) { + const lastLogTime = Date.now(); + const diff = lastLogTime - this.lastLogTime; + this.workingTime += diff < WORKING_TIME_THRESHOLD ? diff : 0; + this.lastLogTime = lastLogTime; + } + } + + async configure(isActiveChecker, activityHelper) { + const result = await PluginRegistry + .apiWrapper.call( + this, LoggerStorage.prototype.configure, + isActiveChecker, activityHelper, + ); + return result; + } + + async log(logType, payload = {}, wait = false) { + const result = await PluginRegistry + .apiWrapper.call(this, LoggerStorage.prototype.log, logType, payload, wait); + return result; + } + + async save() { + const result = await PluginRegistry + .apiWrapper.call(this, LoggerStorage.prototype.save); + return result; + } +} + +LoggerStorage.prototype.configure.implementation = function ( + isActiveChecker, + userActivityCallback, +) { + if (typeof (isActiveChecker) !== 'function') { + throw new ArgumentError('isActiveChecker argument must be callable'); + } + + if (!Array.isArray(userActivityCallback)) { + throw new ArgumentError('userActivityCallback argument must be an array'); + } + + this.isActiveChecker = () => !!isActiveChecker(); + userActivityCallback.push(this.updateWorkingTime.bind(this)); +}; + +LoggerStorage.prototype.log.implementation = function (logType, payload, wait) { + if (typeof (payload) !== 'object') { + throw new ArgumentError('Payload must be an object'); + } + + if (typeof (wait) !== 'boolean') { + throw new ArgumentError('Payload must be an object'); + } + + if (logType in this.ignoreRules) { + const ignoreRule = this.ignoreRules[logType]; + const { lastLog } = ignoreRule; + if (lastLog && ignoreRule.ignore(lastLog, payload)) { + lastLog.payload = { + ...lastLog.payload, + ...payload, + }; + + this.updateWorkingTime(); + return ignoreRule.lastLog; + } + } + + const logPayload = { ...payload }; + logPayload.client_id = this.clientID; + if (this.isActiveChecker) { + logPayload.is_active = this.isActiveChecker(); + } + + const log = logFactory(logType, { ...logPayload }); + if (logType in this.ignoreRules) { + this.ignoreRules[logType].lastLog = log; + } + + const pushEvent = () => { + this.updateWorkingTime(); + log.validatePayload(); + log.onClose(null); + this.collection.push(log); + }; + + if (wait) { + log.onClose(pushEvent); + } else { + pushEvent(); + } + + return log; +}; + +LoggerStorage.prototype.save.implementation = async function () { + const collectionToSend = [...this.collection]; + const lastLog = this.collection[this.collection.length - 1]; + + const logPayload = {}; + logPayload.client_id = this.clientID; + logPayload.working_time = this.workingTime; + if (this.isActiveChecker) { + logPayload.is_active = this.isActiveChecker(); + } + + if (lastLog && lastLog.type === LogType.sendTaskInfo) { + logPayload.job_id = lastLog.payload.job_id; + logPayload.task_id = lastLog.payload.task_id; + } + + const userActivityLog = logFactory(LogType.sendUserActivity, logPayload); + collectionToSend.push(userActivityLog); + + await server.logs.save(collectionToSend.map((log) => log.dump())); + + for (const rule of Object.values(this.ignoreRules)) { + rule.lastLog = null; + } + this.collection = []; + this.workingTime = 0; + this.lastLogTime = Date.now(); +}; + +module.exports = new LoggerStorage(); diff --git a/cvat-core/src/logging.js b/cvat-core/src/logging.js deleted file mode 100644 index f6b52c4ec32..00000000000 --- a/cvat-core/src/logging.js +++ /dev/null @@ -1,42 +0,0 @@ -/* -* Copyright (C) 2019 Intel Corporation -* SPDX-License-Identifier: MIT -*/ - -/* global - require:false -*/ - -(() => { - const PluginRegistry = require('./plugins'); - - /** - * Class describe scheme of a log object - * @memberof module:API.cvat.classes - * @hideconstructor - */ - class Log { - constructor(logType, continuous, details) { - this.type = logType; - this.continuous = continuous; - this.details = details; - } - - /** - * Method closes a continue log - * @method close - * @memberof module:API.cvat.classes.Log - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.PluginError} - */ - async close() { - const result = await PluginRegistry - .apiWrapper.call(this, Log.prototype.close); - return result; - } - } - - module.exports = Log; -})(); diff --git a/cvat-core/src/server-proxy.js b/cvat-core/src/server-proxy.js index 25f101822ca..7a833fb1f92 100644 --- a/cvat-core/src/server-proxy.js +++ b/cvat-core/src/server-proxy.js @@ -584,6 +584,21 @@ }); } + async function saveLogs(logs) { + const { backendAPI } = config; + + try { + await Axios.post(`${backendAPI}/server/logs`, JSON.stringify(logs), { + proxy: config.proxy, + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (errorData) { + throw generateError(errorData); + } + } + Object.defineProperties(this, Object.freeze({ server: { value: Object.freeze({ @@ -646,6 +661,13 @@ }), writable: false, }, + + logs: { + value: Object.freeze({ + save: saveLogs, + }), + writable: false, + }, })); } } diff --git a/cvat-core/src/session.js b/cvat-core/src/session.js index 32643dd200b..edd5efc8be8 100644 --- a/cvat-core/src/session.js +++ b/cvat-core/src/session.js @@ -9,6 +9,7 @@ (() => { const PluginRegistry = require('./plugins'); + const loggerStorage = require('./logger-storage'); const serverProxy = require('./server-proxy'); const { getFrame, getPreview } = require('./frames'); const { ArgumentError } = require('./exceptions'); @@ -125,16 +126,11 @@ }, writable: true, }), - logs: Object.freeze({ + logger: Object.freeze({ value: { - async put(logType, details) { + async log(logType, payload = {}, wait = false) { const result = await PluginRegistry - .apiWrapper.call(this, prototype.logs.put, logType, details); - return result; - }, - async save(onUpdate) { - const result = await PluginRegistry - .apiWrapper.call(this, prototype.logs.save, onUpdate); + .apiWrapper.call(this, prototype.logger.log, logType, payload, wait); return result; }, }, @@ -436,33 +432,28 @@ /** * Namespace is used for an interaction with logs - * @namespace logs + * @namespace logger * @memberof Session */ /** - * Append log to a log collection. - * Continue logs will have been added after "close" method is called - * @method put - * @memberof Session.logs - * @param {module:API.cvat.enums.LogType} type a type of a log - * @param {boolean} continuous log is a continuous log - * @param {Object} details any others data which will be append to log data + * Create a log and add it to a log collection
      + * Durable logs will be added after "close" method is called for them
      + * The fields "task_id" and "job_id" automatically added when add logs + * throught a task or a job
      + * Ignore rules exist for some logs (e.g. zoomImage, changeAttribute)
      + * Payload of ignored logs are shallowly combined to previous logs of the same type + * @method log + * @memberof Session.logger + * @param {module:API.cvat.enums.LogType | string} type - log type + * @param {Object} [payload = {}] - any other data that will be appended to the log + * @param {boolean} [wait = false] - specifies if log is durable * @returns {module:API.cvat.classes.Log} * @instance * @async * @throws {module:API.cvat.exceptions.PluginError} * @throws {module:API.cvat.exceptions.ArgumentError} */ - /** - * Save accumulated logs on a server - * @method save - * @memberof Session.logs - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - * @instance - * @async - */ /** * Namespace is used for an interaction with actions @@ -702,6 +693,10 @@ get: Object.getPrototypeOf(this).frames.get.bind(this), preview: Object.getPrototypeOf(this).frames.preview.bind(this), }; + + this.logger = { + log: Object.getPrototypeOf(this).logger.log.bind(this), + }; } /** @@ -1212,6 +1207,10 @@ get: Object.getPrototypeOf(this).frames.get.bind(this), preview: Object.getPrototypeOf(this).frames.preview.bind(this), }; + + this.logger = { + log: Object.getPrototypeOf(this).logger.log.bind(this), + }; } /** @@ -1452,6 +1451,11 @@ return result; }; + Job.prototype.logger.log.implementation = async function (logType, payload, wait) { + const result = await this.task.logger.log(logType, { ...payload, job_id: this.id }, wait); + return result; + }; + Task.prototype.save.implementation = async function saveTaskImplementation(onUpdate) { // TODO: Add ability to change an owner and an assignee if (typeof (this.id) !== 'undefined') { @@ -1663,4 +1667,9 @@ const result = getActions(this); return result; }; + + Task.prototype.logger.log.implementation = async function (logType, payload, wait) { + const result = await loggerStorage.log(logType, { ...payload, task_id: this.id }, wait); + return result; + }; })(); diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index a13444e5df0..b80c0a0f1d5 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -22,6 +22,7 @@ import { } from 'reducers/interfaces'; import getCore from 'cvat-core'; +import logger, { LogType } from 'cvat-logger'; import { RectDrawingMethod } from 'cvat-canvas'; import { getCVATStore } from 'cvat-store'; @@ -88,6 +89,23 @@ function computeZRange(states: any[]): number[] { return [minZ, maxZ]; } +async function jobInfoGenerator(job: any): Promise> { + const { total } = await job.annotations.statistics(); + return { + 'frame count': job.stopFrame - job.startFrame + 1, + 'track count': total.rectangle.shape + total.rectangle.track + + total.polygon.shape + total.polygon.track + + total.polyline.shape + total.polyline.track + + total.points.shape + total.points.track, + 'object count': total.total, + 'box count': total.rectangle.shape + total.rectangle.track, + 'polygon count': total.polygon.shape + total.polygon.track, + 'polyline count': total.polyline.shape + total.polyline.track, + 'points count': total.points.shape + total.points.track, + 'tag count': total.tags, + }; +} + export enum AnnotationActionTypes { GET_JOB = 'GET_JOB', GET_JOB_SUCCESS = 'GET_JOB_SUCCESS', @@ -165,6 +183,28 @@ export enum AnnotationActionTypes { ADD_Z_LAYER = 'ADD_Z_LAYER', SEARCH_ANNOTATIONS_FAILED = 'SEARCH_ANNOTATIONS_FAILED', CHANGE_WORKSPACE = 'CHANGE_WORKSPACE', + SAVE_LOGS_SUCCESS = 'SAVE_LOGS_SUCCESS', + SAVE_LOGS_FAILED = 'SAVE_LOGS_FAILED', +} + +export function saveLogsAsync(): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator) => { + try { + await logger.save(); + dispatch({ + type: AnnotationActionTypes.SAVE_LOGS_SUCCESS, + payload: {}, + }); + } catch (error) { + dispatch({ + type: AnnotationActionTypes.SAVE_LOGS_FAILED, + payload: { + error, + }, + }); + } + }; } export function changeWorkspace(workspace: Workspace): AnyAction { @@ -192,8 +232,7 @@ export function switchZLayer(cur: number): AnyAction { }; } -export function fetchAnnotationsAsync(): -ThunkAction, {}, {}, AnyAction> { +export function fetchAnnotationsAsync(): ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { try { const { @@ -250,14 +289,21 @@ export function undoActionAsync(sessionInstance: any, frame: number): ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { try { + const state = getStore().getState(); const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); // TODO: use affected IDs as an optimization + const [undoName] = state.annotation.annotations.history.undo.slice(-1); + const undoLog = await sessionInstance.logger.log(LogType.undoAction, { + name: undoName, + count: 1, + }, true); await sessionInstance.actions.undo(); const history = await sessionInstance.actions.get(); const states = await sessionInstance.annotations .get(frame, showAllInterpolationTracks, filters); const [minZ, maxZ] = computeZRange(states); + await undoLog.close(); dispatch({ type: AnnotationActionTypes.UNDO_ACTION_SUCCESS, @@ -283,14 +329,21 @@ export function redoActionAsync(sessionInstance: any, frame: number): ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { try { + const state = getStore().getState(); const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); // TODO: use affected IDs as an optimization + const [redoName] = state.annotation.annotations.history.redo.slice(-1); + const redoLog = await sessionInstance.logger.log(LogType.redoAction, { + name: redoName, + count: 1, + }, true); await sessionInstance.actions.redo(); const history = await sessionInstance.actions.get(); const states = await sessionInstance.annotations .get(frame, showAllInterpolationTracks, filters); const [minZ, maxZ] = computeZRange(states); + await redoLog.close(); dispatch({ type: AnnotationActionTypes.REDO_ACTION_SUCCESS, @@ -373,6 +426,12 @@ ThunkAction, {}, {}, AnyAction> { const frame = state.annotation.player.frame.number; await job.annotations.upload(file, loader); + await job.logger.log( + LogType.uploadAnnotations, { + ...(await jobInfoGenerator(job)), + }, + ); + // One more update to escape some problems // in canvas when shape with the same // clientID has different type (polygon, rectangle) for example @@ -499,6 +558,9 @@ export function propagateObjectAsync( frame: from, }; + await sessionInstance.logger.log( + LogType.propagateObject, { count: to - from + 1 }, + ); const states = []; for (let frame = from; frame <= to; frame++) { copy.frame = frame; @@ -549,6 +611,7 @@ export function removeObjectAsync(sessionInstance: any, objectState: any, force: ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { try { + await sessionInstance.logger.log(LogType.deleteObject, { count: 1 }); const removed = await objectState.delete(force); const history = await sessionInstance.actions.get(); @@ -584,6 +647,9 @@ export function editShape(enabled: boolean): AnyAction { } export function copyShape(objectState: any): AnyAction { + const job = getStore().getState().annotation.job.instance; + job.logger.log(LogType.copyObject, { count: 1 }); + return { type: AnnotationActionTypes.COPY_SHAPE, payload: { @@ -687,6 +753,12 @@ ThunkAction, {}, {}, AnyAction> { payload: {}, }); + await job.logger.log( + LogType.changeFrame, { + from: frame, + to: toFrame, + }, + ); const data = await job.frames.get(toFrame); const states = await job.annotations.get(toFrame, showAllInterpolationTracks, filters); const [minZ, maxZ] = computeZRange(states); @@ -707,6 +779,7 @@ ThunkAction, {}, {}, AnyAction> { } const delay = Math.max(0, Math.round(1000 / frameSpeed) - currentTime + (state.annotation.player.frame.changeTime as number)); + dispatch({ type: AnnotationActionTypes.CHANGE_FRAME_SUCCESS, payload: { @@ -734,14 +807,33 @@ ThunkAction, {}, {}, AnyAction> { export function rotateCurrentFrame(rotation: Rotation): AnyAction { const state: CombinedState = getStore().getState(); - const { number: frameNumber } = state.annotation.player.frame; - const { startFrame } = state.annotation.job.instance; - const { frameAngles } = state.annotation.player; - const { rotateAll } = state.settings.player; + const { + annotation: { + player: { + frame: { + number: frameNumber, + }, + frameAngles, + }, + job: { + instance: job, + instance: { + startFrame, + }, + }, + }, + settings: { + player: { + rotateAll, + }, + }, + } = state; const frameAngle = (frameAngles[frameNumber - startFrame] + (rotation === Rotation.CLOCKWISE90 ? 90 : 270)) % 360; + job.logger.log(LogType.rotateImage, { angle: frameAngle }); + return { type: AnnotationActionTypes.ROTATE_FRAME, payload: { @@ -791,11 +883,6 @@ export function getJobAsync( initialFilters: string[], ): ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { - dispatch({ - type: AnnotationActionTypes.GET_JOB, - payload: {}, - }); - try { const state: CombinedState = getStore().getState(); const filters = initialFilters; @@ -808,6 +895,18 @@ export function getJobAsync( }); } + dispatch({ + type: AnnotationActionTypes.GET_JOB, + payload: {}, + }); + + const loadJobEvent = await logger.log( + LogType.loadJob, { + task_id: tid, + job_id: jid, + }, true, + ); + // Check state if the task is already there let task = state.tasks.current .filter((_task: Task) => _task.instance.id === tid) @@ -832,6 +931,8 @@ export function getJobAsync( const [minZ, maxZ] = computeZRange(states); const colors = [...cvat.enums.colors]; + loadJobEvent.close(await jobInfoGenerator(job)); + dispatch({ type: AnnotationActionTypes.GET_JOB_SUCCESS, payload: { @@ -865,6 +966,10 @@ ThunkAction, {}, {}, AnyAction> { }); try { + const saveJobEvent = await sessionInstance.logger.log( + LogType.saveJob, {}, true, + ); + await sessionInstance.annotations.save((status: string) => { dispatch({ type: AnnotationActionTypes.SAVE_UPDATE_ANNOTATIONS_STATUS, @@ -874,6 +979,13 @@ ThunkAction, {}, {}, AnyAction> { }); }); + await saveJobEvent.close(); + await sessionInstance.logger.log( + LogType.sendTaskInfo, + await jobInfoGenerator(sessionInstance), + ); + dispatch(saveLogsAsync()); + dispatch({ type: AnnotationActionTypes.SAVE_ANNOTATIONS_SUCCESS, payload: {}, @@ -889,6 +1001,7 @@ ThunkAction, {}, {}, AnyAction> { }; } +// used to reproduce the latest drawing (in case of tags just creating) by using N export function rememberObject( objectType: ObjectType, labelID: number, diff --git a/cvat-ui/src/components/annotation-page/annotation-page.tsx b/cvat-ui/src/components/annotation-page/annotation-page.tsx index a096afd2fde..db5b6500e9b 100644 --- a/cvat-ui/src/components/annotation-page/annotation-page.tsx +++ b/cvat-ui/src/components/annotation-page/annotation-page.tsx @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MIT import './styles.scss'; -import React from 'react'; +import React, { useEffect } from 'react'; import { Layout, @@ -21,18 +21,24 @@ interface Props { job: any | null | undefined; fetching: boolean; getJob(): void; + saveLogs(): void; workspace: Workspace; } - export default function AnnotationPageComponent(props: Props): JSX.Element { const { job, fetching, getJob, + saveLogs, workspace, } = props; + useEffect(() => { + saveLogs(); + return saveLogs; + }, []); + if (job === null) { if (!fetching) { getJob(); diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx index d19b94ef9de..49d3b4992cf 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx @@ -11,6 +11,7 @@ import { CheckboxChangeEvent } from 'antd/lib/checkbox'; import { Row, Col } from 'antd/lib/grid'; import Text from 'antd/lib/typography/Text'; +import { LogType } from 'cvat-logger'; import { activateObject as activateObjectAction, updateAnnotationsAsync, @@ -28,6 +29,7 @@ interface StateToProps { activatedAttributeID: number | null; states: any[]; labels: any[]; + jobInstance: any; } interface DispatchToProps { @@ -48,12 +50,14 @@ function mapStateToProps(state: CombinedState): StateToProps { states, }, job: { + instance: jobInstance, labels, }, }, } = state; return { + jobInstance, labels, activatedStateID, activatedAttributeID, @@ -78,6 +82,7 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX. states, activatedStateID, activatedAttributeID, + jobInstance, updateAnnotations, activateObject, } = props; @@ -267,6 +272,13 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX. currentValue={activeObjectState.attributes[activeAttribute.id]} onChange={(value: string) => { const { attributes } = activeObjectState; + jobInstance.logger.log( + LogType.changeAttribute, { + id: activeAttribute.id, + object_id: activeObjectState.clientID, + value, + }, + ); attributes[activeAttribute.id] = value; activeObjectState.attributes = attributes; updateAnnotations([activeObjectState]); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx index 91a2e20e698..16bc2e3ec14 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -9,6 +9,7 @@ import Layout from 'antd/lib/layout'; import Icon from 'antd/lib/icon'; import Tooltip from 'antd/lib/tooltip'; +import { LogType } from 'cvat-logger'; import { Canvas } from 'cvat-canvas'; import getCore from 'cvat-core'; import { @@ -214,6 +215,10 @@ export default class CanvasWrapperComponent extends React.PureComponent { canvasInstance.html().removeEventListener('canvas.deactivated', this.onCanvasShapeDeactivated); canvasInstance.html().removeEventListener('canvas.moved', this.onCanvasCursorMoved); + canvasInstance.html().removeEventListener('canvas.zoom', this.onCanvasZoomChanged); + canvasInstance.html().removeEventListener('canvas.fit', this.onCanvasImageFitted); + canvasInstance.html().removeEventListener('canvas.dragshape', this.onCanvasShapeDragged); + canvasInstance.html().removeEventListener('canvas.resizeshape', this.onCanvasShapeResized); canvasInstance.html().removeEventListener('canvas.clicked', this.onCanvasShapeClicked); canvasInstance.html().removeEventListener('canvas.drawn', this.onCanvasShapeDrawn); canvasInstance.html().removeEventListener('canvas.merged', this.onCanvasObjectsMerged); @@ -237,20 +242,18 @@ export default class CanvasWrapperComponent extends React.PureComponent { onShapeDrawn(); } - const { state } = event.detail; - if (!state.objectType) { - state.objectType = activeObjectType; - } - - if (!state.label) { - [state.label] = jobInstance.task.labels - .filter((label: any) => label.id === activeLabelID); - } - - if (typeof (state.occluded) === 'undefined') { - state.occluded = false; + const { state, duration } = event.detail; + const isDrawnFromScratch = !state.label; + if (isDrawnFromScratch) { + jobInstance.logger.log(LogType.drawObject, { count: 1, duration }); + } else { + jobInstance.logger.log(LogType.pasteObject, { count: 1, duration }); } + state.objectType = state.objectType || activeObjectType; + state.label = state.label || jobInstance.task.labels + .filter((label: any) => label.id === activeLabelID)[0]; + state.occluded = state.occluded || false; state.frame = frame; const objectState = new cvat.classes.ObjectState(state); onCreateAnnotations(jobInstance, frame, [objectState]); @@ -266,7 +269,11 @@ export default class CanvasWrapperComponent extends React.PureComponent { onMergeObjects(false); - const { states } = event.detail; + const { states, duration } = event.detail; + jobInstance.logger.log(LogType.mergeObjects, { + duration, + count: states.length, + }); onMergeAnnotations(jobInstance, frame, states); }; @@ -324,6 +331,28 @@ export default class CanvasWrapperComponent extends React.PureComponent { onUpdateContextMenu(activatedStateID !== null, e.clientX, e.clientY); }; + private onCanvasShapeDragged = (e: any): void => { + const { jobInstance } = this.props; + const { id } = e.detail; + jobInstance.logger.log(LogType.dragObject, { id }); + }; + + private onCanvasShapeResized = (e: any): void => { + const { jobInstance } = this.props; + const { id } = e.detail; + jobInstance.logger.log(LogType.resizeObject, { id }); + }; + + private onCanvasImageFitted = (): void => { + const { jobInstance } = this.props; + jobInstance.logger.log(LogType.fitImage); + }; + + private onCanvasZoomChanged = (): void => { + const { jobInstance } = this.props; + jobInstance.logger.log(LogType.zoomImage); + }; + private onCanvasShapeClicked = (e: any): void => { const { clientID } = e.detail.state; const sidebarItem = window.document @@ -581,6 +610,10 @@ export default class CanvasWrapperComponent extends React.PureComponent { canvasInstance.html().addEventListener('canvas.deactivated', this.onCanvasShapeDeactivated); canvasInstance.html().addEventListener('canvas.moved', this.onCanvasCursorMoved); + canvasInstance.html().addEventListener('canvas.zoom', this.onCanvasZoomChanged); + canvasInstance.html().addEventListener('canvas.fit', this.onCanvasImageFitted); + canvasInstance.html().addEventListener('canvas.dragshape', this.onCanvasShapeDragged); + canvasInstance.html().addEventListener('canvas.resizeshape', this.onCanvasShapeResized); canvasInstance.html().addEventListener('canvas.clicked', this.onCanvasShapeClicked); canvasInstance.html().addEventListener('canvas.drawn', this.onCanvasShapeDrawn); canvasInstance.html().addEventListener('canvas.merged', this.onCanvasObjectsMerged); diff --git a/cvat-ui/src/components/cvat-app.tsx b/cvat-ui/src/components/cvat-app.tsx index 91795797980..bab5887a139 100644 --- a/cvat-ui/src/components/cvat-app.tsx +++ b/cvat-ui/src/components/cvat-app.tsx @@ -27,6 +27,7 @@ import LoginPageContainer from 'containers/login-page/login-page'; import RegisterPageContainer from 'containers/register-page/register-page'; import HeaderContainer from 'containers/header/header'; +import getCore from 'cvat-core'; import { NotificationsState } from 'reducers/interfaces'; interface CVATAppProps { @@ -56,8 +57,17 @@ interface CVATAppProps { class CVATApplication extends React.PureComponent { public componentDidMount(): void { + const core = getCore(); const { verifyAuthorized } = this.props; configure({ ignoreRepeatedEventsWhenKeyHeldDown: false }); + + // Logger configuration + const userActivityCallback: (() => void)[] = []; + window.addEventListener('click', () => { + userActivityCallback.forEach((handler) => handler()); + }); + core.logger.configure(() => window.document.hasFocus, userActivityCallback); + verifyAuthorized(); } @@ -198,7 +208,7 @@ class CVATApplication extends React.PureComponent - { withModels - && } - { installedAutoAnnotation - && } + {withModels + && } + {installedAutoAnnotation + && }
      {/* eslint-disable-next-line */} - +
      ); diff --git a/cvat-ui/src/containers/annotation-page/annotation-page.tsx b/cvat-ui/src/containers/annotation-page/annotation-page.tsx index 264d1d9450f..8d2972ba66b 100644 --- a/cvat-ui/src/containers/annotation-page/annotation-page.tsx +++ b/cvat-ui/src/containers/annotation-page/annotation-page.tsx @@ -7,7 +7,7 @@ import { withRouter } from 'react-router-dom'; import { RouteComponentProps } from 'react-router'; import AnnotationPageComponent from 'components/annotation-page/annotation-page'; -import { getJobAsync } from 'actions/annotation-actions'; +import { getJobAsync, saveLogsAsync } from 'actions/annotation-actions'; import { CombinedState, Workspace } from 'reducers/interfaces'; @@ -24,6 +24,7 @@ interface StateToProps { interface DispatchToProps { getJob(): void; + saveLogs(): void; } function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { @@ -77,6 +78,9 @@ function mapDispatchToProps(dispatch: any, own: OwnProps): DispatchToProps { getJob(): void { dispatch(getJobAsync(taskID, jobID, initialFrame, initialFilters)); }, + saveLogs(): void { + dispatch(saveLogsAsync()); + }, }; } diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx index fb11fe1de67..4efc88037c0 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx @@ -5,6 +5,8 @@ import React from 'react'; import copy from 'copy-to-clipboard'; import { connect } from 'react-redux'; + +import { LogType } from 'cvat-logger'; import { ActiveControl, CombinedState, @@ -292,13 +294,15 @@ class ObjectItemContainer extends React.PureComponent { }; private lock = (): void => { - const { objectState } = this.props; + const { objectState, jobInstance } = this.props; + jobInstance.logger.log(LogType.lockObject, { locked: true }); objectState.lock = true; this.commit(); }; private unlock = (): void => { - const { objectState } = this.props; + const { objectState, jobInstance } = this.props; + jobInstance.logger.log(LogType.lockObject, { locked: false }); objectState.lock = false; this.commit(); }; @@ -405,7 +409,12 @@ class ObjectItemContainer extends React.PureComponent { }; private changeAttribute = (id: number, value: string): void => { - const { objectState } = this.props; + const { objectState, jobInstance } = this.props; + jobInstance.logger.log(LogType.changeAttribute, { + id, + value, + object_id: objectState.clientID, + }); const attr: Record = {}; attr[id] = value; objectState.attributes = attr; diff --git a/cvat-ui/src/cvat-logger.ts b/cvat-ui/src/cvat-logger.ts new file mode 100644 index 00000000000..f1277ff281f --- /dev/null +++ b/cvat-ui/src/cvat-logger.ts @@ -0,0 +1,14 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import getCore from 'cvat-core'; + +const core = getCore(); +const { logger } = core; +const { LogType } = core.enums; + +export default logger; +export { + LogType, +}; diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index e0595b90b9f..08f54a63ff0 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -230,6 +230,7 @@ export interface NotificationsState { undo: null | ErrorState; redo: null | ErrorState; search: null | ErrorState; + savingLogs: null | ErrorState; }; [index: string]: any; diff --git a/cvat-ui/src/reducers/notifications-reducer.ts b/cvat-ui/src/reducers/notifications-reducer.ts index 9ca0240d680..668e70f2e5f 100644 --- a/cvat-ui/src/reducers/notifications-reducer.ts +++ b/cvat-ui/src/reducers/notifications-reducer.ts @@ -74,6 +74,7 @@ const defaultState: NotificationsState = { undo: null, redo: null, search: null, + savingLogs: null, }, }, messages: { @@ -766,6 +767,21 @@ export default function (state = defaultState, action: AnyAction): Notifications }, }; } + case AnnotationActionTypes.SAVE_LOGS_FAILED: { + return { + ...state, + errors: { + ...state.errors, + annotation: { + ...state.errors.annotation, + savingLogs: { + message: 'Could not send logs to the server', + reason: action.payload.error.toString(), + }, + }, + }, + }; + } case NotificationsActionType.RESET_ERRORS: { return { ...state, diff --git a/cvat-ui/webpack.config.js b/cvat-ui/webpack.config.js index e298954d6f3..a172632a428 100644 --- a/cvat-ui/webpack.config.js +++ b/cvat-ui/webpack.config.js @@ -86,8 +86,8 @@ module.exports = { }, plugins: [ new HtmlWebpackPlugin({ - template: "./src/index.html", - inject: false, + template: "./src/index.html", + inject: false, }), new Dotenv({ systemvars: true, From 14084435bcc856bce4bbc0c0a2f2506233d58ea0 Mon Sep 17 00:00:00 2001 From: Ben Hoff Date: Fri, 20 Mar 2020 04:20:43 -0400 Subject: [PATCH 18/49] OpenVino 2020 (#1269) * added support for OpenVINO 2020 * fixed dextr and tf_annotation Co-authored-by: Andrey Zhavoronkov --- CHANGELOG.md | 1 + cvat/apps/auto_annotation/inference_engine.py | 17 ++++++++++-- cvat/apps/auto_annotation/model_loader.py | 26 ++++++++++--------- cvat/apps/dextr_segmentation/dextr.py | 9 ++++--- cvat/apps/tf_annotation/views.py | 9 ++++--- 5 files changed, 42 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e50226d9110..483feb90381 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - React & Redux & Antd based dashboard - Yolov3 interpretation script fix and changes to mapping.json - YOLO format support ([#1151](https://github.com/opencv/cvat/pull/1151)) +- Added support for OpenVINO 2020 ### Fixed - Exception in Git plugin [#826](https://github.com/opencv/cvat/issues/826) diff --git a/cvat/apps/auto_annotation/inference_engine.py b/cvat/apps/auto_annotation/inference_engine.py index 310e78c45be..fb6b543d34e 100644 --- a/cvat/apps/auto_annotation/inference_engine.py +++ b/cvat/apps/auto_annotation/inference_engine.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: MIT -from openvino.inference_engine import IENetwork, IEPlugin +from openvino.inference_engine import IENetwork, IEPlugin, IECore, get_version import subprocess import os @@ -19,7 +19,20 @@ def _check_instruction(instruction): ) -def make_plugin(): +def make_plugin_or_core(): + version = get_version() + use_core_openvino = False + try: + major, minor, reference = [int(x) for x in version.split('.')] + if major >= 2 and minor >= 1 and reference >= 37988: + use_core_openvino = True + except Exception: + pass + + if use_core_openvino: + ie = IECore() + return ie + if _IE_PLUGINS_PATH is None: raise OSError('Inference engine plugin path env not found in the system.') diff --git a/cvat/apps/auto_annotation/model_loader.py b/cvat/apps/auto_annotation/model_loader.py index cb923a9cada..e48d5c8e4d1 100644 --- a/cvat/apps/auto_annotation/model_loader.py +++ b/cvat/apps/auto_annotation/model_loader.py @@ -8,25 +8,22 @@ import os import numpy as np -from cvat.apps.auto_annotation.inference_engine import make_plugin, make_network +from cvat.apps.auto_annotation.inference_engine import make_plugin_or_core, make_network class ModelLoader(): def __init__(self, model, weights): self._model = model self._weights = weights - IE_PLUGINS_PATH = os.getenv("IE_PLUGINS_PATH") - if not IE_PLUGINS_PATH: - raise OSError("Inference engine plugin path env not found in the system.") - - plugin = make_plugin() + core_or_plugin = make_plugin_or_core() network = make_network(self._model, self._weights) - supported_layers = plugin.get_supported_layers(network) - not_supported_layers = [l for l in network.layers.keys() if l not in supported_layers] - if len(not_supported_layers) != 0: - raise Exception("Following layers are not supported by the plugin for specified device {}:\n {}". - format(plugin.device, ", ".join(not_supported_layers))) + if getattr(core_or_plugin, 'get_supported_layers', False): + supported_layers = core_or_plugin.get_supported_layers(network) + not_supported_layers = [l for l in network.layers.keys() if l not in supported_layers] + if len(not_supported_layers) != 0: + raise Exception("Following layers are not supported by the plugin for specified device {}:\n {}". + format(core_or_plugin.device, ", ".join(not_supported_layers))) iter_inputs = iter(network.inputs) self._input_blob_name = next(iter_inputs) @@ -45,7 +42,12 @@ def __init__(self, model, weights): if self._input_blob_name in info_names: self._input_blob_name = next(iter_inputs) - self._net = plugin.load(network=network, num_requests=2) + if getattr(core_or_plugin, 'load_network', False): + self._net = core_or_plugin.load_network(network, + "CPU", + num_requests=2) + else: + self._net = core_or_plugin.load(network=network, num_requests=2) input_type = network.inputs[self._input_blob_name] self._input_layout = input_type if isinstance(input_type, list) else input_type.shape diff --git a/cvat/apps/dextr_segmentation/dextr.py b/cvat/apps/dextr_segmentation/dextr.py index 703c6d08398..628961ff576 100644 --- a/cvat/apps/dextr_segmentation/dextr.py +++ b/cvat/apps/dextr_segmentation/dextr.py @@ -3,7 +3,7 @@ # # SPDX-License-Identifier: MIT -from cvat.apps.auto_annotation.inference_engine import make_plugin, make_network +from cvat.apps.auto_annotation.inference_engine import make_plugin_or_core, make_network import os import cv2 @@ -32,12 +32,15 @@ def __init__(self): def handle(self, im_path, points): # Lazy initialization if not self._plugin: - self._plugin = make_plugin() + self._plugin = make_plugin_or_core() self._network = make_network(os.path.join(_DEXTR_MODEL_DIR, 'dextr.xml'), os.path.join(_DEXTR_MODEL_DIR, 'dextr.bin')) self._input_blob = next(iter(self._network.inputs)) self._output_blob = next(iter(self._network.outputs)) - self._exec_network = self._plugin.load(network=self._network) + if getattr(self._plugin, 'load_network', False): + self._exec_network = self._plugin.load_network(self._network, 'CPU') + else: + self._exec_network = self._plugin.load(network=self._network) image = PIL.Image.open(im_path) numpy_image = np.array(image) diff --git a/cvat/apps/tf_annotation/views.py b/cvat/apps/tf_annotation/views.py index 2a70c9db323..4aa0589cae6 100644 --- a/cvat/apps/tf_annotation/views.py +++ b/cvat/apps/tf_annotation/views.py @@ -30,7 +30,7 @@ def load_image_into_numpy(image): def run_inference_engine_annotation(image_list, labels_mapping, treshold): - from cvat.apps.auto_annotation.inference_engine import make_plugin, make_network + from cvat.apps.auto_annotation.inference_engine import make_plugin_or_core, make_network def _normalize_box(box, w, h, dw, dh): xmin = min(int(box[0] * dw * w), w) @@ -44,11 +44,14 @@ def _normalize_box(box, w, h, dw, dh): if MODEL_PATH is None: raise OSError('Model path env not found in the system.') - plugin = make_plugin() + core_or_plugin = make_plugin_or_core() network = make_network('{}.xml'.format(MODEL_PATH), '{}.bin'.format(MODEL_PATH)) input_blob_name = next(iter(network.inputs)) output_blob_name = next(iter(network.outputs)) - executable_network = plugin.load(network=network) + if getattr(core_or_plugin, 'load_network', False): + executable_network = core_or_plugin.load_network(network, 'CPU') + else: + executable_network = core_or_plugin.load(network=network) job = rq.get_current_job() del network From 1c1ab6a0e59b3ee8f0377a55403392a91fe0ce05 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Fri, 20 Mar 2020 12:31:33 +0300 Subject: [PATCH 19/49] fixed point context menu for rectangles --- .../standard-workspace/canvas-wrapper.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx index 0aedbdb09d5..41d8acc4c09 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -19,6 +19,7 @@ import { ObjectType, ContextMenuType, Workspace, + ShapeType, } from 'reducers/interfaces'; import { Canvas } from 'cvat-canvas'; import getCore from 'cvat-core'; @@ -468,10 +469,14 @@ export default class CanvasWrapperComponent extends React.PureComponent { const { activatedStateID, onUpdateContextMenu, + annotations, } = this.props; - onUpdateContextMenu(activatedStateID !== null, e.detail.mouseEvent.clientX, - e.detail.mouseEvent.clientY, ContextMenuType.CANVAS_SHAPE_POINT, e.detail.pointID); + const [state] = annotations.filter((el: any) => (el.clientID === activatedStateID)); + if (state.shapeType !== ShapeType.RECTANGLE) { + onUpdateContextMenu(activatedStateID !== null, e.detail.mouseEvent.clientX, + e.detail.mouseEvent.clientY, ContextMenuType.CANVAS_SHAPE_POINT, e.detail.pointID); + } }; private activateOnCanvas(): void { From 8efaf58f113e82a71641f23e2db401b1476a0622 Mon Sep 17 00:00:00 2001 From: zhiltsov-max Date: Fri, 20 Mar 2020 15:27:57 +0300 Subject: [PATCH 20/49] Add recursive importers (#1290) --- datumaro/datumaro/plugins/coco_format/importer.py | 9 +++------ datumaro/datumaro/plugins/cvat_format/importer.py | 8 +------- .../datumaro/plugins/tf_detection_api_format/importer.py | 3 ++- datumaro/datumaro/plugins/yolo_format/importer.py | 2 +- 4 files changed, 7 insertions(+), 15 deletions(-) diff --git a/datumaro/datumaro/plugins/coco_format/importer.py b/datumaro/datumaro/plugins/coco_format/importer.py index 42e5be9fc89..932944fa6a4 100644 --- a/datumaro/datumaro/plugins/coco_format/importer.py +++ b/datumaro/datumaro/plugins/coco_format/importer.py @@ -11,7 +11,7 @@ from datumaro.components.extractor import Importer from datumaro.util.log_utils import logging_disabled -from .format import CocoTask, CocoPath +from .format import CocoTask class CocoImporter(Importer): @@ -55,11 +55,8 @@ def find_subsets(path): if path.endswith('.json') and osp.isfile(path): subset_paths = [path] else: - subset_paths = glob(osp.join(path, '*_*.json')) - - if osp.basename(osp.normpath(path)) != CocoPath.ANNOTATIONS_DIR: - path = osp.join(path, CocoPath.ANNOTATIONS_DIR) - subset_paths += glob(osp.join(path, '*_*.json')) + subset_paths = glob(osp.join(path, '**', '*_*.json'), + recursive=True) subsets = defaultdict(dict) for subset_path in subset_paths: diff --git a/datumaro/datumaro/plugins/cvat_format/importer.py b/datumaro/datumaro/plugins/cvat_format/importer.py index 79be0c61052..31f8dbd44f5 100644 --- a/datumaro/datumaro/plugins/cvat_format/importer.py +++ b/datumaro/datumaro/plugins/cvat_format/importer.py @@ -9,8 +9,6 @@ from datumaro.components.extractor import Importer -from .format import CvatPath - class CvatImporter(Importer): EXTRACTOR_NAME = 'cvat' @@ -49,9 +47,5 @@ def find_subsets(path): if path.endswith('.xml') and osp.isfile(path): subset_paths = [path] else: - subset_paths = glob(osp.join(path, '*.xml')) - - if osp.basename(osp.normpath(path)) != CvatPath.ANNOTATIONS_DIR: - path = osp.join(path, CvatPath.ANNOTATIONS_DIR) - subset_paths += glob(osp.join(path, '*.xml')) + subset_paths = glob(osp.join(path, '**', '*.xml'), recursive=True) return subset_paths \ No newline at end of file diff --git a/datumaro/datumaro/plugins/tf_detection_api_format/importer.py b/datumaro/datumaro/plugins/tf_detection_api_format/importer.py index 9783a23cb15..169618ba2db 100644 --- a/datumaro/datumaro/plugins/tf_detection_api_format/importer.py +++ b/datumaro/datumaro/plugins/tf_detection_api_format/importer.py @@ -47,5 +47,6 @@ def find_subsets(path): if path.endswith('.tfrecord') and osp.isfile(path): subset_paths = [path] else: - subset_paths = glob(osp.join(path, '*.tfrecord')) + subset_paths = glob(osp.join(path, '**', '*.tfrecord'), + recursive=True) return subset_paths \ No newline at end of file diff --git a/datumaro/datumaro/plugins/yolo_format/importer.py b/datumaro/datumaro/plugins/yolo_format/importer.py index 4e14d0315ae..344475c6771 100644 --- a/datumaro/datumaro/plugins/yolo_format/importer.py +++ b/datumaro/datumaro/plugins/yolo_format/importer.py @@ -42,5 +42,5 @@ def find_configs(path): if path.endswith('.data') and osp.isfile(path): config_paths = [path] else: - config_paths = glob(osp.join(path, '*.data')) + config_paths = glob(osp.join(path, '**', '*.data'), recursive=True) return config_paths \ No newline at end of file From 0744c6ae1622f5e15284cbe5524d7131b339f017 Mon Sep 17 00:00:00 2001 From: zhiltsov-max Date: Fri, 20 Mar 2020 16:17:02 +0300 Subject: [PATCH 21/49] [Datumaro] MOT format (#1289) * Add mot format base * Add mot format * Extract common code --- .../datumaro/plugins/coco_format/converter.py | 30 +- .../datumaro/plugins/cvat_format/converter.py | 11 +- .../plugins/datumaro_format/converter.py | 35 +- datumaro/datumaro/plugins/mot_format.py | 341 ++++++++++++++++++ datumaro/datumaro/util/__init__.py | 27 +- datumaro/tests/test_mot_format.py | 146 ++++++++ 6 files changed, 540 insertions(+), 50 deletions(-) create mode 100644 datumaro/datumaro/plugins/mot_format.py create mode 100644 datumaro/tests/test_mot_format.py diff --git a/datumaro/datumaro/plugins/coco_format/converter.py b/datumaro/datumaro/plugins/coco_format/converter.py index 403a6a83eb6..b5766d046c2 100644 --- a/datumaro/datumaro/plugins/coco_format/converter.py +++ b/datumaro/datumaro/plugins/coco_format/converter.py @@ -17,7 +17,7 @@ AnnotationType, Points ) from datumaro.components.cli_plugin import CliPlugin -from datumaro.util import find +from datumaro.util import find, cast from datumaro.util.image import save_image import datumaro.util.mask_tools as mask_tools import datumaro.util.annotation_tools as anno_tools @@ -25,15 +25,6 @@ from .format import CocoTask, CocoPath -def _cast(value, type_conv, default=None): - if value is None: - return default - try: - return type_conv(value) - except Exception: - return default - - SegmentationMode = Enum('SegmentationMode', ['guess', 'polygons', 'mask']) class _TaskConverter: @@ -82,7 +73,7 @@ def save_image_info(self, item, filename): 'id': self._get_image_id(item), 'width': int(w), 'height': int(h), - 'file_name': _cast(filename, str, ''), + 'file_name': cast(filename, str, ''), 'license': 0, 'flickr_url': '', 'coco_url': '', @@ -162,8 +153,8 @@ def save_categories(self, dataset): for idx, cat in enumerate(label_categories.items): self.categories.append({ 'id': 1 + idx, - 'name': _cast(cat.name, str, ''), - 'supercategory': _cast(cat.parent, str, ''), + 'name': cast(cat.name, str, ''), + 'supercategory': cast(cat.parent, str, ''), }) @classmethod @@ -309,7 +300,7 @@ def convert_instance(self, instance, item): elem = { 'id': self._get_ann_id(ann), 'image_id': self._get_image_id(item), - 'category_id': _cast(ann.label, int, -1) + 1, + 'category_id': cast(ann.label, int, -1) + 1, 'segmentation': segmentation, 'area': float(area), 'bbox': list(map(float, bbox)), @@ -334,10 +325,11 @@ def save_categories(self, dataset): for idx, label_cat in enumerate(label_categories.items): cat = { 'id': 1 + idx, - 'name': _cast(label_cat.name, str, ''), - 'supercategory': _cast(label_cat.parent, str, ''), + 'name': cast(label_cat.name, str, ''), + 'supercategory': cast(label_cat.parent, str, ''), 'keypoints': [], 'skeleton': [], + } if point_categories is not None: @@ -416,8 +408,8 @@ def save_categories(self, dataset): for idx, cat in enumerate(label_categories.items): self.categories.append({ 'id': 1 + idx, - 'name': _cast(cat.name, str, ''), - 'supercategory': _cast(cat.parent, str, ''), + 'name': cast(cat.name, str, ''), + 'supercategory': cast(cat.parent, str, ''), }) def save_annotations(self, item): @@ -504,7 +496,7 @@ def _make_task_converters(self): def _get_image_id(self, item): image_id = self._image_ids.get(item.id) if image_id is None: - image_id = _cast(item.id, int, len(self._image_ids) + 1) + image_id = cast(item.id, int, len(self._image_ids) + 1) self._image_ids[item.id] = image_id return image_id diff --git a/datumaro/datumaro/plugins/cvat_format/converter.py b/datumaro/datumaro/plugins/cvat_format/converter.py index a64addad02b..1d364184477 100644 --- a/datumaro/datumaro/plugins/cvat_format/converter.py +++ b/datumaro/datumaro/plugins/cvat_format/converter.py @@ -12,19 +12,12 @@ from datumaro.components.cli_plugin import CliPlugin from datumaro.components.converter import Converter from datumaro.components.extractor import DEFAULT_SUBSET_NAME, AnnotationType +from datumaro.util import cast from datumaro.util.image import save_image from .format import CvatPath -def _cast(value, type_conv, default=None): - if value is None: - return default - try: - return type_conv(value) - except Exception: - return default - def pairwise(iterable): a = iter(iterable) return zip(a, a) @@ -188,7 +181,7 @@ def _save_image(self, item): def _write_item(self, item, index): image_info = OrderedDict([ - ("id", str(_cast(item.id, int, index))), + ("id", str(cast(item.id, int, index))), ]) if item.has_image: size = item.image.size diff --git a/datumaro/datumaro/plugins/datumaro_format/converter.py b/datumaro/datumaro/plugins/datumaro_format/converter.py index cc860cbad3d..08dc0062dd2 100644 --- a/datumaro/datumaro/plugins/datumaro_format/converter.py +++ b/datumaro/datumaro/plugins/datumaro_format/converter.py @@ -16,6 +16,7 @@ Label, Mask, RleMask, Points, Polygon, PolyLine, Bbox, Caption, LabelCategories, MaskCategories, PointsCategories ) +from datumaro.util import cast from datumaro.util.image import save_image import pycocotools.mask as mask_utils from datumaro.components.cli_plugin import CliPlugin @@ -23,14 +24,6 @@ from .format import DatumaroPath -def _cast(value, type_conv, default=None): - if value is None: - return default - try: - return type_conv(value) - except Exception: - return default - class _SubsetWriter: def __init__(self, name, context): self._name = name @@ -108,10 +101,10 @@ def _convert_annotation(self, obj): assert isinstance(obj, Annotation) ann_json = { - 'id': _cast(obj.id, int), - 'type': _cast(obj.type.name, str), + 'id': cast(obj.id, int), + 'type': cast(obj.type.name, str), 'attributes': obj.attributes, - 'group': _cast(obj.group, int, 0), + 'group': cast(obj.group, int, 0), } return ann_json @@ -119,7 +112,7 @@ def _convert_label_object(self, obj): converted = self._convert_annotation(obj) converted.update({ - 'label_id': _cast(obj.label, int), + 'label_id': cast(obj.label, int), }) return converted @@ -133,7 +126,7 @@ def _convert_mask_object(self, obj): np.require(obj.image, dtype=np.uint8, requirements='F')) converted.update({ - 'label_id': _cast(obj.label, int), + 'label_id': cast(obj.label, int), 'rle': { # serialize as compressed COCO mask 'counts': rle['counts'].decode('ascii'), @@ -146,7 +139,7 @@ def _convert_polyline_object(self, obj): converted = self._convert_annotation(obj) converted.update({ - 'label_id': _cast(obj.label, int), + 'label_id': cast(obj.label, int), 'points': [float(p) for p in obj.points], }) return converted @@ -155,7 +148,7 @@ def _convert_polygon_object(self, obj): converted = self._convert_annotation(obj) converted.update({ - 'label_id': _cast(obj.label, int), + 'label_id': cast(obj.label, int), 'points': [float(p) for p in obj.points], }) return converted @@ -164,7 +157,7 @@ def _convert_bbox_object(self, obj): converted = self._convert_annotation(obj) converted.update({ - 'label_id': _cast(obj.label, int), + 'label_id': cast(obj.label, int), 'bbox': [float(p) for p in obj.get_bbox()], }) return converted @@ -173,7 +166,7 @@ def _convert_points_object(self, obj): converted = self._convert_annotation(obj) converted.update({ - 'label_id': _cast(obj.label, int), + 'label_id': cast(obj.label, int), 'points': [float(p) for p in obj.points], 'visibility': [int(v.value) for v in obj.visibility], }) @@ -183,7 +176,7 @@ def _convert_caption_object(self, obj): converted = self._convert_annotation(obj) converted.update({ - 'caption': _cast(obj.caption, str), + 'caption': cast(obj.caption, str), }) return converted @@ -193,8 +186,8 @@ def _convert_label_categories(self, obj): } for label in obj.items: converted['labels'].append({ - 'name': _cast(label.name, str), - 'parent': _cast(label.parent, str), + 'name': cast(label.name, str), + 'parent': cast(label.parent, str), }) return converted @@ -218,7 +211,7 @@ def _convert_points_categories(self, obj): for label_id, item in obj.items.items(): converted['items'].append({ 'label_id': int(label_id), - 'labels': [_cast(label, str) for label in item.labels], + 'labels': [cast(label, str) for label in item.labels], 'adjacent': [int(v) for v in item.adjacent], }) return converted diff --git a/datumaro/datumaro/plugins/mot_format.py b/datumaro/datumaro/plugins/mot_format.py new file mode 100644 index 00000000000..18d3695b145 --- /dev/null +++ b/datumaro/datumaro/plugins/mot_format.py @@ -0,0 +1,341 @@ + +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +# The Multiple Object Tracking Benchmark challenge format support +# Format description: https://arxiv.org/pdf/1906.04567.pdf +# Another description: https://motchallenge.net/instructions + +from collections import OrderedDict +import csv +from enum import Enum +import logging as log +import os +import os.path as osp + +from datumaro.components.extractor import (SourceExtractor, + DatasetItem, AnnotationType, Bbox, LabelCategories +) +from datumaro.components.extractor import Importer +from datumaro.components.converter import Converter +from datumaro.components.cli_plugin import CliPlugin +from datumaro.util import cast +from datumaro.util.image import Image, save_image + + +MotLabel = Enum('MotLabel', [ + ('pedestrian', 1), + ('person on vehicle', 2), + ('car', 3), + ('bicycle', 4), + ('motorbike', 5), + ('non motorized vehicle', 6), + ('static person', 7), + ('distractor', 8), + ('occluder', 9), + ('occluder on the ground', 10), + ('occluder full', 11), + ('reflection', 12), +]) + +class MotPath: + IMAGE_DIR = 'img1' + SEQINFO_FILE = 'seqinfo.ini' + LABELS_FILE = 'labels.txt' + GT_FILENAME = 'gt.txt' + DET_FILENAME = 'det.txt' + + IMAGE_EXT = '.jpg' + + FIELDS = [ + 'frame_id', + 'track_id', + 'x', + 'y', + 'w', + 'h', + 'confidence', # or 'not ignored' flag for GT anns + 'class_id', + 'visibility' + ] + + +class MotSeqExtractor(SourceExtractor): + def __init__(self, path, labels=None, occlusion_threshold=0, is_gt=None): + super().__init__() + + assert osp.isfile(path) + self._path = path + seq_root = osp.dirname(osp.dirname(path)) + + self._image_dir = '' + if osp.isdir(osp.join(seq_root, MotPath.IMAGE_DIR)): + self._image_dir = osp.join(seq_root, MotPath.IMAGE_DIR) + + seq_info = osp.join(seq_root, MotPath.SEQINFO_FILE) + if osp.isfile(seq_info): + seq_info = self._parse_seq_info(seq_info) + self._image_dir = osp.join(seq_root, seq_info['imdir']) + else: + seq_info = None + self._seq_info = seq_info + + self._occlusion_threshold = float(occlusion_threshold) + + assert is_gt in {None, True, False} + if is_gt is None: + if osp.basename(path) == MotPath.DET_FILENAME: + is_gt = False + else: + is_gt = True + self._is_gt = is_gt + + self._subset = None + + if labels is None: + if osp.isfile(osp.join(seq_root, MotPath.LABELS_FILE)): + labels = osp.join(seq_root, MotPath.LABELS_FILE) + else: + labels = [lbl.name for lbl in MotLabel] + if isinstance(labels, str): + labels = self._parse_labels(labels) + elif isinstance(labels, list): + assert all(isinstance(lbl, str) for lbl in labels), labels + else: + raise TypeError("Unexpected type of 'labels' argument: %s" % labels) + self._categories = self._load_categories(labels) + self._items = self._load_items(path) + + def categories(self): + return self._categories + + def __iter__(self): + for item in self._items.values(): + yield item + + def __len__(self): + return len(self._items) + + def subsets(self): + if self._subset: + return [self._subset] + return None + + def get_subset(self, name): + if name != self._subset: + return None + return self + + @staticmethod + def _parse_labels(path): + with open(path, encoding='utf-8') as labels_file: + return [s.strip() for s in labels_file] + + def _load_categories(self, labels): + attributes = ['track_id'] + if self._is_gt: + attributes += ['occluded', 'visibility', 'ignored'] + else: + attributes += ['score'] + label_cat = LabelCategories(attributes=attributes) + for label in labels: + label_cat.add(label) + + return { AnnotationType.label: label_cat } + + def _load_items(self, path): + labels_count = len(self._categories[AnnotationType.label].items) + items = OrderedDict() + + if self._seq_info: + for frame_id in range(self._seq_info['seqlength']): + items[frame_id] = DatasetItem( + id=frame_id, + subset=self._subset, + image=Image( + path=osp.join(self._image_dir, + '%06d%s' % (frame_id, self._seq_info['imext'])), + size=(self._seq_info['imheight'], self._seq_info['imwidth']) + ) + ) + elif osp.isdir(self._image_dir): + for p in os.listdir(self._image_dir): + if p.endswith(MotPath.IMAGE_EXT): + frame_id = int(osp.splitext(p)[0]) + items[frame_id] = DatasetItem( + id=frame_id, + subset=self._subset, + image=osp.join(self._image_dir, p), + ) + + with open(path, newline='', encoding='utf-8') as csv_file: + # NOTE: Different MOT files have different count of fields + # (7, 9 or 10). This is handled by reader: + # - all extra fields go to a separate field + # - all unmet fields have None values + for row in csv.DictReader(csv_file, fieldnames=MotPath.FIELDS): + frame_id = int(row['frame_id']) + item = items.get(frame_id) + if item is None: + item = DatasetItem(id=frame_id, subset=self._subset) + annotations = item.annotations + + x, y = float(row['x']), float(row['y']) + w, h = float(row['w']), float(row['h']) + label_id = row.get('class_id') + if label_id and label_id != '-1': + label_id = int(label_id) - 1 + assert label_id < labels_count, label_id + else: + label_id = None + + attributes = {} + + # Annotations for detection task are not related to any track + track_id = int(row['track_id']) + if 0 < track_id: + attributes['track_id'] = track_id + + confidence = cast(row.get('confidence'), float, 1) + visibility = cast(row.get('visibility'), float, 1) + if self._is_gt: + attributes['visibility'] = visibility + attributes['occluded'] = \ + visibility <= self._occlusion_threshold + attributes['ignored'] = confidence == 0 + else: + attributes['score'] = float(confidence) + + annotations.append(Bbox(x, y, w, h, label=label_id, + attributes=attributes)) + + items[frame_id] = item + return items + + @classmethod + def _parse_seq_info(cls, path): + fields = {} + with open(path, encoding='utf-8') as f: + for line in f: + entry = line.lower().strip().split('=', maxsplit=1) + if len(entry) == 2: + fields[entry[0]] = entry[1] + cls._check_seq_info(fields) + for k in { 'framerate', 'seqlength', 'imwidth', 'imheight' }: + fields[k] = int(fields[k]) + return fields + + @staticmethod + def _check_seq_info(seq_info): + assert set(seq_info) == {'name', 'imdir', 'framerate', 'seqlength', 'imwidth', 'imheight', 'imext'}, seq_info + +class MotSeqImporter(Importer): + _EXTRACTOR_NAME = 'mot_seq' + + @classmethod + def detect(cls, path): + return len(cls.find_subsets(path)) != 0 + + def __call__(self, path, **extra_params): + from datumaro.components.project import Project # cyclic import + project = Project() + + subsets = self.find_subsets(path) + if len(subsets) == 0: + raise Exception("Failed to find 'mot' dataset at '%s'" % path) + + for ann_file in subsets: + log.info("Found a dataset at '%s'" % ann_file) + + source_name = osp.splitext(osp.basename(ann_file))[0] + project.add_source(source_name, { + 'url': ann_file, + 'format': self._EXTRACTOR_NAME, + 'options': extra_params, + }) + + return project + + @staticmethod + def find_subsets(path): + subsets = [] + if path.endswith('.txt') and osp.isfile(path): + subsets = [path] + elif osp.isdir(path): + p = osp.join(path, 'gt', MotPath.GT_FILENAME) + if osp.isfile(p): + subsets.append(p) + return subsets + +class MotSeqGtConverter(Converter, CliPlugin): + @classmethod + def build_cmdline_parser(cls, **kwargs): + parser = super().__init__(**kwargs) + parser.add_argument('--save-images', action='store_true', + help="Save images (default: %(default)s)") + return parser + + def __init__(self, save_images=False): + super().__init__() + + self._save_images = save_images + + def __call__(self, extractor, save_dir): + images_dir = osp.join(save_dir, MotPath.IMAGE_DIR) + os.makedirs(images_dir, exist_ok=True) + self._images_dir = images_dir + + anno_dir = osp.join(save_dir, 'gt') + os.makedirs(anno_dir, exist_ok=True) + anno_file = osp.join(anno_dir, MotPath.GT_FILENAME) + with open(anno_file, 'w', encoding="utf-8") as csv_file: + writer = csv.DictWriter(csv_file, fieldnames=MotPath.FIELDS) + for idx, item in enumerate(extractor): + log.debug("Converting item '%s'", item.id) + + frame_id = cast(item.id, int, 1 + idx) + + for anno in item.annotations: + if anno.type != AnnotationType.bbox: + continue + + writer.writerow({ + 'frame_id': frame_id, + 'track_id': int(anno.attributes.get('track_id', -1)), + 'x': anno.x, + 'y': anno.y, + 'w': anno.w, + 'h': anno.h, + 'confidence': int(anno.attributes.get('ignored') != True), + 'class_id': 1 + cast(anno.label, int, -2), + 'visibility': float( + anno.attributes.get('visibility', + 1 - float( + anno.attributes.get('occluded', False) + ) + ) + ) + }) + + if self._save_images: + if item.has_image and item.image.has_data: + self._save_image(item, index=frame_id) + else: + log.debug("Item '%s' has no image" % item.id) + + labels_file = osp.join(save_dir, MotPath.LABELS_FILE) + with open(labels_file, 'w', encoding='utf-8') as f: + f.write('\n'.join(l.name + for l in extractor.categories()[AnnotationType.label].items) + ) + + def _save_image(self, item, index): + if item.image.filename: + frame_id = osp.splitext(item.image.filename)[0] + else: + frame_id = item.id + frame_id = cast(frame_id, int, index) + image_filename = '%06d%s' % (frame_id, MotPath.IMAGE_EXT) + save_image(osp.join(self._images_dir, image_filename), + item.image.data) \ No newline at end of file diff --git a/datumaro/datumaro/util/__init__.py b/datumaro/datumaro/util/__init__.py index 87c800fe515..7c36fe8efad 100644 --- a/datumaro/datumaro/util/__init__.py +++ b/datumaro/datumaro/util/__init__.py @@ -4,6 +4,7 @@ # SPDX-License-Identifier: MIT import os +import os.path as osp def find(iterable, pred=lambda x: True, default=None): @@ -17,4 +18,28 @@ def dir_items(path, ext, truncate_ext=False): if truncate_ext: f = f[:ext_pos] items.append(f) - return items \ No newline at end of file + return items + +def split_path(path): + path = osp.normpath(path) + parts = [] + + while True: + path, part = osp.split(path) + if part: + parts.append(part) + else: + if path: + parts.append(path) + break + parts.reverse() + + return parts + +def cast(value, type_conv, default=None): + if value is None: + return default + try: + return type_conv(value) + except Exception: + return default \ No newline at end of file diff --git a/datumaro/tests/test_mot_format.py b/datumaro/tests/test_mot_format.py new file mode 100644 index 00000000000..efe62502572 --- /dev/null +++ b/datumaro/tests/test_mot_format.py @@ -0,0 +1,146 @@ +import numpy as np + +from unittest import TestCase + +from datumaro.components.extractor import (Extractor, DatasetItem, + AnnotationType, Bbox, LabelCategories +) +from datumaro.plugins.mot_format import MotSeqGtConverter, MotSeqImporter +from datumaro.util.test_utils import TestDir, compare_datasets + + +class MotConverterTest(TestCase): + def _test_save_and_load(self, source_dataset, converter, test_dir, + target_dataset=None, importer_args=None): + converter(source_dataset, test_dir) + + if importer_args is None: + importer_args = {} + parsed_dataset = MotSeqImporter()(test_dir, **importer_args) \ + .make_dataset() + + if target_dataset is None: + target_dataset = source_dataset + + compare_datasets(self, expected=target_dataset, actual=parsed_dataset) + + def test_can_save_bboxes(self): + class SrcExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, subset='train', + image=np.ones((16, 16, 3)), + annotations=[ + Bbox(0, 4, 4, 8, label=2, attributes={ + 'occluded': True, + }), + Bbox(0, 4, 4, 4, label=3, attributes={ + 'visibility': 0.4, + }), + Bbox(2, 4, 4, 4, attributes={ + 'ignored': True + }), + ] + ), + + DatasetItem(id=2, subset='val', + image=np.ones((8, 8, 3)), + annotations=[ + Bbox(1, 2, 4, 2, label=3), + ] + ), + + DatasetItem(id=3, subset='test', + image=np.ones((5, 4, 3)) * 3, + ), + ]) + + def categories(self): + label_cat = LabelCategories() + for label in range(10): + label_cat.add('label_' + str(label)) + return { + AnnotationType.label: label_cat, + } + + class DstExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, + image=np.ones((16, 16, 3)), + annotations=[ + Bbox(0, 4, 4, 8, label=2, attributes={ + 'occluded': True, + 'visibility': 0.0, + 'ignored': False, + }), + Bbox(0, 4, 4, 4, label=3, attributes={ + 'occluded': False, + 'visibility': 0.4, + 'ignored': False, + }), + Bbox(2, 4, 4, 4, attributes={ + 'occluded': False, + 'visibility': 1.0, + 'ignored': True, + }), + ] + ), + + DatasetItem(id=2, + image=np.ones((8, 8, 3)), + annotations=[ + Bbox(1, 2, 4, 2, label=3, attributes={ + 'occluded': False, + 'visibility': 1.0, + 'ignored': False, + }), + ] + ), + + DatasetItem(id=3, + image=np.ones((5, 4, 3)) * 3, + ), + ]) + + def categories(self): + label_cat = LabelCategories() + for label in range(10): + label_cat.add('label_' + str(label)) + return { + AnnotationType.label: label_cat, + } + + with TestDir() as test_dir: + self._test_save_and_load( + SrcExtractor(), MotSeqGtConverter(save_images=True), + test_dir, target_dataset=DstExtractor()) + +class MotImporterTest(TestCase): + def test_can_detect(self): + class TestExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, subset='train', + image=np.ones((16, 16, 3)), + annotations=[ + Bbox(0, 4, 4, 8, label=2), + ] + ), + ]) + + def categories(self): + label_cat = LabelCategories() + for label in range(10): + label_cat.add('label_' + str(label)) + return { + AnnotationType.label: label_cat, + } + + def generate_dummy_dataset(path): + MotSeqGtConverter()(TestExtractor(), save_dir=path) + + with TestDir() as test_dir: + generate_dummy_dataset(test_dir) + + self.assertTrue(MotSeqImporter.detect(test_dir)) From c91e8957df02117330c339b70daa19eb3bf13593 Mon Sep 17 00:00:00 2001 From: zhiltsov-max Date: Fri, 20 Mar 2020 16:17:52 +0300 Subject: [PATCH 22/49] [Datumaro] LabelMe format (#1293) * Little refactoring * Add LabelMe format --- datumaro/datumaro/components/extractor.py | 1 + datumaro/datumaro/components/project.py | 2 +- datumaro/datumaro/plugins/labelme_format.py | 455 ++++++++++++++++++++ datumaro/datumaro/plugins/transforms.py | 3 +- datumaro/datumaro/util/mask_tools.py | 1 - datumaro/tests/test_labelme_format.py | 120 ++++++ 6 files changed, 578 insertions(+), 4 deletions(-) create mode 100644 datumaro/datumaro/plugins/labelme_format.py create mode 100644 datumaro/tests/test_labelme_format.py diff --git a/datumaro/datumaro/components/extractor.py b/datumaro/datumaro/components/extractor.py index 247807539b9..dc7867d032d 100644 --- a/datumaro/datumaro/components/extractor.py +++ b/datumaro/datumaro/components/extractor.py @@ -104,6 +104,7 @@ def add(self, name, parent=None, attributes=None): index = len(self.items) self.items.append(self.Category(name, parent, attributes)) self._indices[name] = index + return index def find(self, name): index = self._indices.get(name) diff --git a/datumaro/datumaro/components/project.py b/datumaro/datumaro/components/project.py index ea184083a46..bcc210e9646 100644 --- a/datumaro/datumaro/components/project.py +++ b/datumaro/datumaro/components/project.py @@ -234,7 +234,7 @@ def _load_plugins(cls, plugins_dir, types): try: exports = cls._import_module(module_dir, module_name, types, package) - except ImportError as e: + except Exception as e: log.debug("Failed to import module '%s': %s" % (module_name, e)) continue diff --git a/datumaro/datumaro/plugins/labelme_format.py b/datumaro/datumaro/plugins/labelme_format.py new file mode 100644 index 00000000000..22a07d70121 --- /dev/null +++ b/datumaro/datumaro/plugins/labelme_format.py @@ -0,0 +1,455 @@ + +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from collections import defaultdict +from defusedxml import ElementTree +import logging as log +import numpy as np +import os +import os.path as osp + +from datumaro.components.extractor import (SourceExtractor, + DatasetItem, AnnotationType, Mask, Bbox, Polygon, LabelCategories +) +from datumaro.components.extractor import Importer +from datumaro.components.converter import Converter +from datumaro.components.cli_plugin import CliPlugin +from datumaro.util.image import Image, save_image +from datumaro.util.mask_tools import load_mask, find_mask_bbox + + +class LabelMePath: + MASKS_DIR = 'Masks' + IMAGE_EXT = '.jpg' + +class LabelMeExtractor(SourceExtractor): + def __init__(self, path, subset_name=None): + super().__init__() + + assert osp.isdir(path) + self._rootdir = path + + self._subset = subset_name + + items, categories = self._parse(path) + self._categories = categories + self._items = items + + def categories(self): + return self._categories + + def __iter__(self): + for item in self._items: + yield item + + def __len__(self): + return len(self._items) + + def subsets(self): + if self._subset: + return [self._subset] + return None + + def get_subset(self, name): + if name != self._subset: + return None + return self + + def _parse(self, path): + categories = { + AnnotationType.label: LabelCategories(attributes={'occluded'}) + } + + items = [] + for p in sorted(p for p in os.listdir(path) if p.endswith('.xml')): + root = ElementTree.parse(osp.join(path, p)) + + image = None + image_path = osp.join(path, root.find('filename').text) + + image_size = None + imagesize_elem = root.find('imagesize') + if imagesize_elem is not None: + width_elem = imagesize_elem.find('ncols') + height_elem = imagesize_elem.find('nrows') + image_size = (int(height_elem.text), int(width_elem.text)) + image = Image(path=image_path, size=image_size) + + annotations = self._parse_annotations(root, path, categories) + + items.append(DatasetItem(id=osp.splitext(p)[0], subset=self._subset, + image=image, annotations=annotations)) + return items, categories + + @classmethod + def _parse_annotations(cls, xml_root, dataset_root, categories): + def parse_attributes(attr_str): + parsed = [] + if not attr_str: + return parsed + + for attr in [a.strip() for a in attr_str.split(',') if a.strip()]: + if '=' in attr: + name, value = attr.split('=', maxsplit=1) + parsed.append((name, value)) + else: + parsed.append((attr, '1')) + + return parsed + + label_cat = categories[AnnotationType.label] + def get_label_id(label): + if not label: + return None + idx, _ = label_cat.find(label) + if idx is None: + idx = label_cat.add(label) + return idx + + image_annotations = [] + + parsed_annotations = dict() + group_assignments = dict() + root_annotations = set() + for obj_elem in xml_root.iter('object'): + obj_id = int(obj_elem.find('id').text) + + ann_items = [] + + label = get_label_id(obj_elem.find('name').text) + + attributes = [] + attributes_elem = obj_elem.find('attributes') + if attributes_elem is not None and attributes_elem.text: + attributes = parse_attributes(attributes_elem.text) + + occluded = False + occluded_elem = obj_elem.find('occluded') + if occluded_elem is not None and occluded_elem.text: + occluded = (occluded_elem.text == 'yes') + attributes.append(('occluded', occluded)) + + deleted = False + deleted_elem = obj_elem.find('deleted') + if deleted_elem is not None and deleted_elem.text: + deleted = bool(int(deleted_elem.text)) + + poly_elem = obj_elem.find('polygon') + segm_elem = obj_elem.find('segm') + type_elem = obj_elem.find('type') # the only value is 'bounding_box' + if poly_elem is not None: + points = [] + for point_elem in poly_elem.iter('pt'): + x = float(point_elem.find('x').text) + y = float(point_elem.find('y').text) + points.append(x) + points.append(y) + + if type_elem is not None and type_elem.text == 'bounding_box': + xmin = min(points[::2]) + xmax = max(points[::2]) + ymin = min(points[1::2]) + ymax = max(points[1::2]) + ann_items.append(Bbox(xmin, ymin, xmax - xmin, ymax - ymin, + label=label, attributes=attributes, + )) + else: + ann_items.append(Polygon(points, + label=label, attributes=attributes, + )) + elif segm_elem is not None: + mask_path = osp.join(dataset_root, LabelMePath.MASKS_DIR, + segm_elem.find('mask').text) + if not osp.isfile(mask_path): + raise Exception("Can't find mask at '%s'" % mask_path) + mask = load_mask(mask_path) + mask = np.any(mask, axis=2) + ann_items.append(Mask(image=mask, label=label, + attributes=attributes)) + + if not deleted: + parsed_annotations[obj_id] = ann_items + + # Find parents and children + parts_elem = obj_elem.find('parts') + if parts_elem is not None: + children_ids = [] + hasparts_elem = parts_elem.find('hasparts') + if hasparts_elem is not None and hasparts_elem.text: + children_ids = [int(c) for c in hasparts_elem.text.split(',')] + + parent_ids = [] + ispartof_elem = parts_elem.find('ispartof') + if ispartof_elem is not None and ispartof_elem.text: + parent_ids = [int(c) for c in ispartof_elem.text.split(',')] + + if children_ids and not parent_ids and hasparts_elem.text: + root_annotations.add(obj_id) + group_assignments[obj_id] = [None, children_ids] + + # assign single group to all grouped annotations + current_group_id = 0 + annotations_to_visit = list(root_annotations) + while annotations_to_visit: + ann_id = annotations_to_visit.pop() + ann_assignment = group_assignments[ann_id] + group_id, children_ids = ann_assignment + if group_id: + continue + + if ann_id in root_annotations: + current_group_id += 1 # start a new group + + group_id = current_group_id + ann_assignment[0] = group_id + + # continue with children + annotations_to_visit.extend(children_ids) + + assert current_group_id == len(root_annotations) + + for ann_id, ann_items in parsed_annotations.items(): + group_id = 0 + if ann_id in group_assignments: + ann_assignment = group_assignments[ann_id] + group_id = ann_assignment[0] + + for ann_item in ann_items: + if group_id: + ann_item.group = group_id + + image_annotations.append(ann_item) + + return image_annotations + + +class LabelMeImporter(Importer): + _EXTRACTOR_NAME = 'label_me' + + @classmethod + def detect(cls, path): + if not osp.isdir(path): + return False + return len(cls.find_subsets(path)) != 0 + + def __call__(self, path, **extra_params): + from datumaro.components.project import Project # cyclic import + project = Project() + + subset_paths = self.find_subsets(path) + if len(subset_paths) == 0: + raise Exception("Failed to find 'label_me' dataset at '%s'" % path) + + for subset_path, subset_name in subset_paths: + params = {} + if subset_name: + params['subset_name'] = subset_name + params.update(extra_params) + + source_name = osp.splitext(osp.basename(subset_path))[0] + project.add_source(source_name, + { + 'url': subset_path, + 'format': self._EXTRACTOR_NAME, + 'options': params, + }) + + return project + + @staticmethod + def find_subsets(path): + subset_paths = [] + if not osp.isdir(path): + raise Exception("Expected directory path, got '%s'" % path) + + path = osp.normpath(path) + + def has_annotations(d): + return len([p for p in os.listdir(d) if p.endswith('.xml')]) != 0 + + if has_annotations(path): + subset_paths = [(path, None)] + else: + for d in os.listdir(path): + subset = d + d = osp.join(path, d) + if osp.isdir(d) and has_annotations(d): + subset_paths.append((d, subset)) + return subset_paths + + +class LabelMeConverter(Converter, CliPlugin): + @classmethod + def build_cmdline_parser(cls, **kwargs): + parser = super().build_cmdline_parser(**kwargs) + parser.add_argument('--save-images', action='store_true', + help="Save images (default: %(default)s)") + return parser + + def __init__(self, save_images=False): + super().__init__() + + self._save_images = save_images + + def __call__(self, extractor, save_dir): + self._extractor = extractor + + subsets = extractor.subsets() + if len(subsets) == 0: + subsets = [ None ] + + for subset_name in subsets: + if subset_name: + subset = extractor.get_subset(subset_name) + else: + subset_name = DEFAULT_SUBSET_NAME + subset = extractor + + subset_dir = osp.join(save_dir, subset_name) + os.makedirs(subset_dir, exist_ok=True) + os.makedirs(osp.join(subset_dir, LabelMePath.MASKS_DIR), + exist_ok=True) + + for item in subset: + self._save_item(item, subset_dir) + + def _get_label(self, label_id): + if label_id is None: + return '' + return self._extractor.categories()[AnnotationType.label] \ + .items[label_id].name + + def _save_item(self, item, subset_dir): + from lxml import etree as ET + + log.debug("Converting item '%s'", item.id) + + image_filename = '' + if item.has_image: + image_filename = item.image.filename + if self._save_images: + if item.has_image and item.image.has_data: + if image_filename: + image_filename = osp.splitext(image_filename)[0] + else: + image_filename = item.id + image_filename += LabelMePath.IMAGE_EXT + save_image(osp.join(subset_dir, image_filename), + item.image.data) + else: + log.debug("Item '%s' has no image" % item.id) + + root_elem = ET.Element('annotation') + ET.SubElement(root_elem, 'filename').text = image_filename + ET.SubElement(root_elem, 'folder').text = '' + + source_elem = ET.SubElement(root_elem, 'source') + ET.SubElement(source_elem, 'sourceImage').text = '' + ET.SubElement(source_elem, 'sourceAnnotation').text = 'Datumaro' + + if item.has_image: + image_elem = ET.SubElement(root_elem, 'imagesize') + image_size = item.image.size + ET.SubElement(image_elem, 'nrows').text = str(image_size[0]) + ET.SubElement(image_elem, 'ncols').text = str(image_size[1]) + + groups = defaultdict(list) + + obj_id = 0 + for ann in item.annotations: + if not ann.type in { AnnotationType.polygon, + AnnotationType.bbox, AnnotationType.mask }: + continue + + obj_elem = ET.SubElement(root_elem, 'object') + ET.SubElement(obj_elem, 'name').text = self._get_label(ann.label) + ET.SubElement(obj_elem, 'deleted').text = '0' + ET.SubElement(obj_elem, 'verified').text = '0' + ET.SubElement(obj_elem, 'occluded').text = \ + 'yes' if ann.attributes.get('occluded') == True else 'no' + ET.SubElement(obj_elem, 'date').text = '' + ET.SubElement(obj_elem, 'id').text = str(obj_id) + + parts_elem = ET.SubElement(obj_elem, 'parts') + if ann.group: + groups[ann.group].append((obj_id, parts_elem)) + else: + ET.SubElement(parts_elem, 'hasparts').text = '' + ET.SubElement(parts_elem, 'ispartof').text = '' + + if ann.type == AnnotationType.bbox: + ET.SubElement(obj_elem, 'type').text = 'bounding_box' + + poly_elem = ET.SubElement(obj_elem, 'polygon') + x0, y0, x1, y1 = ann.points + points = [ (x0, y0), (x1, y0), (x1, y1), (x0, y1) ] + for x, y in points: + point_elem = ET.SubElement(poly_elem, 'pt') + ET.SubElement(point_elem, 'x').text = '%.2f' % x + ET.SubElement(point_elem, 'y').text = '%.2f' % y + + ET.SubElement(poly_elem, 'username').text = '' + elif ann.type == AnnotationType.polygon: + poly_elem = ET.SubElement(obj_elem, 'polygon') + for x, y in zip(ann.points[::2], ann.points[1::2]): + point_elem = ET.SubElement(poly_elem, 'pt') + ET.SubElement(point_elem, 'x').text = '%.2f' % x + ET.SubElement(point_elem, 'y').text = '%.2f' % y + + ET.SubElement(poly_elem, 'username').text = '' + elif ann.type == AnnotationType.mask: + mask_filename = '%s_mask_%s.png' % (item.id, obj_id) + save_image(osp.join(subset_dir, LabelMePath.MASKS_DIR, + mask_filename), + self._paint_mask(ann.image)) + + segm_elem = ET.SubElement(obj_elem, 'segm') + ET.SubElement(segm_elem, 'mask').text = mask_filename + + bbox = find_mask_bbox(ann.image) + box_elem = ET.SubElement(segm_elem, 'box') + ET.SubElement(box_elem, 'xmin').text = '%.2f' % bbox[0] + ET.SubElement(box_elem, 'ymin').text = '%.2f' % bbox[1] + ET.SubElement(box_elem, 'xmax').text = \ + '%.2f' % (bbox[0] + bbox[2]) + ET.SubElement(box_elem, 'ymax').text = \ + '%.2f' % (bbox[1] + bbox[3]) + else: + raise NotImplementedError("Unknown shape type '%s'" % ann.type) + + attrs = [] + for k, v in ann.attributes.items(): + if k == 'occluded': + continue + if isinstance(v, bool): + attrs.append(k) + else: + attrs.append('%s=%s' % (k, v)) + ET.SubElement(obj_elem, 'attributes').text = ', '.join(attrs) + + obj_id += 1 + + for _, group in groups.items(): + leader_id, leader_parts_elem = group[0] + leader_parts = [str(o_id) for o_id, _ in group[1:]] + ET.SubElement(leader_parts_elem, 'hasparts').text = \ + ','.join(leader_parts) + ET.SubElement(leader_parts_elem, 'ispartof').text = '' + + for obj_id, parts_elem in group[1:]: + ET.SubElement(parts_elem, 'hasparts').text = '' + ET.SubElement(parts_elem, 'ispartof').text = str(leader_id) + + xml_path = osp.join(subset_dir, '%s.xml' % item.id) + with open(xml_path, 'w', encoding='utf-8') as f: + xml_data = ET.tostring(root_elem, encoding='unicode', + pretty_print=True) + f.write(xml_data) + + @staticmethod + def _paint_mask(mask): + # TODO: check if mask colors are random + return np.array([[0, 0, 0, 0], [255, 203, 0, 153]], + dtype=np.uint8)[mask.astype(np.uint8)] \ No newline at end of file diff --git a/datumaro/datumaro/plugins/transforms.py b/datumaro/datumaro/plugins/transforms.py index 78d9ecf36aa..17f0b2a08fd 100644 --- a/datumaro/datumaro/plugins/transforms.py +++ b/datumaro/datumaro/plugins/transforms.py @@ -443,9 +443,8 @@ def _make_label_id_map(self, src_label_cat, label_mapping, default_action): dst_index = dst_label_cat.find(dst_label)[0] if dst_index is None: - dst_label_cat.add(dst_label, + dst_index = dst_label_cat.add(dst_label, src_label.parent, src_label.attributes) - dst_index = dst_label_cat.find(dst_label)[0] id_mapping[src_index] = dst_index if log.getLogger().isEnabledFor(log.DEBUG): diff --git a/datumaro/datumaro/util/mask_tools.py b/datumaro/datumaro/util/mask_tools.py index dea22c8ea5f..a4eb81507b0 100644 --- a/datumaro/datumaro/util/mask_tools.py +++ b/datumaro/datumaro/util/mask_tools.py @@ -3,7 +3,6 @@ # # SPDX-License-Identifier: MIT -from itertools import groupby import numpy as np from datumaro.util.image import lazy_image, load_image diff --git a/datumaro/tests/test_labelme_format.py b/datumaro/tests/test_labelme_format.py new file mode 100644 index 00000000000..2ec731e33ab --- /dev/null +++ b/datumaro/tests/test_labelme_format.py @@ -0,0 +1,120 @@ +import numpy as np + +from unittest import TestCase + +from datumaro.components.extractor import (Extractor, DatasetItem, + AnnotationType, Bbox, Mask, Polygon, LabelCategories +) +from datumaro.plugins.labelme_format import LabelMeImporter, LabelMeConverter +from datumaro.util.test_utils import TestDir, compare_datasets + + +class LabelMeConverterTest(TestCase): + def _test_save_and_load(self, source_dataset, converter, test_dir, + target_dataset=None, importer_args=None): + converter(source_dataset, test_dir) + + if importer_args is None: + importer_args = {} + parsed_dataset = LabelMeImporter()(test_dir, **importer_args) \ + .make_dataset() + + if target_dataset is None: + target_dataset = source_dataset + + compare_datasets(self, expected=target_dataset, actual=parsed_dataset) + + def test_can_save_and_load(self): + class SrcExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, subset='train', + image=np.ones((16, 16, 3)), + annotations=[ + Bbox(0, 4, 4, 8, label=2, group=2), + Polygon([0, 4, 4, 4, 5, 6], label=3, attributes={ + 'occluded': True + }), + Mask(np.array([[0, 1], [1, 0], [1, 1]]), group=2), + Bbox(1, 2, 3, 4, group=3), + Mask(np.array([[0, 0], [0, 0], [1, 1]]), group=3, + attributes={ 'occluded': True } + ), + ] + ), + ]) + + def categories(self): + label_cat = LabelCategories() + for label in range(10): + label_cat.add('label_' + str(label)) + return { + AnnotationType.label: label_cat, + } + + class DstExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, subset='train', + image=np.ones((16, 16, 3)), + annotations=[ + Bbox(0, 4, 4, 8, label=0, group=2, attributes={ + 'occluded': False + }), + Polygon([0, 4, 4, 4, 5, 6], label=1, attributes={ + 'occluded': True + }), + Mask(np.array([[0, 1], [1, 0], [1, 1]]), group=2, + attributes={ 'occluded': False } + ), + Bbox(1, 2, 3, 4, group=1, attributes={ + 'occluded': False + }), + Mask(np.array([[0, 0], [0, 0], [1, 1]]), group=1, + attributes={ 'occluded': True } + ), + ] + ), + ]) + + def categories(self): + label_cat = LabelCategories() + label_cat.add('label_2') + label_cat.add('label_3') + return { + AnnotationType.label: label_cat, + } + + with TestDir() as test_dir: + self._test_save_and_load( + SrcExtractor(), LabelMeConverter(save_images=True), + test_dir, target_dataset=DstExtractor()) + +class LabelMeImporterTest(TestCase): + def test_can_detect(self): + class TestExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, subset='train', + image=np.ones((16, 16, 3)), + annotations=[ + Bbox(0, 4, 4, 8, label=2), + ] + ), + ]) + + def categories(self): + label_cat = LabelCategories() + for label in range(10): + label_cat.add('label_' + str(label)) + return { + AnnotationType.label: label_cat, + } + + def generate_dummy(path): + LabelMeConverter()(TestExtractor(), save_dir=path) + + with TestDir() as test_dir: + generate_dummy(test_dir) + + self.assertTrue(LabelMeImporter.detect(test_dir)) \ No newline at end of file From fe862b4abc846750870e12890a1b5c9cb92b9f96 Mon Sep 17 00:00:00 2001 From: zhiltsov-max Date: Fri, 20 Mar 2020 16:37:49 +0300 Subject: [PATCH 23/49] [Datumaro] Update LabelMe format (#1296) * Little refactoring * Add LabelMe format * Add usernames * Update tests * Add extractor test --- datumaro/datumaro/components/project.py | 2 + datumaro/datumaro/plugins/labelme_format.py | 35 +++-- .../labelme_dataset/Masks/img1_mask_1.png | Bin 0 -> 211 bytes .../labelme_dataset/Masks/img1_mask_5.png | Bin 0 -> 388 bytes .../Scribbles/img1_scribble_1.png | Bin 0 -> 206 bytes .../Scribbles/img1_scribble_5.png | Bin 0 -> 387 bytes .../tests/assets/labelme_dataset/img1.png | Bin 0 -> 215 bytes .../tests/assets/labelme_dataset/img1.xml | 1 + datumaro/tests/test_labelme_format.py | 144 +++++++++++++++--- 9 files changed, 148 insertions(+), 34 deletions(-) create mode 100644 datumaro/tests/assets/labelme_dataset/Masks/img1_mask_1.png create mode 100644 datumaro/tests/assets/labelme_dataset/Masks/img1_mask_5.png create mode 100644 datumaro/tests/assets/labelme_dataset/Scribbles/img1_scribble_1.png create mode 100644 datumaro/tests/assets/labelme_dataset/Scribbles/img1_scribble_5.png create mode 100644 datumaro/tests/assets/labelme_dataset/img1.png create mode 100644 datumaro/tests/assets/labelme_dataset/img1.xml diff --git a/datumaro/datumaro/components/project.py b/datumaro/datumaro/components/project.py index bcc210e9646..4f23639b442 100644 --- a/datumaro/datumaro/components/project.py +++ b/datumaro/datumaro/components/project.py @@ -367,6 +367,8 @@ def categories(self): def get(self, item_id, subset=None, path=None): if path: raise KeyError("Requested dataset item path is not found") + if subset is None: + subset = '' return self._subsets[subset].items[item_id] def put(self, item, item_id=None, subset=None, path=None): diff --git a/datumaro/datumaro/plugins/labelme_format.py b/datumaro/datumaro/plugins/labelme_format.py index 22a07d70121..41069da9dab 100644 --- a/datumaro/datumaro/plugins/labelme_format.py +++ b/datumaro/datumaro/plugins/labelme_format.py @@ -59,7 +59,9 @@ def get_subset(self, name): def _parse(self, path): categories = { - AnnotationType.label: LabelCategories(attributes={'occluded'}) + AnnotationType.label: LabelCategories(attributes={ + 'occluded', 'username' + }) } items = [] @@ -136,10 +138,17 @@ def get_label_id(label): if deleted_elem is not None and deleted_elem.text: deleted = bool(int(deleted_elem.text)) + user = '' + poly_elem = obj_elem.find('polygon') segm_elem = obj_elem.find('segm') type_elem = obj_elem.find('type') # the only value is 'bounding_box' if poly_elem is not None: + user_elem = poly_elem.find('username') + if user_elem is not None and user_elem.text: + user = user_elem.text + attributes.append(('username', user)) + points = [] for point_elem in poly_elem.iter('pt'): x = float(point_elem.find('x').text) @@ -153,20 +162,25 @@ def get_label_id(label): ymin = min(points[1::2]) ymax = max(points[1::2]) ann_items.append(Bbox(xmin, ymin, xmax - xmin, ymax - ymin, - label=label, attributes=attributes, + label=label, attributes=attributes, id=obj_id, )) else: ann_items.append(Polygon(points, - label=label, attributes=attributes, + label=label, attributes=attributes, id=obj_id, )) elif segm_elem is not None: + user_elem = segm_elem.find('username') + if user_elem is not None and user_elem.text: + user = user_elem.text + attributes.append(('username', user)) + mask_path = osp.join(dataset_root, LabelMePath.MASKS_DIR, segm_elem.find('mask').text) if not osp.isfile(mask_path): raise Exception("Can't find mask at '%s'" % mask_path) mask = load_mask(mask_path) mask = np.any(mask, axis=2) - ann_items.append(Mask(image=mask, label=label, + ann_items.append(Mask(image=mask, label=label, id=obj_id, attributes=attributes)) if not deleted: @@ -368,7 +382,7 @@ def _save_item(self, item, subset_dir): ET.SubElement(obj_elem, 'deleted').text = '0' ET.SubElement(obj_elem, 'verified').text = '0' ET.SubElement(obj_elem, 'occluded').text = \ - 'yes' if ann.attributes.get('occluded') == True else 'no' + 'yes' if ann.attributes.pop('occluded', '') == True else 'no' ET.SubElement(obj_elem, 'date').text = '' ET.SubElement(obj_elem, 'id').text = str(obj_id) @@ -390,7 +404,8 @@ def _save_item(self, item, subset_dir): ET.SubElement(point_elem, 'x').text = '%.2f' % x ET.SubElement(point_elem, 'y').text = '%.2f' % y - ET.SubElement(poly_elem, 'username').text = '' + ET.SubElement(poly_elem, 'username').text = \ + str(ann.attributes.pop('username', '')) elif ann.type == AnnotationType.polygon: poly_elem = ET.SubElement(obj_elem, 'polygon') for x, y in zip(ann.points[::2], ann.points[1::2]): @@ -398,7 +413,8 @@ def _save_item(self, item, subset_dir): ET.SubElement(point_elem, 'x').text = '%.2f' % x ET.SubElement(point_elem, 'y').text = '%.2f' % y - ET.SubElement(poly_elem, 'username').text = '' + ET.SubElement(poly_elem, 'username').text = \ + str(ann.attributes.pop('username', '')) elif ann.type == AnnotationType.mask: mask_filename = '%s_mask_%s.png' % (item.id, obj_id) save_image(osp.join(subset_dir, LabelMePath.MASKS_DIR, @@ -416,13 +432,14 @@ def _save_item(self, item, subset_dir): '%.2f' % (bbox[0] + bbox[2]) ET.SubElement(box_elem, 'ymax').text = \ '%.2f' % (bbox[1] + bbox[3]) + + ET.SubElement(segm_elem, 'username').text = \ + str(ann.attributes.pop('username', '')) else: raise NotImplementedError("Unknown shape type '%s'" % ann.type) attrs = [] for k, v in ann.attributes.items(): - if k == 'occluded': - continue if isinstance(v, bool): attrs.append(k) else: diff --git a/datumaro/tests/assets/labelme_dataset/Masks/img1_mask_1.png b/datumaro/tests/assets/labelme_dataset/Masks/img1_mask_1.png new file mode 100644 index 0000000000000000000000000000000000000000..a37c5508f9b63496a536ed20877da884eb88e854 GIT binary patch literal 211 zcmeAS@N?(olHy`uVBq!ia0vp^X+Z4D!3HElwg^rKQjEnx?oJHr&dIz4a#+$GeH|GX zHuiJ>Nn{1`ISV`@iy0XB4ude`@%$AjK*0=87srr_xVI+`@;U?vxL#bycvQnnei5hc zhLCnYowqYjtas39DV{Xjd?rva4$x4&YSVo4ZO12EPC5RyNn{1`ISV`@iy0XB4ude`@%$AjKn;?fE{-7;ac{31>ahgMuzgTe;cE4J%pt_u z%eaGheZXkv$pvXG0a(833^=Pp#(6nQr1-oLt&-|y8g|NHFh znJ}Oo3=9SvHkRpMIhQl-X`0-L#L%_-w->8>>8IJ94m0LY+59@`*zLU`|95_r+pV`W ztjbXS?Z3<3MYCj7s!w`;db;qgwdpGLXEw`|%vau5b^p0{{vEaaldhL`2HF}1SN>PI zKW);^Ct9wu-`AC_`I7W+(~O+ub6QhWKS#aK3Z1*P`ni|>Gpn81$Kqf4zwAvho#`=e z((DwCDRDl3cg)H=womV~#P0m(=cfL%Pcu60WBmQ=oTRwhl3+)p5eX6@SJh`Nn{1`ISV`@iy0XB4ude`@%$AjK*3~B7srr_xVPsI@;W#Ov|W7o>cJYhySe4J z3IdswmoXIz-1GidGkI?=P$>>@z&7^l=WiXF#?RaC%#hE2{yX~XRr?#Ovh#NO0d>q1 ZEM(}&Wm)G^ko^K=fTydU%Q~loCII=|J+=S< literal 0 HcmV?d00001 diff --git a/datumaro/tests/assets/labelme_dataset/Scribbles/img1_scribble_5.png b/datumaro/tests/assets/labelme_dataset/Scribbles/img1_scribble_5.png new file mode 100644 index 0000000000000000000000000000000000000000..415e1f88b2cafe6d77e4e9b4555664a005460d5a GIT binary patch literal 387 zcmeAS@N?(olHy`uVBq!ia0vp^X+Z4D!3HElwg^rKQjEnx?oJHr&dIz4a#+$GeH|GX zHuiJ>Nn{1`ISV`@iy0XB4ude`@%$AjKn)U}E{-7;ac{5bd$Tw)v_G``Kh4?2Vhcyp z(FBnz3QcaV-6u;r*eL8?AX362lWO7;64@S>5uG=90N5U)pT{QaStLiKg{Se$_7x zoV)C;$<$4mq4wXOU-tZbV14${&Y#D8Z+W`E)VaHS`xWmVFFz-9-dU&JOlMaWKP@%- zKJ9X))a>xc?0u&;y@-~)cKP@7BLRPpt+@U`@$NJCze_ATo!l| OB<<8wB`-xB}__|NjF?B0==h_NhRnoCO|{#S9F5he4R}c>anMprC=Li(^Q{ z;kTz33Nk1NFmFg_-t~VIhs?TZ-Zg5r&*$0pKegz4Y_c3gTJ(MX?#b7j^*rxUX47<# N-JY&~F6*2UngCPUH#YzP literal 0 HcmV?d00001 diff --git a/datumaro/tests/assets/labelme_dataset/img1.xml b/datumaro/tests/assets/labelme_dataset/img1.xml new file mode 100644 index 00000000000..ff8ae1b46e3 --- /dev/null +++ b/datumaro/tests/assets/labelme_dataset/img1.xml @@ -0,0 +1 @@ +img1.pngexample_folderThe MIT-CSAIL database of objects and scenesLabelMe Webtoolwindow0025-May-2012 00:09:480admin433445344537433777102license plate00no27-Jul-2014 02:58:501brussell58666268img1_mask_1.png58666268img1_scribble_1.pngo100yesa13,415-Nov-2019 14:38:512anonymous3012422124261522181422122712q100nokj215-Nov-2019 14:39:003anonymous352143224028283131223225b100yeshg215-Nov-2019 14:39:094bounding_boxanonymous1319231923301330m100nod615-Nov-2019 14:39:305bounding_boxanonymous56147023img1_mask_5.png55137023img1_scribble_5.pnghg00nogfd lkj lkj hi515-Nov-2019 14:41:576anonymous642174247232623460276222 \ No newline at end of file diff --git a/datumaro/tests/test_labelme_format.py b/datumaro/tests/test_labelme_format.py index 2ec731e33ab..35fa2ca848b 100644 --- a/datumaro/tests/test_labelme_format.py +++ b/datumaro/tests/test_labelme_format.py @@ -1,11 +1,14 @@ import numpy as np +import os.path as osp from unittest import TestCase from datumaro.components.extractor import (Extractor, DatasetItem, AnnotationType, Bbox, Mask, Polygon, LabelCategories ) -from datumaro.plugins.labelme_format import LabelMeImporter, LabelMeConverter +from datumaro.components.project import Dataset +from datumaro.plugins.labelme_format import LabelMeExtractor, LabelMeImporter, \ + LabelMeConverter from datumaro.util.test_utils import TestDir, compare_datasets @@ -35,7 +38,8 @@ def __iter__(self): Polygon([0, 4, 4, 4, 5, 6], label=3, attributes={ 'occluded': True }), - Mask(np.array([[0, 1], [1, 0], [1, 1]]), group=2), + Mask(np.array([[0, 1], [1, 0], [1, 1]]), group=2, + attributes={ 'username': 'test' }), Bbox(1, 2, 3, 4, group=3), Mask(np.array([[0, 0], [0, 0], [1, 1]]), group=3, attributes={ 'occluded': True } @@ -58,20 +62,28 @@ def __iter__(self): DatasetItem(id=1, subset='train', image=np.ones((16, 16, 3)), annotations=[ - Bbox(0, 4, 4, 8, label=0, group=2, attributes={ - 'occluded': False - }), - Polygon([0, 4, 4, 4, 5, 6], label=1, attributes={ - 'occluded': True - }), + Bbox(0, 4, 4, 8, label=0, group=2, id=0, + attributes={ + 'occluded': False, 'username': '', + } + ), + Polygon([0, 4, 4, 4, 5, 6], label=1, id=1, + attributes={ + 'occluded': True, 'username': '', + } + ), Mask(np.array([[0, 1], [1, 0], [1, 1]]), group=2, - attributes={ 'occluded': False } + id=2, attributes={ + 'occluded': False, 'username': 'test' + } ), - Bbox(1, 2, 3, 4, group=1, attributes={ - 'occluded': False + Bbox(1, 2, 3, 4, group=1, id=3, attributes={ + 'occluded': False, 'username': '', }), Mask(np.array([[0, 0], [0, 0], [1, 1]]), group=1, - attributes={ 'occluded': True } + id=4, attributes={ + 'occluded': True, 'username': '' + } ), ] ), @@ -90,31 +102,113 @@ def categories(self): SrcExtractor(), LabelMeConverter(save_images=True), test_dir, target_dataset=DstExtractor()) -class LabelMeImporterTest(TestCase): - def test_can_detect(self): - class TestExtractor(Extractor): + +DUMMY_DATASET_DIR = osp.join(osp.dirname(__file__), 'assets', 'labelme_dataset') + +class LabelMeExtractorTest(TestCase): + def test_can_load(self): + class DstExtractor(Extractor): def __iter__(self): + img1 = np.ones((77, 102, 3)) * 255 + img1[6:32, 7:41] = 0 + + mask1 = np.zeros((77, 102), dtype=int) + mask1[67:69, 58:63] = 1 + + mask2 = np.zeros((77, 102), dtype=int) + mask2[13:25, 54:71] = [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], + [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], + [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0], + [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0], + [0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0], + [0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0], + [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0], + [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0], + [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0], + [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ] + return iter([ - DatasetItem(id=1, subset='train', - image=np.ones((16, 16, 3)), + DatasetItem(id='img1', image=img1, annotations=[ - Bbox(0, 4, 4, 8, label=2), + Polygon([43, 34, 45, 34, 45, 37, 43, 37], + label=0, id=0, + attributes={ + 'occluded': False, + 'username': 'admin' + } + ), + Mask(mask1, label=1, id=1, + attributes={ + 'occluded': False, + 'username': 'brussell' + } + ), + Polygon([30, 12, 42, 21, 24, 26, 15, 22, 18, 14, 22, 12, 27, 12], + label=2, group=2, id=2, + attributes={ + 'a1': '1', + 'occluded': True, + 'username': 'anonymous' + } + ), + Polygon([35, 21, 43, 22, 40, 28, 28, 31, 31, 22, 32, 25], + label=3, group=2, id=3, + attributes={ + 'kj': '1', + 'occluded': False, + 'username': 'anonymous' + } + ), + Bbox(13, 19, 10, 11, label=4, group=2, id=4, + attributes={ + 'hg': '1', + 'occluded': True, + 'username': 'anonymous' + } + ), + Mask(mask2, label=5, group=1, id=5, + attributes={ + 'd': '1', + 'occluded': False, + 'username': 'anonymous' + } + ), + Polygon([64, 21, 74, 24, 72, 32, 62, 34, 60, 27, 62, 22], + label=6, group=1, id=6, + attributes={ + 'gfd lkj lkj hi': '1', + 'occluded': False, + 'username': 'anonymous' + } + ), ] ), ]) def categories(self): label_cat = LabelCategories() - for label in range(10): - label_cat.add('label_' + str(label)) + label_cat.add('window') + label_cat.add('license plate') + label_cat.add('o1') + label_cat.add('q1') + label_cat.add('b1') + label_cat.add('m1') + label_cat.add('hg') return { AnnotationType.label: label_cat, } - def generate_dummy(path): - LabelMeConverter()(TestExtractor(), save_dir=path) + parsed = Dataset.from_extractors(LabelMeExtractor(DUMMY_DATASET_DIR)) + compare_datasets(self, expected=DstExtractor(), actual=parsed) - with TestDir() as test_dir: - generate_dummy(test_dir) +class LabelMeImporterTest(TestCase): + def test_can_detect(self): + self.assertTrue(LabelMeImporter.detect(DUMMY_DATASET_DIR)) - self.assertTrue(LabelMeImporter.detect(test_dir)) \ No newline at end of file + def test_can_import(self): + parsed = LabelMeImporter()(DUMMY_DATASET_DIR).make_dataset() + self.assertEqual(1, len(parsed)) From feebec2670186f8f56f4314cb45d596d5dc13e8a Mon Sep 17 00:00:00 2001 From: Nikita Manovich <40690625+nmanovic@users.noreply.github.com> Date: Sat, 21 Mar 2020 11:17:27 +0300 Subject: [PATCH 24/49] Release v0.6.1 (#1267) * Change the version and updated CHANGELOG.md * Installation issues for development environment (#1280) * Installation issues * Added ffmpeg * Bump acorn from 6.3.0 to 6.4.1 in /cvat-ui (#1270) * Bump acorn from 6.3.0 to 6.4.1 in /cvat-ui Bumps [acorn](https://github.com/acornjs/acorn) from 6.3.0 to 6.4.1. - [Release notes](https://github.com/acornjs/acorn/releases) - [Commits](https://github.com/acornjs/acorn/compare/6.3.0...6.4.1) Signed-off-by: dependabot[bot] * Updated CHANGELOG.md Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Boris Sekachev * Bump acorn from 6.2.1 to 6.4.1 in /cvat-canvas (#1281) Bumps [acorn](https://github.com/acornjs/acorn) from 6.2.1 to 6.4.1. - [Release notes](https://github.com/acornjs/acorn/releases) - [Commits](https://github.com/acornjs/acorn/compare/6.2.1...6.4.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Use source label map for voc export (#1276) * Use source label map for voc export * Add line to changelog * [Datumaro] Fix frame matching in video annotations import (#1274) * Add extra frame matching way for videos * Add line to changelog * [Datumaro] Allow empty COCO dataset export (#1272) * Allow empty dataset export in coco * Add line to changelog Co-authored-by: Nikita Manovich <40690625+nmanovic@users.noreply.github.com> * [Datumaro] Fix occluded and z_order attributes export (#1271) * Fix occluded and z_order attributes export * Add line to changelog Co-authored-by: Nikita Manovich <40690625+nmanovic@users.noreply.github.com> * Fix LabelMe format (#1260) * Fix labelme filenames * Change module path * Add tests for LabelMe * Update test * Fix test * Add line in changelog * Fix release date. Co-authored-by: Boris Sekachev <40690378+bsekachev@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Boris Sekachev Co-authored-by: zhiltsov-max --- CHANGELOG.md | 16 +++++++++ CONTRIBUTING.md | 4 +-- cvat-canvas/package-lock.json | 8 ++--- cvat-ui/package-lock.json | 20 +++++------ cvat/__init__.py | 2 +- cvat/apps/annotation/labelme.py | 8 ++--- cvat/apps/annotation/pascal_voc.py | 2 +- cvat/apps/dataset_manager/bindings.py | 12 +++++-- cvat/apps/engine/tests/test_rest_api.py | 15 ++++++++ cvat/requirements/development.txt | 2 +- .../datumaro/plugins/coco_format/converter.py | 36 +++++++++++-------- .../datumaro/plugins/yolo_format/extractor.py | 4 ++- datumaro/tests/test_coco_format.py | 5 ++- 13 files changed, 92 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bbc45348ba..52efb211e13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.6.1] - 2020-03-21 +### Changed +- VOC task export now does not use official label map by default, but takes one + from the source task to avoid primary-class and class part name + clashing ([#1275](https://github.com/opencv/cvat/issues/1275)) + +### Fixed +- File names in LabelMe format export are no longer truncated ([#1259](https://github.com/opencv/cvat/issues/1259)) +- `occluded` and `z_order` annotation attributes are now correctly passed to Datumaro ([#1271](https://github.com/opencv/cvat/pull/1271)) +- Annotation-less tasks now can be exported as empty datasets in COCO ([#1277](https://github.com/opencv/cvat/issues/1277)) +- Frame name matching for video annotations import - + allowed `frame_XXXXXX[.ext]` format ([#1274](https://github.com/opencv/cvat/pull/1274)) + +### Security +- Bump acorn from 6.3.0 to 6.4.1 in /cvat-ui ([#1270](https://github.com/opencv/cvat/pull/1270)) + ## [0.6.0] - 2020-03-15 ### Added - Server only support for projects. Extend REST API v1 (/api/v1/projects*) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a0ff41aca79..40faceb7642 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,7 +15,7 @@ Next steps should work on clear Ubuntu 18.04. - Install necessary dependencies: ```sh -$ sudo apt update && apt install -y nodejs npm curl redis-server python3-dev python3-pip python3-venv libldap2-dev libsasl2-dev +$ sudo apt-get update && sudo apt-get --no-install-recommends install -y ffmpeg build-essential nodejs npm curl redis-server python3-dev python3-pip python3-venv libldap2-dev libsasl2-dev ``` - Install [Visual Studio Code](https://code.visualstudio.com/docs/setup/linux#_debian-and-ubuntu-based-distributions) @@ -28,7 +28,7 @@ git clone https://github.com/opencv/cvat cd cvat && mkdir logs keys python3 -m venv .env . .env/bin/activate -pip install -U pip wheel +pip install -U pip wheel setuptools pip install -r cvat/requirements/development.txt pip install -r datumaro/requirements.txt python manage.py migrate diff --git a/cvat-canvas/package-lock.json b/cvat-canvas/package-lock.json index 46c12b74665..022e4582b77 100644 --- a/cvat-canvas/package-lock.json +++ b/cvat-canvas/package-lock.json @@ -1,6 +1,6 @@ { "name": "cvat-canvas", - "version": "0.1.0", + "version": "0.5.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1203,9 +1203,9 @@ } }, "acorn": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.2.1.tgz", - "integrity": "sha512-JD0xT5FCRDNyjDda3Lrg/IxFscp9q4tiYtxE1/nOzlKCk7hIRuYjhq1kCNkbPjMRMZuFq20HNQn1I9k8Oj0E+Q==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", + "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", "dev": true }, "acorn-jsx": { diff --git a/cvat-ui/package-lock.json b/cvat-ui/package-lock.json index 828118cfaaf..f2453993069 100644 --- a/cvat-ui/package-lock.json +++ b/cvat-ui/package-lock.json @@ -1421,9 +1421,9 @@ } }, "acorn": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz", - "integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", + "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", "dev": true }, "acorn-jsx": { @@ -4293,14 +4293,6 @@ "acorn": "^7.1.0", "acorn-jsx": "^5.1.0", "eslint-visitor-keys": "^1.1.0" - }, - "dependencies": { - "acorn": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz", - "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==", - "dev": true - } } }, "esprima": { @@ -12354,6 +12346,12 @@ "webpack-sources": "^1.4.1" }, "dependencies": { + "acorn": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", + "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", + "dev": true + }, "schema-utils": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", diff --git a/cvat/__init__.py b/cvat/__init__.py index a30e5d43896..5da4433250d 100644 --- a/cvat/__init__.py +++ b/cvat/__init__.py @@ -4,6 +4,6 @@ from cvat.utils.version import get_version -VERSION = (0, 6, 0, 'final', 0) +VERSION = (0, 6, 1, 'final', 0) __version__ = get_version(VERSION) diff --git a/cvat/apps/annotation/labelme.py b/cvat/apps/annotation/labelme.py index 0128ca73922..baacb388ef0 100644 --- a/cvat/apps/annotation/labelme.py +++ b/cvat/apps/annotation/labelme.py @@ -107,17 +107,17 @@ def dump_frame_anno(frame_annotation): return ET.tostring(root_elem, encoding='unicode', pretty_print=True) def dump_as_labelme_annotation(file_object, annotations): + import os.path as osp from zipfile import ZipFile, ZIP_DEFLATED with ZipFile(file_object, 'w', compression=ZIP_DEFLATED) as output_zip: for frame_annotation in annotations.group_by_frame(): xml_data = dump_frame_anno(frame_annotation) - filename = frame_annotation.name - filename = filename[ : filename.rfind('.')] + '.xml' + filename = osp.splitext(frame_annotation.name)[0] + '.xml' output_zip.writestr(filename, xml_data) def parse_xml_annotations(xml_data, annotations, input_zip): - from cvat.apps.annotation.coco import mask_to_polygon + from datumaro.util.mask_tools import mask_to_polygons from io import BytesIO from lxml import etree as ET import numpy as np @@ -229,7 +229,7 @@ def parse_attributes(attributes_string): mask = input_zip.read(osp.join(_MASKS_DIR, mask_file)) mask = np.asarray(Image.open(BytesIO(mask)).convert('L')) mask = (mask != 0) - polygons = mask_to_polygon(mask) + polygons = mask_to_polygons(mask) for polygon in polygons: ann_items.append(annotations.LabeledShape( diff --git a/cvat/apps/annotation/pascal_voc.py b/cvat/apps/annotation/pascal_voc.py index 2dd0aa48f51..b6bcfaa33ee 100644 --- a/cvat/apps/annotation/pascal_voc.py +++ b/cvat/apps/annotation/pascal_voc.py @@ -74,7 +74,7 @@ def dump(file_object, annotations): extractor = CvatAnnotationsExtractor('', annotations) extractor = extractor.transform(id_from_image) extractor = Dataset.from_extractors(extractor) # apply lazy transforms - converter = env.make_converter('voc') + converter = env.make_converter('voc', label_map='source') with TemporaryDirectory() as temp_dir: converter(extractor, save_dir=temp_dir) make_zip_archive(temp_dir, file_object) \ No newline at end of file diff --git a/cvat/apps/dataset_manager/bindings.py b/cvat/apps/dataset_manager/bindings.py index da37a3048e6..d1d98af279a 100644 --- a/cvat/apps/dataset_manager/bindings.py +++ b/cvat/apps/dataset_manager/bindings.py @@ -91,7 +91,9 @@ def categories(self): @staticmethod def _load_categories(cvat_anno): categories = {} - label_categories = datumaro.LabelCategories() + + label_categories = datumaro.LabelCategories( + attributes=['occluded', 'z_order']) for _, label in cvat_anno.meta['task']['labels']: label_categories.add(label['name']) @@ -144,6 +146,8 @@ def convert_attrs(label, cvat_attrs): anno_group = shape_obj.group anno_label = map_label(shape_obj.label) anno_attr = convert_attrs(shape_obj.label, shape_obj.attributes) + anno_attr['occluded'] = shape_obj.occluded + anno_attr['z_order'] = shape_obj.z_order anno_points = shape_obj.points if shape_obj.type == ShapeType.POINTS: @@ -177,6 +181,8 @@ def __init__(self, url, db_task, user): def match_frame(item, cvat_task_anno): + is_video = cvat_task_anno.meta['task']['mode'] == 'interpolation' + frame_number = None if frame_number is None: try: @@ -193,6 +199,8 @@ def match_frame(item, cvat_task_anno): frame_number = int(item.id) except Exception: pass + if frame_number is None and is_video and item.id.startswith('frame_'): + frame_number = int(item.id[len('frame_'):]) if not frame_number in cvat_task_anno.frame_info: raise Exception("Could not match item id: '%s' with any task frame" % item.id) @@ -234,7 +242,7 @@ def import_dm_annotations(dm_dataset, cvat_task_anno): frame=frame_number, label=label_cat.items[ann.label].name, points=ann.points, - occluded=False, + occluded=ann.attributes.get('occluded') == True, group=group_map.get(ann.group, 0), attributes=[cvat_task_anno.Attribute(name=n, value=str(v)) for n, v in ann.attributes.items()], diff --git a/cvat/apps/engine/tests/test_rest_api.py b/cvat/apps/engine/tests/test_rest_api.py index d959f71c5b5..f3da0410623 100644 --- a/cvat/apps/engine/tests/test_rest_api.py +++ b/cvat/apps/engine/tests/test_rest_api.py @@ -2655,6 +2655,15 @@ def _get_initial_annotation(annotation_format): "points": [20.0, 0.1, 10, 3.22, 4, 7, 10, 30, 1, 2, 4.44, 5.55], "type": "polygon", "occluded": True + }, + { + "frame": 2, + "label_id": task["labels"][1]["id"], + "group": 1, + "attributes": [], + "points": [4, 7, 10, 30, 4, 5.55], + "type": "polygon", + "occluded": False }] tags_wo_attrs = [{ @@ -2711,6 +2720,12 @@ def _get_initial_annotation(annotation_format): elif annotation_format == "MOT CSV 1.0": annotations["tracks"] = rectangle_tracks_wo_attrs + elif annotation_format == "LabelMe ZIP 3.0 for images": + annotations["shapes"] = rectangle_shapes_with_attrs + \ + rectangle_shapes_wo_attrs + \ + polygon_shapes_wo_attrs + \ + polygon_shapes_with_attrs + return annotations response = self._get_annotation_formats(annotator) diff --git a/cvat/requirements/development.txt b/cvat/requirements/development.txt index fc0333ad0a5..de046528865 100644 --- a/cvat/requirements/development.txt +++ b/cvat/requirements/development.txt @@ -7,7 +7,7 @@ pylint==2.3.1 pylint-django==0.9.4 pylint-plugin-utils==0.2.6 rope==0.11 -wrapt==1.10.11 +wrapt==1.11.1 django-extensions==2.0.6 Werkzeug==0.15.3 snakeviz==0.4.2 diff --git a/datumaro/datumaro/plugins/coco_format/converter.py b/datumaro/datumaro/plugins/coco_format/converter.py index 39fe7b15402..403a6a83eb6 100644 --- a/datumaro/datumaro/plugins/coco_format/converter.py +++ b/datumaro/datumaro/plugins/coco_format/converter.py @@ -329,20 +329,24 @@ def save_categories(self, dataset): label_categories = dataset.categories().get(AnnotationType.label) if label_categories is None: return - points_categories = dataset.categories().get(AnnotationType.points) - if points_categories is None: - return - - for idx, kp_cat in points_categories.items.items(): - label_cat = label_categories.items[idx] + point_categories = dataset.categories().get(AnnotationType.points) + for idx, label_cat in enumerate(label_categories.items): cat = { 'id': 1 + idx, 'name': _cast(label_cat.name, str, ''), 'supercategory': _cast(label_cat.parent, str, ''), - 'keypoints': [str(l) for l in kp_cat.labels], - 'skeleton': [int(i) for i in kp_cat.adjacent], + 'keypoints': [], + 'skeleton': [], } + + if point_categories is not None: + kp_cat = point_categories.items.get(idx) + if kp_cat is not None: + cat.update({ + 'keypoints': [str(l) for l in kp_cat.labels], + 'skeleton': [int(i) for i in kp_cat.adjacent], + }) self.categories.append(cat) def save_annotations(self, item): @@ -447,14 +451,19 @@ class _Converter: def __init__(self, extractor, save_dir, tasks=None, save_images=False, segmentation_mode=None, crop_covered=False): - assert tasks is None or isinstance(tasks, (CocoTask, list)) + assert tasks is None or isinstance(tasks, (CocoTask, list, str)) if tasks is None: tasks = list(self._TASK_CONVERTER) elif isinstance(tasks, CocoTask): tasks = [tasks] + elif isinstance(tasks, str): + tasks = [CocoTask[tasks]] else: - for t in tasks: - assert t in CocoTask + for i, t in enumerate(tasks): + if isinstance(t, str): + tasks[i] = CocoTask[t] + else: + assert t in CocoTask, t self._tasks = tasks self._extractor = extractor @@ -546,9 +555,8 @@ def convert(self): task_conv.save_annotations(item) for task, task_conv in task_converters.items(): - if not task_conv.is_empty(): - task_conv.write(osp.join(self._ann_dir, - '%s_%s.json' % (task.name, subset_name))) + task_conv.write(osp.join(self._ann_dir, + '%s_%s.json' % (task.name, subset_name))) class CocoConverter(Converter, CliPlugin): @staticmethod diff --git a/datumaro/datumaro/plugins/yolo_format/extractor.py b/datumaro/datumaro/plugins/yolo_format/extractor.py index 7840b26c5ca..11e829d4a5b 100644 --- a/datumaro/datumaro/plugins/yolo_format/extractor.py +++ b/datumaro/datumaro/plugins/yolo_format/extractor.py @@ -90,7 +90,9 @@ def __init__(self, config_path, image_info=None): subset = YoloExtractor.Subset(subset_name, self) with open(list_path, 'r') as f: subset.items = OrderedDict( - (osp.splitext(osp.basename(p))[0], p.strip()) for p in f) + (osp.splitext(osp.basename(p.strip()))[0], p.strip()) + for p in f + ) for item_id, image_path in subset.items.items(): image_path = self._make_local_path(image_path) diff --git a/datumaro/tests/test_coco_format.py b/datumaro/tests/test_coco_format.py index 2caa03a7c09..7bf7247f59a 100644 --- a/datumaro/tests/test_coco_format.py +++ b/datumaro/tests/test_coco_format.py @@ -626,10 +626,13 @@ def __iter__(self): def categories(self): label_cat = LabelCategories() + point_cat = PointsCategories() for label in range(10): label_cat.add('label_' + str(label)) + point_cat.add(label) return { AnnotationType.label: label_cat, + AnnotationType.points: point_cat, } with TestDir() as test_dir: @@ -645,4 +648,4 @@ def __iter__(self): with TestDir() as test_dir: self._test_save_and_load(TestExtractor(), - CocoConverter(), test_dir) \ No newline at end of file + CocoConverter(tasks='image_info'), test_dir) \ No newline at end of file From 267c76547fc8cfba12266dfdc780a6f7ac160e6a Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Sat, 21 Mar 2020 11:32:45 +0300 Subject: [PATCH 25/49] Add information about v0.6.1 release. --- CHANGELOG.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 483feb90381..3f15c960877 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,14 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [1.0.0-beta] - Unreleased +## [1.0.0-alpha] - Unreleased ### Added - ### Changed -- VOC task export now does not use official label map by default, but takes one - from the source task to avoid primary-class and class part name - clashing ([#1275](https://github.com/opencv/cvat/issues/1275)) +- ### Deprecated - @@ -19,6 +17,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed - +### Fixed +- + +### Security +- + +## [0.6.1] - 2020-03-21 +### Changed +- VOC task export now does not use official label map by default, but takes one + from the source task to avoid primary-class and class part name + clashing ([#1275](https://github.com/opencv/cvat/issues/1275)) + ### Fixed - File names in LabelMe format export are no longer truncated ([#1259](https://github.com/opencv/cvat/issues/1259)) - `occluded` and `z_order` annotation attributes are now correctly passed to Datumaro ([#1271](https://github.com/opencv/cvat/pull/1271)) From 796044782f45c40f842f305fd10924e8daadf4a5 Mon Sep 17 00:00:00 2001 From: Boris Sekachev <40690378+bsekachev@users.noreply.github.com> Date: Mon, 23 Mar 2020 13:15:36 +0300 Subject: [PATCH 26/49] React UI: Better exception handling (#1297) --- cvat-core/package.json | 1 + cvat-core/src/enums.js | 2 + cvat-core/src/log.js | 43 ++++ cvat-core/src/logger-storage.js | 12 +- cvat-ui/package-lock.json | 19 +- cvat-ui/package.json | 1 + cvat-ui/src/actions/annotation-actions.ts | 6 +- cvat-ui/src/actions/boundaries-actions.ts | 88 +++++++ .../attribute-annotation-sidebar.tsx | 4 +- cvat-ui/src/components/cvat-app.tsx | 79 ++++--- .../global-error-boundary.tsx | 223 ++++++++++++++++++ .../global-error-boundary/styles.scss | 17 ++ cvat-ui/src/index.tsx | 50 +++- cvat-ui/src/reducers/about-reducer.ts | 6 +- cvat-ui/src/reducers/annotation-reducer.ts | 20 +- cvat-ui/src/reducers/auth-reducer.ts | 6 +- cvat-ui/src/reducers/formats-reducer.ts | 8 +- cvat-ui/src/reducers/interfaces.ts | 3 + cvat-ui/src/reducers/models-reducer.ts | 11 +- cvat-ui/src/reducers/notifications-reducer.ts | 24 +- cvat-ui/src/reducers/settings-reducer.ts | 7 + cvat-ui/src/reducers/share-reducer.ts | 8 +- cvat-ui/src/reducers/shortcuts-reducer.ts | 11 +- cvat-ui/src/reducers/tasks-reducer.ts | 6 +- cvat-ui/src/reducers/users-reducer.ts | 8 +- cvat-ui/src/utils/redux.ts | 5 +- 26 files changed, 578 insertions(+), 90 deletions(-) create mode 100644 cvat-ui/src/actions/boundaries-actions.ts create mode 100644 cvat-ui/src/components/global-error-boundary/global-error-boundary.tsx create mode 100644 cvat-ui/src/components/global-error-boundary/styles.scss diff --git a/cvat-core/package.json b/cvat-core/package.json index d07e81aa12a..8859adc59bd 100644 --- a/cvat-core/package.json +++ b/cvat-core/package.json @@ -34,6 +34,7 @@ "dependencies": { "axios": "^0.18.0", "browser-or-node": "^1.2.1", + "detect-browser": "^5.0.0", "error-stack-parser": "^2.0.2", "form-data": "^2.5.0", "jest-config": "^24.8.0", diff --git a/cvat-core/src/enums.js b/cvat-core/src/enums.js index 8b6c86fcaab..feef4825bdb 100644 --- a/cvat-core/src/enums.js +++ b/cvat-core/src/enums.js @@ -109,6 +109,7 @@ * @memberof module:API.cvat.enums * @property {string} loadJob Load job * @property {string} saveJob Save job + * @property {string} restoreJob Restore job * @property {string} uploadAnnotations Upload annotations * @property {string} sendUserActivity Send user activity * @property {string} sendException Send exception @@ -142,6 +143,7 @@ const LogType = Object.freeze({ loadJob: 'Load job', saveJob: 'Save job', + restoreJob: 'Restore job', uploadAnnotations: 'Upload annotations', sendUserActivity: 'Send user activity', sendException: 'Send exception', diff --git a/cvat-core/src/log.js b/cvat-core/src/log.js index 56d9592d4d9..68cce5c5d5a 100644 --- a/cvat-core/src/log.js +++ b/cvat-core/src/log.js @@ -6,6 +6,7 @@ require:false */ +const { detect } = require('detect-browser'); const PluginRegistry = require('./plugins'); const { ArgumentError } = require('./exceptions'); const { LogType } = require('./enums'); @@ -179,6 +180,48 @@ class LogWithExceptionInfo extends Log { + 'It must be a number'; throw new ArgumentError(message); } + + if (typeof (this.payload.column) !== 'number') { + const message = `The field "column" is required for ${this.type} log. ` + + 'It must be a number'; + throw new ArgumentError(message); + } + + if (typeof (this.payload.stack) !== 'string') { + const message = `The field "stack" is required for ${this.type} log. ` + + 'It must be a string'; + throw new ArgumentError(message); + } + } + + dump() { + const payload = { ...this.payload }; + const client = detect(); + const body = { + client_id: payload.client_id, + name: this.type, + time: this.time.toISOString(), + message: payload.message, + filename: payload.filename, + line: payload.line, + column: payload.column, + stack: payload.stack, + system: client.os, + client: client.name, + version: client.version, + }; + + delete payload.client_id; + delete payload.message; + delete payload.filename; + delete payload.line; + delete payload.column; + delete payload.stack; + + return { + ...body, + payload, + }; } } diff --git a/cvat-core/src/logger-storage.js b/cvat-core/src/logger-storage.js index e8e24d7d0ce..ca6860af898 100644 --- a/cvat-core/src/logger-storage.js +++ b/cvat-core/src/logger-storage.js @@ -7,7 +7,7 @@ */ const PluginRegistry = require('./plugins'); -const server = require('./server-proxy'); +const serverProxy = require('./server-proxy'); const logFactory = require('./log'); const { ArgumentError } = require('./exceptions'); const { LogType } = require('./enums'); @@ -128,6 +128,14 @@ LoggerStorage.prototype.log.implementation = function (logType, payload, wait) { this.collection.push(log); }; + if (log.type === LogType.sendException) { + serverProxy.server.exception(log.dump()).catch(() => { + pushEvent(); + }); + + return log; + } + if (wait) { log.onClose(pushEvent); } else { @@ -156,7 +164,7 @@ LoggerStorage.prototype.save.implementation = async function () { const userActivityLog = logFactory(LogType.sendUserActivity, logPayload); collectionToSend.push(userActivityLog); - await server.logs.save(collectionToSend.map((log) => log.dump())); + await serverProxy.logs.save(collectionToSend.map((log) => log.dump())); for (const rule of Object.values(this.ignoreRules)) { rule.lastLog = null; diff --git a/cvat-ui/package-lock.json b/cvat-ui/package-lock.json index f2453993069..3fea00e5fcf 100644 --- a/cvat-ui/package-lock.json +++ b/cvat-ui/package-lock.json @@ -3815,6 +3815,14 @@ "is-arrayish": "^0.2.1" } }, + "error-stack-parser": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz", + "integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==", + "requires": { + "stackframe": "^1.1.1" + } + }, "es-abstract": { "version": "1.16.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.16.0.tgz", @@ -11203,6 +11211,11 @@ "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", "dev": true }, + "stackframe": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.1.1.tgz", + "integrity": "sha512-0PlYhdKh6AfFxRyK/v+6/k+/mMfyiEBbTM5L94D0ZytQnJ166wuwoTYLHFWGbs2dpA8Rgq763KGWmN1EQEYHRQ==" + }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -11981,9 +11994,9 @@ "dev": true }, "ua-parser-js": { - "version": "0.7.20", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.20.tgz", - "integrity": "sha512-8OaIKfzL5cpx8eCMAhhvTlft8GYF8b2eQr6JkCyVdrgjcytyOmPCXrqXFcUnhonRpLlh5yxEZVohm6mzaowUOw==" + "version": "0.7.21", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz", + "integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==" }, "uglify-js": { "version": "3.4.10", diff --git a/cvat-ui/package.json b/cvat-ui/package.json index 60f6aa30c61..bdf21526846 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -57,6 +57,7 @@ "antd": "^3.25.2", "copy-to-clipboard": "^3.2.0", "dotenv-webpack": "^1.7.0", + "error-stack-parser": "^2.0.6", "moment": "^2.24.0", "prop-types": "^15.7.2", "react": "^16.9.0", diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index b80c0a0f1d5..678323c62c9 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -78,10 +78,14 @@ function receiveAnnotationsParameters(): AnnotationsParameters { }; } -function computeZRange(states: any[]): number[] { +export function computeZRange(states: any[]): number[] { let minZ = states.length ? states[0].zOrder : 0; let maxZ = states.length ? states[0].zOrder : 0; states.forEach((state: any): void => { + if (state.objectType === ObjectType.TAG) { + return; + } + minZ = Math.min(minZ, state.zOrder); maxZ = Math.max(maxZ, state.zOrder); }); diff --git a/cvat-ui/src/actions/boundaries-actions.ts b/cvat-ui/src/actions/boundaries-actions.ts new file mode 100644 index 00000000000..fa2faba0308 --- /dev/null +++ b/cvat-ui/src/actions/boundaries-actions.ts @@ -0,0 +1,88 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import { + ActionUnion, + createAction, + ThunkAction, + ThunkDispatch, +} from 'utils/redux'; +import getCore from 'cvat-core'; +import { LogType } from 'cvat-logger'; +import { computeZRange } from './annotation-actions'; + +const cvat = getCore(); + +export enum BoundariesActionTypes { + RESET_AFTER_ERROR = 'RESET_AFTER_ERROR', + THROW_RESET_ERROR = 'THROW_RESET_ERROR', +} + +export const boundariesActions = { + resetAfterError: ( + job: any, + states: any[], + frameNumber: number, + frameData: any | null, + minZ: number, + maxZ: number, + colors: string[], + ) => createAction(BoundariesActionTypes.RESET_AFTER_ERROR, { + job, + states, + frameNumber, + frameData, + minZ, + maxZ, + colors, + }), + throwResetError: () => createAction(BoundariesActionTypes.THROW_RESET_ERROR), +}; + +export function resetAfterErrorAsync(): ThunkAction { + return async (dispatch: ThunkDispatch, getState): Promise => { + try { + const state = getState(); + const job = state.annotation.job.instance; + + if (job) { + const currentFrame = state.annotation.player.frame.number; + const { showAllInterpolationTracks } = state.settings.workspace; + const frameNumber = Math.max(Math.min(job.stopFrame, currentFrame), job.startFrame); + + const states = await job.annotations + .get(frameNumber, showAllInterpolationTracks, []); + const frameData = await job.frames.get(frameNumber); + const [minZ, maxZ] = computeZRange(states); + const colors = [...cvat.enums.colors]; + + await job.logger.log(LogType.restoreJob); + + dispatch(boundariesActions.resetAfterError( + job, + states, + frameNumber, + frameData, + minZ, + maxZ, + colors, + )); + } else { + dispatch(boundariesActions.resetAfterError( + null, + [], + 0, + null, + 0, + 0, + [], + )); + } + } catch (error) { + dispatch(boundariesActions.throwResetError()); + } + }; +} + +export type boundariesActions = ActionUnion; diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx index 49d3b4992cf..74cd0beca38 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx @@ -5,6 +5,8 @@ import React, { useState, useEffect } from 'react'; import { GlobalHotKeys, KeyMap } from 'react-hotkeys'; import { connect } from 'react-redux'; +import { Action } from 'redux'; +import { ThunkDispatch } from 'redux-thunk'; import Layout, { SiderProps } from 'antd/lib/layout'; import { SelectValue } from 'antd/lib/select'; import { CheckboxChangeEvent } from 'antd/lib/checkbox'; @@ -65,7 +67,7 @@ function mapStateToProps(state: CombinedState): StateToProps { }; } -function mapDispatchToProps(dispatch: any): DispatchToProps { +function mapDispatchToProps(dispatch: ThunkDispatch): DispatchToProps { return { activateObject(clientID: number, attrID: number): void { dispatch(activateObjectAction(clientID, attrID)); diff --git a/cvat-ui/src/components/cvat-app.tsx b/cvat-ui/src/components/cvat-app.tsx index bab5887a139..c168a0288ee 100644 --- a/cvat-ui/src/components/cvat-app.tsx +++ b/cvat-ui/src/components/cvat-app.tsx @@ -8,13 +8,11 @@ import React from 'react'; import { Switch, Route, Redirect } from 'react-router'; import { withRouter, RouteComponentProps } from 'react-router-dom'; import { GlobalHotKeys, KeyMap, configure } from 'react-hotkeys'; +import Spin from 'antd/lib/spin'; +import Layout from 'antd/lib/layout'; +import notification from 'antd/lib/notification'; -import { - Spin, - Layout, - notification, -} from 'antd'; - +import GlobalErrorBoundary from 'components/global-error-boundary/global-error-boundary'; import ShorcutsDialog from 'components/shortcuts-dialog/shortcuts-dialog'; import SettingsPageContainer from 'containers/settings-page/settings-page'; import TasksPageContainer from 'containers/tasks-page/tasks-page'; @@ -40,6 +38,7 @@ interface CVATAppProps { resetMessages: () => void; switchShortcutsDialog: () => void; userInitialized: boolean; + userFetching: boolean; pluginsInitialized: boolean; pluginsFetching: boolean; formatsInitialized: boolean; @@ -73,11 +72,13 @@ class CVATApplication extends React.PureComponent - - - - - - - - - - - {withModels - && } - {installedAutoAnnotation - && } - - - - {/* eslint-disable-next-line */} - - - + + + + + + + + + + + + + {withModels + && } + {installedAutoAnnotation + && } + + + + {/* eslint-disable-next-line */} + + + + ); } return ( - - - - - + + + + + + + ); } diff --git a/cvat-ui/src/components/global-error-boundary/global-error-boundary.tsx b/cvat-ui/src/components/global-error-boundary/global-error-boundary.tsx new file mode 100644 index 00000000000..3c132fd9f9b --- /dev/null +++ b/cvat-ui/src/components/global-error-boundary/global-error-boundary.tsx @@ -0,0 +1,223 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import './styles.scss'; +import React from 'react'; +import { connect } from 'react-redux'; +import { Action } from 'redux'; +import { ThunkDispatch } from 'redux-thunk'; +import Result from 'antd/lib/result'; +import Text from 'antd/lib/typography/Text'; +import Paragraph from 'antd/lib/typography/Paragraph'; +import Collapse from 'antd/lib/collapse'; +import TextArea from 'antd/lib/input/TextArea'; +import Tooltip from 'antd/lib/tooltip'; +import copy from 'copy-to-clipboard'; +import ErrorStackParser from 'error-stack-parser'; + +import { resetAfterErrorAsync } from 'actions/boundaries-actions'; +import { CombinedState } from 'reducers/interfaces'; +import logger, { LogType } from 'cvat-logger'; + +interface StateToProps { + job: any | null; + serverVersion: string; + coreVersion: string; + canvasVersion: string; + uiVersion: string; +} + +interface DispatchToProps { + restore(): void; +} + +interface State { + hasError: boolean; + error: Error | null; +} + +function mapStateToProps(state: CombinedState): StateToProps { + const { + annotation: { + job: { + instance: job, + }, + }, + about: { + server, + packageVersion, + }, + } = state; + + return { + job, + serverVersion: server.version as string, + coreVersion: packageVersion.core, + canvasVersion: packageVersion.canvas, + uiVersion: packageVersion.ui, + }; +} + +function mapDispatchToProps(dispatch: ThunkDispatch): DispatchToProps { + return { + restore(): void { + dispatch(resetAfterErrorAsync()); + }, + }; +} + + +type Props = StateToProps & DispatchToProps; +class GlobalErrorBoundary extends React.PureComponent { + public constructor(props: Props) { + super(props); + this.state = { + hasError: false, + error: null, + }; + } + + static getDerivedStateFromError(error: Error): State { + return { + hasError: true, + error, + }; + } + + public componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void { + const { job } = this.props; + const parsed = ErrorStackParser.parse(error); + + const logPayload = { + filename: parsed[0].fileName, + line: parsed[0].lineNumber, + message: error.message, + column: parsed[0].columnNumber, + stack: error.stack, + componentStack: errorInfo.componentStack, + }; + + if (job) { + job.logger.log(LogType.sendException, logPayload); + } else { + logger.log(LogType.sendException, logPayload); + } + } + + public render(): React.ReactNode { + const { + restore, + job, + serverVersion, + coreVersion, + canvasVersion, + uiVersion, + } = this.props; + + const { hasError, error } = this.state; + + const restoreGlobalState = (): void => { + this.setState({ + error: null, + hasError: false, + }); + + restore(); + }; + + if (hasError && error) { + const message = `${error.name}\n${error.message}\n\n${error.stack}`; + return ( +
      + +
      + + What has happened? + Program error has just occured + + + +