Merge pull request #1323 from opencv/bs/ui_titles

React UI: Added titles
main
Dmitry Kalinin 6 years ago committed by GitHub
commit 65b1b5c7d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { GlobalHotKeys, KeyMap } from 'react-hotkeys'; import { GlobalHotKeys, ExtendedKeyMapOptions } from 'react-hotkeys';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Action } from 'redux'; import { Action } from 'redux';
import { ThunkDispatch } from 'redux-thunk'; import { ThunkDispatch } from 'redux-thunk';
@ -32,6 +32,8 @@ interface StateToProps {
states: any[]; states: any[];
labels: any[]; labels: any[];
jobInstance: any; jobInstance: any;
keyMap: Record<string, ExtendedKeyMapOptions>;
normalizedKeyMap: Record<string, string>;
} }
interface DispatchToProps { interface DispatchToProps {
@ -56,6 +58,10 @@ function mapStateToProps(state: CombinedState): StateToProps {
labels, labels,
}, },
}, },
shortcuts: {
keyMap,
normalizedKeyMap,
},
} = state; } = state;
return { return {
@ -64,6 +70,8 @@ function mapStateToProps(state: CombinedState): StateToProps {
activatedStateID, activatedStateID,
activatedAttributeID, activatedAttributeID,
states, states,
keyMap,
normalizedKeyMap,
}; };
} }
@ -87,6 +95,8 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.
jobInstance, jobInstance,
updateAnnotations, updateAnnotations,
activateObject, activateObject,
keyMap,
normalizedKeyMap,
} = props; } = props;
const [labelAttrMap, setLabelAttrMap] = useState( const [labelAttrMap, setLabelAttrMap] = useState(
@ -167,31 +177,11 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.
trigger: null, trigger: null,
}; };
const keyMap = { const subKeyMap = {
NEXT_ATTRIBUTE: { NEXT_ATTRIBUTE: keyMap.NEXT_ATTRIBUTE,
name: 'Next attribute', PREVIOUS_ATTRIBUTE: keyMap.PREVIOUS_ATTRIBUTE,
description: 'Go to the next attribute', NEXT_OBJECT: keyMap.NEXT_OBJECT,
sequence: 'ArrowDown', PREVIOUS_OBJECT: keyMap.PREVIOUS_OBJECT,
action: 'keydown',
},
PREVIOUS_ATTRIBUTE: {
name: 'Previous attribute',
description: 'Go to the previous attribute',
sequence: 'ArrowUp',
action: 'keydown',
},
NEXT_OBJECT: {
name: 'Next object',
description: 'Go to the next object',
sequence: 'Tab',
action: 'keydown',
},
PREVIOUS_OBJECT: {
name: 'Previous object',
description: 'Go to the previous object',
sequence: 'Shift+Tab',
action: 'keydown',
},
}; };
const handlers = { const handlers = {
@ -228,7 +218,7 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.
if (activeObjectState) { if (activeObjectState) {
return ( return (
<Layout.Sider {...siderProps}> <Layout.Sider {...siderProps}>
<GlobalHotKeys keyMap={keyMap as any as KeyMap} handlers={handlers} allowChanges /> <GlobalHotKeys keyMap={subKeyMap} handlers={handlers} allowChanges />
<Row> <Row>
<Col> <Col>
<AnnotationsFiltersInput /> <AnnotationsFiltersInput />
@ -240,6 +230,7 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.
occluded={activeObjectState.occluded} occluded={activeObjectState.occluded}
objectsCount={states.length} objectsCount={states.length}
currentIndex={states.indexOf(activeObjectState)} currentIndex={states.indexOf(activeObjectState)}
normalizedKeyMap={normalizedKeyMap}
nextObject={nextObject} nextObject={nextObject}
/> />
<ObjectBasicsEditor <ObjectBasicsEditor
@ -267,6 +258,7 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.
currentIndex={activeObjectState.label.attributes currentIndex={activeObjectState.label.attributes
.indexOf(activeAttribute)} .indexOf(activeAttribute)}
attributesCount={activeObjectState.label.attributes.length} attributesCount={activeObjectState.label.attributes.length}
normalizedKeyMap={normalizedKeyMap}
nextAttribute={nextAttribute} nextAttribute={nextAttribute}
/> />
<AttributeEditor <AttributeEditor

@ -12,6 +12,7 @@ interface Props {
currentAttribute: string; currentAttribute: string;
currentIndex: number; currentIndex: number;
attributesCount: number; attributesCount: number;
normalizedKeyMap: Record<string, string>;
nextAttribute(step: number): void; nextAttribute(step: number): void;
} }
@ -21,21 +22,26 @@ function AttributeSwitcher(props: Props): JSX.Element {
currentIndex, currentIndex,
attributesCount, attributesCount,
nextAttribute, nextAttribute,
normalizedKeyMap,
} = props; } = props;
const title = `${currentAttribute} [${currentIndex + 1}/${attributesCount}]`; const title = `${currentAttribute} [${currentIndex + 1}/${attributesCount}]`;
return ( return (
<div className='attribute-annotation-sidebar-switcher'> <div className='attribute-annotation-sidebar-switcher'>
<Button disabled={attributesCount <= 1} onClick={() => nextAttribute(-1)}> <Tooltip title={`Previous attribute ${normalizedKeyMap.PREVIOUS_ATTRIBUTE}`}>
<Icon type='left' /> <Button disabled={attributesCount <= 1} onClick={() => nextAttribute(-1)}>
</Button> <Icon type='left' />
</Button>
</Tooltip>
<Tooltip title={title}> <Tooltip title={title}>
<Text className='cvat-text'>{currentAttribute}</Text> <Text className='cvat-text'>{currentAttribute}</Text>
<Text strong>{` [${currentIndex + 1}/${attributesCount}]`}</Text> <Text strong>{` [${currentIndex + 1}/${attributesCount}]`}</Text>
</Tooltip> </Tooltip>
<Button disabled={attributesCount <= 1} onClick={() => nextAttribute(1)}> <Tooltip title={`Next attribute ${normalizedKeyMap.NEXT_ATTRIBUTE}`}>
<Icon type='right' /> <Button disabled={attributesCount <= 1} onClick={() => nextAttribute(1)}>
</Button> <Icon type='right' />
</Button>
</Tooltip>
</div> </div>
); );
} }

@ -14,6 +14,7 @@ interface Props {
occluded: boolean; occluded: boolean;
objectsCount: number; objectsCount: number;
currentIndex: number; currentIndex: number;
normalizedKeyMap: Record<string, string>;
nextObject(step: number): void; nextObject(step: number): void;
} }
@ -24,23 +25,28 @@ function ObjectSwitcher(props: Props): JSX.Element {
objectsCount, objectsCount,
currentIndex, currentIndex,
nextObject, nextObject,
normalizedKeyMap,
} = props; } = props;
const title = `${currentLabel} ${clientID} [${currentIndex + 1}/${objectsCount}]`; const title = `${currentLabel} ${clientID} [${currentIndex + 1}/${objectsCount}]`;
return ( return (
<div className='attribute-annotation-sidebar-switcher'> <div className='attribute-annotation-sidebar-switcher'>
<Button disabled={objectsCount <= 1} onClick={() => nextObject(-1)}> <Tooltip title={`Previous object ${normalizedKeyMap.PREVIOUS_OBJECT}`}>
<Icon type='left' /> <Button disabled={objectsCount <= 1} onClick={() => nextObject(-1)}>
</Button> <Icon type='left' />
</Button>
</Tooltip>
<Tooltip title={title}> <Tooltip title={title}>
<Text className='cvat-text'>{currentLabel}</Text> <Text className='cvat-text'>{currentLabel}</Text>
<Text className='cvat-text'>{` ${clientID} `}</Text> <Text className='cvat-text'>{` ${clientID} `}</Text>
<Text strong>{`[${currentIndex + 1}/${objectsCount}]`}</Text> <Text strong>{`[${currentIndex + 1}/${objectsCount}]`}</Text>
</Tooltip> </Tooltip>
<Button disabled={objectsCount <= 1} onClick={() => nextObject(1)}> <Tooltip title={`Next object ${normalizedKeyMap.NEXT_OBJECT}`}>
<Icon type='right' /> <Button disabled={objectsCount <= 1} onClick={() => nextObject(1)}>
</Button> <Icon type='right' />
</Button>
</Tooltip>
</div> </div>
); );
} }

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React from 'react'; import React from 'react';
import { GlobalHotKeys, KeyMap } from 'react-hotkeys'; import { GlobalHotKeys, ExtendedKeyMapOptions } from 'react-hotkeys';
import Tooltip from 'antd/lib/tooltip'; import Tooltip from 'antd/lib/tooltip';
import Icon from 'antd/lib/icon'; import Icon from 'antd/lib/icon';
@ -59,6 +59,7 @@ interface Props {
contextType: ContextMenuType; contextType: ContextMenuType;
aamZoomMargin: number; aamZoomMargin: number;
workspace: Workspace; workspace: Workspace;
keyMap: Record<string, ExtendedKeyMapOptions>;
onSetupCanvas: () => void; onSetupCanvas: () => void;
onDragCanvas: (enabled: boolean) => void; onDragCanvas: (enabled: boolean) => void;
onZoomCanvas: (enabled: boolean) => void; onZoomCanvas: (enabled: boolean) => void;
@ -683,6 +684,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
onChangeGridColor, onChangeGridColor,
onChangeGridOpacity, onChangeGridOpacity,
onSwitchGrid, onSwitchGrid,
keyMap,
} = this.props; } = this.props;
const preventDefault = (event: KeyboardEvent | undefined): void => { const preventDefault = (event: KeyboardEvent | undefined): void => {
@ -691,61 +693,16 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
} }
}; };
const keyMap = { const subKeyMap = {
INCREASE_BRIGHTNESS: { INCREASE_BRIGHTNESS: keyMap.INCREASE_BRIGHTNESS,
name: 'Brightness+', DECREASE_BRIGHTNESS: keyMap.DECREASE_BRIGHTNESS,
description: 'Increase brightness level for the image', INCREASE_CONTRAST: keyMap.INCREASE_CONTRAST,
sequence: 'shift+b+=', DECREASE_CONTRAST: keyMap.DECREASE_CONTRAST,
action: 'keypress', INCREASE_SATURATION: keyMap.INCREASE_SATURATION,
}, DECREASE_SATURATION: keyMap.DECREASE_SATURATION,
DECREASE_BRIGHTNESS: { INCREASE_GRID_OPACITY: keyMap.INCREASE_GRID_OPACITY,
name: 'Brightness-', DECREASE_GRID_OPACITY: keyMap.DECREASE_GRID_OPACITY,
description: 'Decrease brightness level for the image', CHANGE_GRID_COLOR: keyMap.CHANGE_GRID_COLOR,
sequence: 'shift+b+-',
action: 'keydown',
},
INCREASE_CONTRAST: {
name: 'Contrast+',
description: 'Increase contrast level for the image',
sequence: 'shift+c+=',
action: 'keydown',
},
DECREASE_CONTRAST: {
name: 'Contrast-',
description: 'Decrease contrast level for the image',
sequence: 'shift+c+-',
action: 'keydown',
},
INCREASE_SATURATION: {
name: 'Saturation+',
description: 'Increase saturation level for the image',
sequence: 'shift+s+=',
action: 'keydown',
},
DECREASE_SATURATION: {
name: 'Saturation-',
description: 'Increase contrast level for the image',
sequence: 'shift+s+-',
action: 'keydown',
},
INCREASE_GRID_OPACITY: {
name: 'Grid opacity+',
description: 'Make the grid more visible',
sequence: 'shift+g+=',
action: 'keydown',
},
DECREASE_GRID_OPACITY: {
name: 'Grid opacity-',
description: 'Make the grid less visible',
sequences: 'shift+g+-',
action: 'keydown',
},
CHANGE_GRID_COLOR: {
name: 'Grid color',
description: 'Set another color for the image grid',
sequence: 'shift+g+enter',
action: 'keydown',
},
}; };
const step = 10; const step = 10;
@ -826,7 +783,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
return ( return (
<Layout.Content style={{ position: 'relative' }}> <Layout.Content style={{ position: 'relative' }}>
<GlobalHotKeys keyMap={keyMap as any as KeyMap} handlers={handlers} allowChanges /> <GlobalHotKeys keyMap={subKeyMap} handlers={handlers} allowChanges />
{/* {/*
This element doesn't have any props This element doesn't have any props
So, React isn't going to rerender it So, React isn't going to rerender it

@ -3,20 +3,11 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React from 'react'; import React from 'react';
import { GlobalHotKeys, KeyMap } from 'react-hotkeys'; import { GlobalHotKeys, ExtendedKeyMapOptions } from 'react-hotkeys';
import Layout from 'antd/lib/layout';
import { import { ActiveControl, Rotation } from 'reducers/interfaces';
Layout, import { Canvas } from 'cvat-canvas';
} from 'antd';
import {
ActiveControl,
Rotation,
} from 'reducers/interfaces';
import {
Canvas,
} from 'cvat-canvas';
import RotateControl from './rotate-control'; import RotateControl from './rotate-control';
import CursorControl from './cursor-control'; import CursorControl from './cursor-control';
@ -35,6 +26,8 @@ import SplitControl from './split-control';
interface Props { interface Props {
canvasInstance: Canvas; canvasInstance: Canvas;
activeControl: ActiveControl; activeControl: ActiveControl;
keyMap: Record<string, ExtendedKeyMapOptions>;
normalizedKeyMap: Record<string, string>;
mergeObjects(enabled: boolean): void; mergeObjects(enabled: boolean): void;
groupObjects(enabled: boolean): void; groupObjects(enabled: boolean): void;
@ -57,6 +50,8 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element {
repeatDrawShape, repeatDrawShape,
pasteShape, pasteShape,
resetGroup, resetGroup,
normalizedKeyMap,
keyMap,
} = props; } = props;
const preventDefault = (event: KeyboardEvent | undefined): void => { const preventDefault = (event: KeyboardEvent | undefined): void => {
@ -65,55 +60,15 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element {
} }
}; };
const keyMap = { const subKeyMap = {
PASTE_SHAPE: { PASTE_SHAPE: keyMap.PASTE_SHAPE,
name: 'Paste shape', SWITCH_DRAW_MODE: keyMap.SWITCH_DRAW_MODE,
description: 'Paste a shape from internal CVAT clipboard', SWITCH_MERGE_MODE: keyMap.SWITCH_MERGE_MODE,
sequence: 'ctrl+v', SWITCH_GROUP_MODE: keyMap.SWITCH_GROUP_MODE,
action: 'keydown', RESET_GROUP: keyMap.RESET_GROUP,
}, CANCEL: keyMap.CANCEL,
SWITCH_DRAW_MODE: { CLOCKWISE_ROTATION: keyMap.CLOCKWISE_ROTATION,
name: 'Draw mode', ANTICLOCKWISE_ROTATION: keyMap.ANTICLOCKWISE_ROTATION,
description: 'Repeat the latest procedure of drawing with the same parameters',
sequence: 'n',
action: 'keydown',
},
SWITCH_MERGE_MODE: {
name: 'Merge mode',
description: 'Activate or deactivate mode to merging shapes',
sequence: 'm',
action: 'keydown',
},
SWITCH_GROUP_MODE: {
name: 'Group mode',
description: 'Activate or deactivate mode to grouping shapes',
sequence: 'g',
action: 'keydown',
},
RESET_GROUP: {
name: 'Reset group',
description: 'Reset group for selected shapes (in group mode)',
sequence: 'shift+g',
action: 'keyup',
},
CANCEL: {
name: 'Cancel',
description: 'Cancel any active canvas mode',
sequence: 'esc',
action: 'keydown',
},
CLOCKWISE_ROTATION: {
name: 'Rotate clockwise',
description: 'Change image angle (add 90 degrees)',
sequence: 'ctrl+r',
action: 'keydown',
},
ANTICLOCKWISE_ROTATION: {
name: 'Rotate anticlockwise',
description: 'Change image angle (substract 90 degrees)',
sequence: 'ctrl+shift+r',
action: 'keydown',
},
}; };
const handlers = { const handlers = {
@ -186,11 +141,18 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element {
theme='light' theme='light'
width={44} width={44}
> >
<GlobalHotKeys keyMap={keyMap as any as KeyMap} handlers={handlers} allowChanges /> <GlobalHotKeys keyMap={subKeyMap} handlers={handlers} allowChanges />
<CursorControl
<CursorControl canvasInstance={canvasInstance} activeControl={activeControl} /> cursorShortkey={normalizedKeyMap.CANCEL}
canvasInstance={canvasInstance}
activeControl={activeControl}
/>
<MoveControl canvasInstance={canvasInstance} activeControl={activeControl} /> <MoveControl canvasInstance={canvasInstance} activeControl={activeControl} />
<RotateControl rotateFrame={rotateFrame} /> <RotateControl
anticlockwiseShortcut={normalizedKeyMap.ANTICLOCKWISE_ROTATION}
clockwiseShortcut={normalizedKeyMap.CLOCKWISE_ROTATION}
rotateFrame={rotateFrame}
/>
<hr /> <hr />
@ -224,11 +186,14 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element {
<hr /> <hr />
<MergeControl <MergeControl
switchMergeShortcut={normalizedKeyMap.SWITCH_MERGE_MODE}
canvasInstance={canvasInstance} canvasInstance={canvasInstance}
activeControl={activeControl} activeControl={activeControl}
mergeObjects={mergeObjects} mergeObjects={mergeObjects}
/> />
<GroupControl <GroupControl
switchGroupShortcut={normalizedKeyMap.SWITCH_GROUP_MODE}
resetGroupShortcut={normalizedKeyMap.RESET_GROUP}
canvasInstance={canvasInstance} canvasInstance={canvasInstance}
activeControl={activeControl} activeControl={activeControl}
groupObjects={groupObjects} groupObjects={groupObjects}

@ -3,26 +3,16 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React from 'react'; import React from 'react';
import Icon from 'antd/lib/icon';
import Tooltip from 'antd/lib/tooltip';
import { import { CursorIcon } from 'icons';
Icon, import { ActiveControl } from 'reducers/interfaces';
Tooltip, import { Canvas } from 'cvat-canvas';
} from 'antd';
import {
CursorIcon,
} from 'icons';
import {
ActiveControl,
} from 'reducers/interfaces';
import {
Canvas,
} from 'cvat-canvas';
interface Props { interface Props {
canvasInstance: Canvas; canvasInstance: Canvas;
cursorShortkey: string;
activeControl: ActiveControl; activeControl: ActiveControl;
} }
@ -30,10 +20,11 @@ function CursorControl(props: Props): JSX.Element {
const { const {
canvasInstance, canvasInstance,
activeControl, activeControl,
cursorShortkey,
} = props; } = props;
return ( return (
<Tooltip title='Cursor' placement='right'> <Tooltip title={`Cursor ${cursorShortkey}`} placement='right'>
<Icon <Icon
component={CursorIcon} component={CursorIcon}
className={activeControl === ActiveControl.CURSOR className={activeControl === ActiveControl.CURSOR

@ -3,10 +3,8 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React from 'react'; import React from 'react';
import { import Popover from 'antd/lib/popover';
Popover, import Icon from 'antd/lib/icon';
Icon,
} from 'antd';
import { Canvas } from 'cvat-canvas'; import { Canvas } from 'cvat-canvas';
import { PointIcon } from 'icons'; import { PointIcon } from 'icons';
@ -20,10 +18,7 @@ interface Props {
} }
function DrawPointsControl(props: Props): JSX.Element { function DrawPointsControl(props: Props): JSX.Element {
const { const { canvasInstance, isDrawing } = props;
canvasInstance,
isDrawing,
} = props;
const dynamcPopoverPros = isDrawing ? { const dynamcPopoverPros = isDrawing ? {
overlayStyle: { overlayStyle: {

@ -3,10 +3,8 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React from 'react'; import React from 'react';
import { import Popover from 'antd/lib/popover';
Popover, import Icon from 'antd/lib/icon';
Icon,
} from 'antd';
import { Canvas } from 'cvat-canvas'; import { Canvas } from 'cvat-canvas';
import { PolygonIcon } from 'icons'; import { PolygonIcon } from 'icons';
@ -20,10 +18,7 @@ interface Props {
} }
function DrawPolygonControl(props: Props): JSX.Element { function DrawPolygonControl(props: Props): JSX.Element {
const { const { canvasInstance, isDrawing } = props;
canvasInstance,
isDrawing,
} = props;
const dynamcPopoverPros = isDrawing ? { const dynamcPopoverPros = isDrawing ? {
overlayStyle: { overlayStyle: {

@ -3,10 +3,8 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React from 'react'; import React from 'react';
import { import Popover from 'antd/lib/popover';
Popover, import Icon from 'antd/lib/icon';
Icon,
} from 'antd';
import { Canvas } from 'cvat-canvas'; import { Canvas } from 'cvat-canvas';
import { PolylineIcon } from 'icons'; import { PolylineIcon } from 'icons';
@ -20,10 +18,7 @@ interface Props {
} }
function DrawPolylineControl(props: Props): JSX.Element { function DrawPolylineControl(props: Props): JSX.Element {
const { const { canvasInstance, isDrawing } = props;
canvasInstance,
isDrawing,
} = props;
const dynamcPopoverPros = isDrawing ? { const dynamcPopoverPros = isDrawing ? {
overlayStyle: { overlayStyle: {

@ -3,10 +3,8 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React from 'react'; import React from 'react';
import { import Popover from 'antd/lib/popover';
Popover, import Icon from 'antd/lib/icon';
Icon,
} from 'antd';
import { Canvas } from 'cvat-canvas'; import { Canvas } from 'cvat-canvas';
import { RectangleIcon } from 'icons'; import { RectangleIcon } from 'icons';
@ -20,10 +18,7 @@ interface Props {
} }
function DrawRectangleControl(props: Props): JSX.Element { function DrawRectangleControl(props: Props): JSX.Element {
const { const { canvasInstance, isDrawing } = props;
canvasInstance,
isDrawing,
} = props;
const dynamcPopoverPros = isDrawing ? { const dynamcPopoverPros = isDrawing ? {
overlayStyle: { overlayStyle: {
@ -44,7 +39,9 @@ function DrawRectangleControl(props: Props): JSX.Element {
overlayClassName='cvat-draw-shape-popover' overlayClassName='cvat-draw-shape-popover'
placement='right' placement='right'
content={( content={(
<DrawShapePopoverContainer shapeType={ShapeType.RECTANGLE} /> <DrawShapePopoverContainer
shapeType={ShapeType.RECTANGLE}
/>
)} )}
> >
<Icon <Icon

@ -9,6 +9,7 @@ import Select from 'antd/lib/select';
import Button from 'antd/lib/button'; import Button from 'antd/lib/button';
import InputNumber from 'antd/lib/input-number'; import InputNumber from 'antd/lib/input-number';
import Radio, { RadioChangeEvent } from 'antd/lib/radio'; import Radio, { RadioChangeEvent } from 'antd/lib/radio';
import Tooltip from 'antd/lib/tooltip';
import Text from 'antd/lib/typography/Text'; import Text from 'antd/lib/typography/Text';
import { RectDrawingMethod } from 'cvat-canvas'; import { RectDrawingMethod } from 'cvat-canvas';
@ -22,6 +23,7 @@ interface Props {
rectDrawingMethod?: RectDrawingMethod; rectDrawingMethod?: RectDrawingMethod;
numberOfPoints?: number; numberOfPoints?: number;
selectedLabeID: number; selectedLabeID: number;
repeatShapeShortcut: string;
onChangeLabel(value: string): void; onChangeLabel(value: string): void;
onChangePoints(value: number | undefined): void; onChangePoints(value: number | undefined): void;
onChangeRectDrawingMethod(event: RadioChangeEvent): void; onChangeRectDrawingMethod(event: RadioChangeEvent): void;
@ -37,6 +39,7 @@ function DrawShapePopoverComponent(props: Props): JSX.Element {
selectedLabeID, selectedLabeID,
numberOfPoints, numberOfPoints,
rectDrawingMethod, rectDrawingMethod,
repeatShapeShortcut,
onDrawTrack, onDrawTrack,
onDrawShape, onDrawShape,
onChangeLabel, onChangeLabel,
@ -133,19 +136,23 @@ function DrawShapePopoverComponent(props: Props): JSX.Element {
} }
<Row type='flex' justify='space-around'> <Row type='flex' justify='space-around'>
<Col span={12}> <Col span={12}>
<Button <Tooltip title={`Press ${repeatShapeShortcut} to draw again`}>
onClick={onDrawShape} <Button
> onClick={onDrawShape}
Shape >
</Button> Shape
</Button>
</Tooltip>
</Col> </Col>
<Col span={12}> <Col span={12}>
<Button <Tooltip title={`Press ${repeatShapeShortcut} to draw again`}>
onClick={onDrawTrack} <Button
disabled={shapeType !== ShapeType.RECTANGLE} onClick={onDrawTrack}
> disabled={shapeType !== ShapeType.RECTANGLE}
Track >
</Button> Track
</Button>
</Tooltip>
</Col> </Col>
</Row> </Row>
</div> </div>

@ -3,19 +3,11 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React from 'react'; import React from 'react';
import Icon from 'antd/lib/icon';
import Tooltip from 'antd/lib/tooltip';
import { import { FitIcon } from 'icons';
Icon, import { Canvas } from 'cvat-canvas';
Tooltip,
} from 'antd';
import {
FitIcon,
} from 'icons';
import {
Canvas,
} from 'cvat-canvas';
interface Props { interface Props {
canvasInstance: Canvas; canvasInstance: Canvas;
@ -27,7 +19,7 @@ function FitControl(props: Props): JSX.Element {
} = props; } = props;
return ( return (
<Tooltip title='Fit the image' placement='right'> <Tooltip title='Fit the image [Double Click]' placement='right'>
<Icon component={FitIcon} onClick={(): void => canvasInstance.fit()} /> <Icon component={FitIcon} onClick={(): void => canvasInstance.fit()} />
</Tooltip> </Tooltip>
); );

@ -3,28 +3,25 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React from 'react'; import React from 'react';
import Tooltip from 'antd/lib/tooltip';
import Icon from 'antd/lib/icon';
import { import { GroupIcon } from 'icons';
Tooltip,
Icon,
} from 'antd';
import {
GroupIcon,
} from 'icons';
import { Canvas } from 'cvat-canvas'; import { Canvas } from 'cvat-canvas';
import { ActiveControl } from 'reducers/interfaces'; import { ActiveControl } from 'reducers/interfaces';
interface Props { interface Props {
canvasInstance: Canvas; canvasInstance: Canvas;
activeControl: ActiveControl; activeControl: ActiveControl;
switchGroupShortcut: string;
resetGroupShortcut: string;
groupObjects(enabled: boolean): void; groupObjects(enabled: boolean): void;
} }
function GroupControl(props: Props): JSX.Element { function GroupControl(props: Props): JSX.Element {
const { const {
switchGroupShortcut,
resetGroupShortcut,
activeControl, activeControl,
canvasInstance, canvasInstance,
groupObjects, groupObjects,
@ -45,8 +42,10 @@ function GroupControl(props: Props): JSX.Element {
}, },
}; };
const title = `Group shapes/tracks ${switchGroupShortcut}.`
+ ` Select and press ${resetGroupShortcut} to reset a group`;
return ( return (
<Tooltip title='Group shapes/tracks' placement='right'> <Tooltip title={title} placement='right'>
<Icon {...dynamicIconProps} component={GroupIcon} /> <Icon {...dynamicIconProps} component={GroupIcon} />
</Tooltip> </Tooltip>
); );

@ -19,12 +19,13 @@ import { ActiveControl } from 'reducers/interfaces';
interface Props { interface Props {
canvasInstance: Canvas; canvasInstance: Canvas;
activeControl: ActiveControl; activeControl: ActiveControl;
switchMergeShortcut: string;
mergeObjects(enabled: boolean): void; mergeObjects(enabled: boolean): void;
} }
function MergeControl(props: Props): JSX.Element { function MergeControl(props: Props): JSX.Element {
const { const {
switchMergeShortcut,
activeControl, activeControl,
canvasInstance, canvasInstance,
mergeObjects, mergeObjects,
@ -46,7 +47,7 @@ function MergeControl(props: Props): JSX.Element {
}; };
return ( return (
<Tooltip title='Merge shapes/tracks' placement='right'> <Tooltip title={`Merge shapes/tracks ${switchMergeShortcut}`} placement='right'>
<Icon {...dynamicIconProps} component={MergeIcon} /> <Icon {...dynamicIconProps} component={MergeIcon} />
</Tooltip> </Tooltip>
); );

@ -3,23 +3,12 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React from 'react'; import React from 'react';
import Icon from 'antd/lib/icon';
import Tooltip from 'antd/lib/tooltip';
import { import { MoveIcon } from 'icons';
Icon, import { ActiveControl } from 'reducers/interfaces';
Tooltip, import { Canvas } from 'cvat-canvas';
} from 'antd';
import {
MoveIcon,
} from 'icons';
import {
ActiveControl,
} from 'reducers/interfaces';
import {
Canvas,
} from 'cvat-canvas';
interface Props { interface Props {
canvasInstance: Canvas; canvasInstance: Canvas;
@ -27,10 +16,7 @@ interface Props {
} }
function MoveControl(props: Props): JSX.Element { function MoveControl(props: Props): JSX.Element {
const { const { canvasInstance, activeControl } = props;
canvasInstance,
activeControl,
} = props;
return ( return (
<Tooltip title='Move the image' placement='right'> <Tooltip title='Move the image' placement='right'>

@ -3,23 +3,12 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React from 'react'; import React from 'react';
import Icon from 'antd/lib/icon';
import Tooltip from 'antd/lib/tooltip';
import { import { ZoomIcon } from 'icons';
Icon, import { ActiveControl } from 'reducers/interfaces';
Tooltip, import { Canvas } from 'cvat-canvas';
} from 'antd';
import {
ZoomIcon,
} from 'icons';
import {
ActiveControl,
} from 'reducers/interfaces';
import {
Canvas,
} from 'cvat-canvas';
interface Props { interface Props {
canvasInstance: Canvas; canvasInstance: Canvas;

@ -3,27 +3,23 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React from 'react'; import React from 'react';
import Icon from 'antd/lib/icon';
import Tooltip from 'antd/lib/tooltip';
import Popover from 'antd/lib/popover';
import { import { RotateIcon } from 'icons';
Icon, import { Rotation } from 'reducers/interfaces';
Tooltip,
Popover,
} from 'antd';
import {
RotateIcon,
} from 'icons';
import {
Rotation,
} from 'reducers/interfaces';
interface Props { interface Props {
clockwiseShortcut: string;
anticlockwiseShortcut: string;
rotateFrame(rotation: Rotation): void; rotateFrame(rotation: Rotation): void;
} }
function RotateControl(props: Props): JSX.Element { function RotateControl(props: Props): JSX.Element {
const { const {
anticlockwiseShortcut,
clockwiseShortcut,
rotateFrame, rotateFrame,
} = props; } = props;
@ -33,14 +29,14 @@ function RotateControl(props: Props): JSX.Element {
placement='right' placement='right'
content={( content={(
<> <>
<Tooltip title='Rotate the image anticlockwise' placement='topRight'> <Tooltip title={`Rotate the image anticlockwise ${anticlockwiseShortcut}`} placement='topRight'>
<Icon <Icon
className='cvat-rotate-canvas-controls-left' className='cvat-rotate-canvas-controls-left'
onClick={(): void => rotateFrame(Rotation.ANTICLOCKWISE90)} onClick={(): void => rotateFrame(Rotation.ANTICLOCKWISE90)}
component={RotateIcon} component={RotateIcon}
/> />
</Tooltip> </Tooltip>
<Tooltip title='Rotate the image clockwise' placement='topRight'> <Tooltip title={`Rotate the image clockwise ${clockwiseShortcut}`} placement='topRight'>
<Icon <Icon
className='cvat-rotate-canvas-controls-right' className='cvat-rotate-canvas-controls-right'
onClick={(): void => rotateFrame(Rotation.CLOCKWISE90)} onClick={(): void => rotateFrame(Rotation.CLOCKWISE90)}

@ -3,10 +3,8 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React from 'react'; import React from 'react';
import { import Popover from 'antd/lib/popover';
Popover, import Icon from 'antd/lib/icon';
Icon,
} from 'antd';
import { Canvas } from 'cvat-canvas'; import { Canvas } from 'cvat-canvas';
import { TagIcon } from 'icons'; import { TagIcon } from 'icons';

@ -3,29 +3,27 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React from 'react'; import React from 'react';
import { Row, Col } from 'antd/lib/grid';
import { import Select from 'antd/lib/select';
Row, import Button from 'antd/lib/button';
Col, import Tooltip from 'antd/lib/tooltip';
Select,
Button,
} from 'antd';
import Text from 'antd/lib/typography/Text'; import Text from 'antd/lib/typography/Text';
interface Props { interface Props {
labels: any[]; labels: any[];
selectedLabeID: number; selectedLabeID: number;
repeatShapeShortcut: string;
onChangeLabel(value: string): void; onChangeLabel(value: string): void;
onSetup( onSetup(
labelID: number, labelID: number,
): void; ): void;
} }
function setupTagPopover(props: Props): JSX.Element { function SetupTagPopover(props: Props): JSX.Element {
const { const {
labels, labels,
selectedLabeID, selectedLabeID,
repeatShapeShortcut,
onChangeLabel, onChangeLabel,
onSetup, onSetup,
} = props; } = props;
@ -63,13 +61,15 @@ function setupTagPopover(props: Props): JSX.Element {
</Row> </Row>
<Row type='flex' justify='space-around'> <Row type='flex' justify='space-around'>
<Col span={24}> <Col span={24}>
<Button onClick={() => onSetup(selectedLabeID)}> <Tooltip title={`Press ${repeatShapeShortcut} to add a tag again`}>
Tag <Button onClick={() => onSetup(selectedLabeID)}>
</Button> Tag
</Button>
</Tooltip>
</Col> </Col>
</Row> </Row>
</div> </div>
); );
} }
export default React.memo(setupTagPopover); export default React.memo(SetupTagPopover);

@ -3,16 +3,10 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React from 'react'; import React from 'react';
import Tooltip from 'antd/lib/tooltip';
import Icon from 'antd/lib/icon';
import { import { SplitIcon } from 'icons';
Tooltip,
Icon,
} from 'antd';
import {
SplitIcon,
} from 'icons';
import { Canvas } from 'cvat-canvas'; import { Canvas } from 'cvat-canvas';
import { ActiveControl } from 'reducers/interfaces'; import { ActiveControl } from 'reducers/interfaces';

@ -3,7 +3,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React from 'react'; import React from 'react';
import { Row, Col } from 'antd/lib/grid'; import { Row, Col } from 'antd/lib/grid';
import Icon from 'antd/lib/icon'; import Icon from 'antd/lib/icon';
import Select from 'antd/lib/select'; import Select from 'antd/lib/select';
@ -18,6 +17,7 @@ import Button from 'antd/lib/button';
import Modal from 'antd/lib/modal'; import Modal from 'antd/lib/modal';
import Popover from 'antd/lib/popover'; import Popover from 'antd/lib/popover';
import Text from 'antd/lib/typography/Text'; import Text from 'antd/lib/typography/Text';
import Tooltip from 'antd/lib/tooltip';
import ColorChanger from 'components/annotation-page/standard-workspace/objects-side-bar/color-changer'; import ColorChanger from 'components/annotation-page/standard-workspace/objects-side-bar/color-changer';
@ -38,6 +38,12 @@ function ItemMenu(
serverID: number | undefined, serverID: number | undefined,
locked: boolean, locked: boolean,
objectType: ObjectType, objectType: ObjectType,
copyShortcut: string,
pasteShortcut: string,
propagateShortcut: string,
toBackgroundShortcut: string,
toForegroundShortcut: string,
removeShortcut: string,
copy: (() => void), copy: (() => void),
remove: (() => void), remove: (() => void),
propagate: (() => void), propagate: (() => void),
@ -46,58 +52,68 @@ function ItemMenu(
toForeground: (() => void), toForeground: (() => void),
): JSX.Element { ): JSX.Element {
return ( return (
<Menu key='unique' className='cvat-object-item-menu'> <Menu className='cvat-object-item-menu'>
<Menu.Item> <Menu.Item>
<Button disabled={serverID === undefined} type='link' icon='link' onClick={createURL}> <Button disabled={serverID === undefined} type='link' icon='link' onClick={createURL}>
Create object URL Create object URL
</Button> </Button>
</Menu.Item> </Menu.Item>
<Menu.Item> <Menu.Item>
<Button type='link' icon='copy' onClick={copy}> <Tooltip title={`${copyShortcut} and ${pasteShortcut}`}>
Make a copy <Button type='link' icon='copy' onClick={copy}>
</Button> Make a copy
</Button>
</Tooltip>
</Menu.Item> </Menu.Item>
<Menu.Item> <Menu.Item>
<Button type='link' icon='block' onClick={propagate}> <Tooltip title={`${propagateShortcut}`}>
Propagate <Button type='link' icon='block' onClick={propagate}>
</Button> Propagate
</Button>
</Tooltip>
</Menu.Item> </Menu.Item>
{ objectType !== ObjectType.TAG && ( { objectType !== ObjectType.TAG && (
<> <Menu.Item>
<Menu.Item> <Tooltip title={`${toBackgroundShortcut}`}>
<Button type='link' onClick={toBackground}> <Button type='link' onClick={toBackground}>
<Icon component={BackgroundIcon} /> <Icon component={BackgroundIcon} />
To background To background
</Button> </Button>
</Menu.Item> </Tooltip>
<Menu.Item> </Menu.Item>
)}
{ objectType !== ObjectType.TAG && (
<Menu.Item>
<Tooltip title={`${toForegroundShortcut}`}>
<Button type='link' onClick={toForeground}> <Button type='link' onClick={toForeground}>
<Icon component={ForegroundIcon} /> <Icon component={ForegroundIcon} />
To foreground To foreground
</Button> </Button>
</Menu.Item> </Tooltip>
</> </Menu.Item>
)} )}
<Menu.Item> <Menu.Item>
<Button <Tooltip title={`${removeShortcut}`}>
type='link' <Button
icon='delete' type='link'
onClick={(): void => { icon='delete'
if (locked) { onClick={(): void => {
Modal.confirm({ if (locked) {
title: 'Object is locked', Modal.confirm({
content: 'Are you sure you want to remove it?', title: 'Object is locked',
onOk() { content: 'Are you sure you want to remove it?',
remove(); onOk() {
}, remove();
}); },
} else { });
remove(); } else {
} remove();
}} }
> }}
Remove >
</Button> Remove
</Button>
</Tooltip>
</Menu.Item> </Menu.Item>
</Menu> </Menu>
); );
@ -111,6 +127,12 @@ interface ItemTopComponentProps {
objectType: ObjectType; objectType: ObjectType;
type: string; type: string;
locked: boolean; locked: boolean;
copyShortcut: string;
pasteShortcut: string;
propagateShortcut: string;
toBackgroundShortcut: string;
toForegroundShortcut: string;
removeShortcut: string;
changeLabel(labelID: string): void; changeLabel(labelID: string): void;
copy(): void; copy(): void;
remove(): void; remove(): void;
@ -129,6 +151,12 @@ function ItemTopComponent(props: ItemTopComponentProps): JSX.Element {
objectType, objectType,
type, type,
locked, locked,
copyShortcut,
pasteShortcut,
propagateShortcut,
toBackgroundShortcut,
toForegroundShortcut,
removeShortcut,
changeLabel, changeLabel,
copy, copy,
remove, remove,
@ -146,13 +174,15 @@ function ItemTopComponent(props: ItemTopComponentProps): JSX.Element {
<Text type='secondary' style={{ fontSize: 10 }}>{type}</Text> <Text type='secondary' style={{ fontSize: 10 }}>{type}</Text>
</Col> </Col>
<Col span={12}> <Col span={12}>
<Select size='small' value={`${labelID}`} onChange={changeLabel}> <Tooltip title='Change current label'>
{ labels.map((label: any): JSX.Element => ( <Select size='small' value={`${labelID}`} onChange={changeLabel}>
<Select.Option key={label.id} value={`${label.id}`}> { labels.map((label: any): JSX.Element => (
{label.name} <Select.Option key={label.id} value={`${label.id}`}>
</Select.Option> {label.name}
))} </Select.Option>
</Select> ))}
</Select>
</Tooltip>
</Col> </Col>
<Col span={2}> <Col span={2}>
<Dropdown <Dropdown
@ -161,6 +191,12 @@ function ItemTopComponent(props: ItemTopComponentProps): JSX.Element {
serverID, serverID,
locked, locked,
objectType, objectType,
copyShortcut,
pasteShortcut,
propagateShortcut,
toBackgroundShortcut,
toForegroundShortcut,
removeShortcut,
copy, copy,
remove, remove,
propagate, propagate,
@ -187,6 +223,13 @@ interface ItemButtonsComponentProps {
pinned: boolean; pinned: boolean;
hidden: boolean; hidden: boolean;
keyframe: boolean | undefined; keyframe: boolean | undefined;
switchOccludedShortcut: string;
switchOutsideShortcut: string;
switchLockShortcut: string;
switchHiddenShortcut: string;
switchKeyFrameShortcut: string;
nextKeyFrameShortcut: string;
prevKeyFrameShortcut: string;
navigateFirstKeyframe: null | (() => void); navigateFirstKeyframe: null | (() => void);
navigatePrevKeyframe: null | (() => void); navigatePrevKeyframe: null | (() => void);
@ -217,6 +260,13 @@ function ItemButtonsComponent(props: ItemButtonsComponentProps): JSX.Element {
pinned, pinned,
hidden, hidden,
keyframe, keyframe,
switchOccludedShortcut,
switchOutsideShortcut,
switchLockShortcut,
switchHiddenShortcut,
switchKeyFrameShortcut,
nextKeyFrameShortcut,
prevKeyFrameShortcut,
navigateFirstKeyframe, navigateFirstKeyframe,
navigatePrevKeyframe, navigatePrevKeyframe,
@ -249,12 +299,26 @@ function ItemButtonsComponent(props: ItemButtonsComponentProps): JSX.Element {
</Col> </Col>
<Col> <Col>
{ navigatePrevKeyframe { navigatePrevKeyframe
? <Icon component={PreviousIcon} onClick={navigatePrevKeyframe} /> ? (
<Tooltip title={`Go to previous keyframe ${prevKeyFrameShortcut}`}>
<Icon
component={PreviousIcon}
onClick={navigatePrevKeyframe}
/>
</Tooltip>
)
: <Icon component={PreviousIcon} style={{ opacity: 0.5, pointerEvents: 'none' }} />} : <Icon component={PreviousIcon} style={{ opacity: 0.5, pointerEvents: 'none' }} />}
</Col> </Col>
<Col> <Col>
{ navigateNextKeyframe { navigateNextKeyframe
? <Icon component={NextIcon} onClick={navigateNextKeyframe} /> ? (
<Tooltip title={`Go to next keyframe ${nextKeyFrameShortcut}`}>
<Icon
component={NextIcon}
onClick={navigateNextKeyframe}
/>
</Tooltip>
)
: <Icon component={NextIcon} style={{ opacity: 0.5, pointerEvents: 'none' }} />} : <Icon component={NextIcon} style={{ opacity: 0.5, pointerEvents: 'none' }} />}
</Col> </Col>
<Col> <Col>
@ -265,36 +329,48 @@ function ItemButtonsComponent(props: ItemButtonsComponentProps): JSX.Element {
</Row> </Row>
<Row type='flex' justify='space-around'> <Row type='flex' justify='space-around'>
<Col> <Col>
{ outside <Tooltip title={`Switch outside property ${switchOutsideShortcut}`}>
? <Icon component={ObjectOutsideIcon} onClick={unsetOutside} /> { outside
: <Icon type='select' onClick={setOutside} />} ? <Icon component={ObjectOutsideIcon} onClick={unsetOutside} />
: <Icon type='select' onClick={setOutside} />}
</Tooltip>
</Col> </Col>
<Col> <Col>
{ locked <Tooltip title={`Switch lock property ${switchLockShortcut}`}>
? <Icon type='lock' onClick={unlock} /> { locked
: <Icon type='unlock' onClick={lock} />} ? <Icon type='lock' onClick={unlock} />
: <Icon type='unlock' onClick={lock} />}
</Tooltip>
</Col> </Col>
<Col> <Col>
{ occluded <Tooltip title={`Switch occluded property ${switchOccludedShortcut}`}>
? <Icon type='team' onClick={unsetOccluded} /> { occluded
: <Icon type='user' onClick={setOccluded} />} ? <Icon type='team' onClick={unsetOccluded} />
: <Icon type='user' onClick={setOccluded} />}
</Tooltip>
</Col> </Col>
<Col> <Col>
{ hidden <Tooltip title={`Switch hidden property ${switchHiddenShortcut}`}>
? <Icon type='eye-invisible' onClick={show} /> { hidden
: <Icon type='eye' onClick={hide} />} ? <Icon type='eye-invisible' onClick={show} />
: <Icon type='eye' onClick={hide} />}
</Tooltip>
</Col> </Col>
<Col> <Col>
{ keyframe <Tooltip title={`Switch keyframe property ${switchKeyFrameShortcut}`}>
? <Icon type='star' theme='filled' onClick={unsetKeyframe} /> { keyframe
: <Icon type='star' onClick={setKeyframe} />} ? <Icon type='star' theme='filled' onClick={unsetKeyframe} />
: <Icon type='star' onClick={setKeyframe} />}
</Tooltip>
</Col> </Col>
{ {
shapeType !== ShapeType.POINTS && ( shapeType !== ShapeType.POINTS && (
<Col> <Col>
{ pinned <Tooltip title='Switch pinned property'>
? <Icon type='pushpin' theme='filled' onClick={unpin} /> { pinned
: <Icon type='pushpin' onClick={pin} />} ? <Icon type='pushpin' theme='filled' onClick={unpin} />
: <Icon type='pushpin' onClick={pin} />}
</Tooltip>
</Col> </Col>
) )
} }
@ -310,9 +386,11 @@ function ItemButtonsComponent(props: ItemButtonsComponentProps): JSX.Element {
<Col span={20} style={{ textAlign: 'center' }}> <Col span={20} style={{ textAlign: 'center' }}>
<Row type='flex' justify='space-around'> <Row type='flex' justify='space-around'>
<Col> <Col>
{ locked <Tooltip title={`Switch lock property ${switchLockShortcut}`}>
? <Icon type='lock' onClick={unlock} /> { locked
: <Icon type='unlock' onClick={lock} />} ? <Icon type='lock' onClick={unlock} />
: <Icon type='unlock' onClick={lock} />}
</Tooltip>
</Col> </Col>
</Row> </Row>
</Col> </Col>
@ -325,26 +403,34 @@ function ItemButtonsComponent(props: ItemButtonsComponentProps): JSX.Element {
<Col span={20} style={{ textAlign: 'center' }}> <Col span={20} style={{ textAlign: 'center' }}>
<Row type='flex' justify='space-around'> <Row type='flex' justify='space-around'>
<Col> <Col>
{ locked <Tooltip title={`Switch lock property ${switchLockShortcut}`}>
? <Icon type='lock' onClick={unlock} /> { locked
: <Icon type='unlock' onClick={lock} />} ? <Icon type='lock' onClick={unlock} />
: <Icon type='unlock' onClick={lock} />}
</Tooltip>
</Col> </Col>
<Col> <Col>
{ occluded <Tooltip title={`Switch occluded property ${switchOccludedShortcut}`}>
? <Icon type='team' onClick={unsetOccluded} /> { occluded
: <Icon type='user' onClick={setOccluded} />} ? <Icon type='team' onClick={unsetOccluded} />
: <Icon type='user' onClick={setOccluded} />}
</Tooltip>
</Col> </Col>
<Col> <Col>
{ hidden <Tooltip title={`Switch hidden property ${switchHiddenShortcut}`}>
? <Icon type='eye-invisible' onClick={show} /> { hidden
: <Icon type='eye' onClick={hide} />} ? <Icon type='eye-invisible' onClick={show} />
: <Icon type='eye' onClick={hide} />}
</Tooltip>
</Col> </Col>
{ {
shapeType !== ShapeType.POINTS && ( shapeType !== ShapeType.POINTS && (
<Col> <Col>
{ pinned <Tooltip title='Switch pinned property'>
? <Icon type='pushpin' theme='filled' onClick={unpin} /> { pinned
: <Icon type='pushpin' onClick={pin} />} ? <Icon type='pushpin' theme='filled' onClick={unpin} />
: <Icon type='pushpin' onClick={pin} />}
</Tooltip>
</Col> </Col>
) )
} }
@ -587,6 +673,7 @@ function ItemAttributesComponent(props: ItemAttributesComponentProps): JSX.Eleme
const ItemAttributes = React.memo(ItemAttributesComponent, attrAreTheSame); const ItemAttributes = React.memo(ItemAttributesComponent, attrAreTheSame);
interface Props { interface Props {
normalizedKeyMap: Record<string, string>;
activated: boolean; activated: boolean;
objectType: ObjectType; objectType: ObjectType;
shapeType: ShapeType; shapeType: ShapeType;
@ -653,6 +740,7 @@ function objectItemsAreEqual(prevProps: Props, nextProps: Props): boolean {
&& nextProps.collapsed === prevProps.collapsed && nextProps.collapsed === prevProps.collapsed
&& nextProps.labels === prevProps.labels && nextProps.labels === prevProps.labels
&& nextProps.attributes === prevProps.attributes && nextProps.attributes === prevProps.attributes
&& nextProps.normalizedKeyMap === prevProps.normalizedKeyMap
&& nextProps.navigateFirstKeyframe === prevProps.navigateFirstKeyframe && nextProps.navigateFirstKeyframe === prevProps.navigateFirstKeyframe
&& nextProps.navigatePrevKeyframe === prevProps.navigatePrevKeyframe && nextProps.navigatePrevKeyframe === prevProps.navigatePrevKeyframe
&& nextProps.navigateNextKeyframe === prevProps.navigateNextKeyframe && nextProps.navigateNextKeyframe === prevProps.navigateNextKeyframe
@ -681,6 +769,7 @@ function ObjectItemComponent(props: Props): JSX.Element {
attributes, attributes,
labels, labels,
collapsed, collapsed,
normalizedKeyMap,
navigateFirstKeyframe, navigateFirstKeyframe,
navigatePrevKeyframe, navigatePrevKeyframe,
navigateNextKeyframe, navigateNextKeyframe,
@ -748,6 +837,12 @@ function ObjectItemComponent(props: Props): JSX.Element {
objectType={objectType} objectType={objectType}
type={type} type={type}
locked={locked} locked={locked}
copyShortcut={normalizedKeyMap.COPY_SHAPE}
pasteShortcut={normalizedKeyMap.PASTE_SHAPE}
propagateShortcut={normalizedKeyMap.PROPAGATE_OBJECT}
toBackgroundShortcut={normalizedKeyMap.TO_BACKGROUND}
toForegroundShortcut={normalizedKeyMap.TO_FOREGROUND}
removeShortcut={normalizedKeyMap.DELETE_OBJECT}
changeLabel={changeLabel} changeLabel={changeLabel}
copy={copy} copy={copy}
remove={remove} remove={remove}
@ -765,6 +860,13 @@ function ObjectItemComponent(props: Props): JSX.Element {
pinned={pinned} pinned={pinned}
hidden={hidden} hidden={hidden}
keyframe={keyframe} keyframe={keyframe}
switchOccludedShortcut={normalizedKeyMap.SWITCH_OCCLUDED}
switchOutsideShortcut={normalizedKeyMap.SWITCH_OUTSIDE}
switchLockShortcut={normalizedKeyMap.SWITCH_LOCK}
switchHiddenShortcut={normalizedKeyMap.SWITCH_HIDDEN}
switchKeyFrameShortcut={normalizedKeyMap.SWITCH_KEYFRAME}
nextKeyFrameShortcut={normalizedKeyMap.NEXT_KEY_FRAME}
prevKeyFrameShortcut={normalizedKeyMap.PREV_KEY_FRAME}
navigateFirstKeyframe={navigateFirstKeyframe} navigateFirstKeyframe={navigateFirstKeyframe}
navigatePrevKeyframe={navigatePrevKeyframe} navigatePrevKeyframe={navigatePrevKeyframe}
navigateNextKeyframe={navigateNextKeyframe} navigateNextKeyframe={navigateNextKeyframe}

@ -10,6 +10,7 @@ import Text from 'antd/lib/typography/Text';
import AnnotationsFiltersInput from 'components/annotation-page/annotations-filters-input'; import AnnotationsFiltersInput from 'components/annotation-page/annotations-filters-input';
import { StatesOrdering } from 'reducers/interfaces'; import { StatesOrdering } from 'reducers/interfaces';
import { Tooltip } from 'antd';
interface StatesOrderingSelectorComponentProps { interface StatesOrderingSelectorComponentProps {
statesOrdering: StatesOrdering; statesOrdering: StatesOrdering;
@ -56,6 +57,8 @@ interface Props {
statesLocked: boolean; statesLocked: boolean;
statesCollapsed: boolean; statesCollapsed: boolean;
statesOrdering: StatesOrdering; statesOrdering: StatesOrdering;
switchLockAllShortcut: string;
switchHiddenAllShortcut: string;
changeStatesOrdering(value: StatesOrdering): void; changeStatesOrdering(value: StatesOrdering): void;
lockAllStates(): void; lockAllStates(): void;
unlockAllStates(): void; unlockAllStates(): void;
@ -71,6 +74,8 @@ function ObjectListHeader(props: Props): JSX.Element {
statesLocked, statesLocked,
statesCollapsed, statesCollapsed,
statesOrdering, statesOrdering,
switchLockAllShortcut,
switchHiddenAllShortcut,
changeStatesOrdering, changeStatesOrdering,
lockAllStates, lockAllStates,
unlockAllStates, unlockAllStates,
@ -89,19 +94,25 @@ function ObjectListHeader(props: Props): JSX.Element {
</Row> </Row>
<Row type='flex' justify='space-between' align='middle'> <Row type='flex' justify='space-between' align='middle'>
<Col span={2}> <Col span={2}>
{ statesLocked <Tooltip title={`Switch lock property for all ${switchLockAllShortcut}`}>
? <Icon type='lock' onClick={unlockAllStates} /> { statesLocked
: <Icon type='unlock' onClick={lockAllStates} />} ? <Icon type='lock' onClick={unlockAllStates} />
: <Icon type='unlock' onClick={lockAllStates} />}
</Tooltip>
</Col> </Col>
<Col span={2}> <Col span={2}>
{ statesHidden <Tooltip title={`Switch hidden property for all ${switchHiddenAllShortcut}`}>
? <Icon type='eye-invisible' onClick={showAllStates} /> { statesHidden
: <Icon type='eye' onClick={hideAllStates} />} ? <Icon type='eye-invisible' onClick={showAllStates} />
: <Icon type='eye' onClick={hideAllStates} />}
</Tooltip>
</Col> </Col>
<Col span={2}> <Col span={2}>
{ statesCollapsed <Tooltip title='Expand/collapse all'>
? <Icon type='caret-down' onClick={expandAllStates} /> { statesCollapsed
: <Icon type='caret-up' onClick={collapseAllStates} />} ? <Icon type='caret-down' onClick={expandAllStates} />
: <Icon type='caret-up' onClick={collapseAllStates} />}
</Tooltip>
</Col> </Col>
<StatesOrderingSelector <StatesOrderingSelector
statesOrdering={statesOrdering} statesOrdering={statesOrdering}

@ -8,7 +8,6 @@ import { StatesOrdering } from 'reducers/interfaces';
import ObjectItemContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/object-item'; import ObjectItemContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/object-item';
import ObjectListHeader from './objects-list-header'; import ObjectListHeader from './objects-list-header';
interface Props { interface Props {
listHeight: number; listHeight: number;
statesHidden: boolean; statesHidden: boolean;
@ -16,6 +15,8 @@ interface Props {
statesCollapsed: boolean; statesCollapsed: boolean;
statesOrdering: StatesOrdering; statesOrdering: StatesOrdering;
sortedStatesID: number[]; sortedStatesID: number[];
switchLockAllShortcut: string;
switchHiddenAllShortcut: string;
changeStatesOrdering(value: StatesOrdering): void; changeStatesOrdering(value: StatesOrdering): void;
lockAllStates(): void; lockAllStates(): void;
unlockAllStates(): void; unlockAllStates(): void;
@ -33,6 +34,8 @@ function ObjectListComponent(props: Props): JSX.Element {
statesCollapsed, statesCollapsed,
statesOrdering, statesOrdering,
sortedStatesID, sortedStatesID,
switchLockAllShortcut,
switchHiddenAllShortcut,
changeStatesOrdering, changeStatesOrdering,
lockAllStates, lockAllStates,
unlockAllStates, unlockAllStates,
@ -49,6 +52,8 @@ function ObjectListComponent(props: Props): JSX.Element {
statesLocked={statesLocked} statesLocked={statesLocked}
statesCollapsed={statesCollapsed} statesCollapsed={statesCollapsed}
statesOrdering={statesOrdering} statesOrdering={statesOrdering}
switchLockAllShortcut={switchLockAllShortcut}
switchHiddenAllShortcut={switchHiddenAllShortcut}
changeStatesOrdering={changeStatesOrdering} changeStatesOrdering={changeStatesOrdering}
lockAllStates={lockAllStates} lockAllStates={lockAllStates}
unlockAllStates={unlockAllStates} unlockAllStates={unlockAllStates}

@ -102,13 +102,19 @@
> div:nth-child(3) > div > div { > div:nth-child(3) > div > div {
width: 100%; width: 100%;
} }
div:last-child > div > button { > div:last-child {
width: 100%; span {
&:nth-child(1) { width: 100%;
border-radius: 3px 0px 0px 3px;
} }
&:nth-child(2) {
border-radius: 0px 3px 3px 0px; button {
width: 100%;
&:nth-child(1) {
border-radius: 3px 0px 0px 3px;
}
&:nth-child(2) {
border-radius: 0px 3px 3px 0px;
}
} }
} }
} }

@ -3,12 +3,8 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React from 'react'; import React from 'react';
import Menu, { ClickParam } from 'antd/lib/menu';
import { import Modal from 'antd/lib/modal';
Menu, Modal,
} from 'antd';
import { ClickParam } from 'antd/lib/menu/index';
import DumpSubmenu from 'components/actions-menu/dump-submenu'; import DumpSubmenu from 'components/actions-menu/dump-submenu';
import LoadSubmenu from 'components/actions-menu/load-submenu'; import LoadSubmenu from 'components/actions-menu/load-submenu';

@ -3,30 +3,29 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React from 'react'; import React from 'react';
import { Col } from 'antd/lib/grid';
import { import Icon from 'antd/lib/icon';
Col, import Modal from 'antd/lib/modal';
Icon, import Button from 'antd/lib/button';
Modal, import Timeline from 'antd/lib/timeline';
Button, import Dropdown from 'antd/lib/dropdown';
Timeline,
Dropdown,
} from 'antd';
import AnnotationMenuContainer from 'containers/annotation-page/top-bar/annotation-menu'; import AnnotationMenuContainer from 'containers/annotation-page/top-bar/annotation-menu';
import { import {
MainMenuIcon, MainMenuIcon,
SaveIcon, SaveIcon,
UndoIcon, UndoIcon,
RedoIcon, RedoIcon,
} from '../../../icons'; } from 'icons';
interface Props { interface Props {
saving: boolean; saving: boolean;
savingStatuses: string[]; savingStatuses: string[];
undoAction?: string; undoAction?: string;
redoAction?: string; redoAction?: string;
saveShortcut: string;
undoShortcut: string;
redoShortcut: string;
onSaveAnnotation(): void; onSaveAnnotation(): void;
onUndoClick(): void; onUndoClick(): void;
onRedoClick(): void; onRedoClick(): void;
@ -38,6 +37,9 @@ function LeftGroup(props: Props): JSX.Element {
savingStatuses, savingStatuses,
undoAction, undoAction,
redoAction, redoAction,
saveShortcut,
undoShortcut,
redoShortcut,
onSaveAnnotation, onSaveAnnotation,
onUndoClick, onUndoClick,
onRedoClick, onRedoClick,
@ -52,6 +54,7 @@ function LeftGroup(props: Props): JSX.Element {
</Button> </Button>
</Dropdown> </Dropdown>
<Button <Button
title={`Save current changes ${saveShortcut}`}
onClick={saving ? undefined : onSaveAnnotation} onClick={saving ? undefined : onSaveAnnotation}
type='link' type='link'
className={saving className={saving
@ -79,7 +82,7 @@ function LeftGroup(props: Props): JSX.Element {
</Modal> </Modal>
</Button> </Button>
<Button <Button
title={undoAction} title={`Undo: ${undoAction} ${undoShortcut}`}
disabled={!undoAction} disabled={!undoAction}
style={{ pointerEvents: undoAction ? 'initial' : 'none', opacity: undoAction ? 1 : 0.5 }} style={{ pointerEvents: undoAction ? 'initial' : 'none', opacity: undoAction ? 1 : 0.5 }}
type='link' type='link'
@ -90,7 +93,7 @@ function LeftGroup(props: Props): JSX.Element {
<span>Undo</span> <span>Undo</span>
</Button> </Button>
<Button <Button
title={redoAction} title={`Redo: ${redoAction} ${redoShortcut}`}
disabled={!redoAction} disabled={!redoAction}
style={{ pointerEvents: redoAction ? 'initial' : 'none', opacity: redoAction ? 1 : 0.5 }} style={{ pointerEvents: redoAction ? 'initial' : 'none', opacity: redoAction ? 1 : 0.5 }}
type='link' type='link'

@ -4,11 +4,9 @@
import React from 'react'; import React from 'react';
import { import { Col } from 'antd/lib/grid';
Col, import Icon from 'antd/lib/icon';
Icon, import Tooltip from 'antd/lib/tooltip';
Tooltip,
} from 'antd';
import { import {
FirstIcon, FirstIcon,
@ -19,10 +17,15 @@ import {
NextIcon, NextIcon,
ForwardJumpIcon, ForwardJumpIcon,
LastIcon, LastIcon,
} from '../../../icons'; } from 'icons';
interface Props { interface Props {
playing: boolean; playing: boolean;
playPauseShortcut: string;
nextFrameShortcut: string;
previousFrameShortcut: string;
forwardShortcut: string;
backwardShortcut: string;
onSwitchPlay(): void; onSwitchPlay(): void;
onPrevFrame(): void; onPrevFrame(): void;
onNextFrame(): void; onNextFrame(): void;
@ -35,6 +38,11 @@ interface Props {
function PlayerButtons(props: Props): JSX.Element { function PlayerButtons(props: Props): JSX.Element {
const { const {
playing, playing,
playPauseShortcut,
nextFrameShortcut,
previousFrameShortcut,
forwardShortcut,
backwardShortcut,
onSwitchPlay, onSwitchPlay,
onPrevFrame, onPrevFrame,
onNextFrame, onNextFrame,
@ -49,16 +57,16 @@ function PlayerButtons(props: Props): JSX.Element {
<Tooltip title='Go to the first frame'> <Tooltip title='Go to the first frame'>
<Icon component={FirstIcon} onClick={onFirstFrame} /> <Icon component={FirstIcon} onClick={onFirstFrame} />
</Tooltip> </Tooltip>
<Tooltip title='Go back with a step'> <Tooltip title={`Go back with a step ${backwardShortcut}`}>
<Icon component={BackJumpIcon} onClick={onBackward} /> <Icon component={BackJumpIcon} onClick={onBackward} />
</Tooltip> </Tooltip>
<Tooltip title='Go back'> <Tooltip title={`Go back ${previousFrameShortcut}`}>
<Icon component={PreviousIcon} onClick={onPrevFrame} /> <Icon component={PreviousIcon} onClick={onPrevFrame} />
</Tooltip> </Tooltip>
{!playing {!playing
? ( ? (
<Tooltip title='Play'> <Tooltip title={`Play ${playPauseShortcut}`}>
<Icon <Icon
component={PlayIcon} component={PlayIcon}
onClick={onSwitchPlay} onClick={onSwitchPlay}
@ -66,7 +74,7 @@ function PlayerButtons(props: Props): JSX.Element {
</Tooltip> </Tooltip>
) )
: ( : (
<Tooltip title='Pause'> <Tooltip title={`Pause ${playPauseShortcut}`}>
<Icon <Icon
component={PauseIcon} component={PauseIcon}
onClick={onSwitchPlay} onClick={onSwitchPlay}
@ -74,10 +82,10 @@ function PlayerButtons(props: Props): JSX.Element {
</Tooltip> </Tooltip>
)} )}
<Tooltip title='Go next'> <Tooltip title={`Go next ${nextFrameShortcut}`}>
<Icon component={NextIcon} onClick={onNextFrame} /> <Icon component={NextIcon} onClick={onNextFrame} />
</Tooltip> </Tooltip>
<Tooltip title='Go next with a step'> <Tooltip title={`Go next with a step ${forwardShortcut}`}>
<Icon component={ForwardJumpIcon} onClick={onForward} /> <Icon component={ForwardJumpIcon} onClick={onForward} />
</Tooltip> </Tooltip>
<Tooltip title='Go to the last frame'> <Tooltip title='Go to the last frame'>

@ -18,6 +18,7 @@ interface Props {
stopFrame: number; stopFrame: number;
frameNumber: number; frameNumber: number;
frameFilename: string; frameFilename: string;
focusFrameInputShortcut: string;
inputFrameRef: React.RefObject<InputNumber>; inputFrameRef: React.RefObject<InputNumber>;
onSliderChange(value: SliderValue): void; onSliderChange(value: SliderValue): void;
onInputChange(value: number): void; onInputChange(value: number): void;
@ -30,6 +31,7 @@ function PlayerNavigation(props: Props): JSX.Element {
stopFrame, stopFrame,
frameNumber, frameNumber,
frameFilename, frameFilename,
focusFrameInputShortcut,
inputFrameRef, inputFrameRef,
onSliderChange, onSliderChange,
onInputChange, onInputChange,
@ -72,26 +74,27 @@ function PlayerNavigation(props: Props): JSX.Element {
</Row> </Row>
</Col> </Col>
<Col> <Col>
<InputNumber <Tooltip title={`Press ${focusFrameInputShortcut} to focus here`}>
className='cvat-player-frame-selector' <InputNumber
type='number' className='cvat-player-frame-selector'
value={frameInputValue} type='number'
// https://stackoverflow.com/questions/38256332/in-react-whats-the-difference-between-onchange-and-oninput value={frameInputValue}
onChange={(value: number | undefined) => { onChange={(value: number | undefined) => {
if (typeof (value) === 'number') { if (typeof (value) === 'number') {
setFrameInputValue(Math.floor( setFrameInputValue(Math.floor(
clamp(value, startFrame, stopFrame), clamp(value, startFrame, stopFrame),
)); ));
} }
}} }}
onBlur={() => { onBlur={() => {
onInputChange(frameInputValue); onInputChange(frameInputValue);
}} }}
onPressEnter={() => { onPressEnter={() => {
onInputChange(frameInputValue); onInputChange(frameInputValue);
}} }}
ref={inputFrameRef} ref={inputFrameRef}
/> />
</Tooltip>
</Col> </Col>
</> </>
); );

@ -3,16 +3,13 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React from 'react'; import React from 'react';
import { Col } from 'antd/lib/grid';
import { import Icon from 'antd/lib/icon';
Col, import Select from 'antd/lib/select';
Icon, import Button from 'antd/lib/button';
Select,
Button,
} from 'antd';
import { Workspace } from 'reducers/interfaces'; import { Workspace } from 'reducers/interfaces';
import { InfoIcon, FullscreenIcon } from '../../../icons'; import { InfoIcon, FullscreenIcon } from 'icons';
interface Props { interface Props {
workspace: Workspace; workspace: Workspace;

@ -3,18 +3,13 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React from 'react'; import React from 'react';
import { Row, Col } from 'antd/lib/grid';
import { import Tooltip from 'antd/lib/tooltip';
Tooltip, import Select from 'antd/lib/select';
Select, import Table from 'antd/lib/table';
Table, import Modal from 'antd/lib/modal';
Modal, import Spin from 'antd/lib/spin';
Spin, import Icon from 'antd/lib/icon';
Icon,
Row,
Col,
} from 'antd';
import Text from 'antd/lib/typography/Text'; import Text from 'antd/lib/typography/Text';
interface Props { interface Props {

@ -26,6 +26,15 @@ interface Props {
undoAction?: string; undoAction?: string;
redoAction?: string; redoAction?: string;
workspace: Workspace; workspace: Workspace;
saveShortcut: string;
undoShortcut: string;
redoShortcut: string;
playPauseShortcut: string;
nextFrameShortcut: string;
previousFrameShortcut: string;
forwardShortcut: string;
backwardShortcut: string;
focusFrameInputShortcut: string;
changeWorkspace(workspace: Workspace): void; changeWorkspace(workspace: Workspace): void;
showStatistics(): void; showStatistics(): void;
onSwitchPlay(): void; onSwitchPlay(): void;
@ -56,6 +65,15 @@ export default function AnnotationTopBarComponent(props: Props): JSX.Element {
startFrame, startFrame,
stopFrame, stopFrame,
workspace, workspace,
saveShortcut,
undoShortcut,
redoShortcut,
playPauseShortcut,
nextFrameShortcut,
previousFrameShortcut,
forwardShortcut,
backwardShortcut,
focusFrameInputShortcut,
showStatistics, showStatistics,
changeWorkspace, changeWorkspace,
onSwitchPlay, onSwitchPlay,
@ -78,9 +96,12 @@ export default function AnnotationTopBarComponent(props: Props): JSX.Element {
<LeftGroup <LeftGroup
saving={saving} saving={saving}
savingStatuses={savingStatuses} savingStatuses={savingStatuses}
onSaveAnnotation={onSaveAnnotation}
undoAction={undoAction} undoAction={undoAction}
redoAction={redoAction} redoAction={redoAction}
saveShortcut={saveShortcut}
undoShortcut={undoShortcut}
redoShortcut={redoShortcut}
onSaveAnnotation={onSaveAnnotation}
onUndoClick={onUndoClick} onUndoClick={onUndoClick}
onRedoClick={onRedoClick} onRedoClick={onRedoClick}
/> />
@ -88,6 +109,11 @@ export default function AnnotationTopBarComponent(props: Props): JSX.Element {
<Row type='flex' align='middle'> <Row type='flex' align='middle'>
<PlayerButtons <PlayerButtons
playing={playing} playing={playing}
playPauseShortcut={playPauseShortcut}
nextFrameShortcut={nextFrameShortcut}
previousFrameShortcut={previousFrameShortcut}
forwardShortcut={forwardShortcut}
backwardShortcut={backwardShortcut}
onPrevFrame={onPrevFrame} onPrevFrame={onPrevFrame}
onNextFrame={onNextFrame} onNextFrame={onNextFrame}
onForward={onForward} onForward={onForward}
@ -101,6 +127,7 @@ export default function AnnotationTopBarComponent(props: Props): JSX.Element {
stopFrame={stopFrame} stopFrame={stopFrame}
frameNumber={frameNumber} frameNumber={frameNumber}
frameFilename={frameFilename} frameFilename={frameFilename}
focusFrameInputShortcut={focusFrameInputShortcut}
inputFrameRef={inputFrameRef} inputFrameRef={inputFrameRef}
onSliderChange={onSliderChange} onSliderChange={onSliderChange}
onInputChange={onInputChange} onInputChange={onInputChange}

@ -7,7 +7,7 @@ import '../styles.scss';
import React from 'react'; import React from 'react';
import { Switch, Route, Redirect } from 'react-router'; import { Switch, Route, Redirect } from 'react-router';
import { withRouter, RouteComponentProps } from 'react-router-dom'; import { withRouter, RouteComponentProps } from 'react-router-dom';
import { GlobalHotKeys, KeyMap, configure } from 'react-hotkeys'; import { GlobalHotKeys, ExtendedKeyMapOptions, configure } from 'react-hotkeys';
import Spin from 'antd/lib/spin'; import Spin from 'antd/lib/spin';
import Layout from 'antd/lib/layout'; import Layout from 'antd/lib/layout';
import notification from 'antd/lib/notification'; import notification from 'antd/lib/notification';
@ -37,6 +37,7 @@ interface CVATAppProps {
resetErrors: () => void; resetErrors: () => void;
resetMessages: () => void; resetMessages: () => void;
switchShortcutsDialog: () => void; switchShortcutsDialog: () => void;
keyMap: Record<string, ExtendedKeyMapOptions>;
userInitialized: boolean; userInitialized: boolean;
userFetching: boolean; userFetching: boolean;
pluginsInitialized: boolean; pluginsInitialized: boolean;
@ -209,6 +210,7 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
switchShortcutsDialog, switchShortcutsDialog,
user, user,
history, history,
keyMap,
} = this.props; } = this.props;
const readyForRender = (userInitialized && user == null) const readyForRender = (userInitialized && user == null)
@ -218,19 +220,9 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
const withModels = installedAutoAnnotation const withModels = installedAutoAnnotation
|| installedTFAnnotation || installedTFSegmentation; || installedTFAnnotation || installedTFSegmentation;
const keyMap = { const subKeyMap = {
SWITCH_SHORTCUTS: { SWITCH_SHORTCUTS: keyMap.SWITCH_SHORTCUTS,
name: 'Show shortcuts', OPEN_SETTINGS: keyMap.OPEN_SETTINGS,
description: 'Open/hide the list of available shortcuts',
sequence: 'f1',
action: 'keydown',
},
OPEN_SETTINGS: {
name: 'Open settings',
description: 'Go to the settings page or go back',
sequence: 'f2',
action: 'keydown',
},
}; };
const handlers = { const handlers = {
@ -262,7 +254,7 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
<HeaderContainer> </HeaderContainer> <HeaderContainer> </HeaderContainer>
<Layout.Content> <Layout.Content>
<ShorcutsDialog /> <ShorcutsDialog />
<GlobalHotKeys keyMap={keyMap as KeyMap} handlers={handlers}> <GlobalHotKeys keyMap={subKeyMap} handlers={handlers}>
<Switch> <Switch>
<Route exact path='/settings' component={SettingsPageContainer} /> <Route exact path='/settings' component={SettingsPageContainer} />
<Route exact path='/tasks' component={TasksPageContainer} /> <Route exact path='/tasks' component={TasksPageContainer} />

@ -4,26 +4,18 @@
import './styles.scss'; import './styles.scss';
import React from 'react'; import React from 'react';
import { RouteComponentProps } from 'react-router'; import { RouteComponentProps } from 'react-router';
import { withRouter } from 'react-router-dom'; import { withRouter } from 'react-router-dom';
import { import { Row, Col } from 'antd/lib/grid';
Layout, import Layout from 'antd/lib/layout';
Icon, import Icon from 'antd/lib/icon';
Button, import Button from 'antd/lib/button';
Menu, import Menu from 'antd/lib/menu';
Dropdown, import Dropdown from 'antd/lib/dropdown';
Modal, import Modal from 'antd/lib/modal';
Row,
Col,
} from 'antd';
import Text from 'antd/lib/typography/Text'; import Text from 'antd/lib/typography/Text';
import { import { CVATLogo, AccountIcon } from 'icons';
CVATLogo,
AccountIcon,
} from 'icons';
interface HeaderContainerProps { interface HeaderContainerProps {
onLogout: () => void; onLogout: () => void;
@ -40,6 +32,7 @@ interface HeaderContainerProps {
coreVersion: string; coreVersion: string;
canvasVersion: string; canvasVersion: string;
uiVersion: string; uiVersion: string;
switchSettingsShortcut: string;
} }
type Props = HeaderContainerProps & RouteComponentProps; type Props = HeaderContainerProps & RouteComponentProps;
@ -60,6 +53,7 @@ function HeaderContainer(props: Props): JSX.Element {
uiVersion, uiVersion,
onLogout, onLogout,
logoutFetching, logoutFetching,
switchSettingsShortcut,
} = props; } = props;
const renderModels = installedAutoAnnotation const renderModels = installedAutoAnnotation
@ -131,6 +125,7 @@ function HeaderContainer(props: Props): JSX.Element {
const menu = ( const menu = (
<Menu className='cvat-header-menu' mode='vertical'> <Menu className='cvat-header-menu' mode='vertical'>
<Menu.Item <Menu.Item
title={`Press ${switchSettingsShortcut} to switch`}
onClick={ onClick={
(): void => props.history.push('/settings') (): void => props.history.push('/settings')
} }

@ -2,6 +2,7 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { ExtendedKeyMapOptions } from 'react-hotkeys';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import CanvasWrapperComponent from 'components/annotation-page/standard-workspace/canvas-wrapper'; import CanvasWrapperComponent from 'components/annotation-page/standard-workspace/canvas-wrapper';
@ -78,6 +79,7 @@ interface StateToProps {
curZLayer: number; curZLayer: number;
contextVisible: boolean; contextVisible: boolean;
contextType: ContextMenuType; contextType: ContextMenuType;
keyMap: Record<string, ExtendedKeyMapOptions>;
} }
interface DispatchToProps { interface DispatchToProps {
@ -169,6 +171,9 @@ function mapStateToProps(state: CombinedState): StateToProps {
blackBorders, blackBorders,
}, },
}, },
shortcuts: {
keyMap,
},
} = state; } = state;
return { return {
@ -204,6 +209,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
contextVisible, contextVisible,
contextType, contextType,
workspace, workspace,
keyMap,
}; };
} }

@ -2,10 +2,10 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { ExtendedKeyMapOptions } from 'react-hotkeys';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Canvas } from 'cvat-canvas'; import { Canvas } from 'cvat-canvas';
import { import {
mergeObjects, mergeObjects,
groupObjects, groupObjects,
@ -16,16 +16,14 @@ import {
resetAnnotationsGroup, resetAnnotationsGroup,
} from 'actions/annotation-actions'; } from 'actions/annotation-actions';
import ControlsSideBarComponent from 'components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar'; import ControlsSideBarComponent from 'components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar';
import { import { ActiveControl, CombinedState, Rotation } from 'reducers/interfaces';
ActiveControl,
CombinedState,
Rotation,
} from 'reducers/interfaces';
interface StateToProps { interface StateToProps {
canvasInstance: Canvas; canvasInstance: Canvas;
rotateAll: boolean; rotateAll: boolean;
activeControl: ActiveControl; activeControl: ActiveControl;
keyMap: Record<string, ExtendedKeyMapOptions>;
normalizedKeyMap: Record<string, string>;
} }
interface DispatchToProps { interface DispatchToProps {
@ -51,12 +49,18 @@ function mapStateToProps(state: CombinedState): StateToProps {
rotateAll, rotateAll,
}, },
}, },
shortcuts: {
keyMap,
normalizedKeyMap,
},
} = state; } = state;
return { return {
rotateAll, rotateAll,
canvasInstance, canvasInstance,
activeControl, activeControl,
normalizedKeyMap,
keyMap,
}; };
} }

@ -6,15 +6,8 @@ import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { RadioChangeEvent } from 'antd/lib/radio'; import { RadioChangeEvent } from 'antd/lib/radio';
import { import { CombinedState, ShapeType, ObjectType } from 'reducers/interfaces';
CombinedState, import { rememberObject } from 'actions/annotation-actions';
ShapeType,
ObjectType,
} from 'reducers/interfaces';
import {
rememberObject,
} from 'actions/annotation-actions';
import { Canvas, RectDrawingMethod } from 'cvat-canvas'; import { Canvas, RectDrawingMethod } from 'cvat-canvas';
import DrawShapePopoverComponent from 'components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover'; import DrawShapePopoverComponent from 'components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover';
@ -33,6 +26,7 @@ interface DispatchToProps {
} }
interface StateToProps { interface StateToProps {
normalizedKeyMap: Record<string, string>;
canvasInstance: Canvas; canvasInstance: Canvas;
shapeType: ShapeType; shapeType: ShapeType;
labels: any[]; labels: any[];
@ -62,12 +56,16 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps {
labels, labels,
}, },
}, },
shortcuts: {
normalizedKeyMap,
},
} = state; } = state;
return { return {
...own, ...own,
canvasInstance, canvasInstance,
labels, labels,
normalizedKeyMap,
}; };
} }
@ -164,6 +162,7 @@ class DrawShapePopoverContainer extends React.PureComponent<Props, State> {
} = this.state; } = this.state;
const { const {
normalizedKeyMap,
labels, labels,
shapeType, shapeType,
} = this.props; } = this.props;
@ -176,6 +175,7 @@ class DrawShapePopoverContainer extends React.PureComponent<Props, State> {
selectedLabeID={selectedLabelID} selectedLabeID={selectedLabelID}
numberOfPoints={numberOfPoints} numberOfPoints={numberOfPoints}
rectDrawingMethod={rectDrawingMethod} rectDrawingMethod={rectDrawingMethod}
repeatShapeShortcut={normalizedKeyMap.SWITCH_DRAW_MODE}
onChangeLabel={this.onChangeLabel} onChangeLabel={this.onChangeLabel}
onChangePoints={this.onChangePoints} onChangePoints={this.onChangePoints}
onChangeRectDrawingMethod={this.onChangeRectDrawingMethod} onChangeRectDrawingMethod={this.onChangeRectDrawingMethod}

@ -5,15 +5,8 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import { CombinedState, ObjectType } from 'reducers/interfaces';
CombinedState, import { createAnnotationsAsync, rememberObject } from 'actions/annotation-actions';
ObjectType,
} from 'reducers/interfaces';
import {
createAnnotationsAsync,
rememberObject,
} from 'actions/annotation-actions';
import SetupTagPopoverComponent from 'components/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover'; import SetupTagPopoverComponent from 'components/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover';
import { Canvas } from 'cvat-canvas'; import { Canvas } from 'cvat-canvas';
@ -26,6 +19,7 @@ interface DispatchToProps {
} }
interface StateToProps { interface StateToProps {
normalizedKeyMap: Record<string, string>;
canvasInstance: Canvas; canvasInstance: Canvas;
jobInstance: any; jobInstance: any;
labels: any[]; labels: any[];
@ -59,6 +53,9 @@ function mapStateToProps(state: CombinedState): StateToProps {
}, },
}, },
}, },
shortcuts: {
normalizedKeyMap,
},
} = state; } = state;
return { return {
@ -66,6 +63,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
jobInstance, jobInstance,
labels, labels,
frame, frame,
normalizedKeyMap,
}; };
} }
@ -91,7 +89,7 @@ class DrawShapePopoverContainer extends React.PureComponent<Props, State> {
}); });
}; };
private onSetup(): void { private onSetup = (): void => {
const { const {
frame, frame,
labels, labels,
@ -114,23 +112,17 @@ class DrawShapePopoverContainer extends React.PureComponent<Props, State> {
}); });
onAnnotationCreate(jobInstance, frame, [objectState]); onAnnotationCreate(jobInstance, frame, [objectState]);
} };
public render(): JSX.Element { public render(): JSX.Element {
const { const { selectedLabelID } = this.state;
selectedLabelID, const { normalizedKeyMap, labels } = this.props;
} = this.state;
const {
labels,
} = this.props;
this.onSetup = this.onSetup.bind(this);
return ( return (
<SetupTagPopoverComponent <SetupTagPopoverComponent
labels={labels} labels={labels}
selectedLabeID={selectedLabelID} selectedLabeID={selectedLabelID}
repeatShapeShortcut={normalizedKeyMap.SWITCH_DRAW_MODE}
onChangeLabel={this.onChangeLabel} onChangeLabel={this.onChangeLabel}
onSetup={this.onSetup} onSetup={this.onSetup}
/> />

@ -7,11 +7,7 @@ import copy from 'copy-to-clipboard';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { LogType } from 'cvat-logger'; import { LogType } from 'cvat-logger';
import { import { ActiveControl, CombinedState, ColorBy } from 'reducers/interfaces';
ActiveControl,
CombinedState,
ColorBy,
} from 'reducers/interfaces';
import { import {
collapseObjectItems, collapseObjectItems,
changeLabelColorAsync, changeLabelColorAsync,
@ -47,6 +43,7 @@ interface StateToProps {
activeControl: ActiveControl; activeControl: ActiveControl;
minZLayer: number; minZLayer: number;
maxZLayer: number; maxZLayer: number;
normalizedKeyMap: Record<string, string>;
} }
interface DispatchToProps { interface DispatchToProps {
@ -95,6 +92,9 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps {
colorBy, colorBy,
}, },
}, },
shortcuts: {
normalizedKeyMap,
},
} = state; } = state;
const index = states const index = states
@ -118,6 +118,7 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps {
activated: activatedStateID === own.clientID, activated: activatedStateID === own.clientID,
minZLayer, minZLayer,
maxZLayer, maxZLayer,
normalizedKeyMap,
}; };
} }
@ -440,6 +441,7 @@ class ObjectItemContainer extends React.PureComponent<Props> {
activated, activated,
colorBy, colorBy,
colors, colors,
normalizedKeyMap,
} = this.props; } = this.props;
const { const {
@ -481,6 +483,7 @@ class ObjectItemContainer extends React.PureComponent<Props> {
color={stateColor} color={stateColor}
colors={colors} colors={colors}
attributes={attributes} attributes={attributes}
normalizedKeyMap={normalizedKeyMap}
labels={labels} labels={labels}
collapsed={collapsed} collapsed={collapsed}
navigateFirstKeyframe={ navigateFirstKeyframe={

@ -4,7 +4,7 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { GlobalHotKeys, KeyMap } from 'react-hotkeys'; import { GlobalHotKeys, ExtendedKeyMapOptions } from 'react-hotkeys';
import ObjectsListComponent from 'components/annotation-page/standard-workspace/objects-side-bar/objects-list'; import ObjectsListComponent from 'components/annotation-page/standard-workspace/objects-side-bar/objects-list';
import { import {
@ -15,12 +15,7 @@ import {
copyShape as copyShapeAction, copyShape as copyShapeAction,
propagateObject as propagateObjectAction, propagateObject as propagateObjectAction,
} from 'actions/annotation-actions'; } from 'actions/annotation-actions';
import { CombinedState, StatesOrdering, ObjectType } from 'reducers/interfaces';
import {
CombinedState,
StatesOrdering,
ObjectType,
} from 'reducers/interfaces';
interface StateToProps { interface StateToProps {
jobInstance: any; jobInstance: any;
@ -35,6 +30,8 @@ interface StateToProps {
minZLayer: number; minZLayer: number;
maxZLayer: number; maxZLayer: number;
annotationsFiltersHistory: string[]; annotationsFiltersHistory: string[];
keyMap: Record<string, ExtendedKeyMapOptions>;
normalizedKeyMap: Record<string, string>;
} }
interface DispatchToProps { interface DispatchToProps {
@ -70,6 +67,10 @@ function mapStateToProps(state: CombinedState): StateToProps {
}, },
tabContentHeight: listHeight, tabContentHeight: listHeight,
}, },
shortcuts: {
keyMap,
normalizedKeyMap,
},
} = state; } = state;
let statesHidden = true; let statesHidden = true;
@ -101,6 +102,8 @@ function mapStateToProps(state: CombinedState): StateToProps {
minZLayer, minZLayer,
maxZLayer, maxZLayer,
annotationsFiltersHistory, annotationsFiltersHistory,
keyMap,
normalizedKeyMap,
}; };
} }
@ -249,97 +252,29 @@ class ObjectsListContainer extends React.PureComponent<Props, State> {
changeFrame, changeFrame,
maxZLayer, maxZLayer,
minZLayer, minZLayer,
keyMap,
normalizedKeyMap,
} = this.props; } = this.props;
const { const {
sortedStatesID, sortedStatesID,
statesOrdering, statesOrdering,
} = this.state; } = this.state;
const keyMap = { const subKeyMap = {
SWITCH_ALL_LOCK: { SWITCH_ALL_LOCK: keyMap.SWITCH_ALL_LOCK,
name: 'Lock/unlock all objects', SWITCH_LOCK: keyMap.SWITCH_LOCK,
description: 'Change locked state for all objects in the side bar', SWITCH_ALL_HIDDEN: keyMap.SWITCH_ALL_HIDDEN,
sequence: 't+l', SWITCH_HIDDEN: keyMap.SWITCH_HIDDEN,
action: 'keydown', SWITCH_OCCLUDED: keyMap.SWITCH_OCCLUDED,
}, SWITCH_KEYFRAME: keyMap.SWITCH_KEYFRAME,
SWITCH_LOCK: { SWITCH_OUTSIDE: keyMap.SWITCH_OUTSIDE,
name: 'Lock/unlock an object', DELETE_OBJECT: keyMap.DELETE_OBJECT,
description: 'Change locked state for an active object', TO_BACKGROUND: keyMap.TO_BACKGROUND,
sequence: 'l', TO_FOREGROUND: keyMap.TO_FOREGROUND,
action: 'keydown', COPY_SHAPE: keyMap.COPY_SHAPE,
}, PROPAGATE_OBJECT: keyMap.PROPAGATE_OBJECT,
SWITCH_ALL_HIDDEN: { NEXT_KEY_FRAME: keyMap.NEXT_KEY_FRAME,
name: 'Hide/show all objects', PREV_KEY_FRAME: keyMap.PREV_KEY_FRAME,
description: 'Change hidden state for objects in the side bar',
sequence: 't+h',
action: 'keydown',
},
SWITCH_HIDDEN: {
name: 'Hide/show an object',
description: 'Change hidden state for an active object',
sequence: 'h',
action: 'keydown',
},
SWITCH_OCCLUDED: {
name: 'Switch occluded',
description: 'Change occluded property for an active object',
sequences: ['q', '/'],
action: 'keydown',
},
SWITCH_KEYFRAME: {
name: 'Switch keyframe',
description: 'Change keyframe property for an active track',
sequence: 'k',
action: 'keydown',
},
SWITCH_OUTSIDE: {
name: 'Switch outside',
description: 'Change outside property for an active track',
sequence: 'o',
action: 'keydown',
},
DELETE_OBJECT: {
name: 'Delete object',
description: 'Delete an active object. Use shift to force delete of locked objects',
sequences: ['del', 'shift+del'],
action: 'keydown',
},
TO_BACKGROUND: {
name: 'To background',
description: 'Put an active object "farther" from the user (decrease z axis value)',
sequences: ['-', '_'],
action: 'keydown',
},
TO_FOREGROUND: {
name: 'To foreground',
description: 'Put an active object "closer" to the user (increase z axis value)',
sequences: ['+', '='],
action: 'keydown',
},
COPY_SHAPE: {
name: 'Copy shape',
description: 'Copy shape to CVAT internal clipboard',
sequence: 'ctrl+c',
action: 'keydown',
},
PROPAGATE_OBJECT: {
name: 'Propagate object',
description: 'Make a copy of the object on the following frames',
sequence: 'ctrl+b',
action: 'keydown',
},
NEXT_KEY_FRAME: {
name: 'Next keyframe',
description: 'Go to the next keyframe of an active track',
sequence: 'r',
action: 'keydown',
},
PREV_KEY_FRAME: {
name: 'Previous keyframe',
description: 'Go to the previous keyframe of an active track',
sequence: 'e',
action: 'keydown',
},
}; };
const preventDefault = (event: KeyboardEvent | undefined): void => { const preventDefault = (event: KeyboardEvent | undefined): void => {
@ -473,11 +408,13 @@ class ObjectsListContainer extends React.PureComponent<Props, State> {
return ( return (
<> <>
<GlobalHotKeys keyMap={keyMap as any as KeyMap} handlers={handlers} allowChanges /> <GlobalHotKeys keyMap={subKeyMap} handlers={handlers} allowChanges />
<ObjectsListComponent <ObjectsListComponent
{...this.props} {...this.props}
statesOrdering={statesOrdering} statesOrdering={statesOrdering}
sortedStatesID={sortedStatesID} sortedStatesID={sortedStatesID}
switchHiddenAllShortcut={normalizedKeyMap.SWITCH_ALL_HIDDEN}
switchLockAllShortcut={normalizedKeyMap.SWITCH_ALL_LOCK}
changeStatesOrdering={this.onChangeStatesOrdering} changeStatesOrdering={this.onChangeStatesOrdering}
lockAllStates={this.onLockAllStates} lockAllStates={this.onLockAllStates}
unlockAllStates={this.onUnlockAllStates} unlockAllStates={this.onUnlockAllStates}

@ -5,11 +5,9 @@
import React from 'react'; import React from 'react';
import copy from 'copy-to-clipboard'; import copy from 'copy-to-clipboard';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { withRouter } from 'react-router'; import { withRouter } from 'react-router';
import { RouteComponentProps } from 'react-router-dom'; import { RouteComponentProps } from 'react-router-dom';
import { GlobalHotKeys, KeyMap } from 'react-hotkeys'; import { GlobalHotKeys, ExtendedKeyMapOptions } from 'react-hotkeys';
import InputNumber from 'antd/lib/input-number'; import InputNumber from 'antd/lib/input-number';
import { SliderValue } from 'antd/lib/slider'; import { SliderValue } from 'antd/lib/slider';
@ -45,6 +43,8 @@ interface StateToProps {
autoSave: boolean; autoSave: boolean;
autoSaveInterval: number; autoSaveInterval: number;
workspace: Workspace; workspace: Workspace;
keyMap: Record<string, ExtendedKeyMapOptions>;
normalizedKeyMap: Record<string, string>;
} }
interface DispatchToProps { interface DispatchToProps {
@ -94,6 +94,10 @@ function mapStateToProps(state: CombinedState): StateToProps {
autoSaveInterval, autoSaveInterval,
}, },
}, },
shortcuts: {
keyMap,
normalizedKeyMap,
},
} = state; } = state;
return { return {
@ -112,6 +116,8 @@ function mapStateToProps(state: CombinedState): StateToProps {
autoSave, autoSave,
autoSaveInterval, autoSaveInterval,
workspace, workspace,
keyMap,
normalizedKeyMap,
}; };
} }
@ -464,6 +470,8 @@ class AnnotationTopBarContainer extends React.PureComponent<Props> {
canvasIsReady, canvasIsReady,
searchAnnotations, searchAnnotations,
changeWorkspace, changeWorkspace,
keyMap,
normalizedKeyMap,
} = this.props; } = this.props;
const preventDefault = (event: KeyboardEvent | undefined): void => { const preventDefault = (event: KeyboardEvent | undefined): void => {
@ -472,73 +480,18 @@ class AnnotationTopBarContainer extends React.PureComponent<Props> {
} }
}; };
const keyMap = { const subKeyMap = {
SAVE_JOB: { SAVE_JOB: keyMap.SAVE_JOB,
name: 'Save the job', UNDO: keyMap.UNDO,
description: 'Send all changes of annotations to the server', REDO: keyMap.REDO,
sequence: 'ctrl+s', NEXT_FRAME: keyMap.NEXT_FRAME,
action: 'keydown', PREV_FRAME: keyMap.PREV_FRAME,
}, FORWARD_FRAME: keyMap.FORWARD_FRAME,
UNDO: { BACKWARD_FRAME: keyMap.BACKWARD_FRAME,
name: 'Undo action', SEARCH_FORWARD: keyMap.SEARCH_FORWARD,
description: 'Cancel the latest action related with objects', SEARCH_BACKWARD: keyMap.SEARCH_BACKWARD,
sequence: 'ctrl+z', PLAY_PAUSE: keyMap.PLAY_PAUSE,
action: 'keydown', FOCUS_INPUT_FRAME: keyMap.FOCUS_INPUT_FRAME,
},
REDO: {
name: 'Redo action',
description: 'Cancel undo action',
sequences: ['ctrl+shift+z', 'ctrl+y'],
action: 'keydown',
},
NEXT_FRAME: {
name: 'Next frame',
description: 'Go to the next frame',
sequence: 'f',
action: 'keydown',
},
PREV_FRAME: {
name: 'Previous frame',
description: 'Go to the previous frame',
sequence: 'd',
action: 'keydown',
},
FORWARD_FRAME: {
name: 'Forward frame',
description: 'Go forward with a step',
sequence: 'v',
action: 'keydown',
},
BACKWARD_FRAME: {
name: 'Backward frame',
description: 'Go backward with a step',
sequence: 'c',
action: 'keydown',
},
SEARCH_FORWARD: {
name: 'Search forward',
description: 'Search the next frame that satisfies to the filters',
sequence: 'right',
action: 'keydown',
},
SEARCH_BACKWARD: {
name: 'Search backward',
description: 'Search the previous frame that satisfies to the filters',
sequence: 'left',
action: 'keydown',
},
PLAY_PAUSE: {
name: 'Play/pause',
description: 'Start/stop automatic changing frames',
sequence: 'space',
action: 'keydown',
},
FOCUS_INPUT_FRAME: {
name: 'Focus input frame',
description: 'Focus on the element to change the current frame',
sequences: ['`', '~'],
action: 'keydown',
},
}; };
const handlers = { const handlers = {
@ -608,7 +561,7 @@ class AnnotationTopBarContainer extends React.PureComponent<Props> {
return ( return (
<> <>
<GlobalHotKeys keyMap={keyMap as any as KeyMap} handlers={handlers} allowChanges /> <GlobalHotKeys keyMap={subKeyMap} handlers={handlers} allowChanges />
<AnnotationTopBarComponent <AnnotationTopBarComponent
showStatistics={this.showStatistics} showStatistics={this.showStatistics}
onSwitchPlay={this.onSwitchPlay} onSwitchPlay={this.onSwitchPlay}
@ -634,6 +587,15 @@ class AnnotationTopBarContainer extends React.PureComponent<Props> {
inputFrameRef={this.inputFrameRef} inputFrameRef={this.inputFrameRef}
undoAction={undoAction} undoAction={undoAction}
redoAction={redoAction} redoAction={redoAction}
saveShortcut={normalizedKeyMap.SAVE_JOB}
undoShortcut={normalizedKeyMap.UNDO}
redoShortcut={normalizedKeyMap.REDO}
playPauseShortcut={normalizedKeyMap.PLAY_PAUSE}
nextFrameShortcut={normalizedKeyMap.NEXT_FRAME}
previousFrameShortcut={normalizedKeyMap.PREV_FRAME}
forwardShortcut={normalizedKeyMap.FORWARD_FRAME}
backwardShortcut={normalizedKeyMap.BACKWARD_FRAME}
focusFrameInputShortcut={normalizedKeyMap.FOCUS_INPUT_FRAME}
onUndoClick={this.undo} onUndoClick={this.undo}
onRedoClick={this.redo} onRedoClick={this.redo}
/> />

@ -4,13 +4,9 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import {
SupportedPlugins,
CombinedState,
} from 'reducers/interfaces';
import getCore from 'cvat-core'; import getCore from 'cvat-core';
import HeaderComponent from 'components/header/header'; import HeaderComponent from 'components/header/header';
import { SupportedPlugins, CombinedState } from 'reducers/interfaces';
import { logoutAsync } from 'actions/auth-actions'; import { logoutAsync } from 'actions/auth-actions';
const core = getCore(); const core = getCore();
@ -29,6 +25,7 @@ interface StateToProps {
coreVersion: string; coreVersion: string;
canvasVersion: string; canvasVersion: string;
uiVersion: string; uiVersion: string;
switchSettingsShortcut: string;
} }
interface DispatchToProps { interface DispatchToProps {
@ -50,6 +47,9 @@ function mapStateToProps(state: CombinedState): StateToProps {
server, server,
packageVersion, packageVersion,
}, },
shortcuts: {
normalizedKeyMap,
},
} = state; } = state;
return { return {
@ -66,6 +66,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
coreVersion: packageVersion.core, coreVersion: packageVersion.core,
canvasVersion: packageVersion.canvas, canvasVersion: packageVersion.canvas,
uiVersion: packageVersion.ui, uiVersion: packageVersion.ui,
switchSettingsShortcut: normalizedKeyMap.OPEN_SETTINGS,
}; };
} }

@ -5,6 +5,7 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { connect, Provider } from 'react-redux'; import { connect, Provider } from 'react-redux';
import { ExtendedKeyMapOptions } from 'react-hotkeys';
import { BrowserRouter } from 'react-router-dom'; import { BrowserRouter } from 'react-router-dom';
import CVATApplication from 'components/cvat-app'; import CVATApplication from 'components/cvat-app';
@ -48,6 +49,7 @@ interface StateToProps {
installedTFAnnotation: boolean; installedTFAnnotation: boolean;
notifications: NotificationsState; notifications: NotificationsState;
user: any; user: any;
keyMap: Record<string, ExtendedKeyMapOptions>;
} }
interface DispatchToProps { interface DispatchToProps {
@ -67,6 +69,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
const { formats } = state; const { formats } = state;
const { users } = state; const { users } = state;
const { about } = state; const { about } = state;
const { shortcuts } = state;
return { return {
userInitialized: auth.initialized, userInitialized: auth.initialized,
@ -84,6 +87,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
installedTFAnnotation: plugins.list.TF_ANNOTATION, installedTFAnnotation: plugins.list.TF_ANNOTATION,
notifications: state.notifications, notifications: state.notifications,
user: auth.user, user: auth.user,
keyMap: shortcuts.keyMap,
}; };
} }

@ -2,6 +2,7 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { ExtendedKeyMapOptions } from 'react-hotkeys';
import { Canvas, RectDrawingMethod } from 'cvat-canvas'; import { Canvas, RectDrawingMethod } from 'cvat-canvas';
export type StringObject = { export type StringObject = {
@ -443,6 +444,8 @@ export interface SettingsState {
export interface ShortcutsState { export interface ShortcutsState {
visibleShortcutsHelp: boolean; visibleShortcutsHelp: boolean;
keyMap: Record<string, ExtendedKeyMapOptions>;
normalizedKeyMap: Record<string, string>;
} }
export interface CombinedState { export interface CombinedState {

@ -1,10 +1,331 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import { ExtendedKeyMapOptions } from 'react-hotkeys';
import { boundariesActions, BoundariesActionTypes } from 'actions/boundaries-actions'; import { boundariesActions, BoundariesActionTypes } from 'actions/boundaries-actions';
import { AuthActions, AuthActionTypes } from 'actions/auth-actions'; import { AuthActions, AuthActionTypes } from 'actions/auth-actions';
import { ShortcutsActions, ShortcutsActionsTypes } from 'actions/shortcuts-actions'; import { ShortcutsActions, ShortcutsActionsTypes } from 'actions/shortcuts-actions';
import { ShortcutsState } from './interfaces'; import { ShortcutsState } from './interfaces';
function formatShortcuts(shortcuts: ExtendedKeyMapOptions): string {
const list: string[] = shortcuts.sequences as string[];
return `[${list.map((shortcut: string): string => {
let keys = shortcut.split('+');
keys = keys.map((key: string): string => `${key ? key[0].toUpperCase() : key}${key.slice(1)}`);
keys = keys.join('+').split(/\s/g);
keys = keys.map((key: string): string => `${key ? key[0].toUpperCase() : key}${key.slice(1)}`);
return keys.join(' ');
}).join(', ')}]`;
}
const defaultKeyMap = {
SWITCH_SHORTCUTS: {
name: 'Show shortcuts',
description: 'Open/hide the list of available shortcuts',
sequences: ['f1'],
action: 'keydown',
},
OPEN_SETTINGS: {
name: 'Open settings',
description: 'Go to the settings page or go back',
sequences: ['f2'],
action: 'keydown',
},
SWITCH_ALL_LOCK: {
name: 'Lock/unlock all objects',
description: 'Change locked state for all objects in the side bar',
sequences: ['t+l'],
action: 'keydown',
},
SWITCH_LOCK: {
name: 'Lock/unlock an object',
description: 'Change locked state for an active object',
sequences: ['l'],
action: 'keydown',
},
SWITCH_ALL_HIDDEN: {
name: 'Hide/show all objects',
description: 'Change hidden state for objects in the side bar',
sequences: ['t+h'],
action: 'keydown',
},
SWITCH_HIDDEN: {
name: 'Hide/show an object',
description: 'Change hidden state for an active object',
sequences: ['h'],
action: 'keydown',
},
SWITCH_OCCLUDED: {
name: 'Switch occluded',
description: 'Change occluded property for an active object',
sequences: ['q', '/'],
action: 'keydown',
},
SWITCH_KEYFRAME: {
name: 'Switch keyframe',
description: 'Change keyframe property for an active track',
sequences: ['k'],
action: 'keydown',
},
SWITCH_OUTSIDE: {
name: 'Switch outside',
description: 'Change outside property for an active track',
sequences: ['o'],
action: 'keydown',
},
DELETE_OBJECT: {
name: 'Delete object',
description: 'Delete an active object. Use shift to force delete of locked objects',
sequences: ['del', 'shift+del'],
action: 'keydown',
},
TO_BACKGROUND: {
name: 'To background',
description: 'Put an active object "farther" from the user (decrease z axis value)',
sequences: ['-', '_'],
action: 'keydown',
},
TO_FOREGROUND: {
name: 'To foreground',
description: 'Put an active object "closer" to the user (increase z axis value)',
sequences: ['+', '='],
action: 'keydown',
},
COPY_SHAPE: {
name: 'Copy shape',
description: 'Copy shape to CVAT internal clipboard',
sequences: ['ctrl+c'],
action: 'keydown',
},
PROPAGATE_OBJECT: {
name: 'Propagate object',
description: 'Make a copy of the object on the following frames',
sequences: ['ctrl+b'],
action: 'keydown',
},
NEXT_KEY_FRAME: {
name: 'Next keyframe',
description: 'Go to the next keyframe of an active track',
sequences: ['r'],
action: 'keydown',
},
PREV_KEY_FRAME: {
name: 'Previous keyframe',
description: 'Go to the previous keyframe of an active track',
sequences: ['e'],
action: 'keydown',
},
NEXT_ATTRIBUTE: {
name: 'Next attribute',
description: 'Go to the next attribute',
sequences: ['ArrowDown'],
action: 'keydown',
},
PREVIOUS_ATTRIBUTE: {
name: 'Previous attribute',
description: 'Go to the previous attribute',
sequences: ['ArrowUp'],
action: 'keydown',
},
NEXT_OBJECT: {
name: 'Next object',
description: 'Go to the next object',
sequences: ['Tab'],
action: 'keydown',
},
PREVIOUS_OBJECT: {
name: 'Previous object',
description: 'Go to the previous object',
sequences: ['Shift+Tab'],
action: 'keydown',
},
INCREASE_BRIGHTNESS: {
name: 'Brightness+',
description: 'Increase brightness level for the image',
sequences: ['shift+b+='],
action: 'keypress',
},
DECREASE_BRIGHTNESS: {
name: 'Brightness-',
description: 'Decrease brightness level for the image',
sequences: ['shift+b+-'],
action: 'keydown',
},
INCREASE_CONTRAST: {
name: 'Contrast+',
description: 'Increase contrast level for the image',
sequences: ['shift+c+='],
action: 'keydown',
},
DECREASE_CONTRAST: {
name: 'Contrast-',
description: 'Decrease contrast level for the image',
sequences: ['shift+c+-'],
action: 'keydown',
},
INCREASE_SATURATION: {
name: 'Saturation+',
description: 'Increase saturation level for the image',
sequences: ['shift+s+='],
action: 'keydown',
},
DECREASE_SATURATION: {
name: 'Saturation-',
description: 'Increase contrast level for the image',
sequences: ['shift+s+-'],
action: 'keydown',
},
INCREASE_GRID_OPACITY: {
name: 'Grid opacity+',
description: 'Make the grid more visible',
sequences: ['shift+g+='],
action: 'keydown',
},
DECREASE_GRID_OPACITY: {
name: 'Grid opacity-',
description: 'Make the grid less visible',
sequences: ['shift+g+-'],
action: 'keydown',
},
CHANGE_GRID_COLOR: {
name: 'Grid color',
description: 'Set another color for the image grid',
sequences: ['shift+g+enter'],
action: 'keydown',
},
PASTE_SHAPE: {
name: 'Paste shape',
description: 'Paste a shape from internal CVAT clipboard',
sequences: ['ctrl+v'],
action: 'keydown',
},
SWITCH_DRAW_MODE: {
name: 'Draw mode',
description: 'Repeat the latest procedure of drawing with the same parameters',
sequences: ['n'],
action: 'keydown',
},
SWITCH_MERGE_MODE: {
name: 'Merge mode',
description: 'Activate or deactivate mode to merging shapes',
sequences: ['m'],
action: 'keydown',
},
SWITCH_GROUP_MODE: {
name: 'Group mode',
description: 'Activate or deactivate mode to grouping shapes',
sequences: ['g'],
action: 'keydown',
},
RESET_GROUP: {
name: 'Reset group',
description: 'Reset group for selected shapes (in group mode)',
sequences: ['shift+g'],
action: 'keyup',
},
CANCEL: {
name: 'Cancel',
description: 'Cancel any active canvas mode',
sequences: ['esc'],
action: 'keydown',
},
CLOCKWISE_ROTATION: {
name: 'Rotate clockwise',
description: 'Change image angle (add 90 degrees)',
sequences: ['ctrl+r'],
action: 'keydown',
},
ANTICLOCKWISE_ROTATION: {
name: 'Rotate anticlockwise',
description: 'Change image angle (substract 90 degrees)',
sequences: ['ctrl+shift+r'],
action: 'keydown',
},
SAVE_JOB: {
name: 'Save the job',
description: 'Send all changes of annotations to the server',
sequences: ['ctrl+s'],
action: 'keydown',
},
UNDO: {
name: 'Undo action',
description: 'Cancel the latest action related with objects',
sequences: ['ctrl+z'],
action: 'keydown',
},
REDO: {
name: 'Redo action',
description: 'Cancel undo action',
sequences: ['ctrl+shift+z', 'ctrl+y'],
action: 'keydown',
},
NEXT_FRAME: {
name: 'Next frame',
description: 'Go to the next frame',
sequences: ['f'],
action: 'keydown',
},
PREV_FRAME: {
name: 'Previous frame',
description: 'Go to the previous frame',
sequences: ['d'],
action: 'keydown',
},
FORWARD_FRAME: {
name: 'Forward frame',
description: 'Go forward with a step',
sequences: ['v'],
action: 'keydown',
},
BACKWARD_FRAME: {
name: 'Backward frame',
description: 'Go backward with a step',
sequences: ['c'],
action: 'keydown',
},
SEARCH_FORWARD: {
name: 'Search forward',
description: 'Search the next frame that satisfies to the filters',
sequences: ['right'],
action: 'keydown',
},
SEARCH_BACKWARD: {
name: 'Search backward',
description: 'Search the previous frame that satisfies to the filters',
sequences: ['left'],
action: 'keydown',
},
PLAY_PAUSE: {
name: 'Play/pause',
description: 'Start/stop automatic changing frames',
sequences: ['space'],
action: 'keydown',
},
FOCUS_INPUT_FRAME: {
name: 'Focus input frame',
description: 'Focus on the element to change the current frame',
sequences: ['`', '~'],
action: 'keydown',
},
} as any as Record<string, ExtendedKeyMapOptions>;
const defaultState: ShortcutsState = { const defaultState: ShortcutsState = {
visibleShortcutsHelp: false, visibleShortcutsHelp: false,
keyMap: defaultKeyMap,
normalizedKeyMap: Object.keys(defaultKeyMap)
.reduce((acc: Record<string, string>, key: string) => {
const normalized = formatShortcuts(defaultKeyMap[key]);
acc[key] = normalized;
return acc;
}, {}),
}; };
export default ( export default (

Loading…
Cancel
Save