diff --git a/cvat-core/src/api-implementation.js b/cvat-core/src/api-implementation.js index 1c779b7c..72213c94 100644 --- a/cvat-core/src/api-implementation.js +++ b/cvat-core/src/api-implementation.js @@ -31,6 +31,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); @@ -116,9 +136,10 @@ // If task was found by its id, then create task instance and get Job instance from it if (tasks !== null && tasks.length) { - tasks[0].owner = await serverProxy.users.getUsers(tasks[0].owner); - tasks[0].assignee = await serverProxy.users.getUsers(tasks[0].assignee); - const task = new Task(tasks[0]); + 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; } @@ -161,13 +182,14 @@ } } - const users = await serverProxy.users.getUsers(); + const users = (await serverProxy.users.getUsers()) + .map((userData) => new User(userData)); const tasksData = await serverProxy.tasks.getTasks(searchParams.toString()); - const tasks = tasksData.map((task) => { - [task.owner] = users.filter((user) => user.id === task.owner); - [task.assignee] = users.filter((user) => user.id === task.assignee); - return 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/session.js b/cvat-core/src/session.js index 9a4ce0f5..b0bc69a8 100644 --- a/cvat-core/src/session.js +++ b/cvat-core/src/session.js @@ -14,6 +14,7 @@ const { ArgumentError } = require('./exceptions'); const { TaskStatus } = require('./enums'); const { Label } = require('./labels'); + const User = require('./user'); function buildDublicatedAPI(prototype) { Object.defineProperties(prototype, { @@ -536,19 +537,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; @@ -817,10 +818,10 @@ */ 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; @@ -957,11 +958,7 @@ } } - if (typeof (data.id) === 'undefined') { - data.labels = [...labels]; - } else { - data.labels = data.labels.concat([...labels]); - } + data.labels = [...labels]; }, }, /** @@ -1313,6 +1310,7 @@ 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, diff --git a/cvat-core/tests/api/tasks.js b/cvat-core/tests/api/tasks.js index 232bec63..627bf7e8 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-ui/.eslintrc.js b/cvat-ui/.eslintrc.js index c0c246ea..48445ed0 100644 --- a/cvat-ui/.eslintrc.js +++ b/cvat-ui/.eslintrc.js @@ -28,6 +28,7 @@ module.exports = { '@typescript-eslint/indent': ['warn', 4], '@typescript-eslint/no-explicit-any': [0], 'no-restricted-syntax': [0, {'selector': 'ForOfStatement'}], + 'no-plusplus': [0], }, 'settings': { 'import/resolver': { diff --git a/cvat-ui/package-lock.json b/cvat-ui/package-lock.json index a66a939a..5073a3ee 100644 --- a/cvat-ui/package-lock.json +++ b/cvat-ui/package-lock.json @@ -44,18 +44,18 @@ } }, "@babel/core": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.6.2.tgz", - "integrity": "sha512-l8zto/fuoZIbncm+01p8zPSDZu/VuuJhAfA7d/AbzM09WR7iVhavvfNDYCNpo1VvLk6E6xgAoP9P+/EMJHuRkQ==", + "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.2", + "@babel/generator": "^7.6.4", "@babel/helpers": "^7.6.2", - "@babel/parser": "^7.6.2", + "@babel/parser": "^7.6.4", "@babel/template": "^7.6.0", - "@babel/traverse": "^7.6.2", - "@babel/types": "^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", @@ -66,11 +66,11 @@ } }, "@babel/generator": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.2.tgz", - "integrity": "sha512-j8iHaIW4gGPnViaIHI7e9t/Hl8qLjERI6DcV9kEpAIDJsAOrcnXqRS7t+QbhL76pwbtqP+QCQLL0z1CyVmtjjQ==", + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.4.tgz", + "integrity": "sha512-jsBuXkFoZxk0yWLyGI9llT9oiQ2FeTASmRFE32U+aaDTfoE92t78eroO7PTpU/OrYq38hlcDM6vbfLDaOLy+7w==", "requires": { - "@babel/types": "^7.6.0", + "@babel/types": "^7.6.3", "jsesc": "^2.5.1", "lodash": "^4.17.13", "source-map": "^0.5.0" @@ -306,9 +306,9 @@ } }, "@babel/parser": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.2.tgz", - "integrity": "sha512-mdFqWrSPCmikBoaBYMuBulzTIKuXVPtEISFbRRVNwMWpCms/hmE2kRq0bblUHaNRKrjRlmVbx1sDHmjmRgD2Xg==" + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.4.tgz", + "integrity": "sha512-D8RHPW5qd0Vbyo3qb+YjO5nvUVRTXFLQ/FsDxJU2Nqz4uB5EnUN0ZQSEYpvTIbRuttig1XbHWU5oMeQwQSAA+A==" }, "@babel/plugin-proposal-async-generator-functions": { "version": "7.2.0", @@ -474,9 +474,9 @@ } }, "@babel/plugin-transform-block-scoping": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.6.2.tgz", - "integrity": "sha512-zZT8ivau9LOQQaOGC7bQLQOT4XPkPXgN2ERfUgk1X8ql+mVkLc4E8eKk+FO3o0154kxzqenWCorfmEXpEZcrSQ==", + "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", @@ -629,9 +629,9 @@ } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.6.2.tgz", - "integrity": "sha512-xBdB+XOs+lgbZc2/4F5BVDVcDNS4tcSKQc96KmlqLEAwz6tpYPEvPdmDfvVG0Ssn8lAhronaRs6Z6KSexIpK5g==", + "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": { "regexpu-core": "^4.6.0" @@ -782,9 +782,9 @@ } }, "@babel/plugin-transform-typescript": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.6.0.tgz", - "integrity": "sha512-yzw7EopOOr6saONZ3KA3lpizKnWRTe+rfBqg4AmQbSow7ik7fqmzrfIqt053osLwLE2AaTqGinLM2tl6+M/uog==", + "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", @@ -804,9 +804,9 @@ } }, "@babel/preset-env": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.6.2.tgz", - "integrity": "sha512-Ru7+mfzy9M1/YTEtlDS8CD45jd22ngb9tXnn64DvQK3ooyqSw9K4K9DUWmYknTTVk4TqygL9dqCrZgm1HMea/Q==", + "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", @@ -825,7 +825,7 @@ "@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.6.2", + "@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.6.0", @@ -840,7 +840,7 @@ "@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.6.2", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.6.3", "@babel/plugin-transform-new-target": "^7.4.4", "@babel/plugin-transform-object-super": "^7.5.5", "@babel/plugin-transform-parameters": "^7.4.4", @@ -853,7 +853,7 @@ "@babel/plugin-transform-template-literals": "^7.4.4", "@babel/plugin-transform-typeof-symbol": "^7.2.0", "@babel/plugin-transform-unicode-regex": "^7.6.2", - "@babel/types": "^7.6.0", + "@babel/types": "^7.6.3", "browserslist": "^4.6.0", "core-js-compat": "^3.1.1", "invariant": "^2.2.2", @@ -862,9 +862,9 @@ } }, "@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", @@ -885,9 +885,9 @@ } }, "@babel/runtime": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.2.tgz", - "integrity": "sha512-EXxN64agfUqqIGeEjI5dL5z0Sw0ZwWo1mLTi4mQowCZ42O59b7DRpZAnTC6OqdF28wMBMFKNb/4uFGrVaigSpg==", + "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" }, @@ -910,25 +910,25 @@ } }, "@babel/traverse": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.2.tgz", - "integrity": "sha512-8fRE76xNwNttVEF2TwxJDGBLWthUkHWSldmfuBzVRmEDWOtu4XdINTgN7TDWzuLg4bbeIMLvfMFD9we5YcWkRQ==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.3.tgz", + "integrity": "sha512-unn7P4LGsijIxaAJo/wpoU11zN+2IaClkQAxcJWBNCMS6cmVh802IyLHNkAjQ0iYnRS3nnxk5O3fuXW28IMxTw==", "requires": { "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.6.2", + "@babel/generator": "^7.6.3", "@babel/helper-function-name": "^7.1.0", "@babel/helper-split-export-declaration": "^7.4.4", - "@babel/parser": "^7.6.2", - "@babel/types": "^7.6.0", + "@babel/parser": "^7.6.3", + "@babel/types": "^7.6.3", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.13" } }, "@babel/types": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", - "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.3.tgz", + "integrity": "sha512-CqbcpTxMcpuQTMhjI37ZHVgjBkysg5icREQIEZ0eG1yCNwg3oy+5AaLiOKmjsCj6nqOsa6Hf0ObjRVwokb7srA==", "requires": { "esutils": "^2.0.2", "lodash": "^4.17.13", @@ -985,9 +985,9 @@ "dev": true }, "@types/node": { - "version": "12.7.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.8.tgz", - "integrity": "sha512-FMdVn84tJJdV+xe+53sYiZS4R5yn1mAIxfj+DVoNiQjTYz1+OYmjwEZr1ev9nU0axXwda0QDbYl06QHanRVH3A==", + "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": { @@ -996,26 +996,26 @@ "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" }, "@types/react": { - "version": "16.9.3", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.3.tgz", - "integrity": "sha512-Ogb2nSn+2qQv5opoCv7Ls5yFxtyrdUYxp5G+SWTrlGk7dmFKw331GiezCgEZj9U7QeXJi1CDtws9pdXU1zUL4g==", + "version": "16.9.11", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.11.tgz", + "integrity": "sha512-UBT4GZ3PokTXSWmdgC/GeCGEJXE5ofWyibCcecRLUVN2ZBpXQGVgQGtG2foS7CrTKFKlQVVswLvf7Js6XA/CVQ==", "requires": { "@types/prop-types": "*", "csstype": "^2.2.0" } }, "@types/react-dom": { - "version": "16.9.1", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.1.tgz", - "integrity": "sha512-1S/akvkKr63qIUWVu5IKYou2P9fHLb/P2VAwyxVV85JGaGZTcUniMiTuIqM3lXFB25ej6h+CYEQ27ERVwi6eGA==", + "version": "16.9.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.3.tgz", + "integrity": "sha512-FUuZKXPr9qlzUT9lhuzrZgLjH63TvNn28Ch3MvKG4B+F52zQtO8DtE0Opbncy3xaucNZM2WIPfuNTgkbKx5Brg==", "requires": { "@types/react": "*" } }, "@types/react-redux": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.3.tgz", - "integrity": "sha512-coaQFfn6XrHF/4CSF8eBM9rUbi5TX6qVhS+KF89Z2nC9YdTKTckOpJLJyQocYLrXF0IFX+/ZUJwMvbZ0nNmkDg==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.5.tgz", + "integrity": "sha512-ZoNGQMDxh5ENY7PzU7MVonxDzS1l/EWiy8nUhDqxFqUZn4ovboCyvk4Djf68x6COb7vhGTKjyjxHxtFdAA5sUA==", "requires": { "@types/hoist-non-react-statics": "^3.3.0", "@types/react": "*", @@ -1024,18 +1024,18 @@ } }, "@types/react-router": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.0.tgz", - "integrity": "sha512-XqxaIqG+LJTh9wsGpZCVQNOAQyEjXcfYRqoIXEFqxc49BKnmvJ5FLylsNUUCTckSffD468cOn4NJvxcWuLwiDw==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.2.tgz", + "integrity": "sha512-euC3SiwDg3NcjFdNmFL8uVuAFTpZJm0WMFUw+4eXMUnxa7M9RGFEG0szt0z+/Zgk4G2k9JBFhaEnY64RBiFmuw==", "requires": { "@types/history": "*", "@types/react": "*" } }, "@types/react-router-dom": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.1.0.tgz", - "integrity": "sha512-YCh8r71pL5p8qDwQf59IU13hFy/41fDQG/GeOI3y+xmD4o0w3vEPxE8uBe+dvOgMoDl0W1WUZsWH0pxc1mcZyQ==", + "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": { "@types/history": "*", "@types/react": "*", @@ -1382,9 +1382,9 @@ } }, "antd": { - "version": "3.23.4", - "resolved": "https://registry.npmjs.org/antd/-/antd-3.23.4.tgz", - "integrity": "sha512-WmumOVZ7GSbLkiQyL54edRBDW4AVhFROlNJbd0lajonP2i2uzgC/2B1pZbnY9EE2yjv9tY7MiqlkBT7HLu75cw==", + "version": "3.24.3", + "resolved": "https://registry.npmjs.org/antd/-/antd-3.24.3.tgz", + "integrity": "sha512-yr4kV8lUdYNCOj5+TjIufLGYF0naTfNiAJV0JWqh9RzRGapGVES928K/2gyF7Ow3da6ZlsgxtO1P25exbvDlSw==", "requires": { "@ant-design/create-react-context": "^0.2.4", "@ant-design/icons": "~2.1.1", @@ -1408,29 +1408,30 @@ "rc-checkbox": "~2.1.6", "rc-collapse": "~1.11.3", "rc-dialog": "~7.5.2", - "rc-drawer": "~2.0.1", + "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.5.0", "rc-mentions": "~0.4.0", - "rc-menu": "~7.4.23", + "rc-menu": "~7.5.1", "rc-notification": "~3.3.1", "rc-pagination": "~1.20.5", "rc-progress": "~2.5.0", "rc-rate": "~2.5.0", + "rc-resize-observer": "^0.1.0", "rc-select": "~9.2.0", - "rc-slider": "~8.6.11", + "rc-slider": "~8.7.1", "rc-steps": "~3.5.0", "rc-switch": "~1.9.0", - "rc-table": "~6.7.0", + "rc-table": "~6.9.4", "rc-tabs": "~9.6.4", "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.7.0", + "rc-upload": "~2.8.0", "rc-util": "^4.10.0", "react-lazy-load": "^3.0.13", "react-lifecycles-compat": "^3.0.4", @@ -1593,10 +1594,13 @@ "dev": true }, "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true + "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", @@ -1657,6 +1661,16 @@ "object.assign": "^4.1.0" } }, + "babel-plugin-import": { + "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-runtime": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", @@ -1752,9 +1766,9 @@ "dev": true }, "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": { @@ -1964,14 +1978,14 @@ } }, "browserslist": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.7.0.tgz", - "integrity": "sha512-9rGNDtnj+HaahxiVV38Gn8n8Lr8REKsel68v1sPFfIGEK6uSXTY3h9acgiT1dZVtOOUtifo/Dn8daDQ5dUgVsA==", + "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": { - "caniuse-lite": "^1.0.30000989", - "electron-to-chromium": "^1.3.247", - "node-releases": "^1.1.29" + "caniuse-lite": "^1.0.30001004", + "electron-to-chromium": "^1.3.295", + "node-releases": "^1.1.38" } }, "buffer": { @@ -2056,9 +2070,9 @@ } }, "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": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true } } @@ -2097,9 +2111,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30000997", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000997.tgz", - "integrity": "sha512-BQLFPIdj2ntgBNWp9Q64LGUIEmvhKkzzHhUHR3CD5A9Lb7ZKF20/+sgadhFap69lk5XmK1fTUleDclaRFvgVUA==", + "version": "1.0.30001006", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001006.tgz", + "integrity": "sha512-MXnUVX27aGs/QINz+QG1sWSLDr3P1A3Hq5EUWoIt0T7K24DuvMxZEnh3Y5aHlJW6Bz2aApJdSewdYLd8zQnUuw==", "dev": true }, "capture-stack-trace": { @@ -2289,9 +2303,9 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "commander": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", - "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "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": { @@ -2454,13 +2468,10 @@ "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" - } + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true }, "constants-browserify": { "version": "1.0.0", @@ -2539,17 +2550,17 @@ } }, "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.2.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.2.1.tgz", - "integrity": "sha512-MwPZle5CF9dEaMYdDeWm73ao/IflDH+FjeJCWEADcEgFSE9TLimFKwJsfmkwzI8eC0Aj0mgvMDjeQjrElkz4/A==", + "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.6", + "browserslist": "^4.7.2", "semver": "^6.3.0" }, "dependencies": { @@ -2713,9 +2724,9 @@ "dev": true }, "csstype": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.6.tgz", - "integrity": "sha512-RpFbQGUE74iyPgvr46U9t1xoQBM8T4BL8SxrN66Le2xYAPSaDJJKeztV3awugusb3g3G9iL8StmkBBXhcbbXhg==" + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.7.tgz", + "integrity": "sha512-9Mcn9sFbGBAdmimWb2gLVDtFJzeKtDGIr76TUqmjZrw9LFXBMSU70lcs+C0/7fyCd6iBDqmksUcCOUIkisPHsQ==" }, "cyclist": { "version": "1.0.1", @@ -2729,12 +2740,6 @@ "integrity": "sha512-CBCRqFnpu715iPmw1KrdOrzRqbdFwQTwAWyyyYS42+iAgHCuXZ+/TdMgQkUENPomxEz9z1BEzuQU2Xw0kUuAgA==", "dev": true }, - "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": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -3155,9 +3160,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.266", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.266.tgz", - "integrity": "sha512-UTuTZ4v8T0gLPHI7U75PXLQePWI65MTS3mckRrnLCkNljHvsutbYs+hn2Ua/RFul3Jt/L3Ht2rLP+dU/AlBfrQ==", + "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": { @@ -3211,14 +3216,56 @@ } }, "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": { @@ -3251,9 +3298,9 @@ } }, "es-abstract": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.14.2.tgz", - "integrity": "sha512-DgoQmbpFNOofkjJtKwr87Ma5EW4Dc8fWhD0R+ndq7Oc456ivUfGOOP6oAZTTKl5/CcNMP+EN+e3/iUzgE0veZg==", + "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", @@ -3264,8 +3311,8 @@ "is-regex": "^1.0.4", "object-inspect": "^1.6.0", "object-keys": "^1.1.1", - "string.prototype.trimleft": "^2.0.0", - "string.prototype.trimright": "^2.0.0" + "string.prototype.trimleft": "^2.1.0", + "string.prototype.trimright": "^2.1.0" } }, "es-to-primitive": { @@ -3483,20 +3530,20 @@ } }, "eslint-plugin-react": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.14.3.tgz", - "integrity": "sha512-EzdyyBWC4Uz2hPYBiEJrKCUi2Fn+BJ9B/pJQcjw5X+x/H2Nm59S4MJIvL4O5NEE0+WbnQwEBxWY03oUk+Bc3FA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.16.0.tgz", + "integrity": "sha512-GacBAATewhhptbK3/vTP09CbFrgUJmBSaaRcWdbQLFvUZy9yVcQxigBNHGPU/KE2AyHpzj3AWXpxoMTsIDiHug==", "dev": true, "requires": { "array-includes": "^3.0.3", "doctrine": "^2.1.0", "has": "^1.0.3", - "jsx-ast-utils": "^2.1.0", + "jsx-ast-utils": "^2.2.1", "object.entries": "^1.1.0", "object.fromentries": "^2.0.0", "object.values": "^1.1.0", "prop-types": "^15.7.2", - "resolve": "^1.10.1" + "resolve": "^1.12.0" }, "dependencies": { "doctrine": { @@ -3521,12 +3568,12 @@ } }, "eslint-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.2.tgz", - "integrity": "sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==", + "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.0.0" + "eslint-visitor-keys": "^1.1.0" } }, "eslint-visitor-keys": { @@ -4719,9 +4766,9 @@ "dev": true }, "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", @@ -4843,9 +4890,9 @@ } }, "graceful-fs": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", - "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", + "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": { @@ -4984,9 +5031,9 @@ } }, "hosted-git-info": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.4.tgz", - "integrity": "sha512-pzXIvANXEFrc5oFFXRMkbLPQ2rXRoDERwDLyrcUxGhaZhgP54BBSl9Oheh7Vv0T090cszWBxPjkQQ5Sq1PbBRQ==", + "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": { @@ -5332,9 +5379,9 @@ "dev": true }, "is-absolute-url": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.2.tgz", - "integrity": "sha512-+5g/wLlcm1AcxSP7014m6GvbPHswDx980vD/3bZaap8aGV9Yfs7Q6y6tfaupgZ5O74Byzc8dGrSCJ+bFXx0KdA==", + "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": { @@ -5615,11 +5662,6 @@ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, - "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", @@ -5678,18 +5720,18 @@ "dev": true }, "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==", + "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" } }, "jsx-ast-utils": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.1.tgz", - "integrity": "sha512-v3FxCcAf20DayI+uxnCuw795+oOIkVu6EnJ1+kSzhqqTZHNkTZ7B66ZgLp4oLJ/gbA64cI0B7WRoHZMSRdyVRQ==", + "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", @@ -6265,9 +6307,9 @@ } }, "node-forge": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.8.2.tgz", - "integrity": "sha512-mXQ9GBq1N3uDCyV1pdSzgIguwgtVpM7f5/5J4ipz12PKWElmPpVWLDuWl8iXmhysr21+WmX/OJ5UKx82wjomgg==", + "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-libs-browser": { @@ -6342,27 +6384,35 @@ } }, "node-releases": { - "version": "1.1.32", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.32.tgz", - "integrity": "sha512-VhVknkitq8dqtWoluagsGPn3dxTvN9fwgR59fV3D7sLBHe0JfDramsMI8n8mY//ccq/Kkrf8ZRHRpsyVZ3qw1A==", + "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 + } } }, "nodemon": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.19.2.tgz", - "integrity": "sha512-hRLYaw5Ihyw9zK7NF+9EUzVyS6Cvgc14yh8CAYr38tPxJa6UrOxwAQ351GwrgoanHCF0FalQFn6w5eoX/LGdJw==", + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.19.4.tgz", + "integrity": "sha512-VGPaqQBNk193lrJFotBU8nvWZPqEZY2eIzymy2jjY0fJ9qIsxA0sxQ8ATPl0gZC645gijYEc1jtZvpS8QWzJGQ==", "dev": true, "requires": { - "chokidar": "^2.1.5", - "debug": "^3.1.0", + "chokidar": "^2.1.8", + "debug": "^3.2.6", "ignore-by-default": "^1.0.1", "minimatch": "^3.0.4", - "pstree.remy": "^1.1.6", - "semver": "^5.5.0", - "supports-color": "^5.2.0", + "pstree.remy": "^1.1.7", + "semver": "^5.7.1", + "supports-color": "^5.5.0", "touch": "^3.1.0", "undefsafe": "^2.0.2", "update-notifier": "^2.5.0" @@ -6518,15 +6568,15 @@ } }, "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": { @@ -6956,30 +7006,24 @@ } }, "portfinder": { - "version": "1.0.24", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.24.tgz", - "integrity": "sha512-ekRl7zD2qxYndYflwiryJwMioBI7LI7rVXg3EnLK3sjkouT5eOuhS3gS255XxBksa30VG8UPZYZCdgfGOfkSUg==", + "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=", - "dev": true } } }, @@ -6990,9 +7034,9 @@ "dev": true }, "postcss": { - "version": "7.0.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.18.tgz", - "integrity": "sha512-/7g1QXXgegpF+9GJj4iN7ChGF40sYuGYJ8WZu8DZWnmhQ/G36hfdk3q9LBJmoK+lZ+yzZ5KYpOoxq7LF1BxE8g==", + "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", @@ -7315,9 +7359,9 @@ } }, "rc-animate": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/rc-animate/-/rc-animate-2.10.0.tgz", - "integrity": "sha512-gZM3WteZO0e3X8B71KP0bs95EY2tAPRuiZyKnlhdLpOjTX/64SrhDZM3pT2Z8mJjKWNiiB5q2SSSf+BD8ljwVw==", + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/rc-animate/-/rc-animate-2.10.1.tgz", + "integrity": "sha512-yfP3g5fNf8wB5eh85nim2IGrqNu5u7TKrrSh710+1vlUqZvnI2R5YHK99IBCQNgkLCAWjT0sHtkcYdynjly39w==", "requires": { "babel-runtime": "6.x", "classnames": "^2.2.6", @@ -7329,9 +7373,9 @@ } }, "rc-calendar": { - "version": "9.15.5", - "resolved": "https://registry.npmjs.org/rc-calendar/-/rc-calendar-9.15.5.tgz", - "integrity": "sha512-nvoEXk5P0DADt5b7FHlKiXKj+IhoWawQGSkb5soa6gXQIfoqQJ5+zB2Ogy7k1RxNbxQu4iIkEW/a3+HObVRDdA==", + "version": "9.15.6", + "resolved": "https://registry.npmjs.org/rc-calendar/-/rc-calendar-9.15.6.tgz", + "integrity": "sha512-TJD4cUXsBAjyCzo7BaGb86nZyJetBUt/Rpu0H1WWhp9AJc+Tl7aj7TCD3TM5Y8Ak/yxsA8WDBMuKw1XdQMsM5g==", "requires": { "babel-runtime": "6.x", "classnames": "2.x", @@ -7382,9 +7426,9 @@ } }, "rc-dialog": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-7.5.7.tgz", - "integrity": "sha512-hSKzxdbkWylenjdyNwUPz2Wb4pkmpFld/Qp7u5uhXhlLUTUjQceCj+VFXHWKfBGlesm34SD4wNl4ZvyEYIAdNA==", + "version": "7.5.12", + "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-7.5.12.tgz", + "integrity": "sha512-FsZQfHBXYjBwuxUN9Cd+0n7YRSyemDtRJ9jX2a1HvIf4ajBJK9WVUiWm2+K1vZBZOciA+jm6gQETqyXzDKnwzQ==", "requires": { "babel-runtime": "6.x", "rc-animate": "2.x", @@ -7392,13 +7436,13 @@ } }, "rc-drawer": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-2.0.9.tgz", - "integrity": "sha512-7qwEND3TLvJeyuUvZfMDkL2pHsR/XHX5HvoaBlIH9mTcFWBmMNrvYGDuGHgGsdNKZZgIBwlkvl5vhckydTUc9Q==", + "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", - "rc-util": "^4.7.0", + "babel-runtime": "^6.26.0", + "classnames": "^2.2.6", + "rc-util": "^4.11.2", "react-lifecycles-compat": "^3.0.4" } }, @@ -7444,9 +7488,9 @@ } }, "rc-form": { - "version": "2.4.8", - "resolved": "https://registry.npmjs.org/rc-form/-/rc-form-2.4.8.tgz", - "integrity": "sha512-hlHajcYg51pFQf+B6neAbhy2ZA+8DmxnDxiOYZRAXCLhPN788ZnrtZq5/iADDWcZqjHFnXiThoZE/Fu8syciDQ==", + "version": "2.4.9", + "resolved": "https://registry.npmjs.org/rc-form/-/rc-form-2.4.9.tgz", + "integrity": "sha512-uu6wtJqSQWTFOgv1NcYJIPf7TlJHmQHbDJTBQQuQsKKap8CiW6aeAfvOZpThQuWwV/NeznP4WKeOJurIw4zzlA==", "requires": { "async-validator": "~1.11.3", "babel-runtime": "6.x", @@ -7468,9 +7512,9 @@ } }, "rc-input-number": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-4.5.0.tgz", - "integrity": "sha512-0igTdXuxVykBB82jafUmhbRVmgtd0FuGSIX4SbrynGNrLDOyze3EUKsZl+LyQ4JMRXLrcuZKJg385880RVLA2w==", + "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", @@ -7480,9 +7524,9 @@ } }, "rc-mentions": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-0.4.0.tgz", - "integrity": "sha512-xnkQBTUFp4llaJuDOLVFKX9ELrXFHk1FuUdIIC/ijQ6cLjDhCUu+jpHNcXWuQ/yIFzF376VlXkmT57iqxSnZzw==", + "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", "classnames": "^2.2.6", @@ -7493,21 +7537,19 @@ } }, "rc-menu": { - "version": "7.4.29", - "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-7.4.29.tgz", - "integrity": "sha512-KayRzEWUtDnrOnookEgoZ4xyj34H9eONwcHLIbmR2xUqQKr/o64RgPk1gRVA4vLIxPscPR1gD16nxgwLk8nNgQ==", + "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": { @@ -7523,9 +7565,9 @@ } }, "rc-pagination": { - "version": "1.20.7", - "resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-1.20.7.tgz", - "integrity": "sha512-I548/cVC26L0CvZzGNewcoMPwGAxeQxuLL7jS+U6rF3ZYX3A1j7B88O3JoPM6L3rj+s2UJbcZn3XqUA2FKaDEQ==", + "version": "1.20.9", + "resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-1.20.9.tgz", + "integrity": "sha512-X/y2kZWrUyX/x7Ncbh/KrcPxStMuQTytqx4XPsla5ub881wGpiCdiVJxfhlqlVlqJmXRsxLYAcn8Vbi8pmmjKA==", "requires": { "babel-runtime": "6.x", "classnames": "^2.2.6", @@ -7553,6 +7595,16 @@ "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.2.1", "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-9.2.1.tgz", @@ -7573,16 +7625,17 @@ } }, "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" } }, @@ -7608,30 +7661,18 @@ } }, "rc-table": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-6.7.0.tgz", - "integrity": "sha512-zzu7UtEHLTzZibB1EOoeKQejH21suoxRQx3evlGGLwz5NUh2HDUHobSr12z5Kd8EPr1+y/LPzXJdX1ctFPC+hA==", + "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": { @@ -7676,9 +7717,9 @@ } }, "rc-tree": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-2.1.2.tgz", - "integrity": "sha512-IQG0bkY4bfK11oVIF44Y4V3IuIOAmIIc5j8b8XGkRjsnUOElRr/BNqKCvg9h2UsNJm1J2xv4OA0HfEIv70765Q==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-2.1.3.tgz", + "integrity": "sha512-COvV65spQ6omrHBUhHRKqKNL5+ddXjlS+qWZchaL9FFuQNvjM5pjp9RnmMWK4fJJ5kBhhpLneh6wh9Vh3kSMXQ==", "requires": { "@ant-design/create-react-context": "^0.2.4", "classnames": "2.x", @@ -7779,9 +7820,9 @@ } }, "rc-upload": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-2.7.0.tgz", - "integrity": "sha512-Oh9EJB4xE8MQUZ2D0OUST3UMIBjHjnO2IjPNW/cbPredxZz+lzbLPCZxcxRwUwu1gt0LA968UWXAgT1EvZdFfA==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-2.8.1.tgz", + "integrity": "sha512-FmKZgWsgOyKeZuperDjHrj8Qx5fdQqYuNpmDR50AP7Za87o8QsRvCbIKG2pgQ9MpNkUbiQOV15FqlQBl2WisfQ==", "requires": { "babel-runtime": "6.x", "classnames": "^2.2.5", @@ -7790,9 +7831,9 @@ } }, "rc-util": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.11.0.tgz", - "integrity": "sha512-nB29kXOXsSVjBkWfH+Z1GVh6tRg7XGZtZ0Yfie+OI0stCDixGQ1cPrS6iYxlg+AV2St6COCK5MFrCmpTgghh0w==", + "version": "4.14.4", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.14.4.tgz", + "integrity": "sha512-GQgEn6ywJYZq1NEoZ6NZzeaE2U6mT6DhdqrtRV5IBNM3yTZZW8HRjIiMOpXOhTEUj10bnHnKWKZpC36RoNmS9Q==", "requires": { "add-dom-event-listener": "^1.1.0", "babel-runtime": "6.x", @@ -7812,9 +7853,9 @@ } }, "react": { - "version": "16.9.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.9.0.tgz", - "integrity": "sha512-+7LQnFBwkiw+BobzOF6N//BdoNw0ouwmSJTEm9cglOOmsg/TMiFHZLe2sEoN5M7LgJTj9oHH0gxklfnQe66S1w==", + "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", @@ -7822,20 +7863,20 @@ } }, "react-dom": { - "version": "16.9.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.9.0.tgz", - "integrity": "sha512-YFT2rxO9hM70ewk9jq0y6sQk8cL02xm4+IzYBz75CQGlClQQ1Bxq0nhHF6OtSbit+AIahujJgb/CPRibFkMNJQ==", + "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.15.0" + "scheduler": "^0.17.0" } }, "react-is": { - "version": "16.9.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.9.0.tgz", - "integrity": "sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw==" + "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", @@ -7867,9 +7908,9 @@ } }, "react-router": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.1.0.tgz", - "integrity": "sha512-n9HXxaL/6yRlig9XPfGyagI8+bUNdqcu7FUAx0/Z+Us22Z8iHsbkyJ21Inebn9HOxI5Nxlfc8GNabkNSeXfhqw==", + "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", @@ -7884,15 +7925,15 @@ } }, "react-router-dom": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.1.0.tgz", - "integrity": "sha512-OkxKbMKjO7IkYqnoaZNX19MnwgjhxwZE871cPUTq0YU2wpIw7QwGxSnSoNRMOa7wO1TwvJJMFpgiEB4C/gVhTw==", + "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.1.0", + "react-router": "5.1.2", "tiny-invariant": "^1.0.2", "tiny-warning": "^1.0.0" } @@ -8131,9 +8172,9 @@ } }, "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": { @@ -8341,18 +8382,18 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "scheduler": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.15.0.tgz", - "integrity": "sha512-xAefmSfN6jqAa7Kuq7LIJY0bwAPG3xlCj0HMEBQk1lxYiDKZscY2xJ5U/61ZTrYbmNQbXa+gc7czPkVo11tnCg==", + "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": "2.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.2.0.tgz", - "integrity": "sha512-5EwsCNhfFTZvUreQhx/4vVQpJ/lnCAkgoIHLhSpp4ZirE+4hzFvdJi0FMub6hxbFVBJYSpeVVmon+2e7uEGRrA==", + "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.10.2", @@ -8366,12 +8407,12 @@ "dev": true }, "selfsigned": { - "version": "1.10.6", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.6.tgz", - "integrity": "sha512-i3+CeqxL7DpAazgVpAGdKMwHuL63B5nhJMh9NQ7xmChGkA3jNFflq6Jyo1LLJYcr3idWiNOPWHCrm4zMayLG4w==", + "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.8.2" + "node-forge": "0.9.0" } }, "semver": { @@ -8781,9 +8822,9 @@ } }, "source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "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", @@ -9146,9 +9187,9 @@ } }, "terser": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.3.2.tgz", - "integrity": "sha512-obxk4x19Zlzj9zY4QeXj9iPCb5W8YGn4v3pn4/fHj0Nw8+R7N02Kvwvz9VpOItCZZD8RC+vnYCDL0gP6FAJ7Xg==", + "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.20.0", @@ -9243,9 +9284,9 @@ } }, "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 }, "timed-out": { @@ -9395,9 +9436,9 @@ "dev": true }, "typescript": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.3.tgz", - "integrity": "sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==", + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.4.tgz", + "integrity": "sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg==", "dev": true }, "ua-parser-js": { @@ -9787,9 +9828,9 @@ } }, "webpack": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.41.0.tgz", - "integrity": "sha512-yNV98U4r7wX1VJAj5kyMsu36T8RPPQntcb5fJLOsMz/pt/WrKC0Vp1bAlqPLkA1LegSwQwf6P+kAbyhRKVQ72g==", + "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", @@ -9831,9 +9872,9 @@ } }, "webpack-cli": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.9.tgz", - "integrity": "sha512-xwnSxWl8nZtBl/AFJCOn9pG7s5CYUYdZxmmukv+fAHLcBIHM36dImfpQg3WfShZXeArkWlf6QRw24Klcsv8a5A==", + "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", @@ -9862,6 +9903,17 @@ "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", @@ -9874,9 +9926,9 @@ } }, "webpack-dev-middleware": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.1.tgz", - "integrity": "sha512-5MWu9SH1z3hY7oHOV6Kbkz5x7hXbxK56mGHNqHTe6d+ewxOwKUxoUJBs7QIaJb33lPjl9bJZ3X0vCoooUzC36A==", + "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", @@ -9895,9 +9947,9 @@ } }, "webpack-dev-server": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.8.1.tgz", - "integrity": "sha512-9F5DnfFA9bsrhpUCAfQic/AXBVHvq+3gQS+x6Zj0yc1fVVE0erKh2MV4IV12TBewuTrYeeTIRwCH9qLMvdNvTw==", + "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", @@ -9909,18 +9961,18 @@ "del": "^4.1.1", "express": "^4.17.1", "html-entities": "^1.2.1", - "http-proxy-middleware": "^0.19.1", + "http-proxy-middleware": "0.19.1", "import-local": "^2.0.0", "internal-ip": "^4.3.0", "ip": "^1.1.5", - "is-absolute-url": "^3.0.2", + "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.24", + "portfinder": "^1.0.25", "schema-utils": "^1.0.0", - "selfsigned": "^1.10.6", + "selfsigned": "^1.10.7", "semver": "^6.3.0", "serve-index": "^1.9.1", "sockjs": "0.3.19", @@ -9929,7 +9981,7 @@ "strip-ansi": "^3.0.1", "supports-color": "^6.1.0", "url": "^0.11.0", - "webpack-dev-middleware": "^3.7.1", + "webpack-dev-middleware": "^3.7.2", "webpack-log": "^2.0.0", "ws": "^6.2.1", "yargs": "12.0.5" diff --git a/cvat-ui/package.json b/cvat-ui/package.json index 7dd1329c..441b4b5d 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -17,6 +17,7 @@ "@typescript-eslint/eslint-plugin": "^1.13.0", "babel": "^6.23.0", "babel-loader": "^8.0.6", + "babel-plugin-import": "^1.12.2", "css-loader": "^3.2.0", "eslint-config-airbnb-typescript": "^4.0.1", "eslint-plugin-import": "^2.18.2", @@ -26,7 +27,7 @@ "nodemon": "^1.19.2", "style-loader": "^1.0.0", "typescript": "^3.6.3", - "webpack": "^4.39.3", + "webpack": "^4.41.2", "webpack-cli": "^3.3.8", "webpack-dev-server": "^3.8.0" }, @@ -37,7 +38,7 @@ "@types/react-redux": "^7.1.2", "@types/react-router": "^5.0.5", "@types/react-router-dom": "^5.1.0", - "antd": "^3.23.2", + "antd": "^3.24.2", "dotenv-webpack": "^1.7.0", "moment": "^2.24.0", "prop-types": "^15.7.2", diff --git a/cvat-ui/src/actions/plugins-actions.ts b/cvat-ui/src/actions/plugins-actions.ts new file mode 100644 index 00000000..89c04d03 --- /dev/null +++ b/cvat-ui/src/actions/plugins-actions.ts @@ -0,0 +1,45 @@ +import { AnyAction, Dispatch, ActionCreator } from 'redux'; +import { ThunkAction } from 'redux-thunk'; +import { SupportedPlugins } from '../reducers/interfaces'; +import PluginChecker from '../utils/plugin-checker'; + +export enum PluginsActionTypes { + CHECKED_ALL_PLUGINS = 'CHECKED_ALL_PLUGINS' +} + +interface PluginObjects { + [plugin: string]: boolean; +} + +function checkedAllPlugins(plugins: PluginObjects): AnyAction { + const action = { + type: PluginsActionTypes.CHECKED_ALL_PLUGINS, + payload: { + plugins, + }, + }; + + return action; +} + +export function checkPluginsAsync(): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + const plugins: PluginObjects = {}; + + const promises: Promise[] = []; + const keys = Object.keys(SupportedPlugins); + for (const key of keys) { + const plugin = SupportedPlugins[key as any]; + promises.push(PluginChecker.check(plugin as SupportedPlugins)); + } + + const values = await Promise.all(promises); + let i = 0; + for (const key of keys) { + plugins[key] = values[i++]; + } + + dispatch(checkedAllPlugins(plugins)); + }; +} diff --git a/cvat-ui/src/actions/task-actions.ts b/cvat-ui/src/actions/task-actions.ts new file mode 100644 index 00000000..82bc1adf --- /dev/null +++ b/cvat-ui/src/actions/task-actions.ts @@ -0,0 +1,120 @@ +import { AnyAction, Dispatch, ActionCreator } from 'redux'; +import { ThunkAction } from 'redux-thunk'; + +import getCore from '../core'; + +const core = getCore(); + +export enum TaskActionTypes { + GET_TASK = 'GET_TASK', + GET_TASK_SUCCESS = 'GET_TASK_SUCCESS', + GET_TASK_FAILED = 'GET_TASK_FAILED', + UPDATE_TASK = 'UPDATE_TASK', + UPDATE_TASK_SUCCESS = 'UPDATE_TASK_SUCCESS', + UPDATE_TASK_FAILED = 'UPDATE_TASK_FAILED', +} + +function getTask(): AnyAction { + const action = { + type: TaskActionTypes.GET_TASK, + payload: {}, + }; + + return action; +} + +function getTaskSuccess(taskInstance: any, previewImage: string): AnyAction { + const action = { + type: TaskActionTypes.GET_TASK_SUCCESS, + payload: { + taskInstance, + previewImage, + }, + }; + + return action; +} + +function getTaskFailed(error: any): AnyAction { + const action = { + type: TaskActionTypes.GET_TASK_FAILED, + payload: { + error, + }, + }; + + return action; +} + +export function getTaskAsync(tid: number): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + try { + dispatch(getTask()); + const taskInstance = (await core.tasks.get({ id: tid }))[0]; + if (taskInstance) { + const previewImage = await taskInstance.frames.preview(); + dispatch(getTaskSuccess(taskInstance, previewImage)); + } else { + throw Error(`Task ${tid} wasn't found on the server`); + } + } catch (error) { + dispatch(getTaskFailed(error)); + } + }; +} + +function updateTask(): AnyAction { + const action = { + type: TaskActionTypes.UPDATE_TASK, + payload: {}, + }; + + return action; +} + +function updateTaskSuccess(taskInstance: any): AnyAction { + const action = { + type: TaskActionTypes.UPDATE_TASK_SUCCESS, + payload: { + taskInstance, + }, + }; + + return action; +} + +function updateTaskFailed(error: any, taskInstance: any): AnyAction { + const action = { + type: TaskActionTypes.UPDATE_TASK_FAILED, + payload: { + error, + taskInstance, + }, + }; + + return action; +} + +export function updateTaskAsync(taskInstance: any): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + try { + dispatch(updateTask()); + await taskInstance.save(); + const [task] = await core.tasks.get({ id: taskInstance.id }); + dispatch(updateTaskSuccess(task)); + } catch (error) { + // try abort all changes + let task = null; + try { + [task] = await core.tasks.get({ id: taskInstance.id }); + } catch (_) { + // server error? + dispatch(updateTaskFailed(error, taskInstance)); + } + + dispatch(updateTaskFailed(error, task)); + } + }; +} diff --git a/cvat-ui/src/actions/tasks-actions.ts b/cvat-ui/src/actions/tasks-actions.ts index a7f69ae5..b378a90b 100644 --- a/cvat-ui/src/actions/tasks-actions.ts +++ b/cvat-ui/src/actions/tasks-actions.ts @@ -16,6 +16,9 @@ export enum TasksActionTypes { DUMP_ANNOTATIONS = 'DUMP_ANNOTATIONS', DUMP_ANNOTATIONS_SUCCESS = 'DUMP_ANNOTATIONS_SUCCESS', DUMP_ANNOTATIONS_FAILED = 'DUMP_ANNOTATIONS_FAILED', + DELETE_TASK = 'DELETE_TASK', + DELETE_TASK_SUCCESS = 'DELETE_TASK_SUCCESS', + DELETE_TASK_FAILED = 'DELETE_TASK_FAILED', } function getTasks(): AnyAction { @@ -199,3 +202,52 @@ ThunkAction, {}, {}, AnyAction> { dispatch(loadAnnotationsSuccess(task)); }; } + +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)); + }; +} diff --git a/cvat-ui/src/actions/users-actions.ts b/cvat-ui/src/actions/users-actions.ts new file mode 100644 index 00000000..c64cf111 --- /dev/null +++ b/cvat-ui/src/actions/users-actions.ts @@ -0,0 +1,56 @@ +import { AnyAction, Dispatch, ActionCreator } from 'redux'; +import { ThunkAction } from 'redux-thunk'; + +import getCore from '../core'; + +const core = getCore(); + +export enum UsersActionTypes { + GET_USERS = 'GET_USERS', + GET_USERS_SUCCESS = 'GET_USERS_SUCCESS', + GET_USERS_FAILED = 'GET_USERS_FAILED', +} + +function getUsers(): AnyAction { + const action = { + type: UsersActionTypes.GET_USERS, + payload: { }, + }; + + return action; +} + +function getUsersSuccess(users: any[]): AnyAction { + const action = { + type: UsersActionTypes.GET_USERS_SUCCESS, + payload: { users }, + }; + + return action; +} + +function getUsersFailed(error: any): AnyAction { + const action = { + type: UsersActionTypes.GET_USERS_FAILED, + payload: { error }, + }; + + return action; +} + +export function getUsersAsync(): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + try { + dispatch(getUsers()); + const users = await core.users.get(); + dispatch( + getUsersSuccess( + users.map((userData: any): any => new core.classes.User(userData)), + ), + ); + } catch (error) { + dispatch(getUsersFailed(error)); + } + }; +} 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 00000000..7ca9805d --- /dev/null +++ b/cvat-ui/src/components/actions-menu/actions-menu.tsx @@ -0,0 +1,101 @@ +import React from 'react'; + +import { + Menu, + Modal, +} from 'antd'; + +import { ClickParam } from 'antd/lib/menu/index'; + +import LoaderItemComponent from './loader-item'; +import DumperItemComponent from './dumper-item'; + + +interface ActionsMenuComponentProps { + taskInstance: any; + loaders: any[]; + dumpers: any[]; + loadActivity: string | null; + dumpActivities: string[] | null; + installedTFAnnotation: boolean; + installedAutoAnnotation: boolean; + onLoadAnnotation: (taskInstance: any, loader: any, file: File) => void; + onDumpAnnotation: (task: any, dumper: any) => void; + onDeleteTask: (task: any) => void; +} + +interface MinActionsMenuProps { + taskInstance: any; + onDeleteTask: (task: any) => void; +} + +export function handleMenuClick(props: MinActionsMenuProps, params: ClickParam) { + const { taskInstance } = props; + const tracker = taskInstance.bugTracker; + + if (params.keyPath.length !== 2) { + switch (params.key) { + case 'tracker': { + window.open(`${tracker}`, '_blank') + return; + } case 'auto': { + + return; + } case 'tf': { + + return; + } case 'delete': { + const taskID = taskInstance.id; + Modal.confirm({ + title: `The task ${taskID} will be deleted`, + content: 'All related data (images, annotations) will be lost. Continue?', + onOk: () => { + props.onDeleteTask(taskInstance); + }, + }); + return; + } default: { + return; + } + } + } +} + +export default function ActionsMenuComponent(props: ActionsMenuComponentProps) { + const tracker = props.taskInstance.bugTracker; + + return ( + handleMenuClick(props, params) + }> + + { + props.dumpers.map((dumper) => DumperItemComponent({ + dumper, + taskInstance: props.taskInstance, + dumpActivities: props.dumpActivities, + onDumpAnnotation: props.onDumpAnnotation, + } ))} + + + { + props.loaders.map((loader) => LoaderItemComponent({ + loader, + taskInstance: props.taskInstance, + loadActivity: props.loadActivity, + onLoadAnnotation: props.onLoadAnnotation, + })) + } + + {tracker ? Open bug tracker : null} + { props.installedTFAnnotation ? + Run TF annotation : null + } + { props.installedAutoAnnotation ? + Run auto annotation : null + } +
+ Delete +
+ ); +} diff --git a/cvat-ui/src/components/actions-menu/dumper-item.tsx b/cvat-ui/src/components/actions-menu/dumper-item.tsx new file mode 100644 index 00000000..e870a363 --- /dev/null +++ b/cvat-ui/src/components/actions-menu/dumper-item.tsx @@ -0,0 +1,48 @@ +import React from 'react'; + +import { + Menu, + Button, + Icon, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; + +interface DumperItemComponentProps { + taskInstance: any; + dumper: any; + dumpActivities: string[] | null; + onDumpAnnotation: (task: any, dumper: any) => void; +} + +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'); +} + +export default function DumperItemComponent(props: DumperItemComponentProps) { + const task = props.taskInstance; + const { mode } = task; + const { dumper } = props; + + const dumpingWithThisDumper = (props.dumpActivities || []) + .filter((_dumper: string) => _dumper === dumper.name)[0]; + + const pending = !!dumpingWithThisDumper; + + return ( + + + + ); +} + diff --git a/cvat-ui/src/components/actions-menu/loader-item.tsx b/cvat-ui/src/components/actions-menu/loader-item.tsx new file mode 100644 index 00000000..e7eb7a33 --- /dev/null +++ b/cvat-ui/src/components/actions-menu/loader-item.tsx @@ -0,0 +1,52 @@ +import React from 'react'; + +import { + Menu, + Button, + Icon, + Upload, +} from 'antd'; + +import { RcFile } from 'antd/lib/upload'; +import Text from 'antd/lib/typography/Text'; + +interface LoaderItemComponentProps { + taskInstance: any; + loader: any; + loadActivity: string | null; + onLoadAnnotation: (taskInstance: any, loader: any, file: File) => void; +} + +export default function LoaderItemComponent(props: LoaderItemComponentProps) { + const { loader } = props; + + const loadingWithThisLoader = props.loadActivity + && props.loadActivity === loader.name + ? props.loadActivity : null; + + const pending = !!loadingWithThisLoader; + + return ( + + { + props.onLoadAnnotation( + props.taskInstance, + loader, + file as File, + ); + + return false; + }}> + + + + ); +} \ No newline at end of file diff --git a/cvat-ui/src/components/cvat-app.tsx b/cvat-ui/src/components/cvat-app.tsx index 4cb5160e..0373883c 100644 --- a/cvat-ui/src/components/cvat-app.tsx +++ b/cvat-ui/src/components/cvat-app.tsx @@ -19,11 +19,16 @@ import HeaderContainer from '../containers/header/header'; type CVATAppProps = { loadFormats: () => void; + loadUsers: () => void; verifyAuthorized: () => void; + initPlugins: () => void; + pluginsInitialized: boolean; userInitialized: boolean; formatsInitialized: boolean; + usersInitialized: boolean; gettingAuthError: string; gettingFormatsError: string; + gettingUsersError: string; user: any; } @@ -33,22 +38,63 @@ export default class CVATApplication extends React.PureComponent { } public componentDidMount() { - this.props.loadFormats(); this.props.verifyAuthorized(); } public componentDidUpdate() { + if (!this.props.userInitialized) { + return; + } + if (this.props.gettingAuthError) { Modal.error({ title: 'Could not check authorization', content: `${this.props.gettingAuthError}`, }); + return; + } + + if (!this.props.formatsInitialized) { + this.props.loadFormats(); + return; + } + + if (this.props.gettingFormatsError) { + Modal.error({ + title: 'Could not receive annotations formats', + content: `${this.props.gettingFormatsError}`, + }); + return; + } + + if (!this.props.usersInitialized) { + this.props.loadUsers(); + return; + } + + if (this.props.gettingUsersError) { + Modal.error({ + title: 'Could not receive users', + content: `${this.props.gettingUsersError}`, + }); + + return; + } + + if (!this.props.pluginsInitialized) { + this.props.initPlugins(); + return; } } // Where you go depends on your URL public render() { - if (this.props.userInitialized && this.props.formatsInitialized) { + const readyForRender = this.props.userInitialized + && this.props.formatsInitialized + && this.props.pluginsInitialized + && this.props.usersInitialized; + + if (readyForRender) { if (this.props.user) { return ( @@ -59,8 +105,8 @@ export default class CVATApplication extends React.PureComponent { - - + + diff --git a/cvat-ui/src/components/header/header.tsx b/cvat-ui/src/components/header/header.tsx index df0f560f..4bd4658a 100644 --- a/cvat-ui/src/components/header/header.tsx +++ b/cvat-ui/src/components/header/header.tsx @@ -14,15 +14,20 @@ import { import Text from 'antd/lib/typography/Text'; +import getCore from '../../core'; + +const core = getCore(); + interface HeaderContainerProps { onLogout: () => void; + installedAnalytics: boolean; + installedAutoAnnotation: boolean; username: string; logoutError: string; } function HeaderContainer(props: HeaderContainerProps & RouteComponentProps) { const cvatLogo = () => (); - const backLogo = () => (); const userLogo = () => (); if (props.logoutError) { @@ -42,36 +47,45 @@ function HeaderContainer(props: HeaderContainerProps & RouteComponentProps) { return ( -
+
- props.history.push('/tasks') }> Tasks - props.history.push('/models') - }> Models - + { props.installedAutoAnnotation ? + props.history.push('/models') + }> Models : null + } + { props.installedAnalytics ? + : null + }
-
- - + - {props.username} - + {props.username} + }> 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 00000000..61328b44 --- /dev/null +++ b/cvat-ui/src/components/labels-editor/common.ts @@ -0,0 +1,29 @@ +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 00000000..f3f189d3 --- /dev/null +++ b/cvat-ui/src/components/labels-editor/constructor-creator.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import LabelForm from './label-form'; + +import { Label, Attribute } from './common'; + +interface Props { + onCreate: (label: Label | null) => void; +} + +interface State { + attributes: Attribute[]; +} + +export default class ConstructorCreator extends React.PureComponent { + public constructor(props: Props) { + super(props); + } + + public render() { + 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 00000000..c87e3d75 --- /dev/null +++ b/cvat-ui/src/components/labels-editor/constructor-updater.tsx @@ -0,0 +1,28 @@ +import React from 'react'; + +import LabelForm from './label-form'; +import { Label, Attribute } from './common'; + +interface Props { + label: Label; + onUpdate: (label: Label | null) => void; +} + +interface State { + savedAttributes: Attribute[]; + unsavedAttributes: Attribute[]; +} + +export default class ConstructorUpdater extends React.PureComponent { + constructor(props: Props) { + super(props); + } + + public render() { + 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 00000000..272b380f --- /dev/null +++ b/cvat-ui/src/components/labels-editor/constructor-viewer-item.tsx @@ -0,0 +1,37 @@ +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) { + return ( +
+ { props.label.name } + + props.onUpdate(props.label)}> + + + + { props.label.id >= 0 ? null : + + props.onDelete(props.label)}> + + + + } +
+ ); +} 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 00000000..7c12983e --- /dev/null +++ b/cvat-ui/src/components/labels-editor/constructor-viewer.tsx @@ -0,0 +1,59 @@ +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() { + const color = colors[currentColor]; + currentColor += 1; + if (currentColor >= colors.length) { + currentColor = 0; + } + return color; +} + +export default function ConstructorViewer(props: ConstructorViewerProps) { + currentColor = 0; + + const list = [ + ]; + for (const label of props.labels) { + list.push( + + ) + } + + return ( +
+ { list } +
+ ); +} \ No newline at end of file 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 00000000..78596259 --- /dev/null +++ b/cvat-ui/src/components/labels-editor/label-form.tsx @@ -0,0 +1,464 @@ +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 { + equalArrayHead, + idGenerator, + Label, + Attribute, +} from './common'; +import patterns from '../../utils/validation-patterns'; + +export enum AttributeType { + SELECT = 'SELECT', + RADIO = 'RADIO', + CHECKBOX = 'CHECKBOX', + TEXT = 'TEXT', + NUMBER = 'NUMBER', +} + +type Props = FormComponentProps & { + label: Label | null; + onSubmit: (label: Label | null) => void; +}; + +interface State { + +} + +class LabelForm extends React.PureComponent { + private continueAfterSubmit: boolean; + + constructor(props: Props) { + super(props); + + this.continueAfterSubmit = false; + } + + private handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + this.props.form.validateFields((error, values) => { + if (!error) { + this.props.onSubmit({ + name: values.labelName, + id: this.props.label ? this.props.label.id : idGenerator(), + attributes: values.keys.map((key: number, index: number) => { + return { + name: values.attrName[key], + type: values.type[key], + mutable: values.mutable[key], + id: this.props.label && index < this.props.label.attributes.length + ? this.props.label.attributes[index].id : key, + values: Array.isArray(values.values[key]) + ? values.values[key] : [values.values[key]] + }; + }), + }); + + this.props.form.resetFields(); + + if (!this.continueAfterSubmit) { + this.props.onSubmit(null); + } + } + }); + } + + private addAttribute = () => { + const { form } = this.props; + const keys = form.getFieldValue('keys'); + const nextKeys = keys.concat(idGenerator()); + form.setFieldsValue({ + keys: nextKeys, + }); + } + + private removeAttribute = (key: number) => { + 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) { + const locked = attr ? attr.id >= 0 : false; + const value = attr ? attr.name : ''; + + return ( + + { + this.props.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) { + const locked = attr ? attr.id >= 0 : false; + const type = attr ? attr.type.toUpperCase() : AttributeType.SELECT; + + return ( + + + + {this.props.form.getFieldDecorator(`type[${key}]`, { + initialValue: type, + })( + + ) + } + + + ); + } + + private renderAttributeValuesInput(key: number, attr: Attribute | null) { + const locked = attr ? attr.id >= 0 : false; + const existedValues = attr ? attr.values : []; + + const validator = (_: any, values: string[], callback: any) => { + 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 ( + + { this.props.form.getFieldDecorator(`values[${key}]`, { + initialValue: existedValues, + rules: [{ + required: true, + message: 'Please specify values', + }, { + validator, + }], + })( + + False + True + + )} + + ); + } + + private renderNumberRangeInput(key: number, attr: Attribute | null) { + const locked = attr ? attr.id >= 0 : false; + const value = attr ? attr.values[0] : ''; + + const validator = (_: any, value: string, callback: any) => { + const numbers = value.split(';').map((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 ( + + { this.props.form.getFieldDecorator(`values[${key}]`, { + initialValue: value, + rules: [{ + required: true, + message: 'Please set a range', + }, { + validator, + }] + })( + + )} + + ); + } + + private renderDefaultValueInput(key: number, attr: Attribute | null) { + const value = attr ? attr.values[0] : ''; + + return ( + + { this.props.form.getFieldDecorator(`values[${key}]`, { + initialValue: value, + })( + + )} + + ); + } + + private renderMutableAttributeInput(key: number, attr: Attribute | null) { + const locked = attr ? attr.id >= 0 : false; + const value = attr ? attr.mutable : false; + + return ( + + + { this.props.form.getFieldDecorator(`mutable[${key}]`, { + initialValue: value, + valuePropName: 'checked', + })( + Mutable + )} + + + ); + } + + private renderDeleteAttributeButton(key: number, attr: Attribute | null) { + const locked = attr ? attr.id >= 0 : false; + + return ( + + + + + + ); + } + + private renderAttribute = (key: number, index: number) => { + const attr = (this.props.label && index < this.props.label.attributes.length + ? this.props.label.attributes[index] + : null); + + return ( + + + { this.renderAttributeNameInput(key, attr) } + { this.renderAttributeTypeInput(key, attr) } + { + (() => { + const type = this.props.form.getFieldValue(`type[${key}]`); + let element = null; + + [AttributeType.SELECT, AttributeType.RADIO] + .includes(type) ? + element = this.renderAttributeValuesInput(key, attr) + : type === AttributeType.CHECKBOX ? + element = this.renderBooleanValueInput(key, attr) + : type === AttributeType.NUMBER ? + element = this.renderNumberRangeInput(key, attr) + : element = this.renderDefaultValueInput(key, attr) + + return element; + })() + } + + { this.renderMutableAttributeInput(key, attr) } + + + { this.renderDeleteAttributeButton(key, attr) } + + + + ); + } + + private renderLabelNameInput() { + const value = this.props.label ? this.props.label.name : ''; + const locked = this.props.label ? this.props.label.id >= 0 : false; + + return ( + + { + this.props.form.getFieldDecorator('labelName', { + initialValue: value, + rules: [{ + required: true, + message: 'Please specify a name', + }, { + pattern: patterns.validateAttributeName.pattern, + message: patterns.validateAttributeName.message, + }] + })() + } + + ); + } + + private renderNewAttributeButton() { + return ( + + + + + + ); + } + + private renderDoneButton() { + return ( + + + + + + ); + } + + private renderContinueButton() { + return ( + this.props.label ?
: + + + + + + ); + } + + private renderCancelButton() { + return ( + + + + + + ); + } + + public render() { + this.props.form.getFieldDecorator('keys', { + initialValue: this.props.label + ? this.props.label.attributes.map((attr: Attribute) => attr.id) + : [] + }); + + let keys = this.props.form.getFieldValue('keys'); + const attributeItems = keys.map(this.renderAttribute); + + return ( +
+ + { this.renderLabelNameInput() } + + { this.renderNewAttributeButton() } + + { attributeItems.length > 0 ? + + + Attributes + + : null + } + { attributeItems.reverse() } + + { this.renderDoneButton() } + + { this.renderContinueButton() } + + { this.renderCancelButton() } + +
+ ); + } +} + +export default Form.create()(LabelForm); + + +// add validators +// add initial values +// add readonly fields 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 00000000..77392bd7 --- /dev/null +++ b/cvat-ui/src/components/labels-editor/labels-editor.tsx @@ -0,0 +1,255 @@ +import React from 'react'; + +import { + Tabs, + Icon, + Modal, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; + +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, + }; + } + + private handleSubmit(savedLabels: Label[], unsavedLabels: Label[]) { + function transformLabel(label: Label): any { + return { + name: label.name, + id: label.id < 0 ? undefined : label.id, + attributes: label.attributes.map((attr: Attribute): any => { + return { + 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 output = []; + for (const label of savedLabels.concat(unsavedLabels)) { + output.push(transformLabel(label)); + } + + this.props.onSubmit(output); + } + + private handleRawSubmit = (labels: Label[]) => { + const unsavedLabels = []; + const savedLabels = []; + + for (let label of labels) { + if (label.id >= 0) { + savedLabels.push(label); + } else { + unsavedLabels.push(label); + } + } + + this.setState({ + unsavedLabels, + savedLabels, + }); + + this.handleSubmit(savedLabels, unsavedLabels); + } + + private handleUpdate = (label: Label | null) => { + if (label) { + const savedLabels = this.state.savedLabels + .filter((_label: Label) => _label.id !== label.id); + const unsavedLabels = this.state.unsavedLabels + .filter((_label: Label) => _label.id !== label.id); + if (label.id >= 0) { + savedLabels.push(label); + this.setState({ + savedLabels, + constructorMode: ConstructorMode.SHOW, + }); + } else { + unsavedLabels.push(label); + this.setState({ + unsavedLabels, + constructorMode: ConstructorMode.SHOW, + }); + } + + this.handleSubmit(savedLabels, unsavedLabels); + } else { + this.setState({ + constructorMode: ConstructorMode.SHOW, + }); + } + }; + + private handleDelete = (label: Label) => { + // the label is saved on the server, cannot delete it + if (typeof(label.id) !== 'undefined' && label.id >= 0) { + Modal.error({ + title: 'Could not delete the label', + content: 'It has been already saved on the server', + }); + } + + const unsavedLabels = this.state.unsavedLabels.filter( + (_label: Label) => _label.id !== label.id + ); + + this.setState({ + unsavedLabels: [...unsavedLabels], + }); + + this.handleSubmit(this.state.savedLabels, unsavedLabels); + }; + + private handleCreate = (label: Label | null) => { + if (label === null) { + this.setState({ + constructorMode: ConstructorMode.SHOW, + }); + } else { + const unsavedLabels = [...this.state.unsavedLabels, + { + ...label, + id: idGenerator() + } + ]; + + this.setState({ + unsavedLabels, + }); + + this.handleSubmit(this.state.savedLabels, unsavedLabels); + } + }; + + public componentDidMount() { + this.componentDidUpdate(null as any as LabelsEditortProps); + } + + public componentDidUpdate(prevProps: LabelsEditortProps) { + function transformLabel(label: any): Label { + return { + name: label.name, + id: label.id || idGenerator(), + attributes: label.attributes.map((attr: any): Attribute => { + return { + id: attr.id || idGenerator(), + name: attr.name, + type: attr.input_type, + mutable: attr.mutable, + values: [...attr.values], + }; + }), + } + } + + if (!prevProps || prevProps.labels !== this.props.labels) { + const transformedLabels = this.props.labels.map(transformLabel); + this.setState({ + savedLabels: transformedLabels + .filter((label: Label) => label.id >= 0), + unsavedLabels: transformedLabels + .filter((label: Label) => label.id < 0), + }); + } + } + + public render() { + return ( + + + + Raw + + } key='1'> + + + + + + Constructor + + } key='2'> + { + this.state.constructorMode === ConstructorMode.SHOW ? + { + this.setState({ + constructorMode: ConstructorMode.UPDATE, + labelForUpdate: label, + }); + }} + onDelete={this.handleDelete} + onCreate={() => { + this.setState({ + constructorMode: ConstructorMode.CREATE, + }) + }} + /> : + + this.state.constructorMode === ConstructorMode.UPDATE + && this.state.labelForUpdate !== null ? + : + + + } + + + ); + } +} 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 00000000..4bece82f --- /dev/null +++ b/cvat-ui/src/components/labels-editor/raw-viewer.tsx @@ -0,0 +1,115 @@ +import React from 'react'; + +import { + Row, + Col, + Form, + Input, + Button, + Tooltip, +} from 'antd'; + +import { FormComponentProps } from 'antd/lib/form/Form'; + +import { + Attribute, + Label, + equalArrayHead, +} from './common'; + + + +type Props = FormComponentProps & { + labels: Label[]; + onSubmit: (labels: Label[]) => void; +} + +interface State { + labels: object[]; + valid: boolean; +} + +class RawViewer extends React.PureComponent { + public constructor(props: Props) { + super(props); + + const labels = JSON.parse(JSON.stringify(this.props.labels)); + for (const label of labels) { + for (const attr of label.attributes) { + if (attr.id < 0) { + delete attr.id; + } + } + + if (label.id < 0) { + delete label.id; + } + } + + this.state = { + labels, + valid: true, + }; + } + + private validateLabels = (_: any, value: string, callback: any) => { + try { + JSON.parse(value); + } catch (error) { + callback(error.toString()); + } + + callback(); + } + + private handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + this.props.form.validateFields((error, values) => { + if (!error) { + this.props.onSubmit(JSON.parse(values.labels)); + } + }); + } + + public render() { + const textLabels = JSON.stringify(this.state.labels, null, 2); + + return ( +
+ { + this.props.form.getFieldDecorator('labels', { + initialValue: textLabels, + rules: [{ + validator: this.validateLabels, + }] + })( ) + } + + + + + + + + + + + + + +
+ ); + } +} + +export default Form.create()(RawViewer); 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 00000000..3ced58b1 --- /dev/null +++ b/cvat-ui/src/components/task-page/details.tsx @@ -0,0 +1,279 @@ +import React from 'react'; + +import { + Row, + Col, + Modal, + Button, + Select, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; +import Title from 'antd/lib/typography/Title'; + +import moment from 'moment'; + +import LabelsEditorComponent from '../labels-editor/labels-editor'; +import getCore from '../../core'; +import patterns from '../../utils/validation-patterns'; + +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; + assignee: any; +} + +export default class DetailsComponent extends React.PureComponent { + constructor(props: Props) { + super(props); + + const { taskInstance } = props; + + this.state = { + name: taskInstance.name, + bugTracker: taskInstance.bugTracker, + assignee: taskInstance.assignee, + }; + } + + private renderTaskName() { + const { taskInstance } = this.props; + const { name } = this.state; + return ( + { + this.setState({ + name: value, + }); + + taskInstance.name = value; + this.props.onTaskUpdate(taskInstance); + }, + }} + className='cvat-black-color' + >{name} + ); + } + + private renderPreview() { + return ( +
+ Preview +
+ ); + } + + private renderParameters() { + 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() { + const { taskInstance } = this.props; + const owner = taskInstance.owner ? taskInstance.owner.username : null; + const assignee = this.state.assignee ? this.state.assignee.username : null; + const created = moment(taskInstance.createdDate).format('MMMM Do YYYY'); + const assigneeSelect = ( + + ); + + return ( + + + { owner ? + Created by {owner} on {created} + : null } + + + + {'Assigned to'} + { assigneeSelect } + + + + ); + } + + private renderBugTracker() { + const { taskInstance } = this.props; + const { bugTracker } = this.state; + + const onChangeValue = (value: string) => { + if (value && !patterns.validateURL.pattern.test(value)) { + Modal.error({ + title: `Could not update the task ${taskInstance.id}`, + content: 'Issue tracker is expected to be URL', + }); + } else { + this.setState({ + bugTracker: value, + }); + + taskInstance.bugTracker = value; + this.props.onTaskUpdate(taskInstance); + } + } + + if (bugTracker) { + return ( + + + Issue Tracker +
+ {bugTracker} + + +
+ ); + } else { + return ( + + + Issue Tracker +
+ {'Not specified'} + +
+ ); + } + } + + private renderLabelsEditor() { + const { taskInstance } = this.props; + + return ( + + + label.toJSON() + )} + onSubmit={(labels: any[]) => { + taskInstance.labels = labels.map((labelData) => { + return new core.classes.Label(labelData); + }); + + this.props.onTaskUpdate(taskInstance); + }} + /> + + + ); + } + + public componentDidUpdate(prevProps: Props) { + if (prevProps !== this.props) { + this.setState({ + name: this.props.taskInstance.name, + bugTracker: this.props.taskInstance.bugTracker, + assignee: this.props.taskInstance.assignee, + }); + } + } + + public render() { + return ( +
+ + + { this.renderTaskName() } + + + + + + + { this.renderPreview() } + + + + + { this.renderParameters() } + + + + + { this.renderUsers() } + { this.renderBugTracker() } + { 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 00000000..c1985ca7 --- /dev/null +++ b/cvat-ui/src/components/task-page/job-list.tsx @@ -0,0 +1,110 @@ +import React from 'react'; + +import { + Row, + Col, + Icon, + Table, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; +import Title from 'antd/lib/typography/Title'; + +import moment from 'moment'; + +import getCore from '../../core'; +const core = getCore(); + +const baseURL = core.config.backendAPI.slice(0, -7); + +interface Props { + taskInstance: any; +} + +export default function JobListComponent(props: Props) { + const { jobs } = props.taskInstance; + const columns = [{ + title: 'Job', + dataIndex: 'job', + key: 'job', + render: (id: number) => { + return ( + { `Job #${id++}` } + ); + } + }, { + title: 'Frames', + dataIndex: 'frames', + key: 'frames', + className: 'cvat-black-color', + }, { + title: 'Status', + dataIndex: 'status', + key: 'status', + render: (status: string) => { + const progressColor = status === 'completed' ? 'cvat-job-completed-color': + status === 'validation' ? 'cvat-job-validation-color' : 'cvat-job-annotation-color'; + + return ( + { status } + ); + } + }, { + title: 'Started on', + dataIndex: 'started', + key: 'started', + className: 'cvat-black-color', + }, { + title: 'Duration', + dataIndex: 'duration', + key: 'duration', + className: 'cvat-black-color', + }, { + title: 'Assignee', + dataIndex: 'assignee', + key: 'assignee', + className: 'cvat-black-color', + }]; + + 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.assignee ? job.assignee.username : ''}`, + }); + + return acc; + }, []); + + return ( +
+ + + Jobs + + + + {`${completed} of ${data.length} jobs`} + + + + + + ); +} \ No newline at end of file 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 00000000..1e739cc0 --- /dev/null +++ b/cvat-ui/src/components/task-page/task-page.tsx @@ -0,0 +1,78 @@ +import React from 'react'; +import { RouteComponentProps } from 'react-router'; +import { withRouter } from 'react-router-dom'; + +import { + Col, + Row, + Spin, + Modal, +} from 'antd'; + +import TopBarContainer from '../../containers/task-page/top-bar'; +import DetailsContainer from '../../containers/task-page/details'; +import JobListContainer from '../../containers/task-page/job-list'; + +interface TaskPageComponentProps { + taskInstance: any; + taskFetchingError: string; + taskUpdatingError: string; + deleteActivity: boolean | null; + installedGit: boolean; + onFetchTask: (tid: number) => void; +} + +type Props = TaskPageComponentProps & RouteComponentProps<{id: string}>; + +class TaskPageComponent extends React.PureComponent { + public componentDidUpdate() { + if (this.props.deleteActivity) { + this.props.history.replace('/tasks'); + } + + const { id } = this.props.match.params; + + if (this.props.taskFetchingError) { + Modal.error({ + title: `Could not receive the task ${id}`, + content: this.props.taskFetchingError, + }); + } + + if (this.props.taskUpdatingError) { + Modal.error({ + title: `Could not update the task ${id}`, + content: this.props.taskUpdatingError, + }); + } + } + + public render() { + const { id } = this.props.match.params; + const fetchTask = !this.props.taskInstance && !this.props.taskFetchingError + || (this.props.taskInstance && this.props.taskInstance.id !== +id ); + + if (fetchTask) { + this.props.onFetchTask(+id); + return ( + + ); + } else if (this.props.taskFetchingError) { + return ( +
+ ) + } else { + 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 00000000..42bb19f9 --- /dev/null +++ b/cvat-ui/src/components/task-page/top-bar.tsx @@ -0,0 +1,60 @@ +import React from 'react'; + +import { + Row, + Col, + Button, + Dropdown, + Icon, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; + +import ActionsMenu from '../actions-menu/actions-menu'; + +interface DetailsComponentProps { + taskInstance: any; + loaders: any[]; + dumpers: any[]; + loadActivity: string | null; + dumpActivities: string[] | null; + installedTFAnnotation: boolean; + installedAutoAnnotation: boolean; + onLoadAnnotation: (taskInstance: any, loader: any, file: File) => void; + onDumpAnnotation: (task: any, dumper: any) => void; + onDeleteTask: (task: any) => void; +} + +export default function DetailsComponent(props: DetailsComponentProps) { + const subMenuIcon = () => (); + const { id } = props.taskInstance; + + return ( + + + Task details #{id} + + + + + + + + ); +} \ No newline at end of file diff --git a/cvat-ui/src/components/tasks-page/task-item.tsx b/cvat-ui/src/components/tasks-page/task-item.tsx index 181b3f8e..8faa9e2c 100644 --- a/cvat-ui/src/components/tasks-page/task-item.tsx +++ b/cvat-ui/src/components/tasks-page/task-item.tsx @@ -1,4 +1,6 @@ import React from 'react'; +import { RouteComponentProps } from 'react-router'; +import { withRouter } from 'react-router-dom'; import Text from 'antd/lib/typography/Text'; import { @@ -7,75 +9,38 @@ import { Button, Icon, Progress, - Menu, Dropdown, - Upload, } from 'antd'; -import { ClickParam } from 'antd/lib/menu/index'; -import { UploadChangeParam } from 'antd/lib/upload'; -import { RcFile } from 'antd/lib/upload'; - import moment from 'moment'; +import ActionsMenu from '../actions-menu/actions-menu'; + export interface TaskItemProps { + installedTFAnnotation: boolean; + installedAutoAnnotation: boolean; taskInstance: any; previewImage: string; dumpActivities: string[] | null; loadActivity: string | null; loaders: any[]; dumpers: any[]; + deleted: boolean; + onDeleteTask: (taskInstance: any) => void; onDumpAnnotation: (task: any, dumper: any) => void; onLoadAnnotation: (task: any, loader: any, file: File) => void; } -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'); -} - -export default class TaskItemComponent extends React.PureComponent { - constructor(props: TaskItemProps) { +class TaskItemComponent extends React.PureComponent { + constructor(props: TaskItemProps & RouteComponentProps) { super(props); } - private handleMenuClick = (params: ClickParam) => { - const tracker = this.props.taskInstance.bugTracker; - - if (params.keyPath.length === 2) { - // dump or upload - if (params.keyPath[1] === 'dump') { - - } - } else { - switch (params.key) { - case 'tracker': { - window.open(`${tracker}`, '_blank') - return; - } case 'auto': { - - return; - } case 'tf': { - - return; - } case 'update': { - - return; - } case 'delete': { - - return; - } default: { - return; - } - } - } - } - private renderPreview() { return ( -
- Preview +
+ Preview
) @@ -94,7 +59,7 @@ export default class TaskItemComponent extends React.PureComponent - {id} {name}
+ {`${id} ${name}`}
{ owner ? <> @@ -134,7 +99,7 @@ export default class TaskItemComponent extends React.PureComponent
- {numOfCompleted} of {numOfJobs} jobs + {`${numOfCompleted} of ${numOfJobs} jobs`} @@ -151,98 +116,36 @@ export default class TaskItemComponent extends React.PureComponent _dumper === dumper.name)[0]; - - const pending = !!dumpingWithThisDumper; - - return ( - - - - ); - } - - private renderLoaderItem(loader: any) { - const loadingWithThisLoader = this.props.loadActivity - && this.props.loadActivity === loader.name - ? this.props.loadActivity : null; - - const pending = !!loadingWithThisLoader; - - return ( - - { - this.props.onLoadAnnotation( - this.props.taskInstance, - loader, - file as File, - ); - - return false; - }}> - - - - ); - } - - private renderMenu() { - const tracker = this.props.taskInstance.bugTracker; - - return ( - - - {this.props.dumpers.map((dumper) => this.renderDumperItem(dumper))} - - - {this.props.loaders.map((loader) => this.renderLoaderItem(loader))} - - {tracker ? Open bug tracker : null} - Run auto annotation - Run TF annotation -
- Update - Delete -
- ); - } - private renderNavigation() { const subMenuIcon = () => (); + const { id } = this.props.taskInstance; return (
- + - Actions - + Actions + @@ -252,8 +155,14 @@ export default class TaskItemComponent extends React.PureComponent + {this.renderPreview()} {this.renderDescription()} {this.renderProgress()} @@ -262,3 +171,5 @@ export default class TaskItemComponent extends React.PureComponent { +class TopBarComponent extends React.PureComponent { public render() { return ( <> @@ -37,11 +40,13 @@ export default class TopBarComponent extends React.PureComponent ) } -} \ No newline at end of file +} + +export default withRouter(TopBarComponent); \ No newline at end of file diff --git a/cvat-ui/src/containers/header/header.tsx b/cvat-ui/src/containers/header/header.tsx index 2aa60d40..41c1ff36 100644 --- a/cvat-ui/src/containers/header/header.tsx +++ b/cvat-ui/src/containers/header/header.tsx @@ -3,10 +3,13 @@ import { connect } from 'react-redux'; import { logoutAsync } from '../../actions/auth-actions'; import { CombinedState } from '../../reducers/root-reducer'; +import { SupportedPlugins } from '../../reducers/interfaces'; import HeaderComponent from '../../components/header/header'; interface StateToProps { + installedAnalytics: boolean; + installedAutoAnnotation: boolean; username: string; logoutError: any; } @@ -16,9 +19,13 @@ interface DispatchToProps { } function mapStateToProps(state: CombinedState): StateToProps { + const { auth } = state; + const { plugins } = state.plugins; return { - username: state.auth.user.username, - logoutError: state.auth.logoutError, + installedAnalytics: plugins[SupportedPlugins.ANALYTICS], + installedAutoAnnotation: plugins[SupportedPlugins.AUTO_ANNOTATION], + username: auth.user.username, + logoutError: auth.logoutError, }; } @@ -31,6 +38,8 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { function HeaderContainer(props: StateToProps & DispatchToProps) { return ( void; +} + +function mapStateToProps(state: CombinedState): StateToProps { + const { plugins } = state.plugins; + const taskInstance = (state.activeTask.task as any).instance; + const previewImage = (state.activeTask.task as any).preview; + + return { + registeredUsers: state.users.users, + taskInstance, + previewImage, + installedGit: plugins.GIT_INTEGRATION, + }; +} + + +function mapDispatchToProps(dispatch: any): DispatchToProps { + return { + onTaskUpdate: (taskInstance: any) => + dispatch(updateTaskAsync(taskInstance)) + } +} + + +function TaskPageContainer(props: StateToProps & DispatchToProps) { + return ( + + ); +} + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(TaskPageContainer); \ No newline at end of file diff --git a/cvat-ui/src/containers/task-page/job-list.tsx b/cvat-ui/src/containers/task-page/job-list.tsx new file mode 100644 index 00000000..c045c747 --- /dev/null +++ b/cvat-ui/src/containers/task-page/job-list.tsx @@ -0,0 +1,99 @@ +import React from 'react'; +import { connect } from 'react-redux'; + +import { getTaskAsync } from '../../actions/task-actions'; +import { + dumpAnnotationsAsync, + loadAnnotationsAsync, + deleteTaskAsync, +} from '../../actions/tasks-actions'; + +import JobListComponent from '../../components/task-page/job-list'; +import { CombinedState } from '../../reducers/root-reducer'; + +interface StateToProps { + taskFetchingError: any; + previewImage: string; + taskInstance: any; + loaders: any[]; + dumpers: any[]; + loadActivity: string | null; + dumpActivities: string[] | null; + deleteActivity: boolean | null; + installedTFAnnotation: boolean; + installedAutoAnnotation: boolean; + installedGit: boolean; +} + +interface DispatchToProps { + fetchTask: (tid: number) => void; + deleteTask: (taskInstance: any) => void; + dumpAnnotations: (task: any, format: string) => void; + loadAnnotations: (task: any, format: string, file: File) => void; +} + +function mapStateToProps(state: CombinedState): StateToProps { + const { plugins } = state.plugins; + const { formats } = state; + const { activeTask } = state; + const { dumps } = state.tasks.activities; + const { loads } = state.tasks.activities; + const { deletes } = state.tasks.activities; + + const taskInstance = activeTask.task ? activeTask.task.instance : null; + const previewImage = activeTask.task ? activeTask.task.preview : ''; + + let dumpActivities = null; + let loadActivity = null; + let deleteActivity = null; + if (taskInstance) { + const { id } = taskInstance; + dumpActivities = dumps.byTask[id] ? dumps.byTask[id] : null; + loadActivity = loads.byTask[id] ? loads.byTask[id] : null; + deleteActivity = deletes.byTask[id] ? deletes.byTask[id] : null; + } + + return { + previewImage, + taskInstance, + taskFetchingError: activeTask.taskFetchingError, + loaders: formats.loaders, + dumpers: formats.dumpers, + dumpActivities, + loadActivity, + deleteActivity, + installedGit: plugins.GIT_INTEGRATION, + installedTFAnnotation: plugins.TF_ANNOTATION, + installedAutoAnnotation: plugins.AUTO_ANNOTATION, + }; +} + +function mapDispatchToProps(dispatch: any): DispatchToProps { + return { + fetchTask: (tid: number) => { + dispatch(getTaskAsync(tid)); + }, + deleteTask: (taskInstance: any): void => { + dispatch(deleteTaskAsync(taskInstance)); + }, + dumpAnnotations: (task: any, dumper: any): void => { + dispatch(dumpAnnotationsAsync(task, dumper)); + }, + loadAnnotations: (task: any, loader: any, file: File): void => { + dispatch(loadAnnotationsAsync(task, loader, file)); + }, + }; +} + +function TaskPageContainer(props: StateToProps & DispatchToProps) { + return ( + + ); +} + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(TaskPageContainer); \ No newline at end of file diff --git a/cvat-ui/src/containers/task-page/task-page.tsx b/cvat-ui/src/containers/task-page/task-page.tsx index 85f0da99..5bea0169 100644 --- a/cvat-ui/src/containers/task-page/task-page.tsx +++ b/cvat-ui/src/containers/task-page/task-page.tsx @@ -1,9 +1,67 @@ import React from 'react'; +import { connect } from 'react-redux'; -export default function TaskPage() { +import { getTaskAsync } from '../../actions/task-actions'; + +import TaskPageComponent from '../../components/task-page/task-page'; +import { CombinedState } from '../../reducers/root-reducer'; + +interface StateToProps { + taskFetchingError: any; + taskUpdatingError: any; + taskInstance: any; + deleteActivity: boolean | null; + installedGit: boolean; +} + +interface DispatchToProps { + fetchTask: (tid: number) => void; +} + +function mapStateToProps(state: CombinedState): StateToProps { + const { plugins } = state.plugins; + const { activeTask } = state; + const { deletes } = state.tasks.activities; + + const taskInstance = activeTask.task ? activeTask.task.instance : null; + + let deleteActivity = null; + if (taskInstance) { + const { id } = taskInstance; + deleteActivity = deletes.byTask[id] ? deletes.byTask[id] : null; + } + + return { + taskInstance, + taskFetchingError: activeTask.taskFetchingError, + taskUpdatingError: activeTask.taskUpdatingError, + deleteActivity, + installedGit: plugins.GIT_INTEGRATION, + }; +} + +function mapDispatchToProps(dispatch: any): DispatchToProps { + return { + fetchTask: (tid: number) => { + dispatch(getTaskAsync(tid)); + }, + }; +} + +function TaskPageContainer(props: StateToProps & DispatchToProps) { return ( -
- "Task Page" -
+ ); } + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(TaskPageContainer); \ No newline at end of file diff --git a/cvat-ui/src/containers/task-page/top-bar.tsx b/cvat-ui/src/containers/task-page/top-bar.tsx new file mode 100644 index 00000000..45d8177d --- /dev/null +++ b/cvat-ui/src/containers/task-page/top-bar.tsx @@ -0,0 +1,86 @@ +import React from 'react'; +import { connect } from 'react-redux'; + +import { + dumpAnnotationsAsync, + loadAnnotationsAsync, + deleteTaskAsync, +} from '../../actions/tasks-actions'; + +import TopBarComponent from '../../components/task-page/top-bar'; +import { CombinedState } from '../../reducers/root-reducer'; + +interface StateToProps { + taskInstance: any; + loaders: any[]; + dumpers: any[]; + loadActivity: string | null; + dumpActivities: string[] | null; + installedTFAnnotation: boolean; + installedAutoAnnotation: boolean; +} + +interface DispatchToProps { + deleteTask: (taskInstance: any) => void; + dumpAnnotations: (task: any, format: string) => void; + loadAnnotations: (task: any, format: string, file: File) => void; +} + +function mapStateToProps(state: CombinedState): StateToProps { + const taskInstance = (state.activeTask.task as any).instance; + + const { plugins } = state.plugins; + const { formats } = state; + const { dumps } = state.tasks.activities; + const { loads } = state.tasks.activities; + + const { id } = taskInstance; + const dumpActivities = dumps.byTask[id] ? dumps.byTask[id] : null; + const loadActivity = loads.byTask[id] ? loads.byTask[id] : null; + + return { + taskInstance, + loaders: formats.loaders, + dumpers: formats.dumpers, + dumpActivities, + loadActivity, + installedTFAnnotation: plugins.TF_ANNOTATION, + installedAutoAnnotation: plugins.AUTO_ANNOTATION, + }; +} + +function mapDispatchToProps(dispatch: any): DispatchToProps { + return { + deleteTask: (taskInstance: any): void => { + dispatch(deleteTaskAsync(taskInstance)); + }, + dumpAnnotations: (task: any, dumper: any): void => { + dispatch(dumpAnnotationsAsync(task, dumper)); + }, + loadAnnotations: (task: any, loader: any, file: File): void => { + dispatch(loadAnnotationsAsync(task, loader, file)); + }, + }; +} + +function TaskPageContainer(props: StateToProps & DispatchToProps) { + return ( + + ); +} + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(TaskPageContainer); \ No newline at end of file diff --git a/cvat-ui/src/containers/tasks-page/task-item.tsx b/cvat-ui/src/containers/tasks-page/task-item.tsx index 2ac5bce1..2f14010f 100644 --- a/cvat-ui/src/containers/tasks-page/task-item.tsx +++ b/cvat-ui/src/containers/tasks-page/task-item.tsx @@ -3,6 +3,7 @@ import { connect } from 'react-redux'; import { TasksQuery, + SupportedPlugins, } from '../../reducers/interfaces'; import { @@ -15,11 +16,15 @@ import { getTasksAsync, dumpAnnotationsAsync, loadAnnotationsAsync, + deleteTaskAsync, } from '../../actions/tasks-actions'; interface StateToProps { + installedTFAnnotation: boolean; + installedAutoAnnotation: boolean; dumpActivities: string[] | null; loadActivity: string | null; + deleteActivity: boolean | null; previewImage: string; taskInstance: any; loaders: any[]; @@ -28,6 +33,7 @@ interface StateToProps { interface DispatchToProps { getTasks: (query: TasksQuery) => void; + delete: (taskInstance: any) => void; dump: (task: any, format: string) => void; load: (task: any, format: string, file: File) => void; } @@ -42,10 +48,16 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { const { formats } = state; const { dumps } = state.tasks.activities; const { loads } = state.tasks.activities; + const { deletes } = state.tasks.activities; + const { plugins } = state.plugins; + const id = own.taskID; return { - dumpActivities: dumps.byTask[own.taskID] ? dumps.byTask[own.taskID] : null, - loadActivity: loads.byTask[own.taskID] ? loads.byTask[own.taskID] : null, + installedTFAnnotation: plugins.TF_ANNOTATION, + installedAutoAnnotation: plugins.AUTO_ANNOTATION, + dumpActivities: dumps.byTask[id] ? dumps.byTask[id] : null, + loadActivity: loads.byTask[id] ? loads.byTask[id] : null, + deleteActivity: deletes.byTask[id] ? deletes.byTask[id] : null, previewImage: task.preview, taskInstance: task.instance, loaders: formats.loaders, @@ -64,6 +76,9 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { load: (task: any, loader: any, file: File): void => { dispatch(loadAnnotationsAsync(task, loader, file)); }, + delete: (taskInstance: any): void => { + dispatch(deleteTaskAsync(taskInstance)); + }, } } @@ -72,12 +87,16 @@ type TasksItemContainerProps = StateToProps & DispatchToProps & OwnProps; function TaskItemContainer(props: TasksItemContainerProps) { return ( diff --git a/cvat-ui/src/containers/tasks-page/tasks-page.tsx b/cvat-ui/src/containers/tasks-page/tasks-page.tsx index 4e301dbc..a6fcf538 100644 --- a/cvat-ui/src/containers/tasks-page/tasks-page.tsx +++ b/cvat-ui/src/containers/tasks-page/tasks-page.tsx @@ -11,6 +11,7 @@ import TasksPageComponent from '../../components/tasks-page/tasks-page'; import { getTasksAsync } from '../../actions/tasks-actions'; interface StateToProps { + deletingError: any; dumpingError: any; loadingError: any; tasksFetchingError: any; @@ -30,8 +31,10 @@ function mapStateToProps(state: CombinedState): StateToProps { const { activities } = tasks; const { dumps } = activities; const { loads } = activities; + const { deletes } = activities; return { + deletingError: deletes.deletingError, dumpingError: dumps.dumpingError, loadingError: loads.loadingError, tasksFetchingError: tasks.tasksFetchingError, @@ -54,6 +57,7 @@ type TasksPageContainerProps = StateToProps & DispatchToProps; function TasksPageContainer(props: TasksPageContainerProps) { return ( -
+
diff --git a/cvat-ui/src/index.tsx b/cvat-ui/src/index.tsx index ff279eef..f8c68d42 100644 --- a/cvat-ui/src/index.tsx +++ b/cvat-ui/src/index.tsx @@ -7,53 +7,72 @@ import createCVATStore from './store'; import { authorizedAsync } from './actions/auth-actions'; import { gettingFormatsAsync } from './actions/formats-actions'; +import { checkPluginsAsync } from './actions/plugins-actions'; +import { getUsersAsync } from './actions/users-actions'; import { CombinedState } from './reducers/root-reducer'; const cvatStore = createCVATStore(); interface StateToProps { + pluginsInitialized: boolean; userInitialized: boolean; + usersInitialized: boolean; formatsInitialized: boolean; gettingAuthError: any; gettingFormatsError: any; + gettingUsersError: any; user: any; } interface DispatchToProps { loadFormats: () => void; verifyAuthorized: () => void; + loadUsers: () => void; + initPlugins: () => void; } function mapStateToProps(state: CombinedState): StateToProps { + const { plugins } = state; const { auth } = state; const { formats } = state; + const { users } = state; return { + pluginsInitialized: plugins.initialized, userInitialized: auth.initialized, + usersInitialized: users.initialized, formatsInitialized: formats.initialized, gettingAuthError: auth.authError, - user: auth.user, + gettingUsersError: users.gettingUsersError, gettingFormatsError: formats.gettingFormatsError, + user: auth.user, }; } function mapDispatchToProps(dispatch: any): DispatchToProps { return { loadFormats: (): void => dispatch(gettingFormatsAsync()), - verifyAuthorized: (): void => dispatch(authorizedAsync()) + verifyAuthorized: (): void => dispatch(authorizedAsync()), + initPlugins: (): void => dispatch(checkPluginsAsync()), + loadUsers: (): void => dispatch(getUsersAsync()), }; } function reduxAppWrapper(props: StateToProps & DispatchToProps) { return ( ) diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index a6f8d675..68324b02 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -46,6 +46,12 @@ export interface TasksState { [tid: number]: string; // loader name }; }; + deletes: { + deletingError: any; + byTask: { + [tid: number]: boolean; // deleted (deleting if in dictionary) + }; + }; }; } @@ -55,3 +61,30 @@ export interface FormatsState { initialized: boolean; gettingFormatsError: any; } + +// eslint-disable-next-line import/prefer-default-export +export enum SupportedPlugins { + GIT_INTEGRATION = 'GIT_INTEGRATION', + AUTO_ANNOTATION = 'AUTO_ANNOTATION', + TF_ANNOTATION = 'TF_ANNOTATION', + ANALYTICS = 'ANALYTICS', +} + +export interface PluginsState { + initialized: boolean; + plugins: { + [name in SupportedPlugins]: boolean; + }; +} + +export interface TaskState { + task: Task | null; + taskFetchingError: any; + taskUpdatingError: any; +} + +export interface UsersState { + users: any[]; + initialized: boolean; + gettingUsersError: any; +} diff --git a/cvat-ui/src/reducers/plugins-reducer.ts b/cvat-ui/src/reducers/plugins-reducer.ts new file mode 100644 index 00000000..0cb717e8 --- /dev/null +++ b/cvat-ui/src/reducers/plugins-reducer.ts @@ -0,0 +1,32 @@ +import { AnyAction } from 'redux'; + +import { PluginsActionTypes } from '../actions/plugins-actions'; + +import { + PluginsState, +} from './interfaces'; + +const defaultState: PluginsState = { + initialized: false, + plugins: { + GIT_INTEGRATION: false, + AUTO_ANNOTATION: false, + TF_ANNOTATION: false, + ANALYTICS: false, + }, +}; + +export default function (state = defaultState, action: AnyAction): PluginsState { + switch (action.type) { + case PluginsActionTypes.CHECKED_ALL_PLUGINS: { + const { plugins } = action.payload; + return { + ...state, + initialized: true, + plugins, + }; + } + default: + return { ...state }; + } +} diff --git a/cvat-ui/src/reducers/root-reducer.ts b/cvat-ui/src/reducers/root-reducer.ts index 74e63790..57b4fee8 100644 --- a/cvat-ui/src/reducers/root-reducer.ts +++ b/cvat-ui/src/reducers/root-reducer.ts @@ -1,24 +1,36 @@ import { combineReducers, Reducer } from 'redux'; import authReducer from './auth-reducer'; import tasksReducer from './tasks-reducer'; +import usersReducer from './users-reducer'; import formatsReducer from './formats-reducer'; +import pluginsReducer from './plugins-reducer'; +import taskReducer from './task-reducer'; import { AuthState, TasksState, + UsersState, FormatsState, + PluginsState, + TaskState, } from './interfaces'; export interface CombinedState { auth: AuthState; tasks: TasksState; + users: UsersState; formats: FormatsState; + plugins: PluginsState; + activeTask: TaskState; } export default function createRootReducer(): Reducer { return combineReducers({ auth: authReducer, tasks: tasksReducer, + users: usersReducer, formats: formatsReducer, + plugins: pluginsReducer, + activeTask: taskReducer, }); } diff --git a/cvat-ui/src/reducers/task-reducer.ts b/cvat-ui/src/reducers/task-reducer.ts new file mode 100644 index 00000000..c91aa821 --- /dev/null +++ b/cvat-ui/src/reducers/task-reducer.ts @@ -0,0 +1,65 @@ +import { AnyAction } from 'redux'; + +import { TaskActionTypes } from '../actions/task-actions'; +import { Task, TaskState } from './interfaces'; + +const defaultState: TaskState = { + taskFetchingError: null, + taskUpdatingError: null, + task: null, +}; + +export default function (state = defaultState, action: AnyAction): TaskState { + switch (action.type) { + case TaskActionTypes.GET_TASK: + return { + ...state, + taskFetchingError: null, + taskUpdatingError: null, + }; + case TaskActionTypes.GET_TASK_SUCCESS: { + return { + ...state, + task: { + instance: action.payload.taskInstance, + preview: action.payload.previewImage, + }, + }; + } + case TaskActionTypes.GET_TASK_FAILED: { + return { + ...state, + task: null, + taskFetchingError: action.payload.error, + }; + } + case TaskActionTypes.UPDATE_TASK: { + return { + ...state, + taskUpdatingError: null, + taskFetchingError: null, + }; + } + case TaskActionTypes.UPDATE_TASK_SUCCESS: { + return { + ...state, + task: { + ...(state.task as Task), + instance: action.payload.taskInstance, + }, + }; + } + case TaskActionTypes.UPDATE_TASK_FAILED: { + return { + ...state, + task: { + ...(state.task as Task), + instance: action.payload.taskInstance, + }, + taskUpdatingError: action.payload.error, + }; + } + default: + return { ...state }; + } +} diff --git a/cvat-ui/src/reducers/tasks-reducer.ts b/cvat-ui/src/reducers/tasks-reducer.ts index 6a84ec65..691e8486 100644 --- a/cvat-ui/src/reducers/tasks-reducer.ts +++ b/cvat-ui/src/reducers/tasks-reducer.ts @@ -28,6 +28,10 @@ const defaultState: TasksState = { loadingDoneMessage: '', byTask: {}, }, + deletes: { + deletingError: null, + byTask: {}, + }, }, }; @@ -57,6 +61,13 @@ export default (inputState: TasksState = defaultState, action: AnyAction): Tasks case TasksActionTypes.GET_TASKS: return { ...state, + activities: { + ...state.activities, + deletes: { + deletingError: null, + byTask: {}, + }, + }, initialized: false, }; case TasksActionTypes.GET_TASKS_SUCCESS: { @@ -214,6 +225,64 @@ export default (inputState: TasksState = defaultState, action: AnyAction): Tasks }, }; } + case TasksActionTypes.DELETE_TASK: { + const { taskID } = action.payload; + + const deletesActivities = state.activities.deletes; + + const activities = { ...state.activities }; + activities.deletes = { ...activities.deletes }; + + activities.deletes.byTask[taskID] = false; + + return { + ...state, + activities: { + ...state.activities, + deletes: deletesActivities, + }, + }; + } + case TasksActionTypes.DELETE_TASK_SUCCESS: { + const { taskID } = action.payload; + + const deletesActivities = state.activities.deletes; + + const activities = { ...state.activities }; + activities.deletes = { ...activities.deletes }; + + activities.deletes.byTask[taskID] = true; + + return { + ...state, + activities: { + ...state.activities, + deletes: deletesActivities, + }, + }; + } + case TasksActionTypes.DELETE_TASK_FAILED: { + const { taskID } = action.payload; + const { error } = action.payload; + + const deletesActivities = state.activities.deletes; + + const activities = { ...state.activities }; + activities.deletes = { ...activities.deletes }; + + delete activities.deletes.byTask[taskID]; + + return { + ...state, + activities: { + ...state.activities, + deletes: { + ...deletesActivities, + deletingError: error, + }, + }, + }; + } default: return state; } diff --git a/cvat-ui/src/reducers/users-reducer.ts b/cvat-ui/src/reducers/users-reducer.ts new file mode 100644 index 00000000..cb821d1c --- /dev/null +++ b/cvat-ui/src/reducers/users-reducer.ts @@ -0,0 +1,38 @@ +import { AnyAction } from 'redux'; +import { UsersState } from './interfaces'; + +import { UsersActionTypes } from '../actions/users-actions'; + +const initialState: UsersState = { + users: [], + initialized: false, + gettingUsersError: null, +}; + +export default function (state: UsersState = initialState, action: AnyAction): UsersState { + switch (action.type) { + case UsersActionTypes.GET_USERS: + return { + ...state, + initialized: false, + gettingUsersError: null, + }; + case UsersActionTypes.GET_USERS_SUCCESS: + return { + ...state, + initialized: true, + users: action.payload.users, + }; + case UsersActionTypes.GET_USERS_FAILED: + return { + ...state, + initialized: true, + users: [], + gettingUsersError: action.payload.error, + }; + default: + return { + ...state, + }; + } +} diff --git a/cvat-ui/src/stylesheet.css b/cvat-ui/src/stylesheet.css index d522fd9d..77e4a70e 100644 --- a/cvat-ui/src/stylesheet.css +++ b/cvat-ui/src/stylesheet.css @@ -7,20 +7,32 @@ background: #D8D8D8; } -.left-header { +.cvat-left-header { width: 50%; display: flex; justify-content: flex-start; align-items: center; } -.right-header { +.cvat-right-header { width: 50%; display: flex; justify-content: flex-end; align-items: center; } +.cvat-flex { + display: flex; +} + +.cvat-flex-center { + align-items: center; +} + +.cvat-black-color { + color: black; +} + .cvat-header-buttons.ant-radio-group { height: 100%; display: flex; @@ -54,11 +66,7 @@ .anticon.cvat-logo-icon { display: flex; align-items: center; -} - -.anticon.cvat-back-icon { - display: flex; - align-items: center; + margin-right: 20px; } .anticon.cvat-logo-icon > img { @@ -78,7 +86,7 @@ filter: invert(0.4); } -.ant-btn.header-button { +.ant-btn.cvat-header-button { height: 100%; background: rgba(255, 255, 255, 0); border-radius: 0px; @@ -87,7 +95,7 @@ padding: 0 30px; } -.ant-btn.header-button:active { +.ant-btn.cvat-header-button:active { background: #C3C3C3; } @@ -118,10 +126,6 @@ margin: 16px; } -.anticon.cvat-header-menu-icon { - display: contents; -} - .anticon.cvat-header-menu-icon > img { width: 14px; margin: 10px; @@ -134,7 +138,7 @@ .cvat-title { font-weight: 400; - font-size: 23px; + font-size: 21px; color: black; padding-top: 5px; } @@ -283,12 +287,12 @@ padding: 0px; } -.cvat-task-preview { +.cvat-task-item-preview { max-width: 140px; max-height: 80px; } -.cvat-task-preview-wrapper { +.cvat-task-item-preview-wrapper { display: flex; justify-content: center; overflow: hidden; @@ -322,6 +326,171 @@ margin-right: 5px; } +.cvat-task-details { + width: 100%; + height: auto; + border: 1px solid #c3c3c3; + border-radius: 3px; + padding: 20px; + background: white; +} + +.cvat-task-details > div:nth-child(2) > div:nth-child(2) { + padding-left: 20px; +} + +.cvat-task-details > div:nth-child(2) > div:nth-child(2) > div:not(:first-child) { + margin-top: 20px; +} + +.cvat-task-job-list { + width: 100%; + height: auto; + border: 1px solid #c3c3c3; + border-radius: 3px; + margin-top: 20px; + padding: 20px; + background: white; +} + +.cvat-task-top-bar { + margin-top: 20px; + margin-bottom: 10px; +} + +.cvat-task-preview { + max-width: 252px; + max-height: 144px; +} + +.cvat-task-preview-wrapper { + display: flex; + justify-content: start; + overflow: hidden; + margin-bottom: 20px; +} + +.cvat-task-assignee-selector { + margin-left: 10px; + width: 150px; +} + +.cvat-open-bug-tracker-button { + margin-left: 15px; +} + +.cvat-raw-labels-viewer { + border-color: #d9d9d9 !important; + box-shadow: none !important; + border-top: none; + border-radius: 0px 0px 5px 5px; + min-height: 9em !important; + font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace; +} + +.cvat-constructor-viewer { + border: 1px solid #d9d9d9; + box-shadow: none; + border-top: none; + border-radius: 0px 0px 5px 5px; + padding: 5px; + display: flex; + flex-wrap: wrap; + overflow-y: auto; + min-height: 9em; +} + +.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 rgba(0, 0, 0, 0); + opacity: 0.6; +} + +.cvat-constructor-viewer-item > span { + margin-left: 5px; + color: white; +} + +.cvat-constructor-viewer-item > span > i:hover { + color: #111111; +} + +.cvat-constructor-viewer-item:hover { + opacity: 1; + border: 1px solid rgba(0, 0, 0, 255); +} + +.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; +} + +.labels-editor-new-label-button > i { + margin-left: 10px; +} + +.labels-editor-new-label-button > i:hover { + color: #111111; +} + +.cvat-label-constructor-creator > form:first-child { + margin-top: 10px; +} + +.cvat-label-constructor-updater > form:first-child { + margin-top: 10px; +} + +.cvat-attribute-constructor-form > div:first-child > div:nth-child(4) { + display: contents; +} + +.cvat-save-attribute-button:hover > i { + color: black; +} + +.cvat-delete-attribute-button:hover > i { + color: red; +} + +.ant-typography.cvat-jobs-header { + margin-bottom: 0px; +} + +/* Pagination in center */ +.cvat-task-jobs-table > div > div { + text-align: center; +} +.cvat-task-jobs-table > div > div > ul { + float: none !important; +} + +.cvat-job-completed-color { + color: #61C200; +} + +.cvat-job-validation-color { + color: #1890FF; +} + +.cvat-job-annotation-color { + color: #C1C1C1; +} + #cvat-create-task-button { padding: 0 30px; } diff --git a/cvat-ui/src/utils/plugin-checker.ts b/cvat-ui/src/utils/plugin-checker.ts new file mode 100644 index 00000000..edda0593 --- /dev/null +++ b/cvat-ui/src/utils/plugin-checker.ts @@ -0,0 +1,46 @@ +import getCore from '../core'; +import { SupportedPlugins } from '../reducers/interfaces'; + +const core = getCore(); + +// Easy plugin checker to understand what plugins supports by a server +class PluginChecker { + public static async check(plugin: SupportedPlugins): Promise { + const serverHost = core.config.backendAPI.slice(0, -7); + + switch (plugin) { + case SupportedPlugins.GIT_INTEGRATION: { + const response = await fetch(`${serverHost}/git/repository/meta/get`); + if (response.ok) { + return true; + } + return false; + } + case SupportedPlugins.AUTO_ANNOTATION: { + const response = await fetch(`${serverHost}/auto_annotation/meta/get`); + if (response.ok) { + return true; + } + return false; + } + case SupportedPlugins.TF_ANNOTATION: { + const response = await fetch(`${serverHost}/tensorflow/annotation/meta/get`); + if (response.ok) { + return true; + } + return false; + } + case SupportedPlugins.ANALYTICS: { + const response = await fetch(`${serverHost}/analytics/app/kibana`); + if (response.ok) { + return true; + } + return false; + } + default: + return false; + } + } +} + +export default PluginChecker; diff --git a/cvat-ui/src/utils/validation-patterns.ts b/cvat-ui/src/utils/validation-patterns.ts index 634c65b9..f3761ac5 100644 --- a/cvat-ui/src/utils/validation-patterns.ts +++ b/cvat-ui/src/utils/validation-patterns.ts @@ -35,6 +35,26 @@ const validationPatterns = { pattern: /^[a-zA-Z]{2,}(([',. -][a-zA-Z ])?[a-zA-Z]*)*$/, message: 'Invalid name', }, + + validateAttributeName: { + pattern: /\S+/, + message: 'Invalid name', + }, + + validateLabelName: { + pattern: /\S+/, + message: 'Invalid name', + }, + + validateAttributeValue: { + pattern: /\S+/, + message: 'Invalid attribute value', + }, + + validateURL: { + pattern: /^(https?):\/\/[^\s$.?#].[^\s]*$/, + message: 'URL is not valid', + }, }; export default { ...validationPatterns }; diff --git a/cvat-ui/webpack.config.js b/cvat-ui/webpack.config.js index 95316f28..ae568897 100644 --- a/cvat-ui/webpack.config.js +++ b/cvat-ui/webpack.config.js @@ -34,7 +34,9 @@ module.exports = { use: { loader: 'babel-loader', options: { - plugins: ['@babel/plugin-proposal-class-properties'], + plugins: ['@babel/plugin-proposal-class-properties', ['import', { + 'libraryName': 'antd', + }]], presets: [ ['@babel/preset-env', { targets: { @@ -48,7 +50,7 @@ module.exports = { }, }, }, { - test: /\.(css|scss)$/, + test: /\.(css|sass)$/, use: ['style-loader', 'css-loader'] }], },