Skip to content

Commit 939de86

Browse files
authored
React UI: Added annotation menus, added shape context menu, added some confirmations before dangerous actions (#1123)
* Annotation menu, modified tasks menu * Removed extra styles * Context menu using side panel * Mousewheel on draw * Added more cursor icons * Do not check .svg & .scss by eslint
1 parent 42614c2 commit 939de86

31 files changed

+1341
-463
lines changed

cvat-canvas/src/typescript/canvasView.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,8 @@ export class CanvasViewImpl implements CanvasView, Listener {
445445
});
446446
}
447447
}
448+
449+
e.preventDefault();
448450
}
449451

450452
if (value) {
@@ -615,9 +617,10 @@ export class CanvasViewImpl implements CanvasView, Listener {
615617
if ([1, 2].includes(event.which)) {
616618
if ([Mode.DRAG_CANVAS, Mode.IDLE].includes(this.mode)) {
617619
self.controller.enableDrag(event.clientX, event.clientY);
618-
} else if (this.mode === Mode.ZOOM_CANVAS && event.which === 2) {
620+
} else if ([Mode.ZOOM_CANVAS, Mode.DRAW].includes(this.mode) && event.which === 2) {
619621
self.controller.enableDrag(event.clientX, event.clientY);
620622
}
623+
event.preventDefault();
621624
}
622625
});
623626

@@ -751,25 +754,37 @@ export class CanvasViewImpl implements CanvasView, Listener {
751754
} else if (reason === UpdateReasons.DRAW) {
752755
const data: DrawData = this.controller.drawData;
753756
if (data.enabled) {
757+
this.canvas.style.cursor = 'crosshair';
754758
this.mode = Mode.DRAW;
759+
} else {
760+
this.canvas.style.cursor = '';
755761
}
756762
this.drawHandler.draw(data, this.geometry);
757763
} else if (reason === UpdateReasons.MERGE) {
758764
const data: MergeData = this.controller.mergeData;
759765
if (data.enabled) {
766+
this.canvas.style.cursor = 'copy';
760767
this.mode = Mode.MERGE;
768+
} else {
769+
this.canvas.style.cursor = '';
761770
}
762771
this.mergeHandler.merge(data);
763772
} else if (reason === UpdateReasons.SPLIT) {
764773
const data: SplitData = this.controller.splitData;
765774
if (data.enabled) {
775+
this.canvas.style.cursor = 'copy';
766776
this.mode = Mode.SPLIT;
777+
} else {
778+
this.canvas.style.cursor = '';
767779
}
768780
this.splitHandler.split(data);
769781
} else if (reason === UpdateReasons.GROUP) {
770782
const data: GroupData = this.controller.groupData;
771783
if (data.enabled) {
784+
this.canvas.style.cursor = 'copy';
772785
this.mode = Mode.GROUP;
786+
} else {
787+
this.canvas.style.cursor = '';
773788
}
774789
this.groupHandler.group(data);
775790
} else if (reason === UpdateReasons.SELECT) {

cvat-canvas/src/typescript/splitHandler.ts

+7-4
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ export class SplitHandlerImpl implements SplitHandler {
2727
private release(): void {
2828
if (this.initialized) {
2929
this.resetShape();
30-
this.canvas.node.removeEventListener('mousemove', this.onFindObject);
30+
this.canvas.node.removeEventListener('mousemove', this.findObject);
3131
this.initialized = false;
3232
}
3333
}
3434

3535
private initSplitting(): void {
36-
this.canvas.node.addEventListener('mousemove', this.onFindObject);
36+
this.canvas.node.addEventListener('mousemove', this.findObject);
3737
this.initialized = true;
3838
this.splitDone = false;
3939
}
@@ -47,6 +47,11 @@ export class SplitHandlerImpl implements SplitHandler {
4747
this.release();
4848
}
4949

50+
private findObject = (e: MouseEvent): void => {
51+
this.resetShape();
52+
this.onFindObject(e);
53+
};
54+
5055
public constructor(
5156
onSplitDone: (object: any) => void,
5257
onFindObject: (event: MouseEvent) => void,
@@ -83,8 +88,6 @@ export class SplitHandlerImpl implements SplitHandler {
8388
once: true,
8489
});
8590
}
86-
} else {
87-
this.resetShape();
8891
}
8992
}
9093

