You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

242 lines
6.9 KiB
TypeScript

// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React from 'react';
import { connect } from 'react-redux';
import { CombinedState, ContextMenuType, Workspace } from 'reducers/interfaces';
import CanvasContextMenuComponent from 'components/annotation-page/canvas/canvas-context-menu';
import { updateCanvasContextMenu } from 'actions/annotation-actions';
import { reviewActions, finishIssueAsync } from 'actions/review-actions';
import { ThunkDispatch } from 'utils/redux';
interface OwnProps {
readonly: boolean;
}
interface StateToProps {
contextMenuClientID: number | null;
objectStates: any[];
visible: boolean;
top: number;
left: number;
type: ContextMenuType;
collapsed: boolean | undefined;
workspace: Workspace;
latestComments: string[];
}
interface DispatchToProps {
onStartIssue(position: number[]): void;
openIssue(position: number[], message: string): void;
}
function mapStateToProps(state: CombinedState): StateToProps {
const {
annotation: {
annotations: { collapsed, states: objectStates },
canvas: {
contextMenu: {
visible, top, left, type, clientID,
},
ready,
},
workspace,
},
review: { latestComments },
} = state;
return {
contextMenuClientID: clientID,
collapsed: clientID !== null ? collapsed[clientID] : undefined,
objectStates,
visible:
clientID !== null &&
visible &&
ready &&
objectStates.map((_state: any): number => _state.clientID).includes(clientID),
left,
top,
type,
workspace,
latestComments,
};
}
function mapDispatchToProps(dispatch: ThunkDispatch): DispatchToProps {
return {
onStartIssue(position: number[]): void {
dispatch(reviewActions.startIssue(position));
dispatch(updateCanvasContextMenu(false, 0, 0));
},
openIssue(position: number[], message: string): void {
dispatch(reviewActions.startIssue(position));
dispatch(finishIssueAsync(message));
dispatch(updateCanvasContextMenu(false, 0, 0));
},
};
}
type Props = StateToProps & DispatchToProps & OwnProps;
interface State {
latestLeft: number;
latestTop: number;
left: number;
top: number;
}
class CanvasContextMenuContainer extends React.PureComponent<Props, State> {
static defaultProps = {
readonly: false,
};
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,
contextMenuClientID,
objectStates,
type,
readonly,
workspace,
latestComments,
onStartIssue,
openIssue,
} = this.props;
return (
<>
{type === ContextMenuType.CANVAS_SHAPE && (
<CanvasContextMenuComponent
contextMenuClientID={contextMenuClientID}
readonly={readonly}
left={left}
top={top}
visible={visible}
objectStates={objectStates}
workspace={workspace}
latestComments={latestComments}
onStartIssue={onStartIssue}
openIssue={openIssue}
/>
)}
</>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(CanvasContextMenuContainer);