From 7524202492129dd19591670929c04f8dadf67051 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 7 Apr 2021 11:50:24 +0300 Subject: [PATCH] Added hotkeys to change labels (#3070) * temp commit * Added ability to change default label and object label by using Ctrl+{number} * Removed extra changes * Minor refactoring * Added ability to change assigned keys * Redesigned popover * Added changelog record & updated version * Added memoization * Some minor changes * Applied comments Co-authored-by: Dmitry Kalinin --- CHANGELOG.md | 8 +- cvat-ui/package-lock.json | 2 +- cvat-ui/package.json | 2 +- cvat-ui/src/actions/annotation-actions.ts | 36 ++----- .../objects-side-bar/issues-list.tsx | 2 +- .../objects-side-bar/label-item.tsx | 51 +++++++-- .../label-key-selector-popover.tsx | 85 +++++++++++++++ .../objects-side-bar/labels-list.tsx | 100 ++++++++++++++++-- .../objects-side-bar/objects-side-bar.tsx | 8 +- .../objects-side-bar/styles.scss | 50 +++++++-- .../tag-annotation-sidebar.tsx | 2 +- .../controls-side-bar/draw-shape-popover.tsx | 10 +- .../controls-side-bar/setup-tag-popover.tsx | 4 +- .../objects-side-bar/label-item.tsx | 34 +++--- .../objects-side-bar/labels-list.tsx | 29 ----- cvat-ui/src/reducers/annotation-reducer.ts | 26 +++-- cvat-ui/src/reducers/interfaces.ts | 1 + cvat-ui/src/reducers/shortcuts-reducer.ts | 8 +- cvat-ui/src/utils/mousetrap-react.tsx | 2 +- 19 files changed, 341 insertions(+), 119 deletions(-) create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/label-key-selector-popover.tsx delete mode 100644 cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/labels-list.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 799d9fc2832b..217545cb63b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,23 +8,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.4.0] - Unreleased ### Added + - Documentation on mask annotation () +- Hotkeys to switch a label of existing object or to change default label (for objects created with N) () ### Changed + - ### Deprecated + - ### Removed + - ### Fixed + - Export of instance masks with holes () ### Security -- +- ## [1.3.0] - 3/31/2021 diff --git a/cvat-ui/package-lock.json b/cvat-ui/package-lock.json index 27c93845c188..f4cc563d1794 100644 --- a/cvat-ui/package-lock.json +++ b/cvat-ui/package-lock.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.18.1", + "version": "1.19.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/cvat-ui/package.json b/cvat-ui/package.json index caf6ba255fde..caae68129325 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.18.1", + "version": "1.19.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 8e5fd184df86..a7afc3b1bd9f 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -1130,36 +1130,16 @@ export function saveAnnotationsAsync(sessionInstance: any, afterSave?: () => voi } // used to reproduce the latest drawing (in case of tags just creating) by using N -export function rememberObject( - objectType: ObjectType, - labelID: number, - shapeType?: ShapeType, - points?: number, - rectDrawingMethod?: RectDrawingMethod, -): AnyAction { - let activeControl = ActiveControl.CURSOR; - if (shapeType === ShapeType.RECTANGLE) { - activeControl = ActiveControl.DRAW_RECTANGLE; - } else if (shapeType === ShapeType.POLYGON) { - activeControl = ActiveControl.DRAW_POLYGON; - } else if (shapeType === ShapeType.POLYLINE) { - activeControl = ActiveControl.DRAW_POLYLINE; - } else if (shapeType === ShapeType.POINTS) { - activeControl = ActiveControl.DRAW_POINTS; - } else if (shapeType === ShapeType.CUBOID) { - activeControl = ActiveControl.DRAW_CUBOID; - } - +export function rememberObject(createParams: { + activeObjectType?: ObjectType; + activeLabelID?: number; + activeShapeType?: ShapeType; + activeNumOfPoints?: number; + activeRectDrawingMethod?: RectDrawingMethod; +}): AnyAction { return { type: AnnotationActionTypes.REMEMBER_CREATED_OBJECT, - payload: { - shapeType, - labelID, - objectType, - points, - activeControl, - rectDrawingMethod, - }, + payload: createParams, }; } diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/issues-list.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/issues-list.tsx index f9fb7070176c..ffd78023c500 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/issues-list.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/issues-list.tsx @@ -4,7 +4,6 @@ import React from 'react'; import { useSelector, useDispatch } from 'react-redux'; -import { CombinedState } from 'reducers/interfaces'; import { LeftOutlined, RightOutlined, EyeInvisibleFilled, EyeOutlined, } from '@ant-design/icons'; @@ -14,6 +13,7 @@ import { Row, Col } from 'antd/lib/grid'; import { changeFrameAsync } from 'actions/annotation-actions'; import { reviewActions } from 'actions/review-actions'; import CVATTooltip from 'components/common/cvat-tooltip'; +import { CombinedState } from 'reducers/interfaces'; export default function LabelsListComponent(): JSX.Element { const dispatch = useDispatch(); 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 fc038b391663..45f47c712d40 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 @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -10,22 +10,30 @@ import { LockFilled, UnlockOutlined, EyeInvisibleFilled, EyeOutlined, } from '@ant-design/icons'; +import CVATTooltip from 'components/common/cvat-tooltip'; +import LabelKeySelectorPopover from './label-key-selector-popover'; + interface Props { labelName: string; labelColor: string; + labelID: number; visible: boolean; statesHidden: boolean; statesLocked: boolean; + keyToLabelMapping: Record; hideStates(): void; showStates(): void; lockStates(): void; unlockStates(): void; + updateLabelShortcutKey(updatedKey: string, labelID: number): void; } function LabelItemComponent(props: Props): JSX.Element { const { labelName, labelColor, + labelID, + keyToLabelMapping, visible, statesHidden, statesLocked, @@ -33,8 +41,14 @@ function LabelItemComponent(props: Props): JSX.Element { showStates, lockStates, unlockStates, + updateLabelShortcutKey, } = props; + // create reversed mapping just to receive key easily + const labelToKeyMapping: Record = Object.fromEntries( + Object.entries(keyToLabelMapping).map(([key, _labelID]) => [_labelID, key]), + ); + const labelShortcutKey = labelToKeyMapping[labelID] || '?'; const classes = { lock: { enabled: { className: 'cvat-label-item-button-lock cvat-label-item-button-lock-enabled' }, @@ -48,22 +62,37 @@ function LabelItemComponent(props: Props): JSX.Element { return ( - - + - - - {labelName} - + + + + {labelName} + + + + + + + {statesLocked ? ( ) : ( diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/label-key-selector-popover.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/label-key-selector-popover.tsx new file mode 100644 index 000000000000..bb92bf47bb36 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/label-key-selector-popover.tsx @@ -0,0 +1,85 @@ +// Copyright (C) 2021 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import { useSelector } from 'react-redux'; +import Popover from 'antd/lib/popover'; +import Button from 'antd/lib/button'; +import { Row, Col } from 'antd/lib/grid'; +import Text from 'antd/lib/typography/Text'; + +import { CombinedState } from 'reducers/interfaces'; +import CVATTooltip from 'components/common/cvat-tooltip'; + +interface LabelKeySelectorPopoverProps { + updateLabelShortcutKey(updatedKey: string, labelID: number): void; + keyToLabelMapping: Record; + labelID: number; + children: JSX.Element; +} + +interface LabelKeySelectorPopoverContentProps { + updateLabelShortcutKey(updatedKey: string, labelID: number): void; + labelID: number; + keyToLabelMapping: Record; +} + +function PopoverContent(props: LabelKeySelectorPopoverContentProps): JSX.Element { + const { keyToLabelMapping, labelID, updateLabelShortcutKey } = props; + const labels = useSelector((state: CombinedState) => state.annotation.job.labels); + + return ( +
+ {[['1', '2', '3'], ['4', '5', '6'], ['7', '8', '9'], ['0']].map((arr, i_) => ( + + {arr.map((i) => { + const previousLabelID = keyToLabelMapping[i]; + const labelName = Number.isInteger(previousLabelID) ? + labels.filter((label: any): boolean => label.id === previousLabelID)[0]?.name || + 'undefined' : + 'None'; + + return ( + + + + + + ); + })} + + ))} +
+ ); +} + +const MemoizedContent = React.memo(PopoverContent); + +function LabelKeySelectorPopover(props: LabelKeySelectorPopoverProps): JSX.Element { + const { + children, labelID, updateLabelShortcutKey, keyToLabelMapping, + } = props; + + return ( + + )} + placement='left' + > + {children} + + ); +} + +export default React.memo(LabelKeySelectorPopover); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/labels-list.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/labels-list.tsx index 827ff3e7dd0d..b0955de0bed7 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/labels-list.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/labels-list.tsx @@ -1,26 +1,108 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2021 Intel Corporation // // SPDX-License-Identifier: MIT -import React from 'react'; +import React, { useCallback, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import message from 'antd/lib/message'; +import { CombinedState } from 'reducers/interfaces'; +import { rememberObject, updateAnnotationsAsync } from 'actions/annotation-actions'; import LabelItemContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/label-item'; +import GlobalHotKeys from 'utils/mousetrap-react'; -interface Props { - labelIDs: number[]; - listHeight: number; -} +function LabelsListComponent(): JSX.Element { + const dispatch = useDispatch(); + const { + annotation: { + job: { labels }, + tabContentHeight: listHeight, + annotations: { activatedStateID, states }, + }, + shortcuts: { keyMap }, + } = useSelector((state: CombinedState) => state); + const labelIDs = labels.map((label: any): number => label.id); + + const [keyToLabelMapping, setKeyToLabelMapping] = useState>( + Object.fromEntries(labelIDs.slice(0, 10).map((labelID: number, idx: number) => [(idx + 1) % 10, labelID])), + ); + + const updateLabelShortcutKey = useCallback( + (key: string, labelID: number) => { + // unassign any keys assigned to the current labels + const keyToLabelMappingCopy = { ...keyToLabelMapping }; + for (const shortKey of Object.keys(keyToLabelMappingCopy)) { + if (keyToLabelMappingCopy[shortKey] === labelID) { + delete keyToLabelMappingCopy[shortKey]; + } + } + + if (key === '—') { + setKeyToLabelMapping(keyToLabelMappingCopy); + return; + } -export default function LabelsListComponent(props: Props): JSX.Element { - const { listHeight, labelIDs } = props; + // check if this key is assigned to another label + if (key in keyToLabelMappingCopy) { + // try to find a new key for the other label + for (let i = 0; i < 10; i++) { + const adjustedI = (i + 1) % 10; + if (!(adjustedI in keyToLabelMappingCopy)) { + keyToLabelMappingCopy[adjustedI] = keyToLabelMappingCopy[key]; + break; + } + } + // delete assigning to the other label + delete keyToLabelMappingCopy[key]; + } + + // assigning to the current label + keyToLabelMappingCopy[key] = labelID; + setKeyToLabelMapping(keyToLabelMappingCopy); + }, + [keyToLabelMapping], + ); + + const subKeyMap = { + SWITCH_LABEL: keyMap.SWITCH_LABEL, + }; + + const handlers = { + SWITCH_LABEL: (event: KeyboardEvent | undefined, shortcut: string) => { + if (event) event.preventDefault(); + const labelID = keyToLabelMapping[shortcut.split('+')[1].trim()]; + const label = labels.filter((_label: any) => _label.id === labelID)[0]; + if (Number.isInteger(labelID) && label) { + if (Number.isInteger(activatedStateID)) { + const activatedState = states.filter((state: any) => state.clientID === activatedStateID)[0]; + if (activatedState) { + activatedState.label = label; + dispatch(updateAnnotationsAsync([activatedState])); + } + } else { + dispatch(rememberObject({ activeLabelID: labelID })); + message.destroy(); + message.success(`Default label was changed to "${label.name}"`); + } + } + }, + }; return (
+ {labelIDs.map( (labelID: number): JSX.Element => ( - + ), )}
); } + +export default React.memo(LabelsListComponent); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx index c5288a9b7132..6d15c3ce8bf7 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -13,7 +13,7 @@ import Layout from 'antd/lib/layout'; import { Canvas } from 'cvat-canvas-wrapper'; import { CombinedState } from 'reducers/interfaces'; -import LabelsListContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/labels-list'; +import LabelsList from 'components/annotation-page/standard-workspace/objects-side-bar/labels-list'; import { collapseSidebar as collapseSidebarAction, updateTabContentHeight as updateTabContentHeightAction, @@ -123,8 +123,8 @@ function ObjectsSideBar(props: StateToProps & DispatchToProps & OwnProps): JSX.E Objects} key='objects'> {objectsList} - Labels} key='labels'> - + Labels} key='labels'> + Issues} key='issues'> diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss index 8b7a7425f4d8..c66316031256 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss @@ -167,7 +167,8 @@ padding: 3px 1px 1px 3px; } -.cvat-objects-sidebar-state-item-color { +.cvat-objects-sidebar-state-item-color, +.cvat-objects-sidebar-label-item-color { border: 1px solid $object-item-border-color; width: 7px; opacity: 1; @@ -288,12 +289,16 @@ .cvat-objects-sidebar-label-active-item { background: $active-label-background-color; + border-top: 2px solid $object-item-border-color; + border-right: 2px solid $object-item-border-color; + border-bottom: 2px solid $object-item-border-color; + padding: 3px 1px 1px 3px; } .cvat-objects-sidebar-label-item { height: 2.5em; border-bottom: 1px solid $border-color-1; - padding: 5px; + padding: 5px 3px 3px 3px; span { @extend .cvat-object-sidebar-icon; @@ -311,10 +316,39 @@ } } -.cvat-label-item-color-button { - width: 30px; - height: 20px; - border-radius: 5px; +.cvat-label-item-color { + background: rgb(25, 184, 14); + height: 80%; + width: 90%; + border-radius: $grid-unit-size / 2; +} + +.cvat-label-item-setup-shortcut-button { + border-color: $objects-bar-icons-color; +} + +.cvat-label-item-setup-shortcut-popover { + margin-top: -$grid-unit-size; + margin-bottom: -$grid-unit-size; + + > div { + padding-top: $grid-unit-size; + padding-bottom: $grid-unit-size; + + > div { + display: flex; + justify-content: center; + + > button { + width: $grid-unit-size * 15; + overflow-x: hidden; + + span:first-child { + margin-right: $grid-unit-size; + } + } + } + } } .cvat-objects-appearance-content { @@ -361,3 +395,7 @@ margin-right: $grid-unit-size; } } + +.cvat-objects-sidebar-label-item-disabled { + opacity: 0.5; +} diff --git a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/tag-annotation-sidebar.tsx b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/tag-annotation-sidebar.tsx index 7b5e44563d44..2292c48ee42d 100644 --- a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/tag-annotation-sidebar.tsx +++ b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/tag-annotation-sidebar.tsx @@ -82,7 +82,7 @@ function mapDispatchToProps(dispatch: ThunkDispatch): dispatch(removeObjectAsync(jobInstance, objectState, true)); }, onRememberObject(labelID: number): void { - dispatch(rememberObject(ObjectType.TAG, labelID)); + dispatch(rememberObject({ activeObjectType: ObjectType.TAG, activeLabelID: labelID })); }, }; } diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx index f88c752df838..9cd2669cd065 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx @@ -41,7 +41,15 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { points?: number, rectDrawingMethod?: RectDrawingMethod, ): void { - dispatch(rememberObject(objectType, labelID, shapeType, points, rectDrawingMethod)); + dispatch( + rememberObject({ + activeObjectType: objectType, + activeShapeType: shapeType, + activeLabelID: labelID, + activeNumOfPoints: points, + activeRectDrawingMethod: rectDrawingMethod, + }), + ); }, }; } diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover.tsx index 5c0c5b8b7300..7004505f87cc 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -32,7 +32,7 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { dispatch(createAnnotationsAsync(sessionInstance, frame, states)); }, onRememberObject(labelID: number): void { - dispatch(rememberObject(ObjectType.TAG, labelID)); + dispatch(rememberObject({ activeObjectType: ObjectType.TAG, activeLabelID: labelID })); }, }; } diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/label-item.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/label-item.tsx index 8e89e244a019..6353ab2f13ef 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/label-item.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/label-item.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -12,6 +12,8 @@ import { CombinedState, ObjectType } from 'reducers/interfaces'; interface OwnProps { labelID: number; + keyToLabelMapping: Record; + updateLabelShortcutKey(updatedKey: string, labelID: number): void; } interface StateToProps { @@ -20,7 +22,7 @@ interface StateToProps { labelColor: string; objectStates: any[]; jobInstance: any; - frameNumber: any; + frameNumber: number; } interface DispatchToProps { @@ -127,35 +129,38 @@ class LabelItemContainer extends React.PureComponent { private switchHidden(value: boolean): void { const { updateAnnotations } = this.props; - const { ownObjectStates } = this.state; - for (const state of ownObjectStates) { - state.hidden = value; - } - updateAnnotations(ownObjectStates); + if (ownObjectStates.length) { + // false alarm + // eslint-disable-next-line + updateAnnotations(ownObjectStates.map((state: any) => ((state.hidden = value), state))); + } } private switchLock(value: boolean): void { const { updateAnnotations } = this.props; - const { ownObjectStates } = this.state; - for (const state of ownObjectStates) { - state.lock = value; - } - updateAnnotations(ownObjectStates); + if (ownObjectStates.length) { + // false alarm + // eslint-disable-next-line + updateAnnotations(ownObjectStates.map((state: any) => ((state.lock = value), state))); + } } public render(): JSX.Element { + const { + labelName, labelColor, keyToLabelMapping, labelID, updateLabelShortcutKey, + } = this.props; const { visible, statesHidden, statesLocked } = this.state; - const { labelName, labelColor } = this.props; - return ( { showStates={this.showStates} lockStates={this.lockStates} unlockStates={this.unlockStates} + updateLabelShortcutKey={updateLabelShortcutKey} /> ); } diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/labels-list.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/labels-list.tsx deleted file mode 100644 index 16cd97678ea1..000000000000 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/labels-list.tsx +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (C) 2020 Intel Corporation -// -// SPDX-License-Identifier: MIT - -import { connect } from 'react-redux'; - -import LabelsListComponent from 'components/annotation-page/standard-workspace/objects-side-bar/labels-list'; -import { CombinedState } from 'reducers/interfaces'; - -interface StateToProps { - labelIDs: number[]; - listHeight: number; -} - -function mapStateToProps(state: CombinedState): StateToProps { - const { - annotation: { - job: { labels }, - tabContentHeight: listHeight, - }, - } = state; - - return { - labelIDs: labels.map((label: any): number => label.id), - listHeight, - }; -} - -export default connect(mapStateToProps)(LabelsListComponent); diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index 84682fb262dd..cf8515cc1834 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -456,9 +456,22 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { }; } case AnnotationActionTypes.REMEMBER_CREATED_OBJECT: { - const { - shapeType, labelID, objectType, points, activeControl, rectDrawingMethod, - } = action.payload; + const { payload } = action; + + let { activeControl } = state.canvas; + if (payload.activeShapeType === ShapeType.RECTANGLE) { + activeControl = ActiveControl.DRAW_RECTANGLE; + } else if (payload.activeShapeType === ShapeType.POLYGON) { + activeControl = ActiveControl.DRAW_POLYGON; + } else if (payload.activeShapeType === ShapeType.POLYLINE) { + activeControl = ActiveControl.DRAW_POLYLINE; + } else if (payload.activeShapeType === ShapeType.POINTS) { + activeControl = ActiveControl.DRAW_POINTS; + } else if (payload.activeShapeType === ShapeType.CUBOID) { + activeControl = ActiveControl.DRAW_CUBOID; + } else if (payload.activeObjectType === ObjectType.TAG) { + activeControl = ActiveControl.CURSOR; + } return { ...state, @@ -471,12 +484,9 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { activeControl, }, drawing: { + ...state.drawing, + ...payload, activeInteractor: undefined, - activeLabelID: labelID, - activeNumOfPoints: points, - activeObjectType: objectType, - activeShapeType: shapeType, - activeRectDrawingMethod: rectDrawingMethod, }, }; } diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index 4defc78006f6..4ddeb301135c 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -381,6 +381,7 @@ export interface PredictorState { fetching: boolean; annotationAmount: number; mediaAmount: number; + annotatedFrames: number[]; } export interface AnnotationState { diff --git a/cvat-ui/src/reducers/shortcuts-reducer.ts b/cvat-ui/src/reducers/shortcuts-reducer.ts index 98baa9aa4419..3863f3b71652 100644 --- a/cvat-ui/src/reducers/shortcuts-reducer.ts +++ b/cvat-ui/src/reducers/shortcuts-reducer.ts @@ -287,10 +287,16 @@ const defaultKeyMap = ({ }, TOGGLE_LAYOUT_GRID: { name: 'Toggle layout grid', - description: 'Is used in development', + description: 'The grid is used to UI development', sequences: ['ctrl+alt+enter'], action: 'keydown', }, + SWITCH_LABEL: { + name: 'Switch label', + description: 'Changes a label for an activated object or for the next drawn object if no objects are activated', + sequences: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'].map((val: string): string => `ctrl+${val}`), + action: 'keydown', + }, } as any) as KeyMap; const defaultState: ShortcutsState = { diff --git a/cvat-ui/src/utils/mousetrap-react.tsx b/cvat-ui/src/utils/mousetrap-react.tsx index c51fe5fcd786..e8452e3d5562 100644 --- a/cvat-ui/src/utils/mousetrap-react.tsx +++ b/cvat-ui/src/utils/mousetrap-react.tsx @@ -17,7 +17,7 @@ export interface KeyMap { } export interface Handlers { - [index: string]: (event: KeyboardEvent) => void; + [index: string]: (event: KeyboardEvent, shortcut: string) => void; } interface Props {