diff --git a/cvat-canvas/src/typescript/canvasController.ts b/cvat-canvas/src/typescript/canvasController.ts index 786836d8..065478a6 100644 --- a/cvat-canvas/src/typescript/canvasController.ts +++ b/cvat-canvas/src/typescript/canvasController.ts @@ -14,6 +14,7 @@ import { GroupData, Mode, InteractionData, + Configuration, } from './canvasModel'; export interface CanvasController { @@ -27,6 +28,7 @@ export interface CanvasController { readonly splitData: SplitData; readonly groupData: GroupData; readonly selected: any; + readonly configuration: Configuration; mode: Mode; geometry: Geometry; @@ -151,6 +153,10 @@ export class CanvasControllerImpl implements CanvasController { return this.model.selected; } + public get configuration(): Configuration { + return this.model.configuration; + } + public set mode(value: Mode) { this.model.mode = value; } diff --git a/cvat-canvas/src/typescript/canvasModel.ts b/cvat-canvas/src/typescript/canvasModel.ts index 4521c570..eb5e878b 100644 --- a/cvat-canvas/src/typescript/canvasModel.ts +++ b/cvat-canvas/src/typescript/canvasModel.ts @@ -56,6 +56,7 @@ export interface Configuration { displayAllText?: boolean; undefinedAttrValue?: string; showProjections?: boolean; + forceDisableEditing?: boolean; } export interface DrawData { @@ -288,15 +289,15 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { const mutiplier = Math.sin((angle * Math.PI) / 180) + Math.cos((angle * Math.PI) / 180); if ((angle / 90) % 2) { // 90, 270, .. - this.data.top += - mutiplier * ((x - this.data.imageSize.width / 2) * (oldScale / this.data.scale - 1)) * this.data.scale; - this.data.left -= - mutiplier * ((y - this.data.imageSize.height / 2) * (oldScale / this.data.scale - 1)) * this.data.scale; + const topMultiplier = (x - this.data.imageSize.width / 2) * (oldScale / this.data.scale - 1); + const leftMultiplier = (y - this.data.imageSize.height / 2) * (oldScale / this.data.scale - 1); + this.data.top += mutiplier * topMultiplier * this.data.scale; + this.data.left -= mutiplier * leftMultiplier * this.data.scale; } else { - this.data.left += - mutiplier * ((x - this.data.imageSize.width / 2) * (oldScale / this.data.scale - 1)) * this.data.scale; - this.data.top += - mutiplier * ((y - this.data.imageSize.height / 2) * (oldScale / this.data.scale - 1)) * this.data.scale; + const leftMultiplier = (x - this.data.imageSize.width / 2) * (oldScale / this.data.scale - 1); + const topMultiplier = (y - this.data.imageSize.height / 2) * (oldScale / this.data.scale - 1); + this.data.left += mutiplier * leftMultiplier * this.data.scale; + this.data.top += mutiplier * topMultiplier * this.data.scale; } this.notify(UpdateReasons.IMAGE_ZOOMED); @@ -599,13 +600,16 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { this.data.configuration.undefinedAttrValue = configuration.undefinedAttrValue; } + if (typeof configuration.forceDisableEditing !== 'undefined') { + this.data.configuration.forceDisableEditing = configuration.forceDisableEditing; + } + this.notify(UpdateReasons.CONFIG_UPDATED); } public isAbleToChangeFrame(): boolean { - const isUnable = - [Mode.DRAG, Mode.EDIT, Mode.RESIZE, Mode.INTERACT].includes(this.data.mode) || - (this.data.mode === Mode.DRAW && typeof this.data.drawData.redraw === 'number'); + const isUnable = [Mode.DRAG, Mode.EDIT, Mode.RESIZE, Mode.INTERACT].includes(this.data.mode) + || (this.data.mode === Mode.DRAW && typeof this.data.drawData.redraw === 'number'); return !isUnable; } diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 8d8e4177..6708545f 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -94,6 +94,11 @@ export class CanvasViewImpl implements CanvasView, Listener { return this.serviceFlags.drawHidden[clientID] || false; } + private stateIsLocked(state: any): boolean { + const { configuration } = this.controller; + return state.lock || configuration.forceDisableEditing; + } + private setupServiceHidden(clientID: number, value: boolean): void { this.serviceFlags.drawHidden[clientID] = value; const shape = this.svgShapes[clientID]; @@ -455,8 +460,8 @@ export class CanvasViewImpl implements CanvasView, Listener { // Transform all text for (const key in this.svgShapes) { if ( - Object.prototype.hasOwnProperty.call(this.svgShapes, key) && - Object.prototype.hasOwnProperty.call(this.svgTexts, key) + Object.prototype.hasOwnProperty.call(this.svgShapes, key) + && Object.prototype.hasOwnProperty.call(this.svgTexts, key) ) { this.updateTextPosition(this.svgTexts[key], this.svgShapes[key]); } @@ -874,9 +879,9 @@ export class CanvasViewImpl implements CanvasView, Listener { this.content.addEventListener('mousedown', (event): void => { if ([0, 1].includes(event.button)) { if ( - [Mode.IDLE, Mode.DRAG_CANVAS, Mode.MERGE, Mode.SPLIT].includes(this.mode) || - event.button === 1 || - event.altKey + [Mode.IDLE, Mode.DRAG_CANVAS, Mode.MERGE, Mode.SPLIT].includes(this.mode) + || event.button === 1 + || event.altKey ) { self.controller.enableDrag(event.clientX, event.clientY); } @@ -1325,8 +1330,8 @@ export class CanvasViewImpl implements CanvasView, Listener { } if ( - state.points.length !== drawnState.points.length || - state.points.some((p: number, id: number): boolean => p !== drawnState.points[id]) + state.points.length !== drawnState.points.length + || state.points.some((p: number, id: number): boolean => p !== drawnState.points[id]) ) { const translatedPoints: number[] = translate(state.points); @@ -1542,7 +1547,7 @@ export class CanvasViewImpl implements CanvasView, Listener { if (state && state.shapeType === 'points') { this.svgShapes[clientID] .remember('_selectHandler') - .nested.style('pointer-events', state.lock ? 'none' : ''); + .nested.style('pointer-events', this.stateIsLocked(state) ? 'none' : ''); } if (!state || state.hidden || state.outside) { @@ -1550,8 +1555,14 @@ export class CanvasViewImpl implements CanvasView, Listener { } const shape = this.svgShapes[clientID]; + let text = this.svgTexts[clientID]; + if (!text) { + text = this.addText(state); + this.svgTexts[state.clientID] = text; + } + this.updateTextPosition(text, shape); - if (state.lock) { + if (this.stateIsLocked(state)) { return; } @@ -1567,12 +1578,6 @@ export class CanvasViewImpl implements CanvasView, Listener { (shape as any).attr('projections', true); } - let text = this.svgTexts[clientID]; - if (!text) { - text = this.addText(state); - this.svgTexts[state.clientID] = text; - } - const hideText = (): void => { if (text) { text.addClass('cvat_canvas_hidden'); @@ -1601,12 +1606,14 @@ export class CanvasViewImpl implements CanvasView, Listener { const p2 = e.detail.p; const delta = 1; const { offset } = this.controller.geometry; - if (Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2) >= delta) { + const dx2 = (p1.x - p2.x) ** 2; + const dy2 = (p1.y - p2.y) ** 2; + if (Math.sqrt(dx2 + dy2) >= delta) { const points = pointsToNumberArray( - shape.attr('points') || - `${shape.attr('x')},${shape.attr('y')} ` + - `${shape.attr('x') + shape.attr('width')},` + - `${shape.attr('y') + shape.attr('height')}`, + shape.attr('points') + || `${shape.attr('x')},${shape.attr('y')} ` + + `${shape.attr('x') + shape.attr('width')},` + + `${shape.attr('y') + shape.attr('height')}`, ).map((x: number): number => x - offset); this.drawnStates[state.clientID].points = points; @@ -1677,10 +1684,10 @@ export class CanvasViewImpl implements CanvasView, Listener { const { offset } = this.controller.geometry; const points = pointsToNumberArray( - shape.attr('points') || - `${shape.attr('x')},${shape.attr('y')} ` + - `${shape.attr('x') + shape.attr('width')},` + - `${shape.attr('y') + shape.attr('height')}`, + shape.attr('points') + || `${shape.attr('x')},${shape.attr('y')} ` + + `${shape.attr('x') + shape.attr('width')},` + + `${shape.attr('y') + shape.attr('height')}`, ).map((x: number): number => x - offset); this.drawnStates[state.clientID].points = points; @@ -1697,7 +1704,6 @@ export class CanvasViewImpl implements CanvasView, Listener { } }); - this.updateTextPosition(text, shape); this.canvas.dispatchEvent( new CustomEvent('canvas.activated', { bubbles: false, @@ -1757,8 +1763,8 @@ export class CanvasViewImpl implements CanvasView, Listener { // Find the best place for a text let [clientX, clientY]: number[] = [box.x + box.width, box.y]; if ( - clientX + ((text.node as any) as SVGTextElement).getBBox().width + consts.TEXT_MARGIN > - this.canvas.offsetWidth + clientX + ((text.node as any) as SVGTextElement).getBBox().width + consts.TEXT_MARGIN + > this.canvas.offsetWidth ) { [clientX, clientY] = [box.x, box.y]; } @@ -1778,7 +1784,9 @@ export class CanvasViewImpl implements CanvasView, Listener { private addText(state: any): SVG.Text { const { undefinedAttrValue } = this.configuration; - const { label, clientID, attributes, source } = state; + const { + label, clientID, attributes, source, + } = state; const attrNames = label.attributes.reduce((acc: any, val: any): void => { acc[val.id] = val.name; return acc; 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 acc697ae..251f2ffa 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 @@ -93,7 +93,9 @@ interface Props { export default class CanvasWrapperComponent extends React.PureComponent { public componentDidMount(): void { - const { automaticBordering, showObjectsTextAlways, canvasInstance } = this.props; + const { + automaticBordering, showObjectsTextAlways, canvasInstance, workspace, + } = this.props; // It's awful approach from the point of view React // But we do not have another way because cvat-canvas returns regular DOM element @@ -104,6 +106,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { autoborders: automaticBordering, undefinedAttrValue: consts.UNDEFINED_ATTRIBUTE_VALUE, displayAllText: showObjectsTextAlways, + forceDisableEditing: workspace === Workspace.ATTRIBUTE_ANNOTATION, }); this.initialSetup(); @@ -247,6 +250,18 @@ export default class CanvasWrapperComponent extends React.PureComponent { canvasInstance.rotate(frameAngle); } + if (prevProps.workspace !== workspace) { + if (workspace === Workspace.ATTRIBUTE_ANNOTATION) { + canvasInstance.configure({ + forceDisableEditing: true, + }); + } else if (prevProps.workspace === Workspace.ATTRIBUTE_ANNOTATION) { + canvasInstance.configure({ + forceDisableEditing: false, + }); + } + } + const loadingAnimation = window.document.getElementById('cvat_canvas_loading_animation'); if (loadingAnimation && frameFetching !== prevProps.frameFetching) { if (frameFetching) {