From 4b9b9c95c1608ba342cd90deb1707ef59e4ce878 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 13 Jan 2020 17:56:46 +0300 Subject: [PATCH 01/15] Move, zoom integration --- cvat-canvas/src/typescript/canvasView.ts | 13 ++++++++- cvat-ui/src/actions/annotation-actions.ts | 20 +++++++++++++ .../standard-workspace/canvas-wrapper.tsx | 16 ++++++++++ .../standard-workspace/controls-side-bar.tsx | 29 ++++++++++++++++--- .../standard-workspace/styles.scss | 6 +++- .../standard-workspace/canvas-wrapper.tsx | 20 +++++++++++++ .../standard-workspace/controls-side-bar.tsx | 14 +++++++-- cvat-ui/src/reducers/annotation-reducer.ts | 24 ++++++++++++++- cvat-ui/src/reducers/interfaces.ts | 7 +++++ 9 files changed, 140 insertions(+), 9 deletions(-) diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 5af68f30a9e..4a57ca922ba 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -438,7 +438,10 @@ export class CanvasViewImpl implements CanvasView, Listener { this.content.addEventListener('mousedown', (event): void => { if ((event.which === 1 && this.mode === Mode.IDLE) || (event.which === 2)) { self.controller.enableDrag(event.clientX, event.clientY); - + this.canvas.dispatchEvent(new CustomEvent('canvas.dragstart', { + bubbles: false, + cancelable: true, + })); event.preventDefault(); } }); @@ -446,12 +449,20 @@ export class CanvasViewImpl implements CanvasView, Listener { window.document.addEventListener('mouseup', (event): void => { if (event.which === 1 || event.which === 2) { self.controller.disableDrag(); + this.canvas.dispatchEvent(new CustomEvent('canvas.dragstop', { + bubbles: false, + cancelable: true, + })); } }); this.content.addEventListener('wheel', (event): void => { const point = translateToSVG(self.background, [event.clientX, event.clientY]); self.controller.zoom(point[0], point[1], event.deltaY > 0 ? -1 : 1); + this.canvas.dispatchEvent(new CustomEvent('canvas.zoom', { + bubbles: false, + cancelable: true, + })); event.preventDefault(); }); diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 904fa2a89ec..44f1ea17219 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -20,6 +20,8 @@ export enum AnnotationActionTypes { CHANGE_FRAME_FAILED = 'CHANGE_FRAME_FAILED', SWITCH_PLAY = 'SWITCH_PLAY', CONFIRM_CANVAS_READY = 'CONFIRM_CANVAS_READY', + DRAG_CANVAS = 'DRAG_CANVAS', + ZOOM_CANVAS = 'ZOOM_CANVAS', } export function switchPlay(playing: boolean): AnyAction { @@ -76,6 +78,24 @@ ThunkAction, {}, {}, AnyAction> { }; } +export function dragCanvas(inprogress: boolean): AnyAction { + return { + type: AnnotationActionTypes.DRAG_CANVAS, + payload: { + inprogress, + }, + }; +} + +export function zoomCanvas(inprogress: boolean): AnyAction { + return { + type: AnnotationActionTypes.ZOOM_CANVAS, + payload: { + inprogress, + }, + }; +} + export function confirmCanvasReady(): AnyAction { return { type: AnnotationActionTypes.CONFIRM_CANVAS_READY, 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 abd5dbc2bb2..6be33d0c11f 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 @@ -20,6 +20,8 @@ interface Props { gridColor: GridColor; gridOpacity: number; onSetupCanvas: () => void; + onDragCanvas: (inprogress: boolean) => void; + onZoomCanvas: () => void; } export default class CanvasWrapperComponent extends React.PureComponent { @@ -84,6 +86,8 @@ export default class CanvasWrapperComponent extends React.PureComponent { canvasInstance, jobInstance, onSetupCanvas, + onDragCanvas, + onZoomCanvas, } = this.props; // Size @@ -112,6 +116,18 @@ export default class CanvasWrapperComponent extends React.PureComponent { canvasInstance.html().addEventListener('canvas.setup', () => { canvasInstance.fit(); }, { once: true }); + + canvasInstance.html().addEventListener('canvas.dragstart', () => { + onDragCanvas(true); + }); + + canvasInstance.html().addEventListener('canvas.dragstop', () => { + onDragCanvas(false); + }); + + canvasInstance.html().addEventListener('canvas.zoom', () => { + onZoomCanvas(); + }); } private updateCanvas(): void { diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar.tsx index 2abb67addb9..06b8d233497 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar.tsx @@ -23,6 +23,10 @@ import { SplitIcon, } from '../../../icons'; +import { + ActiveControl, +} from '../../../reducers/interfaces'; + import { Canvas, Rotation, @@ -31,12 +35,14 @@ import { interface Props { canvasInstance: Canvas; rotateAll: boolean; + activeControls: ActiveControl[]; } export default function ControlsSideBarComponent(props: Props): JSX.Element { const { rotateAll, canvasInstance, + activeControls, } = props; return ( @@ -46,11 +52,21 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element { width={44} > - + - + - + canvasInstance.fit()} /> - +
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 3814e9178dc..a93e3843bf7 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss +++ b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss @@ -39,13 +39,17 @@ } } +.cvat-annotation-page-active-control { + background: $header-color; + transform: scale(0.75); +} + .cvat-annotation-page-controls-rotate-left, .cvat-annotation-page-controls-rotate-right { transform: scale(0.65); border-radius: 5px; &:hover { - background: $header-color; transform: scale(0.75); } &:active { 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 36251baf32b..3dd7887128b 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 @@ -5,6 +5,8 @@ import CanvasWrapperComponent from '../../../components/annotation-page/standard import { confirmCanvasReady, + dragCanvas, + zoomCanvas, } from '../../../actions/annotation-actions'; import { GridColor, @@ -26,6 +28,8 @@ interface StateToProps { interface DispatchToProps { onSetupCanvas(): void; + onDragCanvas: (inprogress: boolean) => void; + onZoomCanvas: () => void; } function mapStateToProps(state: CombinedState): StateToProps { @@ -55,11 +59,27 @@ function mapStateToProps(state: CombinedState): StateToProps { }; } +let zoomResetTimeout: null | number = null; function mapDispatchToProps(dispatch: any): DispatchToProps { return { onSetupCanvas(): void { dispatch(confirmCanvasReady()); }, + onDragCanvas(inprogress: boolean): void { + dispatch(dragCanvas(inprogress)); + }, + onZoomCanvas(): void { + dispatch(zoomCanvas(true)); + if (zoomResetTimeout !== null) { + clearTimeout(zoomResetTimeout); + zoomResetTimeout = null; + } + + zoomResetTimeout = window.setTimeout(() => { + zoomResetTimeout = null; + dispatch(zoomCanvas(false)); + }, 200); + }, }; } diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar.tsx index bc8bf1d9a73..f7c492232a8 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar.tsx @@ -4,12 +4,16 @@ import { connect } from 'react-redux'; import { Canvas } from '../../../canvas'; import ControlsSideBarComponent from '../../../components/annotation-page/standard-workspace/controls-side-bar'; -import { CombinedState } from '../../../reducers/interfaces'; +import { + ActiveControl, + CombinedState, +} from '../../../reducers/interfaces'; interface StateToProps { canvasInstance: Canvas; rotateAll: boolean; + activeControls: ActiveControl[]; } function mapStateToProps(state: CombinedState): StateToProps { @@ -18,9 +22,15 @@ function mapStateToProps(state: CombinedState): StateToProps { settings, } = state; + const { + canvasInstance, + activeControls, + } = annotation; + return { rotateAll: settings.player.rotateAll, - canvasInstance: annotation.canvasInstance, + canvasInstance, + activeControls, }; } diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index b4c1f91797f..1259d213df8 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -2,12 +2,16 @@ import { AnyAction } from 'redux'; import { Canvas } from '../canvas'; -import { AnnotationState } from './interfaces'; +import { + AnnotationState, + ActiveControl, +} from './interfaces'; import { AnnotationActionTypes } from '../actions/annotation-actions'; const defaultState: AnnotationState = { canvasInstance: new Canvas(), canvasIsReady: false, + activeControls: [ActiveControl.CURSOR], jobInstance: null, frame: 0, playing: false, @@ -78,6 +82,24 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { canvasIsReady: true, }; } + case AnnotationActionTypes.DRAG_CANVAS: { + const { inprogress } = action.payload; + return { + ...state, + activeControls: inprogress ? [...state.activeControls, ActiveControl.DRAG_CANVAS] + : state.activeControls + .filter((control: ActiveControl) => control !== ActiveControl.DRAG_CANVAS), + }; + } + case AnnotationActionTypes.ZOOM_CANVAS: { + const { inprogress } = action.payload; + return { + ...state, + activeControls: inprogress ? [...state.activeControls, ActiveControl.ZOOM_CANVAS] + : state.activeControls + .filter((control: ActiveControl) => control !== ActiveControl.ZOOM_CANVAS), + }; + } default: { return { ...state, diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index 7a3d70dbe04..76b3310c5ee 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -199,9 +199,16 @@ export interface NotificationsState { }; } +export enum ActiveControl { + CURSOR = 'cursor', + DRAG_CANVAS = 'drag_canvas', + ZOOM_CANVAS = 'zoom_canvas', +} + export interface AnnotationState { canvasInstance: Canvas; canvasIsReady: boolean; + activeControls: ActiveControl[]; jobInstance: any | null | undefined; frameData: any | null; frame: number; From acf0b6c040229cc0bfc7acec01c0636e565b57a7 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 14 Jan 2020 14:59:35 +0300 Subject: [PATCH 02/15] Moving, zooming, additional canvas handler --- cvat-canvas/README.md | 81 ++-- cvat-canvas/src/scss/canvas.scss | 6 + cvat-canvas/src/typescript/canvas.ts | 14 +- cvat-canvas/src/typescript/canvasModel.ts | 90 ++-- cvat-canvas/src/typescript/canvasView.ts | 384 ++++++++++-------- cvat-canvas/src/typescript/drawHandler.ts | 1 + cvat-canvas/src/typescript/shared.ts | 12 +- cvat-canvas/src/typescript/zoomHandler.ts | 135 ++++++ cvat-ui/src/actions/annotation-actions.ts | 16 +- .../standard-workspace/canvas-wrapper.tsx | 18 +- .../standard-workspace/controls-side-bar.tsx | 63 ++- .../standard-workspace/canvas-wrapper.tsx | 27 +- .../standard-workspace/controls-side-bar.tsx | 6 +- cvat-ui/src/reducers/annotation-reducer.ts | 20 +- cvat-ui/src/reducers/interfaces.ts | 2 +- 15 files changed, 560 insertions(+), 315 deletions(-) create mode 100644 cvat-canvas/src/typescript/zoomHandler.ts diff --git a/cvat-canvas/README.md b/cvat-canvas/README.md index 61d6f4bca91..a7def23c98e 100644 --- a/cvat-canvas/README.md +++ b/cvat-canvas/README.md @@ -74,7 +74,6 @@ Canvas itself handles: activate(clientID: number, attributeID?: number): void; rotate(rotation: Rotation, remember?: boolean): void; focus(clientID: number, padding?: number): void; - fitCanvas(): void; fit(): void; grid(stepX: number, stepY: number): void; @@ -84,6 +83,10 @@ Canvas itself handles: merge(mergeData: MergeData): void; select(objectState: any): void; + fitCanvas(): void; + dragCanvas(enable: boolean): void; + zoomCanvas(enable: boolean): void; + cancel(): void; } ``` @@ -118,6 +121,10 @@ Standard JS events are used. - canvas.groupped => {states: ObjectState[]} - canvas.merged => {states: ObjectState[]} - canvas.canceled + - canvas.dragstart + - canvas.dragstop + - canvas.zoomstart + - canvas.zoomstop ``` ### WEB @@ -138,64 +145,26 @@ Standard JS events are used. }); ``` -### TypeScript -- Add to ```tsconfig.json```: -```json - "compilerOptions": { - "paths": { - "cvat-canvas.node": ["3rdparty/cvat-canvas.node"] - } - } -``` - -- ```3rdparty``` directory contains both ```cvat-canvas.node.js``` and ```cvat-canvas.node.d.ts```. -- Add alias to ```webpack.config.js```: -```js -module.exports = { - resolve: { - alias: { - 'cvat-canvas.node': path.resolve(__dirname, '3rdparty/cvat-canvas.node.js'), - } - } -} -``` - -Than you can use it in TypeScript: -```ts - import * as CANVAS from 'cvat-canvas.node'; - // Create an instance of a canvas - const canvas = new CANVAS.Canvas(); - - // Put canvas to a html container - htmlContainer.appendChild(canvas.html()); - - // Next you can use its API methods. For example: - canvas.rotate(CANVAS.Rotation.CLOCKWISE90); - canvas.draw({ - enabled: true, - shapeType: 'rectangle', - crosshair: true, - }); -``` - ## States ![](images/states.svg) ## API Reaction -| | IDLE | GROUPING | SPLITTING | DRAWING | MERGING | EDITING | -|-------------|------|----------|-----------|---------|---------|---------| -| html() | + | + | + | + | + | + | -| setup() | + | + | + | + | + | - | -| activate() | + | - | - | - | - | - | -| rotate() | + | + | + | + | + | + | -| focus() | + | + | + | + | + | + | -| fit() | + | + | + | + | + | + | -| fitCanvas() | + | + | + | + | + | + | -| grid() | + | + | + | + | + | + | -| draw() | + | - | - | - | - | - | -| split() | + | - | + | - | - | - | -| group | + | + | - | - | - | - | -| merge() | + | - | - | - | + | - | -| cancel() | - | + | + | + | + | + | +| | IDLE | GROUPING | SPLITTING | DRAWING | MERGING | EDITING | DRAG | ZOOM | +|--------------|------|----------|-----------|---------|---------|---------|------|------| +| html() | + | + | + | + | + | + | + | + | +| setup() | + | + | + | + | + | - | + | + | +| activate() | + | - | - | - | - | - | - | - | +| rotate() | + | + | + | + | + | + | + | + | +| focus() | + | + | + | + | + | + | + | + | +| fit() | + | + | + | + | + | + | + | + | +| grid() | + | + | + | + | + | + | + | + | +| draw() | + | - | - | - | - | - | - | - | +| split() | + | - | + | - | - | - | - | - | +| group() | + | + | - | - | - | - | - | - | +| merge() | + | - | - | - | + | - | - | - | +| fitCanvas() | + | + | + | + | + | + | + | + | +| dragCanvas() | + | - | - | - | - | - | + | - | +| zoomCanvas() | + | - | - | - | - | - | - | + | +| cancel() | - | + | + | + | + | + | + | + | diff --git a/cvat-canvas/src/scss/canvas.scss b/cvat-canvas/src/scss/canvas.scss index 7cf72018b66..53156c5e450 100644 --- a/cvat-canvas/src/scss/canvas.scss +++ b/cvat-canvas/src/scss/canvas.scss @@ -68,6 +68,12 @@ polyline.cvat_canvas_shape_merging { stroke: black; } +.cvat_canvas_zoom_selection { + stroke: #096dd9; + fill-opacity: 0; + stroke-dasharray: 4; +} + .cvat_canvas_shape_occluded { stroke-dasharray: 5; } diff --git a/cvat-canvas/src/typescript/canvas.ts b/cvat-canvas/src/typescript/canvas.ts index 1f9e04465d6..3c6dfec3421 100644 --- a/cvat-canvas/src/typescript/canvas.ts +++ b/cvat-canvas/src/typescript/canvas.ts @@ -44,6 +44,10 @@ interface Canvas { merge(mergeData: MergeData): void; select(objectState: any): void; + fitCanvas(): void; + dragCanvas(enable: boolean): void; + zoomCanvas(enable: boolean): void; + cancel(): void; } @@ -73,7 +77,15 @@ class CanvasImpl implements Canvas { ); } - public activate(clientID: number, attributeID: number = null): void { + public dragCanvas(enable: boolean): void { + this.model.dragCanvas(enable); + } + + public zoomCanvas(enable: boolean): void { + this.model.zoomCanvas(enable); + } + + public activate(clientID: number, attributeID: number | null = null): void { this.model.activate(clientID, attributeID); } diff --git a/cvat-canvas/src/typescript/canvasModel.ts b/cvat-canvas/src/typescript/canvasModel.ts index d4959af81f2..af08193894e 100644 --- a/cvat-canvas/src/typescript/canvasModel.ts +++ b/cvat-canvas/src/typescript/canvasModel.ts @@ -33,8 +33,8 @@ export interface FocusData { } export interface ActiveElement { - clientID: number; - attributeID: number; + clientID: number | null; + attributeID: number | null; } export interface DrawData { @@ -74,21 +74,26 @@ export enum Rotation { } export enum UpdateReasons { - IMAGE = 'image', - OBJECTS = 'objects', - ZOOM = 'zoom', - FIT = 'fit', - FIT_CANVAS = 'fit_canvas', - MOVE = 'move', - GRID = 'grid', - FOCUS = 'focus', - ACTIVATE = 'activate', + IMAGE_CHANGED = 'image_changed', + IMAGE_ZOOMED = 'image_zoomed', + IMAGE_FITTED = 'image_fitted', + IMAGE_MOVED = 'image_moved', + GRID_UPDATED = 'grid_updated', + + OBJECTS_UPDATED = 'objects_updated', + SHAPE_ACTIVATED = 'shape_activated', + SHAPE_FOCUSED = 'shape_focused', + + FITTED_CANVAS = 'fitted_canvas', + DRAW = 'draw', MERGE = 'merge', SPLIT = 'split', GROUP = 'group', SELECT = 'select', CANCEL = 'cancel', + DRAG_CANVAS = 'drag_canvas', + ZOOM_CANVAS = 'ZOOM_CANVAS', } export enum Mode { @@ -100,6 +105,8 @@ export enum Mode { MERGE = 'merge', SPLIT = 'split', GROUP = 'group', + DRAG_CANVAS = 'drag_canvas', + ZOOM_CANVAS = 'zoom_canvas', } export interface CanvasModel { @@ -120,11 +127,10 @@ export interface CanvasModel { move(topOffset: number, leftOffset: number): void; setup(frameData: any, objectStates: any[]): void; - activate(clientID: number, attributeID: number): void; + activate(clientID: number, attributeID: number | null): void; rotate(rotation: Rotation, remember: boolean): void; focus(clientID: number, padding: number): void; fit(): void; - fitCanvas(width: number, height: number): void; grid(stepX: number, stepY: number): void; draw(drawData: DrawData): void; @@ -133,6 +139,10 @@ export interface CanvasModel { merge(mergeData: MergeData): void; select(objectState: any): void; + fitCanvas(width: number, height: number): void; + dragCanvas(enable: boolean): void; + zoomCanvas(enable: boolean): void; + cancel(): void; } @@ -193,8 +203,6 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { top: 0, drawData: { enabled: false, - shapeType: null, - numberOfPoints: null, initialState: null, }, mergeData: { @@ -207,7 +215,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { enabled: false, }, selected: null, - mode: null, + mode: Mode.IDLE, }; } @@ -232,13 +240,13 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { * (oldScale / this.data.scale - 1)) * this.data.scale; } - this.notify(UpdateReasons.ZOOM); + this.notify(UpdateReasons.IMAGE_ZOOMED); } public move(topOffset: number, leftOffset: number): void { this.data.top += topOffset; this.data.left += leftOffset; - this.notify(UpdateReasons.MOVE); + this.notify(UpdateReasons.IMAGE_MOVED); } public fitCanvas(width: number, height: number): void { @@ -250,15 +258,41 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { this.data.canvasSize.width / FrameZoom.MIN, )); - this.notify(UpdateReasons.FIT_CANVAS); - this.notify(UpdateReasons.OBJECTS); + this.notify(UpdateReasons.FITTED_CANVAS); + this.notify(UpdateReasons.OBJECTS_UPDATED); + } + + public dragCanvas(enable: boolean): void { + if (enable && this.data.mode !== Mode.IDLE) { + throw Error(`Canvas is busy. Action: ${this.data.mode}`); + } + + if (!enable && this.data.mode !== Mode.DRAG_CANVAS) { + throw Error(`Canvas is not in the drag mode. Action: ${this.data.mode}`); + } + + this.data.mode = enable ? Mode.DRAG_CANVAS : Mode.IDLE; + this.notify(UpdateReasons.DRAG_CANVAS); + } + + public zoomCanvas(enable: boolean): void { + if (enable && this.data.mode !== Mode.IDLE) { + throw Error(`Canvas is busy. Action: ${this.data.mode}`); + } + + if (!enable && this.data.mode !== Mode.ZOOM_CANVAS) { + throw Error(`Canvas is not in the zoom mode. Action: ${this.data.mode}`); + } + + this.data.mode = enable ? Mode.ZOOM_CANVAS : Mode.IDLE; + this.notify(UpdateReasons.ZOOM_CANVAS); } public setup(frameData: any, objectStates: any[]): void { frameData.data( (): void => { this.data.image = ''; - this.notify(UpdateReasons.IMAGE); + this.notify(UpdateReasons.IMAGE_CHANGED); }, ).then((data: string): void => { this.data.imageSize = { @@ -271,15 +305,15 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { } this.data.image = data; - this.notify(UpdateReasons.IMAGE); + this.notify(UpdateReasons.IMAGE_CHANGED); this.data.objects = objectStates; - this.notify(UpdateReasons.OBJECTS); + this.notify(UpdateReasons.OBJECTS_UPDATED); }).catch((exception: any): void => { throw exception; }); } - public activate(clientID: number, attributeID: number): void { + public activate(clientID: number, attributeID: number | null): void { if (this.data.mode !== Mode.IDLE) { // Exception or just return? throw Error(`Canvas is busy. Action: ${this.data.mode}`); @@ -290,7 +324,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { attributeID, }; - this.notify(UpdateReasons.ACTIVATE); + this.notify(UpdateReasons.SHAPE_ACTIVATED); } public rotate(rotation: Rotation, remember: boolean = false): void { @@ -311,7 +345,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { padding, }; - this.notify(UpdateReasons.FOCUS); + this.notify(UpdateReasons.SHAPE_FOCUSED); } public fit(): void { @@ -338,7 +372,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { this.data.top = (this.data.canvasSize.height / 2 - this.data.imageSize.height / 2); this.data.left = (this.data.canvasSize.width / 2 - this.data.imageSize.width / 2); - this.notify(UpdateReasons.FIT); + this.notify(UpdateReasons.IMAGE_FITTED); } public grid(stepX: number, stepY: number): void { @@ -347,7 +381,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { width: stepX, }; - this.notify(UpdateReasons.GRID); + this.notify(UpdateReasons.GRID_UPDATED); } public draw(drawData: DrawData): void { diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 4a57ca922ba..aad81fe9e0d 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -16,6 +16,7 @@ import { EditHandler, EditHandlerImpl } from './editHandler'; import { MergeHandler, MergeHandlerImpl } from './mergeHandler'; import { SplitHandler, SplitHandlerImpl } from './splitHandler'; import { GroupHandler, GroupHandlerImpl } from './groupHandler'; +import { ZoomHandler, ZoomHandlerImpl } from './zoomHandler'; import consts from './consts'; import { translateToSVG, @@ -29,7 +30,6 @@ import { CanvasModel, Geometry, UpdateReasons, - FocusData, FrameZoom, ActiveElement, DrawData, @@ -86,10 +86,11 @@ export class CanvasViewImpl implements CanvasView, Listener { private mergeHandler: MergeHandler; private splitHandler: SplitHandler; private groupHandler: GroupHandler; + private zoomHandler: ZoomHandler; private activeElement: { state: any; attributeID: number; - }; + } | null; private set mode(value: Mode) { this.controller.mode = value; @@ -252,12 +253,141 @@ export class CanvasViewImpl implements CanvasView, Listener { } } + private onFocusRegion(x: number, y: number, width: number, height: number): void { + // First of all, compute and apply scale + let scale = null; + + if ((this.geometry.angle / 90) % 2) { + // 90, 270, .. + scale = Math.min(Math.max(Math.min( + this.geometry.canvas.width / height, + this.geometry.canvas.height / width, + ), FrameZoom.MIN), FrameZoom.MAX); + } else { + scale = Math.min(Math.max(Math.min( + this.geometry.canvas.width / width, + this.geometry.canvas.height / height, + ), FrameZoom.MIN), FrameZoom.MAX); + } + + this.geometry = { ...this.geometry, scale }; + this.transformCanvas(); + + const [canvasX, canvasY] = translateFromSVG(this.content, [ + x + width / 2, + y + height / 2, + ]); + + const [cx, cy] = [ + this.canvas.clientWidth / 2 + this.canvas.offsetLeft, + this.canvas.clientHeight / 2 + this.canvas.offsetTop, + ]; + + const dragged = { + ...this.geometry, + top: this.geometry.top + cy - canvasY, + left: this.geometry.left + cx - canvasX, + scale, + }; + + this.controller.geometry = dragged; + this.geometry = dragged; + this.moveCanvas(); + } + + private moveCanvas(): void { + for (const obj of [this.background, this.grid, this.loadingAnimation]) { + obj.style.top = `${this.geometry.top}px`; + obj.style.left = `${this.geometry.left}px`; + } + + for (const obj of [this.content, this.text]) { + obj.style.top = `${this.geometry.top - this.geometry.offset}px`; + obj.style.left = `${this.geometry.left - this.geometry.offset}px`; + } + + // Transform handlers + this.drawHandler.transform(this.geometry); + this.editHandler.transform(this.geometry); + this.zoomHandler.transform(this.geometry); + } + + private transformCanvas(): void { + // Transform canvas + for (const obj of [this.background, this.grid, this.loadingAnimation, this.content]) { + obj.style.transform = `scale(${this.geometry.scale}) rotate(${this.geometry.angle}deg)`; + } + + // Transform grid + this.gridPath.setAttribute('stroke-width', `${consts.BASE_GRID_WIDTH / (this.geometry.scale)}px`); + + // Transform all shape points + for (const element of window.document.getElementsByClassName('svg_select_points')) { + element.setAttribute( + 'stroke-width', + `${consts.POINTS_STROKE_WIDTH / this.geometry.scale}`, + ); + element.setAttribute( + 'r', + `${consts.BASE_POINT_SIZE / this.geometry.scale}`, + ); + } + + for (const element of + window.document.getElementsByClassName('cvat_canvas_selected_point')) { + const previousWidth = element.getAttribute('stroke-width') as string; + element.setAttribute( + 'stroke-width', + `${+previousWidth * 2}`, + ); + } + + // Transform all drawn shapes + for (const key in this.svgShapes) { + if (Object.prototype.hasOwnProperty.call(this.svgShapes, key)) { + const object = this.svgShapes[key]; + if (object.attr('stroke-width')) { + object.attr({ + 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, + }); + } + } + } + + // Transform all text + for (const key in this.svgShapes) { + if (Object.prototype.hasOwnProperty.call(this.svgShapes, key) + && Object.prototype.hasOwnProperty.call(this.svgTexts, key)) { + this.updateTextPosition( + this.svgTexts[key], + this.svgShapes[key], + ); + } + } + + // Transform handlers + this.drawHandler.transform(this.geometry); + this.editHandler.transform(this.geometry); + } + + private resizeCanvas(): void { + for (const obj of [this.background, this.grid, this.loadingAnimation]) { + obj.style.width = `${this.geometry.image.width}px`; + obj.style.height = `${this.geometry.image.height}px`; + } + + for (const obj of [this.content, this.text]) { + obj.style.width = `${this.geometry.image.width + this.geometry.offset * 2}px`; + obj.style.height = `${this.geometry.image.height + this.geometry.offset * 2}px`; + } + } + private selectize(value: boolean, shape: SVG.Element): void { const self = this; function dblClickHandler(e: MouseEvent): void { const pointID = Array.prototype.indexOf - .call((e.target as HTMLElement).parentElement.children, e.target); + .call(((e.target as HTMLElement).parentElement as HTMLElement).children, e.target); if (self.activeElement) { if (e.ctrlKey) { @@ -324,6 +454,7 @@ export class CanvasViewImpl implements CanvasView, Listener { public constructor(model: CanvasModel & Master, controller: CanvasController) { this.controller = controller; + this.geometry = controller.geometry; this.svgShapes = {}; this.svgTexts = {}; this.activeElement = null; @@ -426,7 +557,11 @@ export class CanvasViewImpl implements CanvasView, Listener { this.onFindObject.bind(this), this.adoptedContent, ); - + this.zoomHandler = new ZoomHandlerImpl( + this.onFocusRegion.bind(this), + this.adoptedContent, + this.geometry, + ); // Setup event handlers this.content.addEventListener('dblclick', (e: MouseEvent): void => { @@ -436,23 +571,18 @@ export class CanvasViewImpl implements CanvasView, Listener { }); this.content.addEventListener('mousedown', (event): void => { - if ((event.which === 1 && this.mode === Mode.IDLE) || (event.which === 2)) { - self.controller.enableDrag(event.clientX, event.clientY); - this.canvas.dispatchEvent(new CustomEvent('canvas.dragstart', { - bubbles: false, - cancelable: true, - })); - event.preventDefault(); + if ([1, 2].includes(event.which)) { + if ([Mode.DRAG_CANVAS, Mode.IDLE].includes(this.mode)) { + self.controller.enableDrag(event.clientX, event.clientY); + } else if (this.mode === Mode.ZOOM_CANVAS && event.which === 2) { + self.controller.enableDrag(event.clientX, event.clientY); + } } }); window.document.addEventListener('mouseup', (event): void => { if (event.which === 1 || event.which === 2) { self.controller.disableDrag(); - this.canvas.dispatchEvent(new CustomEvent('canvas.dragstop', { - bubbles: false, - cancelable: true, - })); } }); @@ -491,140 +621,6 @@ export class CanvasViewImpl implements CanvasView, Listener { } public notify(model: CanvasModel & Master, reason: UpdateReasons): void { - function transform(): void { - // Transform canvas - for (const obj of [this.background, this.grid, this.loadingAnimation, this.content]) { - obj.style.transform = `scale(${this.geometry.scale}) rotate(${this.geometry.angle}deg)`; - } - - // Transform grid - this.gridPath.setAttribute('stroke-width', `${consts.BASE_GRID_WIDTH / (this.geometry.scale)}px`); - - // Transform all shape points - for (const element of window.document.getElementsByClassName('svg_select_points')) { - element.setAttribute( - 'stroke-width', - `${consts.POINTS_STROKE_WIDTH / this.geometry.scale}`, - ); - element.setAttribute( - 'r', - `${consts.BASE_POINT_SIZE / this.geometry.scale}`, - ); - } - - for (const element of - window.document.getElementsByClassName('cvat_canvas_selected_point')) { - element.setAttribute( - 'stroke-width', - `${+element.getAttribute('stroke-width') * 2}`, - ); - } - - // Transform all drawn shapes - for (const key in this.svgShapes) { - if (Object.prototype.hasOwnProperty.call(this.svgShapes, key)) { - const object = this.svgShapes[key]; - if (object.attr('stroke-width')) { - object.attr({ - 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, - }); - } - } - } - - // Transform all text - for (const key in this.svgShapes) { - if (Object.prototype.hasOwnProperty.call(this.svgShapes, key) - && Object.prototype.hasOwnProperty.call(this.svgTexts, key)) { - this.updateTextPosition( - this.svgTexts[key], - this.svgShapes[key], - ); - } - } - - // Transform handlers - this.drawHandler.transform(this.geometry); - this.editHandler.transform(this.geometry); - } - - function resize(): void { - for (const obj of [this.background, this.grid, this.loadingAnimation]) { - obj.style.width = `${this.geometry.image.width}px`; - obj.style.height = `${this.geometry.image.height}px`; - } - - for (const obj of [this.content, this.text]) { - obj.style.width = `${this.geometry.image.width + this.geometry.offset * 2}px`; - obj.style.height = `${this.geometry.image.height + this.geometry.offset * 2}px`; - } - } - - function move(): void { - for (const obj of [this.background, this.grid, this.loadingAnimation]) { - obj.style.top = `${this.geometry.top}px`; - obj.style.left = `${this.geometry.left}px`; - } - - for (const obj of [this.content, this.text]) { - obj.style.top = `${this.geometry.top - this.geometry.offset}px`; - obj.style.left = `${this.geometry.left - this.geometry.offset}px`; - } - - // Transform handlers - this.drawHandler.transform(this.geometry); - this.editHandler.transform(this.geometry); - } - - function computeFocus(focusData: FocusData): void { - // This computation cann't be done in the model because of lack of data - const object = this.svgShapes[focusData.clientID]; - if (!object) { - return; - } - - // First of all, compute and apply scale - - let scale = null; - const bbox: SVG.BBox = object.bbox(); - if ((this.geometry.angle / 90) % 2) { - // 90, 270, .. - scale = Math.min(Math.max(Math.min( - this.geometry.canvas.width / bbox.height, - this.geometry.canvas.height / bbox.width, - ), FrameZoom.MIN), FrameZoom.MAX); - } else { - scale = Math.min(Math.max(Math.min( - this.geometry.canvas.width / bbox.width, - this.geometry.canvas.height / bbox.height, - ), FrameZoom.MIN), FrameZoom.MAX); - } - - this.geometry = { ...this.geometry, scale }; - transform.call(this); - - const [x, y] = translateFromSVG(this.content, [ - bbox.x + bbox.width / 2, - bbox.y + bbox.height / 2, - ]); - - const [cx, cy] = [ - this.canvas.clientWidth / 2 + this.canvas.offsetLeft, - this.canvas.clientHeight / 2 + this.canvas.offsetTop, - ]; - - const dragged = { - ...this.geometry, - top: this.geometry.top + cy - y, - left: this.geometry.left + cx - x, - scale, - }; - - this.controller.geometry = dragged; - this.geometry = dragged; - move.call(this); - } - function setupObjects(objects: any[]): void { const ctm = this.content.getScreenCTM() .inverse().multiply(this.background.getScreenCTM()); @@ -651,36 +647,77 @@ export class CanvasViewImpl implements CanvasView, Listener { } this.geometry = this.controller.geometry; - if (reason === UpdateReasons.IMAGE) { + if (reason === UpdateReasons.IMAGE_CHANGED) { if (!model.image.length) { this.loadingAnimation.classList.remove('cvat_canvas_hidden'); } else { this.loadingAnimation.classList.add('cvat_canvas_hidden'); this.background.style.backgroundImage = `url("${model.image}")`; - move.call(this); - resize.call(this); - transform.call(this); + this.moveCanvas(); + this.resizeCanvas(); + this.transformCanvas(); } - } else if (reason === UpdateReasons.FIT_CANVAS) { - move.call(this); - resize.call(this); - } else if (reason === UpdateReasons.ZOOM || reason === UpdateReasons.FIT) { - move.call(this); - transform.call(this); - } else if (reason === UpdateReasons.MOVE) { - move.call(this); - } else if (reason === UpdateReasons.OBJECTS) { + } else if (reason === UpdateReasons.FITTED_CANVAS) { + this.moveCanvas(); + this.resizeCanvas(); + } else if (reason === UpdateReasons.IMAGE_ZOOMED || reason === UpdateReasons.IMAGE_FITTED) { + this.moveCanvas(); + this.transformCanvas(); + } else if (reason === UpdateReasons.IMAGE_MOVED) { + this.moveCanvas(); + } else if (reason === UpdateReasons.OBJECTS_UPDATED) { setupObjects.call(this, this.controller.objects); const event: CustomEvent = new CustomEvent('canvas.setup'); this.canvas.dispatchEvent(event); - } else if (reason === UpdateReasons.GRID) { + } else if (reason === UpdateReasons.GRID_UPDATED) { const size: Size = this.geometry.grid; this.gridPattern.setAttribute('width', `${size.width}`); this.gridPattern.setAttribute('height', `${size.height}`); - } else if (reason === UpdateReasons.FOCUS) { - computeFocus.call(this, this.controller.focusData); - } else if (reason === UpdateReasons.ACTIVATE) { + } else if (reason === UpdateReasons.SHAPE_FOCUSED) { + const { + padding, + clientID, + } = this.controller.focusData; + const object = this.svgShapes[clientID]; + if (object) { + const bbox: SVG.BBox = object.bbox(); + this.onFocusRegion(bbox.x - padding, bbox.y - padding, + bbox.width + padding, bbox.height + padding); + } + } else if (reason === UpdateReasons.SHAPE_ACTIVATED) { this.activate(this.controller.activeElement); + } else if (reason === UpdateReasons.DRAG_CANVAS) { + this.deactivate(); + if (this.mode === Mode.DRAG_CANVAS) { + this.canvas.dispatchEvent(new CustomEvent('canvas.dragstart', { + bubbles: false, + cancelable: true, + })); + this.canvas.style.cursor = 'move'; + } else { + this.canvas.dispatchEvent(new CustomEvent('canvas.dragstop', { + bubbles: false, + cancelable: true, + })); + this.canvas.style.cursor = ''; + } + } else if (reason === UpdateReasons.ZOOM_CANVAS) { + this.deactivate(); + if (this.mode === Mode.ZOOM_CANVAS) { + this.canvas.dispatchEvent(new CustomEvent('canvas.zoomstart', { + bubbles: false, + cancelable: true, + })); + this.canvas.style.cursor = 'zoom-in'; + this.zoomHandler.zoom(); + } else { + this.canvas.dispatchEvent(new CustomEvent('canvas.zoomstop', { + bubbles: false, + cancelable: true, + })); + this.canvas.style.cursor = ''; + this.zoomHandler.cancel(); + } } else if (reason === UpdateReasons.DRAW) { const data: DrawData = this.controller.drawData; if (data.enabled) { @@ -728,7 +765,20 @@ export class CanvasViewImpl implements CanvasView, Listener { this.groupHandler.cancel(); } else if (this.mode === Mode.EDIT) { this.editHandler.cancel(); + } else if (this.mode === Mode.DRAG_CANVAS) { + this.canvas.dispatchEvent(new CustomEvent('canvas.dragstop', { + bubbles: false, + cancelable: true, + })); + } else if (this.mode === Mode.ZOOM_CANVAS) { + this.zoomHandler.cancel(); + this.canvas.dispatchEvent(new CustomEvent('canvas.zoomstop', { + bubbles: false, + cancelable: true, + })); } + this.mode = Mode.IDLE; + this.canvas.style.cursor = ''; } } @@ -882,7 +932,7 @@ export class CanvasViewImpl implements CanvasView, Listener { this.selectize(true, shape); } - let shapeSizeElement: ShapeSizeElement = null; + let shapeSizeElement: ShapeSizeElement | null = null; let resized = false; (shape as any).resize().on('resizestart', (): void => { this.mode = Mode.RESIZE; diff --git a/cvat-canvas/src/typescript/drawHandler.ts b/cvat-canvas/src/typescript/drawHandler.ts index 0c27618b1bb..7e8e7690122 100644 --- a/cvat-canvas/src/typescript/drawHandler.ts +++ b/cvat-canvas/src/typescript/drawHandler.ts @@ -26,6 +26,7 @@ import { export interface DrawHandler { draw(drawData: DrawData, geometry: Geometry): void; + transform(geometry: Geometry): void; cancel(): void; } diff --git a/cvat-canvas/src/typescript/shared.ts b/cvat-canvas/src/typescript/shared.ts index 37cecd2b6e3..226dc95bde9 100644 --- a/cvat-canvas/src/typescript/shared.ts +++ b/cvat-canvas/src/typescript/shared.ts @@ -26,11 +26,11 @@ export interface BBox { y: number; } -// Translate point array from the client coordinate system -// to a coordinate system of a canvas +// Translate point array from the canvas coordinate system +// to the coordinate system of a client export function translateFromSVG(svg: SVGSVGElement, points: number[]): number[] { const output = []; - const transformationMatrix = svg.getScreenCTM(); + const transformationMatrix = svg.getScreenCTM() as DOMMatrix; let pt = svg.createSVGPoint(); for (let i = 0; i < points.length - 1; i += 2) { pt.x = points[i]; @@ -42,11 +42,11 @@ export function translateFromSVG(svg: SVGSVGElement, points: number[]): number[] return output; } -// Translate point array from a coordinate system of a canvas -// to the client coordinate system +// Translate point array from the coordinate system of a client +// to the canvas coordinate system export function translateToSVG(svg: SVGSVGElement, points: number[]): number[] { const output = []; - const transformationMatrix = svg.getScreenCTM().inverse(); + const transformationMatrix = (svg.getScreenCTM() as DOMMatrix).inverse(); let pt = svg.createSVGPoint(); for (let i = 0; i < points.length; i += 2) { pt.x = points[i]; diff --git a/cvat-canvas/src/typescript/zoomHandler.ts b/cvat-canvas/src/typescript/zoomHandler.ts new file mode 100644 index 00000000000..a7ae9b87849 --- /dev/null +++ b/cvat-canvas/src/typescript/zoomHandler.ts @@ -0,0 +1,135 @@ +import * as SVG from 'svg.js'; +import consts from './consts'; + +import { + translateToSVG, +} from './shared'; + +import { + Geometry, +} from './canvasModel'; + + +export interface ZoomHandler { + zoom(): void; + cancel(): void; + transform(geometry: Geometry): void; +} + +export class ZoomHandlerImpl implements ZoomHandler { + private onZoomRegion: (x: number, y: number, width: number, height: number) => void; + private bindedOnSelectStart: (event: MouseEvent) => void; + private bindedOnSelectUpdate: (event: MouseEvent) => void; + private bindedOnSelectStop: (event: MouseEvent) => void; + private geometry: Geometry; + private canvas: SVG.Container; + private selectionRect: SVG.Rect | null; + private startSelectionPoint: { + x: number; + y: number; + } | null; + + private onSelectStart(event: MouseEvent): void { + if (!this.selectionRect && event.which === 1) { + const point = translateToSVG( + (this.canvas.node as any as SVGSVGElement), + [event.clientX, event.clientY], + ); + this.startSelectionPoint = { + x: point[0], + y: point[1], + }; + + this.selectionRect = this.canvas.rect().addClass('cvat_canvas_zoom_selection'); + this.selectionRect.attr({ + 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, + ...this.startSelectionPoint, + }); + } + } + + private getSelectionBox(event: MouseEvent): { + x: number; + y: number; + width: number; + height: number; + } { + const point = translateToSVG( + (this.canvas.node as any as SVGSVGElement), + [event.clientX, event.clientY], + ); + const stopSelectionPoint = { + x: point[0], + y: point[1], + }; + + const xtl = Math.min(this.startSelectionPoint.x, stopSelectionPoint.x); + const ytl = Math.min(this.startSelectionPoint.y, stopSelectionPoint.y); + const xbr = Math.max(this.startSelectionPoint.x, stopSelectionPoint.x); + const ybr = Math.max(this.startSelectionPoint.y, stopSelectionPoint.y); + + return { + x: xtl, + y: ytl, + width: xbr - xtl, + height: ybr - ytl, + }; + } + + private onSelectUpdate(event: MouseEvent): void { + if (this.selectionRect) { + this.selectionRect.attr({ + ...this.getSelectionBox(event), + }); + } + } + + private onSelectStop(event: MouseEvent): void { + if (this.selectionRect) { + const box = this.getSelectionBox(event); + this.selectionRect.remove(); + this.selectionRect = null; + this.startSelectionPoint = null; + const threshold = 5; + if (box.width > threshold && box.height > threshold) { + this.onZoomRegion(box.x, box.y, box.width, box.height); + } + } + } + + public constructor( + onZoomRegion: (x: number, y: number, width: number, height: number) => void, + canvas: SVG.Container, + geometry: Geometry, + ) { + this.onZoomRegion = onZoomRegion; + this.canvas = canvas; + this.geometry = geometry; + this.selectionRect = null; + this.startSelectionPoint = null; + this.bindedOnSelectStart = this.onSelectStart.bind(this); + this.bindedOnSelectUpdate = this.onSelectUpdate.bind(this); + this.bindedOnSelectStop = this.onSelectStop.bind(this); + } + + public zoom(): void { + this.canvas.node.addEventListener('mousedown', this.bindedOnSelectStart); + this.canvas.node.addEventListener('mousemove', this.bindedOnSelectUpdate); + this.canvas.node.addEventListener('mouseup', this.bindedOnSelectStop); + } + + public cancel(): void { + this.canvas.node.removeEventListener('mousedown', this.bindedOnSelectStart); + this.canvas.node.removeEventListener('mousemove', this.bindedOnSelectUpdate); + this.canvas.node.removeEventListener('mouseup ', this.bindedOnSelectStop); + } + + public transform(geometry: Geometry): void { + this.geometry = geometry; + if (this.selectionRect) { + this.selectionRect.style({ + 'stroke-width': consts.BASE_STROKE_WIDTH / geometry.scale, + }); + } + } +} diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 44f1ea17219..78ecd198b7a 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -22,6 +22,7 @@ export enum AnnotationActionTypes { CONFIRM_CANVAS_READY = 'CONFIRM_CANVAS_READY', DRAG_CANVAS = 'DRAG_CANVAS', ZOOM_CANVAS = 'ZOOM_CANVAS', + RESET_CANVAS = 'RESET_CANVAS', } export function switchPlay(playing: boolean): AnyAction { @@ -78,24 +79,31 @@ ThunkAction, {}, {}, AnyAction> { }; } -export function dragCanvas(inprogress: boolean): AnyAction { +export function dragCanvas(enabled: boolean): AnyAction { return { type: AnnotationActionTypes.DRAG_CANVAS, payload: { - inprogress, + enabled, }, }; } -export function zoomCanvas(inprogress: boolean): AnyAction { +export function zoomCanvas(enabled: boolean): AnyAction { return { type: AnnotationActionTypes.ZOOM_CANVAS, payload: { - inprogress, + enabled, }, }; } +export function resetCanvas(): AnyAction { + return { + type: AnnotationActionTypes.RESET_CANVAS, + payload: {}, + }; +} + export function confirmCanvasReady(): AnyAction { return { type: AnnotationActionTypes.CONFIRM_CANVAS_READY, 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 6be33d0c11f..0ec948442a4 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 @@ -20,8 +20,9 @@ interface Props { gridColor: GridColor; gridOpacity: number; onSetupCanvas: () => void; - onDragCanvas: (inprogress: boolean) => void; - onZoomCanvas: () => void; + onDragCanvas: (enabled: boolean) => void; + onZoomCanvas: (enabled: boolean) => void; + onResetCanvas: () => void; } export default class CanvasWrapperComponent extends React.PureComponent { @@ -88,6 +89,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { onSetupCanvas, onDragCanvas, onZoomCanvas, + onResetCanvas, } = this.props; // Size @@ -117,6 +119,10 @@ export default class CanvasWrapperComponent extends React.PureComponent { canvasInstance.fit(); }, { once: true }); + canvasInstance.html().addEventListener('canvas.canceled', () => { + onResetCanvas(); + }); + canvasInstance.html().addEventListener('canvas.dragstart', () => { onDragCanvas(true); }); @@ -125,8 +131,12 @@ export default class CanvasWrapperComponent extends React.PureComponent { onDragCanvas(false); }); - canvasInstance.html().addEventListener('canvas.zoom', () => { - onZoomCanvas(); + canvasInstance.html().addEventListener('canvas.zoomstart', () => { + onZoomCanvas(true); + }); + + canvasInstance.html().addEventListener('canvas.zoomstop', () => { + onZoomCanvas(false); }); } diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar.tsx index 06b8d233497..37bff62d426 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar.tsx @@ -35,14 +35,14 @@ import { interface Props { canvasInstance: Canvas; rotateAll: boolean; - activeControls: ActiveControl[]; + activeControl: ActiveControl; } export default function ControlsSideBarComponent(props: Props): JSX.Element { const { rotateAll, canvasInstance, - activeControls, + activeControl, } = props; return ( @@ -54,18 +54,31 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element { { + if (activeControl !== ActiveControl.CURSOR) { + canvasInstance.cancel(); + } + }} /> { + if (activeControl === ActiveControl.DRAG_CANVAS) { + canvasInstance.dragCanvas(false); + } else { + canvasInstance.cancel(); + canvasInstance.dragCanvas(true); + } + }} /> @@ -74,25 +87,27 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element { placement='right' content={( <> - canvasInstance - .rotate(Rotation.ANTICLOCKWISE90, rotateAll)} - component={RotateIcon} - /> - canvasInstance - .rotate(Rotation.CLOCKWISE90, rotateAll)} - component={RotateIcon} - /> + + canvasInstance + .rotate(Rotation.ANTICLOCKWISE90, rotateAll)} + component={RotateIcon} + /> + + + canvasInstance + .rotate(Rotation.CLOCKWISE90, rotateAll)} + component={RotateIcon} + /> + )} trigger='hover' > - - - +

@@ -104,9 +119,17 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element { { + if (activeControl === ActiveControl.ZOOM_CANVAS) { + canvasInstance.zoomCanvas(false); + } else { + canvasInstance.cancel(); + canvasInstance.zoomCanvas(true); + } + }} /> 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 3dd7887128b..1bc0e421161 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 @@ -7,6 +7,7 @@ import { confirmCanvasReady, dragCanvas, zoomCanvas, + resetCanvas, } from '../../../actions/annotation-actions'; import { GridColor, @@ -28,8 +29,9 @@ interface StateToProps { interface DispatchToProps { onSetupCanvas(): void; - onDragCanvas: (inprogress: boolean) => void; - onZoomCanvas: () => void; + onDragCanvas: (enabled: boolean) => void; + onZoomCanvas: (enabled: boolean) => void; + onResetCanvas: () => void; } function mapStateToProps(state: CombinedState): StateToProps { @@ -59,26 +61,19 @@ function mapStateToProps(state: CombinedState): StateToProps { }; } -let zoomResetTimeout: null | number = null; function mapDispatchToProps(dispatch: any): DispatchToProps { return { onSetupCanvas(): void { dispatch(confirmCanvasReady()); }, - onDragCanvas(inprogress: boolean): void { - dispatch(dragCanvas(inprogress)); + onDragCanvas(enabled: boolean): void { + dispatch(dragCanvas(enabled)); }, - onZoomCanvas(): void { - dispatch(zoomCanvas(true)); - if (zoomResetTimeout !== null) { - clearTimeout(zoomResetTimeout); - zoomResetTimeout = null; - } - - zoomResetTimeout = window.setTimeout(() => { - zoomResetTimeout = null; - dispatch(zoomCanvas(false)); - }, 200); + onZoomCanvas(enabled: boolean): void { + dispatch(zoomCanvas(enabled)); + }, + onResetCanvas(): void { + dispatch(resetCanvas()); }, }; } diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar.tsx index f7c492232a8..4581677ddac 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar.tsx @@ -13,7 +13,7 @@ import { interface StateToProps { canvasInstance: Canvas; rotateAll: boolean; - activeControls: ActiveControl[]; + activeControl: ActiveControl; } function mapStateToProps(state: CombinedState): StateToProps { @@ -24,13 +24,13 @@ function mapStateToProps(state: CombinedState): StateToProps { const { canvasInstance, - activeControls, + activeControl, } = annotation; return { rotateAll: settings.player.rotateAll, canvasInstance, - activeControls, + activeControl, }; } diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index 1259d213df8..463dea60412 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -11,7 +11,7 @@ import { AnnotationActionTypes } from '../actions/annotation-actions'; const defaultState: AnnotationState = { canvasInstance: new Canvas(), canvasIsReady: false, - activeControls: [ActiveControl.CURSOR], + activeControl: ActiveControl.CURSOR, jobInstance: null, frame: 0, playing: false, @@ -83,21 +83,23 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { }; } case AnnotationActionTypes.DRAG_CANVAS: { - const { inprogress } = action.payload; + const { enabled } = action.payload; return { ...state, - activeControls: inprogress ? [...state.activeControls, ActiveControl.DRAG_CANVAS] - : state.activeControls - .filter((control: ActiveControl) => control !== ActiveControl.DRAG_CANVAS), + activeControl: enabled ? ActiveControl.DRAG_CANVAS : ActiveControl.CURSOR, }; } case AnnotationActionTypes.ZOOM_CANVAS: { - const { inprogress } = action.payload; + const { enabled } = action.payload; return { ...state, - activeControls: inprogress ? [...state.activeControls, ActiveControl.ZOOM_CANVAS] - : state.activeControls - .filter((control: ActiveControl) => control !== ActiveControl.ZOOM_CANVAS), + activeControl: enabled ? ActiveControl.ZOOM_CANVAS : ActiveControl.CURSOR, + }; + } + case AnnotationActionTypes.RESET_CANVAS: { + return { + ...state, + activeControl: ActiveControl.CURSOR, }; } default: { diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index 76b3310c5ee..f6bf3843b33 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -208,7 +208,7 @@ export enum ActiveControl { export interface AnnotationState { canvasInstance: Canvas; canvasIsReady: boolean; - activeControls: ActiveControl[]; + activeControl: ActiveControl; jobInstance: any | null | undefined; frameData: any | null; frame: number; From 9647dad7b17b4e39bacc3cc3420dc1e214353fcb Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 14 Jan 2020 15:54:17 +0300 Subject: [PATCH 03/15] Activating & changing for objects --- cvat-canvas/src/typescript/consts.ts | 2 +- .../standard-workspace/canvas-wrapper.tsx | 65 +++++++++++++++---- .../standard-workspace/canvas-wrapper.tsx | 3 + 3 files changed, 58 insertions(+), 12 deletions(-) diff --git a/cvat-canvas/src/typescript/consts.ts b/cvat-canvas/src/typescript/consts.ts index 69a926d15ea..e354dca35aa 100644 --- a/cvat-canvas/src/typescript/consts.ts +++ b/cvat-canvas/src/typescript/consts.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: MIT */ -const BASE_STROKE_WIDTH = 2; +const BASE_STROKE_WIDTH = 1.5; const BASE_GRID_WIDTH = 1; const BASE_POINT_SIZE = 5; const TEXT_MARGIN = 10; 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 0ec948442a4..93f9e83da0f 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 @@ -8,13 +8,16 @@ import { GridColor, } from '../../../reducers/interfaces'; -import { Canvas } from '../../../canvas'; +import { + Canvas, +} from '../../../canvas'; interface Props { canvasInstance: Canvas; jobInstance: any; annotations: any[]; frameData: any; + frame: number; grid: boolean; gridSize: number; gridColor: GridColor; @@ -78,6 +81,37 @@ export default class CanvasWrapperComponent extends React.PureComponent { this.updateCanvas(); } + private async onShapeEdit(event: any): Promise { + const { + canvasInstance, + jobInstance, + frameData, + frame, + } = this.props; + + const { + state, + points, + } = event.detail; + state.points = points; + state.save(); + + const annotations = await jobInstance.annotations.get(frame); + canvasInstance.setup(frameData, annotations); + } + + private updateCanvas(): void { + const { + annotations, + frameData, + canvasInstance, + } = this.props; + + if (frameData !== null) { + canvasInstance.setup(frameData, annotations); + } + } + private initialSetup(): void { const { grid, @@ -138,18 +172,27 @@ export default class CanvasWrapperComponent extends React.PureComponent { canvasInstance.html().addEventListener('canvas.zoomstop', () => { onZoomCanvas(false); }); - } - private updateCanvas(): void { - const { - annotations, - frameData, - canvasInstance, - } = this.props; + canvasInstance.html().addEventListener('canvas.moved', async (event: any): Promise => { + const threshold = 50; + const result = await jobInstance.annotations.select( + event.detail.states, + event.detail.x, + event.detail.y, + ); + + if (result && result.state) { + if (result.state.shapeType === 'polyline' || result.state.shapeType === 'points') { + if (result.distance > threshold) { + return; + } + } + + canvasInstance.activate(result.state.clientID); + } + }); - if (frameData !== null) { - canvasInstance.setup(frameData, annotations); - } + canvasInstance.html().addEventListener('canvas.edited', this.onShapeEdit.bind(this)); } public render(): JSX.Element { 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 1bc0e421161..24f88d2fc4b 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 @@ -21,6 +21,7 @@ interface StateToProps { jobInstance: any; annotations: any[]; frameData: any; + frame: number; grid: boolean; gridSize: number; gridColor: GridColor; @@ -39,6 +40,7 @@ function mapStateToProps(state: CombinedState): StateToProps { canvasInstance, jobInstance, frameData, + frame, annotations, } = state.annotation; @@ -53,6 +55,7 @@ function mapStateToProps(state: CombinedState): StateToProps { canvasInstance, jobInstance, frameData, + frame, annotations, grid, gridSize, From 37b59f4ec0d6463bb8115bd3ac3acfb721803677 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 14 Jan 2020 16:20:59 +0300 Subject: [PATCH 04/15] Improved colors --- cvat-canvas/src/scss/canvas.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cvat-canvas/src/scss/canvas.scss b/cvat-canvas/src/scss/canvas.scss index 53156c5e450..461f84ba661 100644 --- a/cvat-canvas/src/scss/canvas.scss +++ b/cvat-canvas/src/scss/canvas.scss @@ -109,6 +109,7 @@ polyline.cvat_canvas_shape_merging { } #cvat_canvas_text_content { + text-rendering: optimizeSpeed; position: absolute; z-index: 3; pointer-events: none; @@ -141,6 +142,7 @@ polyline.cvat_canvas_shape_merging { } #cvat_canvas_content { + filter: brightness(200%) contrast(200%) saturate(150%); position: absolute; z-index: 2; outline: 10px solid black; From 200eac8fa062e94d3c2f33914b76b8e60cc75ddc Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 14 Jan 2020 16:56:19 +0300 Subject: [PATCH 05/15] Improved colors --- cvat-canvas/src/scss/canvas.scss | 2 +- cvat-core/src/annotations-collection.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cvat-canvas/src/scss/canvas.scss b/cvat-canvas/src/scss/canvas.scss index 461f84ba661..a35674eb0b2 100644 --- a/cvat-canvas/src/scss/canvas.scss +++ b/cvat-canvas/src/scss/canvas.scss @@ -142,7 +142,7 @@ polyline.cvat_canvas_shape_merging { } #cvat_canvas_content { - filter: brightness(200%) contrast(200%) saturate(150%); + filter: brightness(200%) contrast(120%) saturate(150%); position: absolute; z-index: 2; outline: 10px solid black; diff --git a/cvat-core/src/annotations-collection.js b/cvat-core/src/annotations-collection.js index a95b16e7ba5..2939177a5e7 100644 --- a/cvat-core/src/annotations-collection.js +++ b/cvat-core/src/annotations-collection.js @@ -39,7 +39,7 @@ const colors = [ '#0066FF', '#AF593E', '#01A368', '#FF861F', '#ED0A3F', '#FF3F34', '#76D7EA', - '#8359A3', '#FBE870', '#C5E17A', '#03BB85', '#FFDF00', '#8B8680', '#0A6B0D', + '#8359A3', '#FBE870', '#C5E17A', '#03BB85', '#FFDF00', '#0A6B0D', '#D9D6CF', '#8FD8D8', '#A36F40', '#F653A6', '#CA3435', '#FFCBA4', '#FF99CC', '#FA9D5A', '#FFAE42', '#A78B00', '#788193', '#514E49', '#1164B4', '#F4FA9F', '#FED8B1', '#C32148', '#01796F', '#E90067', '#FF91A4', '#404E5A', '#6CDAE7', '#FFC1CC', @@ -52,7 +52,7 @@ '#FF7A00', '#4F69C6', '#A50B5E', '#F0E68C', '#FDFF00', '#F091A9', '#FFFF66', '#6F9940', '#FC74FD', '#652DC1', '#D6AEDD', '#EE34D2', '#BB3385', '#6B3FA0', '#33CC99', '#FFDB00', '#87FF2A', '#6EEB6E', '#FFC800', '#CC99BA', '#7A89B8', - '#006A93', '#867200', '#E2B631', '#D9D6CF', + '#006A93', '#867200', '#E2B631', ]; function shapeFactory(shapeData, clientID, injection) { From 5b6d7497149abaeb62fc0493f1a02562e522572d Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 14 Jan 2020 18:38:45 +0300 Subject: [PATCH 06/15] Saving annotations on the server --- cvat-core/src/annotations-saver.js | 19 +++----- cvat-core/src/session.js | 4 +- cvat-ui/src/actions/annotation-actions.ts | 37 +++++++++++++++ .../components/annotation-page/styles.scss | 6 +++ .../annotation-page/top-bar/top-bar.tsx | 46 +++++++++++++++++-- .../annotation-page/top-bar/top-bar.tsx | 46 +++++++++---------- cvat-ui/src/reducers/annotation-reducer.ts | 27 +++++++++++ cvat-ui/src/reducers/interfaces.ts | 2 + 8 files changed, 146 insertions(+), 41 deletions(-) diff --git a/cvat-core/src/annotations-saver.js b/cvat-core/src/annotations-saver.js index 80c1bccf985..8592ba680cf 100644 --- a/cvat-core/src/annotations-saver.js +++ b/cvat-core/src/annotations-saver.js @@ -204,17 +204,14 @@ const exported = this.collection.export(); const { flush } = this.collection; if (flush) { - onUpdate('New objects are being saved..'); + onUpdate('Created objects are being saved on the server'); const indexes = this._receiveIndexes(exported); const savedData = await this._put({ ...exported, version: this.version }); this.version = savedData.version; this.collection.flush = false; - onUpdate('Saved objects are being updated in the client'); this._updateCreatedObjects(savedData, indexes); - onUpdate('Initial state is being updated'); - this._resetState(); for (const type of Object.keys(this.initialObjects)) { for (const object of savedData[type]) { @@ -228,39 +225,35 @@ deleted, } = this._split(exported); - onUpdate('New objects are being saved..'); + onUpdate('Created objects are being saved on the server'); const indexes = this._receiveIndexes(created); const createdData = await this._create({ ...created, version: this.version }); this.version = createdData.version; - onUpdate('Saved objects are being updated in the client'); this._updateCreatedObjects(createdData, indexes); - onUpdate('Initial state is being updated'); for (const type of Object.keys(this.initialObjects)) { for (const object of createdData[type]) { this.initialObjects[type][object.id] = object; } } - onUpdate('Changed objects are being saved..'); + onUpdate('Updated objects are being saved on the server'); this._receiveIndexes(updated); const updatedData = await this._update({ ...updated, version: this.version }); this.version = updatedData.version; - onUpdate('Initial state is being updated'); for (const type of Object.keys(this.initialObjects)) { for (const object of updatedData[type]) { this.initialObjects[type][object.id] = object; } } - onUpdate('Changed objects are being saved..'); + onUpdate('Deleted objects are being deleted from the server'); this._receiveIndexes(deleted); const deletedData = await this._delete({ ...deleted, version: this.version }); this._version = deletedData.version; - onUpdate('Initial state is being updated'); for (const type of Object.keys(this.initialObjects)) { for (const object of deletedData[type]) { delete this.initialObjects[type][object.id]; @@ -269,9 +262,9 @@ } this.hash = this._getHash(); - onUpdate('Saving is done'); + onUpdate('Annotations have been saved successfuly'); } catch (error) { - onUpdate(`Can not save annotations: ${error.message}`); + onUpdate('Could not save annotations'); throw error; } } diff --git a/cvat-core/src/session.js b/cvat-core/src/session.js index 55ff05b35d1..f02e073cd62 100644 --- a/cvat-core/src/session.js +++ b/cvat-core/src/session.js @@ -26,9 +26,9 @@ return result; }, - async save() { + async save(onUpdate) { const result = await PluginRegistry - .apiWrapper.call(this, prototype.annotations.save); + .apiWrapper.call(this, prototype.annotations.save, onUpdate); return result; }, diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 78ecd198b7a..20e11a7fa1a 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -18,6 +18,10 @@ export enum AnnotationActionTypes { CHANGE_FRAME = 'CHANGE_FRAME', CHANGE_FRAME_SUCCESS = 'CHANGE_FRAME_SUCCESS', CHANGE_FRAME_FAILED = 'CHANGE_FRAME_FAILED', + SAVE_ANNOTATIONS = 'SAVE_ANNOTATIONS', + SAVE_ANNOTATIONS_SUCCESS = 'SAVE_ANNOTATIONS_SUCCESS', + SAVE_ANNOTATIONS_FAILED = 'SAVE_ANNOTATIONS_FAILED', + SAVE_ANNOTATIONS_UPDATED_STATUS = 'SAVE_ANNOTATIONS_UPDATED_STATUS', SWITCH_PLAY = 'SWITCH_PLAY', CONFIRM_CANVAS_READY = 'CONFIRM_CANVAS_READY', DRAG_CANVAS = 'DRAG_CANVAS', @@ -158,3 +162,36 @@ ThunkAction, {}, {}, AnyAction> { } }; } + +export function saveAnnotationsAsync(sessionInstance: any): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + dispatch({ + type: AnnotationActionTypes.SAVE_ANNOTATIONS, + payload: {}, + }); + + try { + await sessionInstance.annotations.save((status: string) => { + dispatch({ + type: AnnotationActionTypes.SAVE_ANNOTATIONS_UPDATED_STATUS, + payload: { + status, + }, + }); + }); + + dispatch({ + type: AnnotationActionTypes.SAVE_ANNOTATIONS_SUCCESS, + payload: {}, + }); + } catch (error) { + dispatch({ + type: AnnotationActionTypes.SAVE_ANNOTATIONS_FAILED, + payload: { + error, + }, + }); + } + }; +} diff --git a/cvat-ui/src/components/annotation-page/styles.scss b/cvat-ui/src/components/annotation-page/styles.scss index deb40196044..12ed5d90168 100644 --- a/cvat-ui/src/components/annotation-page/styles.scss +++ b/cvat-ui/src/components/annotation-page/styles.scss @@ -53,6 +53,12 @@ } } +.cvat-annotation-disabled-header-button { + @extend .cvat-annotation-header-button; + opacity: 0.5; + pointer-events: none; +} + .cvat-annotation-header-player-group > div { height: 54px; line-height: 0px; diff --git a/cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx b/cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx index 2b948517ab9..3d1f6c2d5b6 100644 --- a/cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx +++ b/cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx @@ -9,6 +9,8 @@ import { Input, Tooltip, Select, + Modal, + Timeline, } from 'antd'; import { SliderValue } from 'antd/lib/slider'; @@ -36,9 +38,34 @@ interface Props { frame: number; frameStep: number; playing: boolean; + saving: boolean; + savingStatuses: string[]; canvasIsReady: boolean; onChangeFrame(frame: number, playing: boolean): void; onSwitchPlay(playing: boolean): void; + onSaveAnnotation(sessionInstance: any): void; +} + +function SavingOverlay(saving: boolean, statuses: string[]): JSX.Element { + return ( + + + { + statuses.slice(0, -1) + .map(( + status: string, + id: number, + // eslint-disable-next-line react/no-array-index-key + ) => {status}) + } + + + ); } export default function AnnotationTopBarComponent(props: Props): JSX.Element { @@ -47,9 +74,12 @@ export default function AnnotationTopBarComponent(props: Props): JSX.Element { frame, frameStep, playing, + saving, + savingStatuses, canvasIsReady, onChangeFrame, onSwitchPlay, + onSaveAnnotation, } = props; if (playing && canvasIsReady) { @@ -62,6 +92,8 @@ export default function AnnotationTopBarComponent(props: Props): JSX.Element { } } + const savingOverlay = SavingOverlay(saving, savingStatuses); + return ( @@ -70,9 +102,17 @@ export default function AnnotationTopBarComponent(props: Props): JSX.Element { Menu -
- - Save +
+ => { + onSaveAnnotation(jobInstance); + }} + /> + + { saving ? 'Saving...' : 'Save' } + + { savingOverlay }
diff --git a/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx b/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx index a1f8bc88064..3923d0aa93f 100644 --- a/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx +++ b/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx @@ -4,6 +4,7 @@ import { connect } from 'react-redux'; import { changeFrameAsync, switchPlay as switchPlayAction, + saveAnnotationsAsync, } from '../../../actions/annotation-actions'; import AnnotationTopBarComponent from '../../../components/annotation-page/top-bar/top-bar'; @@ -15,11 +16,14 @@ interface StateToProps { frameStep: number; playing: boolean; canvasIsReady: boolean; + saving: boolean; + savingStatuses: string[]; } interface DispatchToProps { onChangeFrame(frame: number, playing: boolean): void; onSwitchPlay(playing: boolean): void; + onSaveAnnotation(sessionInstance: any): void; } function mapStateToProps(state: CombinedState): StateToProps { @@ -28,12 +32,23 @@ function mapStateToProps(state: CombinedState): StateToProps { settings, } = state; + const { + playing, + saving, + savingStatuses, + canvasIsReady, + frame, + jobInstance, + } = annotation; + return { - jobInstance: annotation.jobInstance, - frame: annotation.frame as number, // is number when jobInstance specified frameStep: settings.player.frameStep, - playing: annotation.playing, - canvasIsReady: annotation.canvasIsReady, + playing, + saving, + savingStatuses, + canvasIsReady, + frame, + jobInstance, }; } @@ -45,30 +60,15 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { onSwitchPlay(playing: boolean): void { dispatch(switchPlayAction(playing)); }, + onSaveAnnotation(sessionInstance: any): void { + dispatch(saveAnnotationsAsync(sessionInstance)); + }, }; } function AnnotationTopBarContainer(props: StateToProps & DispatchToProps): JSX.Element { - const { - jobInstance, - frame, - frameStep, - playing, - canvasIsReady, - onChangeFrame, - onSwitchPlay, - } = props; - return ( - + ); } diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index 463dea60412..21fd7ecd4c5 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -17,6 +17,8 @@ const defaultState: AnnotationState = { playing: false, annotations: [], frameData: null, + saving: false, + savingStatuses: [], dataFetching: false, jobFetching: false, }; @@ -70,6 +72,31 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { dataFetching: false, }; // add notification if failed } + case AnnotationActionTypes.SAVE_ANNOTATIONS: { + return { + ...state, + saving: true, + savingStatuses: [], + }; + } + case AnnotationActionTypes.SAVE_ANNOTATIONS_SUCCESS: { + return { + ...state, + saving: false, + }; + } + case AnnotationActionTypes.SAVE_ANNOTATIONS_FAILED: { + return { + ...state, + saving: false, + }; // add notification if failed + } + case AnnotationActionTypes.SAVE_ANNOTATIONS_UPDATED_STATUS: { + return { + ...state, + savingStatuses: [...state.savingStatuses, action.payload.status], + }; + } case AnnotationActionTypes.SWITCH_PLAY: { return { ...state, diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index f6bf3843b33..1323ffa1fc3 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -214,6 +214,8 @@ export interface AnnotationState { frame: number; playing: boolean; annotations: any[]; + saving: boolean; + savingStatuses: string[]; jobFetching: boolean; dataFetching: boolean; } From 5901067310bcefc9e7bcef7f62811568f3265ba4 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 14 Jan 2020 18:53:31 +0300 Subject: [PATCH 07/15] Fixed size --- cvat-canvas/src/scss/canvas.scss | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cvat-canvas/src/scss/canvas.scss b/cvat-canvas/src/scss/canvas.scss index a35674eb0b2..23788a9121f 100644 --- a/cvat-canvas/src/scss/canvas.scss +++ b/cvat-canvas/src/scss/canvas.scss @@ -84,9 +84,9 @@ polyline.cvat_canvas_shape_merging { } #cvat_canvas_wrapper { - width: 98%; - height: 98%; - margin: 10px; + width: calc(100% - 10px); + height: calc(100% - 10px); + margin: 5px; border-radius: 5px; background-color: white; overflow: hidden; From 617e2ca7a78ee63ebc22638547ea027b777dc2e2 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 14 Jan 2020 19:26:45 +0300 Subject: [PATCH 08/15] Refactoring --- .../standard-workspace/controls-side-bar.tsx | 171 ------------------ .../controls-side-bar/controls-side-bar.tsx | 91 ++++++++++ .../controls-side-bar/cursor-control.tsx | 46 +++++ .../controls-side-bar/fit-control.tsx | 30 +++ .../controls-side-bar/move-control.tsx | 49 +++++ .../controls-side-bar/resize-control.tsx | 49 +++++ .../controls-side-bar/rotate-control.tsx | 58 ++++++ .../standard-workspace/standard-workspace.tsx | 2 +- .../controls-side-bar.tsx | 6 +- 9 files changed, 327 insertions(+), 175 deletions(-) delete mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/cursor-control.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/fit-control.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/move-control.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/resize-control.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx rename cvat-ui/src/containers/annotation-page/standard-workspace/{ => controls-side-bar}/controls-side-bar.tsx (77%) diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar.tsx deleted file mode 100644 index 37bff62d426..00000000000 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import React from 'react'; - -import { - Icon, - Layout, - Tooltip, - Popover, -} from 'antd'; - -import { - CursorIcon, - MoveIcon, - RotateIcon, - FitIcon, - ZoomIcon, - RectangleIcon, - PolygonIcon, - PointIcon, - PolylineIcon, - TagIcon, - MergeIcon, - GroupIcon, - SplitIcon, -} from '../../../icons'; - -import { - ActiveControl, -} from '../../../reducers/interfaces'; - -import { - Canvas, - Rotation, -} from '../../../canvas'; - -interface Props { - canvasInstance: Canvas; - rotateAll: boolean; - activeControl: ActiveControl; -} - -export default function ControlsSideBarComponent(props: Props): JSX.Element { - const { - rotateAll, - canvasInstance, - activeControl, - } = props; - - return ( - - - { - if (activeControl !== ActiveControl.CURSOR) { - canvasInstance.cancel(); - } - }} - /> - - - - { - if (activeControl === ActiveControl.DRAG_CANVAS) { - canvasInstance.dragCanvas(false); - } else { - canvasInstance.cancel(); - canvasInstance.dragCanvas(true); - } - }} - /> - - - - - canvasInstance - .rotate(Rotation.ANTICLOCKWISE90, rotateAll)} - component={RotateIcon} - /> - - - canvasInstance - .rotate(Rotation.CLOCKWISE90, rotateAll)} - component={RotateIcon} - /> - - - )} - trigger='hover' - > - - - -
- - - canvasInstance.fit()} /> - - - - { - if (activeControl === ActiveControl.ZOOM_CANVAS) { - canvasInstance.zoomCanvas(false); - } else { - canvasInstance.cancel(); - canvasInstance.zoomCanvas(true); - } - }} - /> - - -
- - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - -
- ); -} diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx new file mode 100644 index 00000000000..d04a5af4513 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx @@ -0,0 +1,91 @@ +import React from 'react'; + +import { + Icon, + Layout, + Tooltip, +} from 'antd'; + +import { + RectangleIcon, + PolygonIcon, + PointIcon, + PolylineIcon, + TagIcon, + MergeIcon, + GroupIcon, + SplitIcon, +} from '../../../../icons'; + +import { + ActiveControl, +} from '../../../../reducers/interfaces'; + +import { + Canvas, +} from '../../../../canvas'; + +import CursorControl from './cursor-control'; +import MoveControl from './move-control'; +import RotateControl from './rotate-control'; +import FitControl from './fit-control'; +import ResizeControl from './resize-control'; + +interface Props { + canvasInstance: Canvas; + rotateAll: boolean; + activeControl: ActiveControl; +} + +export default function ControlsSideBarComponent(props: Props): JSX.Element { + return ( + + + + + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ ); +} diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/cursor-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/cursor-control.tsx new file mode 100644 index 00000000000..1d1d0a310bf --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/cursor-control.tsx @@ -0,0 +1,46 @@ +import React from 'react'; + +import { + Icon, + Tooltip, +} from 'antd'; + +import { + CursorIcon, +} from '../../../../icons'; + +import { + ActiveControl, +} from '../../../../reducers/interfaces'; + +import { + Canvas, +} from '../../../../canvas'; + +interface Props { + canvasInstance: Canvas; + activeControl: ActiveControl; +} + +export default function CursorControl(props: Props): JSX.Element { + const { + canvasInstance, + activeControl, + } = props; + + return ( + + { + if (activeControl !== ActiveControl.CURSOR) { + canvasInstance.cancel(); + } + }} + /> + + ); +} diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/fit-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/fit-control.tsx new file mode 100644 index 00000000000..ad022541d02 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/fit-control.tsx @@ -0,0 +1,30 @@ +import React from 'react'; + +import { + Icon, + Tooltip, +} from 'antd'; + +import { + FitIcon, +} from '../../../../icons'; + +import { + Canvas, +} from '../../../../canvas'; + +interface Props { + canvasInstance: Canvas; +} + +export default function FitControl(props: Props): JSX.Element { + const { + canvasInstance, + } = props; + + return ( + + canvasInstance.fit()} /> + + ); +} diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/move-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/move-control.tsx new file mode 100644 index 00000000000..34f4fefd3ec --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/move-control.tsx @@ -0,0 +1,49 @@ +import React from 'react'; + +import { + Icon, + Tooltip, +} from 'antd'; + +import { + MoveIcon, +} from '../../../../icons'; + +import { + ActiveControl, +} from '../../../../reducers/interfaces'; + +import { + Canvas, +} from '../../../../canvas'; + +interface Props { + canvasInstance: Canvas; + activeControl: ActiveControl; +} + +export default function MoveControl(props: Props): JSX.Element { + const { + canvasInstance, + activeControl, + } = props; + + return ( + + { + if (activeControl === ActiveControl.DRAG_CANVAS) { + canvasInstance.dragCanvas(false); + } else { + canvasInstance.cancel(); + canvasInstance.dragCanvas(true); + } + }} + /> + + ); +} diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/resize-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/resize-control.tsx new file mode 100644 index 00000000000..43945728ffe --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/resize-control.tsx @@ -0,0 +1,49 @@ +import React from 'react'; + +import { + Icon, + Tooltip, +} from 'antd'; + +import { + ZoomIcon, +} from '../../../../icons'; + +import { + ActiveControl, +} from '../../../../reducers/interfaces'; + +import { + Canvas, +} from '../../../../canvas'; + +interface Props { + canvasInstance: Canvas; + activeControl: ActiveControl; +} + +export default function ResizeControl(props: Props): JSX.Element { + const { + activeControl, + canvasInstance, + } = props; + + return ( + + { + if (activeControl === ActiveControl.ZOOM_CANVAS) { + canvasInstance.zoomCanvas(false); + } else { + canvasInstance.cancel(); + canvasInstance.zoomCanvas(true); + } + }} + /> + + ); +} diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx new file mode 100644 index 00000000000..479e4f24878 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx @@ -0,0 +1,58 @@ +import React from 'react'; + +import { + Icon, + Tooltip, + Popover, +} from 'antd'; + +import { + RotateIcon, +} from '../../../../icons'; + +import { + Rotation, + Canvas, +} from '../../../../canvas'; + +interface Props { + canvasInstance: Canvas; + rotateAll: boolean; +} + +export default function RotateControl(props: Props): JSX.Element { + const { + rotateAll, + canvasInstance, + } = props; + + return ( + + + canvasInstance + .rotate(Rotation.ANTICLOCKWISE90, rotateAll)} + component={RotateIcon} + /> + + + canvasInstance + .rotate(Rotation.CLOCKWISE90, rotateAll)} + component={RotateIcon} + /> + + + )} + trigger='hover' + > + + + ); +} 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 09b490b7597..bdbc842c784 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 @@ -8,7 +8,7 @@ import { import { Canvas } from '../../../canvas'; import CanvasWrapperContainer from '../../../containers/annotation-page/standard-workspace/canvas-wrapper'; -import ControlsSideBarContainer from '../../../containers/annotation-page/standard-workspace/controls-side-bar'; +import ControlsSideBarContainer from '../../../containers/annotation-page/standard-workspace/controls-side-bar/controls-side-bar'; import ObjectSideBarComponent from './objects-side-bar/objects-side-bar'; interface Props { diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx similarity index 77% rename from cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar.tsx rename to cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx index 4581677ddac..fc4c024b976 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx @@ -1,13 +1,13 @@ import React from 'react'; import { connect } from 'react-redux'; -import { Canvas } from '../../../canvas'; +import { Canvas } from '../../../../canvas'; -import ControlsSideBarComponent from '../../../components/annotation-page/standard-workspace/controls-side-bar'; +import ControlsSideBarComponent from '../../../../components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar'; import { ActiveControl, CombinedState, -} from '../../../reducers/interfaces'; +} from '../../../../reducers/interfaces'; interface StateToProps { From fe8d7df23499d1081e822fbc80751e763b95f9c6 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 14 Jan 2020 19:43:48 +0300 Subject: [PATCH 09/15] Added couple of notifications --- cvat-ui/src/components/cvat-app.tsx | 9 ++++- cvat-ui/src/reducers/interfaces.ts | 4 +++ cvat-ui/src/reducers/notifications-reducer.ts | 35 +++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/cvat-ui/src/components/cvat-app.tsx b/cvat-ui/src/components/cvat-app.tsx index e8eabc3d7c1..952f783396f 100644 --- a/cvat-ui/src/components/cvat-app.tsx +++ b/cvat-ui/src/components/cvat-app.tsx @@ -155,13 +155,14 @@ export default class CVATApplication extends React.PureComponent { const { users } = notifications.errors; const { share } = notifications.errors; const { models } = notifications.errors; + const { annotation } = notifications.errors; const shown = !!auth.authorized || !!auth.login || !!auth.logout || !!auth.register || !!tasks.fetching || !!tasks.updating || !!tasks.dumping || !!tasks.loading || !!tasks.exporting || !!tasks.deleting || !!tasks.creating || !!formats.fetching || !!users.fetching || !!share.fetching || !!models.creating || !!models.starting || !!models.fetching || !!models.deleting || !!models.inferenceStatusFetching - || !!models.metaFetching; + || !!models.metaFetching || !!annotation.frameFetching || !!annotation.saving; if (auth.authorized) { showError(auth.authorized.message, auth.authorized.reason); @@ -226,6 +227,12 @@ export default class CVATApplication extends React.PureComponent { models.inferenceStatusFetching.reason, ); } + if (annotation.frameFetching) { + showError(annotation.frameFetching.message, annotation.frameFetching.reason); + } + if (annotation.saving) { + showError(annotation.saving.message, annotation.saving.reason); + } if (shown) { resetErrors(); diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index 1323ffa1fc3..3dd4edbf08f 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -188,6 +188,10 @@ export interface NotificationsState { metaFetching: null | ErrorState; inferenceStatusFetching: null | ErrorState; }; + annotation: { + saving: null | ErrorState; + frameFetching: null | ErrorState; + }; }; messages: { tasks: { diff --git a/cvat-ui/src/reducers/notifications-reducer.ts b/cvat-ui/src/reducers/notifications-reducer.ts index e6fa3487386..02ada707394 100644 --- a/cvat-ui/src/reducers/notifications-reducer.ts +++ b/cvat-ui/src/reducers/notifications-reducer.ts @@ -6,6 +6,7 @@ import { ModelsActionTypes } from '../actions/models-actions'; import { ShareActionTypes } from '../actions/share-actions'; import { TasksActionTypes } from '../actions/tasks-actions'; import { UsersActionTypes } from '../actions/users-actions'; +import { AnnotationActionTypes } from '../actions/annotation-actions'; import { NotificationsActionType } from '../actions/notification-actions'; import { NotificationsState } from './interfaces'; @@ -44,6 +45,10 @@ const defaultState: NotificationsState = { metaFetching: null, inferenceStatusFetching: null, }, + annotation: { + saving: null, + frameFetching: null, + }, }, messages: { tasks: { @@ -405,6 +410,36 @@ export default function (state = defaultState, action: AnyAction): Notifications }, }; } + case AnnotationActionTypes.CHANGE_FRAME_FAILED: { + return { + ...state, + errors: { + ...state.errors, + annotation: { + ...state.errors.annotation, + frameFetching: { + message: `Could not receive frame ${action.payload.frame}`, + reason: action.payload.error.toString(), + }, + }, + }, + }; + } + case AnnotationActionTypes.SAVE_ANNOTATIONS_FAILED: { + return { + ...state, + errors: { + ...state.errors, + annotation: { + ...state.errors.annotation, + saving: { + message: 'Could not save annotations', + reason: action.payload.error.toString(), + }, + }, + }, + }; + } case NotificationsActionType.RESET_ERRORS: { return { ...state, From cd42194cb38bf6b2be5b9b21dc2e3fbad8d40892 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 15 Jan 2020 17:44:36 +0300 Subject: [PATCH 10/15] Basic shape drawing --- cvat-core/src/annotations-objects.js | 18 +- cvat-ui/.eslintrc.js | 6 +- cvat-ui/package-lock.json | 75 ++++++++ cvat-ui/package.json | 2 + cvat-ui/src/actions/annotation-actions.ts | 55 +++++- cvat-ui/src/actions/auth-actions.ts | 2 +- cvat-ui/src/actions/formats-actions.ts | 2 +- cvat-ui/src/actions/models-actions.ts | 4 +- cvat-ui/src/actions/plugins-actions.ts | 4 +- cvat-ui/src/actions/settings-actions.ts | 2 +- cvat-ui/src/actions/share-actions.ts | 4 +- cvat-ui/src/actions/tasks-actions.ts | 5 +- cvat-ui/src/actions/users-actions.ts | 2 +- .../annotation-page/annotation-page.tsx | 4 +- .../standard-workspace/canvas-wrapper.tsx | 57 +++++- .../controls-side-bar/controls-side-bar.tsx | 51 +++--- .../controls-side-bar/cursor-control.tsx | 6 +- .../controls-side-bar/draw-points-control.tsx | 66 +++++++ .../draw-polygon-control.tsx | 65 +++++++ .../draw-polyline-control.tsx | 65 +++++++ .../draw-rectangle-control.tsx | 65 +++++++ .../draw-shape-popover-content.tsx | 167 ++++++++++++++++++ .../controls-side-bar/fit-control.tsx | 4 +- .../controls-side-bar/move-control.tsx | 6 +- .../controls-side-bar/resize-control.tsx | 6 +- .../controls-side-bar/rotate-control.tsx | 4 +- .../standard-workspace/standard-workspace.tsx | 6 +- .../standard-workspace/styles.scss | 33 ++++ .../create-model-content.tsx | 9 +- .../create-model-page/create-model-page.tsx | 3 +- .../advanced-configuration-form.tsx | 2 +- .../create-task-page/create-task-content.tsx | 2 +- cvat-ui/src/components/cvat-app.tsx | 22 +-- cvat-ui/src/components/header/header.tsx | 4 +- .../components/labels-editor/label-form.tsx | 3 +- .../model-runner-modal/model-runner-modal.tsx | 2 +- .../models-page/built-model-item.tsx | 2 +- .../models-page/built-models-list.tsx | 2 +- .../src/components/models-page/empty-list.tsx | 2 +- .../models-page/uploaded-model-item.tsx | 4 +- .../models-page/uploaded-models-list.tsx | 3 +- .../register-page/register-form.tsx | 2 +- .../settings-page/player-settings.tsx | 4 +- .../settings-page/settings-page.tsx | 4 +- cvat-ui/src/components/task-page/details.tsx | 6 +- cvat-ui/src/components/task-page/job-list.tsx | 3 +- .../src/components/task-page/task-page.tsx | 8 +- cvat-ui/src/components/task-page/top-bar.tsx | 4 +- .../src/components/tasks-page/empty-list.tsx | 2 +- .../src/components/tasks-page/task-item.tsx | 6 +- .../src/components/tasks-page/task-list.tsx | 4 +- .../src/components/tasks-page/tasks-page.tsx | 7 +- .../containers/actions-menu/actions-menu.tsx | 8 +- .../annotation-page/annotation-page.tsx | 6 +- .../standard-workspace/canvas-wrapper.tsx | 29 ++- .../controls-side-bar/controls-side-bar.tsx | 45 ++++- .../standard-workspace/standard-workspace.tsx | 6 +- .../annotation-page/top-bar/top-bar.tsx | 6 +- .../create-model-page/create-model-page.tsx | 6 +- .../create-task-page/create-task-page.tsx | 8 +- .../containers/file-manager/file-manager.tsx | 6 +- cvat-ui/src/containers/header/header.tsx | 6 +- .../src/containers/login-page/login-page.tsx | 6 +- .../model-runner-dialog.tsx | 6 +- .../containers/models-page/models-page.tsx | 6 +- .../register-page/register-page.tsx | 6 +- .../settings-page/player-settings.tsx | 6 +- .../settings-page/workspace-settings.tsx | 4 +- cvat-ui/src/containers/task-page/details.tsx | 6 +- cvat-ui/src/containers/task-page/job-list.tsx | 6 +- .../src/containers/task-page/task-page.tsx | 6 +- .../src/containers/tasks-page/task-item.tsx | 6 +- .../src/containers/tasks-page/tasks-list.tsx | 6 +- .../src/containers/tasks-page/tasks-page.tsx | 6 +- cvat-ui/src/{canvas.ts => cvat-canvas.ts} | 0 cvat-ui/src/{core.ts => cvat-core.ts} | 0 cvat-ui/src/{store.ts => cvat-store.ts} | 0 cvat-ui/src/index.tsx | 2 +- cvat-ui/src/reducers/annotation-reducer.ts | 53 +++++- cvat-ui/src/reducers/auth-reducer.ts | 2 +- cvat-ui/src/reducers/formats-reducer.ts | 4 +- cvat-ui/src/reducers/interfaces.ts | 25 ++- cvat-ui/src/reducers/models-reducer.ts | 4 +- cvat-ui/src/reducers/notifications-reducer.ts | 16 +- cvat-ui/src/reducers/plugins-reducer.ts | 6 +- cvat-ui/src/reducers/settings-reducer.ts | 2 +- cvat-ui/src/reducers/share-reducer.ts | 4 +- cvat-ui/src/reducers/tasks-reducer.ts | 4 +- cvat-ui/src/reducers/users-reducer.ts | 6 +- cvat-ui/src/utils/git-utils.ts | 2 +- cvat-ui/src/utils/plugin-checker.ts | 4 +- cvat-ui/tsconfig.json | 6 +- cvat-ui/webpack.config.js | 2 + 93 files changed, 1004 insertions(+), 230 deletions(-) create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-points-control.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-polygon-control.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-polyline-control.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-rectangle-control.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover-content.tsx rename cvat-ui/src/{canvas.ts => cvat-canvas.ts} (100%) rename cvat-ui/src/{core.ts => cvat-core.ts} (100%) rename cvat-ui/src/{store.ts => cvat-store.ts} (100%) diff --git a/cvat-core/src/annotations-objects.js b/cvat-core/src/annotations-objects.js index ba4508dffda..b6bd1a6b249 100644 --- a/cvat-core/src/annotations-objects.js +++ b/cvat-core/src/annotations-objects.js @@ -716,9 +716,21 @@ // Add/update keyframe if (positionUpdated || (updated.keyframe && data.keyframe)) { - // Remove all cache after this keyframe because it have just become outdated - for (const cacheFrame in this.cache) { - if (+cacheFrame > frame) { + // Remove affected cached frames + const { + leftFrame, + rightFrame, + } = this.neighborsFrames(frame); + for (const cacheFrame of Object.keys(this.cache)) { + if (leftFrame === null && +cacheFrame < frame) { + delete this.cache[cacheFrame]; + } else if (+cacheFrame < frame && +cacheFrame > leftFrame) { + delete this.cache[cacheFrame]; + } + + if (rightFrame === null && +cacheFrame > frame) { + delete this.cache[cacheFrame]; + } else if (+cacheFrame > frame && +cacheFrame < rightFrame) { delete this.cache[cacheFrame]; } } diff --git a/cvat-ui/.eslintrc.js b/cvat-ui/.eslintrc.js index f40cac1879f..279f581cca0 100644 --- a/cvat-ui/.eslintrc.js +++ b/cvat-ui/.eslintrc.js @@ -39,9 +39,9 @@ module.exports = { }, 'settings': { 'import/resolver': { - 'node': { - 'extensions': ['.tsx', '.ts', '.jsx', '.js', '.json'], - }, + 'typescript': { + 'directory': './tsconfig.json' + } }, }, }; diff --git a/cvat-ui/package-lock.json b/cvat-ui/package-lock.json index 41ef8cc917d..d7edd37f5a0 100644 --- a/cvat-ui/package-lock.json +++ b/cvat-ui/package-lock.json @@ -1002,6 +1002,12 @@ "integrity": "sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==", "dev": true }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -3849,6 +3855,19 @@ } } }, + "eslint-import-resolver-typescript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.0.0.tgz", + "integrity": "sha512-bT5Frpl8UWoHBtY25vKUOMoVIMlJQOMefHLyQ4Tz3MQpIZ2N6yYKEEIHMo38bszBNUuMBW6M3+5JNYxeiGFH4w==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "is-glob": "^4.0.1", + "resolve": "^1.12.0", + "tiny-glob": "^0.2.6", + "tsconfig-paths": "^3.9.0" + } + }, "eslint-module-utils": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.4.1.tgz", @@ -5408,6 +5427,12 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" }, + "globalyzer": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.4.tgz", + "integrity": "sha512-LeguVWaxgHN0MNbWC6YljNMzHkrCny9fzjmEUdnF1kQ7wATFD1RHFRqA1qxaX2tgxGENlcxjOflopBwj3YZiXA==", + "dev": true + }, "globby": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", @@ -5429,6 +5454,12 @@ } } }, + "globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true + }, "globule": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", @@ -11116,6 +11147,16 @@ "setimmediate": "^1.0.4" } }, + "tiny-glob": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.6.tgz", + "integrity": "sha512-A7ewMqPu1B5PWwC3m7KVgAu96Ch5LA0w4SnEN/LbDREj/gAD0nPWboRbn8YoP9ISZXqeNAlMvKSKoEuhcfK3Pw==", + "dev": true, + "requires": { + "globalyzer": "^0.1.0", + "globrex": "^0.1.1" + } + }, "tiny-invariant": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz", @@ -11234,6 +11275,40 @@ "glob": "^7.1.2" } }, + "tsconfig-paths": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", + "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + } + } + }, + "tsconfig-paths-webpack-plugin": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-3.2.0.tgz", + "integrity": "sha512-S/gOOPOkV8rIL4LurZ1vUdYCVgo15iX9ZMJ6wx6w2OgcpT/G4wMyHB6WM+xheSqGMrWKuxFul+aXpCju3wmj/g==", + "dev": true, + "requires": { + "chalk": "^2.3.0", + "enhanced-resolve": "^4.0.0", + "tsconfig-paths": "^3.4.0" + } + }, "tslib": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", diff --git a/cvat-ui/package.json b/cvat-ui/package.json index eb3ca983d9e..8d6f75f58ce 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -20,6 +20,7 @@ "babel-plugin-import": "^1.12.2", "css-loader": "^3.2.0", "eslint-config-airbnb-typescript": "^4.0.1", + "eslint-import-resolver-typescript": "^2.0.0", "eslint-plugin-import": "^2.18.2", "eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-react": "^7.17.0", @@ -33,6 +34,7 @@ "react-svg-loader": "^3.0.3", "sass-loader": "^8.0.0", "style-loader": "^1.0.0", + "tsconfig-paths-webpack-plugin": "^3.2.0", "typescript": "^3.7.3", "webpack": "^4.41.2", "webpack-cli": "^3.3.8", diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 20e11a7fa1a..e7c5b476d82 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -3,11 +3,14 @@ import { ThunkAction } from 'redux-thunk'; import { CombinedState, + ActiveControl, + ShapeType, + ObjectType, Task, -} from '../reducers/interfaces'; +} from 'reducers/interfaces'; -import getCore from '../core'; -import { getCVATStore } from '../store'; +import getCore from 'cvat-core'; +import { getCVATStore } from 'cvat-store'; const cvat = getCore(); @@ -26,7 +29,10 @@ export enum AnnotationActionTypes { CONFIRM_CANVAS_READY = 'CONFIRM_CANVAS_READY', DRAG_CANVAS = 'DRAG_CANVAS', ZOOM_CANVAS = 'ZOOM_CANVAS', + DRAW_SHAPE = 'DRAW_SHAPE', + SHAPE_DRAWN = 'SHAPE_DRAWN', RESET_CANVAS = 'RESET_CANVAS', + ANNOTATIONS_UPDATED = 'ANNOTATIONS_UPDATED', } export function switchPlay(playing: boolean): AnyAction { @@ -195,3 +201,46 @@ ThunkAction, {}, {}, AnyAction> { } }; } + +export function drawShape( + shapeType: ShapeType, + labelID: number, + objectType: ObjectType, + points?: number, +): AnyAction { + let activeControl = ActiveControl.DRAW_RECTANGLE; + if (shapeType === ShapeType.POLYGON) { + activeControl = ActiveControl.DRAW_POLYGON; + } else if (shapeType === ShapeType.POLYLINE) { + activeControl = ActiveControl.DRAW_POLYLINE; + } else if (shapeType === ShapeType.POINTS) { + activeControl = ActiveControl.DRAW_POINTS; + } + + return { + type: AnnotationActionTypes.DRAW_SHAPE, + payload: { + shapeType, + labelID, + objectType, + points, + activeControl, + }, + }; +} + +export function shapeDrawn(): AnyAction { + return { + type: AnnotationActionTypes.SHAPE_DRAWN, + payload: {}, + }; +} + +export function annotationsUpdated(annotations: any[]): AnyAction { + return { + type: AnnotationActionTypes.ANNOTATIONS_UPDATED, + payload: { + annotations, + }, + }; +} diff --git a/cvat-ui/src/actions/auth-actions.ts b/cvat-ui/src/actions/auth-actions.ts index a8b9748f03b..d225f2d5982 100644 --- a/cvat-ui/src/actions/auth-actions.ts +++ b/cvat-ui/src/actions/auth-actions.ts @@ -1,7 +1,7 @@ import { AnyAction, Dispatch, ActionCreator } from 'redux'; import { ThunkAction } from 'redux-thunk'; -import getCore from '../core'; +import getCore from 'cvat-core'; const cvat = getCore(); diff --git a/cvat-ui/src/actions/formats-actions.ts b/cvat-ui/src/actions/formats-actions.ts index a3e74685a9d..d34510c87d6 100644 --- a/cvat-ui/src/actions/formats-actions.ts +++ b/cvat-ui/src/actions/formats-actions.ts @@ -1,7 +1,7 @@ import { AnyAction, Dispatch, ActionCreator } from 'redux'; import { ThunkAction } from 'redux-thunk'; -import getCore from '../core'; +import getCore from 'cvat-core'; const cvat = getCore(); diff --git a/cvat-ui/src/actions/models-actions.ts b/cvat-ui/src/actions/models-actions.ts index e8b60da5609..f120d11d923 100644 --- a/cvat-ui/src/actions/models-actions.ts +++ b/cvat-ui/src/actions/models-actions.ts @@ -1,8 +1,8 @@ import { AnyAction, Dispatch, ActionCreator } from 'redux'; import { ThunkAction } from 'redux-thunk'; -import getCore from '../core'; -import { getCVATStore } from '../store'; +import getCore from 'cvat-core'; +import { getCVATStore } from 'cvat-store'; import { Model, ModelFiles, diff --git a/cvat-ui/src/actions/plugins-actions.ts b/cvat-ui/src/actions/plugins-actions.ts index 0e27013fa4f..26e9faaac72 100644 --- a/cvat-ui/src/actions/plugins-actions.ts +++ b/cvat-ui/src/actions/plugins-actions.ts @@ -1,7 +1,7 @@ import { AnyAction, Dispatch, ActionCreator } from 'redux'; import { ThunkAction } from 'redux-thunk'; -import { SupportedPlugins } from '../reducers/interfaces'; -import PluginChecker from '../utils/plugin-checker'; +import { SupportedPlugins } from 'reducers/interfaces'; +import PluginChecker from 'utils/plugin-checker'; export enum PluginsActionTypes { CHECK_PLUGINS = 'CHECK_PLUGINS', diff --git a/cvat-ui/src/actions/settings-actions.ts b/cvat-ui/src/actions/settings-actions.ts index 6371f70f1fc..33802c780f8 100644 --- a/cvat-ui/src/actions/settings-actions.ts +++ b/cvat-ui/src/actions/settings-actions.ts @@ -1,7 +1,7 @@ import { AnyAction } from 'redux'; import { GridColor, -} from '../reducers/interfaces'; +} from 'reducers/interfaces'; export enum SettingsActionTypes { SWITCH_ROTATE_ALL = 'SWITCH_ROTATE_ALL', diff --git a/cvat-ui/src/actions/share-actions.ts b/cvat-ui/src/actions/share-actions.ts index 47fbcb90145..978f4fa8655 100644 --- a/cvat-ui/src/actions/share-actions.ts +++ b/cvat-ui/src/actions/share-actions.ts @@ -1,8 +1,8 @@ import { AnyAction, Dispatch, ActionCreator } from 'redux'; import { ThunkAction } from 'redux-thunk'; -import { ShareFileInfo } from '../reducers/interfaces'; -import getCore from '../core'; +import { ShareFileInfo } from 'reducers/interfaces'; +import getCore from 'cvat-core'; const core = getCore(); diff --git a/cvat-ui/src/actions/tasks-actions.ts b/cvat-ui/src/actions/tasks-actions.ts index 015c12ff336..fa9c3a5851f 100644 --- a/cvat-ui/src/actions/tasks-actions.ts +++ b/cvat-ui/src/actions/tasks-actions.ts @@ -1,10 +1,9 @@ import { AnyAction, Dispatch, ActionCreator } from 'redux'; import { ThunkAction } from 'redux-thunk'; -import { TasksQuery } from '../reducers/interfaces'; +import { TasksQuery } from 'reducers/interfaces'; +import getCore from 'cvat-core'; import { getInferenceStatusAsync } from './models-actions'; -import getCore from '../core'; - const cvat = getCore(); export enum TasksActionTypes { diff --git a/cvat-ui/src/actions/users-actions.ts b/cvat-ui/src/actions/users-actions.ts index 3d75197a5c2..d69ca0246d1 100644 --- a/cvat-ui/src/actions/users-actions.ts +++ b/cvat-ui/src/actions/users-actions.ts @@ -1,7 +1,7 @@ import { AnyAction, Dispatch, ActionCreator } from 'redux'; import { ThunkAction } from 'redux-thunk'; -import getCore from '../core'; +import getCore from 'cvat-core'; const core = getCore(); diff --git a/cvat-ui/src/components/annotation-page/annotation-page.tsx b/cvat-ui/src/components/annotation-page/annotation-page.tsx index bf6155c8dc5..11e32c1349c 100644 --- a/cvat-ui/src/components/annotation-page/annotation-page.tsx +++ b/cvat-ui/src/components/annotation-page/annotation-page.tsx @@ -7,8 +7,8 @@ import { Result, } from 'antd'; -import AnnotationTopBarContainer from '../../containers/annotation-page/top-bar/top-bar'; -import StandardWorkspaceContainer from '../../containers/annotation-page/standard-workspace/standard-workspace'; +import AnnotationTopBarContainer from 'containers/annotation-page/top-bar/top-bar'; +import StandardWorkspaceContainer from 'containers/annotation-page/standard-workspace/standard-workspace'; interface Props { jobInstance: any | null | undefined; 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 93f9e83da0f..4db78a730fd 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 @@ -6,11 +6,16 @@ import { import { GridColor, -} from '../../../reducers/interfaces'; + ObjectType, +} from 'reducers/interfaces'; import { Canvas, -} from '../../../canvas'; +} from 'cvat-canvas'; + +import getCore from 'cvat-core'; + +const cvat = getCore(); interface Props { canvasInstance: Canvas; @@ -22,10 +27,14 @@ interface Props { gridSize: number; gridColor: GridColor; gridOpacity: number; + activeLabelID: number; + activeObjectType: ObjectType; onSetupCanvas: () => void; onDragCanvas: (enabled: boolean) => void; onZoomCanvas: (enabled: boolean) => void; + onShapeDrawn: () => void; onResetCanvas: () => void; + onAnnotationsUpdated: (annotations: any[]) => void; } export default class CanvasWrapperComponent extends React.PureComponent { @@ -81,12 +90,45 @@ export default class CanvasWrapperComponent extends React.PureComponent { this.updateCanvas(); } - private async onShapeEdit(event: any): Promise { + private async onShapeDrawn(event: any): Promise { + const { + jobInstance, + activeLabelID, + activeObjectType, + frame, + onShapeDrawn, + onAnnotationsUpdated, + } = this.props; + + onShapeDrawn(); + + const { state } = event.detail; + if (!state.objectType) { + state.objectType = activeObjectType; + } + + if (!state.label) { + [state.label] = jobInstance.task.labels + .filter((label: any) => label.id === activeLabelID); + } + + if (!state.occluded) { + state.occluded = false; + } + + state.frame = frame; + const objectState = new cvat.classes.ObjectState(state); + await jobInstance.annotations.put([objectState]); + + const annotations = await jobInstance.annotations.get(frame); + onAnnotationsUpdated(annotations); + } + + private async onShapeEdited(event: any): Promise { const { - canvasInstance, jobInstance, - frameData, frame, + onAnnotationsUpdated, } = this.props; const { @@ -97,7 +139,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { state.save(); const annotations = await jobInstance.annotations.get(frame); - canvasInstance.setup(frameData, annotations); + onAnnotationsUpdated(annotations); } private updateCanvas(): void { @@ -192,7 +234,8 @@ export default class CanvasWrapperComponent extends React.PureComponent { } }); - canvasInstance.html().addEventListener('canvas.edited', this.onShapeEdit.bind(this)); + canvasInstance.html().addEventListener('canvas.drawn', this.onShapeDrawn.bind(this)); + canvasInstance.html().addEventListener('canvas.edited', this.onShapeEdited.bind(this)); } public render(): JSX.Element { diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx index d04a5af4513..7706a3ca1d5 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx @@ -7,34 +7,46 @@ import { } from 'antd'; import { - RectangleIcon, - PolygonIcon, - PointIcon, - PolylineIcon, + ActiveControl, + ShapeType, + ObjectType, +} from 'reducers/interfaces'; + +import { TagIcon, MergeIcon, GroupIcon, SplitIcon, -} from '../../../../icons'; - -import { - ActiveControl, -} from '../../../../reducers/interfaces'; +} from 'icons'; import { Canvas, -} from '../../../../canvas'; +} from 'cvat-canvas'; import CursorControl from './cursor-control'; import MoveControl from './move-control'; import RotateControl from './rotate-control'; import FitControl from './fit-control'; import ResizeControl from './resize-control'; +import DrawRectangleControl from './draw-rectangle-control'; +import DrawPolygonControl from './draw-polygon-control'; +import DrawPolylineControl from './draw-polyline-control'; +import DrawPointsControl from './draw-points-control'; interface Props { canvasInstance: Canvas; rotateAll: boolean; activeControl: ActiveControl; + + labels: { + [index: number]: string; + }; + onDrawStart( + shapeType: ShapeType, + labelID: number, + objectType: ObjectType, + points?: number, + ): void; } export default function ControlsSideBarComponent(props: Props): JSX.Element { @@ -55,21 +67,10 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element {
- - - - - - - - - - - - - - - + + + + diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/cursor-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/cursor-control.tsx index 1d1d0a310bf..f8c53e1f563 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/cursor-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/cursor-control.tsx @@ -7,15 +7,15 @@ import { import { CursorIcon, -} from '../../../../icons'; +} from 'icons'; import { ActiveControl, -} from '../../../../reducers/interfaces'; +} from 'reducers/interfaces'; import { Canvas, -} from '../../../../canvas'; +} from 'cvat-canvas'; interface Props { canvasInstance: Canvas; diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-points-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-points-control.tsx new file mode 100644 index 00000000000..b54854fb333 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-points-control.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { + Popover, + Icon, +} from 'antd'; + +import { Canvas } from 'cvat-canvas'; +import { PointIcon } from 'icons'; +import { + ShapeType, + ActiveControl, + ObjectType, +} from 'reducers/interfaces'; + +import DrawShapePopoverContent from './draw-shape-popover-content'; + +interface Props { + canvasInstance: Canvas; + activeControl: ActiveControl; + labels: { + [index: number]: string; + }; + onDrawStart( + shapeType: ShapeType, + labelID: number, + objectType: ObjectType, + points?: number, + ): void; +} + +export default function DrawRectangleControl(props: Props): JSX.Element { + const { + canvasInstance, + activeControl, + } = props; + + if (activeControl === ActiveControl.DRAW_POINTS) { + return ( + { + canvasInstance.draw({ enabled: false }); + }} + component={PointIcon} + /> + ); + } + + + return ( + + )} + > + + + ); +} diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-polygon-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-polygon-control.tsx new file mode 100644 index 00000000000..61b43bd26c9 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-polygon-control.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { + Popover, + Icon, +} from 'antd'; + +import { Canvas } from 'cvat-canvas'; +import { PolygonIcon } from 'icons'; +import { + ShapeType, + ActiveControl, + ObjectType, +} from 'reducers/interfaces'; + +import DrawShapePopoverContent from './draw-shape-popover-content'; + +interface Props { + canvasInstance: Canvas; + activeControl: ActiveControl; + labels: { + [index: number]: string; + }; + onDrawStart( + shapeType: ShapeType, + labelID: number, + objectType: ObjectType, + points?: number, + ): void; +} + +export default function DrawRectangleControl(props: Props): JSX.Element { + const { + canvasInstance, + activeControl, + } = props; + + if (activeControl === ActiveControl.DRAW_POLYGON) { + return ( + { + canvasInstance.draw({ enabled: false }); + }} + component={PolygonIcon} + /> + ); + } + + return ( + + )} + > + + + ); +} diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-polyline-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-polyline-control.tsx new file mode 100644 index 00000000000..87c70cca438 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-polyline-control.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { + Popover, + Icon, +} from 'antd'; + +import { Canvas } from 'cvat-canvas'; +import { PolylineIcon } from 'icons'; +import { + ShapeType, + ActiveControl, + ObjectType, +} from 'reducers/interfaces'; + +import DrawShapePopoverContent from './draw-shape-popover-content'; + +interface Props { + canvasInstance: Canvas; + activeControl: ActiveControl; + labels: { + [index: number]: string; + }; + onDrawStart( + shapeType: ShapeType, + labelID: number, + objectType: ObjectType, + points?: number, + ): void; +} + +export default function DrawRectangleControl(props: Props): JSX.Element { + const { + canvasInstance, + activeControl, + } = props; + + if (activeControl === ActiveControl.DRAW_POLYLINE) { + return ( + { + canvasInstance.draw({ enabled: false }); + }} + component={PolylineIcon} + /> + ); + } + + return ( + + )} + > + + + ); +} diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-rectangle-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-rectangle-control.tsx new file mode 100644 index 00000000000..67ce676218d --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-rectangle-control.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { + Popover, + Icon, +} from 'antd'; + +import { Canvas } from 'cvat-canvas'; +import { RectangleIcon } from 'icons'; +import { + ShapeType, + ActiveControl, + ObjectType, +} from 'reducers/interfaces'; + +import DrawShapePopoverContent from './draw-shape-popover-content'; + +interface Props { + canvasInstance: Canvas; + activeControl: ActiveControl; + labels: { + [index: number]: string; + }; + onDrawStart( + shapeType: ShapeType, + labelID: number, + objectType: ObjectType, + points?: number, + ): void; +} + +export default function DrawRectangleControl(props: Props): JSX.Element { + const { + canvasInstance, + activeControl, + } = props; + + if (activeControl === ActiveControl.DRAW_RECTANGLE) { + return ( + { + canvasInstance.draw({ enabled: false }); + }} + component={RectangleIcon} + /> + ); + } + + return ( + + )} + > + + + ); +} diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover-content.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover-content.tsx new file mode 100644 index 00000000000..95b7255eb76 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover-content.tsx @@ -0,0 +1,167 @@ +import React from 'react'; + +import { + Row, + Col, + Select, + Button, + InputNumber, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; + +import { + ShapeType, + ObjectType, +} from 'reducers/interfaces'; + +import { + Canvas, +} from 'cvat-canvas'; + +interface Props { + canvasInstance: Canvas; + shapeType: ShapeType; + labels: { + [index: number]: string; + }; + onDrawStart( + shapeType: ShapeType, + labelID: number, + objectType: ObjectType, + points?: number, + ): void; +} + +interface State { + numberOfPoints?: number; + selectedLabeID: number; +} + +export default class DrawShapePopoverContent extends React.PureComponent { + constructor(props: Props) { + super(props); + const defaultLabelID = +Object.keys(props.labels)[0]; + this.state = { + selectedLabeID: defaultLabelID, + }; + } + + public render(): JSX.Element { + const { + numberOfPoints, + selectedLabeID, + } = this.state; + + const { + shapeType, + labels, + onDrawStart, + canvasInstance, + } = this.props; + + let minimumPoints = 0; + if (shapeType === ShapeType.POLYGON) { + minimumPoints = 3; + } else if (shapeType === ShapeType.POLYLINE) { + minimumPoints = 2; + } else if (shapeType === ShapeType.POINTS) { + minimumPoints = 1; + } + + return ( +
+ + + {`Create new ${shapeType}`} + + + + + Label + + + + + + + + { + shapeType !== ShapeType.RECTANGLE && ( + + + Number of points: + + + { + this.setState({ + numberOfPoints: value, + }); + }} + className='cvat-draw-shape-popover-points-selector' + min={minimumPoints} + step={1} + /> + + + ) + } + + + + + + + + +
+ ); + } +} diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/fit-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/fit-control.tsx index ad022541d02..dc35d4d578a 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/fit-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/fit-control.tsx @@ -7,11 +7,11 @@ import { import { FitIcon, -} from '../../../../icons'; +} from 'icons'; import { Canvas, -} from '../../../../canvas'; +} from 'cvat-canvas'; interface Props { canvasInstance: Canvas; diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/move-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/move-control.tsx index 34f4fefd3ec..82fafb5f58c 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/move-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/move-control.tsx @@ -7,15 +7,15 @@ import { import { MoveIcon, -} from '../../../../icons'; +} from 'icons'; import { ActiveControl, -} from '../../../../reducers/interfaces'; +} from 'reducers/interfaces'; import { Canvas, -} from '../../../../canvas'; +} from 'cvat-canvas'; interface Props { canvasInstance: Canvas; diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/resize-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/resize-control.tsx index 43945728ffe..6922195c095 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/resize-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/resize-control.tsx @@ -7,15 +7,15 @@ import { import { ZoomIcon, -} from '../../../../icons'; +} from 'icons'; import { ActiveControl, -} from '../../../../reducers/interfaces'; +} from 'reducers/interfaces'; import { Canvas, -} from '../../../../canvas'; +} from 'cvat-canvas'; interface Props { canvasInstance: Canvas; diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx index 479e4f24878..10cfcb765e9 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx @@ -8,12 +8,12 @@ import { import { RotateIcon, -} from '../../../../icons'; +} from 'icons'; import { Rotation, Canvas, -} from '../../../../canvas'; +} from 'cvat-canvas'; interface Props { canvasInstance: Canvas; 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 bdbc842c784..8f41e67a6ae 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 @@ -5,10 +5,10 @@ import { Layout, } from 'antd'; -import { Canvas } from '../../../canvas'; +import { Canvas } from 'cvat-canvas'; -import CanvasWrapperContainer from '../../../containers/annotation-page/standard-workspace/canvas-wrapper'; -import ControlsSideBarContainer from '../../../containers/annotation-page/standard-workspace/controls-side-bar/controls-side-bar'; +import CanvasWrapperContainer from 'containers/annotation-page/standard-workspace/canvas-wrapper'; +import ControlsSideBarContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/controls-side-bar'; import ObjectSideBarComponent from './objects-side-bar/objects-side-bar'; interface Props { 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 a93e3843bf7..a7ba264bb02 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss +++ b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss @@ -66,4 +66,37 @@ .cvat-annotation-page-controls-rotate-right > svg { transform: scaleX(-1); +} + +.cvat-draw-shape-popover > +.ant-popover-content > +.ant-popover-inner > div > +.ant-popover-inner-content { + padding: 0px; +} + +.cvat-draw-shape-popover-points-selector { + width: 100%; +} + +.cvat-draw-shape-popover-content { + padding: 10px; + border-radius: 5px; + background: $background-color-2; + width: 250px; + > div { + margin-top: 5px; + } + > div:nth-child(3) > div > div { + width: 100%; + } + div:last-child > div > button { + width: 100%; + &:nth-child(1) { + border-radius: 3px 0px 0px 3px; + } + &:nth-child(2) { + border-radius: 0px 3px 3px 0px; + } + } } \ No newline at end of file diff --git a/cvat-ui/src/components/create-model-page/create-model-content.tsx b/cvat-ui/src/components/create-model-page/create-model-content.tsx index a2ada3765b8..947122a9ccc 100644 --- a/cvat-ui/src/components/create-model-page/create-model-content.tsx +++ b/cvat-ui/src/components/create-model-page/create-model-content.tsx @@ -13,13 +13,14 @@ import { import Text from 'antd/lib/typography/Text'; +import ConnectedFileManager, { + FileManagerContainer, +} from 'containers/file-manager/file-manager'; +import { ModelFiles } from 'reducers/interfaces'; + import CreateModelForm, { CreateModelForm as WrappedCreateModelForm, } from './create-model-form'; -import ConnectedFileManager, { - FileManagerContainer, -} from '../../containers/file-manager/file-manager'; -import { ModelFiles } from '../../reducers/interfaces'; interface Props { createModel(name: string, files: ModelFiles, global: boolean): void; diff --git a/cvat-ui/src/components/create-model-page/create-model-page.tsx b/cvat-ui/src/components/create-model-page/create-model-page.tsx index ab5014b29db..7ac33219477 100644 --- a/cvat-ui/src/components/create-model-page/create-model-page.tsx +++ b/cvat-ui/src/components/create-model-page/create-model-page.tsx @@ -7,9 +7,8 @@ import { } from 'antd'; import Text from 'antd/lib/typography/Text'; - +import { ModelFiles } from 'reducers/interfaces'; import CreateModelContent from './create-model-content'; -import { ModelFiles } from '../../reducers/interfaces'; interface Props { createModel(name: string, files: ModelFiles, global: boolean): void; diff --git a/cvat-ui/src/components/create-task-page/advanced-configuration-form.tsx b/cvat-ui/src/components/create-task-page/advanced-configuration-form.tsx index 1ea5a9eb3ca..2c654661b75 100644 --- a/cvat-ui/src/components/create-task-page/advanced-configuration-form.tsx +++ b/cvat-ui/src/components/create-task-page/advanced-configuration-form.tsx @@ -12,7 +12,7 @@ import { import Form, { FormComponentProps } from 'antd/lib/form/Form'; import Text from 'antd/lib/typography/Text'; -import patterns from '../../utils/validation-patterns'; +import patterns from 'utils/validation-patterns'; export interface AdvancedConfiguration { bugTracker?: string; diff --git a/cvat-ui/src/components/create-task-page/create-task-content.tsx b/cvat-ui/src/components/create-task-page/create-task-content.tsx index c4543d5cb10..26006d0711a 100644 --- a/cvat-ui/src/components/create-task-page/create-task-content.tsx +++ b/cvat-ui/src/components/create-task-page/create-task-content.tsx @@ -11,10 +11,10 @@ import { import Text from 'antd/lib/typography/Text'; +import FileManagerContainer from 'containers/file-manager/file-manager'; import BasicConfigurationForm, { BaseConfiguration } from './basic-configuration-form'; import AdvancedConfigurationForm, { AdvancedConfiguration } from './advanced-configuration-form'; import LabelsEditor from '../labels-editor/labels-editor'; -import FileManagerContainer from '../../containers/file-manager/file-manager'; import { Files } from '../file-manager/file-manager'; export interface CreateTaskData { diff --git a/cvat-ui/src/components/cvat-app.tsx b/cvat-ui/src/components/cvat-app.tsx index 952f783396f..177a451a54c 100644 --- a/cvat-ui/src/components/cvat-app.tsx +++ b/cvat-ui/src/components/cvat-app.tsx @@ -13,18 +13,18 @@ import { notification, } from 'antd'; -import SettingsPageComponent from './settings-page/settings-page'; -import TasksPageContainer from '../containers/tasks-page/tasks-page'; -import CreateTaskPageContainer from '../containers/create-task-page/create-task-page'; -import TaskPageContainer from '../containers/task-page/task-page'; -import ModelsPageContainer from '../containers/models-page/models-page'; -import CreateModelPageContainer from '../containers/create-model-page/create-model-page'; -import AnnotationPageContainer from '../containers/annotation-page/annotation-page'; -import LoginPageContainer from '../containers/login-page/login-page'; -import RegisterPageContainer from '../containers/register-page/register-page'; -import HeaderContainer from '../containers/header/header'; +import SettingsPageComponent from 'components/settings-page/settings-page'; +import TasksPageContainer from 'containers/tasks-page/tasks-page'; +import CreateTaskPageContainer from 'containers/create-task-page/create-task-page'; +import TaskPageContainer from 'containers/task-page/task-page'; +import ModelsPageContainer from 'containers/models-page/models-page'; +import CreateModelPageContainer from 'containers/create-model-page/create-model-page'; +import AnnotationPageContainer from 'containers/annotation-page/annotation-page'; +import LoginPageContainer from 'containers/login-page/login-page'; +import RegisterPageContainer from 'containers/register-page/register-page'; +import HeaderContainer from 'containers/header/header'; -import { NotificationsState } from '../reducers/interfaces'; +import { NotificationsState } from 'reducers/interfaces'; type CVATAppProps = { loadFormats: () => void; diff --git a/cvat-ui/src/components/header/header.tsx b/cvat-ui/src/components/header/header.tsx index 29dc680f33f..427ca21be7a 100644 --- a/cvat-ui/src/components/header/header.tsx +++ b/cvat-ui/src/components/header/header.tsx @@ -14,11 +14,11 @@ import { import Text from 'antd/lib/typography/Text'; -import getCore from '../../core'; +import getCore from 'cvat-core'; import { CVATLogo, AccountIcon, -} from '../../icons'; +} from 'icons'; const core = getCore(); const serverHost = core.config.backendAPI.slice(0, -7); diff --git a/cvat-ui/src/components/labels-editor/label-form.tsx b/cvat-ui/src/components/labels-editor/label-form.tsx index b23f84f659f..c71ce87c497 100644 --- a/cvat-ui/src/components/labels-editor/label-form.tsx +++ b/cvat-ui/src/components/labels-editor/label-form.tsx @@ -13,6 +13,7 @@ import { import Form, { FormComponentProps } from 'antd/lib/form/Form'; import Text from 'antd/lib/typography/Text'; +import patterns from 'utils/validation-patterns'; import { equalArrayHead, @@ -20,7 +21,7 @@ import { Label, Attribute, } from './common'; -import patterns from '../../utils/validation-patterns'; + export enum AttributeType { SELECT = 'SELECT', diff --git a/cvat-ui/src/components/model-runner-modal/model-runner-modal.tsx b/cvat-ui/src/components/model-runner-modal/model-runner-modal.tsx index 2fec246ed19..fcf7225c17d 100644 --- a/cvat-ui/src/components/model-runner-modal/model-runner-modal.tsx +++ b/cvat-ui/src/components/model-runner-modal/model-runner-modal.tsx @@ -14,7 +14,7 @@ import { notification, } from 'antd'; -import { Model } from '../../reducers/interfaces'; +import { Model } from 'reducers/interfaces'; interface StringObject { [index: string]: string; diff --git a/cvat-ui/src/components/models-page/built-model-item.tsx b/cvat-ui/src/components/models-page/built-model-item.tsx index 5b996955c59..f00dc5e1ca5 100644 --- a/cvat-ui/src/components/models-page/built-model-item.tsx +++ b/cvat-ui/src/components/models-page/built-model-item.tsx @@ -9,7 +9,7 @@ import { import Text from 'antd/lib/typography/Text'; -import { Model } from '../../reducers/interfaces'; +import { Model } from 'reducers/interfaces'; interface Props { model: Model; diff --git a/cvat-ui/src/components/models-page/built-models-list.tsx b/cvat-ui/src/components/models-page/built-models-list.tsx index 1201e231871..b4f5fc61200 100644 --- a/cvat-ui/src/components/models-page/built-models-list.tsx +++ b/cvat-ui/src/components/models-page/built-models-list.tsx @@ -7,8 +7,8 @@ import { import Text from 'antd/lib/typography/Text'; +import { Model } from 'reducers/interfaces'; import BuiltModelItemComponent from './built-model-item'; -import { Model } from '../../reducers/interfaces'; interface Props { models: Model[]; diff --git a/cvat-ui/src/components/models-page/empty-list.tsx b/cvat-ui/src/components/models-page/empty-list.tsx index 0a6fdd4dfd7..f6d2c61f992 100644 --- a/cvat-ui/src/components/models-page/empty-list.tsx +++ b/cvat-ui/src/components/models-page/empty-list.tsx @@ -10,7 +10,7 @@ import { import { EmptyTasksIcon as EmptyModelsIcon, -} from '../../icons'; +} from 'icons'; export default function EmptyListComponent(): JSX.Element { return ( diff --git a/cvat-ui/src/components/models-page/uploaded-model-item.tsx b/cvat-ui/src/components/models-page/uploaded-model-item.tsx index a7fb28d8aac..4a42de1da04 100644 --- a/cvat-ui/src/components/models-page/uploaded-model-item.tsx +++ b/cvat-ui/src/components/models-page/uploaded-model-item.tsx @@ -13,8 +13,8 @@ import { import Text from 'antd/lib/typography/Text'; import moment from 'moment'; -import { MenuIcon } from '../../icons'; -import { Model } from '../../reducers/interfaces'; +import { MenuIcon } from 'icons'; +import { Model } from 'reducers/interfaces'; interface Props { model: Model; diff --git a/cvat-ui/src/components/models-page/uploaded-models-list.tsx b/cvat-ui/src/components/models-page/uploaded-models-list.tsx index 0c67cf8aff1..dfdcebc0b1b 100644 --- a/cvat-ui/src/components/models-page/uploaded-models-list.tsx +++ b/cvat-ui/src/components/models-page/uploaded-models-list.tsx @@ -7,8 +7,9 @@ import { import Text from 'antd/lib/typography/Text'; +import { Model } from 'reducers/interfaces'; import UploadedModelItem from './uploaded-model-item'; -import { Model } from '../../reducers/interfaces'; + interface Props { registeredUsers: any[]; diff --git a/cvat-ui/src/components/register-page/register-form.tsx b/cvat-ui/src/components/register-page/register-form.tsx index 8683b3ce670..e2332f64122 100644 --- a/cvat-ui/src/components/register-page/register-form.tsx +++ b/cvat-ui/src/components/register-page/register-form.tsx @@ -7,7 +7,7 @@ import { Form, } from 'antd'; -import patterns from '../../utils/validation-patterns'; +import patterns from 'utils/validation-patterns'; export interface RegisterData { username: string; diff --git a/cvat-ui/src/components/settings-page/player-settings.tsx b/cvat-ui/src/components/settings-page/player-settings.tsx index 8a4c3af5783..cf9d553b2ed 100644 --- a/cvat-ui/src/components/settings-page/player-settings.tsx +++ b/cvat-ui/src/components/settings-page/player-settings.tsx @@ -16,12 +16,12 @@ import { CheckboxChangeEvent } from 'antd/lib/checkbox'; import { PlaycontrolBackJumpIcon, PlaycontrolForwardJumpIcon, -} from '../../icons'; +} from 'icons'; import { FrameSpeed, GridColor, -} from '../../reducers/interfaces'; +} from 'reducers/interfaces'; interface Props { frameStep: number; diff --git a/cvat-ui/src/components/settings-page/settings-page.tsx b/cvat-ui/src/components/settings-page/settings-page.tsx index 702ba39ed76..9be48c87acb 100644 --- a/cvat-ui/src/components/settings-page/settings-page.tsx +++ b/cvat-ui/src/components/settings-page/settings-page.tsx @@ -13,8 +13,8 @@ import Text from 'antd/lib/typography/Text'; import { RouteComponentProps } from 'react-router'; import { withRouter } from 'react-router-dom'; -import WorkspaceSettingsContainer from '../../containers/settings-page/workspace-settings'; -import PlayerSettingsContainer from '../../containers/settings-page/player-settings'; +import WorkspaceSettingsContainer from 'containers/settings-page/workspace-settings'; +import PlayerSettingsContainer from 'containers/settings-page/player-settings'; function SettingsPage(props: RouteComponentProps): JSX.Element { return ( diff --git a/cvat-ui/src/components/task-page/details.tsx b/cvat-ui/src/components/task-page/details.tsx index fc8ea41eb3e..1d551f6adeb 100644 --- a/cvat-ui/src/components/task-page/details.tsx +++ b/cvat-ui/src/components/task-page/details.tsx @@ -15,11 +15,11 @@ import Title from 'antd/lib/typography/Title'; import moment from 'moment'; +import getCore from 'cvat-core'; +import patterns from 'utils/validation-patterns'; +import { getReposData, syncRepos } from 'utils/git-utils'; import UserSelector from './user-selector'; import LabelsEditorComponent from '../labels-editor/labels-editor'; -import getCore from '../../core'; -import patterns from '../../utils/validation-patterns'; -import { getReposData, syncRepos } from '../../utils/git-utils'; const core = getCore(); diff --git a/cvat-ui/src/components/task-page/job-list.tsx b/cvat-ui/src/components/task-page/job-list.tsx index 589e0b78baf..fb29cca56d7 100644 --- a/cvat-ui/src/components/task-page/job-list.tsx +++ b/cvat-ui/src/components/task-page/job-list.tsx @@ -14,9 +14,8 @@ import Text from 'antd/lib/typography/Text'; import moment from 'moment'; import copy from 'copy-to-clipboard'; +import getCore from 'cvat-core'; import UserSelector from './user-selector'; -import getCore from '../../core'; - const core = getCore(); diff --git a/cvat-ui/src/components/task-page/task-page.tsx b/cvat-ui/src/components/task-page/task-page.tsx index ec252e9c182..8238be5a792 100644 --- a/cvat-ui/src/components/task-page/task-page.tsx +++ b/cvat-ui/src/components/task-page/task-page.tsx @@ -10,11 +10,11 @@ import { Result, } from 'antd'; +import DetailsContainer from 'containers/task-page/details'; +import JobListContainer from 'containers/task-page/job-list'; +import ModelRunnerModalContainer from 'containers/model-runner-dialog/model-runner-dialog'; +import { Task } from 'reducers/interfaces'; import TopBarComponent from './top-bar'; -import DetailsContainer from '../../containers/task-page/details'; -import JobListContainer from '../../containers/task-page/job-list'; -import ModelRunnerModalContainer from '../../containers/model-runner-dialog/model-runner-dialog'; -import { Task } from '../../reducers/interfaces'; interface TaskPageComponentProps { task: Task | null | undefined; diff --git a/cvat-ui/src/components/task-page/top-bar.tsx b/cvat-ui/src/components/task-page/top-bar.tsx index ebeb59d791c..1edb8085e03 100644 --- a/cvat-ui/src/components/task-page/top-bar.tsx +++ b/cvat-ui/src/components/task-page/top-bar.tsx @@ -10,8 +10,8 @@ import { import Text from 'antd/lib/typography/Text'; -import ActionsMenuContainer from '../../containers/actions-menu/actions-menu'; -import { MenuIcon } from '../../icons'; +import ActionsMenuContainer from 'containers/actions-menu/actions-menu'; +import { MenuIcon } from 'icons'; interface DetailsComponentProps { taskInstance: any; diff --git a/cvat-ui/src/components/tasks-page/empty-list.tsx b/cvat-ui/src/components/tasks-page/empty-list.tsx index afc08f23f2f..6f358bf13a8 100644 --- a/cvat-ui/src/components/tasks-page/empty-list.tsx +++ b/cvat-ui/src/components/tasks-page/empty-list.tsx @@ -8,7 +8,7 @@ import { Icon, } from 'antd'; -import { EmptyTasksIcon } from '../../icons'; +import { EmptyTasksIcon } from 'icons'; export default function EmptyListComponent(): JSX.Element { return ( diff --git a/cvat-ui/src/components/tasks-page/task-item.tsx b/cvat-ui/src/components/tasks-page/task-item.tsx index 09799c6444e..cedd0b5ce74 100644 --- a/cvat-ui/src/components/tasks-page/task-item.tsx +++ b/cvat-ui/src/components/tasks-page/task-item.tsx @@ -14,9 +14,9 @@ import { import moment from 'moment'; -import ActionsMenuContainer from '../../containers/actions-menu/actions-menu'; -import { ActiveInference } from '../../reducers/interfaces'; -import { MenuIcon } from '../../icons'; +import ActionsMenuContainer from 'containers/actions-menu/actions-menu'; +import { ActiveInference } from 'reducers/interfaces'; +import { MenuIcon } from 'icons'; export interface TaskItemProps { taskInstance: any; diff --git a/cvat-ui/src/components/tasks-page/task-list.tsx b/cvat-ui/src/components/tasks-page/task-list.tsx index 936276eb440..bef5acf602d 100644 --- a/cvat-ui/src/components/tasks-page/task-list.tsx +++ b/cvat-ui/src/components/tasks-page/task-list.tsx @@ -6,8 +6,8 @@ import { Pagination, } from 'antd'; -import ModelRunnerModalContainer from '../../containers/model-runner-dialog/model-runner-dialog'; -import TaskItem from '../../containers/tasks-page/task-item'; +import ModelRunnerModalContainer from 'containers/model-runner-dialog/model-runner-dialog'; +import TaskItem from 'containers/tasks-page/task-item'; export interface ContentListProps { onSwitchPage(page: number): void; diff --git a/cvat-ui/src/components/tasks-page/tasks-page.tsx b/cvat-ui/src/components/tasks-page/tasks-page.tsx index 51232923bc4..a9ee5ee6b33 100644 --- a/cvat-ui/src/components/tasks-page/tasks-page.tsx +++ b/cvat-ui/src/components/tasks-page/tasks-page.tsx @@ -13,12 +13,13 @@ import Text from 'antd/lib/typography/Text'; import { TasksQuery, -} from '../../reducers/interfaces'; +} from 'reducers/interfaces'; +import FeedbackComponent from 'components/feedback/feedback'; +import TaskListContainer from 'containers/tasks-page/tasks-list'; import TopBar from './top-bar'; -import FeedbackComponent from '../feedback/feedback'; import EmptyListComponent from './empty-list'; -import TaskListContainer from '../../containers/tasks-page/tasks-list'; + interface TasksPageProps { tasksFetching: boolean; diff --git a/cvat-ui/src/containers/actions-menu/actions-menu.tsx b/cvat-ui/src/containers/actions-menu/actions-menu.tsx index dc8692c6145..8914c83b6dc 100644 --- a/cvat-ui/src/containers/actions-menu/actions-menu.tsx +++ b/cvat-ui/src/containers/actions-menu/actions-menu.tsx @@ -1,18 +1,18 @@ import React from 'react'; import { connect } from 'react-redux'; -import ActionsMenuComponent from '../../components/actions-menu/actions-menu'; +import ActionsMenuComponent from 'components/actions-menu/actions-menu'; import { CombinedState, -} from '../../reducers/interfaces'; +} from 'reducers/interfaces'; -import { showRunModelDialog } from '../../actions/models-actions'; +import { showRunModelDialog } from 'actions/models-actions'; import { dumpAnnotationsAsync, loadAnnotationsAsync, exportDatasetAsync, deleteTaskAsync, -} from '../../actions/tasks-actions'; +} from 'actions/tasks-actions'; interface OwnProps { taskInstance: any; diff --git a/cvat-ui/src/containers/annotation-page/annotation-page.tsx b/cvat-ui/src/containers/annotation-page/annotation-page.tsx index b6343c40832..c84c021a33c 100644 --- a/cvat-ui/src/containers/annotation-page/annotation-page.tsx +++ b/cvat-ui/src/containers/annotation-page/annotation-page.tsx @@ -3,12 +3,12 @@ import { connect } from 'react-redux'; import { withRouter } from 'react-router-dom'; import { RouteComponentProps } from 'react-router'; -import AnnotationPageComponent from '../../components/annotation-page/annotation-page'; -import { getJobAsync } from '../../actions/annotation-actions'; +import AnnotationPageComponent from 'components/annotation-page/annotation-page'; +import { getJobAsync } from 'actions/annotation-actions'; import { CombinedState, -} from '../../reducers/interfaces'; +} from 'reducers/interfaces'; type OwnProps = RouteComponentProps<{ tid: string; 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 24f88d2fc4b..b1aeb3f8f9a 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 @@ -1,20 +1,23 @@ import React from 'react'; import { connect } from 'react-redux'; -import CanvasWrapperComponent from '../../../components/annotation-page/standard-workspace/canvas-wrapper'; +import CanvasWrapperComponent from 'components/annotation-page/standard-workspace/canvas-wrapper'; import { confirmCanvasReady, dragCanvas, zoomCanvas, resetCanvas, -} from '../../../actions/annotation-actions'; + shapeDrawn, + annotationsUpdated, +} from 'actions/annotation-actions'; import { GridColor, + ObjectType, CombinedState, -} from '../../../reducers/interfaces'; +} from 'reducers/interfaces'; -import { Canvas } from '../../../canvas'; +import { Canvas } from 'cvat-canvas'; interface StateToProps { canvasInstance: Canvas; @@ -26,6 +29,8 @@ interface StateToProps { gridSize: number; gridColor: GridColor; gridOpacity: number; + activeLabelID: number; + activeObjectType: ObjectType; } interface DispatchToProps { @@ -33,6 +38,8 @@ interface DispatchToProps { onDragCanvas: (enabled: boolean) => void; onZoomCanvas: (enabled: boolean) => void; onResetCanvas: () => void; + onShapeDrawn: () => void; + onAnnotationsUpdated: (annotations: any[]) => void; } function mapStateToProps(state: CombinedState): StateToProps { @@ -42,8 +49,14 @@ function mapStateToProps(state: CombinedState): StateToProps { frameData, frame, annotations, + drawing, } = state.annotation; + const { + activeLabelID, + activeObjectType, + } = drawing; + const { grid, gridSize, @@ -61,6 +74,8 @@ function mapStateToProps(state: CombinedState): StateToProps { gridSize, gridColor, gridOpacity, + activeLabelID, + activeObjectType, }; } @@ -78,6 +93,12 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { onResetCanvas(): void { dispatch(resetCanvas()); }, + onShapeDrawn(): void { + dispatch(shapeDrawn()); + }, + onAnnotationsUpdated(annotations: any[]): void { + dispatch(annotationsUpdated(annotations)); + }, }; } diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx index fc4c024b976..9be5de5af13 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx @@ -1,19 +1,35 @@ import React from 'react'; import { connect } from 'react-redux'; -import { Canvas } from '../../../../canvas'; +import { Canvas } from 'cvat-canvas'; -import ControlsSideBarComponent from '../../../../components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar'; +import { drawShape } from 'actions/annotation-actions'; +import ControlsSideBarComponent from 'components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar'; import { ActiveControl, CombinedState, -} from '../../../../reducers/interfaces'; + ShapeType, + ObjectType, +} from 'reducers/interfaces'; +type StringObject = { + [index: number]: string; +}; interface StateToProps { canvasInstance: Canvas; rotateAll: boolean; activeControl: ActiveControl; + labels: StringObject; +} + +interface DispatchToProps { + onDrawStart( + shapeType: ShapeType, + labelID: number, + objectType: ObjectType, + points?: number, + ): void; } function mapStateToProps(state: CombinedState): StateToProps { @@ -27,14 +43,34 @@ function mapStateToProps(state: CombinedState): StateToProps { activeControl, } = annotation; + const labels = annotation.jobInstance.task.labels + .reduce((acc: StringObject, label: any): StringObject => { + acc[label.id as number] = label.name; + return acc; + }, {}); + return { rotateAll: settings.player.rotateAll, canvasInstance, activeControl, + labels, + }; +} + +function dispatchToProps(dispatch: any): DispatchToProps { + return { + onDrawStart( + shapeType: ShapeType, + labelID: number, + objectType: ObjectType, + points?: number, + ): void { + dispatch(drawShape(shapeType, labelID, objectType, points)); + }, }; } -function StandardWorkspaceContainer(props: StateToProps): JSX.Element { +function StandardWorkspaceContainer(props: StateToProps & DispatchToProps): JSX.Element { return ( ); @@ -42,4 +78,5 @@ function StandardWorkspaceContainer(props: StateToProps): JSX.Element { export default connect( mapStateToProps, + dispatchToProps, )(StandardWorkspaceContainer); diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/standard-workspace.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/standard-workspace.tsx index a9835c5b3b6..d98234ae7c9 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/standard-workspace.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/standard-workspace.tsx @@ -1,10 +1,10 @@ import React from 'react'; import { connect } from 'react-redux'; -import { Canvas } from '../../../canvas'; +import { Canvas } from 'cvat-canvas'; -import StandardWorkspaceComponent from '../../../components/annotation-page/standard-workspace/standard-workspace'; -import { CombinedState } from '../../../reducers/interfaces'; +import StandardWorkspaceComponent from 'components/annotation-page/standard-workspace/standard-workspace'; +import { CombinedState } from 'reducers/interfaces'; interface StateToProps { diff --git a/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx b/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx index 3923d0aa93f..652dac9a402 100644 --- a/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx +++ b/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx @@ -5,10 +5,10 @@ import { changeFrameAsync, switchPlay as switchPlayAction, saveAnnotationsAsync, -} from '../../../actions/annotation-actions'; +} from 'actions/annotation-actions'; -import AnnotationTopBarComponent from '../../../components/annotation-page/top-bar/top-bar'; -import { CombinedState } from '../../../reducers/interfaces'; +import AnnotationTopBarComponent from 'components/annotation-page/top-bar/top-bar'; +import { CombinedState } from 'reducers/interfaces'; interface StateToProps { jobInstance: any; diff --git a/cvat-ui/src/containers/create-model-page/create-model-page.tsx b/cvat-ui/src/containers/create-model-page/create-model-page.tsx index a20b6fb5628..b6299aee67b 100644 --- a/cvat-ui/src/containers/create-model-page/create-model-page.tsx +++ b/cvat-ui/src/containers/create-model-page/create-model-page.tsx @@ -1,12 +1,12 @@ import React from 'react'; import { connect } from 'react-redux'; -import CreateModelPageComponent from '../../components/create-model-page/create-model-page'; -import { createModelAsync } from '../../actions/models-actions'; +import CreateModelPageComponent from 'components/create-model-page/create-model-page'; +import { createModelAsync } from 'actions/models-actions'; import { ModelFiles, CombinedState, -} from '../../reducers/interfaces'; +} from 'reducers/interfaces'; interface StateToProps { isAdmin: boolean; diff --git a/cvat-ui/src/containers/create-task-page/create-task-page.tsx b/cvat-ui/src/containers/create-task-page/create-task-page.tsx index aac98fa42ed..eab887051ca 100644 --- a/cvat-ui/src/containers/create-task-page/create-task-page.tsx +++ b/cvat-ui/src/containers/create-task-page/create-task-page.tsx @@ -1,10 +1,10 @@ import React from 'react'; import { connect } from 'react-redux'; -import { CombinedState } from '../../reducers/interfaces'; -import CreateTaskComponent from '../../components/create-task-page/create-task-page'; -import { CreateTaskData } from '../../components/create-task-page/create-task-content'; -import { createTaskAsync } from '../../actions/tasks-actions'; +import { CombinedState } from 'reducers/interfaces'; +import CreateTaskComponent from 'components/create-task-page/create-task-page'; +import { CreateTaskData } from 'components/create-task-page/create-task-content'; +import { createTaskAsync } from 'actions/tasks-actions'; interface StateToProps { status: string; diff --git a/cvat-ui/src/containers/file-manager/file-manager.tsx b/cvat-ui/src/containers/file-manager/file-manager.tsx index bdfff58eaed..2fb55321af7 100644 --- a/cvat-ui/src/containers/file-manager/file-manager.tsx +++ b/cvat-ui/src/containers/file-manager/file-manager.tsx @@ -2,13 +2,13 @@ import React from 'react'; import { connect } from 'react-redux'; import { TreeNodeNormal } from 'antd/lib/tree/Tree'; -import FileManagerComponent, { Files } from '../../components/file-manager/file-manager'; +import FileManagerComponent, { Files } from 'components/file-manager/file-manager'; -import { loadShareDataAsync } from '../../actions/share-actions'; +import { loadShareDataAsync } from 'actions/share-actions'; import { ShareItem, CombinedState, -} from '../../reducers/interfaces'; +} from 'reducers/interfaces'; interface OwnProps { ref: any; diff --git a/cvat-ui/src/containers/header/header.tsx b/cvat-ui/src/containers/header/header.tsx index b434c18b6b1..e0de3939581 100644 --- a/cvat-ui/src/containers/header/header.tsx +++ b/cvat-ui/src/containers/header/header.tsx @@ -1,13 +1,13 @@ import React from 'react'; import { connect } from 'react-redux'; -import { logoutAsync } from '../../actions/auth-actions'; +import { logoutAsync } from 'actions/auth-actions'; import { SupportedPlugins, CombinedState, -} from '../../reducers/interfaces'; +} from 'reducers/interfaces'; -import HeaderComponent from '../../components/header/header'; +import HeaderComponent from 'components/header/header'; interface StateToProps { logoutFetching: boolean; diff --git a/cvat-ui/src/containers/login-page/login-page.tsx b/cvat-ui/src/containers/login-page/login-page.tsx index b6b9b57dee8..a9c7c56ae9b 100644 --- a/cvat-ui/src/containers/login-page/login-page.tsx +++ b/cvat-ui/src/containers/login-page/login-page.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { connect } from 'react-redux'; -import { loginAsync } from '../../actions/auth-actions'; -import LoginPageComponent from '../../components/login-page/login-page'; -import { CombinedState } from '../../reducers/interfaces'; +import { loginAsync } from 'actions/auth-actions'; +import LoginPageComponent from 'components/login-page/login-page'; +import { CombinedState } from 'reducers/interfaces'; interface StateToProps { fetching: boolean; diff --git a/cvat-ui/src/containers/model-runner-dialog/model-runner-dialog.tsx b/cvat-ui/src/containers/model-runner-dialog/model-runner-dialog.tsx index 1da1386caef..e95b0da629e 100644 --- a/cvat-ui/src/containers/model-runner-dialog/model-runner-dialog.tsx +++ b/cvat-ui/src/containers/model-runner-dialog/model-runner-dialog.tsx @@ -1,16 +1,16 @@ import React from 'react'; import { connect } from 'react-redux'; -import ModelRunnerModalComponent from '../../components/model-runner-modal/model-runner-modal'; +import ModelRunnerModalComponent from 'components/model-runner-modal/model-runner-modal'; import { Model, CombinedState, -} from '../../reducers/interfaces'; +} from 'reducers/interfaces'; import { getModelsAsync, inferModelAsync, closeRunModelDialog, -} from '../../actions/models-actions'; +} from 'actions/models-actions'; interface StateToProps { diff --git a/cvat-ui/src/containers/models-page/models-page.tsx b/cvat-ui/src/containers/models-page/models-page.tsx index ba56bb97e25..07a2900aff8 100644 --- a/cvat-ui/src/containers/models-page/models-page.tsx +++ b/cvat-ui/src/containers/models-page/models-page.tsx @@ -1,15 +1,15 @@ import React from 'react'; import { connect } from 'react-redux'; -import ModelsPageComponent from '../../components/models-page/models-page'; +import ModelsPageComponent from 'components/models-page/models-page'; import { Model, CombinedState, -} from '../../reducers/interfaces'; +} from 'reducers/interfaces'; import { getModelsAsync, deleteModelAsync, -} from '../../actions/models-actions'; +} from 'actions/models-actions'; interface StateToProps { installedAutoAnnotation: boolean; diff --git a/cvat-ui/src/containers/register-page/register-page.tsx b/cvat-ui/src/containers/register-page/register-page.tsx index c1131086544..a5487f32e81 100644 --- a/cvat-ui/src/containers/register-page/register-page.tsx +++ b/cvat-ui/src/containers/register-page/register-page.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { connect } from 'react-redux'; -import { registerAsync } from '../../actions/auth-actions'; -import RegisterPageComponent from '../../components/register-page/register-page'; -import { CombinedState } from '../../reducers/interfaces'; +import { registerAsync } from 'actions/auth-actions'; +import RegisterPageComponent from 'components/register-page/register-page'; +import { CombinedState } from 'reducers/interfaces'; interface StateToProps { fetching: boolean; diff --git a/cvat-ui/src/containers/settings-page/player-settings.tsx b/cvat-ui/src/containers/settings-page/player-settings.tsx index be52e3dd429..73193b8f427 100644 --- a/cvat-ui/src/containers/settings-page/player-settings.tsx +++ b/cvat-ui/src/containers/settings-page/player-settings.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { connect } from 'react-redux'; -import PlayerSettingsComponent from '../../components/settings-page/player-settings'; +import PlayerSettingsComponent from 'components/settings-page/player-settings'; import { switchRotateAll, @@ -9,13 +9,13 @@ import { changeGridSize, changeGridColor, changeGridOpacity, -} from '../../actions/settings-actions'; +} from 'actions/settings-actions'; import { CombinedState, FrameSpeed, GridColor, -} from '../../reducers/interfaces'; +} from 'reducers/interfaces'; interface StateToProps { frameStep: number; diff --git a/cvat-ui/src/containers/settings-page/workspace-settings.tsx b/cvat-ui/src/containers/settings-page/workspace-settings.tsx index 48eac3cc125..0c5a597efbf 100644 --- a/cvat-ui/src/containers/settings-page/workspace-settings.tsx +++ b/cvat-ui/src/containers/settings-page/workspace-settings.tsx @@ -3,9 +3,9 @@ import { connect } from 'react-redux'; import { CombinedState, -} from '../../reducers/interfaces'; +} from 'reducers/interfaces'; -import WorkspaceSettingsComponent from '../../components/settings-page/workspace-settings'; +import WorkspaceSettingsComponent from 'components/settings-page/workspace-settings'; interface StateToProps { autoSave: boolean; diff --git a/cvat-ui/src/containers/task-page/details.tsx b/cvat-ui/src/containers/task-page/details.tsx index 913bbdf19dd..269041b5d0b 100644 --- a/cvat-ui/src/containers/task-page/details.tsx +++ b/cvat-ui/src/containers/task-page/details.tsx @@ -1,12 +1,12 @@ import React from 'react'; import { connect } from 'react-redux'; -import DetailsComponent from '../../components/task-page/details'; -import { updateTaskAsync } from '../../actions/tasks-actions'; +import DetailsComponent from 'components/task-page/details'; +import { updateTaskAsync } from 'actions/tasks-actions'; import { Task, CombinedState, -} from '../../reducers/interfaces'; +} from 'reducers/interfaces'; interface OwnProps { task: Task; diff --git a/cvat-ui/src/containers/task-page/job-list.tsx b/cvat-ui/src/containers/task-page/job-list.tsx index c7e32c589cc..04ca7ecdc09 100644 --- a/cvat-ui/src/containers/task-page/job-list.tsx +++ b/cvat-ui/src/containers/task-page/job-list.tsx @@ -1,12 +1,12 @@ import React from 'react'; import { connect } from 'react-redux'; -import JobListComponent from '../../components/task-page/job-list'; -import { updateJobAsync } from '../../actions/tasks-actions'; +import JobListComponent from 'components/task-page/job-list'; +import { updateJobAsync } from 'actions/tasks-actions'; import { Task, CombinedState, -} from '../../reducers/interfaces'; +} from 'reducers/interfaces'; interface OwnProps { task: Task; diff --git a/cvat-ui/src/containers/task-page/task-page.tsx b/cvat-ui/src/containers/task-page/task-page.tsx index da2f5aae93a..abad8db8588 100644 --- a/cvat-ui/src/containers/task-page/task-page.tsx +++ b/cvat-ui/src/containers/task-page/task-page.tsx @@ -3,13 +3,13 @@ import { connect } from 'react-redux'; import { withRouter } from 'react-router-dom'; import { RouteComponentProps } from 'react-router'; -import { getTasksAsync } from '../../actions/tasks-actions'; +import { getTasksAsync } from 'actions/tasks-actions'; -import TaskPageComponent from '../../components/task-page/task-page'; +import TaskPageComponent from 'components/task-page/task-page'; import { Task, CombinedState, -} from '../../reducers/interfaces'; +} from 'reducers/interfaces'; type Props = RouteComponentProps<{id: string}>; diff --git a/cvat-ui/src/containers/tasks-page/task-item.tsx b/cvat-ui/src/containers/tasks-page/task-item.tsx index 9aa96727aca..3e115a3004a 100644 --- a/cvat-ui/src/containers/tasks-page/task-item.tsx +++ b/cvat-ui/src/containers/tasks-page/task-item.tsx @@ -5,13 +5,13 @@ import { TasksQuery, CombinedState, ActiveInference, -} from '../../reducers/interfaces'; +} from 'reducers/interfaces'; -import TaskItemComponent from '../../components/tasks-page/task-item'; +import TaskItemComponent from 'components/tasks-page/task-item'; import { getTasksAsync, -} from '../../actions/tasks-actions'; +} from 'actions/tasks-actions'; interface StateToProps { deleted: boolean; diff --git a/cvat-ui/src/containers/tasks-page/tasks-list.tsx b/cvat-ui/src/containers/tasks-page/tasks-list.tsx index 7afd17b8de2..bbc79be446f 100644 --- a/cvat-ui/src/containers/tasks-page/tasks-list.tsx +++ b/cvat-ui/src/containers/tasks-page/tasks-list.tsx @@ -5,13 +5,13 @@ import { TasksState, TasksQuery, CombinedState, -} from '../../reducers/interfaces'; +} from 'reducers/interfaces'; -import TasksListComponent from '../../components/tasks-page/task-list'; +import TasksListComponent from 'components/tasks-page/task-list'; import { getTasksAsync, -} from '../../actions/tasks-actions'; +} from 'actions/tasks-actions'; interface StateToProps { tasks: TasksState; diff --git a/cvat-ui/src/containers/tasks-page/tasks-page.tsx b/cvat-ui/src/containers/tasks-page/tasks-page.tsx index 7e404d0e87a..b713943e286 100644 --- a/cvat-ui/src/containers/tasks-page/tasks-page.tsx +++ b/cvat-ui/src/containers/tasks-page/tasks-page.tsx @@ -5,14 +5,14 @@ import { Task, TasksQuery, CombinedState, -} from '../../reducers/interfaces'; +} from 'reducers/interfaces'; -import TasksPageComponent from '../../components/tasks-page/tasks-page'; +import TasksPageComponent from 'components/tasks-page/tasks-page'; import { getTasksAsync, hideEmptyTasks, -} from '../../actions/tasks-actions'; +} from 'actions/tasks-actions'; interface StateToProps { tasksFetching: boolean; diff --git a/cvat-ui/src/canvas.ts b/cvat-ui/src/cvat-canvas.ts similarity index 100% rename from cvat-ui/src/canvas.ts rename to cvat-ui/src/cvat-canvas.ts diff --git a/cvat-ui/src/core.ts b/cvat-ui/src/cvat-core.ts similarity index 100% rename from cvat-ui/src/core.ts rename to cvat-ui/src/cvat-core.ts diff --git a/cvat-ui/src/store.ts b/cvat-ui/src/cvat-store.ts similarity index 100% rename from cvat-ui/src/store.ts rename to cvat-ui/src/cvat-store.ts diff --git a/cvat-ui/src/index.tsx b/cvat-ui/src/index.tsx index 3ef428246f1..2d01144eb2e 100644 --- a/cvat-ui/src/index.tsx +++ b/cvat-ui/src/index.tsx @@ -5,7 +5,7 @@ import { connect, Provider } from 'react-redux'; import CVATApplication from './components/cvat-app'; import createRootReducer from './reducers/root-reducer'; -import createCVATStore, { getCVATStore } from './store'; +import createCVATStore, { getCVATStore } from './cvat-store'; import { authorizedAsync } from './actions/auth-actions'; import { getFormatsAsync } from './actions/formats-actions'; diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index 21fd7ecd4c5..57333758a1c 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -1,12 +1,14 @@ import { AnyAction } from 'redux'; -import { Canvas } from '../canvas'; - +import { Canvas } from 'cvat-canvas'; +import { AnnotationActionTypes } from 'actions/annotation-actions'; import { AnnotationState, ActiveControl, + ShapeType, + ObjectType, } from './interfaces'; -import { AnnotationActionTypes } from '../actions/annotation-actions'; + const defaultState: AnnotationState = { canvasInstance: new Canvas(), @@ -21,6 +23,11 @@ const defaultState: AnnotationState = { savingStatuses: [], dataFetching: false, jobFetching: false, + drawing: { + activeShapeType: ShapeType.RECTANGLE, + activeLabelID: 0, + activeObjectType: ObjectType.SHAPE, + }, }; export default (state = defaultState, action: AnyAction): AnnotationState => { @@ -32,13 +39,19 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { }; } case AnnotationActionTypes.GET_JOB_SUCCESS: { + const { jobInstance } = action.payload; return { ...defaultState, jobFetching: false, - jobInstance: action.payload.jobInstance, + jobInstance, frame: action.payload.frame, frameData: action.payload.frameData, annotations: action.payload.annotations, + drawing: { + ...defaultState.drawing, + activeLabelID: jobInstance.task.labels[0].id, + activeObjectType: jobInstance.task.mode === 'interpolation' ? ObjectType.TRACK : ObjectType.SHAPE, + }, }; } case AnnotationActionTypes.GET_JOB_FAILED: { @@ -123,6 +136,38 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { activeControl: enabled ? ActiveControl.ZOOM_CANVAS : ActiveControl.CURSOR, }; } + case AnnotationActionTypes.DRAW_SHAPE: { + const { + shapeType, + labelID, + objectType, + points, + activeControl, + } = action.payload; + + return { + ...state, + activeControl, + drawing: { + activeLabelID: labelID, + activeNumOfPoints: points, + activeObjectType: objectType, + activeShapeType: shapeType, + }, + }; + } + case AnnotationActionTypes.SHAPE_DRAWN: { + return { + ...state, + activeControl: ActiveControl.CURSOR, + }; + } + case AnnotationActionTypes.ANNOTATIONS_UPDATED: { + return { + ...state, + annotations: action.payload.annotations, + }; + } case AnnotationActionTypes.RESET_CANVAS: { return { ...state, diff --git a/cvat-ui/src/reducers/auth-reducer.ts b/cvat-ui/src/reducers/auth-reducer.ts index 63fecdf67ec..73a9bebd72c 100644 --- a/cvat-ui/src/reducers/auth-reducer.ts +++ b/cvat-ui/src/reducers/auth-reducer.ts @@ -1,5 +1,5 @@ import { AnyAction } from 'redux'; -import { AuthActionTypes } from '../actions/auth-actions'; +import { AuthActionTypes } from 'actions/auth-actions'; import { AuthState } from './interfaces'; diff --git a/cvat-ui/src/reducers/formats-reducer.ts b/cvat-ui/src/reducers/formats-reducer.ts index 40fd2a80506..b66afab8bc0 100644 --- a/cvat-ui/src/reducers/formats-reducer.ts +++ b/cvat-ui/src/reducers/formats-reducer.ts @@ -1,6 +1,6 @@ import { AnyAction } from 'redux'; -import { FormatsActionTypes } from '../actions/formats-actions'; -import { AuthActionTypes } from '../actions/auth-actions'; +import { FormatsActionTypes } from 'actions/formats-actions'; +import { AuthActionTypes } from 'actions/auth-actions'; import { FormatsState } from './interfaces'; diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index 3dd4edbf08f..2adea23d552 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -1,4 +1,4 @@ -import { Canvas } from '../canvas'; +import { Canvas } from 'cvat-canvas'; export interface AuthState { initialized: boolean; @@ -207,6 +207,23 @@ export enum ActiveControl { CURSOR = 'cursor', DRAG_CANVAS = 'drag_canvas', ZOOM_CANVAS = 'zoom_canvas', + DRAW_RECTANGLE = 'draw_rectangle', + DRAW_POLYGON = 'draw_polygon', + DRAW_POLYLINE = 'draw_polyline', + DRAW_POINTS = 'draw_points', +} + +export enum ShapeType { + RECTANGLE = 'rectangle', + POLYGON = 'polygon', + POLYLINE = 'polyline', + POINTS = 'points', +} + +export enum ObjectType { + SHAPE = 'shape', + TRACK = 'track', + TAG = 'tag', } export interface AnnotationState { @@ -222,6 +239,12 @@ export interface AnnotationState { savingStatuses: string[]; jobFetching: boolean; dataFetching: boolean; + drawing: { + activeShapeType: ShapeType; + activeNumOfPoints?: number; + activeLabelID: number; + activeObjectType: ObjectType; + }; } export enum GridColor { diff --git a/cvat-ui/src/reducers/models-reducer.ts b/cvat-ui/src/reducers/models-reducer.ts index 664622fecff..453885b4619 100644 --- a/cvat-ui/src/reducers/models-reducer.ts +++ b/cvat-ui/src/reducers/models-reducer.ts @@ -1,7 +1,7 @@ import { AnyAction } from 'redux'; -import { ModelsActionTypes } from '../actions/models-actions'; -import { AuthActionTypes } from '../actions/auth-actions'; +import { ModelsActionTypes } from 'actions/models-actions'; +import { AuthActionTypes } from 'actions/auth-actions'; import { ModelsState } from './interfaces'; const defaultState: ModelsState = { diff --git a/cvat-ui/src/reducers/notifications-reducer.ts b/cvat-ui/src/reducers/notifications-reducer.ts index 02ada707394..a649f741df3 100644 --- a/cvat-ui/src/reducers/notifications-reducer.ts +++ b/cvat-ui/src/reducers/notifications-reducer.ts @@ -1,13 +1,13 @@ import { AnyAction } from 'redux'; -import { AuthActionTypes } from '../actions/auth-actions'; -import { FormatsActionTypes } from '../actions/formats-actions'; -import { ModelsActionTypes } from '../actions/models-actions'; -import { ShareActionTypes } from '../actions/share-actions'; -import { TasksActionTypes } from '../actions/tasks-actions'; -import { UsersActionTypes } from '../actions/users-actions'; -import { AnnotationActionTypes } from '../actions/annotation-actions'; -import { NotificationsActionType } from '../actions/notification-actions'; +import { AuthActionTypes } from 'actions/auth-actions'; +import { FormatsActionTypes } from 'actions/formats-actions'; +import { ModelsActionTypes } from 'actions/models-actions'; +import { ShareActionTypes } from 'actions/share-actions'; +import { TasksActionTypes } from 'actions/tasks-actions'; +import { UsersActionTypes } from 'actions/users-actions'; +import { AnnotationActionTypes } from 'actions/annotation-actions'; +import { NotificationsActionType } from 'actions/notification-actions'; import { NotificationsState } from './interfaces'; diff --git a/cvat-ui/src/reducers/plugins-reducer.ts b/cvat-ui/src/reducers/plugins-reducer.ts index 1a7e1f16078..253cbcf720d 100644 --- a/cvat-ui/src/reducers/plugins-reducer.ts +++ b/cvat-ui/src/reducers/plugins-reducer.ts @@ -1,8 +1,8 @@ import { AnyAction } from 'redux'; -import { PluginsActionTypes } from '../actions/plugins-actions'; -import { AuthActionTypes } from '../actions/auth-actions'; -import { registerGitPlugin } from '../utils/git-utils'; +import { PluginsActionTypes } from 'actions/plugins-actions'; +import { AuthActionTypes } from 'actions/auth-actions'; +import { registerGitPlugin } from 'utils/git-utils'; import { PluginsState, } from './interfaces'; diff --git a/cvat-ui/src/reducers/settings-reducer.ts b/cvat-ui/src/reducers/settings-reducer.ts index 07e226936d3..d5da95d6581 100644 --- a/cvat-ui/src/reducers/settings-reducer.ts +++ b/cvat-ui/src/reducers/settings-reducer.ts @@ -1,5 +1,5 @@ import { AnyAction } from 'redux'; -import { SettingsActionTypes } from '../actions/settings-actions'; +import { SettingsActionTypes } from 'actions/settings-actions'; import { SettingsState, diff --git a/cvat-ui/src/reducers/share-reducer.ts b/cvat-ui/src/reducers/share-reducer.ts index e1c6dcae5c1..17bd58a1e2e 100644 --- a/cvat-ui/src/reducers/share-reducer.ts +++ b/cvat-ui/src/reducers/share-reducer.ts @@ -1,7 +1,7 @@ import { AnyAction } from 'redux'; -import { ShareActionTypes } from '../actions/share-actions'; -import { AuthActionTypes } from '../actions/auth-actions'; +import { ShareActionTypes } from 'actions/share-actions'; +import { AuthActionTypes } from 'actions/auth-actions'; import { ShareState, ShareFileInfo, diff --git a/cvat-ui/src/reducers/tasks-reducer.ts b/cvat-ui/src/reducers/tasks-reducer.ts index eed41a78ac6..08301ff5242 100644 --- a/cvat-ui/src/reducers/tasks-reducer.ts +++ b/cvat-ui/src/reducers/tasks-reducer.ts @@ -1,6 +1,6 @@ import { AnyAction } from 'redux'; -import { TasksActionTypes } from '../actions/tasks-actions'; -import { AuthActionTypes } from '../actions/auth-actions'; +import { TasksActionTypes } from 'actions/tasks-actions'; +import { AuthActionTypes } from 'actions/auth-actions'; import { TasksState, Task } from './interfaces'; diff --git a/cvat-ui/src/reducers/users-reducer.ts b/cvat-ui/src/reducers/users-reducer.ts index bb37e6dfc47..0b44a5d242d 100644 --- a/cvat-ui/src/reducers/users-reducer.ts +++ b/cvat-ui/src/reducers/users-reducer.ts @@ -1,8 +1,8 @@ import { AnyAction } from 'redux'; -import { UsersState } from './interfaces'; -import { AuthActionTypes } from '../actions/auth-actions'; -import { UsersActionTypes } from '../actions/users-actions'; +import { AuthActionTypes } from 'actions/auth-actions'; +import { UsersActionTypes } from 'actions/users-actions'; +import { UsersState } from './interfaces'; const defaultState: UsersState = { users: [], diff --git a/cvat-ui/src/utils/git-utils.ts b/cvat-ui/src/utils/git-utils.ts index ccc402a8fe1..7de60625965 100644 --- a/cvat-ui/src/utils/git-utils.ts +++ b/cvat-ui/src/utils/git-utils.ts @@ -1,4 +1,4 @@ -import getCore from '../core'; +import getCore from 'cvat-core'; const core = getCore(); const baseURL = core.config.backendAPI.slice(0, -7); diff --git a/cvat-ui/src/utils/plugin-checker.ts b/cvat-ui/src/utils/plugin-checker.ts index 0a31ef1a9d4..1c917470560 100644 --- a/cvat-ui/src/utils/plugin-checker.ts +++ b/cvat-ui/src/utils/plugin-checker.ts @@ -1,5 +1,5 @@ -import getCore from '../core'; -import { SupportedPlugins } from '../reducers/interfaces'; +import getCore from 'cvat-core'; +import { SupportedPlugins } from 'reducers/interfaces'; const core = getCore(); diff --git a/cvat-ui/tsconfig.json b/cvat-ui/tsconfig.json index a4261355634..388f07adab9 100644 --- a/cvat-ui/tsconfig.json +++ b/cvat-ui/tsconfig.json @@ -17,10 +17,12 @@ "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, - "jsx": "preserve" + "jsx": "preserve", + "baseUrl": "src" }, "include": [ "./index.d.ts", - "src/index.tsx" + "src/index.tsx", + "src" ] } diff --git a/cvat-ui/webpack.config.js b/cvat-ui/webpack.config.js index 721b14e204d..86b659672d0 100644 --- a/cvat-ui/webpack.config.js +++ b/cvat-ui/webpack.config.js @@ -6,6 +6,7 @@ /* eslint-disable */ const path = require('path'); const HtmlWebpackPlugin = require("html-webpack-plugin"); +const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); const Dotenv = require('dotenv-webpack'); module.exports = { @@ -26,6 +27,7 @@ module.exports = { }, resolve: { extensions: ['.tsx', '.ts', '.jsx', '.js', '.json'], + plugins: [new TsconfigPathsPlugin({ configFile: "./tsconfig.json" })] }, module: { rules: [{ From b4aba4997f2500f63c4cb5985b5d114e831c2add Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 16 Jan 2020 08:50:40 +0300 Subject: [PATCH 11/15] Cancel previous drawing --- .../controls-side-bar/draw-shape-popover-content.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover-content.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover-content.tsx index 95b7255eb76..9c8f632a4a9 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover-content.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover-content.tsx @@ -129,6 +129,7 @@ export default class DrawShapePopoverContent extends React.PureComponent