Skip to content

Commit

Permalink
Context menu using side panel
Browse files Browse the repository at this point in the history
  • Loading branch information
bsekachev committed Feb 5, 2020
1 parent 57741a6 commit d20371b
Show file tree
Hide file tree
Showing 10 changed files with 312 additions and 4 deletions.
3 changes: 3 additions & 0 deletions cvat-canvas/src/typescript/canvasView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,8 @@ export class CanvasViewImpl implements CanvasView, Listener {
});
}
}

e.preventDefault();
}

if (value) {
Expand Down Expand Up @@ -618,6 +620,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
} else if (this.mode === Mode.ZOOM_CANVAS && event.which === 2) {
self.controller.enableDrag(event.clientX, event.clientY);
}
event.preventDefault();
}
});

Expand Down
12 changes: 12 additions & 0 deletions cvat-ui/src/actions/annotation-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,18 @@ export enum AnnotationActionTypes {
UPLOAD_JOB_ANNOTATIONS_FAILED = 'UPLOAD_JOB_ANNOTATIONS_FAILED',
REMOVE_JOB_ANNOTATIONS_SUCCESS = 'REMOVE_JOB_ANNOTATIONS_SUCCESS',
REMOVE_JOB_ANNOTATIONS_FAILED = 'REMOVE_JOB_ANNOTATIONS_FAILED',
UPDATE_CANVAS_CONTEXT_MENU = 'UPDATE_CANVAS_CONTEXT_MENU',
}

export function updateCanvasContextMenu(visible: boolean, left: number, top: number): AnyAction {
return {
type: AnnotationActionTypes.UPDATE_CANVAS_CONTEXT_MENU,
payload: {
visible,
left,
top,
},
};
}

export function removeAnnotationsAsync(sessionInstance: any):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import ReactDOM from 'react-dom';

import ObjectItemContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/object-item';

interface Props {
activatedStateID: number | null;
visible: boolean;
left: number;
top: number;
}

