From e81a9a15164ac910eb8d27cc5c5e8cbca72cb8fc Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 7 Feb 2020 19:35:21 +0300 Subject: [PATCH 1/9] Updated eslint version to support config 'ignorePatterns' --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f98b89a4853f..6499b0da0daf 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ }, "dependencies": {}, "devDependencies": { - "eslint": "^6.1.0", + "eslint": "^6.8.0", "eslint-config-airbnb": "^18.0.1", "eslint-plugin-import": "^2.18.2", "eslint-plugin-jsx-a11y": "^6.2.3", From b2b41f8330ef88b00ee64121bffce690cfd13a07 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 7 Feb 2020 19:35:43 +0300 Subject: [PATCH 2/9] Added undo/redo in cvat-core for shapes --- cvat-core/src/annotations-collection.js | 2 + cvat-core/src/annotations-history.js | 71 ++++++++++++++++ cvat-core/src/annotations-objects.js | 77 ++++++++++++++++- cvat-core/src/annotations.js | 68 ++++++++++++++- cvat-core/src/api.js | 2 + cvat-core/src/enums.js | 27 ++++++ cvat-core/src/session.js | 106 ++++++++++++++++++++++-- 7 files changed, 341 insertions(+), 12 deletions(-) create mode 100644 cvat-core/src/annotations-history.js diff --git a/cvat-core/src/annotations-collection.js b/cvat-core/src/annotations-collection.js index 2df189f9a1a0..c4e7f13a84ce 100644 --- a/cvat-core/src/annotations-collection.js +++ b/cvat-core/src/annotations-collection.js @@ -109,6 +109,7 @@ return labelAccumulator; }, {}); + this.history = data.history; this.shapes = {}; // key is a frame this.tags = {}; // key is a frame this.tracks = []; @@ -124,6 +125,7 @@ collectionZ: this.collectionZ, groups: this.groups, frameMeta: this.frameMeta, + history: this.history, }; } diff --git a/cvat-core/src/annotations-history.js b/cvat-core/src/annotations-history.js new file mode 100644 index 000000000000..22106c7a9fba --- /dev/null +++ b/cvat-core/src/annotations-history.js @@ -0,0 +1,71 @@ +/* +* Copyright (C) 2019 Intel Corporation +* SPDX-License-Identifier: MIT +*/ + +const MAX_HISTORY_LENGTH = 128; + +class AnnotationHistory { + constructor() { + this.clear(); + } + + get() { + return { + undo: this._undo.map((undo) => undo.action), + redo: this._redo.map((redo) => redo.action), + }; + } + + do(action, undo, redo, clientIDs) { + const actionItem = { + clientIDs, + action, + undo, + redo, + }; + + this._undo = this._undo.slice(-MAX_HISTORY_LENGTH + 1); + this._undo.push(actionItem); + this._redo = []; + } + + undo(count) { + const affectedObjects = []; + for (let i = 0; i < count; i++) { + const action = this._undo.pop(); + if (action) { + action.undo(); + this.redo.push(action); + affectedObjects.push(...action.clientIDs); + } else { + break; + } + } + + return affectedObjects; + } + + redo(count) { + const affectedObjects = []; + for (let i = 0; i < count; i++) { + const action = this._undo.pop(); + if (action) { + action.redo(); + this.undo.push(action); + affectedObjects.push(...action.clientIDs); + } else { + break; + } + } + + return affectedObjects; + } + + clear() { + this._undo = []; + this._redo = []; + } +} + +module.exports = AnnotationHistory; diff --git a/cvat-core/src/annotations-objects.js b/cvat-core/src/annotations-objects.js index b689f372147a..e9b955aac33d 100644 --- a/cvat-core/src/annotations-objects.js +++ b/cvat-core/src/annotations-objects.js @@ -17,6 +17,7 @@ ObjectShape, ObjectType, AttributeType, + AnnotationsActionsType, } = require('./enums'); const { @@ -139,6 +140,7 @@ class Annotation { constructor(data, clientID, injection) { this.taskLabels = injection.labels; + this.history = injection.history; this.clientID = clientID; this.serverID = data.id; this.group = data.group; @@ -409,38 +411,111 @@ // Now when all fields are validated, we can apply them if (updated.label) { + const undoLabel = this.label; + const redoLabel = data.label; + + this.history.do(AnnotationsActionsType.CHANGED_LABEL, () => { + this.label = undoLabel; + }, () => { + this.label = redoLabel; + }, [this.clientID]); + this.label = data.label; this.attributes = {}; this.appendDefaultAttributes(data.label); } if (updated.attributes) { + const undoAttributes = { ...this.attributes }; + for (const attrID of Object.keys(data.attributes)) { this.attributes[attrID] = data.attributes[attrID]; } + + const redoAttributes = { ...this.attributes }; + + this.history.do(AnnotationsActionsType.CHANGED_ATTRIBUTES, () => { + this.attributes = undoAttributes; + }, () => { + this.attributes = redoAttributes; + }, [this.clientID]); } if (updated.points && fittedPoints.length) { - this.points = [...fittedPoints]; + const undoPoints = this.points; + const redoPoints = fittedPoints; + + this.history.do(AnnotationsActionsType.CHANGED_POINTS, () => { + this.points = undoPoints; + }, () => { + this.points = redoPoints; + }, [this.clientID]); + + this.points = fittedPoints; } if (updated.occluded) { + const undoOccluded = this.occluded; + const redoOccluded = data.occluded; + + this.history.do(AnnotationsActionsType.CHANGED_OCCLUDED, () => { + this.occluded = undoOccluded; + }, () => { + this.occluded = redoOccluded; + }, [this.clientID]); + this.occluded = data.occluded; } if (updated.zOrder) { + const undoZOrder = this.zOrder; + const redoZOrder = data.zOrder; + + this.history.do(AnnotationsActionsType.CHANGED_ZORDER, () => { + this.zOrder = undoZOrder; + }, () => { + this.zOrder = redoZOrder; + }, [this.clientID]); + this.zOrder = data.zOrder; } if (updated.lock) { + const undoLock = this.lock; + const redoLock = data.lock; + + this.history.do(AnnotationsActionsType.CHANGED_LOCK, () => { + this.lock = undoLock; + }, () => { + this.lock = redoLock; + }, [this.clientID]); + this.lock = data.lock; } if (updated.color) { + const undoColor = this.color; + const redoColor = data.color; + + this.history.do(AnnotationsActionsType.CHANGED_COLOR, () => { + this.color = undoColor; + }, () => { + this.color = redoColor; + }, [this.clientID]); + this.color = data.color; } if (updated.hidden) { + const undoHidden = this.hidden; + const redoHidden = data.hidden; + + this.history.do(AnnotationsActionsType.CHANGED_HIDDEN, () => { + this.hidden = undoHidden; + }, () => { + this.hidden = redoHidden; + }, [this.clientID]); + this.hidden = data.hidden; } diff --git a/cvat-core/src/annotations.js b/cvat-core/src/annotations.js index a892512c2811..f9a5e0ae805d 100644 --- a/cvat-core/src/annotations.js +++ b/cvat-core/src/annotations.js @@ -11,6 +11,7 @@ const serverProxy = require('./server-proxy'); const Collection = require('./annotations-collection'); const AnnotationsSaver = require('./annotations-saver'); + const AnnotationsHistory = require('./annotations-history'); const { checkObjectType } = require('./common'); const { Task } = require('./session'); const { @@ -56,8 +57,10 @@ frameMeta[i] = await session.frames.get(i); } + const history = new AnnotationsHistory(); const collection = new Collection({ labels: session.labels || session.task.labels, + history, startFrame, stopFrame, frameMeta, @@ -68,15 +71,20 @@ cache.set(session, { collection, saver, - + history, }); } } async function getAnnotations(session, frame, filter) { - await getAnnotationsFromServer(session); const sessionType = session instanceof Task ? 'task' : 'job'; const cache = getCache(sessionType); + + if (cache.has(session)) { + return cache.get(session).collection.get(frame, filter); + } + + await getAnnotationsFromServer(session); return cache.get(session).collection.get(frame, filter); } @@ -244,6 +252,58 @@ return result; } + function undoActions(session, count) { + const sessionType = session instanceof Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (cache.has(session)) { + return cache.get(session).history.undo(count); + } + + throw new DataError( + 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', + ); + } + + function redoActions(session, count) { + const sessionType = session instanceof Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (cache.has(session)) { + return cache.get(session).history.redo(count); + } + + throw new DataError( + 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', + ); + } + + function clearActions(session) { + const sessionType = session instanceof Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (cache.has(session)) { + return cache.get(session).history.clear(); + } + + throw new DataError( + 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', + ); + } + + function getActions(session) { + const sessionType = session instanceof Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (cache.has(session)) { + return cache.get(session).history.get(); + } + + throw new DataError( + 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', + ); + } + module.exports = { getAnnotations, putAnnotations, @@ -258,5 +318,9 @@ uploadAnnotations, dumpAnnotations, exportDataset, + undoActions, + redoActions, + clearActions, + getActions, }; })(); diff --git a/cvat-core/src/api.js b/cvat-core/src/api.js index 6414e1737ec0..b55a698b3ad4 100644 --- a/cvat-core/src/api.js +++ b/cvat-core/src/api.js @@ -28,6 +28,7 @@ function build() { ObjectType, ObjectShape, LogType, + AnnotationsActionsType, colors, } = require('./enums'); @@ -498,6 +499,7 @@ function build() { ObjectType, ObjectShape, LogType, + AnnotationsActionsType, colors, }, /** diff --git a/cvat-core/src/enums.js b/cvat-core/src/enums.js index 0e9b066b31db..44ff1954b4b4 100644 --- a/cvat-core/src/enums.js +++ b/cvat-core/src/enums.js @@ -166,6 +166,32 @@ rotateImage: 26, }; + /** + * Types of actions with annotations + * @enum {string} + * @name AnnotationsActionsTypes + * @memberof module:API.cvat.enums + * @property {string} CHANGED_LABEL Changed label + * @property {string} CHANGED_ATTRIBUTES Changed attributes + * @property {string} CHANGED_POINTS Changed points + * @property {string} CHANGED_OCCLUDED Changed occluded + * @property {string} CHANGED_ZORDER Changed z-order + * @property {string} CHANGED_LOCK Changed lock + * @property {string} CHANGED_COLOR Changed color + * @property {string} CHANGED_HIDDEN Changed hidden + * @readonly + */ + const AnnotationsActionsType = Object.freeze({ + CHANGED_LABEL: 'Changed label', + CHANGED_ATTRIBUTES: 'Changed attributes', + CHANGED_POINTS: 'Changed points', + CHANGED_OCCLUDED: 'Changed occluded', + CHANGED_ZORDER: 'Changed z-order', + CHANGED_LOCK: 'Changed lock', + CHANGED_COLOR: 'Changed color', + CHANGED_HIDDEN: 'Changed hidden', + }); + /** * Array of hex colors * @type {module:API.cvat.classes.Loader[]} values @@ -189,6 +215,7 @@ ObjectType, ObjectShape, LogType, + AnnotationsActionsType, colors, }; })(); diff --git a/cvat-core/src/session.js b/cvat-core/src/session.js index f02e073cd62c..35bb02e8687a 100644 --- a/cvat-core/src/session.js +++ b/cvat-core/src/session.js @@ -156,6 +156,11 @@ .apiWrapper.call(this, prototype.actions.clear); return result; }, + async get() { + const result = await PluginRegistry + .apiWrapper.call(this, prototype.actions.get); + return result; + }, }, writable: true, }), @@ -454,28 +459,48 @@ */ /** - * Is a dictionary of pairs "id:action" where "id" is an identifier of an object - * which has been affected by undo/redo and "action" is what exactly has been - * done with the object. Action can be: "created", "deleted", "updated". - * Size of an output array equal the param "count". - * @typedef {Object} HistoryAction + * @typedef {Object} HistoryActions + * @property {string[]} [undo] - array of possible actions to undo + * @property {string[]} [redo] - array of possible actions to redo * @global */ /** - * Undo actions + * Make undo * @method undo * @memberof Session.actions - * @returns {HistoryAction} + * @param {number} [count=1] number of actions to undo + * @returns {number[]} Array of affected objects * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} * @instance * @async */ /** - * Redo actions + * Make redo * @method redo * @memberof Session.actions - * @returns {HistoryAction} + * @param {number} [count=1] number of actions to redo + * @returns {number[]} Array of affected objects + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} + * @instance + * @async + */ + /** + * Remove all actions from history + * @method clear + * @memberof Session.actions + * @throws {module:API.cvat.exceptions.PluginError} + * @instance + * @async + */ + /** + * Get actions + * @method get + * @memberof Session.actions + * @returns {HistoryActions} * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} * @instance * @async */ @@ -653,6 +678,13 @@ .annotations.hasUnsavedChanges.bind(this), }; + this.actions = { + undo: Object.getPrototypeOf(this).actions.undo.bind(this), + redo: Object.getPrototypeOf(this).actions.redo.bind(this), + clear: Object.getPrototypeOf(this).actions.clear.bind(this), + get: Object.getPrototypeOf(this).actions.get.bind(this), + }; + this.frames = { get: Object.getPrototypeOf(this).frames.get.bind(this), preview: Object.getPrototypeOf(this).frames.preview.bind(this), @@ -1155,6 +1187,13 @@ .annotations.exportDataset.bind(this), }; + this.actions = { + undo: Object.getPrototypeOf(this).actions.undo.bind(this), + redo: Object.getPrototypeOf(this).actions.redo.bind(this), + clear: Object.getPrototypeOf(this).actions.clear.bind(this), + get: Object.getPrototypeOf(this).actions.get.bind(this), + }; + this.frames = { get: Object.getPrototypeOf(this).frames.get.bind(this), preview: Object.getPrototypeOf(this).frames.preview.bind(this), @@ -1217,6 +1256,10 @@ uploadAnnotations, dumpAnnotations, exportDataset, + undoActions, + redoActions, + clearActions, + getActions, } = require('./annotations'); buildDublicatedAPI(Job.prototype); @@ -1328,6 +1371,31 @@ return result; }; + Job.prototype.annotations.exportDataset.implementation = async function (format) { + const result = await exportDataset(this.task, format); + return result; + }; + + Job.prototype.actions.undo.implementation = function (count) { + const result = undoActions(this, count); + return result; + }; + + Job.prototype.actions.redo.implementation = function (count) { + const result = redoActions(this, count); + return result; + }; + + Job.prototype.actions.clear.implementation = function () { + const result = clearActions(this); + return result; + }; + + Job.prototype.actions.get.implementation = function () { + const result = getActions(this); + return result; + }; + Task.prototype.save.implementation = async function saveTaskImplementation(onUpdate) { // TODO: Add ability to change an owner and an assignee if (typeof (this.id) !== 'undefined') { @@ -1484,4 +1552,24 @@ const result = await exportDataset(this, format); return result; }; + + Task.prototype.actions.undo.implementation = function (count) { + const result = undoActions(this, count); + return result; + }; + + Task.prototype.actions.redo.implementation = function (count) { + const result = redoActions(this, count); + return result; + }; + + Task.prototype.actions.clear.implementation = function () { + const result = clearActions(this); + return result; + }; + + Task.prototype.actions.get.implementation = function () { + const result = getActions(this); + return result; + }; })(); From f3384208938d7e4c4bba1fbeac2120b7fe335ce0 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 7 Feb 2020 19:36:20 +0300 Subject: [PATCH 3/9] Added basic support for undo/redo in UI --- cvat-ui/src/actions/annotation-actions.ts | 26 +++++++++-- .../controls-side-bar/controls-side-bar.tsx | 2 +- .../controls-side-bar/cursor-control.tsx | 2 +- .../controls-side-bar/fit-control.tsx | 2 +- .../controls-side-bar/group-control.tsx | 2 +- .../controls-side-bar/merge-control.tsx | 2 +- .../controls-side-bar/move-control.tsx | 2 +- .../controls-side-bar/resize-control.tsx | 2 +- .../controls-side-bar/rotate-control.tsx | 4 +- .../controls-side-bar/split-control.tsx | 2 +- .../annotation-page/top-bar/left-group.tsx | 20 +++++++-- .../top-bar/player-buttons.tsx | 16 +++---- .../top-bar/player-navigation.tsx | 2 +- .../top-bar/statistics-modal.tsx | 2 +- .../annotation-page/top-bar/top-bar.tsx | 17 +++---- .../create-model-content.tsx | 2 +- .../create-model-page/create-model-form.tsx | 2 +- .../advanced-configuration-form.tsx | 6 +-- .../components/labels-editor/label-form.tsx | 16 +++---- .../labels-editor/labels-editor.tsx | 2 +- .../components/labels-editor/raw-viewer.tsx | 4 +- .../model-runner-modal/model-runner-modal.tsx | 4 +- .../objects-side-bar/object-item.tsx | 9 ++-- .../annotation-page/top-bar/top-bar.tsx | 9 ++++ cvat-ui/src/reducers/annotation-reducer.ts | 45 ++++++++++++++++--- cvat-ui/src/reducers/interfaces.ts | 4 ++ 26 files changed, 143 insertions(+), 63 deletions(-) diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 6f57ea46a1f1..ac68839a3a54 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -91,11 +91,14 @@ export function removeAnnotationsAsync(sessionInstance: any): ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { try { - sessionInstance.annotations.clear(); + await sessionInstance.annotations.clear(); + await sessionInstance.actions.clear(); + const history = await sessionInstance.actions.get(); + dispatch({ type: AnnotationActionTypes.REMOVE_JOB_ANNOTATIONS_SUCCESS, payload: { - sessionInstance, + history, }, }); } catch (error) { @@ -264,11 +267,13 @@ export function propagateObjectAsync( } await sessionInstance.annotations.put(states); + const history = await sessionInstance.actions.get(); dispatch({ type: AnnotationActionTypes.PROPAGATE_OBJECT_SUCCESS, payload: { objectState, + history, }, }); } catch (error) { @@ -300,16 +305,19 @@ export function changePropagateFrames(frames: number): AnyAction { }; } -export function removeObjectAsync(objectState: any, force: boolean): +export function removeObjectAsync(sessionInstance: any, objectState: any, force: boolean): ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { try { const removed = await objectState.delete(force); + const history = await sessionInstance.actions.get(); + if (removed) { dispatch({ type: AnnotationActionTypes.REMOVE_OBJECT_SUCCESS, payload: { objectState, + history, }, }); } else { @@ -645,11 +653,13 @@ ThunkAction, {}, {}, AnyAction> { try { const promises = statesToUpdate.map((state: any): Promise => state.save()); const states = await Promise.all(promises); + const history = await sessionInstance.actions.get(); dispatch({ type: AnnotationActionTypes.UPDATE_ANNOTATIONS_SUCCESS, payload: { states, + history, }, }); } catch (error) { @@ -671,11 +681,13 @@ ThunkAction, {}, {}, AnyAction> { try { await sessionInstance.annotations.put(statesToCreate); const states = await sessionInstance.annotations.get(frame); + const history = await sessionInstance.actions.get(); dispatch({ type: AnnotationActionTypes.CREATE_ANNOTATIONS_SUCCESS, payload: { states, + history, }, }); } catch (error) { @@ -695,11 +707,13 @@ ThunkAction, {}, {}, AnyAction> { try { await sessionInstance.annotations.merge(statesToMerge); const states = await sessionInstance.annotations.get(frame); + const history = await sessionInstance.actions.get(); dispatch({ type: AnnotationActionTypes.MERGE_ANNOTATIONS_SUCCESS, payload: { states, + history, }, }); } catch (error) { @@ -719,11 +733,13 @@ ThunkAction, {}, {}, AnyAction> { try { await sessionInstance.annotations.group(statesToGroup); const states = await sessionInstance.annotations.get(frame); + const history = await sessionInstance.actions.get(); dispatch({ type: AnnotationActionTypes.GROUP_ANNOTATIONS_SUCCESS, payload: { states, + history, }, }); } catch (error) { @@ -743,11 +759,13 @@ ThunkAction, {}, {}, AnyAction> { try { await sessionInstance.annotations.split(stateToSplit, frame); const states = await sessionInstance.annotations.get(frame); + const history = await sessionInstance.actions.get(); dispatch({ type: AnnotationActionTypes.SPLIT_ANNOTATIONS_SUCCESS, payload: { states, + history, }, }); } catch (error) { @@ -772,11 +790,13 @@ export function changeLabelColorAsync( const updatedLabel = label; updatedLabel.color = color; const states = await sessionInstance.annotations.get(frameNumber); + const history = await sessionInstance.actions.get(); dispatch({ type: AnnotationActionTypes.CHANGE_LABEL_COLOR_SUCCESS, payload: { label: updatedLabel, + history, states, }, }); 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 494718972e08..0870b53f3e2e 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 @@ -86,7 +86,7 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element { isDrawing={activeControl === ActiveControl.DRAW_POINTS} /> - + 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 ae590cc44aa5..0ec834641e66 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 @@ -29,7 +29,7 @@ function CursorControl(props: Props): JSX.Element { } = props; return ( - + + canvasInstance.fit()} /> ); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/group-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/group-control.tsx index f6b6c7175857..63268013b573 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/group-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/group-control.tsx @@ -42,7 +42,7 @@ function GroupControl(props: Props): JSX.Element { }; return ( - + ); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/merge-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/merge-control.tsx index 9e43855473ca..db268eac7d54 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/merge-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/merge-control.tsx @@ -42,7 +42,7 @@ function MergeControl(props: Props): JSX.Element { }; return ( - + ); 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 e17659081ded..5379c72dbe0c 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 @@ -29,7 +29,7 @@ function MoveControl(props: Props): JSX.Element { } = props; return ( - + + - + canvasInstance @@ -40,7 +40,7 @@ function RotateControl(props: Props): JSX.Element { component={RotateIcon} /> - + canvasInstance diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/split-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/split-control.tsx index 6190d4dfd129..4b7700209d27 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/split-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/split-control.tsx @@ -42,7 +42,7 @@ function SplitControl(props: Props): JSX.Element { }; return ( - + ); diff --git a/cvat-ui/src/components/annotation-page/top-bar/left-group.tsx b/cvat-ui/src/components/annotation-page/top-bar/left-group.tsx index c76cf232c767..14705dd8b1ce 100644 --- a/cvat-ui/src/components/annotation-page/top-bar/left-group.tsx +++ b/cvat-ui/src/components/annotation-page/top-bar/left-group.tsx @@ -21,6 +21,8 @@ import { interface Props { saving: boolean; savingStatuses: string[]; + undoAction?: string; + redoAction?: string; onSaveAnnotation(): void; } @@ -28,6 +30,8 @@ function LeftGroup(props: Props): JSX.Element { const { saving, savingStatuses, + undoAction, + redoAction, onSaveAnnotation, } = props; @@ -67,11 +71,21 @@ function LeftGroup(props: Props): JSX.Element { - - diff --git a/cvat-ui/src/components/annotation-page/top-bar/player-buttons.tsx b/cvat-ui/src/components/annotation-page/top-bar/player-buttons.tsx index 3c624c854dce..7234494353d0 100644 --- a/cvat-ui/src/components/annotation-page/top-bar/player-buttons.tsx +++ b/cvat-ui/src/components/annotation-page/top-bar/player-buttons.tsx @@ -42,19 +42,19 @@ function PlayerButtons(props: Props): JSX.Element { return ( - + - + - + {!playing ? ( - + ) : ( - + + - + - + diff --git a/cvat-ui/src/components/annotation-page/top-bar/player-navigation.tsx b/cvat-ui/src/components/annotation-page/top-bar/player-navigation.tsx index 8dcabee0bc68..20a6bd0ca4c5 100644 --- a/cvat-ui/src/components/annotation-page/top-bar/player-navigation.tsx +++ b/cvat-ui/src/components/annotation-page/top-bar/player-navigation.tsx @@ -44,7 +44,7 @@ function PlayerNavigation(props: Props): JSX.Element { - + filename.png diff --git a/cvat-ui/src/components/annotation-page/top-bar/statistics-modal.tsx b/cvat-ui/src/components/annotation-page/top-bar/statistics-modal.tsx index 1d8301ae07d0..71ff6765123f 100644 --- a/cvat-ui/src/components/annotation-page/top-bar/statistics-modal.tsx +++ b/cvat-ui/src/components/annotation-page/top-bar/statistics-modal.tsx @@ -90,7 +90,7 @@ export default function StatisticsModalComponent(props: Props): JSX.Element { }); const makeShapesTracksTitle = (title: string): JSX.Element => ( - + {title} 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 399dc3f6ac09..661f641861f1 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 @@ -20,6 +20,8 @@ interface Props { frameNumber: number; startFrame: number; stopFrame: number; + undoAction?: string; + redoAction?: string; showStatistics(): void; onSwitchPlay(): void; onSaveAnnotation(): void; @@ -33,19 +35,12 @@ interface Props { onInputChange(value: number | undefined): void; } -function propsAreEqual(curProps: Props, prevProps: Props): boolean { - return curProps.playing === prevProps.playing - && curProps.saving === prevProps.saving - && curProps.frameNumber === prevProps.frameNumber - && curProps.startFrame === prevProps.startFrame - && curProps.stopFrame === prevProps.stopFrame - && curProps.savingStatuses.length === prevProps.savingStatuses.length; -} - function AnnotationTopBarComponent(props: Props): JSX.Element { const { saving, savingStatuses, + undoAction, + redoAction, playing, frameNumber, startFrame, @@ -70,6 +65,8 @@ function AnnotationTopBarComponent(props: Props): JSX.Element { saving={saving} savingStatuses={savingStatuses} onSaveAnnotation={onSaveAnnotation} + undoAction={undoAction} + redoAction={redoAction} /> @@ -98,4 +95,4 @@ function AnnotationTopBarComponent(props: Props): JSX.Element { ); } -export default React.memo(AnnotationTopBarComponent, propsAreEqual); +export default React.memo(AnnotationTopBarComponent); 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 947122a9ccc3..dc61f3252592 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 @@ -107,7 +107,7 @@ export default class CreateModelContent extends React.PureComponent { return ( - + { // false positive diff --git a/cvat-ui/src/components/create-model-page/create-model-form.tsx b/cvat-ui/src/components/create-model-page/create-model-form.tsx index dcd8d7095147..d5d07dea230a 100644 --- a/cvat-ui/src/components/create-model-page/create-model-form.tsx +++ b/cvat-ui/src/components/create-model-page/create-model-form.tsx @@ -59,7 +59,7 @@ export class CreateModelForm extends React.PureComponent { - + { getFieldDecorator('global', { initialValue: false, valuePropName: 'checked', 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 2c654661b751..22ad7d246450 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 @@ -85,7 +85,7 @@ class AdvancedConfigurationForm extends React.PureComponent { return ( Image quality}> - + {form.getFieldDecorator('imageQuality', { initialValue: 70, rules: [{ @@ -111,7 +111,7 @@ class AdvancedConfigurationForm extends React.PureComponent { return ( Overlap size}> - + {form.getFieldDecorator('overlapSize')( , )} @@ -125,7 +125,7 @@ class AdvancedConfigurationForm extends React.PureComponent { return ( Segment size}> - + {form.getFieldDecorator('segmentSize')( , )} diff --git a/cvat-ui/src/components/labels-editor/label-form.tsx b/cvat-ui/src/components/labels-editor/label-form.tsx index 22f5cb930c15..ef525d9b36fb 100644 --- a/cvat-ui/src/components/labels-editor/label-form.tsx +++ b/cvat-ui/src/components/labels-editor/label-form.tsx @@ -138,7 +138,7 @@ class LabelForm extends React.PureComponent { return ( - + { form.getFieldDecorator(`type[${key}]`, { initialValue: type, })( @@ -188,7 +188,7 @@ class LabelForm extends React.PureComponent { }; return ( - + { form.getFieldDecorator(`values[${key}]`, { initialValue: existedValues, @@ -215,7 +215,7 @@ class LabelForm extends React.PureComponent { const { form } = this.props; return ( - + { form.getFieldDecorator(`values[${key}]`, { initialValue: value, @@ -299,7 +299,7 @@ class LabelForm extends React.PureComponent { return ( - + { form.getFieldDecorator(`mutable[${key}]`, { initialValue: value, valuePropName: 'checked', @@ -316,7 +316,7 @@ class LabelForm extends React.PureComponent { return ( - +