Merge pull request #1318 from opencv/bs/batch_of_fixes

React UI: Batch of fixes
main
Dmitry Kalinin 6 years ago committed by GitHub
commit 76d0c6b599
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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);

@ -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);

@ -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) {

@ -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);

@ -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);

@ -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 new ArgumentError('Frame argument must be a non negative integer');
}
return this.__internal.delete(frame, force);
}
return false;

@ -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
*/

@ -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;

@ -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);
});

@ -290,86 +290,6 @@ export function changeAnnotationsFilters(filters: string[]): AnyAction {
};
}
export function undoActionAsync(sessionInstance: any, frame: number):
ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
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<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
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<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
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<Promise<void>, {}, {}, AnyAction> {
};
}
export function undoActionAsync(sessionInstance: any, frame: number):
ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
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<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
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();
@ -973,6 +979,8 @@ export function getJobAsync(
export function saveAnnotationsAsync(sessionInstance: any):
ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const { filters, frame, showAllInterpolationTracks } = receiveAnnotationsParameters();
dispatch({
type: AnnotationActionTypes.SAVE_ANNOTATIONS,
payload: {},
@ -992,6 +1000,8 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
});
});
const states = await sessionInstance
.annotations.get(frame, showAllInterpolationTracks, filters);
await saveJobEvent.close();
await sessionInstance.logger.log(
LogType.sendTaskInfo,
@ -1001,7 +1011,9 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
dispatch({
type: AnnotationActionTypes.SAVE_ANNOTATIONS_SUCCESS,
payload: {},
payload: {
states,
},
});
} catch (error) {
dispatch({

@ -5,7 +5,7 @@
export interface Attribute {
id: number;
name: string;
type: string;
input_type: string;
mutable: boolean;
values: string[];
}

@ -76,7 +76,7 @@ class LabelForm extends React.PureComponent<Props, {}> {
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<Props, {}> {
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 (

@ -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],

@ -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}"`);
}
}

@ -255,7 +255,7 @@ class ObjectItemContainer extends React.PureComponent<Props> {
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);
};

@ -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,

@ -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,

@ -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;

Loading…
Cancel
Save