diff --git a/cvat-canvas/src/typescript/canvas.ts b/cvat-canvas/src/typescript/canvas.ts index c53c9d523c6a..eef1de40ef31 100644 --- a/cvat-canvas/src/typescript/canvas.ts +++ b/cvat-canvas/src/typescript/canvas.ts @@ -3,6 +3,7 @@ // SPDX-License-Identifier: MIT import { + Mode, DrawData, MergeData, SplitData, @@ -51,6 +52,7 @@ interface Canvas { dragCanvas(enable: boolean): void; zoomCanvas(enable: boolean): void; + mode(): void; cancel(): void; } @@ -132,6 +134,10 @@ class CanvasImpl implements Canvas { this.model.select(objectState); } + public mode(): Mode { + return this.model.mode; + } + public cancel(): void { this.model.cancel(); } @@ -141,4 +147,5 @@ export { CanvasImpl as Canvas, CanvasVersion, RectDrawingMethod, + Mode as CanvasMode, }; diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 63b26e1dc52c..def0db97f9d0 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -469,21 +469,21 @@ export class CanvasViewImpl implements CanvasView, Listener { 'stroke-width': consts.POINTS_STROKE_WIDTH / self.geometry.scale, }); - circle.node.addEventListener('mouseenter', (): void => { + circle.on('mouseenter', (): void => { circle.attr({ 'stroke-width': consts.POINTS_SELECTED_STROKE_WIDTH / self.geometry.scale, }); - circle.node.addEventListener('dblclick', dblClickHandler); + circle.on('dblclick', dblClickHandler); circle.addClass('cvat_canvas_selected_point'); }); - circle.node.addEventListener('mouseleave', (): void => { + circle.on('mouseleave', (): void => { circle.attr({ 'stroke-width': consts.POINTS_STROKE_WIDTH / self.geometry.scale, }); - circle.node.removeEventListener('dblclick', dblClickHandler); + circle.off('dblclick', dblClickHandler); circle.removeClass('cvat_canvas_selected_point'); }); @@ -1330,13 +1330,18 @@ export class CanvasViewImpl implements CanvasView, Listener { private setupPoints(basicPolyline: SVG.PolyLine, state: any): any { this.selectize(true, basicPolyline); - const group = basicPolyline.remember('_selectHandler').nested + const group: SVG.G = basicPolyline.remember('_selectHandler').nested .addClass('cvat_canvas_shape').attr({ clientID: state.clientID, id: `cvat_canvas_shape_${state.clientID}`, 'data-z-order': state.zOrder, }); + group.on('click.canvas', (event: MouseEvent): void => { + // Need to redispatch the event on another element + basicPolyline.fire(new MouseEvent('click', event)); + }); + group.bbox = basicPolyline.bbox.bind(basicPolyline); group.clone = basicPolyline.clone.bind(basicPolyline); diff --git a/cvat-canvas/src/typescript/editHandler.ts b/cvat-canvas/src/typescript/editHandler.ts index 7734bc898922..defdb69e0b2a 100644 --- a/cvat-canvas/src/typescript/editHandler.ts +++ b/cvat-canvas/src/typescript/editHandler.ts @@ -84,7 +84,7 @@ export class EditHandlerImpl implements EditHandler { }).draw(dummyEvent, { snapToGrid: 0.1 }); if (this.editData.state.shapeType === 'points') { - this.editLine.style('stroke-width', 0); + this.editLine.attr('stroke-width', 0); (this.editLine as any).draw('undo'); } @@ -168,7 +168,7 @@ export class EditHandlerImpl implements EditHandler { for (const points of [firstPart, secondPart]) { this.clones.push(this.canvas.polygon(points.join(' ')) .attr('fill', this.editedShape.attr('fill')) - .style('fill-opacity', '0.5') + .attr('fill-opacity', '0.5') .addClass('cvat_canvas_shape')); } @@ -340,10 +340,16 @@ export class EditHandlerImpl implements EditHandler { public transform(geometry: Geometry): void { this.geometry = geometry; + if (this.editedShape) { + this.editedShape.attr({ + 'stroke-width': consts.BASE_STROKE_WIDTH / geometry.scale, + }); + } + if (this.editLine) { (this.editLine as any).draw('transform'); if (this.editData.state.shapeType !== 'points') { - this.editLine.style({ + this.editLine.attr({ 'stroke-width': consts.BASE_STROKE_WIDTH / geometry.scale, }); } @@ -351,7 +357,7 @@ export class EditHandlerImpl implements EditHandler { const paintHandler = this.editLine.remember('_paintHandler'); for (const point of (paintHandler as any).set.members) { - point.style( + point.attr( 'stroke-width', `${consts.POINTS_STROKE_WIDTH / geometry.scale}`, ); diff --git a/cvat-core/src/annotations-objects.js b/cvat-core/src/annotations-objects.js index f86e92ee861f..65a30b5663a9 100644 --- a/cvat-core/src/annotations-objects.js +++ b/cvat-core/src/annotations-objects.js @@ -339,6 +339,11 @@ if (updated.keyframe) { checkObjectType('keyframe', data.keyframe, 'boolean', null); + if (!this.shapes || (Object.keys(this.shapes).length === 1 && !data.keyframe)) { + throw new ArgumentError( + 'Can not remove the latest keyframe of an object. Consider removing the object instead', + ); + } } return fittedPoints; @@ -964,7 +969,8 @@ const current = this.get(frame); const wasKeyframe = frame in this.shapes; - if ((keyframe && wasKeyframe) || (!keyframe && !wasKeyframe)) { + if ((keyframe && wasKeyframe) + || (!keyframe && !wasKeyframe)) { return; } @@ -1088,7 +1094,7 @@ throw new DataError( 'No one left position or right position was found. ' - + `Interpolation impossible. Client ID: ${this.id}`, + + `Interpolation impossible. Client ID: ${this.clientID}`, ); } } diff --git a/cvat-core/src/object-state.js b/cvat-core/src/object-state.js index 806a29099796..e6a42f188afe 100644 --- a/cvat-core/src/object-state.js +++ b/cvat-core/src/object-state.js @@ -39,7 +39,7 @@ color: null, hidden: null, pinned: null, - keyframes: null, + keyframes: serialized.keyframes, group: serialized.group, updated: serialized.updated, diff --git a/cvat-ui/src/cvat-canvas.ts b/cvat-ui/src/cvat-canvas.ts index 874637adeeb7..6317c43567e7 100644 --- a/cvat-ui/src/cvat-canvas.ts +++ b/cvat-ui/src/cvat-canvas.ts @@ -4,12 +4,14 @@ import { Canvas, + CanvasMode, CanvasVersion, RectDrawingMethod, } from '../../cvat-canvas/src/typescript/canvas'; export { Canvas, + CanvasMode, CanvasVersion, RectDrawingMethod, }; diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index b9615fe70be3..5e021d63bbb1 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -4,7 +4,7 @@ import { AnyAction } from 'redux'; -import { Canvas } from 'cvat-canvas'; +import { Canvas, CanvasMode } from 'cvat-canvas'; import { AnnotationActionTypes } from 'actions/annotation-actions'; import { AuthActionTypes } from 'actions/auth-actions'; import { @@ -609,9 +609,17 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { }; } case AnnotationActionTypes.ACTIVATE_OBJECT: { + const { activatedStateID } = action.payload; const { - activatedStateID, - } = action.payload; + canvas: { + activeControl, + instance, + }, + } = state; + + if (activeControl !== ActiveControl.CURSOR || instance.mode() !== CanvasMode.IDLE) { + return state; + } return { ...state,