From 965ff77898d54f40c35d4019dd646493dd66b01e Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Thu, 12 Mar 2020 13:50:08 +0300 Subject: [PATCH 1/8] wip --- cvat-canvas/src/typescript/canvasView.ts | 23 +++++++++++++++++++ .../standard-workspace/canvas-wrapper.tsx | 4 ++++ 2 files changed, 27 insertions(+) diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index d026c5246a78..1ea3bdd6de38 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -453,6 +453,27 @@ export class CanvasViewImpl implements CanvasView, Listener { e.preventDefault(); } + function contextmenuHandler(e: MouseEvent): void { + const pointID = Array.prototype.indexOf + .call(((e.target as HTMLElement).parentElement as HTMLElement).children, e.target); + if (self.activeElement.clientID !== null) { + const [state] = self.controller.objects + .filter((_state: any): boolean => ( + _state.clientID === self.activeElement.clientID + )); + self.canvas.dispatchEvent(new CustomEvent('point.contextmenu', { + bubbles: false, + cancelable: true, + detail: { + mouseEvent: e, + objectState: state, + pointID, + }, + })); + } + e.preventDefault(); + } + if (value) { (shape as any).selectize(value, { deepSelect: true, @@ -475,6 +496,7 @@ export class CanvasViewImpl implements CanvasView, Listener { }); circle.on('dblclick', dblClickHandler); + circle.on('contextmenu', contextmenuHandler); circle.addClass('cvat_canvas_selected_point'); }); @@ -484,6 +506,7 @@ export class CanvasViewImpl implements CanvasView, Listener { }); circle.off('dblclick', dblClickHandler); + circle.off('contextmenu', contextmenuHandler); circle.removeClass('cvat_canvas_selected_point'); }); 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..aaa6645dc08c 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 @@ -516,6 +516,10 @@ export default class CanvasWrapperComponent extends React.PureComponent { 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('point.contextmenu', (event: any) => { + console.log(event); + }); } public render(): JSX.Element { From 4d0c22513ac2215686a11ec3e262fff3f74b8de8 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Mon, 16 Mar 2020 15:43:23 +0300 Subject: [PATCH 2/8] temp --- cvat-ui/src/actions/annotation-actions.ts | 9 ++++- .../canvas-point-context-menu.tsx | 34 +++++++++++++++++++ .../standard-workspace/canvas-wrapper.tsx | 27 ++++++++++++--- .../standard-workspace/styles.scss | 13 +++++++ .../canvas-context-menu.tsx | 7 +++- .../standard-workspace/canvas-wrapper.tsx | 20 +++++++++-- cvat-ui/src/reducers/annotation-reducer.ts | 4 +++ cvat-ui/src/reducers/interfaces.ts | 2 ++ 8 files changed, 107 insertions(+), 9 deletions(-) create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/canvas-point-context-menu.tsx diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 44aeb752ceb9..916ddb7c80bc 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -18,6 +18,7 @@ import { Task, FrameSpeed, Rotation, + ContextMenuType, } from 'reducers/interfaces'; import getCore from 'cvat-core'; @@ -270,13 +271,19 @@ ThunkAction, {}, {}, AnyAction> { }; } -export function updateCanvasContextMenu(visible: boolean, left: number, top: number): AnyAction { +export function updateCanvasContextMenu( + visible: boolean, + left: number, + top: number, + type?: ContextMenuType, +): AnyAction { return { type: AnnotationActionTypes.UPDATE_CANVAS_CONTEXT_MENU, payload: { visible, left, top, + type, }, }; } 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..4ce488f37f76 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-point-context-menu.tsx @@ -0,0 +1,34 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import ReactDOM from 'react-dom'; + + +interface Props { + activatedStateID: number | null; + visible: boolean; + left: number; + top: number; +} + +export default function CanvasPointContextMenu(props: Props): JSX.Element | null { + const { + activatedStateID, + visible, + left, + top, + } = props; + + if (!visible || activatedStateID === null) { + return null; + } + + return ReactDOM.createPortal( +
+ Haha +
, + 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 aaa6645dc08c..bbb0aab26afd 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 @@ -13,7 +13,12 @@ import { } from 'antd'; import { SliderValue } from 'antd/lib//slider'; -import { ColorBy, GridColor, ObjectType } from 'reducers/interfaces'; +import { + ColorBy, + GridColor, + ObjectType, + ContextMenuType, +} from 'reducers/interfaces'; import { Canvas } from 'cvat-canvas'; import getCore from 'cvat-core'; @@ -48,6 +53,8 @@ interface Props { contrastLevel: number; saturationLevel: number; resetZoom: boolean; + contextVisible: boolean; + contextType: ContextMenuType; onSetupCanvas: () => void; onDragCanvas: (enabled: boolean) => void; onZoomCanvas: (enabled: boolean) => void; @@ -64,7 +71,7 @@ interface Props { 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): void; onAddZLayer(): void; onSwitchZLayer(cur: number): void; onChangeBrightnessLevel(level: number): void; @@ -414,9 +421,14 @@ export default class CanvasWrapperComponent extends React.PureComponent { canvasInstance.html().addEventListener('contextmenu', (e: MouseEvent): void => { const { activatedStateID, + contextType, + contextVisible, } = this.props; - onUpdateContextMenu(activatedStateID !== null, e.clientX, e.clientY); + if (!(contextVisible && contextType === ContextMenuType.CANVAS_SHAPE_POINT)) { + onUpdateContextMenu(activatedStateID !== null, e.clientX, e.clientY, + ContextMenuType.CANVAS_SHAPE); + } }); canvasInstance.html().addEventListener('canvas.editstart', (): void => { @@ -518,7 +530,14 @@ export default class CanvasWrapperComponent extends React.PureComponent { canvasInstance.html().addEventListener('canvas.splitted', this.onTrackSplitted.bind(this)); canvasInstance.html().addEventListener('point.contextmenu', (event: any) => { - console.log(event); + // const { + // activatedStateID, + // } = this.props; + + // console.log(event); + + // onUpdateContextMenu(activatedStateID !== null, event.detail.mouseEvent.clientX, + // event.detail.mouseEvent.clientY, ContextMenuType.CANVAS_SHAPE_POINT); }); } diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss index ed07a9056f3c..2ac89546e964 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss +++ b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss @@ -129,6 +129,19 @@ } } +.cvat-canvas-point-context-menu { + opacity: 0.6; + position: fixed; + width: 100px; + z-index: 10; + max-height: 50%; + overflow-y: auto; + + &:hover { + opacity: 1; + } +} + .cvat-canvas-z-axis-wrapper { position: absolute; background: $background-color-2; diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-context-menu.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-context-menu.tsx index fd2ff9805688..7bd7b9fbc40d 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-context-menu.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-context-menu.tsx @@ -5,15 +5,17 @@ import React from 'react'; import { connect } from 'react-redux'; -import { CombinedState } from 'reducers/interfaces'; +import { CombinedState, ContextMenuType } from 'reducers/interfaces'; import CanvasContextMenuComponent from 'components/annotation-page/standard-workspace/canvas-context-menu'; +import CanvasPointContextMenuComponent from 'components/annotation-page/standard-workspace/canvas-point-context-menu'; interface StateToProps { activatedStateID: number | null; visible: boolean; top: number; left: number; + type: ContextMenuType; collapsed: boolean | undefined; } @@ -29,6 +31,7 @@ function mapStateToProps(state: CombinedState): StateToProps { visible, top, left, + type, }, }, }, @@ -40,6 +43,7 @@ function mapStateToProps(state: CombinedState): StateToProps { visible, left, top, + type, }; } @@ -175,6 +179,7 @@ class CanvasContextMenuContainer extends React.PureComponent { const { visible, activatedStateID, + type, } = this.props; return ( diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx index 0625bece9136..e52d999447f7 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -39,6 +39,7 @@ import { GridColor, ObjectType, CombinedState, + ContextMenuType, } from 'reducers/interfaces'; import { Canvas } from 'cvat-canvas'; @@ -70,6 +71,8 @@ interface StateToProps { minZLayer: number; maxZLayer: number; curZLayer: number; + contextVisible: boolean; + contextType: ContextMenuType; } interface DispatchToProps { @@ -89,7 +92,7 @@ interface DispatchToProps { 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): void; onAddZLayer(): void; onSwitchZLayer(cur: number): void; onChangeBrightnessLevel(level: number): void; @@ -104,6 +107,10 @@ function mapStateToProps(state: CombinedState): StateToProps { const { annotation: { canvas: { + contextMenu: { + visible: contextVisible, + type: contextType, + }, instance: canvasInstance, }, drawing: { @@ -179,6 +186,8 @@ function mapStateToProps(state: CombinedState): StateToProps { curZLayer, minZLayer, maxZLayer, + contextVisible, + contextType, }; } @@ -236,8 +245,13 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { onSelectObjects(selectedStatesID: number[]): void { dispatch(selectObjects(selectedStatesID)); }, - onUpdateContextMenu(visible: boolean, left: number, top: number): void { - dispatch(updateCanvasContextMenu(visible, left, top)); + onUpdateContextMenu( + visible: boolean, + left: number, + top: number, + type: ContextMenuType, + ): void { + dispatch(updateCanvasContextMenu(visible, left, top, type)); }, onAddZLayer(): void { dispatch(addZLayer()); diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index d5de5f6009cc..550a61e4f193 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -12,6 +12,7 @@ import { ActiveControl, ShapeType, ObjectType, + ContextMenuType, } from './interfaces'; const defaultState: AnnotationState = { @@ -23,6 +24,7 @@ const defaultState: AnnotationState = { visible: false, left: 0, top: 0, + type: ContextMenuType.CANVAS_SHAPE, }, instance: new Canvas(), ready: false, @@ -923,6 +925,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { visible, left, top, + type, } = action.payload; return { @@ -934,6 +937,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { visible, left, top, + type, }, }, }; diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index 74e36d86c569..f7bf71318973 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -282,6 +282,7 @@ export enum StatesOrdering { export enum ContextMenuType { CANVAS = 'canvas', CANVAS_SHAPE = 'canvas_shape', + CANVAS_SHAPE_POINT = 'canvas_shape_point', } export enum Rotation { @@ -301,6 +302,7 @@ export interface AnnotationState { visible: boolean; top: number; left: number; + type: ContextMenuType; }; instance: Canvas; ready: boolean; From 845be3b48601cb72664a0db8ae9e66f1dfff16bb Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Thu, 19 Mar 2020 02:03:42 +0300 Subject: [PATCH 3/8] Added point deletion context menu --- cvat-ui/src/actions/annotation-actions.ts | 2 + .../canvas-point-context-menu.tsx | 9 +- .../standard-workspace/canvas-wrapper.tsx | 36 +++- .../standard-workspace/standard-workspace.tsx | 2 + .../standard-workspace/styles.scss | 4 +- .../canvas-context-menu.tsx | 17 +- .../canvas-point-context-menu.tsx | 180 ++++++++++++++++++ .../standard-workspace/canvas-wrapper.tsx | 13 +- cvat-ui/src/reducers/annotation-reducer.ts | 3 + cvat-ui/src/reducers/interfaces.ts | 1 + 10 files changed, 240 insertions(+), 27 deletions(-) create mode 100644 cvat-ui/src/containers/annotation-page/standard-workspace/canvas-point-context-menu.tsx diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 2652cee33edf..4041973dc317 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -318,6 +318,7 @@ export function updateCanvasContextMenu( left: number, top: number, type?: ContextMenuType, + pointID?: number, ): AnyAction { return { type: AnnotationActionTypes.UPDATE_CANVAS_CONTEXT_MENU, @@ -326,6 +327,7 @@ export function updateCanvasContextMenu( left, top, type, + pointID, }, }; } 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 index 4ce488f37f76..b2cf338d2637 100644 --- 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 @@ -5,16 +5,21 @@ 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, @@ -27,7 +32,9 @@ export default function CanvasPointContextMenu(props: Props): JSX.Element | null return ReactDOM.createPortal(
- Haha +
, 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 f74e2c2cb181..0aedbdb09d5a 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 @@ -18,16 +18,10 @@ import { GridColor, ObjectType, ContextMenuType, - Workspace + Workspace, } from 'reducers/interfaces'; import { Canvas } from 'cvat-canvas'; import getCore from 'cvat-core'; -import { - ColorBy, - GridColor, - ObjectType, - Workspace, -} from 'reducers/interfaces'; const cvat = getCore(); @@ -81,7 +75,8 @@ interface Props { onSplitAnnotations(sessionInstance: any, frame: number, state: any): void; onActivateObject(activatedStateID: number | null): void; onSelectObjects(selectedStatesID: number[]): void; - onUpdateContextMenu(visible: boolean, left: number, top: number, type: ContextMenuType): void; + onUpdateContextMenu(visible: boolean, left: number, top: number, type: ContextMenuType, + pointID?: number): void; onAddZLayer(): void; onSwitchZLayer(cur: number): void; onChangeBrightnessLevel(level: number): void; @@ -333,8 +328,17 @@ export default class CanvasWrapperComponent extends React.PureComponent { }; private onCanvasContextMenu = (e: MouseEvent): void => { - const { activatedStateID, onUpdateContextMenu } = this.props; - onUpdateContextMenu(activatedStateID !== null, e.clientX, e.clientY); + const { + activatedStateID, + onUpdateContextMenu, + contextVisible, + contextType, + } = this.props; + + if (!contextVisible && contextType !== ContextMenuType.CANVAS_SHAPE_POINT) { + onUpdateContextMenu(activatedStateID !== null, e.clientX, e.clientY, + ContextMenuType.CANVAS_SHAPE); + } }; private onCanvasShapeClicked = (e: any): void => { @@ -460,6 +464,16 @@ export default class CanvasWrapperComponent extends React.PureComponent { } }; + private onCanvasPointContextMenu = (e: any): void => { + const { + activatedStateID, + onUpdateContextMenu, + } = this.props; + + onUpdateContextMenu(activatedStateID !== null, e.detail.mouseEvent.clientX, + e.detail.mouseEvent.clientY, ContextMenuType.CANVAS_SHAPE_POINT, e.detail.pointID); + }; + private activateOnCanvas(): void { const { activatedStateID, @@ -599,6 +613,8 @@ export default class CanvasWrapperComponent extends React.PureComponent { 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 { diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx index e9bfd6c4e003..5cfcba47bec3 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx @@ -14,6 +14,7 @@ import ControlsSideBarContainer from 'containers/annotation-page/standard-worksp import ObjectSideBarContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/objects-side-bar'; import PropagateConfirmContainer from 'containers/annotation-page/standard-workspace/propagate-confirm'; import CanvasContextMenuContainer from 'containers/annotation-page/standard-workspace/canvas-context-menu'; +import CanvasPointContextMenuContainer from 'containers/annotation-page/standard-workspace/canvas-point-context-menu'; export default function StandardWorkspaceComponent(): JSX.Element { return ( @@ -23,6 +24,7 @@ export default function StandardWorkspaceComponent(): JSX.Element { + ); } diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss index 5f276eb50efc..1dd7d769200f 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss +++ b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss @@ -136,10 +136,12 @@ .cvat-canvas-point-context-menu { opacity: 0.6; position: fixed; - width: 100px; + width: 135px; z-index: 10; max-height: 50%; overflow-y: auto; + background-color: #ffffff; + border-radius: 4px; &:hover { opacity: 1; diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-context-menu.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-context-menu.tsx index 7bd7b9fbc40d..3d0a508c03cb 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-context-menu.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-context-menu.tsx @@ -8,7 +8,6 @@ import { connect } from 'react-redux'; import { CombinedState, ContextMenuType } from 'reducers/interfaces'; import CanvasContextMenuComponent from 'components/annotation-page/standard-workspace/canvas-context-menu'; -import CanvasPointContextMenuComponent from 'components/annotation-page/standard-workspace/canvas-point-context-menu'; interface StateToProps { activatedStateID: number | null; @@ -183,12 +182,16 @@ class CanvasContextMenuContainer extends React.PureComponent { } = this.props; return ( - + <> + { type === ContextMenuType.CANVAS_SHAPE && ( + + )} + ); } } diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-point-context-menu.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-point-context-menu.tsx new file mode 100644 index 000000000000..74d07ff83266 --- /dev/null +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-point-context-menu.tsx @@ -0,0 +1,180 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { connect } from 'react-redux'; +import { CombinedState, ContextMenuType } from 'reducers/interfaces'; + +import { updateAnnotationsAsync, updateCanvasContextMenu } from 'actions/annotation-actions'; + +import CanvasPointContextMenuComponent from 'components/annotation-page/standard-workspace/canvas-point-context-menu'; + +interface StateToProps { + activatedStateID: number | null; + activetedPointID: number | null | undefined; + states: any[]; + visible: boolean; + top: number; + left: number; + type: ContextMenuType; +} + +function mapStateToProps(state: CombinedState): StateToProps { + const { + annotation: { + annotations: { + states, + activatedStateID, + }, + canvas: { + contextMenu: { + visible, + top, + left, + type, + pointID: activetedPointID, + }, + }, + }, + } = state; + + return { + activatedStateID, + activetedPointID, + states, + visible, + left, + top, + type, + }; +} + +interface DispatchToProps { + onUpdateAnnotations(states: any[]): void; + onCloseContextMenu(): void; +} + +function mapDispatchToProps(dispatch: any): DispatchToProps { + return { + onUpdateAnnotations(states: any[]): void { + dispatch(updateAnnotationsAsync(states)); + }, + onCloseContextMenu(): void { + dispatch(updateCanvasContextMenu(false, 0, 0)); + }, + }; +} + +type Props = StateToProps & DispatchToProps; + +interface State { + latestLeft: number; + latestTop: number; + left: number; + top: number; +} + +class CanvasContextMenuContainer extends React.PureComponent { + public constructor(props: Props) { + super(props); + + this.state = { + latestLeft: 0, + latestTop: 0, + left: 0, + top: 0, + }; + } + + static getDerivedStateFromProps(props: Props, state: State): State | null { + if (props.left === state.latestLeft + && props.top === state.latestTop) { + return null; + } + + return { + ...state, + latestLeft: props.left, + latestTop: props.top, + top: props.top, + left: props.left, + }; + } + + public componentDidUpdate(): void { + const { + top, + left, + } = this.state; + + const { + innerWidth, + innerHeight, + } = window; + + const [element] = window.document.getElementsByClassName('cvat-canvas-point-context-menu'); + if (element) { + const height = element.clientHeight; + const width = element.clientWidth; + + if (top + height > innerHeight || left + width > innerWidth) { + this.setState({ + top: top - Math.max(top + height - innerHeight, 0), + left: left - Math.max(left + width - innerWidth, 0), + }); + } + } + } + + private deletePoint(): void { + const { + activetedPointID, + activatedStateID, + states, + onUpdateAnnotations, + onCloseContextMenu, + } = this.props; + + const [objectState] = states.filter((e) => (e.clientID === activatedStateID)); + if (activetedPointID) { + objectState.points = objectState.points.slice(0, activetedPointID * 2) + .concat(objectState.points.slice(activetedPointID * 2 + 2)); + onUpdateAnnotations([objectState]); + onCloseContextMenu(); + } + } + + public render(): JSX.Element { + const { + visible, + activatedStateID, + type, + } = this.props; + + const { + top, + left, + } = this.state; + + return ( + <> + {type === ContextMenuType.CANVAS_SHAPE_POINT && ( + this.deletePoint()} + /> + )} + + ); + } +} + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(CanvasContextMenuContainer); diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx index 7d99f5926e4b..89f0af7e4e25 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -96,7 +96,8 @@ interface DispatchToProps { onSplitAnnotations(sessionInstance: any, frame: number, state: any): void; onActivateObject: (activatedStateID: number | null) => void; onSelectObjects: (selectedStatesID: number[]) => void; - onUpdateContextMenu(visible: boolean, left: number, top: number, type: ContextMenuType): void; + onUpdateContextMenu(visible: boolean, left: number, top: number, type: ContextMenuType, + pointID?: number): void; onAddZLayer(): void; onSwitchZLayer(cur: number): void; onChangeBrightnessLevel(level: number): void; @@ -257,13 +258,9 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { onSelectObjects(selectedStatesID: number[]): void { dispatch(selectObjects(selectedStatesID)); }, - onUpdateContextMenu( - visible: boolean, - left: number, - top: number, - type: ContextMenuType, - ): void { - dispatch(updateCanvasContextMenu(visible, left, top, type)); + onUpdateContextMenu(visible: boolean, left: number, top: number, + type: ContextMenuType, pointID?: number): void { + dispatch(updateCanvasContextMenu(visible, left, top, type, pointID)); }, onAddZLayer(): void { dispatch(addZLayer()); diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index 70eaf5942395..fcb791d1a1a8 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -26,6 +26,7 @@ const defaultState: AnnotationState = { left: 0, top: 0, type: ContextMenuType.CANVAS_SHAPE, + pointID: null, }, instance: new Canvas(), ready: false, @@ -934,6 +935,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { left, top, type, + pointID, } = action.payload; return { @@ -946,6 +948,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { left, top, type, + pointID, }, }, }; diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index 4c308a7b0e81..1b8884059c2b 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -303,6 +303,7 @@ export interface AnnotationState { top: number; left: number; type: ContextMenuType; + pointID: number | null | undefined; }; instance: Canvas; ready: boolean; From 1c1ab6a0e59b3ee8f0377a55403392a91fe0ce05 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Fri, 20 Mar 2020 12:31:33 +0300 Subject: [PATCH 4/8] fixed point context menu for rectangles --- .../standard-workspace/canvas-wrapper.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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 0aedbdb09d5a..41d8acc4c09a 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 @@ -19,6 +19,7 @@ import { ObjectType, ContextMenuType, Workspace, + ShapeType, } from 'reducers/interfaces'; import { Canvas } from 'cvat-canvas'; import getCore from 'cvat-core'; @@ -468,10 +469,14 @@ export default class CanvasWrapperComponent extends React.PureComponent { const { activatedStateID, onUpdateContextMenu, + annotations, } = this.props; - onUpdateContextMenu(activatedStateID !== null, e.detail.mouseEvent.clientX, - e.detail.mouseEvent.clientY, ContextMenuType.CANVAS_SHAPE_POINT, e.detail.pointID); + 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 { From 56f58b66642a63431458003e0631c0ee3fe1d38e Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Mon, 23 Mar 2020 14:09:56 +0300 Subject: [PATCH 5/8] Fixed context menu on ubuntu --- cvat-canvas/src/typescript/canvasView.ts | 6 +++++- .../annotation-page/standard-workspace/canvas-wrapper.tsx | 6 ++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 297ab2c1d86b..58e152ceef22 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -1208,7 +1208,11 @@ export class CanvasViewImpl implements CanvasView, Listener { let shapeSizeElement: ShapeSizeElement | null = null; let resized = false; - (shape as any).resize().on('resizestart', (): void => { + (shape as any).resize().on('resizestart', (e: any): void => { + if (e.detail.event.detail.event.button === 2) { + e.preventDefault(); + return; + } this.mode = Mode.RESIZE; if (state.shapeType === 'rectangle') { shapeSizeElement = displayShapeSize(this.adoptedContent, this.adoptedText); 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 4922a6d1031a..76d429f7949a 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 @@ -12,7 +12,7 @@ import { Tooltip, } from 'antd'; -import { SliderValue } from 'antd/lib//slider'; +import { SliderValue } from 'antd/lib/slider'; import { ColorBy, GridColor, @@ -232,7 +232,9 @@ export default class CanvasWrapperComponent extends React.PureComponent { canvasInstance.html().removeEventListener('canvas.drawn', this.onCanvasShapeDrawn); canvasInstance.html().removeEventListener('canvas.merged', this.onCanvasObjectsMerged); canvasInstance.html().removeEventListener('canvas.groupped', this.onCanvasObjectsGroupped); - canvasInstance.html().addEventListener('canvas.splitted', this.onCanvasTrackSplitted); + canvasInstance.html().removeEventListener('canvas.splitted', this.onCanvasTrackSplitted); + + canvasInstance.html().removeEventListener('point.contextmenu', this.onCanvasPointContextMenu); window.removeEventListener('resize', this.fitCanvas); } From 8aaa4d26d50fb13576378fac911bacef6ef3d16c Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 23 Mar 2020 18:09:06 +0300 Subject: [PATCH 6/8] Fixed deleting of the latest point --- cvat-canvas/src/typescript/canvasView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 58e152ceef22..0074f281a69a 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -923,7 +923,7 @@ export class CanvasViewImpl implements CanvasView, Listener { this.activate(activeElement); } - if (state.points + if (state.points.length !== drawnState.points.length || state.points .some((p: number, id: number): boolean => p !== drawnState.points[id]) ) { const translatedPoints: number[] = translate(state.points); From c11cc642dfa8bba8324a35283675390b516dd84f Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Mon, 23 Mar 2020 18:20:53 +0300 Subject: [PATCH 7/8] fixes --- .../standard-workspace/canvas-wrapper.tsx | 3 +- .../canvas-point-context-menu.tsx | 51 ++++++++++++------- 2 files changed, 33 insertions(+), 21 deletions(-) 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 76d429f7949a..7592769718e2 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 @@ -341,11 +341,10 @@ export default class CanvasWrapperComponent extends React.PureComponent { const { activatedStateID, onUpdateContextMenu, - contextVisible, contextType, } = this.props; - if (!contextVisible && contextType !== ContextMenuType.CANVAS_SHAPE_POINT) { + if (contextType !== ContextMenuType.CANVAS_SHAPE_POINT) { onUpdateContextMenu(activatedStateID !== null, e.clientX, e.clientY, ContextMenuType.CANVAS_SHAPE); } diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-point-context-menu.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-point-context-menu.tsx index 74d07ff83266..59786d8d98bb 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-point-context-menu.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-point-context-menu.tsx @@ -13,7 +13,7 @@ import CanvasPointContextMenuComponent from 'components/annotation-page/standard interface StateToProps { activatedStateID: number | null; - activetedPointID: number | null | undefined; + activatedPointID: number | null | undefined; states: any[]; visible: boolean; top: number; @@ -34,7 +34,7 @@ function mapStateToProps(state: CombinedState): StateToProps { top, left, type, - pointID: activetedPointID, + pointID: activatedPointID, }, }, }, @@ -42,7 +42,7 @@ function mapStateToProps(state: CombinedState): StateToProps { return { activatedStateID, - activetedPointID, + activatedPointID, states, visible, left, @@ -70,6 +70,8 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { type Props = StateToProps & DispatchToProps; interface State { + activatedStateID: number | null | undefined; + activatedPointID: number | null | undefined; latestLeft: number; latestTop: number; left: number; @@ -81,6 +83,8 @@ class CanvasContextMenuContainer extends React.PureComponent { super(props); this.state = { + activatedStateID: null, + activatedPointID: null, latestLeft: 0, latestTop: 0, left: 0, @@ -88,19 +92,25 @@ class CanvasContextMenuContainer extends React.PureComponent { }; } - static getDerivedStateFromProps(props: Props, state: State): State | null { - if (props.left === state.latestLeft - && props.top === state.latestTop) { - return null; + static getDerivedStateFromProps(props: Props, state: State): State { + const newState: State = { ...state }; + + if (props.left !== state.latestLeft + || props.top !== state.latestTop) { + newState.latestLeft = props.left; + newState.latestTop = props.top; + newState.top = props.top; + newState.left = props.left; } - return { - ...state, - latestLeft: props.left, - latestTop: props.top, - top: props.top, - left: props.left, - }; + if (typeof state.activatedStateID !== typeof props.activatedStateID + || typeof state.activatedPointID !== typeof props.activatedPointID) { + newState.activatedStateID = props.activatedStateID; + newState.activatedPointID = props.activatedPointID; + } + + + return newState; } public componentDidUpdate(): void { @@ -130,17 +140,20 @@ class CanvasContextMenuContainer extends React.PureComponent { private deletePoint(): void { const { - activetedPointID, - activatedStateID, states, onUpdateAnnotations, onCloseContextMenu, } = this.props; + const { + activatedStateID, + activatedPointID, + } = this.state; + const [objectState] = states.filter((e) => (e.clientID === activatedStateID)); - if (activetedPointID) { - objectState.points = objectState.points.slice(0, activetedPointID * 2) - .concat(objectState.points.slice(activetedPointID * 2 + 2)); + if (typeof activatedPointID === 'number') { + objectState.points = objectState.points.slice(0, activatedPointID * 2) + .concat(objectState.points.slice(activatedPointID * 2 + 2)); onUpdateAnnotations([objectState]); onCloseContextMenu(); } From 3c128b2a73ce265ff3c30e0f93251cc17294ba2b Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Tue, 24 Mar 2020 15:59:38 +0300 Subject: [PATCH 8/8] fixed PR --- cvat-ui/src/actions/annotation-actions.ts | 2 +- .../standard-workspace/canvas-wrapper.tsx | 11 +-- .../standard-workspace/styles.scss | 74 ------------------- .../components/annotation-page/styles.scss | 15 ++++ .../canvas-point-context-menu.tsx | 10 +-- .../standard-workspace/canvas-wrapper.tsx | 2 +- cvat-ui/src/reducers/interfaces.ts | 2 +- 7 files changed, 27 insertions(+), 89 deletions(-) diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 79ebaaa00ede..891d0fde7a19 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -370,8 +370,8 @@ export function updateCanvasContextMenu( visible: boolean, left: number, top: number, + pointID: number | null = null, type?: ContextMenuType, - pointID?: number, ): AnyAction { return { type: AnnotationActionTypes.UPDATE_CANVAS_CONTEXT_MENU, 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 7592769718e2..4d55d31b1047 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 @@ -5,14 +5,11 @@ import React from 'react'; import { GlobalHotKeys, KeyMap } from 'react-hotkeys'; -import { - Layout, - Slider, - Icon, - Tooltip, -} from 'antd'; +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 { SliderValue } from 'antd/lib/slider'; import { ColorBy, GridColor, diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss index 1dd7d769200f..e731a27f46ec 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss +++ b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss @@ -119,77 +119,3 @@ margin: 0px 5px; } } - -.cvat-canvas-context-menu { - opacity: 0.6; - position: fixed; - width: 300px; - z-index: 10; - max-height: 50%; - overflow-y: auto; - - &:hover { - opacity: 1; - } -} - -.cvat-canvas-point-context-menu { - opacity: 0.6; - position: fixed; - width: 135px; - z-index: 10; - max-height: 50%; - overflow-y: auto; - background-color: #ffffff; - border-radius: 4px; - - &:hover { - opacity: 1; - } -} - -.cvat-canvas-z-axis-wrapper { - position: absolute; - background: $background-color-2; - bottom: 10px; - right: 10px; - height: 150px; - z-index: 100; - border-radius: 6px; - opacity: 0.5; - border: 1px solid $border-color-3; - display: flex; - flex-direction: column; - justify-content: space-between; - padding: 3px; - - &:hover { - opacity: 1; - } - - > .ant-slider { - height: 75%; - margin: 5px 3px; - - > .ant-slider-rail { - background-color: #979797; - } - - > .ant-slider-handle { - transform: none !important; - } - } - - > i { - opacity: 0.7; - color: $objects-bar-icons-color; - - &:hover { - opacity: 1; - } - - &:active { - opacity: 0.7; - } - } -} diff --git a/cvat-ui/src/components/annotation-page/styles.scss b/cvat-ui/src/components/annotation-page/styles.scss index ac89b3032837..005ff868fbd3 100644 --- a/cvat-ui/src/components/annotation-page/styles.scss +++ b/cvat-ui/src/components/annotation-page/styles.scss @@ -229,6 +229,21 @@ } } +.cvat-canvas-point-context-menu { + opacity: 0.6; + position: fixed; + width: 135px; + z-index: 10; + max-height: 50%; + overflow-y: auto; + background-color: #ffffff; + border-radius: 4px; + + &:hover { + opacity: 1; + } +} + .cvat-canvas-z-axis-wrapper { position: absolute; background: $background-color-2; diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-point-context-menu.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-point-context-menu.tsx index 59786d8d98bb..5db5363c037a 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-point-context-menu.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-point-context-menu.tsx @@ -13,7 +13,7 @@ import CanvasPointContextMenuComponent from 'components/annotation-page/standard interface StateToProps { activatedStateID: number | null; - activatedPointID: number | null | undefined; + activatedPointID: number | null; states: any[]; visible: boolean; top: number; @@ -70,15 +70,15 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { type Props = StateToProps & DispatchToProps; interface State { - activatedStateID: number | null | undefined; - activatedPointID: number | null | undefined; + activatedStateID: number | null; + activatedPointID: number | null; latestLeft: number; latestTop: number; left: number; top: number; } -class CanvasContextMenuContainer extends React.PureComponent { +class CanvasPointContextMenuContainer extends React.PureComponent { public constructor(props: Props) { super(props); @@ -190,4 +190,4 @@ class CanvasContextMenuContainer extends React.PureComponent { export default connect( mapStateToProps, mapDispatchToProps, -)(CanvasContextMenuContainer); +)(CanvasPointContextMenuContainer); diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx index 89f0af7e4e25..75645fc202d5 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -260,7 +260,7 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { }, onUpdateContextMenu(visible: boolean, left: number, top: number, type: ContextMenuType, pointID?: number): void { - dispatch(updateCanvasContextMenu(visible, left, top, type, pointID)); + dispatch(updateCanvasContextMenu(visible, left, top, pointID, type)); }, onAddZLayer(): void { dispatch(addZLayer()); diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index 3eb393e3e4e7..5fb1fd02d9b4 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -304,7 +304,7 @@ export interface AnnotationState { top: number; left: number; type: ContextMenuType; - pointID: number | null | undefined; + pointID: number | null; }; instance: Canvas; ready: boolean;