diff --git a/CHANGELOG.md b/CHANGELOG.md index 8df9cb1c..ca2dea56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Siammask tracker as DL serverless function () - [Datumaro] Added model info and source info commands () - [Datumaro] Dataset statistics () +- Ability to change label color in tasks and predefined labels () - [Datumaro] Multi-dataset merge (https://github.com/opencv/cvat/pull/1695) +- Link to django admin page from UI () +- Notification message when users use wrong browser () ### Changed - Shape coordinates are rounded to 2 digits in dumped annotations () @@ -24,6 +27,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Issue loading openvino models for semi-automatic and automatic annotation () - Basic functions of CVAT works without activated nuclio dashboard +- Fixed error with creating task with labels with the same name () +- Django RQ dashboard view () ### Security - diff --git a/Dockerfile b/Dockerfile index 02535a02..e27dde28 100644 --- a/Dockerfile +++ b/Dockerfile @@ -55,7 +55,7 @@ RUN apt-get update && \ curl && \ curl https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash && \ apt-get --no-install-recommends install -y git-lfs && git lfs install && \ - python3 -m pip install --no-cache-dir -U pip==20.0.1 setuptools>=49.1.0 && \ + python3 -m pip install --no-cache-dir -U pip==20.0.1 setuptools>=49.1.0 wheel==0.35.1 && \ ln -fs /usr/share/zoneinfo/${TZ} /etc/localtime && \ dpkg-reconfigure -f noninteractive tzdata && \ add-apt-repository --remove ppa:mc3man/gstffmpeg-keep -y && \ diff --git a/cvat-core/package-lock.json b/cvat-core/package-lock.json index d79d54d0..0bc2c787 100644 --- a/cvat-core/package-lock.json +++ b/cvat-core/package-lock.json @@ -1,6 +1,6 @@ { "name": "cvat-core", - "version": "3.3.1", + "version": "3.5.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/cvat-core/package.json b/cvat-core/package.json index 7d75dbc5..ca65f045 100644 --- a/cvat-core/package.json +++ b/cvat-core/package.json @@ -1,6 +1,6 @@ { "name": "cvat-core", - "version": "3.4.0", + "version": "3.5.0", "description": "Part of Computer Vision Tool which presents an interface for client-side integration", "main": "babel.config.js", "scripts": { diff --git a/cvat-core/src/labels.js b/cvat-core/src/labels.js index fa92a611..ec2c0cbb 100644 --- a/cvat-core/src/labels.js +++ b/cvat-core/src/labels.js @@ -10,7 +10,6 @@ (() => { const { AttributeType, - colors, } = require('./enums'); const { ArgumentError } = require('./exceptions'); @@ -150,9 +149,6 @@ } } - if (typeof (data.id) !== 'undefined') { - data.color = colors[data.id % colors.length]; - } data.attributes = []; if (Object.prototype.hasOwnProperty.call(initialData, 'attributes') @@ -193,10 +189,10 @@ color: { get: () => data.color, set: (color) => { - if (colors.includes(color)) { + if (typeof color === 'string' && color.match(/^#[0-9a-f]{6}$|^$/)) { data.color = color; } else { - throw new ArgumentError('Trying to set unknown color'); + throw new ArgumentError('Trying to set wrong color format'); } }, }, @@ -217,6 +213,7 @@ const object = { name: this.name, attributes: [...this.attributes.map((el) => el.toJSON())], + color: this.color, }; if (typeof (this.id) !== 'undefined') { diff --git a/cvat-ui/package-lock.json b/cvat-ui/package-lock.json index 8def8aaa..4f97ca1e 100644 --- a/cvat-ui/package-lock.json +++ b/cvat-ui/package-lock.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.7.1", + "version": "1.8.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1046,6 +1046,11 @@ "integrity": "sha512-opgSsy+cEF9N8MgaVPnWVtdJ3o4mV2aMHvDq7thkQUFt0EuOHJon4rQpJfhjmNHB+ikl0Cd6WhWIErOyQ+f7tw==", "dev": true }, + "@types/platform": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/platform/-/platform-1.3.2.tgz", + "integrity": "sha512-Tn6OuJDAG7bJbyi4R7HqcxXp1w2lmIxVXqyNhPt1Bm0FO2EWIi3CI87JVzF7ncqK0ZMPuUycS3wTMIk85EeF1Q==" + }, "@types/prop-types": { "version": "15.7.3", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", @@ -12247,6 +12252,7 @@ "convert-source-map": "^1.1.0", "fs-readdir-recursive": "^1.1.0", "glob": "^7.0.0", + "lodash": "^4.17.13", "make-dir": "^2.1.0", "slash": "^2.0.0", "source-map": "^0.5.0" @@ -12286,6 +12292,7 @@ "debug": "^4.1.0", "gensync": "^1.0.0-beta.1", "json5": "^2.1.0", + "lodash": "^4.17.13", "resolve": "^1.3.2", "semver": "^5.4.1", "source-map": "^0.5.0" @@ -12313,6 +12320,7 @@ "requires": { "@babel/types": "^7.8.7", "jsesc": "^2.5.1", + "lodash": "^4.17.13", "source-map": "^0.5.0" } }, @@ -12371,7 +12379,8 @@ "integrity": "sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g==", "requires": { "@babel/helper-function-name": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/types": "^7.8.3", + "lodash": "^4.17.13" } }, "@babel/helper-explode-assignable-expression": { @@ -12435,7 +12444,8 @@ "@babel/helper-simple-access": "^7.8.3", "@babel/helper-split-export-declaration": "^7.8.3", "@babel/template": "^7.8.6", - "@babel/types": "^7.8.6" + "@babel/types": "^7.8.6", + "lodash": "^4.17.13" } }, "@babel/helper-optimise-call-expression": { @@ -12454,7 +12464,10 @@ "@babel/helper-regex": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.8.3.tgz", - "integrity": "sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ==" + "integrity": "sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ==", + "requires": { + "lodash": "^4.17.13" + } }, "@babel/helper-remap-async-to-generator": { "version": "7.8.3", @@ -12700,7 +12713,8 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz", "integrity": "sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.8.3", + "lodash": "^4.17.13" } }, "@babel/plugin-transform-classes": { @@ -13038,7 +13052,8 @@ "@babel/parser": "^7.8.6", "@babel/types": "^7.8.6", "debug": "^4.1.0", - "globals": "^11.1.0" + "globals": "^11.1.0", + "lodash": "^4.17.13" }, "dependencies": { "debug": { @@ -13062,6 +13077,7 @@ "integrity": "sha512-k2TreEHxFA4CjGkL+GYjRyx35W0Mr7DP5+9q6WMkyKXB+904bYmG40syjMFV0oLlhhFCwWl0vA0DyzTDkwAiJw==", "requires": { "esutils": "^2.0.2", + "lodash": "^4.17.13", "to-fast-properties": "^2.0.0" } }, @@ -14212,7 +14228,10 @@ "catharsis": { "version": "0.8.11", "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.8.11.tgz", - "integrity": "sha512-a+xUyMV7hD1BrDQA/3iPV7oc+6W26BgVJO05PGEoatMyIuPScQKsde6i3YorWX1qs+AZjnJ18NqdKoCtKiNh1g==" + "integrity": "sha512-a+xUyMV7hD1BrDQA/3iPV7oc+6W26BgVJO05PGEoatMyIuPScQKsde6i3YorWX1qs+AZjnJ18NqdKoCtKiNh1g==", + "requires": { + "lodash": "^4.17.14" + } }, "chalk": { "version": "2.4.2", @@ -15188,6 +15207,7 @@ "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.3.0", + "lodash": "^4.17.14", "minimatch": "^3.0.4", "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", @@ -15559,6 +15579,7 @@ "js-yaml": "^3.5.1", "json-stable-stringify": "^1.0.0", "levn": "^0.3.0", + "lodash": "^4.0.0", "mkdirp": "^0.5.0", "natural-compare": "^1.4.0", "optionator": "^0.8.2", @@ -15633,6 +15654,7 @@ "cli-cursor": "^1.0.1", "cli-width": "^2.0.0", "figures": "^1.3.5", + "lodash": "^4.3.0", "readline2": "^1.0.1", "run-async": "^0.1.0", "rx-lite": "^3.1.2", @@ -15717,6 +15739,7 @@ "ajv": "^4.7.0", "ajv-keywords": "^1.0.0", "chalk": "^1.1.1", + "lodash": "^4.0.0", "slice-ansi": "0.0.4", "string-width": "^2.0.0" }, @@ -17168,6 +17191,7 @@ "cli-width": "^2.0.0", "external-editor": "^3.0.3", "figures": "^2.0.0", + "lodash": "^4.17.12", "mute-stream": "0.0.7", "run-async": "^2.2.0", "rxjs": "^6.4.0", @@ -18279,6 +18303,11 @@ "path-exists": "^3.0.0" } }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + }, "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", @@ -19451,7 +19480,10 @@ "request-promise-core": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", - "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==" + "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", + "requires": { + "lodash": "^4.17.15" + } }, "request-promise-native": { "version": "1.0.8", @@ -19492,7 +19524,10 @@ "requizzle": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz", - "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==" + "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==", + "requires": { + "lodash": "^4.17.14" + } }, "resolve": { "version": "1.15.1", @@ -20174,6 +20209,7 @@ "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", "requires": { "ajv": "^6.10.2", + "lodash": "^4.17.14", "slice-ansi": "^2.1.0", "string-width": "^3.0.0" } @@ -26159,6 +26195,11 @@ "find-up": "^3.0.0" } }, + "platform": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", + "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==" + }, "portfinder": { "version": "1.0.25", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz", diff --git a/cvat-ui/package.json b/cvat-ui/package.json index f20e3eb6..64353225 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.7.1", + "version": "1.8.1", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { @@ -47,6 +47,7 @@ "worker-loader": "^2.0.0" }, "dependencies": { + "@types/platform": "^1.3.2", "@types/react": "^16.9.2", "@types/react-color": "^3.0.2", "@types/react-dom": "^16.9.0", @@ -62,6 +63,7 @@ "dotenv-webpack": "^1.7.0", "error-stack-parser": "^2.0.6", "moment": "^2.24.0", + "platform": "^1.3.6", "prop-types": "^15.7.2", "react": "^16.9.0", "react-color": "^2.18.1", diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 5aa6c457..3bea5b6c 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -148,8 +148,6 @@ export enum AnnotationActionTypes { GROUP_ANNOTATIONS_FAILED = 'GROUP_ANNOTATIONS_FAILED', SPLIT_ANNOTATIONS_SUCCESS = 'SPLIT_ANNOTATIONS_SUCCESS', SPLIT_ANNOTATIONS_FAILED = 'SPLIT_ANNOTATIONS_FAILED', - CHANGE_LABEL_COLOR_SUCCESS = 'CHANGE_LABEL_COLOR_SUCCESS', - CHANGE_LABEL_COLOR_FAILED = 'CHANGE_LABEL_COLOR_FAILED', UPDATE_TAB_CONTENT_HEIGHT = 'UPDATE_TAB_CONTENT_HEIGHT', COLLAPSE_SIDEBAR = 'COLLAPSE_SIDEBAR', COLLAPSE_APPEARANCE = 'COLLAPSE_APPEARANCE', @@ -1288,44 +1286,6 @@ ThunkAction { }; } -export function changeLabelColorAsync( - label: any, - color: string, -): ThunkAction { - return async (dispatch: ActionCreator): Promise => { - try { - const { - filters, - showAllInterpolationTracks, - jobInstance, - frame, - } = receiveAnnotationsParameters(); - - const updatedLabel = label; - updatedLabel.color = color; - const states = await jobInstance.annotations - .get(frame, showAllInterpolationTracks, filters); - const history = await jobInstance.actions.get(); - - dispatch({ - type: AnnotationActionTypes.CHANGE_LABEL_COLOR_SUCCESS, - payload: { - label: updatedLabel, - history, - states, - }, - }); - } catch (error) { - dispatch({ - type: AnnotationActionTypes.CHANGE_LABEL_COLOR_FAILED, - payload: { - error, - }, - }); - } - }; -} - export function changeGroupColorAsync( group: number, color: string, diff --git a/cvat-ui/src/actions/tasks-actions.ts b/cvat-ui/src/actions/tasks-actions.ts index 2aa9e630..5eb6671d 100644 --- a/cvat-ui/src/actions/tasks-actions.ts +++ b/cvat-ui/src/actions/tasks-actions.ts @@ -344,10 +344,12 @@ function createTask(): AnyAction { return action; } -function createTaskSuccess(): AnyAction { +function createTaskSuccess(taskId: number): AnyAction { const action = { type: TasksActionTypes.CREATE_TASK_SUCCESS, - payload: {}, + payload: { + taskId, + }, }; return action; @@ -434,10 +436,10 @@ ThunkAction, {}, {}, AnyAction> { dispatch(createTask()); try { - await taskInstance.save((status: string): void => { + const savedTask = await taskInstance.save((status: string): void => { dispatch(createTaskUpdateStatus(status)); }); - dispatch(createTaskSuccess()); + dispatch(createTaskSuccess(savedTask.id)); } catch (error) { dispatch(createTaskFailed(error)); } diff --git a/cvat-ui/src/assets/colorize-icon.svg b/cvat-ui/src/assets/colorize-icon.svg new file mode 100644 index 00000000..300f5408 --- /dev/null +++ b/cvat-ui/src/assets/colorize-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/cvat-ui/src/components/annotation-page/appearance-block.tsx b/cvat-ui/src/components/annotation-page/appearance-block.tsx index d242c387..06a060fa 100644 --- a/cvat-ui/src/components/annotation-page/appearance-block.tsx +++ b/cvat-ui/src/components/annotation-page/appearance-block.tsx @@ -166,9 +166,9 @@ function AppearanceBlock(props: Props): JSX.Element {
Color by + {ColorBy.LABEL} {ColorBy.INSTANCE} {ColorBy.GROUP} - {ColorBy.LABEL} Opacity = colors.length) { - break; - } - const color = colors[idx]; - antdCols.push( - - + )} + + + + + + + + + + )} + title={( + + + + Select color + + + + + + + + + + )} + placement={placement || 'left'} + overlayClassName='cvat-label-color-picker' + trigger='click' + visible={typeof visible === 'boolean' ? visible : pickerVisible} + onVisibleChange={changeVisible} + > + {children} + + ); +} + +export default React.forwardRef(ColorPicker); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/label-item.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/label-item.tsx index 96a99a7e..e5067658 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/label-item.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/label-item.tsx @@ -5,32 +5,26 @@ import React from 'react'; import { Row, Col } from 'antd/lib/grid'; import Icon from 'antd/lib/icon'; -import Popover from 'antd/lib/popover'; import Button from 'antd/lib/button'; import Text from 'antd/lib/typography/Text'; -import ColorChanger from 'components/annotation-page/standard-workspace/objects-side-bar/color-changer'; interface Props { labelName: string; labelColor: string; - labelColors: string[]; visible: boolean; statesHidden: boolean; statesLocked: boolean; - changeColorShortcut: string; hideStates(): void; showStates(): void; lockStates(): void; unlockStates(): void; - changeColor(color: string): void; } function LabelItemComponent(props: Props): JSX.Element { const { labelName, labelColor, - labelColors, visible, statesHidden, statesLocked, @@ -38,8 +32,6 @@ function LabelItemComponent(props: Props): JSX.Element { showStates, lockStates, unlockStates, - changeColor, - changeColorShortcut, } = props; return ( @@ -51,19 +43,7 @@ function LabelItemComponent(props: Props): JSX.Element { style={{ display: visible ? 'flex' : 'none' }} > - - )} - > - + + + + )} + ); + notification.info({ message: 'The task has been created', + btn, }); this.basicConfigurationComponent.resetFields(); @@ -253,3 +265,5 @@ export default class CreateTaskContent extends React.PureComponent ); } } + +export default withRouter(CreateTaskContent); diff --git a/cvat-ui/src/components/create-task-page/create-task-page.tsx b/cvat-ui/src/components/create-task-page/create-task-page.tsx index 15c5f18c..39549ea6 100644 --- a/cvat-ui/src/components/create-task-page/create-task-page.tsx +++ b/cvat-ui/src/components/create-task-page/create-task-page.tsx @@ -16,6 +16,7 @@ interface Props { onCreate: (data: CreateTaskData) => void; status: string; error: string; + taskId: number | null; installedGit: boolean; } @@ -23,6 +24,7 @@ export default function CreateTaskPage(props: Props): JSX.Element { const { error, status, + taskId, onCreate, installedGit, } = props; @@ -66,6 +68,7 @@ export default function CreateTaskPage(props: Props): JSX.Element { Create a new task void; @@ -267,12 +271,42 @@ class CVATApplication extends React.PureComponent + + + + {`The browser you are using is ${info.name} ${info.version} based on ${info.engine} .` + + ' CVAT was tested in the latest versions of Chrome and Firefox.' + + ' We recommend to use Chrome (or another Chromium based browser)'} + + + + + + + {`The operating system is ${info.os}`} + + + + + ), + onOk: () => stopNotifications(true), + }); + } + + if (readyForRender) { if (user) { return ( - +
diff --git a/cvat-ui/src/components/header/header.tsx b/cvat-ui/src/components/header/header.tsx index 5391fe8d..379e7c70 100644 --- a/cvat-ui/src/components/header/header.tsx +++ b/cvat-ui/src/components/header/header.tsx @@ -4,8 +4,8 @@ import './styles.scss'; import React from 'react'; -import { RouteComponentProps } from 'react-router'; -import { withRouter } from 'react-router-dom'; +import { connect } from 'react-redux'; +import { useHistory } from 'react-router'; import { Row, Col } from 'antd/lib/grid'; import Layout from 'antd/lib/layout'; import Icon from 'antd/lib/icon'; @@ -15,50 +15,129 @@ import Dropdown from 'antd/lib/dropdown'; import Modal from 'antd/lib/modal'; import Text from 'antd/lib/typography/Text'; -import { CVATLogo, AccountIcon } from 'icons'; +import getCore from 'cvat-core-wrapper'; import consts from 'consts'; + +import { CVATLogo, AccountIcon } from 'icons'; import ChangePasswordDialog from 'components/change-password-modal/change-password-modal'; +import { switchSettingsDialog as switchSettingsDialogAction } from 'actions/settings-actions'; +import { logoutAsync, authActions } from 'actions/auth-actions'; +import { SupportedPlugins, CombinedState } from 'reducers/interfaces'; import SettingsModal from './settings-modal/settings-modal'; -interface HeaderContainerProps { - onLogout: () => void; - switchSettingsDialog: (show: boolean) => void; - switchChangePasswordDialog: (show: boolean) => void; - logoutFetching: boolean; - changePasswordFetching: boolean; - installedAnalytics: boolean; - serverHost: string; - username: string; - toolName: string; - serverVersion: string; - serverDescription: string; - coreVersion: string; - canvasVersion: string; - uiVersion: string; +const core = getCore(); + +interface Tool { + name: string; + description: string; + server: { + host: string; + version: string; + }; + core: { + version: string; + }; + canvas: { + version: string; + }; + ui: { + version: string; + }; +} + +interface StateToProps { + user: any; + tool: Tool; switchSettingsShortcut: string; settingsDialogShown: boolean; changePasswordDialogShown: boolean; + changePasswordFetching: boolean; + logoutFetching: boolean; + installedAnalytics: boolean; renderChangePasswordItem: boolean; } -type Props = HeaderContainerProps & RouteComponentProps; +interface DispatchToProps { + onLogout: () => void; + switchSettingsDialog: (show: boolean) => void; + switchChangePasswordDialog: (show: boolean) => void; +} + +function mapStateToProps(state: CombinedState): StateToProps { + const { + auth: { + user, + fetching: logoutFetching, + fetching: changePasswordFetching, + showChangePasswordDialog: changePasswordDialogShown, + allowChangePassword: renderChangePasswordItem, + }, + plugins: { + list, + }, + about: { + server, + packageVersion, + }, + shortcuts: { + normalizedKeyMap, + }, + settings: { + showDialog: settingsDialogShown, + }, + } = state; + + return { + user, + tool: { + name: server.name as string, + description: server.description as string, + server: { + host: core.config.backendAPI.slice(0, -7), + version: server.version as string, + }, + canvas: { + version: packageVersion.canvas, + }, + core: { + version: packageVersion.core, + }, + ui: { + version: packageVersion.ui, + }, + }, + switchSettingsShortcut: normalizedKeyMap.SWITCH_SETTINGS, + settingsDialogShown, + changePasswordDialogShown, + changePasswordFetching, + logoutFetching, + installedAnalytics: list[SupportedPlugins.ANALYTICS], + renderChangePasswordItem, + }; +} + +function mapDispatchToProps(dispatch: any): DispatchToProps { + return { + onLogout: (): void => dispatch(logoutAsync()), + switchSettingsDialog: (show: boolean): void => dispatch(switchSettingsDialogAction(show)), + switchChangePasswordDialog: (show: boolean): void => ( + dispatch(authActions.switchChangePasswordDialog(show)) + ), + }; +} + +type Props = StateToProps & DispatchToProps; function HeaderContainer(props: Props): JSX.Element { const { + user, + tool, installedAnalytics, - username, - toolName, - serverHost, - serverVersion, - serverDescription, - coreVersion, - canvasVersion, - uiVersion, - onLogout, logoutFetching, changePasswordFetching, settingsDialogShown, switchSettingsShortcut, + onLogout, switchSettingsDialog, switchChangePasswordDialog, renderChangePasswordItem, @@ -72,20 +151,22 @@ function HeaderContainer(props: Props): JSX.Element { GITHUB_URL, } = consts; - function aboutModal(): void { + const history = useHistory(); + + function showAboutModal(): void { Modal.info({ - title: `${toolName}`, + title: `${tool.name}`, content: (

- {`${serverDescription}`} + {`${tool.description}`}

Server version: - {` ${serverVersion}`} + {` ${tool.server.version}`}

@@ -93,7 +174,7 @@ function HeaderContainer(props: Props): JSX.Element { Core version: - {` ${coreVersion}`} + {` ${tool.core.version}`}

@@ -101,7 +182,7 @@ function HeaderContainer(props: Props): JSX.Element { Canvas version: - {` ${canvasVersion}`} + {` ${tool.canvas.version}`}

@@ -109,7 +190,7 @@ function HeaderContainer(props: Props): JSX.Element { UI version: - {` ${uiVersion}`} + {` ${tool.ui.version}`}

@@ -131,16 +212,27 @@ function HeaderContainer(props: Props): JSX.Element { const menu = ( + {user.isStaff && ( + { + // false positive + // eslint-disable-next-line + window.open(`${tool.server.host}/admin`, '_blank'); + }} + > + + Admin page + + )} + switchSettingsDialog(true) - } + onClick={() => switchSettingsDialog(true)} > Settings - aboutModal()}> + About @@ -174,8 +266,12 @@ function HeaderContainer(props: Props): JSX.Element { className='cvat-header-button' type='link' value='tasks' + href='/tasks?page=1' onClick={ - (): void => props.history.push('/tasks?page=1') + (event: React.MouseEvent): void => { + event.preventDefault(); + history.push('/tasks?page=1'); + } } > Tasks @@ -184,8 +280,12 @@ function HeaderContainer(props: Props): JSX.Element { className='cvat-header-button' type='link' value='models' + href='/models' onClick={ - (): void => props.history.push('/models') + (event: React.MouseEvent): void => { + event.preventDefault(); + history.push('/models'); + } } > Models @@ -195,11 +295,13 @@ function HeaderContainer(props: Props): JSX.Element { @@ -482,6 +500,37 @@ class LabelForm extends React.PureComponent { ); } + private renderChangeColorButton(): JSX.Element { + const { label, form } = this.props; + + return ( + + + { + form.getFieldDecorator('labelColor', { + initialValue: (label && label.color) ? label.color : undefined, + })( + + + + + , + ) + } + + + ); + } + public render(): JSX.Element { const { label, @@ -502,6 +551,8 @@ class LabelForm extends React.PureComponent { { this.renderLabelNameInput() } + { this.renderChangeColorButton() } + { this.renderNewAttributeButton() } { attributeItems.length > 0 diff --git a/cvat-ui/src/components/labels-editor/labels-editor.tsx b/cvat-ui/src/components/labels-editor/labels-editor.tsx index 4ade5100..8c0ad072 100644 --- a/cvat-ui/src/components/labels-editor/labels-editor.tsx +++ b/cvat-ui/src/components/labels-editor/labels-editor.tsx @@ -64,6 +64,7 @@ export default class LabelsEditor return { name: label.name, id: label.id || idGenerator(), + color: label.color, attributes: label.attributes.map((attr: any): Attribute => ( { id: attr.id || idGenerator(), @@ -198,6 +199,7 @@ export default class LabelsEditor return { name: label.name, id: label.id < 0 ? undefined : label.id, + color: label.color, attributes: label.attributes.map((attr: Attribute): any => ( { name: attr.name, @@ -221,6 +223,7 @@ export default class LabelsEditor } public render(): JSX.Element { + const { labels } = this.props; const { savedLabels, unsavedLabels, @@ -319,6 +322,7 @@ export default class LabelsEditor constructorMode === ConstructorMode.CREATE && ( l.name)} onCreate={this.handleCreate} /> ) diff --git a/cvat-ui/src/components/labels-editor/raw-viewer.tsx b/cvat-ui/src/components/labels-editor/raw-viewer.tsx index eb798cd2..f7b95a08 100644 --- a/cvat-ui/src/components/labels-editor/raw-viewer.tsx +++ b/cvat-ui/src/components/labels-editor/raw-viewer.tsx @@ -28,6 +28,10 @@ class RawViewer extends React.PureComponent { if (!Array.isArray(parsed)) { callback('Field is expected to be a JSON array'); } + const labelNames = parsed.map((label: Label) => label.name); + if (new Set(labelNames).size !== labelNames.length) { + callback('Label names must be unique for the task'); + } for (const label of parsed) { try { diff --git a/cvat-ui/src/components/labels-editor/styles.scss b/cvat-ui/src/components/labels-editor/styles.scss index ba03ef1a..1e34e992 100644 --- a/cvat-ui/src/components/labels-editor/styles.scss +++ b/cvat-ui/src/components/labels-editor/styles.scss @@ -87,3 +87,21 @@ textarea.ant-input.cvat-raw-labels-viewer { .cvat-delete-attribute-button:hover > i { color: $danger-icon-color; } + +.cvat-new-attribute-button { + width: 100%; +} + +.cvat-change-task-label-color-button { + width: 100%; + + .ant-badge-status-text { + margin-left: 15px; + } +} + +.cvat-change-task-label-color-badge .ant-badge-status-dot { + width: 15px; + height: 15px; + border-radius: unset; +} diff --git a/cvat-ui/src/components/login-page/cookie-policy-drawer.tsx b/cvat-ui/src/components/login-page/cookie-policy-drawer.tsx index 6c765c94..51f9b80e 100644 --- a/cvat-ui/src/components/login-page/cookie-policy-drawer.tsx +++ b/cvat-ui/src/components/login-page/cookie-policy-drawer.tsx @@ -39,7 +39,7 @@ function CookieDrawer(): JSX.Element { This site uses cookies for functionality, analytics, and advertising purposes as described in our Cookie and Similar Technologies Notice. To see what cookies we serve and set your preferences, please visit our - Cookie Consent Tool + Cookie Consent Tool . By continuing to use our website, you agree to our use of cookies.