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 <dmitry.kalinin@intel.com>
main
Boris Sekachev 5 years ago committed by GitHub
parent d2a1d12fba
commit 7524202492
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -8,23 +8,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [1.4.0] - Unreleased ## [1.4.0] - Unreleased
### Added ### Added
- Documentation on mask annotation (<https://github.com/openvinotoolkit/cvat/pull/3044>) - Documentation on mask annotation (<https://github.com/openvinotoolkit/cvat/pull/3044>)
- Hotkeys to switch a label of existing object or to change default label (for objects created with N) (<https://github.com/openvinotoolkit/cvat/pull/3070>)
### Changed ### Changed
- -
### Deprecated ### Deprecated
- -
### Removed ### Removed
- -
### Fixed ### Fixed
- Export of instance masks with holes (<https://github.com/openvinotoolkit/cvat/pull/3044>) - Export of instance masks with holes (<https://github.com/openvinotoolkit/cvat/pull/3044>)
### Security ### Security
-
-
## [1.3.0] - 3/31/2021 ## [1.3.0] - 3/31/2021

@ -1,6 +1,6 @@
{ {
"name": "cvat-ui", "name": "cvat-ui",
"version": "1.18.1", "version": "1.19.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

@ -1,6 +1,6 @@
{ {
"name": "cvat-ui", "name": "cvat-ui",
"version": "1.18.1", "version": "1.19.0",
"description": "CVAT single-page application", "description": "CVAT single-page application",
"main": "src/index.tsx", "main": "src/index.tsx",
"scripts": { "scripts": {

@ -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 // used to reproduce the latest drawing (in case of tags just creating) by using N
export function rememberObject( export function rememberObject(createParams: {
objectType: ObjectType, activeObjectType?: ObjectType;
labelID: number, activeLabelID?: number;
shapeType?: ShapeType, activeShapeType?: ShapeType;
points?: number, activeNumOfPoints?: number;
rectDrawingMethod?: RectDrawingMethod, activeRectDrawingMethod?: RectDrawingMethod;
): AnyAction { }): 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;
}
return { return {
type: AnnotationActionTypes.REMEMBER_CREATED_OBJECT, type: AnnotationActionTypes.REMEMBER_CREATED_OBJECT,
payload: { payload: createParams,
shapeType,
labelID,
objectType,
points,
activeControl,
rectDrawingMethod,
},
}; };
} }

@ -4,7 +4,6 @@
import React from 'react'; import React from 'react';
import { useSelector, useDispatch } from 'react-redux'; import { useSelector, useDispatch } from 'react-redux';
import { CombinedState } from 'reducers/interfaces';
import { import {
LeftOutlined, RightOutlined, EyeInvisibleFilled, EyeOutlined, LeftOutlined, RightOutlined, EyeInvisibleFilled, EyeOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
@ -14,6 +13,7 @@ import { Row, Col } from 'antd/lib/grid';
import { changeFrameAsync } from 'actions/annotation-actions'; import { changeFrameAsync } from 'actions/annotation-actions';
import { reviewActions } from 'actions/review-actions'; import { reviewActions } from 'actions/review-actions';
import CVATTooltip from 'components/common/cvat-tooltip'; import CVATTooltip from 'components/common/cvat-tooltip';
import { CombinedState } from 'reducers/interfaces';
export default function LabelsListComponent(): JSX.Element { export default function LabelsListComponent(): JSX.Element {
const dispatch = useDispatch(); const dispatch = useDispatch();

@ -1,4 +1,4 @@
// Copyright (C) 2020 Intel Corporation // Copyright (C) 2020-2021 Intel Corporation
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
@ -10,22 +10,30 @@ import {
LockFilled, UnlockOutlined, EyeInvisibleFilled, EyeOutlined, LockFilled, UnlockOutlined, EyeInvisibleFilled, EyeOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import CVATTooltip from 'components/common/cvat-tooltip';
import LabelKeySelectorPopover from './label-key-selector-popover';
interface Props { interface Props {
labelName: string; labelName: string;
labelColor: string; labelColor: string;
labelID: number;
visible: boolean; visible: boolean;
statesHidden: boolean; statesHidden: boolean;
statesLocked: boolean; statesLocked: boolean;
keyToLabelMapping: Record<string, number>;
hideStates(): void; hideStates(): void;
showStates(): void; showStates(): void;
lockStates(): void; lockStates(): void;
unlockStates(): void; unlockStates(): void;
updateLabelShortcutKey(updatedKey: string, labelID: number): void;
} }
function LabelItemComponent(props: Props): JSX.Element { function LabelItemComponent(props: Props): JSX.Element {
const { const {
labelName, labelName,
labelColor, labelColor,
labelID,
keyToLabelMapping,
visible, visible,
statesHidden, statesHidden,
statesLocked, statesLocked,
@ -33,8 +41,14 @@ function LabelItemComponent(props: Props): JSX.Element {
showStates, showStates,
lockStates, lockStates,
unlockStates, unlockStates,
updateLabelShortcutKey,
} = props; } = props;
// create reversed mapping just to receive key easily
const labelToKeyMapping: Record<string, string> = Object.fromEntries(
Object.entries(keyToLabelMapping).map(([key, _labelID]) => [_labelID, key]),
);
const labelShortcutKey = labelToKeyMapping[labelID] || '?';
const classes = { const classes = {
lock: { lock: {
enabled: { className: 'cvat-label-item-button-lock cvat-label-item-button-lock-enabled' }, enabled: { className: 'cvat-label-item-button-lock cvat-label-item-button-lock-enabled' },
@ -48,22 +62,37 @@ function LabelItemComponent(props: Props): JSX.Element {
return ( return (
<Row <Row
align='middle' align='stretch'
justify='space-around' justify='space-around'
className='cvat-objects-sidebar-label-item' className={[
style={{ display: visible ? 'flex' : 'none' }} 'cvat-objects-sidebar-label-item',
visible ? '' : 'cvat-objects-sidebar-label-item-disabled',
].join(' ')}
> >
<Col span={4}> <Col span={2}>
<Button style={{ background: labelColor }} className='cvat-label-item-color-button'> <div style={{ background: labelColor }} className='cvat-label-item-color'>
{' '} {' '}
</Button> </div>
</Col> </Col>
<Col span={14}> <Col span={12}>
<CVATTooltip title={labelName}>
<Text strong className='cvat-text'> <Text strong className='cvat-text'>
{labelName} {labelName}
</Text> </Text>
</CVATTooltip>
</Col> </Col>
<Col span={3}> <Col span={3}>
<LabelKeySelectorPopover
keyToLabelMapping={keyToLabelMapping}
labelID={labelID}
updateLabelShortcutKey={updateLabelShortcutKey}
>
<Button className='cvat-label-item-setup-shortcut-button' size='small' ghost type='dashed'>
{labelShortcutKey}
</Button>
</LabelKeySelectorPopover>
</Col>
<Col span={2} offset={1}>
{statesLocked ? ( {statesLocked ? (
<LockFilled {...classes.lock.enabled} onClick={unlockStates} /> <LockFilled {...classes.lock.enabled} onClick={unlockStates} />
) : ( ) : (

@ -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<string, number>;
labelID: number;
children: JSX.Element;
}
interface LabelKeySelectorPopoverContentProps {
updateLabelShortcutKey(updatedKey: string, labelID: number): void;
labelID: number;
keyToLabelMapping: Record<string, number>;
}
function PopoverContent(props: LabelKeySelectorPopoverContentProps): JSX.Element {
const { keyToLabelMapping, labelID, updateLabelShortcutKey } = props;
const labels = useSelector((state: CombinedState) => state.annotation.job.labels);
return (
<div className='cvat-label-item-setup-shortcut-popover'>
{[['1', '2', '3'], ['4', '5', '6'], ['7', '8', '9'], ['0']].map((arr, i_) => (
<Row justify='space-around' gutter={[16, 16]} key={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 (
<Col key={i} span={8}>
<CVATTooltip title={labelName}>
<Button onClick={() => updateLabelShortcutKey(i, labelID)}>
<Text>{`${i}:`}</Text>
<Text type='secondary'>{labelName}</Text>
</Button>
</CVATTooltip>
</Col>
);
})}
</Row>
))}
</div>
);
}
const MemoizedContent = React.memo(PopoverContent);
function LabelKeySelectorPopover(props: LabelKeySelectorPopoverProps): JSX.Element {
const {
children, labelID, updateLabelShortcutKey, keyToLabelMapping,
} = props;
return (
<Popover
destroyTooltipOnHide={{ keepParent: false }}
trigger='click'
content={(
<MemoizedContent
keyToLabelMapping={keyToLabelMapping}
labelID={labelID}
updateLabelShortcutKey={updateLabelShortcutKey}
/>
)}
placement='left'
>
{children}
</Popover>
);
}
export default React.memo(LabelKeySelectorPopover);

@ -1,26 +1,108 @@
// Copyright (C) 2020 Intel Corporation // Copyright (C) 2020-2021 Intel Corporation
// //
// SPDX-License-Identifier: MIT // 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 LabelItemContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/label-item';
import GlobalHotKeys from 'utils/mousetrap-react';
interface Props { function LabelsListComponent(): JSX.Element {
labelIDs: number[]; const dispatch = useDispatch();
listHeight: number; 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<Record<string, number>>(
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 { // check if this key is assigned to another label
const { listHeight, labelIDs } = props; 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 ( return (
<div style={{ height: listHeight }} className='cvat-objects-sidebar-labels-list'> <div style={{ height: listHeight }} className='cvat-objects-sidebar-labels-list'>
<GlobalHotKeys keyMap={subKeyMap} handlers={handlers} />
{labelIDs.map( {labelIDs.map(
(labelID: number): JSX.Element => ( (labelID: number): JSX.Element => (
<LabelItemContainer key={labelID} labelID={labelID} /> <LabelItemContainer
key={labelID}
labelID={labelID}
keyToLabelMapping={keyToLabelMapping}
updateLabelShortcutKey={updateLabelShortcutKey}
/>
), ),
)} )}
</div> </div>
); );
} }
export default React.memo(LabelsListComponent);

@ -1,4 +1,4 @@
// Copyright (C) 2020 Intel Corporation // Copyright (C) 2020-2021 Intel Corporation
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
@ -13,7 +13,7 @@ import Layout from 'antd/lib/layout';
import { Canvas } from 'cvat-canvas-wrapper'; import { Canvas } from 'cvat-canvas-wrapper';
import { CombinedState } from 'reducers/interfaces'; 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 { import {
collapseSidebar as collapseSidebarAction, collapseSidebar as collapseSidebarAction,
updateTabContentHeight as updateTabContentHeightAction, updateTabContentHeight as updateTabContentHeightAction,
@ -123,8 +123,8 @@ function ObjectsSideBar(props: StateToProps & DispatchToProps & OwnProps): JSX.E
<Tabs.TabPane tab={<Text strong>Objects</Text>} key='objects'> <Tabs.TabPane tab={<Text strong>Objects</Text>} key='objects'>
{objectsList} {objectsList}
</Tabs.TabPane> </Tabs.TabPane>
<Tabs.TabPane tab={<Text strong>Labels</Text>} key='labels'> <Tabs.TabPane forceRender tab={<Text strong>Labels</Text>} key='labels'>
<LabelsListContainer /> <LabelsList />
</Tabs.TabPane> </Tabs.TabPane>
<Tabs.TabPane tab={<Text strong>Issues</Text>} key='issues'> <Tabs.TabPane tab={<Text strong>Issues</Text>} key='issues'>
<IssuesListComponent /> <IssuesListComponent />

@ -167,7 +167,8 @@
padding: 3px 1px 1px 3px; 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; border: 1px solid $object-item-border-color;
width: 7px; width: 7px;
opacity: 1; opacity: 1;
@ -288,12 +289,16 @@
.cvat-objects-sidebar-label-active-item { .cvat-objects-sidebar-label-active-item {
background: $active-label-background-color; 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 { .cvat-objects-sidebar-label-item {
height: 2.5em; height: 2.5em;
border-bottom: 1px solid $border-color-1; border-bottom: 1px solid $border-color-1;
padding: 5px; padding: 5px 3px 3px 3px;
span { span {
@extend .cvat-object-sidebar-icon; @extend .cvat-object-sidebar-icon;
@ -311,10 +316,39 @@
} }
} }
.cvat-label-item-color-button { .cvat-label-item-color {
width: 30px; background: rgb(25, 184, 14);
height: 20px; height: 80%;
border-radius: 5px; 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 { .cvat-objects-appearance-content {
@ -361,3 +395,7 @@
margin-right: $grid-unit-size; margin-right: $grid-unit-size;
} }
} }
.cvat-objects-sidebar-label-item-disabled {
opacity: 0.5;
}

@ -82,7 +82,7 @@ function mapDispatchToProps(dispatch: ThunkDispatch<CombinedState, {}, Action>):
dispatch(removeObjectAsync(jobInstance, objectState, true)); dispatch(removeObjectAsync(jobInstance, objectState, true));
}, },
onRememberObject(labelID: number): void { onRememberObject(labelID: number): void {
dispatch(rememberObject(ObjectType.TAG, labelID)); dispatch(rememberObject({ activeObjectType: ObjectType.TAG, activeLabelID: labelID }));
}, },
}; };
} }

@ -41,7 +41,15 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
points?: number, points?: number,
rectDrawingMethod?: RectDrawingMethod, rectDrawingMethod?: RectDrawingMethod,
): void { ): void {
dispatch(rememberObject(objectType, labelID, shapeType, points, rectDrawingMethod)); dispatch(
rememberObject({
activeObjectType: objectType,
activeShapeType: shapeType,
activeLabelID: labelID,
activeNumOfPoints: points,
activeRectDrawingMethod: rectDrawingMethod,
}),
);
}, },
}; };
} }

@ -1,4 +1,4 @@
// Copyright (C) 2020 Intel Corporation // Copyright (C) 2020-2021 Intel Corporation
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
@ -32,7 +32,7 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
dispatch(createAnnotationsAsync(sessionInstance, frame, states)); dispatch(createAnnotationsAsync(sessionInstance, frame, states));
}, },
onRememberObject(labelID: number): void { onRememberObject(labelID: number): void {
dispatch(rememberObject(ObjectType.TAG, labelID)); dispatch(rememberObject({ activeObjectType: ObjectType.TAG, activeLabelID: labelID }));
}, },
}; };
} }

@ -1,4 +1,4 @@
// Copyright (C) 2020 Intel Corporation // Copyright (C) 2020-2021 Intel Corporation
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
@ -12,6 +12,8 @@ import { CombinedState, ObjectType } from 'reducers/interfaces';
interface OwnProps { interface OwnProps {
labelID: number; labelID: number;
keyToLabelMapping: Record<string, number>;
updateLabelShortcutKey(updatedKey: string, labelID: number): void;
} }
interface StateToProps { interface StateToProps {
@ -20,7 +22,7 @@ interface StateToProps {
labelColor: string; labelColor: string;
objectStates: any[]; objectStates: any[];
jobInstance: any; jobInstance: any;
frameNumber: any; frameNumber: number;
} }
interface DispatchToProps { interface DispatchToProps {
@ -127,35 +129,38 @@ class LabelItemContainer extends React.PureComponent<Props, State> {
private switchHidden(value: boolean): void { private switchHidden(value: boolean): void {
const { updateAnnotations } = this.props; const { updateAnnotations } = this.props;
const { ownObjectStates } = this.state; 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 { private switchLock(value: boolean): void {
const { updateAnnotations } = this.props; const { updateAnnotations } = this.props;
const { ownObjectStates } = this.state; 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 { public render(): JSX.Element {
const {
labelName, labelColor, keyToLabelMapping, labelID, updateLabelShortcutKey,
} = this.props;
const { visible, statesHidden, statesLocked } = this.state; const { visible, statesHidden, statesLocked } = this.state;
const { labelName, labelColor } = this.props;
return ( return (
<LabelItemComponent <LabelItemComponent
labelName={labelName} labelName={labelName}
labelColor={labelColor} labelColor={labelColor}
labelID={labelID}
keyToLabelMapping={keyToLabelMapping}
visible={visible} visible={visible}
statesHidden={statesHidden} statesHidden={statesHidden}
statesLocked={statesLocked} statesLocked={statesLocked}
@ -163,6 +168,7 @@ class LabelItemContainer extends React.PureComponent<Props, State> {
showStates={this.showStates} showStates={this.showStates}
lockStates={this.lockStates} lockStates={this.lockStates}
unlockStates={this.unlockStates} unlockStates={this.unlockStates}
updateLabelShortcutKey={updateLabelShortcutKey}
/> />
); );
} }

@ -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);

@ -456,9 +456,22 @@ export default (state = defaultState, action: AnyAction): AnnotationState => {
}; };
} }
case AnnotationActionTypes.REMEMBER_CREATED_OBJECT: { case AnnotationActionTypes.REMEMBER_CREATED_OBJECT: {
const { const { payload } = action;
shapeType, labelID, objectType, points, activeControl, rectDrawingMethod,
} = action.payload; 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 { return {
...state, ...state,
@ -471,12 +484,9 @@ export default (state = defaultState, action: AnyAction): AnnotationState => {
activeControl, activeControl,
}, },
drawing: { drawing: {
...state.drawing,
...payload,
activeInteractor: undefined, activeInteractor: undefined,
activeLabelID: labelID,
activeNumOfPoints: points,
activeObjectType: objectType,
activeShapeType: shapeType,
activeRectDrawingMethod: rectDrawingMethod,
}, },
}; };
} }

@ -381,6 +381,7 @@ export interface PredictorState {
fetching: boolean; fetching: boolean;
annotationAmount: number; annotationAmount: number;
mediaAmount: number; mediaAmount: number;
annotatedFrames: number[];
} }
export interface AnnotationState { export interface AnnotationState {

@ -287,10 +287,16 @@ const defaultKeyMap = ({
}, },
TOGGLE_LAYOUT_GRID: { TOGGLE_LAYOUT_GRID: {
name: 'Toggle layout grid', name: 'Toggle layout grid',
description: 'Is used in development', description: 'The grid is used to UI development',
sequences: ['ctrl+alt+enter'], sequences: ['ctrl+alt+enter'],
action: 'keydown', 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; } as any) as KeyMap;
const defaultState: ShortcutsState = { const defaultState: ShortcutsState = {

@ -17,7 +17,7 @@ export interface KeyMap {
} }
export interface Handlers { export interface Handlers {
[index: string]: (event: KeyboardEvent) => void; [index: string]: (event: KeyboardEvent, shortcut: string) => void;
} }
interface Props { interface Props {

Loading…
Cancel
Save