cvat-ui/.eslintrc.js

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ module.exports = {
1717
'@typescript-eslint',
1818
'import',
1919
],
20+
'ignorePatterns': ['*.svg', '*.scss'],
2021
'extends': [
2122
'plugin:@typescript-eslint/recommended',
2223
'airbnb-typescript',

cvat-ui/src/actions/annotation-actions.ts

+99
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,105 @@ export enum AnnotationActionTypes {
6868
CHANGE_JOB_STATUS = 'CHANGE_JOB_STATUS',
6969
CHANGE_JOB_STATUS_SUCCESS = 'CHANGE_JOB_STATUS_SUCCESS',
7070
CHANGE_JOB_STATUS_FAILED = 'CHANGE_JOB_STATUS_FAILED',
71+
UPLOAD_JOB_ANNOTATIONS = 'UPLOAD_JOB_ANNOTATIONS',
72+
UPLOAD_JOB_ANNOTATIONS_SUCCESS = 'UPLOAD_JOB_ANNOTATIONS_SUCCESS',
73+
UPLOAD_JOB_ANNOTATIONS_FAILED = 'UPLOAD_JOB_ANNOTATIONS_FAILED',
74+
REMOVE_JOB_ANNOTATIONS_SUCCESS = 'REMOVE_JOB_ANNOTATIONS_SUCCESS',
75+
REMOVE_JOB_ANNOTATIONS_FAILED = 'REMOVE_JOB_ANNOTATIONS_FAILED',
76+
UPDATE_CANVAS_CONTEXT_MENU = 'UPDATE_CANVAS_CONTEXT_MENU',
77+
}
78+
79+
export function updateCanvasContextMenu(visible: boolean, left: number, top: number): AnyAction {
80+
return {
81+
type: AnnotationActionTypes.UPDATE_CANVAS_CONTEXT_MENU,
82+
payload: {
83+
visible,
84+
left,
85+
top,
86+
},
87+
};
88+
}
89+
90+
export function removeAnnotationsAsync(sessionInstance: any):
91+
ThunkAction<Promise<void>, {}, {}, AnyAction> {
92+
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
93+
try {
94+
sessionInstance.annotations.clear();
95+
dispatch({
96+
type: AnnotationActionTypes.REMOVE_JOB_ANNOTATIONS_SUCCESS,
97+
payload: {
98+
sessionInstance,
99+
},
100+
});
101+
} catch (error) {
102+
dispatch({
103+
type: AnnotationActionTypes.REMOVE_JOB_ANNOTATIONS_FAILED,
104+
payload: {
105+
error,
106+
},
107+
});
108+
}
109+
};
110+
}
111+
112+
113+
export function uploadJobAnnotationsAsync(job: any, loader: any, file: File):
114+
ThunkAction<Promise<void>, {}, {}, AnyAction> {
115+
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
116+
try {
117+
const store = getCVATStore();
118+
const state: CombinedState = store.getState();
119+
if (state.tasks.activities.loads[job.task.id]) {
120+
throw Error('Annotations is being uploaded for the task');
121+
}
122+
if (state.annotation.activities.loads[job.id]) {
123+
throw Error('Only one uploading of annotations for a job allowed at the same time');
124+
}
125+
126+
dispatch({
127+
type: AnnotationActionTypes.UPLOAD_JOB_ANNOTATIONS,
128+
payload: {
129+
job,
130+
loader,
131+
},
132+
});
133+
134+
const frame = state.annotation.player.frame.number;
135+
await job.annotations.upload(file, loader);
136+
137+
// One more update to escape some problems
138+
// in canvas when shape with the same
139+
// clientID has different type (polygon, rectangle) for example
140+
dispatch({
141+
type: AnnotationActionTypes.UPLOAD_JOB_ANNOTATIONS_SUCCESS,
142+
payload: {
143+
job,
144+
states: [],
145+
},
146+
});
147+
148+
await job.annotations.clear(true);
149+
const states = await job.annotations.get(frame);
150+
151+
setTimeout(() => {
152+
dispatch({
153+
type: AnnotationActionTypes.UPLOAD_JOB_ANNOTATIONS_SUCCESS,
154+
payload: {
155+
job,
156+
states,
157+
},
158+
});
159+
});
160+
} catch (error) {
161+
dispatch({
162+
type: AnnotationActionTypes.UPLOAD_JOB_ANNOTATIONS_FAILED,
163+
payload: {
164+
job,
165+
error,
166+
},
167+
});
168+
}
169+
};
71170
}
72171

73172
export function changeJobStatusAsync(jobInstance: any, status: string):

cvat-ui/src/actions/tasks-actions.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { AnyAction, Dispatch, ActionCreator } from 'redux';
22
import { ThunkAction } from 'redux-thunk';
3-
import { TasksQuery } from 'reducers/interfaces';
3+
import {
4+
TasksQuery,
5+
CombinedState,
6+
} from 'reducers/interfaces';
7+
import { getCVATStore } from 'cvat-store';
48
import getCore from 'cvat-core';
59
import { getInferenceStatusAsync } from './models-actions';
610

@@ -213,6 +217,11 @@ export function loadAnnotationsAsync(task: any, loader: any, file: File):
213217
ThunkAction<Promise<void>, {}, {}, AnyAction> {
214218
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
215219
try {
220+
const store = getCVATStore();
221+
const state: CombinedState = store.getState();
222+
if (state.tasks.activities.loads[task.id]) {
223+
throw Error('Only one loading of annotations for a task allowed at the same time');
224+
}
216225
dispatch(loadAnnotations(task, loader));
217226
await task.annotations.upload(file, loader);
218227
} catch (error) {

0 commit comments

Comments
 (0)