Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React UI: Point deletion context menu #1292

Merged
merged 11 commits into from
Mar 24, 2020
31 changes: 29 additions & 2 deletions cvat-canvas/src/typescript/canvasView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,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,
Expand All @@ -478,6 +499,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
});

circle.on('dblclick', dblClickHandler);
circle.on('contextmenu', contextmenuHandler);
circle.addClass('cvat_canvas_selected_point');
});

Expand All @@ -487,6 +509,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
});

circle.off('dblclick', dblClickHandler);
circle.off('contextmenu', contextmenuHandler);
circle.removeClass('cvat_canvas_selected_point');
});

Expand Down Expand Up @@ -900,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);
Expand Down Expand Up @@ -1185,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);
Expand Down
11 changes: 10 additions & 1 deletion cvat-ui/src/actions/annotation-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
Task,
FrameSpeed,
Rotation,
ContextMenuType,
Workspace,
} from 'reducers/interfaces';

Expand Down Expand Up @@ -365,13 +366,21 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
};
}

export function updateCanvasContextMenu(visible: boolean, left: number, top: number): AnyAction {
export function updateCanvasContextMenu(
visible: boolean,
left: number,
top: number,
pointID: number | null = null,
type?: ContextMenuType,
): AnyAction {
return {
type: AnnotationActionTypes.UPDATE_CANVAS_CONTEXT_MENU,
payload: {
visible,
left,
top,
type,
pointID,
},
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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(
<div className='cvat-canvas-point-context-menu' style={{ top, left }}>
<Button type='link' icon='delete' onClick={onPointDelete}>
Delete point
</Button>
</div>,
window.document.body,
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,23 @@

import React from 'react';
import { GlobalHotKeys, KeyMap } from 'react-hotkeys';
import Slider, { SliderValue } from 'antd/lib/slider';
import Layout from 'antd/lib/layout';
import Icon from 'antd/lib/icon';

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 { LogType } from 'cvat-logger';
import { Canvas } from 'cvat-canvas';
import getCore from 'cvat-core';
import {
ColorBy,
GridColor,
ObjectType,
ContextMenuType,
Workspace,
ShapeType,
} from 'reducers/interfaces';
import { LogType } from 'cvat-logger';
import { Canvas } from 'cvat-canvas';
import getCore from 'cvat-core';

const cvat = getCore();

Expand Down Expand Up @@ -51,6 +54,8 @@ interface Props {
contrastLevel: number;
saturationLevel: number;
resetZoom: boolean;
contextVisible: boolean;
contextType: ContextMenuType;
aamZoomMargin: number;
workspace: Workspace;
onSetupCanvas: () => void;
Expand All @@ -69,7 +74,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): void;
onUpdateContextMenu(visible: boolean, left: number, top: number, type: ContextMenuType,
pointID?: number): void;
onAddZLayer(): void;
onSwitchZLayer(cur: number): void;
onChangeBrightnessLevel(level: number): void;
Expand Down Expand Up @@ -223,7 +229,9 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
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);
}
Expand Down Expand Up @@ -327,8 +335,16 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
};

private onCanvasContextMenu = (e: MouseEvent): void => {
const { activatedStateID, onUpdateContextMenu } = this.props;
onUpdateContextMenu(activatedStateID !== null, e.clientX, e.clientY);
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 => {
Expand Down Expand Up @@ -476,6 +492,20 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
}
};

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,
Expand Down Expand Up @@ -619,6 +649,8 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -23,6 +24,7 @@ export default function StandardWorkspaceComponent(): JSX.Element {
<ObjectSideBarContainer />
<PropagateConfirmContainer />
<CanvasContextMenuContainer />
<CanvasPointContextMenuContainer />
</Layout>
);
}
15 changes: 15 additions & 0 deletions cvat-ui/src/components/annotation-page/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
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';

Expand All @@ -14,6 +14,7 @@ interface StateToProps {
visible: boolean;
top: number;
left: number;
type: ContextMenuType;
collapsed: boolean | undefined;
}

Expand All @@ -29,6 +30,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
visible,
top,
left,
type,
},
},
},
Expand All @@ -40,6 +42,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
visible,
left,
top,
type,
};
}

Expand Down Expand Up @@ -175,15 +178,20 @@ class CanvasContextMenuContainer extends React.PureComponent<Props, State> {
const {
visible,
activatedStateID,
type,
} = this.props;

return (
<CanvasContextMenuComponent
left={left}
top={top}
visible={visible}
activatedStateID={activatedStateID}
/>
<>
{ type === ContextMenuType.CANVAS_SHAPE && (
<CanvasContextMenuComponent
left={left}
top={top}
visible={visible}
activatedStateID={activatedStateID}
/>
)}
</>
);
}
}
Expand Down
Loading