diff --git a/cvat-canvas/src/typescript/canvasModel.ts b/cvat-canvas/src/typescript/canvasModel.ts index 2c7a1f08d20..0ad62484c14 100644 --- a/cvat-canvas/src/typescript/canvasModel.ts +++ b/cvat-canvas/src/typescript/canvasModel.ts @@ -96,6 +96,7 @@ export interface Configuration { controlPointsSize?: number; outlinedBorders?: string | false; resetZoom?: boolean; + hideEditedObject?: boolean; } export interface BrushTool { @@ -416,6 +417,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { textPosition: consts.DEFAULT_SHAPE_TEXT_POSITION, textContent: consts.DEFAULT_SHAPE_TEXT_CONTENT, undefinedAttrValue: consts.DEFAULT_UNDEFINED_ATTR_VALUE, + hideEditedObject: false, }, imageBitmap: false, image: null, @@ -981,6 +983,10 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { this.data.configuration.CSSImageFilter = configuration.CSSImageFilter; } + if (typeof configuration.hideEditedObject === 'boolean') { + this.data.configuration.hideEditedObject = configuration.hideEditedObject; + } + this.notify(UpdateReasons.CONFIG_UPDATED); } diff --git a/cvat-canvas/src/typescript/masksHandler.ts b/cvat-canvas/src/typescript/masksHandler.ts index cdaa4d86d2f..7b1080977ab 100644 --- a/cvat-canvas/src/typescript/masksHandler.ts +++ b/cvat-canvas/src/typescript/masksHandler.ts @@ -65,6 +65,7 @@ export class MasksHandlerImpl implements MasksHandler { private startTimestamp: number; private geometry: Geometry; private drawingOpacity: number; + private isHidden: boolean; private keepDrawnPolygon(): void { const canvasWrapper = this.canvas.getElement().parentElement; @@ -217,12 +218,22 @@ export class MasksHandlerImpl implements MasksHandler { private imageDataFromCanvas(wrappingBBox: WrappingBBox): Uint8ClampedArray { const imageData = this.canvas.toCanvasElement() .getContext('2d').getImageData( - wrappingBBox.left, wrappingBBox.top, - wrappingBBox.right - wrappingBBox.left + 1, wrappingBBox.bottom - wrappingBBox.top + 1, + wrappingBBox.left, + wrappingBBox.top, + wrappingBBox.right - wrappingBBox.left + 1, + wrappingBBox.bottom - wrappingBBox.top + 1, ).data; return imageData; } + private updateHidden(value: boolean) { + if (value) { + this.canvas.getElement().parentElement.style.display = 'none'; + } else { + this.canvas.getElement().parentElement.style.display = 'block'; + } + } + private updateBrushTools(brushTool?: BrushTool, opts: Partial = {}): void { if (this.isPolygonDrawing) { // tool was switched from polygon to brush for example @@ -530,6 +541,10 @@ export class MasksHandlerImpl implements MasksHandler { public configurate(configuration: Configuration): void { this.colorBy = configuration.colorBy; + + if (this.isHidden !== configuration.hideEditedObject) { + this.updateHidden(configuration.hideEditedObject); + } } public transform(geometry: Geometry): void { @@ -563,7 +578,10 @@ export class MasksHandlerImpl implements MasksHandler { const color = fabric.Color.fromHex(this.getStateColor(drawData.initialState)).getSource(); const [left, top, right, bottom] = points.slice(-4); const imageBitmap = expandChannels(color[0], color[1], color[2], points); - imageDataToDataURL(imageBitmap, right - left + 1, bottom - top + 1, + imageDataToDataURL( + imageBitmap, + right - left + 1, + bottom - top + 1, (dataURL: string) => new Promise((resolve) => { fabric.Image.fromURL(dataURL, (image: fabric.Image) => { try { @@ -654,7 +672,10 @@ export class MasksHandlerImpl implements MasksHandler { const color = fabric.Color.fromHex(this.getStateColor(editData.state)).getSource(); const [left, top, right, bottom] = points.slice(-4); const imageBitmap = expandChannels(color[0], color[1], color[2], points); - imageDataToDataURL(imageBitmap, right - left + 1, bottom - top + 1, + imageDataToDataURL( + imageBitmap, + right - left + 1, + bottom - top + 1, (dataURL: string) => new Promise((resolve) => { fabric.Image.fromURL(dataURL, (image: fabric.Image) => { try { diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 14eff3d6387..ae7f1716a86 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -126,7 +126,9 @@ export enum AnnotationActionTypes { COLLAPSE_APPEARANCE = 'COLLAPSE_APPEARANCE', COLLAPSE_OBJECT_ITEMS = 'COLLAPSE_OBJECT_ITEMS', ACTIVATE_OBJECT = 'ACTIVATE_OBJECT', - EDIT_OBJECT = 'EDIT_OBJECT', + UPDATE_EDITED_STATE = 'UPDATE_EDITED_STATE', + HIDE_EDITED_STATE = 'HIDE_EDITED_STATE', + RESET_EDITED_STATE = 'RESET_EDITED_STATE', REMOVE_OBJECT = 'REMOVE_OBJECT', REMOVE_OBJECT_SUCCESS = 'REMOVE_OBJECT_SUCCESS', REMOVE_OBJECT_FAILED = 'REMOVE_OBJECT_FAILED', @@ -243,16 +245,43 @@ export function highlightConflict(conflict: QualityConflict | null): AnyAction { }; } -// TODO: think about better name for this action -export function editObject(objectType: ShapeType | null): AnyAction { +export function updateEditedState(shapeType: ShapeType | null, editedState: ObjectState | null): AnyAction { return { - type: AnnotationActionTypes.EDIT_OBJECT, + type: AnnotationActionTypes.UPDATE_EDITED_STATE, payload: { - objectType, + shapeType, + editedState, }, }; } +export function resetEditedState(): AnyAction { + return { + type: AnnotationActionTypes.RESET_EDITED_STATE, + payload: {}, + }; +} + +// TODO: change to regular func, we dont need async +export function hideEditedState(hide: boolean): ThunkAction { + return async (dispatch: ThunkDispatch, getState): Promise => { + const state = getState(); + const { instance: canvas } = state.annotation.canvas; + if (canvas) { + (canvas as Canvas).configure({ + hideEditedObject: hide, + }); + } + + dispatch({ + type: AnnotationActionTypes.HIDE_EDITED_STATE, + payload: { + hide, + }, + }); + }; +} + function wrapAnnotationsInGTJob(states: ObjectState[]): ObjectState[] { return states.map((state: ObjectState) => new Proxy(state, { get(_state, prop) { diff --git a/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/canvas-wrapper.tsx b/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/canvas-wrapper.tsx index fd9c63326e1..d861745f4af 100644 --- a/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/canvas-wrapper.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/canvas-wrapper.tsx @@ -45,7 +45,8 @@ import { fetchAnnotationsAsync, getDataFailed, canvasErrorOccurred, - editObject, + updateEditedState, + resetEditedState, } from 'actions/annotation-actions'; import { switchGrid, @@ -147,7 +148,8 @@ interface DispatchToProps { onGetDataFailed(error: Error): void; onCanvasErrorOccurred(error: Error): void; onStartIssue(position: number[]): void; - onEditObject(objectType: ShapeType | null): void; + onUpdateEditedObject(objectType: ShapeType | null, editedState: ObjectState | null): void; + onResetEditedObject(): void; } function mapStateToProps(state: CombinedState): StateToProps { @@ -349,8 +351,11 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { onStartIssue(position: number[]): void { dispatch(reviewActions.startIssue(position)); }, - onEditObject(objectType: ShapeType | null): void { - dispatch(editObject(objectType)); + onUpdateEditedObject(objectType: ShapeType | null, editedState: ObjectState | null): void { + dispatch(updateEditedState(objectType, editedState)); + }, + onResetEditedObject(): void { + dispatch(resetEditedState()); }, }; } @@ -639,7 +644,7 @@ class CanvasWrapperComponent extends React.PureComponent { private onCanvasShapeDrawn = (event: any): void => { const { jobInstance, activeLabelID, activeObjectType, frame, updateActiveControl, onCreateAnnotations, - onEditObject, + onResetEditedObject, } = this.props; if (!event.detail.continue) { @@ -675,7 +680,7 @@ class CanvasWrapperComponent extends React.PureComponent { const objectState = new cvat.classes.ObjectState(state); onCreateAnnotations([objectState]); - onEditObject(null); + onResetEditedObject(); }; private onCanvasObjectsMerged = (event: any): void => { @@ -837,19 +842,19 @@ class CanvasWrapperComponent extends React.PureComponent { }; private onCanvasDrawStart = (event: any): void => { - const { onEditObject } = this.props; - onEditObject(event.detail.drawData.shapeType); + const { onUpdateEditedObject } = this.props; + onUpdateEditedObject(event.detail.drawData.shapeType, null); }; private onCanvasEditStart = (event: any): void => { - const { updateActiveControl, onEditObject } = this.props; + const { updateActiveControl, onUpdateEditedObject } = this.props; updateActiveControl(ActiveControl.EDIT); - onEditObject(event.detail.state.shapeType); + onUpdateEditedObject(event.detail.state.shapeType, event.detail.state); }; private onCanvasEditDone = (event: any): void => { const { - activeControl, onUpdateAnnotations, updateActiveControl, onEditObject, + activeControl, onUpdateAnnotations, updateActiveControl, onResetEditedObject, } = this.props; const { state, points, rotation } = event.detail; if (state.rotation !== rotation) { @@ -863,7 +868,7 @@ class CanvasWrapperComponent extends React.PureComponent { updateActiveControl(ActiveControl.CURSOR); } onUpdateAnnotations([state]); - onEditObject(null); + onResetEditedObject(); }; private onCanvasSliceDone = (event: any): void => { @@ -903,9 +908,9 @@ class CanvasWrapperComponent extends React.PureComponent { }; private onCanvasCancel = (): void => { - const { onResetCanvas, onEditObject } = this.props; + const { onResetCanvas, onResetEditedObject } = this.props; onResetCanvas(); - onEditObject(null); + onResetEditedObject(); }; private onCanvasFindObject = async (e: any): Promise => { diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx index 7f120cc981d..68a207fe708 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx @@ -19,6 +19,7 @@ import { switchPropagateVisibility as switchPropagateVisibilityAction, removeObject as removeObjectAction, fetchAnnotationsAsync, + hideEditedState, } from 'actions/annotation-actions'; import { changeShowGroundTruth as changeShowGroundTruthAction, @@ -56,6 +57,11 @@ interface StateToProps { normalizedKeyMap: Record; showGroundTruth: boolean; workspace: Workspace; + editedState: { + shapeType: ShapeType | null; + editedState: ObjectState | null; + editedStateHidden: boolean; + }, } interface DispatchToProps { @@ -67,6 +73,7 @@ interface DispatchToProps { changeFrame(frame: number): void; changeGroupColor(group: number, color: string): void; changeShowGroundTruth(value: boolean): void; + changeHideEditedState(value: boolean): void; } const componentShortcuts = { @@ -180,6 +187,7 @@ function mapStateToProps(state: CombinedState): StateToProps { collapsedAll, activatedStateID, activatedElementID, + edited, zLayer: { min: minZLayer, max: maxZLayer }, }, job: { instance: jobInstance }, @@ -233,6 +241,7 @@ function mapStateToProps(state: CombinedState): StateToProps { normalizedKeyMap, showGroundTruth, workspace, + editedState: edited, }; } @@ -263,6 +272,9 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { dispatch(changeShowGroundTruthAction(value)); dispatch(fetchAnnotationsAsync()); }, + changeHideEditedState(value: boolean): void { + dispatch(hideEditedState(value)); + }, }; } @@ -475,6 +487,11 @@ class ObjectsListContainer extends React.PureComponent { SWITCH_HIDDEN: (event: KeyboardEvent | undefined) => { preventDefault(event); const state = activatedState(); + const { editedState, changeHideEditedState } = this.props; + console.log(editedState); + if (editedState.shapeType === ShapeType.MASK) { + changeHideEditedState(!editedState.editedStateHidden); + } if (state) { state.hidden = !state.hidden; updateAnnotations([state]); diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index 15837f87a3e..2a97edb3459 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -98,7 +98,11 @@ const defaultState: AnnotationState = { activatedStateID: null, activatedElementID: null, activatedAttributeID: null, - editedState: null, + edited: { + shapeType: null, + editedState: null, + editedStateHidden: false, + }, highlightedConflict: null, saving: { forceExit: false, @@ -623,13 +627,44 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { }, }; } - case AnnotationActionTypes.EDIT_OBJECT: { - const { objectType } = action.payload; + case AnnotationActionTypes.RESET_EDITED_STATE: { + return { + ...state, + annotations: { + ...state.annotations, + edited: { + ...state.annotations.edited, + shapeType: null, + editedState: null, + editedStateHidden: false, + }, + }, + }; + } + case AnnotationActionTypes.UPDATE_EDITED_STATE: { + const { shapeType, editedState } = action.payload; return { ...state, annotations: { ...state.annotations, - editedState: objectType, + edited: { + ...state.annotations.edited, + shapeType: (shapeType ?? null), + editedState: (editedState ?? null), + }, + }, + }; + } + case AnnotationActionTypes.HIDE_EDITED_STATE: { + const { hide } = action.payload; + return { + ...state, + annotations: { + ...state.annotations, + edited: { + ...state.annotations.edited, + editedStateHidden: hide, + }, }, }; } diff --git a/cvat-ui/src/reducers/index.ts b/cvat-ui/src/reducers/index.ts index 2508c83a31b..65b87d0e887 100644 --- a/cvat-ui/src/reducers/index.ts +++ b/cvat-ui/src/reducers/index.ts @@ -9,6 +9,7 @@ import { Webhook, MLModel, Organization, Job, Task, Project, Label, User, QualityConflict, FramesMetaData, RQStatus, Event, Invitation, SerializedAPISchema, Request, TargetMetric, ValidationLayout, + ObjectState, } from 'cvat-core-wrapper'; import { IntelligentScissors } from 'utils/opencv-wrapper/intelligent-scissors'; import { KeyMap, KeyMapItem } from 'utils/mousetrap-react'; @@ -770,7 +771,11 @@ export interface AnnotationState { activatedStateID: number | null; activatedElementID: number | null; activatedAttributeID: number | null; - editedState: ShapeType | null; + edited: { + shapeType: ShapeType | null; + editedState: ObjectState | null; + editedStateHidden: boolean; + }; highlightedConflict: QualityConflict | null; collapsed: Record; collapsedAll: boolean;