): void => {
+ if (['ArrowDown', 'ArrowUp', 'ArrowLeft',
+ 'ArrowRight', 'Tab', 'Shift', 'Control']
+ .includes(event.key)
+ ) {
+ event.preventDefault();
+ const copyEvent = new KeyboardEvent('keydown', event);
+ window.document.dispatchEvent(copyEvent);
+ }
+ };
+
+ const renderText = (): JSX.Element => (
+ <>
+ {inputType === 'number' ? Number: : Text: }
+
+ ) => {
+ const { value } = event.target;
+ if (inputType === 'number') {
+ if (value !== '') {
+ const numberValue = +value;
+ if (!Number.isNaN(numberValue)) {
+ onChange(`${numberValue}`);
+ }
+ }
+ } else {
+ onChange(value);
+ }
+ }}
+ onKeyDown={handleKeydown}
+ />
+
+ >
+ );
+
+ let element = null;
+ if (inputType === 'checkbox') {
+ element = renderCheckbox();
+ } else if (inputType === 'select') {
+ element = renderSelect();
+ } else if (inputType === 'radio') {
+ element = renderRadio();
+ } else {
+ element = renderText();
+ }
+
+ return (
+
+ {element}
+
+ );
+}
+
+interface ListParameters {
+ inputType: string;
+ values: string[];
+ onChange(value: string): void;
+}
+
+function renderList(parameters: ListParameters): JSX.Element | null {
+ const { inputType, values, onChange } = parameters;
+
+ if (inputType === 'checkbox') {
+ const sortedValues = ['true', 'false'];
+ if (values[0].toLowerCase() !== 'true') {
+ sortedValues.reverse();
+ }
+
+ const keyMap: KeyMap = {};
+ const handlers: {
+ [key: string]: (keyEvent?: KeyboardEvent) => void;
+ } = {};
+
+ sortedValues.forEach((value: string, index: number): void => {
+ const key = `SET_${index}_VALUE`;
+ keyMap[key] = {
+ name: `Set value "${value}"`,
+ description: `Change current value for the attribute to "${value}"`,
+ sequence: `${index}`,
+ action: 'keydown',
+ };
+
+ handlers[key] = (event: KeyboardEvent | undefined) => {
+ if (event) {
+ event.preventDefault();
+ }
+
+ onChange(value);
+ };
+ });
+
+ return (
+
+
+
+ 0:
+ {` ${sortedValues[0]}`}
+
+
+ 1:
+ {` ${sortedValues[1]}`}
+
+
+ );
+ }
+
+ if (inputType === 'radio' || inputType === 'select') {
+ const keyMap: KeyMap = {};
+ const handlers: {
+ [key: string]: (keyEvent?: KeyboardEvent) => void;
+ } = {};
+
+ const filteredValues = values
+ .filter((value: string): boolean => value !== consts.UNDEFINED_ATTRIBUTE_VALUE);
+ filteredValues.slice(0, 10).forEach((value: string, index: number): void => {
+ const key = `SET_${index}_VALUE`;
+ keyMap[key] = {
+ name: `Set value "${value}"`,
+ description: `Change current value for the attribute to "${value}"`,
+ sequence: `${index}`,
+ action: 'keydown',
+ };
+
+ handlers[key] = (event: KeyboardEvent | undefined) => {
+ if (event) {
+ event.preventDefault();
+ }
+
+ onChange(value);
+ };
+ });
+
+ return (
+
+
+ {filteredValues.map((value: string, index: number): JSX.Element => (
+
+ {`${index}:`}
+ {` ${value}`}
+
+ ))}
+
+ );
+ }
+
+ if (inputType === 'number') {
+ return (
+
+
+ From:
+ {` ${values[0]}`}
+
+
+ To:
+ {` ${values[1]}`}
+
+
+ Step:
+ {` ${values[2]}`}
+
+
+ );
+ }
+
+ return null;
+}
+
+interface Props {
+ attribute: any;
+ currentValue: string;
+ onChange(value: string): void;
+}
+
+function AttributeEditor(props: Props): JSX.Element {
+ const { attribute, currentValue, onChange } = props;
+ const { inputType, values, id: attrID } = attribute;
+
+ return (
+
+ {renderList({ values, inputType, onChange })}
+
+ {renderInputElement({
+ attrID,
+ inputType,
+ currentValue,
+ values,
+ onChange,
+ })}
+
+ );
+}
+
+export default React.memo(AttributeEditor);
diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-switcher.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-switcher.tsx
new file mode 100644
index 000000000000..da835b608510
--- /dev/null
+++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-switcher.tsx
@@ -0,0 +1,49 @@
+// Copyright (C) 2020 Intel Corporation
+//
+// SPDX-License-Identifier: MIT
+
+import React from 'react';
+import Icon from 'antd/lib/icon';
+import Text from 'antd/lib/typography/Text';
+import Tooltip from 'antd/lib/tooltip';
+import Button from 'antd/lib/button';
+
+interface Props {
+ currentAttribute: string;
+ currentIndex: number;
+ attributesCount: number;
+ normalizedKeyMap: Record;
+ nextAttribute(step: number): void;
+}
+
+function AttributeSwitcher(props: Props): JSX.Element {
+ const {
+ currentAttribute,
+ currentIndex,
+ attributesCount,
+ nextAttribute,
+ normalizedKeyMap,
+ } = props;
+
+ const title = `${currentAttribute} [${currentIndex + 1}/${attributesCount}]`;
+ return (
+
+
+
+
+
+ {currentAttribute}
+ {` [${currentIndex + 1}/${attributesCount}]`}
+
+
+
+
+
+ );
+}
+
+export default React.memo(AttributeSwitcher);
diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-basics-edtior.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-basics-edtior.tsx
new file mode 100644
index 000000000000..17a689a997f7
--- /dev/null
+++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-basics-edtior.tsx
@@ -0,0 +1,43 @@
+// Copyright (C) 2020 Intel Corporation
+//
+// SPDX-License-Identifier: MIT
+
+import React from 'react';
+import Select, { SelectValue } from 'antd/lib/select';
+import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox';
+
+interface Props {
+ currentLabel: string;
+ labels: any[];
+ occluded: boolean;
+ setOccluded(event: CheckboxChangeEvent): void;
+ changeLabel(value: SelectValue): void;
+}
+
+function ObjectBasicsEditor(props: Props): JSX.Element {
+ const {
+ currentLabel,
+ occluded,
+ labels,
+ setOccluded,
+ changeLabel,
+ } = props;
+
+ return (
+
+
+ Occluded
+
+ );
+}
+
+export default React.memo(ObjectBasicsEditor);
diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-switcher.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-switcher.tsx
new file mode 100644
index 000000000000..97e77d0b6ec0
--- /dev/null
+++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-switcher.tsx
@@ -0,0 +1,54 @@
+// Copyright (C) 2020 Intel Corporation
+//
+// SPDX-License-Identifier: MIT
+
+import React from 'react';
+import Icon from 'antd/lib/icon';
+import Text from 'antd/lib/typography/Text';
+import Tooltip from 'antd/lib/tooltip';
+import Button from 'antd/lib/button';
+
+interface Props {
+ currentLabel: string;
+ clientID: number;
+ occluded: boolean;
+ objectsCount: number;
+ currentIndex: number;
+ normalizedKeyMap: Record;
+ nextObject(step: number): void;
+}
+
+function ObjectSwitcher(props: Props): JSX.Element {
+ const {
+ currentLabel,
+ clientID,
+ objectsCount,
+ currentIndex,
+ nextObject,
+ normalizedKeyMap,
+ } = props;
+
+
+ const title = `${currentLabel} ${clientID} [${currentIndex + 1}/${objectsCount}]`;
+ return (
+
+
+
+
+
+ {currentLabel}
+ {` ${clientID} `}
+ {`[${currentIndex + 1}/${objectsCount}]`}
+
+
+
+
+
+ );
+}
+
+export default React.memo(ObjectSwitcher);
diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx
new file mode 100644
index 000000000000..d4e5fd8de94d
--- /dev/null
+++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx
@@ -0,0 +1,19 @@
+// Copyright (C) 2020 Intel Corporation
+//
+// SPDX-License-Identifier: MIT
+
+import './styles.scss';
+import React from 'react';
+import Layout from 'antd/lib/layout';
+
+import CanvasWrapperContainer from 'containers/annotation-page/standard-workspace/canvas-wrapper';
+import AttributeAnnotationSidebar from './attribute-annotation-sidebar/attribute-annotation-sidebar';
+
+export default function AttributeAnnotationWorkspace(): JSX.Element {
+ return (
+
+
+
+
+ );
+}
diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/styles.scss b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/styles.scss
new file mode 100644
index 000000000000..231cc13b410b
--- /dev/null
+++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/styles.scss
@@ -0,0 +1,64 @@
+// Copyright (C) 2020 Intel Corporation
+//
+// SPDX-License-Identifier: MIT
+
+@import 'base.scss';
+
+.attribute-annotation-workspace.ant-layout {
+ height: 100%;
+}
+
+.attribute-annotation-sidebar {
+ background: $background-color-2;
+ padding: 5px;
+}
+
+.attribute-annotation-sidebar-switcher {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-size: 18px;
+ margin-top: 10px;
+
+ > span {
+ max-width: 60%;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ }
+
+ > button > i {
+ color: $objects-bar-icons-color;
+ }
+}
+
+.attribute-annotation-sidebar-basics-editor {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-size: 18px;
+ margin: 10px 0px;
+}
+
+.attribute-annotations-sidebar-not-found-wrapper {
+ margin-top: 20px;
+ text-align: center;
+}
+
+.attribute-annotation-sidebar-attr-list-wrapper {
+ margin: 10px 0px 10px 10px;
+}
+
+.attribute-annotation-sidebar-attr-elem-wrapper {
+ width: 60%;
+}
+
+.attribute-annotation-sidebar-number-list {
+ display: flex;
+ justify-content: space-around;
+}
+
+.attribute-annotation-sidebar-attr-editor {
+ display: flex;
+ align-items: center;
+ justify-content: space-around;
+}
diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-point-context-menu.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-point-context-menu.tsx
new file mode 100644
index 000000000000..b2cf338d2637
--- /dev/null
+++ b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-point-context-menu.tsx
@@ -0,0 +1,41 @@
+// Copyright (C) 2020 Intel Corporation
+//
+// SPDX-License-Identifier: MIT
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+
+import {
+ Button,
+} from 'antd';
+
+interface Props {
+ activatedStateID: number | null;
+ visible: boolean;
+ left: number;
+ top: number;
+ onPointDelete(): void;
+}
+
+export default function CanvasPointContextMenu(props: Props): JSX.Element | null {
+ const {
+ onPointDelete,
+ activatedStateID,
+ visible,
+ left,
+ top,
+ } = props;
+
+ if (!visible || activatedStateID === null) {
+ return null;
+ }
+
+ return ReactDOM.createPortal(
+
+
+
,
+ window.document.body,
+ );
+}
diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx
index 1b5d750820d5..8296f287b1f8 100644
--- a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx
+++ b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx
@@ -3,19 +3,25 @@
// SPDX-License-Identifier: MIT
import React from 'react';
-import { GlobalHotKeys, KeyMap } from 'react-hotkeys';
+import { GlobalHotKeys, ExtendedKeyMapOptions } from 'react-hotkeys';
+
+import Tooltip from 'antd/lib/tooltip';
+import Icon from 'antd/lib/icon';
+import Layout from 'antd/lib/layout/layout';
+import Slider, { SliderValue } from 'antd/lib/slider';
import {
- Layout,
- Slider,
- Icon,
- Tooltip,
-} from 'antd';
-
-import { SliderValue } from 'antd/lib//slider';
-import { ColorBy, GridColor, ObjectType } from 'reducers/interfaces';
+ ColorBy,
+ GridColor,
+ ObjectType,
+ ContextMenuType,
+ Workspace,
+ ShapeType,
+} from 'reducers/interfaces';
+import { LogType } from 'cvat-logger';
import { Canvas } from 'cvat-canvas';
import getCore from 'cvat-core';
+import consts from 'consts';
const cvat = getCore();
@@ -26,15 +32,18 @@ interface Props {
canvasInstance: Canvas;
jobInstance: any;
activatedStateID: number | null;
+ activatedAttributeID: number | null;
selectedStatesID: number[];
annotations: any[];
frameData: any;
frameAngle: number;
+ frameFetching: boolean;
frame: number;
opacity: number;
colorBy: ColorBy;
selectedOpacity: number;
blackBorders: boolean;
+ showBitmap: boolean;
grid: boolean;
gridSize: number;
gridColor: GridColor;
@@ -48,6 +57,12 @@ interface Props {
contrastLevel: number;
saturationLevel: number;
resetZoom: boolean;
+ contextVisible: boolean;
+ contextType: ContextMenuType;
+ aamZoomMargin: number;
+ showObjectsTextAlways: boolean;
+ workspace: Workspace;
+ keyMap: Record;
onSetupCanvas: () => void;
onDragCanvas: (enabled: boolean) => void;
onZoomCanvas: (enabled: boolean) => void;
@@ -57,14 +72,15 @@ interface Props {
onEditShape: (enabled: boolean) => void;
onShapeDrawn: () => void;
onResetCanvas: () => void;
- onUpdateAnnotations(sessionInstance: any, frame: number, states: any[]): void;
+ onUpdateAnnotations(states: any[]): void;
onCreateAnnotations(sessionInstance: any, frame: number, states: any[]): void;
onMergeAnnotations(sessionInstance: any, frame: number, states: any[]): void;
onGroupAnnotations(sessionInstance: any, frame: number, states: any[]): void;
onSplitAnnotations(sessionInstance: any, frame: number, state: any): void;
onActivateObject(activatedStateID: number | null): void;
onSelectObjects(selectedStatesID: number[]): void;
- onUpdateContextMenu(visible: boolean, left: number, top: number): void;
+ onUpdateContextMenu(visible: boolean, left: number, top: number, type: ContextMenuType,
+ pointID?: number): void;
onAddZLayer(): void;
onSwitchZLayer(cur: number): void;
onChangeBrightnessLevel(level: number): void;
@@ -78,6 +94,7 @@ interface Props {
export default class CanvasWrapperComponent extends React.PureComponent {
public componentDidMount(): void {
const {
+ showObjectsTextAlways,
canvasInstance,
curZLayer,
} = this.props;
@@ -88,7 +105,12 @@ export default class CanvasWrapperComponent extends React.PureComponent {
.getElementsByClassName('cvat-canvas-container');
wrapper.appendChild(canvasInstance.html());
+ canvasInstance.configure({
+ undefinedAttrValue: consts.UNDEFINED_ATTRIBUTE_VALUE,
+ displayAllText: showObjectsTextAlways,
+ });
canvasInstance.setZLayer(curZLayer);
+
this.initialSetup();
this.updateCanvas();
}
@@ -99,6 +121,7 @@ export default class CanvasWrapperComponent extends React.PureComponent {
colorBy,
selectedOpacity,
blackBorders,
+ showBitmap,
frameData,
frameAngle,
annotations,
@@ -113,8 +136,18 @@ export default class CanvasWrapperComponent extends React.PureComponent {
brightnessLevel,
contrastLevel,
saturationLevel,
+ workspace,
+ frameFetching,
+ showObjectsTextAlways,
} = this.props;
+ if (prevProps.showObjectsTextAlways !== showObjectsTextAlways) {
+ canvasInstance.configure({
+ undefinedAttrValue: consts.UNDEFINED_ATTRIBUTE_VALUE,
+ displayAllText: showObjectsTextAlways,
+ });
+ }
+
if (prevProps.sidebarCollapsed !== sidebarCollapsed) {
const [sidebar] = window.document.getElementsByClassName('cvat-objects-sidebar');
if (sidebar) {
@@ -161,11 +194,18 @@ export default class CanvasWrapperComponent extends React.PureComponent {
}
}
+ if (prevProps.curZLayer !== curZLayer) {
+ canvasInstance.setZLayer(curZLayer);
+ }
+
if (prevProps.annotations !== annotations || prevProps.frameData !== frameData) {
this.updateCanvas();
}
- if (prevProps.frame !== frameData.number && resetZoom) {
+ if (prevProps.frame !== frameData.number
+ && resetZoom
+ && workspace !== Workspace.ATTRIBUTE_ANNOTATION
+ ) {
canvasInstance.html().addEventListener('canvas.setup', () => {
canvasInstance.fit();
}, { once: true });
@@ -176,22 +216,61 @@ export default class CanvasWrapperComponent extends React.PureComponent {
this.updateShapesView();
}
- if (prevProps.curZLayer !== curZLayer) {
- canvasInstance.setZLayer(curZLayer);
+ if (prevProps.showBitmap !== showBitmap) {
+ canvasInstance.bitmap(showBitmap);
}
if (prevProps.frameAngle !== frameAngle) {
canvasInstance.rotate(frameAngle);
}
+ const loadingAnimation = window.document.getElementById('cvat_canvas_loading_animation');
+ if (loadingAnimation && frameFetching !== prevProps.frameFetching) {
+ if (frameFetching) {
+ loadingAnimation.classList.remove('cvat_canvas_hidden');
+ } else {
+ loadingAnimation.classList.add('cvat_canvas_hidden');
+ }
+ }
+
this.activateOnCanvas();
}
public componentWillUnmount(): void {
+ const { canvasInstance } = this.props;
+
+ canvasInstance.html().removeEventListener('mousedown', this.onCanvasMouseDown);
+ canvasInstance.html().removeEventListener('click', this.onCanvasClicked);
+ canvasInstance.html().removeEventListener('contextmenu', this.onCanvasContextMenu);
+ canvasInstance.html().removeEventListener('canvas.editstart', this.onCanvasEditStart);
+ canvasInstance.html().removeEventListener('canvas.edited', this.onCanvasEditDone);
+ canvasInstance.html().removeEventListener('canvas.dragstart', this.onCanvasDragStart);
+ canvasInstance.html().removeEventListener('canvas.dragstop', this.onCanvasDragDone);
+ canvasInstance.html().removeEventListener('canvas.zoomstart', this.onCanvasZoomStart);
+ canvasInstance.html().removeEventListener('canvas.zoomstop', this.onCanvasZoomDone);
+
+ canvasInstance.html().removeEventListener('canvas.setup', this.onCanvasSetup);
+ canvasInstance.html().removeEventListener('canvas.canceled', this.onCanvasCancel);
+ canvasInstance.html().removeEventListener('canvas.find', this.onCanvasFindObject);
+ canvasInstance.html().removeEventListener('canvas.deactivated', this.onCanvasShapeDeactivated);
+ canvasInstance.html().removeEventListener('canvas.moved', this.onCanvasCursorMoved);
+
+ canvasInstance.html().removeEventListener('canvas.zoom', this.onCanvasZoomChanged);
+ canvasInstance.html().removeEventListener('canvas.fit', this.onCanvasImageFitted);
+ canvasInstance.html().removeEventListener('canvas.dragshape', this.onCanvasShapeDragged);
+ canvasInstance.html().removeEventListener('canvas.resizeshape', this.onCanvasShapeResized);
+ canvasInstance.html().removeEventListener('canvas.clicked', this.onCanvasShapeClicked);
+ canvasInstance.html().removeEventListener('canvas.drawn', this.onCanvasShapeDrawn);
+ canvasInstance.html().removeEventListener('canvas.merged', this.onCanvasObjectsMerged);
+ canvasInstance.html().removeEventListener('canvas.groupped', this.onCanvasObjectsGroupped);
+ canvasInstance.html().removeEventListener('canvas.splitted', this.onCanvasTrackSplitted);
+
+ canvasInstance.html().removeEventListener('point.contextmenu', this.onCanvasPointContextMenu);
+
window.removeEventListener('resize', this.fitCanvas);
}
- private onShapeDrawn(event: any): void {
+ private onCanvasShapeDrawn = (event: any): void => {
const {
jobInstance,
activeLabelID,
@@ -205,44 +284,24 @@ export default class CanvasWrapperComponent extends React.PureComponent {
onShapeDrawn();
}
- const { state } = event.detail;
- if (!state.objectType) {
- state.objectType = activeObjectType;
- }
-
- if (!state.label) {
- [state.label] = jobInstance.task.labels
- .filter((label: any) => label.id === activeLabelID);
- }
-
- if (typeof (state.occluded) === 'undefined') {
- state.occluded = false;
+ const { state, duration } = event.detail;
+ const isDrawnFromScratch = !state.label;
+ if (isDrawnFromScratch) {
+ jobInstance.logger.log(LogType.drawObject, { count: 1, duration });
+ } else {
+ jobInstance.logger.log(LogType.pasteObject, { count: 1, duration });
}
+ state.objectType = state.objectType || activeObjectType;
+ state.label = state.label || jobInstance.task.labels
+ .filter((label: any) => label.id === activeLabelID)[0];
+ state.occluded = state.occluded || false;
state.frame = frame;
const objectState = new cvat.classes.ObjectState(state);
onCreateAnnotations(jobInstance, frame, [objectState]);
- }
-
- private onShapeEdited(event: any): void {
- const {
- jobInstance,
- frame,
- onEditShape,
- onUpdateAnnotations,
- } = this.props;
-
- onEditShape(false);
-
- const {
- state,
- points,
- } = event.detail;
- state.points = points;
- onUpdateAnnotations(jobInstance, frame, [state]);
- }
+ };
- private onObjectsMerged(event: any): void {
+ private onCanvasObjectsMerged = (event: any): void => {
const {
jobInstance,
frame,
@@ -252,11 +311,15 @@ export default class CanvasWrapperComponent extends React.PureComponent {
onMergeObjects(false);
- const { states } = event.detail;
+ const { states, duration } = event.detail;
+ jobInstance.logger.log(LogType.mergeObjects, {
+ duration,
+ count: states.length,
+ });
onMergeAnnotations(jobInstance, frame, states);
- }
+ };
- private onObjectsGroupped(event: any): void {
+ private onCanvasObjectsGroupped = (event: any): void => {
const {
jobInstance,
frame,
@@ -268,9 +331,9 @@ export default class CanvasWrapperComponent extends React.PureComponent {
const { states } = event.detail;
onGroupAnnotations(jobInstance, frame, states);
- }
+ };
- private onTrackSplitted(event: any): void {
+ private onCanvasTrackSplitted = (event: any): void => {
const {
jobInstance,
frame,
@@ -282,22 +345,223 @@ export default class CanvasWrapperComponent extends React.PureComponent {
const { state } = event.detail;
onSplitAnnotations(jobInstance, frame, state);
- }
+ };
private fitCanvas = (): void => {
const { canvasInstance } = this.props;
canvasInstance.fitCanvas();
};
+ private onCanvasMouseDown = (e: MouseEvent): void => {
+ const { workspace, activatedStateID, onActivateObject } = this.props;
+
+ if ((e.target as HTMLElement).tagName === 'svg') {
+ if (activatedStateID !== null && workspace !== Workspace.ATTRIBUTE_ANNOTATION) {
+ onActivateObject(null);
+ }
+ }
+ };
+
+ private onCanvasClicked = (): void => {
+ if (document.activeElement instanceof HTMLElement) {
+ document.activeElement.blur();
+ }
+ };
+
+ private onCanvasContextMenu = (e: MouseEvent): void => {
+ const {
+ activatedStateID,
+ onUpdateContextMenu,
+ contextType,
+ } = this.props;
+
+ if (contextType !== ContextMenuType.CANVAS_SHAPE_POINT) {
+ onUpdateContextMenu(activatedStateID !== null, e.clientX, e.clientY,
+ ContextMenuType.CANVAS_SHAPE);
+ }
+ };
+
+ private onCanvasShapeDragged = (e: any): void => {
+ const { jobInstance } = this.props;
+ const { id } = e.detail;
+ jobInstance.logger.log(LogType.dragObject, { id });
+ };
+
+ private onCanvasShapeResized = (e: any): void => {
+ const { jobInstance } = this.props;
+ const { id } = e.detail;
+ jobInstance.logger.log(LogType.resizeObject, { id });
+ };
+
+ private onCanvasImageFitted = (): void => {
+ const { jobInstance } = this.props;
+ jobInstance.logger.log(LogType.fitImage);
+ };
+
+ private onCanvasZoomChanged = (): void => {
+ const { jobInstance } = this.props;
+ jobInstance.logger.log(LogType.zoomImage);
+ };
+
+ private onCanvasShapeClicked = (e: any): void => {
+ const { clientID } = e.detail.state;
+ const sidebarItem = window.document
+ .getElementById(`cvat-objects-sidebar-state-item-${clientID}`);
+ if (sidebarItem) {
+ sidebarItem.scrollIntoView();
+ }
+ };
+
+ private onCanvasShapeDeactivated = (e: any): void => {
+ const { onActivateObject, activatedStateID } = this.props;
+ const { state } = e.detail;
+
+ // when we activate element, canvas deactivates the previous
+ // and triggers this event
+ // in this case we do not need to update our state
+ if (state.clientID === activatedStateID) {
+ onActivateObject(null);
+ }
+ };
+
+ private onCanvasCursorMoved = async (event: any): Promise => {
+ const {
+ jobInstance,
+ activatedStateID,
+ workspace,
+ onActivateObject,
+ } = this.props;
+
+ if (workspace === Workspace.ATTRIBUTE_ANNOTATION) {
+ return;
+ }
+
+ const result = await jobInstance.annotations.select(
+ event.detail.states,
+ event.detail.x,
+ event.detail.y,
+ );
+
+ if (result && result.state) {
+ if (result.state.shapeType === 'polyline' || result.state.shapeType === 'points') {
+ if (result.distance > MAX_DISTANCE_TO_OPEN_SHAPE) {
+ return;
+ }
+ }
+
+ if (activatedStateID !== result.state.clientID) {
+ onActivateObject(result.state.clientID);
+ }
+ }
+ };
+
+ private onCanvasEditStart = (): void => {
+ const { onActivateObject, onEditShape } = this.props;
+ onActivateObject(null);
+ onEditShape(true);
+ };
+
+ private onCanvasEditDone = (event: any): void => {
+ const {
+ onEditShape,
+ onUpdateAnnotations,
+ } = this.props;
+
+ onEditShape(false);
+
+ const {
+ state,
+ points,
+ } = event.detail;
+ state.points = points;
+ onUpdateAnnotations([state]);
+ };
+
+ private onCanvasDragStart = (): void => {
+ const { onDragCanvas } = this.props;
+ onDragCanvas(true);
+ };
+
+ private onCanvasDragDone = (): void => {
+ const { onDragCanvas } = this.props;
+ onDragCanvas(false);
+ };
+
+ private onCanvasZoomStart = (): void => {
+ const { onZoomCanvas } = this.props;
+ onZoomCanvas(true);
+ };
+
+ private onCanvasZoomDone = (): void => {
+ const { onZoomCanvas } = this.props;
+ onZoomCanvas(false);
+ };
+
+ private onCanvasSetup = (): void => {
+ const { onSetupCanvas } = this.props;
+ onSetupCanvas();
+ this.updateShapesView();
+ this.activateOnCanvas();
+ };
+
+ private onCanvasCancel = (): void => {
+ const { onResetCanvas } = this.props;
+ onResetCanvas();
+ };
+
+ private onCanvasFindObject = async (e: any): Promise => {
+ const { jobInstance, canvasInstance } = this.props;
+
+ const result = await jobInstance.annotations
+ .select(e.detail.states, e.detail.x, e.detail.y);
+
+ if (result && result.state) {
+ if (result.state.shapeType === 'polyline' || result.state.shapeType === 'points') {
+ if (result.distance > MAX_DISTANCE_TO_OPEN_SHAPE) {
+ return;
+ }
+ }
+
+ canvasInstance.select(result.state);
+ }
+ };
+
+ private onCanvasPointContextMenu = (e: any): void => {
+ const {
+ activatedStateID,
+ onUpdateContextMenu,
+ annotations,
+ } = this.props;
+
+ const [state] = annotations.filter((el: any) => (el.clientID === activatedStateID));
+ if (state.shapeType !== ShapeType.RECTANGLE) {
+ onUpdateContextMenu(activatedStateID !== null, e.detail.mouseEvent.clientX,
+ e.detail.mouseEvent.clientY, ContextMenuType.CANVAS_SHAPE_POINT, e.detail.pointID);
+ }
+ };
+
private activateOnCanvas(): void {
const {
activatedStateID,
+ activatedAttributeID,
canvasInstance,
selectedOpacity,
+ aamZoomMargin,
+ workspace,
+ annotations,
} = this.props;
if (activatedStateID !== null) {
- canvasInstance.activate(activatedStateID);
+ if (workspace === Workspace.ATTRIBUTE_ANNOTATION) {
+ const [activatedState] = annotations
+ .filter((state: any): boolean => state.clientID === activatedStateID);
+ if (activatedState.objectType !== ObjectType.TAG) {
+ canvasInstance.focus(activatedStateID, aamZoomMargin);
+ } else {
+ canvasInstance.fit();
+ }
+ }
+ canvasInstance.activate(activatedStateID, activatedAttributeID);
const el = window.document.getElementById(`cvat_canvas_shape_${activatedStateID}`);
if (el) {
(el as any as SVGElement).setAttribute('fill-opacity', `${selectedOpacity / 100}`);
@@ -315,6 +579,7 @@ export default class CanvasWrapperComponent extends React.PureComponent {
for (const state of annotations) {
let shapeColor = '';
+
if (colorBy === ColorBy.INSTANCE) {
shapeColor = state.color;
} else if (colorBy === ColorBy.GROUP) {
@@ -330,6 +595,7 @@ export default class CanvasWrapperComponent extends React.PureComponent {
if (handler && handler.nested) {
handler.nested.fill({ color: shapeColor });
}
+
(shapeView as any).instance.fill({ color: shapeColor, opacity: opacity / 100 });
(shapeView as any).instance.stroke({ color: blackBorders ? 'black' : shapeColor });
}
@@ -345,7 +611,8 @@ export default class CanvasWrapperComponent extends React.PureComponent {
} = this.props;
if (frameData !== null) {
- canvasInstance.setup(frameData, annotations);
+ canvasInstance.setup(frameData, annotations
+ .filter((e) => e.objectType !== ObjectType.TAG));
canvasInstance.rotate(frameAngle);
}
}
@@ -357,14 +624,6 @@ export default class CanvasWrapperComponent extends React.PureComponent {
gridColor,
gridOpacity,
canvasInstance,
- jobInstance,
- onSetupCanvas,
- onDragCanvas,
- onZoomCanvas,
- onResetCanvas,
- onActivateObject,
- onUpdateContextMenu,
- onEditShape,
brightnessLevel,
contrastLevel,
saturationLevel,
@@ -395,127 +654,39 @@ export default class CanvasWrapperComponent extends React.PureComponent {
}
// Events
- canvasInstance.html().addEventListener('mousedown', (e: MouseEvent): void => {
- const {
- activatedStateID,
- } = this.props;
-
- if ((e.target as HTMLElement).tagName === 'svg' && activatedStateID !== null) {
- onActivateObject(null);
- }
- });
-
- canvasInstance.html().addEventListener('click', (): void => {
- if (document.activeElement) {
- (document.activeElement as HTMLElement).blur();
- }
- });
-
- canvasInstance.html().addEventListener('contextmenu', (e: MouseEvent): void => {
- const {
- activatedStateID,
- } = this.props;
-
- onUpdateContextMenu(activatedStateID !== null, e.clientX, e.clientY);
- });
-
- canvasInstance.html().addEventListener('canvas.editstart', (): void => {
- onActivateObject(null);
- onEditShape(true);
- });
-
- canvasInstance.html().addEventListener('canvas.setup', (): void => {
- onSetupCanvas();
- this.updateShapesView();
- this.activateOnCanvas();
- });
-
canvasInstance.html().addEventListener('canvas.setup', () => {
+ const { activatedStateID, activatedAttributeID } = this.props;
canvasInstance.fit();
+ canvasInstance.activate(activatedStateID, activatedAttributeID);
}, { once: true });
- canvasInstance.html().addEventListener('canvas.canceled', () => {
- onResetCanvas();
- });
-
- canvasInstance.html().addEventListener('canvas.dragstart', () => {
- onDragCanvas(true);
- });
-
- canvasInstance.html().addEventListener('canvas.dragstop', () => {
- onDragCanvas(false);
- });
-
- canvasInstance.html().addEventListener('canvas.zoomstart', () => {
- onZoomCanvas(true);
- });
-
- canvasInstance.html().addEventListener('canvas.zoomstop', () => {
- onZoomCanvas(false);
- });
-
- canvasInstance.html().addEventListener('canvas.clicked', (e: any) => {
- const { clientID } = e.detail.state;
- const sidebarItem = window.document
- .getElementById(`cvat-objects-sidebar-state-item-${clientID}`);
- if (sidebarItem) {
- sidebarItem.scrollIntoView();
- }
- });
-
- canvasInstance.html().addEventListener('canvas.deactivated', (e: any): void => {
- const { activatedStateID } = this.props;
- const { state } = e.detail;
-
- // when we activate element, canvas deactivates the previous
- // and triggers this event
- // in this case we do not need to update our state
- if (state.clientID === activatedStateID) {
- onActivateObject(null);
- }
- });
-
- canvasInstance.html().addEventListener('canvas.moved', async (event: any): Promise => {
- const { activatedStateID } = this.props;
- const result = await jobInstance.annotations.select(
- event.detail.states,
- event.detail.x,
- event.detail.y,
- );
-
- if (result && result.state) {
- if (result.state.shapeType === 'polyline' || result.state.shapeType === 'points') {
- if (result.distance > MAX_DISTANCE_TO_OPEN_SHAPE) {
- return;
- }
- }
-
- if (activatedStateID !== result.state.clientID) {
- onActivateObject(result.state.clientID);
- }
- }
- });
-
- canvasInstance.html().addEventListener('canvas.find', async (e: any) => {
- const result = await jobInstance.annotations
- .select(e.detail.states, e.detail.x, e.detail.y);
-
- if (result && result.state) {
- if (result.state.shapeType === 'polyline' || result.state.shapeType === 'points') {
- if (result.distance > MAX_DISTANCE_TO_OPEN_SHAPE) {
- return;
- }
- }
-
- canvasInstance.select(result.state);
- }
- });
-
- canvasInstance.html().addEventListener('canvas.edited', this.onShapeEdited.bind(this));
- canvasInstance.html().addEventListener('canvas.drawn', this.onShapeDrawn.bind(this));
- canvasInstance.html().addEventListener('canvas.merged', this.onObjectsMerged.bind(this));
- canvasInstance.html().addEventListener('canvas.groupped', this.onObjectsGroupped.bind(this));
- canvasInstance.html().addEventListener('canvas.splitted', this.onTrackSplitted.bind(this));
+ canvasInstance.html().addEventListener('mousedown', this.onCanvasMouseDown);
+ canvasInstance.html().addEventListener('click', this.onCanvasClicked);
+ canvasInstance.html().addEventListener('contextmenu', this.onCanvasContextMenu);
+ canvasInstance.html().addEventListener('canvas.editstart', this.onCanvasEditStart);
+ canvasInstance.html().addEventListener('canvas.edited', this.onCanvasEditDone);
+ canvasInstance.html().addEventListener('canvas.dragstart', this.onCanvasDragStart);
+ canvasInstance.html().addEventListener('canvas.dragstop', this.onCanvasDragDone);
+ canvasInstance.html().addEventListener('canvas.zoomstart', this.onCanvasZoomStart);
+ canvasInstance.html().addEventListener('canvas.zoomstop', this.onCanvasZoomDone);
+
+ canvasInstance.html().addEventListener('canvas.setup', this.onCanvasSetup);
+ canvasInstance.html().addEventListener('canvas.canceled', this.onCanvasCancel);
+ canvasInstance.html().addEventListener('canvas.find', this.onCanvasFindObject);
+ canvasInstance.html().addEventListener('canvas.deactivated', this.onCanvasShapeDeactivated);
+ canvasInstance.html().addEventListener('canvas.moved', this.onCanvasCursorMoved);
+
+ canvasInstance.html().addEventListener('canvas.zoom', this.onCanvasZoomChanged);
+ canvasInstance.html().addEventListener('canvas.fit', this.onCanvasImageFitted);
+ canvasInstance.html().addEventListener('canvas.dragshape', this.onCanvasShapeDragged);
+ canvasInstance.html().addEventListener('canvas.resizeshape', this.onCanvasShapeResized);
+ canvasInstance.html().addEventListener('canvas.clicked', this.onCanvasShapeClicked);
+ canvasInstance.html().addEventListener('canvas.drawn', this.onCanvasShapeDrawn);
+ canvasInstance.html().addEventListener('canvas.merged', this.onCanvasObjectsMerged);
+ canvasInstance.html().addEventListener('canvas.groupped', this.onCanvasObjectsGroupped);
+ canvasInstance.html().addEventListener('canvas.splitted', this.onCanvasTrackSplitted);
+
+ canvasInstance.html().addEventListener('point.contextmenu', this.onCanvasPointContextMenu);
}
public render(): JSX.Element {
@@ -537,6 +708,7 @@ export default class CanvasWrapperComponent extends React.PureComponent {
onChangeGridColor,
onChangeGridOpacity,
onSwitchGrid,
+ keyMap,
} = this.props;
const preventDefault = (event: KeyboardEvent | undefined): void => {
@@ -545,61 +717,16 @@ export default class CanvasWrapperComponent extends React.PureComponent {
}
};
- const keyMap = {
- INCREASE_BRIGHTNESS: {
- name: 'Brightness+',
- description: 'Increase brightness level for the image',
- sequence: 'shift+b+=',
- action: 'keypress',
- },
- DECREASE_BRIGHTNESS: {
- name: 'Brightness-',
- description: 'Decrease brightness level for the image',
- 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 subKeyMap = {
+ INCREASE_BRIGHTNESS: keyMap.INCREASE_BRIGHTNESS,
+ DECREASE_BRIGHTNESS: keyMap.DECREASE_BRIGHTNESS,
+ INCREASE_CONTRAST: keyMap.INCREASE_CONTRAST,
+ DECREASE_CONTRAST: keyMap.DECREASE_CONTRAST,
+ INCREASE_SATURATION: keyMap.INCREASE_SATURATION,
+ DECREASE_SATURATION: keyMap.DECREASE_SATURATION,
+ INCREASE_GRID_OPACITY: keyMap.INCREASE_GRID_OPACITY,
+ DECREASE_GRID_OPACITY: keyMap.DECREASE_GRID_OPACITY,
+ CHANGE_GRID_COLOR: keyMap.CHANGE_GRID_COLOR,
};
const step = 10;
@@ -680,7 +807,7 @@ export default class CanvasWrapperComponent extends React.PureComponent {
return (
-
+
{/*
This element doesn't have any props
So, React isn't going to rerender it
diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx
index 7deeabb49b99..16063fbb86c8 100644
--- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx
+++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx
@@ -3,26 +3,11 @@
// SPDX-License-Identifier: MIT
import React from 'react';
-import { GlobalHotKeys, KeyMap } from 'react-hotkeys';
+import { GlobalHotKeys, ExtendedKeyMapOptions } from 'react-hotkeys';
+import Layout from 'antd/lib/layout';
-import {
- Icon,
- Layout,
- Tooltip,
-} from 'antd';
-
-import {
- ActiveControl,
- Rotation,
-} from 'reducers/interfaces';
-
-import {
- TagIcon,
-} from 'icons';
-
-import {
- Canvas,
-} from 'cvat-canvas';
+import { ActiveControl, Rotation } from 'reducers/interfaces';
+import { Canvas } from 'cvat-canvas';
import RotateControl from './rotate-control';
import CursorControl from './cursor-control';
@@ -33,6 +18,7 @@ import DrawRectangleControl from './draw-rectangle-control';
import DrawPolygonControl from './draw-polygon-control';
import DrawPolylineControl from './draw-polyline-control';
import DrawPointsControl from './draw-points-control';
+import SetupTagControl from './setup-tag-control';
import MergeControl from './merge-control';
import GroupControl from './group-control';
import SplitControl from './split-control';
@@ -40,6 +26,8 @@ import SplitControl from './split-control';
interface Props {
canvasInstance: Canvas;
activeControl: ActiveControl;
+ keyMap: Record;
+ normalizedKeyMap: Record;
mergeObjects(enabled: boolean): void;
groupObjects(enabled: boolean): void;
@@ -62,6 +50,8 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element {
repeatDrawShape,
pasteShape,
resetGroup,
+ normalizedKeyMap,
+ keyMap,
} = props;
const preventDefault = (event: KeyboardEvent | undefined): void => {
@@ -70,55 +60,15 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element {
}
};
- const keyMap = {
- PASTE_SHAPE: {
- name: 'Paste shape',
- description: 'Paste a shape from internal CVAT clipboard',
- sequence: 'ctrl+v',
- action: 'keydown',
- },
- SWITCH_DRAW_MODE: {
- name: 'Draw mode',
- 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 subKeyMap = {
+ PASTE_SHAPE: keyMap.PASTE_SHAPE,
+ SWITCH_DRAW_MODE: keyMap.SWITCH_DRAW_MODE,
+ SWITCH_MERGE_MODE: keyMap.SWITCH_MERGE_MODE,
+ SWITCH_GROUP_MODE: keyMap.SWITCH_GROUP_MODE,
+ RESET_GROUP: keyMap.RESET_GROUP,
+ CANCEL: keyMap.CANCEL,
+ CLOCKWISE_ROTATION: keyMap.CLOCKWISE_ROTATION,
+ ANTICLOCKWISE_ROTATION: keyMap.ANTICLOCKWISE_ROTATION,
};
const handlers = {
@@ -191,11 +141,18 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element {
theme='light'
width={44}
>
-
-
-
+
+
-
+
@@ -221,18 +178,22 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element {
isDrawing={activeControl === ActiveControl.DRAW_POINTS}
/>
-
-
-
+
+
+
)}
>
{
+ if (typeof (value) === 'number') {
+ onChangePoints(Math.floor(
+ clamp(value, minimumPoints, Number.MAX_SAFE_INTEGER),
+ ));
+ } else if (!value) {
+ onChangePoints(undefined);
+ }
+ }}
className='cvat-draw-shape-popover-points-selector'
min={minimumPoints}
value={numberOfPoints}
@@ -129,19 +136,23 @@ function DrawShapePopoverComponent(props: Props): JSX.Element {
}
-
+
+
+
-
+
+
+
diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/fit-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/fit-control.tsx
index bee90af83a67..049e37a8c573 100644
--- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/fit-control.tsx
+++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/fit-control.tsx
@@ -3,19 +3,11 @@
// SPDX-License-Identifier: MIT
import React from 'react';
+import Icon from 'antd/lib/icon';
+import Tooltip from 'antd/lib/tooltip';
-import {
- Icon,
- Tooltip,
-} from 'antd';
-
-import {
- FitIcon,
-} from 'icons';
-
-import {
- Canvas,
-} from 'cvat-canvas';
+import { FitIcon } from 'icons';
+import { Canvas } from 'cvat-canvas';
interface Props {
canvasInstance: Canvas;
@@ -27,7 +19,7 @@ function FitControl(props: Props): JSX.Element {
} = props;
return (
-
+
canvasInstance.fit()} />
);
diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/group-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/group-control.tsx
index 5fc2c40e62fb..8143caf1a2d3 100644
--- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/group-control.tsx
+++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/group-control.tsx
@@ -3,28 +3,25 @@
// SPDX-License-Identifier: MIT
import React from 'react';
+import Tooltip from 'antd/lib/tooltip';
+import Icon from 'antd/lib/icon';
-import {
- Tooltip,
- Icon,
-} from 'antd';
-
-import {
- GroupIcon,
-} from 'icons';
-
+import { GroupIcon } from 'icons';
import { Canvas } from 'cvat-canvas';
import { ActiveControl } from 'reducers/interfaces';
interface Props {
canvasInstance: Canvas;
activeControl: ActiveControl;
-
+ switchGroupShortcut: string;
+ resetGroupShortcut: string;
groupObjects(enabled: boolean): void;
}
function GroupControl(props: Props): JSX.Element {
const {
+ switchGroupShortcut,
+ resetGroupShortcut,
activeControl,
canvasInstance,
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 (
-
+
);
diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/merge-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/merge-control.tsx
index 3bccdd4bd895..140f64209ecd 100644
--- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/merge-control.tsx
+++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/merge-control.tsx
@@ -19,12 +19,13 @@ import { ActiveControl } from 'reducers/interfaces';
interface Props {
canvasInstance: Canvas;
activeControl: ActiveControl;
-
+ switchMergeShortcut: string;
mergeObjects(enabled: boolean): void;
}
function MergeControl(props: Props): JSX.Element {
const {
+ switchMergeShortcut,
activeControl,
canvasInstance,
mergeObjects,
@@ -46,7 +47,7 @@ function MergeControl(props: Props): JSX.Element {
};
return (
-
+
);
diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/move-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/move-control.tsx
index b62ff354ce15..89c5a8d68949 100644
--- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/move-control.tsx
+++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/move-control.tsx
@@ -3,23 +3,12 @@
// SPDX-License-Identifier: MIT
import React from 'react';
+import Icon from 'antd/lib/icon';
+import Tooltip from 'antd/lib/tooltip';
-import {
- Icon,
- Tooltip,
-} from 'antd';
-
-import {
- MoveIcon,
-} from 'icons';
-
-import {
- ActiveControl,
-} from 'reducers/interfaces';
-
-import {
- Canvas,
-} from 'cvat-canvas';
+import { MoveIcon } from 'icons';
+import { ActiveControl } from 'reducers/interfaces';
+import { Canvas } from 'cvat-canvas';
interface Props {
canvasInstance: Canvas;
@@ -27,10 +16,7 @@ interface Props {
}
function MoveControl(props: Props): JSX.Element {
- const {
- canvasInstance,
- activeControl,
- } = props;
+ const { canvasInstance, activeControl } = props;
return (
diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/resize-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/resize-control.tsx
index 8b3343b65e39..ab036a1eb33f 100644
--- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/resize-control.tsx
+++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/resize-control.tsx
@@ -3,23 +3,12 @@
// SPDX-License-Identifier: MIT
import React from 'react';
+import Icon from 'antd/lib/icon';
+import Tooltip from 'antd/lib/tooltip';
-import {
- Icon,
- Tooltip,
-} from 'antd';
-
-import {
- ZoomIcon,
-} from 'icons';
-
-import {
- ActiveControl,
-} from 'reducers/interfaces';
-
-import {
- Canvas,
-} from 'cvat-canvas';
+import { ZoomIcon } from 'icons';
+import { ActiveControl } from 'reducers/interfaces';
+import { Canvas } from 'cvat-canvas';
interface Props {
canvasInstance: Canvas;
diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx
index 139bff153326..df9d4c159ad7 100644
--- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx
+++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx
@@ -3,27 +3,23 @@
// SPDX-License-Identifier: MIT
import React from 'react';
+import Icon from 'antd/lib/icon';
+import Tooltip from 'antd/lib/tooltip';
+import Popover from 'antd/lib/popover';
-import {
- Icon,
- Tooltip,
- Popover,
-} from 'antd';
-
-import {
- RotateIcon,
-} from 'icons';
-
-import {
- Rotation,
-} from 'reducers/interfaces';
+import { RotateIcon } from 'icons';
+import { Rotation } from 'reducers/interfaces';
interface Props {
+ clockwiseShortcut: string;
+ anticlockwiseShortcut: string;
rotateFrame(rotation: Rotation): void;
}
function RotateControl(props: Props): JSX.Element {
const {
+ anticlockwiseShortcut,
+ clockwiseShortcut,
rotateFrame,
} = props;
@@ -33,14 +29,14 @@ function RotateControl(props: Props): JSX.Element {
placement='right'
content={(
<>
-
+
rotateFrame(Rotation.ANTICLOCKWISE90)}
component={RotateIcon}
/>
-
+
rotateFrame(Rotation.CLOCKWISE90)}
diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/setup-tag-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/setup-tag-control.tsx
new file mode 100644
index 000000000000..4b6703ba9489
--- /dev/null
+++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/setup-tag-control.tsx
@@ -0,0 +1,46 @@
+// Copyright (C) 2020 Intel Corporation
+//
+// SPDX-License-Identifier: MIT
+
+import React from 'react';
+import Popover from 'antd/lib/popover';
+import Icon from 'antd/lib/icon';
+
+import { Canvas } from 'cvat-canvas';
+import { TagIcon } from 'icons';
+
+import SetupTagPopoverContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover';
+
+interface Props {
+ canvasInstance: Canvas;
+ isDrawing: boolean;
+}
+
+function SetupTagControl(props: Props): JSX.Element {
+ const {
+ isDrawing,
+ } = props;
+
+ const dynamcPopoverPros = isDrawing ? {
+ overlayStyle: {
+ display: 'none',
+ },
+ } : {};
+
+ return (
+
+ )}
+ >
+
+
+ );
+}
+
+export default React.memo(SetupTagControl);
diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover.tsx
new file mode 100644
index 000000000000..3366079d0a91
--- /dev/null
+++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover.tsx
@@ -0,0 +1,75 @@
+// Copyright (C) 2020 Intel Corporation
+//
+// SPDX-License-Identifier: MIT
+
+import React from 'react';
+import { Row, Col } from 'antd/lib/grid';
+import Select from 'antd/lib/select';
+import Button from 'antd/lib/button';
+import Tooltip from 'antd/lib/tooltip';
+import Text from 'antd/lib/typography/Text';
+
+interface Props {
+ labels: any[];
+ selectedLabeID: number;
+ repeatShapeShortcut: string;
+ onChangeLabel(value: string): void;
+ onSetup(
+ labelID: number,
+ ): void;
+}
+
+function SetupTagPopover(props: Props): JSX.Element {
+ const {
+ labels,
+ selectedLabeID,
+ repeatShapeShortcut,
+ onChangeLabel,
+ onSetup,
+ } = props;
+
+ return (
+
+
+
+ Setup tag
+
+
+
+
+ Label
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default React.memo(SetupTagPopover);
diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/split-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/split-control.tsx
index 7f434d3a0802..5d7be5f701fe 100644
--- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/split-control.tsx
+++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/split-control.tsx
@@ -3,16 +3,10 @@
// SPDX-License-Identifier: MIT
import React from 'react';
+import Tooltip from 'antd/lib/tooltip';
+import Icon from 'antd/lib/icon';
-import {
- Tooltip,
- Icon,
-} from 'antd';
-
-import {
- SplitIcon,
-} from 'icons';
-
+import { SplitIcon } from 'icons';
import { Canvas } from 'cvat-canvas';
import { ActiveControl } from 'reducers/interfaces';
diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/appearance-block.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/appearance-block.tsx
index 56c5b5ac5fae..d3ed0f03d4d3 100644
--- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/appearance-block.tsx
+++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/appearance-block.tsx
@@ -24,12 +24,14 @@ interface Props {
opacity: number;
selectedOpacity: number;
blackBorders: boolean;
+ showBitmap: boolean;
collapseAppearance(): void;
changeShapesColorBy(event: RadioChangeEvent): void;
changeShapesOpacity(event: SliderValue): void;
changeSelectedShapesOpacity(event: SliderValue): void;
changeShapesBlackBorders(event: CheckboxChangeEvent): void;
+ changeShowBitmap(event: CheckboxChangeEvent): void;
}
function AppearanceBlock(props: Props): JSX.Element {
@@ -39,11 +41,13 @@ function AppearanceBlock(props: Props): JSX.Element {
opacity,
selectedOpacity,
blackBorders,
+ showBitmap,
collapseAppearance,
changeShapesColorBy,
changeShapesOpacity,
changeSelectedShapesOpacity,
changeShapesBlackBorders,
+ changeShowBitmap,
} = props;
return (
@@ -85,6 +89,12 @@ function AppearanceBlock(props: Props): JSX.Element {
>
Black borders
+
+ Show bitmap
+
diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx
index 284fc6130094..2a43e042ff50 100644
--- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx
+++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx
@@ -3,29 +3,24 @@
// SPDX-License-Identifier: MIT
import React from 'react';
-
-import {
- Row,
- Col,
- Icon,
- Select,
- Radio,
- Input,
- Collapse,
- Checkbox,
- InputNumber,
- Dropdown,
- Menu,
- Button,
- Modal,
- Popover,
-} from 'antd';
-
+import { Row, Col } from 'antd/lib/grid';
+import Icon from 'antd/lib/icon';
+import Select from 'antd/lib/select';
+import Radio, { RadioChangeEvent } from 'antd/lib/radio';
+import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox';
+import Input from 'antd/lib/input';
+import InputNumber from 'antd/lib/input-number';
+import Collapse from 'antd/lib/collapse';
+import Dropdown from 'antd/lib/dropdown';
+import Menu from 'antd/lib/menu';
+import Button from 'antd/lib/button';
+import Modal from 'antd/lib/modal';
+import Popover from 'antd/lib/popover';
import Text from 'antd/lib/typography/Text';
-import { RadioChangeEvent } from 'antd/lib/radio';
-import { CheckboxChangeEvent } from 'antd/lib/checkbox';
-import ColorChanger from 'components/annotation-page/standard-workspace/objects-side-bar/color-changer';
+import Tooltip from 'antd/lib/tooltip';
+import ColorChanger from 'components/annotation-page/standard-workspace/objects-side-bar/color-changer';
+import consts from 'consts';
import {
ObjectOutsideIcon,
FirstIcon,
@@ -35,14 +30,20 @@ import {
BackgroundIcon,
ForegroundIcon,
} from 'icons';
+import { ObjectType, ShapeType } from 'reducers/interfaces';
+import { clamp } from 'utils/math';
-import {
- ObjectType, ShapeType,
-} from 'reducers/interfaces';
function ItemMenu(
serverID: number | undefined,
locked: boolean,
+ objectType: ObjectType,
+ copyShortcut: string,
+ pasteShortcut: string,
+ propagateShortcut: string,
+ toBackgroundShortcut: string,
+ toForegroundShortcut: string,
+ removeShortcut: string,
copy: (() => void),
remove: (() => void),
propagate: (() => void),
@@ -51,54 +52,68 @@ function ItemMenu(
toForeground: (() => void),
): JSX.Element {
return (
-