diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts
index 2652cee3..4041973d 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 4ce488f3..b2cf338d 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 f74e2c2c..0aedbdb0 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 e9bfd6c4..5cfcba47 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 5f276eb5..1dd7d769 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 7bd7b9fb..3d0a508c 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 00000000..74d07ff8
--- /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 7d99f592..89f0af7e 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 70eaf594..fcb791d1 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 4c308a7b..1b888405 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;