export default function CanvasContextMenu(props: Props): JSX.Element | null {
const {
activatedStateID,
visible,
left,
top,
} = props;

if (!visible || activatedStateID === null) {
return null;
}

return ReactDOM.createPortal(
<div className='cvat-canvas-context-menu' style={{ top, left }}>
<ObjectItemContainer clientID={activatedStateID} />
</div>,
window.document.body,
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ interface Props {
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;
onActivateObject(activatedStateID: number | null): void;
onSelectObjects(selectedStatesID: number[]): void;
onUpdateContextMenu(visible: boolean, left: number, top: number): void;
}

export default class CanvasWrapperComponent extends React.PureComponent<Props> {
Expand Down Expand Up @@ -322,6 +323,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
onZoomCanvas,
onResetCanvas,
onActivateObject,
onUpdateContextMenu,
onEditShape,
} = this.props;

Expand All @@ -342,12 +344,24 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
canvasInstance.grid(gridSize, gridSize);

// Events
canvasInstance.html().addEventListener('click', (e: MouseEvent): void => {
if ((e.target as HTMLElement).tagName === 'svg') {
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('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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import CanvasWrapperContainer from 'containers/annotation-page/standard-workspac
import ControlsSideBarContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/controls-side-bar';
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';

export default function StandardWorkspaceComponent(): JSX.Element {
return (
Expand All @@ -17,6 +18,7 @@ export default function StandardWorkspaceComponent(): JSX.Element {
<CanvasWrapperContainer />
<ObjectSideBarContainer />
<PropagateConfirmContainer />
<CanvasContextMenuContainer />
</Layout>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,17 @@
width: 70px;
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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import React from 'react';

import { connect } from 'react-redux';
import { CombinedState } from 'reducers/interfaces';

import CanvasContextMenuComponent from 'components/annotation-page/standard-workspace/canvas-context-menu';

interface StateToProps {
activatedStateID: number | null;
visible: boolean;
top: number;
left: number;
collapsed: boolean | undefined;
}

function mapStateToProps(state: CombinedState): StateToProps {
const {
annotation: {
annotations: {
activatedStateID,
collapsed,
},
canvas: {
contextMenu: {
visible,
top,
left,
},
},
},
} = state;

return {
activatedStateID,
collapsed: activatedStateID !== null ? collapsed[activatedStateID] : undefined,
visible,
left,
top,
};
}

type Props = StateToProps;

interface State {
latestLeft: number;
latestTop: number;
left: number;
top: number;
}

class CanvasContextMenuContainer extends React.PureComponent<Props, State> {
private initialized: HTMLDivElement | null;
private dragging: boolean;
private dragInitPosX: number;
private dragInitPosY: number;
public constructor(props: Props) {
super(props);

this.initialized = null;
this.dragging = false;
this.dragInitPosX = 0;
this.dragInitPosY = 0;
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 componentDidMount(): void {
this.updatePositionIfOutOfScreen();
window.addEventListener('mousemove', this.moveContextMenu);
}

public componentDidUpdate(prevProps: Props): void {
const { collapsed } = this.props;

const [element] = window.document.getElementsByClassName('cvat-canvas-context-menu');
if (collapsed !== prevProps.collapsed && element) {
element.addEventListener('transitionend', () => {
this.updatePositionIfOutOfScreen();
}, { once: true });
} else if (element) {
this.updatePositionIfOutOfScreen();
}

if (element && (!this.initialized || this.initialized !== element)) {
this.initialized = element as HTMLDivElement;

this.initialized.addEventListener('mousedown', (e: MouseEvent): any => {
this.dragging = true;
this.dragInitPosX = e.clientX;
this.dragInitPosY = e.clientY;
});

this.initialized.addEventListener('mouseup', () => {
this.dragging = false;
});
}
}

public componentWillUnmount(): void {
window.removeEventListener('mousemove', this.moveContextMenu);
}

private moveContextMenu = (e: MouseEvent): void => {
if (this.dragging) {
this.setState((state) => {
const value = {
left: state.left + e.clientX - this.dragInitPosX,
top: state.top + e.clientY - this.dragInitPosY,
};

this.dragInitPosX = e.clientX;
this.dragInitPosY = e.clientY;

return value;
});

e.preventDefault();
}
};

private updatePositionIfOutOfScreen(): void {
const {
top,
left,
} = this.state;

const {
innerWidth,
innerHeight,
} = window;

const [element] = window.document.getElementsByClassName('cvat-canvas-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),
});
}
}
}

public render(): JSX.Element {
const {
left,
top,
} = this.state;

const {
visible,
activatedStateID,
} = this.props;

return (
<CanvasContextMenuComponent
left={left}
top={top}
visible={visible}
activatedStateID={activatedStateID}
/>
);
}
}

export default connect(
mapStateToProps,
)(CanvasContextMenuContainer);
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
splitAnnotationsAsync,
activateObject,
selectObjects,
updateCanvasContextMenu,
} from 'actions/annotation-actions';
import {
ColorBy,
Expand Down Expand Up @@ -68,6 +69,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;
}

function mapStateToProps(state: CombinedState): StateToProps {
Expand Down Expand Up @@ -179,11 +181,18 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
dispatch(splitAnnotationsAsync(sessionInstance, frame, state));
},
onActivateObject(activatedStateID: number | null): void {
if (activatedStateID === null) {
dispatch(updateCanvasContextMenu(false, 0, 0));
}

dispatch(activateObject(activatedStateID));
},
onSelectObjects(selectedStatesID: number[]): void {
dispatch(selectObjects(selectedStatesID));
},
onUpdateContextMenu(visible: boolean, left: number, top: number): void {
dispatch(updateCanvasContextMenu(visible, left, top));
},
};
}

Expand Down
Loading

0 comments on commit d20371b

Please sign in to comment.