From 7c6d6c9c30f0a6c6c6e33e8e63d3aad65c7b1c0e Mon Sep 17 00:00:00 2001 From: Flymax Date: Mon, 29 Jun 2020 01:00:40 +0900 Subject: [PATCH 01/15] Bug fixes: logout --- cvat-ui/src/containers/header/header.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cvat-ui/src/containers/header/header.tsx b/cvat-ui/src/containers/header/header.tsx index 59dc3f023f13..ed0e55f631cb 100644 --- a/cvat-ui/src/containers/header/header.tsx +++ b/cvat-ui/src/containers/header/header.tsx @@ -32,7 +32,7 @@ interface StateToProps { } interface DispatchToProps { - onLogout: typeof logoutAsync; + onLogout: () => void; switchSettingsDialog: (show: boolean) => void; } @@ -80,7 +80,7 @@ function mapStateToProps(state: CombinedState): StateToProps { function mapDispatchToProps(dispatch: any): DispatchToProps { return { - onLogout: logoutAsync, + onLogout: (): void => dispatch(logoutAsync()), switchSettingsDialog: (show: boolean): void => dispatch(switchSettingsDialog(show)), }; } From 38b19cbe7740b55990820ba2d577397f7d274cc3 Mon Sep 17 00:00:00 2001 From: lizhming Date: Tue, 30 Jun 2020 20:24:31 +0900 Subject: [PATCH 02/15] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef041139e177..79bdeceb694c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,7 +54,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - A couple of exceptions in AAM related with early object activation () - Propagation from the latest frame () - Number attribute value validation (didn't work well with floats) () - +- Fixed Logout function () ### Security - SQL injection in Django `CVE-2020-9402` () From af8db0ccdff249214c6b034fe93a07d869674c5f Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 30 Jun 2020 22:58:10 +0300 Subject: [PATCH 03/15] Bump django-extensions from 2.2.9 to 3.0.1 in /cvat/requirements (#1822) Bumps [django-extensions](https://github.com/django-extensions/django-extensions) from 2.2.9 to 3.0.1. - [Release notes](https://github.com/django-extensions/django-extensions/releases) - [Changelog](https://github.com/django-extensions/django-extensions/blob/master/CHANGELOG.md) - [Commits](https://github.com/django-extensions/django-extensions/compare/2.2.9...3.0.1) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- cvat/requirements/development.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cvat/requirements/development.txt b/cvat/requirements/development.txt index 6d8abb5b7d3c..b3f846268c33 100644 --- a/cvat/requirements/development.txt +++ b/cvat/requirements/development.txt @@ -8,6 +8,6 @@ pylint-django==2.0.15 pylint-plugin-utils==0.6 rope==0.17.0 wrapt==1.12.1 -django-extensions==2.2.9 +django-extensions==3.0.1 Werkzeug==1.0.1 snakeviz==2.1.0 From 6d85840779b03dc61de77b1b2f71dc99fa0f37ff Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 30 Jun 2020 22:58:50 +0300 Subject: [PATCH 04/15] Bump python-ldap from 3.3.0 to 3.3.1 in /cvat/requirements (#1821) Bumps [python-ldap](https://github.com/python-ldap/python-ldap) from 3.3.0 to 3.3.1. - [Release notes](https://github.com/python-ldap/python-ldap/releases) - [Commits](https://github.com/python-ldap/python-ldap/compare/python-ldap-3.3.0...python-ldap-3.3.1) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- cvat/requirements/base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cvat/requirements/base.txt b/cvat/requirements/base.txt index 9dfb577047f2..fae9a95fa1be 100644 --- a/cvat/requirements/base.txt +++ b/cvat/requirements/base.txt @@ -8,7 +8,7 @@ django-rq==2.0.0 EasyProcess==0.3 Pillow==7.1.2 numpy==1.18.5 -python-ldap==3.3.0 +python-ldap==3.3.1 pytz==2020.1 pyunpack==0.2.1 rcssmin==1.0.6 From bb926fc2093762e46ad593a807af2ffee4913c90 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Tue, 30 Jun 2020 23:07:02 +0300 Subject: [PATCH 05/15] Updated version of cvat-ui. --- cvat-ui/package-lock.json | 2 +- cvat-ui/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cvat-ui/package-lock.json b/cvat-ui/package-lock.json index 516c99170442..4b8be0d768c3 100644 --- a/cvat-ui/package-lock.json +++ b/cvat-ui/package-lock.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.5.0", + "version": "1.5.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/cvat-ui/package.json b/cvat-ui/package.json index 157bed2dc226..9e698d2cd6a4 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.5.0", + "version": "1.5.1", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { From f1b01780cca03cbba31960f593c1522884f89e35 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Wed, 1 Jul 2020 00:23:44 +0300 Subject: [PATCH 06/15] Add release 1.1.0-alpha date. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 127dabb6a4bc..9fb914486f13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [1.1.0-alpha] - Unreleased +## [1.1.0-alpha] - 2020-06-30 ### Added - Throttling policy for unauthenticated users () - Added default label color table for mask export () From 0b4ca7eb0c4a604e8913fc0161a2d05cc2454cad Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 1 Jul 2020 21:28:14 +0300 Subject: [PATCH 07/15] Bump pillow from 7.1.2 to 7.2.0 in /cvat/requirements (#1828) Bumps [pillow](https://github.com/python-pillow/Pillow) from 7.1.2 to 7.2.0. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst) - [Commits](https://github.com/python-pillow/Pillow/compare/7.1.2...7.2.0) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- cvat/requirements/base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cvat/requirements/base.txt b/cvat/requirements/base.txt index fae9a95fa1be..b6fd124dae6d 100644 --- a/cvat/requirements/base.txt +++ b/cvat/requirements/base.txt @@ -6,7 +6,7 @@ django-cacheops==5.0 django-compressor==2.4 django-rq==2.0.0 EasyProcess==0.3 -Pillow==7.1.2 +Pillow==7.2.0 numpy==1.18.5 python-ldap==3.3.1 pytz==2020.1 From 76280be4ad4c6143533b6a3960a429d33e7f10a8 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Wed, 1 Jul 2020 21:45:27 +0300 Subject: [PATCH 08/15] Add v1.1.0-beta into changelog and update the server version. --- CHANGELOG.md | 19 +++++++++++++++++++ cvat/__init__.py | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fb914486f13..7c63a6c46f46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,25 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.1.0-beta] - Unreleased +### Added +- + +### Changed +- + +### Deprecated +- + +### Removed +- + +### Fixed +- + +### Security +- + ## [1.1.0-alpha] - 2020-06-30 ### Added - Throttling policy for unauthenticated users () diff --git a/cvat/__init__.py b/cvat/__init__.py index 375b5b868483..e9d49047031b 100644 --- a/cvat/__init__.py +++ b/cvat/__init__.py @@ -4,6 +4,6 @@ from cvat.utils.version import get_version -VERSION = (1, 1, 0, 'alpha', 0) +VERSION = (1, 1, 0, 'beta', 0) __version__ = get_version(VERSION) From 757f0ade177bc5ae5a5fe98910a457a601183fe3 Mon Sep 17 00:00:00 2001 From: Boris Sekachev <40690378+bsekachev@users.noreply.github.com> Date: Thu, 2 Jul 2020 14:29:07 +0300 Subject: [PATCH 09/15] [CVAT-UI]: Appearance settings in AAM, keyframe navigation and other buttons in AAM (#1820) --- CHANGELOG.md | 2 + cvat-ui/package-lock.json | 2 +- cvat-ui/package.json | 2 +- cvat-ui/src/actions/annotation-actions.ts | 58 +- .../annotation-page/appearance-block.tsx | 214 +++++ .../attribute-annotation-sidebar.tsx | 142 +++- .../attribute-editor.tsx | 13 +- .../object-basics-edtior.tsx | 12 +- .../objects-side-bar/appearance-block.tsx | 107 --- .../object-item-attribute.tsx | 175 ++++ .../objects-side-bar/object-item-basics.tsx | 131 +++ .../objects-side-bar/object-item-buttons.tsx | 262 ++++++ .../objects-side-bar/object-item-details.tsx | 86 ++ .../objects-side-bar/object-item-menu.tsx | 139 +++ .../objects-side-bar/object-item.tsx | 802 +----------------- .../objects-side-bar/objects-side-bar.tsx | 121 +-- .../standard-workspace/standard-workspace.tsx | 4 +- .../global-error-boundary.tsx | 5 +- .../objects-side-bar/object-buttons.tsx | 296 +++++++ .../objects-side-bar/object-item.tsx | 168 ---- .../objects-side-bar/objects-side-bar.tsx | 254 ------ .../create-model-page/create-model-page.tsx | 9 +- .../create-task-page/create-task-page.tsx | 9 +- cvat-ui/src/containers/header/header.tsx | 1 - .../model-runner-dialog.tsx | 10 +- .../src/containers/task-page/task-page.tsx | 9 +- .../src/containers/tasks-page/tasks-page.tsx | 11 +- cvat-ui/src/utils/redux.ts | 2 +- 28 files changed, 1526 insertions(+), 1520 deletions(-) create mode 100644 cvat-ui/src/components/annotation-page/appearance-block.tsx delete mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/appearance-block.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-attribute.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-basics.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-buttons.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-details.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-menu.tsx create mode 100644 cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-buttons.tsx delete mode 100644 cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c63a6c46f46..fb7e627aeb21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Ability to change orientation for poylgons/polylines in context menu () - Ability to set the first point for polygons in points context menu () - Added new tag annotation workspace () +- Appearance block in attribute annotation mode () +- Keyframe navigations and some switchers in attribute annotation mode () ### Changed - Removed information about e-mail from the basic user information () diff --git a/cvat-ui/package-lock.json b/cvat-ui/package-lock.json index 4b8be0d768c3..ed0924cdfe7b 100644 --- a/cvat-ui/package-lock.json +++ b/cvat-ui/package-lock.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.5.1", + "version": "1.6.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/cvat-ui/package.json b/cvat-ui/package.json index 9e698d2cd6a4..0d4115f1b824 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.5.1", + "version": "1.6.0", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index c373486a08d0..c78c871b9a81 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -8,7 +8,7 @@ import { ActionCreator, Store, } from 'redux'; -import { ThunkAction } from 'redux-thunk'; +import { ThunkAction } from 'utils/redux'; import { CombinedState, @@ -191,8 +191,7 @@ export enum AnnotationActionTypes { SAVE_LOGS_FAILED = 'SAVE_LOGS_FAILED', } -export function saveLogsAsync(): -ThunkAction, {}, {}, AnyAction> { +export function saveLogsAsync(): ThunkAction { return async (dispatch: ActionCreator) => { try { await logger.save(); @@ -236,7 +235,7 @@ export function switchZLayer(cur: number): AnyAction { }; } -export function fetchAnnotationsAsync(): ThunkAction, {}, {}, AnyAction> { +export function fetchAnnotationsAsync(): ThunkAction { return async (dispatch: ActionCreator): Promise => { try { const { @@ -308,8 +307,7 @@ export function updateCanvasContextMenu( }; } -export function removeAnnotationsAsync(sessionInstance: any): -ThunkAction, {}, {}, AnyAction> { +export function removeAnnotationsAsync(sessionInstance: any): ThunkAction { return async (dispatch: ActionCreator): Promise => { try { await sessionInstance.annotations.clear(); @@ -333,8 +331,7 @@ ThunkAction, {}, {}, AnyAction> { }; } -export function uploadJobAnnotationsAsync(job: any, loader: any, file: File): -ThunkAction, {}, {}, AnyAction> { +export function uploadJobAnnotationsAsync(job: any, loader: any, file: File): ThunkAction { return async (dispatch: ActionCreator): Promise => { try { const state: CombinedState = getStore().getState(); @@ -404,8 +401,7 @@ ThunkAction, {}, {}, AnyAction> { }; } -export function changeJobStatusAsync(jobInstance: any, status: string): -ThunkAction, {}, {}, AnyAction> { +export function changeJobStatusAsync(jobInstance: any, status: string): ThunkAction { return async (dispatch: ActionCreator): Promise => { const oldStatus = jobInstance.status; try { @@ -435,8 +431,7 @@ ThunkAction, {}, {}, AnyAction> { }; } -export function collectStatisticsAsync(sessionInstance: any): -ThunkAction, {}, {}, AnyAction> { +export function collectStatisticsAsync(sessionInstance: any): ThunkAction { return async (dispatch: ActionCreator): Promise => { try { dispatch({ @@ -477,7 +472,7 @@ export function propagateObjectAsync( objectState: any, from: number, to: number, -): ThunkAction, {}, {}, AnyAction> { +): ThunkAction { return async (dispatch: ActionCreator): Promise => { try { const copy = { @@ -542,7 +537,7 @@ export function changePropagateFrames(frames: number): AnyAction { } export function removeObjectAsync(sessionInstance: any, objectState: any, force: boolean): -ThunkAction, {}, {}, AnyAction> { +ThunkAction { return async (dispatch: ActionCreator): Promise => { try { await sessionInstance.logger.log(LogType.deleteObject, { count: 1 }); @@ -659,7 +654,7 @@ export function switchPlay(playing: boolean): AnyAction { } export function changeFrameAsync(toFrame: number, fillBuffer?: boolean, frameStep?: number): -ThunkAction, {}, {}, AnyAction> { +ThunkAction { return async (dispatch: ActionCreator): Promise => { const state: CombinedState = getStore().getState(); const { instance: job } = state.annotation.job; @@ -751,7 +746,7 @@ ThunkAction, {}, {}, AnyAction> { } export function undoActionAsync(sessionInstance: any, frame: number): -ThunkAction, {}, {}, AnyAction> { +ThunkAction { return async (dispatch: ActionCreator): Promise => { try { const state = getStore().getState(); @@ -794,7 +789,7 @@ ThunkAction, {}, {}, AnyAction> { } export function redoActionAsync(sessionInstance: any, frame: number): -ThunkAction, {}, {}, AnyAction> { +ThunkAction { return async (dispatch: ActionCreator): Promise => { try { const state = getStore().getState(); @@ -906,7 +901,7 @@ export function confirmCanvasReady(): AnyAction { }; } -export function closeJob(): ThunkAction, {}, {}, AnyAction> { +export function closeJob(): ThunkAction { return async (dispatch: ActionCreator): Promise => { const { jobInstance } = receiveAnnotationsParameters(); if (jobInstance) { @@ -924,7 +919,7 @@ export function getJobAsync( jid: number, initialFrame: number, initialFilters: string[], -): ThunkAction, {}, {}, AnyAction> { +): ThunkAction { return async (dispatch: ActionCreator): Promise => { try { const state: CombinedState = getStore().getState(); @@ -1001,7 +996,7 @@ export function getJobAsync( } export function saveAnnotationsAsync(sessionInstance: any): -ThunkAction, {}, {}, AnyAction> { +ThunkAction { return async (dispatch: ActionCreator): Promise => { const { filters, frame, showAllInterpolationTracks } = receiveAnnotationsParameters(); @@ -1118,8 +1113,7 @@ export function splitTrack(enabled: boolean): AnyAction { }; } -export function updateAnnotationsAsync(statesToUpdate: any[]): -ThunkAction, {}, {}, AnyAction> { +export function updateAnnotationsAsync(statesToUpdate: any[]): ThunkAction { return async (dispatch: ActionCreator): Promise => { const { jobInstance, @@ -1164,7 +1158,7 @@ ThunkAction, {}, {}, AnyAction> { } export function createAnnotationsAsync(sessionInstance: any, frame: number, statesToCreate: any[]): -ThunkAction, {}, {}, AnyAction> { +ThunkAction { return async (dispatch: ActionCreator): Promise => { try { const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); @@ -1192,7 +1186,7 @@ ThunkAction, {}, {}, AnyAction> { } export function mergeAnnotationsAsync(sessionInstance: any, frame: number, statesToMerge: any[]): -ThunkAction, {}, {}, AnyAction> { +ThunkAction { return async (dispatch: ActionCreator): Promise => { try { const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); @@ -1230,7 +1224,7 @@ export function groupAnnotationsAsync( sessionInstance: any, frame: number, statesToGroup: any[], -): ThunkAction, {}, {}, AnyAction> { +): ThunkAction { return async (dispatch: ActionCreator): Promise => { try { const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); @@ -1266,7 +1260,7 @@ export function groupAnnotationsAsync( } export function splitAnnotationsAsync(sessionInstance: any, frame: number, stateToSplit: any): -ThunkAction, {}, {}, AnyAction> { +ThunkAction { return async (dispatch: ActionCreator): Promise => { const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); try { @@ -1296,7 +1290,7 @@ ThunkAction, {}, {}, AnyAction> { export function changeLabelColorAsync( label: any, color: string, -): ThunkAction, {}, {}, AnyAction> { +): ThunkAction { return async (dispatch: ActionCreator): Promise => { try { const { @@ -1334,7 +1328,7 @@ export function changeLabelColorAsync( export function changeGroupColorAsync( group: number, color: string, -): ThunkAction, {}, {}, AnyAction> { +): ThunkAction { return async (dispatch: ActionCreator): Promise => { const state: CombinedState = getStore().getState(); const groupStates = state.annotation.annotations.states @@ -1352,7 +1346,7 @@ export function searchAnnotationsAsync( sessionInstance: any, frameFrom: number, frameTo: number, -): ThunkAction, {}, {}, AnyAction> { +): ThunkAction { return async (dispatch: ActionCreator): Promise => { try { const { filters } = receiveAnnotationsParameters(); @@ -1371,7 +1365,7 @@ export function searchAnnotationsAsync( }; } -export function pasteShapeAsync(): ThunkAction, {}, {}, AnyAction> { +export function pasteShapeAsync(): ThunkAction { return async (dispatch: ActionCreator): Promise => { const { canvas: { @@ -1430,7 +1424,7 @@ export function pasteShapeAsync(): ThunkAction, {}, {}, AnyAction> }; } -export function repeatDrawShapeAsync(): ThunkAction, {}, {}, AnyAction> { +export function repeatDrawShapeAsync(): ThunkAction { return async (dispatch: ActionCreator): Promise => { const { canvas: { @@ -1494,7 +1488,7 @@ export function repeatDrawShapeAsync(): ThunkAction, {}, {}, AnyAc }; } -export function redrawShapeAsync(): ThunkAction, {}, {}, AnyAction> { +export function redrawShapeAsync(): ThunkAction { return async (dispatch: ActionCreator): Promise => { const { annotations: { diff --git a/cvat-ui/src/components/annotation-page/appearance-block.tsx b/cvat-ui/src/components/annotation-page/appearance-block.tsx new file mode 100644 index 000000000000..d242c387d390 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/appearance-block.tsx @@ -0,0 +1,214 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React, { Dispatch } from 'react'; +import { AnyAction } from 'redux'; +import { connect } from 'react-redux'; +import Text from 'antd/lib/typography/Text'; +import Radio, { RadioChangeEvent } from 'antd/lib/radio'; +import Slider, { SliderValue } from 'antd/lib/slider'; +import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox'; +import Collapse from 'antd/lib/collapse'; + +import { ColorBy, CombinedState } from 'reducers/interfaces'; +import { + collapseAppearance as collapseAppearanceAction, + updateTabContentHeight as updateTabContentHeightAction, +} from 'actions/annotation-actions'; +import { + changeShapesColorBy as changeShapesColorByAction, + changeShapesOpacity as changeShapesOpacityAction, + changeSelectedShapesOpacity as changeSelectedShapesOpacityAction, + changeShapesBlackBorders as changeShapesBlackBordersAction, + changeShowBitmap as changeShowBitmapAction, + changeShowProjections as changeShowProjectionsAction, +} from 'actions/settings-actions'; + +interface StateToProps { + appearanceCollapsed: boolean; + colorBy: ColorBy; + opacity: number; + selectedOpacity: number; + blackBorders: boolean; + showBitmap: boolean; + showProjections: boolean; +} + +interface DispatchToProps { + collapseAppearance(): void; + changeShapesColorBy(event: RadioChangeEvent): void; + changeShapesOpacity(event: SliderValue): void; + changeSelectedShapesOpacity(event: SliderValue): void; + changeShapesBlackBorders(event: CheckboxChangeEvent): void; + changeShowBitmap(event: CheckboxChangeEvent): void; + changeShowProjections(event: CheckboxChangeEvent): void; +} + +export function computeHeight(): number { + const [sidebar] = window.document.getElementsByClassName('cvat-objects-sidebar'); + const [appearance] = window.document.getElementsByClassName('cvat-objects-appearance-collapse'); + const [tabs] = Array.from( + window.document.querySelectorAll('.cvat-objects-sidebar-tabs > .ant-tabs-card-bar'), + ); + + if (sidebar && appearance && tabs) { + const maxHeight = sidebar ? sidebar.clientHeight : 0; + const appearanceHeight = appearance ? appearance.clientHeight : 0; + const tabsHeight = tabs ? tabs.clientHeight : 0; + return maxHeight - appearanceHeight - tabsHeight; + } + + return 0; +} + +function mapStateToProps(state: CombinedState): StateToProps { + const { + annotation: { + appearanceCollapsed, + }, + settings: { + shapes: { + colorBy, + opacity, + selectedOpacity, + blackBorders, + showBitmap, + showProjections, + }, + }, + } = state; + + return { + appearanceCollapsed, + colorBy, + opacity, + selectedOpacity, + blackBorders, + showBitmap, + showProjections, + }; +} + + +function mapDispatchToProps(dispatch: Dispatch): DispatchToProps { + return { + collapseAppearance(): void { + dispatch(collapseAppearanceAction()); + const [collapser] = window.document + .getElementsByClassName('cvat-objects-appearance-collapse'); + + if (collapser) { + const listener = (event: Event): void => { + if ((event as TransitionEvent).propertyName === 'height') { + const height = computeHeight(); + dispatch(updateTabContentHeightAction(height)); + collapser.removeEventListener('transitionend', listener); + } + }; + + collapser.addEventListener('transitionend', listener); + } + }, + changeShapesColorBy(event: RadioChangeEvent): void { + dispatch(changeShapesColorByAction(event.target.value)); + }, + changeShapesOpacity(value: SliderValue): void { + dispatch(changeShapesOpacityAction(value as number)); + }, + changeSelectedShapesOpacity(value: SliderValue): void { + dispatch(changeSelectedShapesOpacityAction(value as number)); + }, + changeShapesBlackBorders(event: CheckboxChangeEvent): void { + dispatch(changeShapesBlackBordersAction(event.target.checked)); + }, + changeShowBitmap(event: CheckboxChangeEvent): void { + dispatch(changeShowBitmapAction(event.target.checked)); + }, + changeShowProjections(event: CheckboxChangeEvent): void { + dispatch(changeShowProjectionsAction(event.target.checked)); + }, + }; +} + +type Props = StateToProps & DispatchToProps; + +function AppearanceBlock(props: Props): JSX.Element { + const { + appearanceCollapsed, + colorBy, + opacity, + selectedOpacity, + blackBorders, + showBitmap, + showProjections, + collapseAppearance, + changeShapesColorBy, + changeShapesOpacity, + changeSelectedShapesOpacity, + changeShapesBlackBorders, + changeShowBitmap, + changeShowProjections, + } = props; + + return ( + + Appearance + } + key='appearance' + > +
+ Color by + + {ColorBy.INSTANCE} + {ColorBy.GROUP} + {ColorBy.LABEL} + + Opacity + + Selected opacity + + + Black borders + + + Show bitmap + + + Show projections + +
+
+
+ ); +} + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(React.memo(AppearanceBlock)); diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx index 51ff9b775e2b..85b186ee82ee 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx @@ -5,23 +5,25 @@ import React, { useState, useEffect } from 'react'; import { GlobalHotKeys, ExtendedKeyMapOptions } from 'react-hotkeys'; import { connect } from 'react-redux'; -import { Action } from 'redux'; -import { ThunkDispatch } from 'redux-thunk'; import Layout, { SiderProps } from 'antd/lib/layout'; import { SelectValue } from 'antd/lib/select'; -import { CheckboxChangeEvent } from 'antd/lib/checkbox'; import { Row, Col } from 'antd/lib/grid'; import Text from 'antd/lib/typography/Text'; import Icon from 'antd/lib/icon'; +import { ThunkDispatch } from 'utils/redux'; import { Canvas } from 'cvat-canvas-wrapper'; import { LogType } from 'cvat-logger'; import { activateObject as activateObjectAction, updateAnnotationsAsync, + changeFrameAsync, } from 'actions/annotation-actions'; -import { CombinedState } from 'reducers/interfaces'; +import { CombinedState, ObjectType } from 'reducers/interfaces'; import AnnotationsFiltersInput from 'components/annotation-page/annotations-filters-input'; +import AppearanceBlock from 'components/annotation-page/appearance-block'; +import ObjectButtonsContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/object-buttons'; + import ObjectSwitcher from './object-switcher'; import AttributeSwitcher from './attribute-switcher'; import ObjectBasicsEditor from './object-basics-edtior'; @@ -43,6 +45,7 @@ interface StateToProps { interface DispatchToProps { activateObject(clientID: number | null, attrID: number | null): void; updateAnnotations(statesToUpdate: any[]): void; + changeFrame(frame: number): void; } interface LabelAttrMap { @@ -85,7 +88,7 @@ function mapStateToProps(state: CombinedState): StateToProps { }; } -function mapDispatchToProps(dispatch: ThunkDispatch): DispatchToProps { +function mapDispatchToProps(dispatch: ThunkDispatch): DispatchToProps { return { activateObject(clientID: number, attrID: number): void { dispatch(activateObjectAction(clientID, attrID)); @@ -93,6 +96,9 @@ function mapDispatchToProps(dispatch: ThunkDispatch): updateAnnotations(states): void { dispatch(updateAnnotationsAsync(states)); }, + changeFrame(frame: number): void { + dispatch(changeFrameAsync(frame)); + }, }; } @@ -104,6 +110,7 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX. activatedAttributeID, jobInstance, updateAnnotations, + changeFrame, activateObject, keyMap, normalizedKeyMap, @@ -111,6 +118,7 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX. canvasIsReady, } = props; + const filteredStates = states.filter((state) => !state.outside && !state.hidden); const [labelAttrMap, setLabelAttrMap] = useState( labels.reduce((acc, label): LabelAttrMap => { acc[label.id] = label.attributes.length ? label.attributes[0] : null; @@ -133,10 +141,11 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX. setSidebarCollapsed(!sidebarCollapsed); }; - const [activeObjectState] = activatedStateID === null - ? [null] : states.filter((objectState: any): boolean => ( - objectState.clientID === activatedStateID - )); + const indexes = filteredStates.map((state) => state.clientID); + const activatedIndex = indexes.indexOf(activatedStateID); + const activeObjectState = activatedStateID === null || activatedIndex === -1 + ? null : filteredStates[activatedIndex]; + const activeAttribute = activeObjectState ? labelAttrMap[activeObjectState.label.id] : null; @@ -147,24 +156,24 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX. if (attribute && attribute.id !== activatedAttributeID) { activateObject(activatedStateID, attribute ? attribute.id : null); } - } else if (states.length) { - const attribute = labelAttrMap[states[0].label.id]; - activateObject(states[0].clientID, attribute ? attribute.id : null); + } else if (filteredStates.length) { + const attribute = labelAttrMap[filteredStates[0].label.id]; + activateObject(filteredStates[0].clientID, attribute ? attribute.id : null); } } const nextObject = (step: number): void => { - if (states.length) { - const index = states.indexOf(activeObjectState); + if (filteredStates.length) { + const index = filteredStates.indexOf(activeObjectState); let nextIndex = index + step; - if (nextIndex > states.length - 1) { + if (nextIndex > filteredStates.length - 1) { nextIndex = 0; } else if (nextIndex < 0) { - nextIndex = states.length - 1; + nextIndex = filteredStates.length - 1; } if (nextIndex !== index) { - const attribute = labelAttrMap[states[nextIndex].label.id]; - activateObject(states[nextIndex].clientID, attribute ? attribute.id : null); + const attribute = labelAttrMap[filteredStates[nextIndex].label.id]; + activateObject(filteredStates[nextIndex].clientID, attribute ? attribute.id : null); } } }; @@ -207,42 +216,74 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX. collapsed: sidebarCollapsed, }; + const preventDefault = (event: KeyboardEvent | undefined): void => { + if (event) { + event.preventDefault(); + } + }; + const subKeyMap = { NEXT_ATTRIBUTE: keyMap.NEXT_ATTRIBUTE, PREVIOUS_ATTRIBUTE: keyMap.PREVIOUS_ATTRIBUTE, NEXT_OBJECT: keyMap.NEXT_OBJECT, PREVIOUS_OBJECT: keyMap.PREVIOUS_OBJECT, + SWITCH_LOCK: keyMap.SWITCH_LOCK, + SWITCH_OCCLUDED: keyMap.SWITCH_OCCLUDED, + NEXT_KEY_FRAME: keyMap.NEXT_KEY_FRAME, + PREV_KEY_FRAME: keyMap.PREV_KEY_FRAME, }; const handlers = { NEXT_ATTRIBUTE: (event: KeyboardEvent | undefined) => { - if (event) { - event.preventDefault(); - } - + preventDefault(event); nextAttribute(1); }, PREVIOUS_ATTRIBUTE: (event: KeyboardEvent | undefined) => { - if (event) { - event.preventDefault(); - } - + preventDefault(event); nextAttribute(-1); }, NEXT_OBJECT: (event: KeyboardEvent | undefined) => { - if (event) { - event.preventDefault(); - } - + preventDefault(event); nextObject(1); }, PREVIOUS_OBJECT: (event: KeyboardEvent | undefined) => { - if (event) { - event.preventDefault(); - } - + preventDefault(event); nextObject(-1); }, + SWITCH_LOCK: (event: KeyboardEvent | undefined) => { + preventDefault(event); + if (activeObjectState) { + activeObjectState.lock = !activeObjectState.lock; + updateAnnotations([activeObjectState]); + } + }, + SWITCH_OCCLUDED: (event: KeyboardEvent | undefined) => { + preventDefault(event); + if (activeObjectState && activeObjectState.objectType !== ObjectType.TAG) { + activeObjectState.occluded = !activeObjectState.occluded; + updateAnnotations([activeObjectState]); + } + }, + NEXT_KEY_FRAME: (event: KeyboardEvent | undefined) => { + preventDefault(event); + if (activeObjectState && activeObjectState.objectType === ObjectType.TRACK) { + const frame = typeof (activeObjectState.keyframes.next) === 'number' + ? activeObjectState.keyframes.next : null; + if (frame !== null && canvasInstance.isAbleToChangeFrame()) { + changeFrame(frame); + } + } + }, + PREV_KEY_FRAME: (event: KeyboardEvent | undefined) => { + preventDefault(event); + if (activeObjectState && activeObjectState.objectType === ObjectType.TRACK) { + const frame = typeof (activeObjectState.keyframes.prev) === 'number' + ? activeObjectState.keyframes.prev : null; + if (frame !== null && canvasInstance.isAbleToChangeFrame()) { + changeFrame(frame); + } + } + }, }; if (activeObjectState) { @@ -268,15 +309,14 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX. currentLabel={activeObjectState.label.name} clientID={activeObjectState.clientID} occluded={activeObjectState.occluded} - objectsCount={states.length} - currentIndex={states.indexOf(activeObjectState)} + objectsCount={filteredStates.length} + currentIndex={filteredStates.indexOf(activeObjectState)} normalizedKeyMap={normalizedKeyMap} nextObject={nextObject} /> { const labelName = value as string; const [newLabel] = labels @@ -284,10 +324,12 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX. activeObjectState.label = newLabel; updateAnnotations([activeObjectState]); }} - setOccluded={(event: CheckboxChangeEvent): void => { - activeObjectState.occluded = event.target.checked; - updateAnnotations([activeObjectState]); - }} + /> + { activeAttribute @@ -302,6 +344,7 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX. nextAttribute={nextAttribute} /> { @@ -326,12 +369,29 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX. ) } + + { !sidebarCollapsed && } ); } return ( + {/* eslint-disable-next-line */} + + {sidebarCollapsed ? + : } + + + + + +
No objects found
diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx index 6917e42fbf2f..2f0b4c005dbc 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx @@ -13,6 +13,7 @@ import Input from 'antd/lib/input'; import consts from 'consts'; interface InputElementParameters { + clientID: number; attrID: number; inputType: string; values: string[]; @@ -24,6 +25,7 @@ function renderInputElement(parameters: InputElementParameters): JSX.Element { const { inputType, attrID, + clientID, values, currentValue, onChange, @@ -103,7 +105,7 @@ function renderInputElement(parameters: InputElementParameters): JSX.Element {
) => { const { value } = event.target; @@ -257,13 +259,19 @@ function renderList(parameters: ListParameters): JSX.Element | null { } interface Props { + clientID: number; attribute: any; currentValue: string; onChange(value: string): void; } function AttributeEditor(props: Props): JSX.Element { - const { attribute, currentValue, onChange } = props; + const { + attribute, + currentValue, + onChange, + clientID, + } = props; const { inputType, values, id: attrID } = attribute; return ( @@ -271,6 +279,7 @@ function AttributeEditor(props: Props): JSX.Element { {renderList({ values, inputType, onChange })}
{renderInputElement({ + clientID, attrID, inputType, currentValue, diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-basics-edtior.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-basics-edtior.tsx index 17a689a997f7..ea9db3355ca7 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-basics-edtior.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-basics-edtior.tsx @@ -4,24 +4,15 @@ import React from 'react'; import Select, { SelectValue } from 'antd/lib/select'; -import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox'; interface Props { currentLabel: string; labels: any[]; - occluded: boolean; - setOccluded(event: CheckboxChangeEvent): void; changeLabel(value: SelectValue): void; } function ObjectBasicsEditor(props: Props): JSX.Element { - const { - currentLabel, - occluded, - labels, - setOccluded, - changeLabel, - } = props; + const { currentLabel, labels, changeLabel } = props; return (
@@ -35,7 +26,6 @@ function ObjectBasicsEditor(props: Props): JSX.Element { ))} - Occluded
); } diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/appearance-block.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/appearance-block.tsx deleted file mode 100644 index d42d7a42d891..000000000000 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/appearance-block.tsx +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (C) 2020 Intel Corporation -// -// SPDX-License-Identifier: MIT - -import React from 'react'; -import Text from 'antd/lib/typography/Text'; -import Radio, { RadioChangeEvent } from 'antd/lib/radio'; -import Slider, { SliderValue } from 'antd/lib/slider'; -import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox'; -import Collapse from 'antd/lib/collapse'; - -import { ColorBy } from 'reducers/interfaces'; - -interface Props { - appearanceCollapsed: boolean; - colorBy: ColorBy; - opacity: number; - selectedOpacity: number; - blackBorders: boolean; - showBitmap: boolean; - showProjections: boolean; - - collapseAppearance(): void; - changeShapesColorBy(event: RadioChangeEvent): void; - changeShapesOpacity(event: SliderValue): void; - changeSelectedShapesOpacity(event: SliderValue): void; - changeShapesBlackBorders(event: CheckboxChangeEvent): void; - changeShowBitmap(event: CheckboxChangeEvent): void; - changeShowProjections(event: CheckboxChangeEvent): void; -} - -function AppearanceBlock(props: Props): JSX.Element { - const { - appearanceCollapsed, - colorBy, - opacity, - selectedOpacity, - blackBorders, - showBitmap, - showProjections, - collapseAppearance, - changeShapesColorBy, - changeShapesOpacity, - changeSelectedShapesOpacity, - changeShapesBlackBorders, - changeShowBitmap, - changeShowProjections, - } = props; - - return ( - - Appearance - } - key='appearance' - > -
- Color by - - {ColorBy.INSTANCE} - {ColorBy.GROUP} - {ColorBy.LABEL} - - Opacity - - Selected opacity - - - Black borders - - - Show bitmap - - - Show projections - -
-
-
- ); -} - -export default React.memo(AppearanceBlock); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-attribute.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-attribute.tsx new file mode 100644 index 000000000000..aca9779e2db6 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-attribute.tsx @@ -0,0 +1,175 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import { Col } from 'antd/lib/grid'; +import Select from 'antd/lib/select'; +import Radio, { RadioChangeEvent } from 'antd/lib/radio'; +import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox'; +import Input from 'antd/lib/input'; +import InputNumber from 'antd/lib/input-number'; +import Text from 'antd/lib/typography/Text'; + +import consts from 'consts'; +import { clamp } from 'utils/math'; + +interface Props { + attrInputType: string; + attrValues: string[]; + attrValue: string; + attrName: string; + attrID: number; + changeAttribute(attrID: number, value: string): void; +} + +function attrIsTheSame( + prevProps: Props, + nextProps: Props, +): boolean { + return nextProps.attrID === prevProps.attrID + && nextProps.attrValue === prevProps.attrValue + && nextProps.attrName === prevProps.attrName + && nextProps.attrInputType === prevProps.attrInputType + && nextProps.attrValues + .map((value: string, id: number): boolean => prevProps.attrValues[id] === value) + .every((value: boolean): boolean => value); +} + +function ItemAttributeComponent(props: Props): JSX.Element { + const { + attrInputType, + attrValues, + attrValue, + attrName, + attrID, + changeAttribute, + } = props; + + if (attrInputType === 'checkbox') { + return ( + + { + const value = event.target.checked ? 'true' : 'false'; + changeAttribute(attrID, value); + }} + > + + {attrName} + + + + ); + } + + if (attrInputType === 'radio') { + return ( + +
+ + {attrName} + + { + changeAttribute(attrID, event.target.value); + }} + > + { attrValues.map((value: string): JSX.Element => ( + + {value === consts.UNDEFINED_ATTRIBUTE_VALUE + ? consts.NO_BREAK_SPACE : value} + + )) } + +
+ + ); + } + + if (attrInputType === 'select') { + return ( + <> + + + {attrName} + + + + + + + ); + } + + if (attrInputType === 'number') { + const [min, max, step] = attrValues.map((value: string): number => +value); + + return ( + <> + + + {attrName} + + + + { + if (typeof (value) === 'number') { + changeAttribute( + attrID, `${clamp(value, min, max)}`, + ); + } + }} + value={+attrValue} + className='cvat-object-item-number-attribute' + min={min} + max={max} + step={step} + /> + + + ); + } + + return ( + <> + + + {attrName} + + + + ): void => { + changeAttribute(attrID, event.target.value); + }} + value={attrValue} + className='cvat-object-item-text-attribute' + /> + + + ); +} + +export default React.memo(ItemAttributeComponent, attrIsTheSame); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-basics.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-basics.tsx new file mode 100644 index 000000000000..08536f877ed5 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-basics.tsx @@ -0,0 +1,131 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import { Row, Col } from 'antd/lib/grid'; +import Icon from 'antd/lib/icon'; +import Select, { OptionProps } from 'antd/lib/select'; +import Dropdown from 'antd/lib/dropdown'; +import Text from 'antd/lib/typography/Text'; +import Tooltip from 'antd/lib/tooltip'; + +import { ObjectType, ShapeType } from 'reducers/interfaces'; +import ItemMenu from './object-item-menu'; + +interface Props { + clientID: number; + serverID: number | undefined; + labelID: number; + labels: any[]; + shapeType: ShapeType; + objectType: ObjectType; + type: string; + locked: boolean; + copyShortcut: string; + pasteShortcut: string; + propagateShortcut: string; + toBackgroundShortcut: string; + toForegroundShortcut: string; + removeShortcut: string; + changeLabel(labelID: string): void; + copy(): void; + remove(): void; + propagate(): void; + createURL(): void; + switchOrientation(): void; + toBackground(): void; + toForeground(): void; + resetCuboidPerspective(): void; +} + +function ItemTopComponent(props: Props): JSX.Element { + const { + clientID, + serverID, + labelID, + labels, + shapeType, + objectType, + type, + locked, + copyShortcut, + pasteShortcut, + propagateShortcut, + toBackgroundShortcut, + toForegroundShortcut, + removeShortcut, + changeLabel, + copy, + remove, + propagate, + createURL, + switchOrientation, + toBackground, + toForeground, + resetCuboidPerspective, + } = props; + + return ( + + + {clientID} +
+ {type} + + + + + + + + + + + +
+ ); +} + +export default React.memo(ItemTopComponent); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-buttons.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-buttons.tsx new file mode 100644 index 000000000000..e7d2f3c03b8a --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-buttons.tsx @@ -0,0 +1,262 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import { Row, Col } from 'antd/lib/grid'; +import Icon from 'antd/lib/icon'; +import Tooltip from 'antd/lib/tooltip'; + +import { + ObjectOutsideIcon, + FirstIcon, + LastIcon, + PreviousIcon, + NextIcon, +} from 'icons'; +import { ObjectType, ShapeType } from 'reducers/interfaces'; + +interface Props { + objectType: ObjectType; + shapeType: ShapeType; + occluded: boolean; + outside: boolean | undefined; + locked: boolean; + pinned: boolean; + hidden: boolean; + keyframe: boolean | undefined; + outsideDisabled: boolean; + hiddenDisabled: boolean; + keyframeDisabled: boolean; + switchOccludedShortcut: string; + switchOutsideShortcut: string; + switchLockShortcut: string; + switchHiddenShortcut: string; + switchKeyFrameShortcut: string; + nextKeyFrameShortcut: string; + prevKeyFrameShortcut: string; + + navigateFirstKeyframe: null | (() => void); + navigatePrevKeyframe: null | (() => void); + navigateNextKeyframe: null | (() => void); + navigateLastKeyframe: null | (() => void); + + setOccluded(): void; + unsetOccluded(): void; + setOutside(): void; + unsetOutside(): void; + setKeyframe(): void; + unsetKeyframe(): void; + lock(): void; + unlock(): void; + pin(): void; + unpin(): void; + hide(): void; + show(): void; +} + +function ItemButtonsComponent(props: Props): JSX.Element { + const { + objectType, + shapeType, + occluded, + outside, + locked, + pinned, + hidden, + keyframe, + outsideDisabled, + hiddenDisabled, + keyframeDisabled, + switchOccludedShortcut, + switchOutsideShortcut, + switchLockShortcut, + switchHiddenShortcut, + switchKeyFrameShortcut, + nextKeyFrameShortcut, + prevKeyFrameShortcut, + + navigateFirstKeyframe, + navigatePrevKeyframe, + navigateNextKeyframe, + navigateLastKeyframe, + + setOccluded, + unsetOccluded, + setOutside, + unsetOutside, + setKeyframe, + unsetKeyframe, + lock, + unlock, + pin, + unpin, + hide, + show, + } = props; + + const outsideStyle = outsideDisabled ? { opacity: 0.5, pointerEvents: 'none' as 'none' } : {}; + const hiddenStyle = hiddenDisabled ? { opacity: 0.5, pointerEvents: 'none' as 'none' } : {}; + const keyframeStyle = keyframeDisabled ? { opacity: 0.5, pointerEvents: 'none' as 'none' } : {}; + + + if (objectType === ObjectType.TRACK) { + return ( + + + + + { navigateFirstKeyframe + ? + : } + + + { navigatePrevKeyframe + ? ( + + + + ) + : } + + + { navigateNextKeyframe + ? ( + + + + ) + : } + + + { navigateLastKeyframe + ? + : } + + + + + + { outside + ? ( + + ) + : } + + + + + { locked + ? + : } + + + + + { occluded + ? + : } + + + + + + + + { keyframe + ? + : } + + + { + shapeType !== ShapeType.POINTS && ( + + + { pinned + ? + : } + + + ) + } + + + + ); + } + + if (objectType === ObjectType.TAG) { + return ( + + + + + + { locked + ? + : } + + + + + + ); + } + + return ( + + + + + + { locked + ? + : } + + + + + { occluded + ? + : } + + + + + + { + shapeType !== ShapeType.POINTS && ( + + + { pinned + ? + : } + + + ) + } + + + + ); +} + +export default React.memo(ItemButtonsComponent); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-details.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-details.tsx new file mode 100644 index 000000000000..f2e17d3bd0d2 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-details.tsx @@ -0,0 +1,86 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import { Row } from 'antd/lib/grid'; +import Collapse from 'antd/lib/collapse'; + +import ItemAttribute from './object-item-attribute'; + +interface Props { + collapsed: boolean; + attributes: any[]; + values: Record; + changeAttribute(attrID: number, value: string): void; + collapse(): void; +} + +export function attrValuesAreEqual( + next: Record, prev: Record, +): boolean { + const prevKeys = Object.keys(prev); + const nextKeys = Object.keys(next); + + return nextKeys.length === prevKeys.length + && nextKeys.map((key: string): boolean => prev[+key] === next[+key]) + .every((value: boolean) => value); +} + +function attrAreTheSame( + prevProps: Props, + nextProps: Props, +): boolean { + return nextProps.collapsed === prevProps.collapsed + && nextProps.attributes === prevProps.attributes + && attrValuesAreEqual(nextProps.values, prevProps.values); +} + +function ItemAttributesComponent(props: Props): JSX.Element { + const { + collapsed, + attributes, + values, + changeAttribute, + collapse, + } = props; + + const sorted = [...attributes] + .sort((a: any, b: any): number => a.inputType.localeCompare(b.inputType)); + + return ( + + + Details} + key='details' + > + { sorted.map((attribute: any): JSX.Element => ( + + + + ))} + + + + ); +} + +export default React.memo(ItemAttributesComponent, attrAreTheSame); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-menu.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-menu.tsx new file mode 100644 index 000000000000..23e2c664dc25 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-menu.tsx @@ -0,0 +1,139 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import Icon from 'antd/lib/icon'; +import Menu from 'antd/lib/menu'; +import Button from 'antd/lib/button'; +import Modal from 'antd/lib/modal'; +import Tooltip from 'antd/lib/tooltip'; + +import { BackgroundIcon, ForegroundIcon, ResetPerspectiveIcon } from 'icons'; +import { ObjectType, ShapeType } from 'reducers/interfaces'; + +interface Props { + serverID: number | undefined; + locked: boolean; + shapeType: ShapeType; + objectType: ObjectType; + copyShortcut: string; + pasteShortcut: string; + propagateShortcut: string; + toBackgroundShortcut: string; + toForegroundShortcut: string; + removeShortcut: string; + copy: (() => void); + remove: (() => void); + propagate: (() => void); + createURL: (() => void); + switchOrientation: (() => void); + toBackground: (() => void); + toForeground: (() => void); + resetCuboidPerspective: (() => void); +} + +export default function ItemMenu(props: Props): JSX.Element { + const { + serverID, + locked, + shapeType, + objectType, + copyShortcut, + pasteShortcut, + propagateShortcut, + toBackgroundShortcut, + toForegroundShortcut, + removeShortcut, + copy, + remove, + propagate, + createURL, + switchOrientation, + toBackground, + toForeground, + resetCuboidPerspective, + } = props; + + return ( + + + + + + + + + + + + + + + { [ShapeType.POLYGON, ShapeType.POLYLINE, ShapeType.CUBOID].includes(shapeType) && ( + + + + )} + {shapeType === ShapeType.CUBOID && ( + + + + )} + {objectType !== ObjectType.TAG && ( + + + + + + )} + {objectType !== ObjectType.TAG && ( + + + + + + )} + + + + + + + ); +} diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx index cd982fe02f7e..3d8010fec157 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx @@ -3,722 +3,15 @@ // SPDX-License-Identifier: MIT import React from 'react'; -import { Row, Col } from 'antd/lib/grid'; -import Icon from 'antd/lib/icon'; -import Select, { OptionProps } from 'antd/lib/select'; -import Radio, { RadioChangeEvent } from 'antd/lib/radio'; -import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox'; -import Input from 'antd/lib/input'; -import InputNumber from 'antd/lib/input-number'; -import Collapse from 'antd/lib/collapse'; -import Dropdown from 'antd/lib/dropdown'; -import Menu from 'antd/lib/menu'; -import Button from 'antd/lib/button'; -import Modal from 'antd/lib/modal'; import Popover from 'antd/lib/popover'; -import Text from 'antd/lib/typography/Text'; -import Tooltip from 'antd/lib/tooltip'; +import ObjectButtonsContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/object-buttons'; import ColorChanger from 'components/annotation-page/standard-workspace/objects-side-bar/color-changer'; -import consts from 'consts'; -import { - ObjectOutsideIcon, - FirstIcon, - LastIcon, - PreviousIcon, - NextIcon, - BackgroundIcon, - ForegroundIcon, - ResetPerspectiveIcon, -} from 'icons'; import { ObjectType, ShapeType } from 'reducers/interfaces'; -import { clamp } from 'utils/math'; +import ItemDetails, { attrValuesAreEqual } from './object-item-details'; +import ItemBasics from './object-item-basics'; -function ItemMenu( - serverID: number | undefined, - locked: boolean, - shapeType: ShapeType, - objectType: ObjectType, - copyShortcut: string, - pasteShortcut: string, - propagateShortcut: string, - toBackgroundShortcut: string, - toForegroundShortcut: string, - removeShortcut: string, - copy: (() => void), - remove: (() => void), - propagate: (() => void), - createURL: (() => void), - switchOrientation: (() => void), - toBackground: (() => void), - toForeground: (() => void), - resetCuboidPerspective: (() => void), -): JSX.Element { - return ( - - - - - - - - - - - - - - - { [ShapeType.POLYGON, ShapeType.POLYLINE, ShapeType.CUBOID].includes(shapeType) && ( - - - - )} - {shapeType === ShapeType.CUBOID && ( - - - - )} - {objectType !== ObjectType.TAG && ( - - - - - - )} - {objectType !== ObjectType.TAG && ( - - - - - - )} - - - - - - - ); -} - -interface ItemTopComponentProps { - clientID: number; - serverID: number | undefined; - labelID: number; - labels: any[]; - shapeType: ShapeType; - objectType: ObjectType; - type: string; - locked: boolean; - copyShortcut: string; - pasteShortcut: string; - propagateShortcut: string; - toBackgroundShortcut: string; - toForegroundShortcut: string; - removeShortcut: string; - changeLabel(labelID: string): void; - copy(): void; - remove(): void; - propagate(): void; - createURL(): void; - switchOrientation(): void; - toBackground(): void; - toForeground(): void; - resetCuboidPerspective(): void; -} - -function ItemTopComponent(props: ItemTopComponentProps): JSX.Element { - const { - clientID, - serverID, - labelID, - labels, - shapeType, - objectType, - type, - locked, - copyShortcut, - pasteShortcut, - propagateShortcut, - toBackgroundShortcut, - toForegroundShortcut, - removeShortcut, - changeLabel, - copy, - remove, - propagate, - createURL, - switchOrientation, - toBackground, - toForeground, - resetCuboidPerspective, - } = props; - - return ( - - - {clientID} -
- {type} - - - - - - - - - - - -
- ); -} - -const ItemTop = React.memo(ItemTopComponent); - -interface ItemButtonsComponentProps { - objectType: ObjectType; - shapeType: ShapeType; - occluded: boolean; - outside: boolean | undefined; - locked: boolean; - pinned: boolean; - hidden: boolean; - keyframe: boolean | undefined; - switchOccludedShortcut: string; - switchOutsideShortcut: string; - switchLockShortcut: string; - switchHiddenShortcut: string; - switchKeyFrameShortcut: string; - nextKeyFrameShortcut: string; - prevKeyFrameShortcut: string; - - navigateFirstKeyframe: null | (() => void); - navigatePrevKeyframe: null | (() => void); - navigateNextKeyframe: null | (() => void); - navigateLastKeyframe: null | (() => void); - - setOccluded(): void; - unsetOccluded(): void; - setOutside(): void; - unsetOutside(): void; - setKeyframe(): void; - unsetKeyframe(): void; - lock(): void; - unlock(): void; - pin(): void; - unpin(): void; - hide(): void; - show(): void; -} - -function ItemButtonsComponent(props: ItemButtonsComponentProps): JSX.Element { - const { - objectType, - shapeType, - occluded, - outside, - locked, - pinned, - hidden, - keyframe, - switchOccludedShortcut, - switchOutsideShortcut, - switchLockShortcut, - switchHiddenShortcut, - switchKeyFrameShortcut, - nextKeyFrameShortcut, - prevKeyFrameShortcut, - - navigateFirstKeyframe, - navigatePrevKeyframe, - navigateNextKeyframe, - navigateLastKeyframe, - - setOccluded, - unsetOccluded, - setOutside, - unsetOutside, - setKeyframe, - unsetKeyframe, - lock, - unlock, - pin, - unpin, - hide, - show, - } = props; - - if (objectType === ObjectType.TRACK) { - return ( - - - - - { navigateFirstKeyframe - ? - : } - - - { navigatePrevKeyframe - ? ( - - - - ) - : } - - - { navigateNextKeyframe - ? ( - - - - ) - : } - - - { navigateLastKeyframe - ? - : } - - - - - - { outside - ? - : } - - - - - { locked - ? - : } - - - - - { occluded - ? - : } - - - - - - - - { keyframe - ? - : } - - - { - shapeType !== ShapeType.POINTS && ( - - - { pinned - ? - : } - - - ) - } - - - - ); - } - - if (objectType === ObjectType.TAG) { - return ( - - - - - - { locked - ? - : } - - - - - - ); - } - - return ( - - - - - - { locked - ? - : } - - - - - { occluded - ? - : } - - - - - - { - shapeType !== ShapeType.POINTS && ( - - - { pinned - ? - : } - - - ) - } - - - - ); -} - -const ItemButtons = React.memo(ItemButtonsComponent); - -interface ItemAttributeComponentProps { - attrInputType: string; - attrValues: string[]; - attrValue: string; - attrName: string; - attrID: number; - changeAttribute(attrID: number, value: string): void; -} - -function attrIsTheSame( - prevProps: ItemAttributeComponentProps, - nextProps: ItemAttributeComponentProps, -): boolean { - return nextProps.attrID === prevProps.attrID - && nextProps.attrValue === prevProps.attrValue - && nextProps.attrName === prevProps.attrName - && nextProps.attrInputType === prevProps.attrInputType - && nextProps.attrValues - .map((value: string, id: number): boolean => prevProps.attrValues[id] === value) - .every((value: boolean): boolean => value); -} - -function ItemAttributeComponent(props: ItemAttributeComponentProps): JSX.Element { - const { - attrInputType, - attrValues, - attrValue, - attrName, - attrID, - changeAttribute, - } = props; - - if (attrInputType === 'checkbox') { - return ( - - { - const value = event.target.checked ? 'true' : 'false'; - changeAttribute(attrID, value); - }} - > - - {attrName} - - - - ); - } - - if (attrInputType === 'radio') { - return ( - -
- - {attrName} - - { - changeAttribute(attrID, event.target.value); - }} - > - { attrValues.map((value: string): JSX.Element => ( - - {value === consts.UNDEFINED_ATTRIBUTE_VALUE - ? consts.NO_BREAK_SPACE : value} - - )) } - -
- - ); - } - - if (attrInputType === 'select') { - return ( - <> - - - {attrName} - - - - - - - ); - } - - if (attrInputType === 'number') { - const [min, max, step] = attrValues.map((value: string): number => +value); - - return ( - <> - - - {attrName} - - - - { - if (typeof (value) === 'number') { - changeAttribute( - attrID, `${clamp(value, min, max)}`, - ); - } - }} - value={+attrValue} - className='cvat-object-item-number-attribute' - min={min} - max={max} - step={step} - /> - - - ); - } - - return ( - <> - - - {attrName} - - - - ): void => { - changeAttribute(attrID, event.target.value); - }} - value={attrValue} - className='cvat-object-item-text-attribute' - /> - - - ); -} - -const ItemAttribute = React.memo(ItemAttributeComponent, attrIsTheSame); - - -interface ItemAttributesComponentProps { - collapsed: boolean; - attributes: any[]; - values: Record; - changeAttribute(attrID: number, value: string): void; - collapse(): void; -} - -function attrValuesAreEqual(next: Record, prev: Record): boolean { - const prevKeys = Object.keys(prev); - const nextKeys = Object.keys(next); - - return nextKeys.length === prevKeys.length - && nextKeys.map((key: string): boolean => prev[+key] === next[+key]) - .every((value: boolean) => value); -} - -function attrAreTheSame( - prevProps: ItemAttributesComponentProps, - nextProps: ItemAttributesComponentProps, -): boolean { - return nextProps.collapsed === prevProps.collapsed - && nextProps.attributes === prevProps.attributes - && attrValuesAreEqual(nextProps.values, prevProps.values); -} - -function ItemAttributesComponent(props: ItemAttributesComponentProps): JSX.Element { - const { - collapsed, - attributes, - values, - changeAttribute, - collapse, - } = props; - - const sorted = [...attributes] - .sort((a: any, b: any): number => a.inputType.localeCompare(b.inputType)); - - return ( - - - Details} - key='details' - > - { sorted.map((attribute: any): JSX.Element => ( - - - - ))} - - - - ); -} - -const ItemAttributes = React.memo(ItemAttributesComponent, attrAreTheSame); - interface Props { normalizedKeyMap: Record; activated: boolean; @@ -727,12 +20,7 @@ interface Props { clientID: number; serverID: number | undefined; labelID: number; - occluded: boolean; - outside: boolean | undefined; locked: boolean; - pinned: boolean; - hidden: boolean; - keyframe: boolean | undefined; attrValues: Record; color: string; colors: string[]; @@ -740,10 +28,6 @@ interface Props { labels: any[]; attributes: any[]; collapsed: boolean; - navigateFirstKeyframe: null | (() => void); - navigatePrevKeyframe: null | (() => void); - navigateNextKeyframe: null | (() => void); - navigateLastKeyframe: null | (() => void); activate(): void; copy(): void; @@ -753,18 +37,6 @@ interface Props { toBackground(): void; toForeground(): void; remove(): void; - setOccluded(): void; - unsetOccluded(): void; - setOutside(): void; - unsetOutside(): void; - setKeyframe(): void; - unsetKeyframe(): void; - lock(): void; - unlock(): void; - pin(): void; - unpin(): void; - hide(): void; - show(): void; changeLabel(labelID: string): void; changeAttribute(attrID: number, value: string): void; changeColor(color: string): void; @@ -775,11 +47,6 @@ interface Props { function objectItemsAreEqual(prevProps: Props, nextProps: Props): boolean { return nextProps.activated === prevProps.activated && nextProps.locked === prevProps.locked - && nextProps.pinned === prevProps.pinned - && nextProps.occluded === prevProps.occluded - && nextProps.outside === prevProps.outside - && nextProps.hidden === prevProps.hidden - && nextProps.keyframe === prevProps.keyframe && nextProps.labelID === prevProps.labelID && nextProps.color === prevProps.color && nextProps.clientID === prevProps.clientID @@ -790,10 +57,6 @@ function objectItemsAreEqual(prevProps: Props, nextProps: Props): boolean { && nextProps.labels === prevProps.labels && nextProps.attributes === prevProps.attributes && nextProps.normalizedKeyMap === prevProps.normalizedKeyMap - && nextProps.navigateFirstKeyframe === prevProps.navigateFirstKeyframe - && nextProps.navigatePrevKeyframe === prevProps.navigatePrevKeyframe - && nextProps.navigateNextKeyframe === prevProps.navigateNextKeyframe - && nextProps.navigateLastKeyframe === prevProps.navigateLastKeyframe && attrValuesAreEqual(nextProps.attrValues, prevProps.attrValues); } @@ -804,12 +67,7 @@ function ObjectItemComponent(props: Props): JSX.Element { shapeType, clientID, serverID, - occluded, - outside, locked, - pinned, - hidden, - keyframe, attrValues, labelID, color, @@ -819,10 +77,6 @@ function ObjectItemComponent(props: Props): JSX.Element { labels, collapsed, normalizedKeyMap, - navigateFirstKeyframe, - navigatePrevKeyframe, - navigateNextKeyframe, - navigateLastKeyframe, activate, copy, @@ -832,18 +86,6 @@ function ObjectItemComponent(props: Props): JSX.Element { toBackground, toForeground, remove, - setOccluded, - unsetOccluded, - setOutside, - unsetOutside, - setKeyframe, - unsetKeyframe, - lock, - unlock, - pin, - unpin, - hide, - show, changeLabel, changeAttribute, changeColor, @@ -881,7 +123,7 @@ function ObjectItemComponent(props: Props): JSX.Element { className={className} style={{ backgroundColor: `${color}88` }} > - -