From 62cd4eccdceef6b199f5b194e432aaf05983e90c Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 25 Mar 2020 18:25:54 +0300 Subject: [PATCH 1/5] Fixed: Inconsistent labels between UI and CLI/API --- cvat-ui/src/components/labels-editor/common.ts | 2 +- cvat-ui/src/components/labels-editor/label-form.tsx | 4 ++-- cvat-ui/src/components/labels-editor/labels-editor.tsx | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cvat-ui/src/components/labels-editor/common.ts b/cvat-ui/src/components/labels-editor/common.ts index 91fd1d8bd727..5ea21f53c5e1 100644 --- a/cvat-ui/src/components/labels-editor/common.ts +++ b/cvat-ui/src/components/labels-editor/common.ts @@ -5,7 +5,7 @@ export interface Attribute { id: number; name: string; - type: string; + input_type: string; mutable: boolean; values: string[]; } diff --git a/cvat-ui/src/components/labels-editor/label-form.tsx b/cvat-ui/src/components/labels-editor/label-form.tsx index 014967a26adb..85c5ff0ddece 100644 --- a/cvat-ui/src/components/labels-editor/label-form.tsx +++ b/cvat-ui/src/components/labels-editor/label-form.tsx @@ -76,7 +76,7 @@ class LabelForm extends React.PureComponent { return { name: formValues.attrName[key], - type: formValues.type[key], + input_type: formValues.type[key], mutable: formValues.mutable[key], id: label && index < label.attributes.length ? label.attributes[index].id : key, @@ -136,7 +136,7 @@ class LabelForm extends React.PureComponent { private renderAttributeTypeInput(key: number, attr: Attribute | null): JSX.Element { const locked = attr ? attr.id >= 0 : false; - const type = attr ? attr.type.toUpperCase() : AttributeType.SELECT; + const type = attr ? attr.input_type.toUpperCase() : AttributeType.SELECT; const { form } = this.props; return ( diff --git a/cvat-ui/src/components/labels-editor/labels-editor.tsx b/cvat-ui/src/components/labels-editor/labels-editor.tsx index 06890d7500fb..28274e34d2ca 100644 --- a/cvat-ui/src/components/labels-editor/labels-editor.tsx +++ b/cvat-ui/src/components/labels-editor/labels-editor.tsx @@ -73,7 +73,7 @@ export default class LabelsEditor { id: attr.id || idGenerator(), name: attr.name, - type: attr.input_type, + input_type: attr.input_type, mutable: attr.mutable, values: [...attr.values], } @@ -207,7 +207,7 @@ export default class LabelsEditor { name: attr.name, id: attr.id < 0 ? undefined : attr.id, - input_type: attr.type.toLowerCase(), + input_type: attr.input_type.toLowerCase(), default_value: attr.values[0], mutable: attr.mutable, values: [...attr.values], From 73a7a7fd79f8dc5adbbe4fcf3446d3b5ab764ec4 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 25 Mar 2020 19:02:08 +0300 Subject: [PATCH 2/5] Fixed resize on right mouse button --- cvat-canvas/src/typescript/canvasView.ts | 6 +----- cvat-canvas/src/typescript/svg.patch.ts | 5 +++++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 0074f281a69a..3290a3824673 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -1208,11 +1208,7 @@ export class CanvasViewImpl implements CanvasView, Listener { let shapeSizeElement: ShapeSizeElement | null = null; let resized = false; - (shape as any).resize().on('resizestart', (e: any): void => { - if (e.detail.event.detail.event.button === 2) { - e.preventDefault(); - return; - } + (shape as any).resize().on('resizestart', (): void => { this.mode = Mode.RESIZE; if (state.shapeType === 'rectangle') { shapeSizeElement = displayShapeSize(this.adoptedContent, this.adoptedText); diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index 5d6c6aae42f0..1c532105c797 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -161,6 +161,11 @@ SVG.Element.prototype.resize = function constructor(...args: any): any { if (!handler) { originalResize.call(this, ...args); handler = this.remember('_resizeHandler'); + handler.resize = function(e: any) { + if (e.detail.event.button === 0) { + return handler.constructor.prototype.resize.call(this, e); + } + } handler.update = function(e: any) { this.m = this.el.node.getScreenCTM().inverse(); return handler.constructor.prototype.update.call(this, e); From 5e2f2ecf316e95eb22009e3311023a0eae54ca24 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 25 Mar 2020 19:28:34 +0300 Subject: [PATCH 3/5] Fixed create object URL after first save, fixed URL itself --- cvat-ui/src/actions/annotation-actions.ts | 8 +++++++- .../src/containers/annotation-page/annotation-page.tsx | 9 +++++---- .../standard-workspace/objects-side-bar/object-item.tsx | 2 +- cvat-ui/src/reducers/annotation-reducer.ts | 2 ++ 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index fbb025877bd3..7d35d529947b 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -973,6 +973,8 @@ export function getJobAsync( export function saveAnnotationsAsync(sessionInstance: any): ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { + const { filters, frame, showAllInterpolationTracks } = receiveAnnotationsParameters(); + dispatch({ type: AnnotationActionTypes.SAVE_ANNOTATIONS, payload: {}, @@ -992,6 +994,8 @@ ThunkAction, {}, {}, AnyAction> { }); }); + const states = await sessionInstance + .annotations.get(frame, showAllInterpolationTracks, filters); await saveJobEvent.close(); await sessionInstance.logger.log( LogType.sendTaskInfo, @@ -1001,7 +1005,9 @@ ThunkAction, {}, {}, AnyAction> { dispatch({ type: AnnotationActionTypes.SAVE_ANNOTATIONS_SUCCESS, - payload: {}, + payload: { + states, + }, }); } catch (error) { dispatch({ diff --git a/cvat-ui/src/containers/annotation-page/annotation-page.tsx b/cvat-ui/src/containers/annotation-page/annotation-page.tsx index 8d2972ba66ba..a47b3572489f 100644 --- a/cvat-ui/src/containers/annotation-page/annotation-page.tsx +++ b/cvat-ui/src/containers/annotation-page/annotation-page.tsx @@ -63,10 +63,11 @@ function mapDispatchToProps(dispatch: any, own: OwnProps): DispatchToProps { } } - if (searchParams.has('object')) { - const searchObject = +(searchParams.get('object') as string); - if (!Number.isNaN(searchObject)) { - initialFilters.push(`serverID==${searchObject}`); + if (searchParams.has('serverID') && searchParams.has('type')) { + const serverID = searchParams.get('serverID'); + const type = searchParams.get('type'); + if (serverID && !Number.isNaN(+serverID)) { + initialFilters.push(`serverID==${serverID} & type=="${type}"`); } } diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx index 4efc88037c04..b7a189339bec 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx @@ -255,7 +255,7 @@ class ObjectItemContainer extends React.PureComponent { pathname, } = window.location; - const search = `frame=${frameNumber}&object=${objectState.serverID}`; + const search = `frame=${frameNumber}&type=${objectState.objectType}&serverID=${objectState.serverID}`; const url = `${origin}${pathname}?${search}`; copy(url); }; diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index 6f13b16fc36b..1dd788b943b0 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -267,10 +267,12 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { }; } case AnnotationActionTypes.SAVE_ANNOTATIONS_SUCCESS: { + const { states } = action.payload; return { ...state, annotations: { ...state.annotations, + states, saving: { ...state.annotations.saving, uploading: false, From c55cbdefe59060befc375ff4248a95c996344e4b Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 25 Mar 2020 20:16:38 +0300 Subject: [PATCH 4/5] Undo/redo returns frame where was a change (as it was done in previous version) --- cvat-core/src/annotations-collection.js | 11 +- cvat-core/src/annotations-history.js | 7 +- cvat-core/src/annotations-objects.js | 100 +++++------ cvat-core/src/object-state.js | 14 +- cvat-core/src/session.js | 1 + cvat-ui/src/actions/annotation-actions.ts | 168 +++++++++--------- .../annotation-page/top-bar/top-bar.tsx | 4 +- cvat-ui/src/reducers/interfaces.ts | 4 +- 8 files changed, 163 insertions(+), 146 deletions(-) diff --git a/cvat-core/src/annotations-collection.js b/cvat-core/src/annotations-collection.js index 2135780ce8c4..e3ba2735bc96 100644 --- a/cvat-core/src/annotations-collection.js +++ b/cvat-core/src/annotations-collection.js @@ -427,7 +427,10 @@ for (const object of objectsForMerge) { object.removed = true; } - }, [...objectsForMerge.map((object) => object.clientID), trackModel.clientID]); + }, [ + ...objectsForMerge + .map((object) => object.clientID), trackModel.clientID, + ], objectStates[0].frame); } split(objectState, frame) { @@ -522,7 +525,7 @@ object.removed = true; prevTrack.removed = false; nextTrack.removed = false; - }, [object.clientID, prevTrack.clientID, nextTrack.clientID]); + }, [object.clientID, prevTrack.clientID, nextTrack.clientID], frame); } group(objectStates, reset) { @@ -554,7 +557,7 @@ objectsForGroup.forEach((object, idx) => { object.group = redoGroups[idx]; }); - }, objectsForGroup.map((object) => object.clientID)); + }, objectsForGroup.map((object) => object.clientID), objectStates[0].frame); return groupIdx; } @@ -790,7 +793,7 @@ importedArray.forEach((object) => { object.removed = false; }); - }, importedArray.map((object) => object.clientID)); + }, importedArray.map((object) => object.clientID), objectStates[0].frame); } select(objectStates, x, y) { diff --git a/cvat-core/src/annotations-history.js b/cvat-core/src/annotations-history.js index d98973e5f86d..4fdbf34c193f 100644 --- a/cvat-core/src/annotations-history.js +++ b/cvat-core/src/annotations-history.js @@ -12,17 +12,18 @@ class AnnotationHistory { get() { return { - undo: this._undo.map((undo) => undo.action), - redo: this._redo.map((redo) => redo.action), + undo: this._undo.map((undo) => [undo.action, undo.frame]), + redo: this._redo.map((redo) => [redo.action, redo.frame]), }; } - do(action, undo, redo, clientIDs) { + do(action, undo, redo, clientIDs, frame) { const actionItem = { clientIDs, action, undo, redo, + frame, }; this._undo = this._undo.slice(-MAX_HISTORY_LENGTH + 1); diff --git a/cvat-core/src/annotations-objects.js b/cvat-core/src/annotations-objects.js index 245d8e375c10..47c65fb9898d 100644 --- a/cvat-core/src/annotations-objects.js +++ b/cvat-core/src/annotations-objects.js @@ -178,7 +178,7 @@ injection.groups.max = Math.max(injection.groups.max, this.group); } - _saveLock(lock) { + _saveLock(lock, frame) { const undoLock = this.lock; const redoLock = lock; @@ -186,12 +186,12 @@ this.lock = undoLock; }, () => { this.lock = redoLock; - }, [this.clientID]); + }, [this.clientID], frame); this.lock = lock; } - _saveColor(color) { + _saveColor(color, frame) { const undoColor = this.color; const redoColor = color; @@ -199,12 +199,12 @@ this.color = undoColor; }, () => { this.color = redoColor; - }, [this.clientID]); + }, [this.clientID], frame); this.color = color; } - _saveHidden(hidden) { + _saveHidden(hidden, frame) { const undoHidden = this.hidden; const redoHidden = hidden; @@ -212,12 +212,12 @@ this.hidden = undoHidden; }, () => { this.hidden = redoHidden; - }, [this.clientID]); + }, [this.clientID], frame); this.hidden = hidden; } - _saveLabel(label) { + _saveLabel(label, frame) { const undoLabel = this.label; const redoLabel = label; const undoAttributes = { ...this.attributes }; @@ -232,10 +232,10 @@ }, () => { this.label = redoLabel; this.attributes = redoAttributes; - }, [this.clientID]); + }, [this.clientID], frame); } - _saveAttributes(attributes) { + _saveAttributes(attributes, frame) { const undoAttributes = { ...this.attributes }; for (const attrID of Object.keys(attributes)) { @@ -248,7 +248,7 @@ this.attributes = undoAttributes; }, () => { this.attributes = redoAttributes; - }, [this.clientID]); + }, [this.clientID], frame); } _validateStateBeforeSave(frame, data, updated) { @@ -368,7 +368,7 @@ } } - delete(force) { + delete(frame, force) { if (!this.lock || force) { this.removed = true; @@ -376,7 +376,7 @@ this.removed = false; }, () => { this.removed = true; - }, [this.clientID]); + }, [this.clientID], frame); } return this.removed; @@ -392,7 +392,7 @@ this.shapeType = null; } - _savePinned(pinned) { + _savePinned(pinned, frame) { const undoPinned = this.pinned; const redoPinned = pinned; @@ -400,7 +400,7 @@ this.pinned = undoPinned; }, () => { this.pinned = redoPinned; - }, [this.clientID]); + }, [this.clientID], frame); this.pinned = pinned; } @@ -483,7 +483,7 @@ }; } - _savePoints(points) { + _savePoints(points, frame) { const undoPoints = this.points; const redoPoints = points; @@ -491,12 +491,12 @@ this.points = undoPoints; }, () => { this.points = redoPoints; - }, [this.clientID]); + }, [this.clientID], frame); this.points = points; } - _saveOccluded(occluded) { + _saveOccluded(occluded, frame) { const undoOccluded = this.occluded; const redoOccluded = occluded; @@ -504,12 +504,12 @@ this.occluded = undoOccluded; }, () => { this.occluded = redoOccluded; - }, [this.clientID]); + }, [this.clientID], frame); this.occluded = occluded; } - _saveZOrder(zOrder) { + _saveZOrder(zOrder, frame) { const undoZOrder = this.zOrder; const redoZOrder = zOrder; @@ -517,7 +517,7 @@ this.zOrder = undoZOrder; }, () => { this.zOrder = redoZOrder; - }, [this.clientID]); + }, [this.clientID], frame); this.zOrder = zOrder; } @@ -538,39 +538,39 @@ // Now when all fields are validated, we can apply them if (updated.label) { - this._saveLabel(data.label); + this._saveLabel(data.label, frame); } if (updated.attributes) { - this._saveAttributes(data.attributes); + this._saveAttributes(data.attributes, frame); } if (updated.points && fittedPoints.length) { - this._savePoints(fittedPoints); + this._savePoints(fittedPoints, frame); } if (updated.occluded) { - this._saveOccluded(data.occluded); + this._saveOccluded(data.occluded, frame); } if (updated.zOrder) { - this._saveZOrder(data.zOrder); + this._saveZOrder(data.zOrder, frame); } if (updated.lock) { - this._saveLock(data.lock); + this._saveLock(data.lock, frame); } if (updated.pinned) { - this._savePinned(data.pinned); + this._savePinned(data.pinned, frame); } if (updated.color) { - this._saveColor(data.color); + this._saveColor(data.color, frame); } if (updated.hidden) { - this._saveHidden(data.hidden); + this._saveHidden(data.hidden, frame); } this.updateTimestamp(updated); @@ -745,7 +745,7 @@ return result; } - _saveLabel(label) { + _saveLabel(label, frame) { const undoLabel = this.label; const redoLabel = label; const undoAttributes = { @@ -783,10 +783,10 @@ for (const mutable of redoAttributes.mutable) { this.shapes[mutable.frame].attributes = mutable.attributes; } - }, [this.clientID]); + }, [this.clientID], frame); } - _saveAttributes(frame, attributes) { + _saveAttributes(attributes, frame) { const current = this.get(frame); const labelAttributes = this.label.attributes .reduce((accumulator, value) => { @@ -858,7 +858,7 @@ if (redoShape) { this.shapes[frame] = redoShape; } - }, [this.clientID]); + }, [this.clientID], frame); } _appendShapeActionToHistory(actionType, frame, undoShape, redoShape) { @@ -874,10 +874,10 @@ } else { this.shapes[frame] = redoShape; } - }, [this.clientID]); + }, [this.clientID], frame); } - _savePoints(frame, points) { + _savePoints(points, frame) { const current = this.get(frame); const wasKeyframe = frame in this.shapes; const undoShape = wasKeyframe ? this.shapes[frame] : undefined; @@ -921,7 +921,7 @@ ); } - _saveOccluded(frame, occluded) { + _saveOccluded(occluded, frame) { const current = this.get(frame); const wasKeyframe = frame in this.shapes; const undoShape = wasKeyframe ? this.shapes[frame] : undefined; @@ -943,7 +943,7 @@ ); } - _saveZOrder(frame, zOrder) { + _saveZOrder(zOrder, frame) { const current = this.get(frame); const wasKeyframe = frame in this.shapes; const undoShape = wasKeyframe ? this.shapes[frame] : undefined; @@ -1007,27 +1007,27 @@ const fittedPoints = this._validateStateBeforeSave(frame, data, updated); if (updated.label) { - this._saveLabel(data.label); + this._saveLabel(data.label, frame); } if (updated.lock) { - this._saveLock(data.lock); + this._saveLock(data.lock, frame); } if (updated.pinned) { - this._savePinned(data.pinned); + this._savePinned(data.pinned, frame); } if (updated.color) { - this._saveColor(data.color); + this._saveColor(data.color, frame); } if (updated.hidden) { - this._saveHidden(data.hidden); + this._saveHidden(data.hidden, frame); } if (updated.points && fittedPoints.length) { - this._savePoints(frame, fittedPoints); + this._savePoints(fittedPoints, frame); } if (updated.outside) { @@ -1035,15 +1035,15 @@ } if (updated.occluded) { - this._saveOccluded(frame, data.occluded); + this._saveOccluded(data.occluded, frame); } if (updated.zOrder) { - this._saveZOrder(frame, data.zOrder); + this._saveZOrder(data.zOrder, frame); } if (updated.attributes) { - this._saveAttributes(frame, data.attributes); + this._saveAttributes(data.attributes, frame); } if (updated.keyframe) { @@ -1161,19 +1161,19 @@ // Now when all fields are validated, we can apply them if (updated.label) { - this._saveLabel(data.label); + this._saveLabel(data.label, frame); } if (updated.attributes) { - this._saveAttributes(data.attributes); + this._saveAttributes(data.attributes, frame); } if (updated.lock) { - this._saveLock(data.lock); + this._saveLock(data.lock, frame); } if (updated.color) { - this._saveColor(data.color); + this._saveColor(data.color, frame); } this.updateTimestamp(updated); diff --git a/cvat-core/src/object-state.js b/cvat-core/src/object-state.js index e6a42f188afe..ac4e18f6bb5a 100644 --- a/cvat-core/src/object-state.js +++ b/cvat-core/src/object-state.js @@ -398,14 +398,16 @@ * @memberof module:API.cvat.classes.ObjectState * @readonly * @instance + * @param {integer} frame current frame number * @param {boolean} [force=false] delete object even if it is locked * @async * @returns {boolean} true if object has been deleted * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} */ - async delete(force = false) { + async delete(frame, force = false) { const result = await PluginRegistry - .apiWrapper.call(this, ObjectState.prototype.delete, force); + .apiWrapper.call(this, ObjectState.prototype.delete, frame, force); return result; } } @@ -420,9 +422,13 @@ }; // Delete element from a collection which contains it - ObjectState.prototype.delete.implementation = async function (force) { + ObjectState.prototype.delete.implementation = async function (frame, force) { if (this.__internal && this.__internal.delete) { - return this.__internal.delete(force); + if (!Number.isInteger(+frame) || +frame < 0) { + throw ArgumentError('Frame argument must be a non negative integer'); + } + + return this.__internal.delete(frame, force); } return false; diff --git a/cvat-core/src/session.js b/cvat-core/src/session.js index edd5efc8be80..5ad15fe7760c 100644 --- a/cvat-core/src/session.js +++ b/cvat-core/src/session.js @@ -504,6 +504,7 @@ * @returns {HistoryActions} * @throws {module:API.cvat.exceptions.PluginError} * @throws {module:API.cvat.exceptions.ArgumentError} + * @returns {[string, number][]} array of pairs [action name, frame number] * @instance * @async */ diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 7d35d529947b..02300fc98386 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -290,86 +290,6 @@ export function changeAnnotationsFilters(filters: string[]): AnyAction { }; } -export function undoActionAsync(sessionInstance: any, frame: number): -ThunkAction, {}, {}, AnyAction> { - return async (dispatch: ActionCreator): Promise => { - try { - const state = getStore().getState(); - const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); - - // TODO: use affected IDs as an optimization - const [undoName] = state.annotation.annotations.history.undo.slice(-1); - const undoLog = await sessionInstance.logger.log(LogType.undoAction, { - name: undoName, - count: 1, - }, true); - await sessionInstance.actions.undo(); - const history = await sessionInstance.actions.get(); - const states = await sessionInstance.annotations - .get(frame, showAllInterpolationTracks, filters); - const [minZ, maxZ] = computeZRange(states); - await undoLog.close(); - - dispatch({ - type: AnnotationActionTypes.UNDO_ACTION_SUCCESS, - payload: { - history, - states, - minZ, - maxZ, - }, - }); - } catch (error) { - dispatch({ - type: AnnotationActionTypes.UNDO_ACTION_FAILED, - payload: { - error, - }, - }); - } - }; -} - -export function redoActionAsync(sessionInstance: any, frame: number): -ThunkAction, {}, {}, AnyAction> { - return async (dispatch: ActionCreator): Promise => { - try { - const state = getStore().getState(); - const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); - - // TODO: use affected IDs as an optimization - const [redoName] = state.annotation.annotations.history.redo.slice(-1); - const redoLog = await sessionInstance.logger.log(LogType.redoAction, { - name: redoName, - count: 1, - }, true); - await sessionInstance.actions.redo(); - const history = await sessionInstance.actions.get(); - const states = await sessionInstance.annotations - .get(frame, showAllInterpolationTracks, filters); - const [minZ, maxZ] = computeZRange(states); - await redoLog.close(); - - dispatch({ - type: AnnotationActionTypes.REDO_ACTION_SUCCESS, - payload: { - history, - states, - minZ, - maxZ, - }, - }); - } catch (error) { - dispatch({ - type: AnnotationActionTypes.REDO_ACTION_FAILED, - payload: { - error, - }, - }); - } - }; -} - export function updateCanvasContextMenu( visible: boolean, left: number, @@ -625,7 +545,9 @@ ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { try { await sessionInstance.logger.log(LogType.deleteObject, { count: 1 }); - const removed = await objectState.delete(force); + const { frame } = receiveAnnotationsParameters(); + + const removed = await objectState.delete(frame, force); const history = await sessionInstance.actions.get(); if (removed) { @@ -817,6 +739,90 @@ ThunkAction, {}, {}, AnyAction> { }; } +export function undoActionAsync(sessionInstance: any, frame: number): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + try { + const state = getStore().getState(); + const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); + + // TODO: use affected IDs as an optimization + const [undo] = state.annotation.annotations.history.undo.slice(-1); + const undoLog = await sessionInstance.logger.log(LogType.undoAction, { + name: undo[0], + frame: undo[1], + count: 1, + }, true); + + dispatch(changeFrameAsync(undo[1])); + await sessionInstance.actions.undo(); + const history = await sessionInstance.actions.get(); + const states = await sessionInstance.annotations + .get(frame, showAllInterpolationTracks, filters); + const [minZ, maxZ] = computeZRange(states); + await undoLog.close(); + + dispatch({ + type: AnnotationActionTypes.UNDO_ACTION_SUCCESS, + payload: { + history, + states, + minZ, + maxZ, + }, + }); + } catch (error) { + dispatch({ + type: AnnotationActionTypes.UNDO_ACTION_FAILED, + payload: { + error, + }, + }); + } + }; +} + +export function redoActionAsync(sessionInstance: any, frame: number): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + try { + const state = getStore().getState(); + const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); + + // TODO: use affected IDs as an optimization + const [redo] = state.annotation.annotations.history.redo.slice(-1); + const redoLog = await sessionInstance.logger.log(LogType.redoAction, { + name: redo[0], + frame: redo[1], + count: 1, + }, true); + dispatch(changeFrameAsync(redo[1])); + await sessionInstance.actions.redo(); + const history = await sessionInstance.actions.get(); + const states = await sessionInstance.annotations + .get(frame, showAllInterpolationTracks, filters); + const [minZ, maxZ] = computeZRange(states); + await redoLog.close(); + + dispatch({ + type: AnnotationActionTypes.REDO_ACTION_SUCCESS, + payload: { + history, + states, + minZ, + maxZ, + }, + }); + } catch (error) { + dispatch({ + type: AnnotationActionTypes.REDO_ACTION_FAILED, + payload: { + error, + }, + }); + } + }; +} export function rotateCurrentFrame(rotation: Rotation): AnyAction { const state: CombinedState = getStore().getState(); 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 2c8020c330f5..381db156c6ff 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 @@ -104,8 +104,8 @@ function mapStateToProps(state: CombinedState): StateToProps { savingStatuses, frameNumber, jobInstance, - undoAction: history.undo[history.undo.length - 1], - redoAction: history.redo[history.redo.length - 1], + undoAction: history.undo.length ? history.undo[history.undo.length - 1][0] : undefined, + redoAction: history.redo.length ? history.redo[history.redo.length - 1][0] : undefined, autoSave, autoSaveInterval, workspace, diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index f2ead55794bb..903f43a6d159 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -349,8 +349,8 @@ export interface AnnotationState { filtersHistory: string[]; resetGroupFlag: boolean; history: { - undo: string[]; - redo: string[]; + undo: [string, number][]; + redo: [string, number][]; }; saving: { uploading: boolean; From e478637e8842df7750d63d4a6f7481b23c76f227 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 25 Mar 2020 22:03:36 +0300 Subject: [PATCH 5/5] Fixed unit tests --- cvat-core/src/object-state.js | 2 +- cvat-core/tests/api/annotations.js | 6 +++--- cvat-core/tests/api/object-state.js | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cvat-core/src/object-state.js b/cvat-core/src/object-state.js index ac4e18f6bb5a..93d17d4827a3 100644 --- a/cvat-core/src/object-state.js +++ b/cvat-core/src/object-state.js @@ -425,7 +425,7 @@ ObjectState.prototype.delete.implementation = async function (frame, force) { if (this.__internal && this.__internal.delete) { if (!Number.isInteger(+frame) || +frame < 0) { - throw ArgumentError('Frame argument must be a non negative integer'); + throw new ArgumentError('Frame argument must be a non negative integer'); } return this.__internal.delete(frame, force); diff --git a/cvat-core/tests/api/annotations.js b/cvat-core/tests/api/annotations.js index 57fe808eca94..ae0572cfe38b 100644 --- a/cvat-core/tests/api/annotations.js +++ b/cvat-core/tests/api/annotations.js @@ -367,7 +367,7 @@ describe('Feature: save annotations', () => { const annotations = await task.annotations.get(0); expect(task.annotations.hasUnsavedChanges()).toBe(false); - await annotations[0].delete(); + await annotations[0].delete(0); expect(task.annotations.hasUnsavedChanges()).toBe(true); await task.annotations.save(); expect(task.annotations.hasUnsavedChanges()).toBe(false); @@ -413,7 +413,7 @@ describe('Feature: save annotations', () => { const annotations = await job.annotations.get(0); expect(job.annotations.hasUnsavedChanges()).toBe(false); - await annotations[0].delete(); + await annotations[0].delete(0); expect(job.annotations.hasUnsavedChanges()).toBe(true); await job.annotations.save(); expect(job.annotations.hasUnsavedChanges()).toBe(false); @@ -436,7 +436,7 @@ describe('Feature: save annotations', () => { return result; }; - await annotations[0].delete(); + await annotations[0].delete(0); await job.annotations.save(); serverProxy.annotations.updateAnnotations = oldImplementation; diff --git a/cvat-core/tests/api/object-state.js b/cvat-core/tests/api/object-state.js index 1cf592ac4d43..92a6c8157db6 100644 --- a/cvat-core/tests/api/object-state.js +++ b/cvat-core/tests/api/object-state.js @@ -289,7 +289,7 @@ describe('Feature: delete object', () => { const task = (await window.cvat.tasks.get({ id: 100 }))[0]; const annotationsBefore = await task.annotations.get(0); const { length } = annotationsBefore; - await annotationsBefore[0].delete(); + await annotationsBefore[0].delete(0); const annotationsAfter = await task.annotations.get(0); expect(annotationsAfter).toHaveLength(length - 1); }); @@ -298,7 +298,7 @@ describe('Feature: delete object', () => { const task = (await window.cvat.tasks.get({ id: 101 }))[0]; const annotationsBefore = await task.annotations.get(0); const { length } = annotationsBefore; - await annotationsBefore[0].delete(); + await annotationsBefore[0].delete(0); const annotationsAfter = await task.annotations.get(0); expect(annotationsAfter).toHaveLength(length - 1); });