|
|
|
|
@ -1,5 +1,5 @@
|
|
|
|
|
/*
|
|
|
|
|
* Copyright (C) 2019 Intel Corporation
|
|
|
|
|
* Copyright (C) 2019-2020 Intel Corporation
|
|
|
|
|
* SPDX-License-Identifier: MIT
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
@ -17,6 +17,7 @@
|
|
|
|
|
ObjectShape,
|
|
|
|
|
ObjectType,
|
|
|
|
|
AttributeType,
|
|
|
|
|
HistoryActions,
|
|
|
|
|
} = 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;
|
|
|
|
|
@ -156,6 +158,79 @@
|
|
|
|
|
injection.groups.max = Math.max(injection.groups.max, this.group);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_saveLock(lock) {
|
|
|
|
|
const undoLock = this.lock;
|
|
|
|
|
const redoLock = lock;
|
|
|
|
|
|
|
|
|
|
this.history.do(HistoryActions.CHANGED_LOCK, () => {
|
|
|
|
|
this.lock = undoLock;
|
|
|
|
|
}, () => {
|
|
|
|
|
this.lock = redoLock;
|
|
|
|
|
}, [this.clientID]);
|
|
|
|
|
|
|
|
|
|
this.lock = lock;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_saveColor(color) {
|
|
|
|
|
const undoColor = this.color;
|
|
|
|
|
const redoColor = color;
|
|
|
|
|
|
|
|
|
|
this.history.do(HistoryActions.CHANGED_COLOR, () => {
|
|
|
|
|
this.color = undoColor;
|
|
|
|
|
}, () => {
|
|
|
|
|
this.color = redoColor;
|
|
|
|
|
}, [this.clientID]);
|
|
|
|
|
|
|
|
|
|
this.color = color;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_saveHidden(hidden) {
|
|
|
|
|
const undoHidden = this.hidden;
|
|
|
|
|
const redoHidden = hidden;
|
|
|
|
|
|
|
|
|
|
this.history.do(HistoryActions.CHANGED_HIDDEN, () => {
|
|
|
|
|
this.hidden = undoHidden;
|
|
|
|
|
}, () => {
|
|
|
|
|
this.hidden = redoHidden;
|
|
|
|
|
}, [this.clientID]);
|
|
|
|
|
|
|
|
|
|
this.hidden = hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_saveLabel(label) {
|
|
|
|
|
const undoLabel = this.label;
|
|
|
|
|
const redoLabel = label;
|
|
|
|
|
const undoAttributes = { ...this.attributes };
|
|
|
|
|
this.label = label;
|
|
|
|
|
this.attributes = {};
|
|
|
|
|
this.appendDefaultAttributes(label);
|
|
|
|
|
const redoAttributes = { ...this.attributes };
|
|
|
|
|
|
|
|
|
|
this.history.do(HistoryActions.CHANGED_LABEL, () => {
|
|
|
|
|
this.label = undoLabel;
|
|
|
|
|
this.attributes = undoAttributes;
|
|
|
|
|
}, () => {
|
|
|
|
|
this.label = redoLabel;
|
|
|
|
|
this.attributes = redoAttributes;
|
|
|
|
|
}, [this.clientID]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_saveAttributes(attributes) {
|
|
|
|
|
const undoAttributes = { ...this.attributes };
|
|
|
|
|
|
|
|
|
|
for (const attrID of Object.keys(attributes)) {
|
|
|
|
|
this.attributes[attrID] = attributes[attrID];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const redoAttributes = { ...this.attributes };
|
|
|
|
|
|
|
|
|
|
this.history.do(HistoryActions.CHANGED_ATTRIBUTES, () => {
|
|
|
|
|
this.attributes = undoAttributes;
|
|
|
|
|
}, () => {
|
|
|
|
|
this.attributes = redoAttributes;
|
|
|
|
|
}, [this.clientID]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
appendDefaultAttributes(label) {
|
|
|
|
|
const labelAttributes = label.attributes;
|
|
|
|
|
for (const attribute of labelAttributes) {
|
|
|
|
|
@ -178,9 +253,15 @@
|
|
|
|
|
delete(force) {
|
|
|
|
|
if (!this.lock || force) {
|
|
|
|
|
this.removed = true;
|
|
|
|
|
|
|
|
|
|
this.history.do(HistoryActions.REMOVED_OBJECT, () => {
|
|
|
|
|
this.removed = false;
|
|
|
|
|
}, () => {
|
|
|
|
|
this.removed = true;
|
|
|
|
|
}, [this.clientID]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
return this.removed;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -205,9 +286,8 @@
|
|
|
|
|
return this.collectionZ[frame];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
validateStateBeforeSave(frame, data) {
|
|
|
|
|
_validateStateBeforeSave(frame, data, updated) {
|
|
|
|
|
let fittedPoints = [];
|
|
|
|
|
const updated = data.updateFlags;
|
|
|
|
|
|
|
|
|
|
if (updated.label) {
|
|
|
|
|
checkObjectType('label', data.label, null, Label);
|
|
|
|
|
@ -255,7 +335,7 @@
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!checkShapeArea(this.shapeType, fittedPoints)) {
|
|
|
|
|
fittedPoints = false;
|
|
|
|
|
fittedPoints = [];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -393,6 +473,45 @@
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_savePoints(points) {
|
|
|
|
|
const undoPoints = this.points;
|
|
|
|
|
const redoPoints = points;
|
|
|
|
|
|
|
|
|
|
this.history.do(HistoryActions.CHANGED_POINTS, () => {
|
|
|
|
|
this.points = undoPoints;
|
|
|
|
|
}, () => {
|
|
|
|
|
this.points = redoPoints;
|
|
|
|
|
}, [this.clientID]);
|
|
|
|
|
|
|
|
|
|
this.points = points;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_saveOccluded(occluded) {
|
|
|
|
|
const undoOccluded = this.occluded;
|
|
|
|
|
const redoOccluded = occluded;
|
|
|
|
|
|
|
|
|
|
this.history.do(HistoryActions.CHANGED_OCCLUDED, () => {
|
|
|
|
|
this.occluded = undoOccluded;
|
|
|
|
|
}, () => {
|
|
|
|
|
this.occluded = redoOccluded;
|
|
|
|
|
}, [this.clientID]);
|
|
|
|
|
|
|
|
|
|
this.occluded = occluded;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_saveZOrder(zOrder) {
|
|
|
|
|
const undoZOrder = this.zOrder;
|
|
|
|
|
const redoZOrder = zOrder;
|
|
|
|
|
|
|
|
|
|
this.history.do(HistoryActions.CHANGED_ZORDER, () => {
|
|
|
|
|
this.zOrder = undoZOrder;
|
|
|
|
|
}, () => {
|
|
|
|
|
this.zOrder = redoZOrder;
|
|
|
|
|
}, [this.clientID]);
|
|
|
|
|
|
|
|
|
|
this.zOrder = zOrder;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
save(frame, data) {
|
|
|
|
|
if (frame !== this.frame) {
|
|
|
|
|
throw new ScriptingError(
|
|
|
|
|
@ -404,44 +523,40 @@
|
|
|
|
|
return objectStateFactory.call(this, frame, this.get(frame));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fittedPoints = this.validateStateBeforeSave(frame, data);
|
|
|
|
|
const updated = data.updateFlags;
|
|
|
|
|
const fittedPoints = this._validateStateBeforeSave(frame, data, updated);
|
|
|
|
|
|
|
|
|
|
// Now when all fields are validated, we can apply them
|
|
|
|
|
if (updated.label) {
|
|
|
|
|
this.label = data.label;
|
|
|
|
|
this.attributes = {};
|
|
|
|
|
this.appendDefaultAttributes(data.label);
|
|
|
|
|
this._saveLabel(data.label);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (updated.attributes) {
|
|
|
|
|
for (const attrID of Object.keys(data.attributes)) {
|
|
|
|
|
this.attributes[attrID] = data.attributes[attrID];
|
|
|
|
|
}
|
|
|
|
|
this._saveAttributes(data.attributes);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (updated.points && fittedPoints.length) {
|
|
|
|
|
this.points = [...fittedPoints];
|
|
|
|
|
this._savePoints(fittedPoints);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (updated.occluded) {
|
|
|
|
|
this.occluded = data.occluded;
|
|
|
|
|
this._saveOccluded(data.occluded);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (updated.zOrder) {
|
|
|
|
|
this.zOrder = data.zOrder;
|
|
|
|
|
this._saveZOrder(data.zOrder);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (updated.lock) {
|
|
|
|
|
this.lock = data.lock;
|
|
|
|
|
this._saveLock(data.lock);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (updated.color) {
|
|
|
|
|
this.color = data.color;
|
|
|
|
|
this._saveColor(data.color);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (updated.hidden) {
|
|
|
|
|
this.hidden = data.hidden;
|
|
|
|
|
this._saveHidden(data.hidden);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.updateTimestamp(updated);
|
|
|
|
|
@ -622,87 +737,304 @@
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
save(frame, data) {
|
|
|
|
|
if (this.lock && data.lock) {
|
|
|
|
|
return objectStateFactory.call(this, frame, this.get(frame));
|
|
|
|
|
_saveLabel(label) {
|
|
|
|
|
const undoLabel = this.label;
|
|
|
|
|
const redoLabel = label;
|
|
|
|
|
const undoAttributes = {
|
|
|
|
|
unmutable: { ...this.attributes },
|
|
|
|
|
mutable: Object.keys(this.shapes).map((key) => ({
|
|
|
|
|
frame: +key,
|
|
|
|
|
attributes: { ...this.shapes[key].attributes },
|
|
|
|
|
})),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.label = label;
|
|
|
|
|
this.attributes = {};
|
|
|
|
|
for (const shape of Object.values(this.shapes)) {
|
|
|
|
|
shape.attributes = {};
|
|
|
|
|
}
|
|
|
|
|
this.appendDefaultAttributes(label);
|
|
|
|
|
|
|
|
|
|
const fittedPoints = this.validateStateBeforeSave(frame, data);
|
|
|
|
|
const updated = data.updateFlags;
|
|
|
|
|
const redoAttributes = {
|
|
|
|
|
unmutable: { ...this.attributes },
|
|
|
|
|
mutable: Object.keys(this.shapes).map((key) => ({
|
|
|
|
|
frame: +key,
|
|
|
|
|
attributes: { ...this.shapes[key].attributes },
|
|
|
|
|
})),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.history.do(HistoryActions.CHANGED_LABEL, () => {
|
|
|
|
|
this.label = undoLabel;
|
|
|
|
|
this.attributes = undoAttributes.unmutable;
|
|
|
|
|
for (const mutable of undoAttributes.mutable) {
|
|
|
|
|
this.shapes[mutable.frame].attributes = mutable.attributes;
|
|
|
|
|
}
|
|
|
|
|
}, () => {
|
|
|
|
|
this.label = redoLabel;
|
|
|
|
|
this.attributes = redoAttributes.unmutable;
|
|
|
|
|
for (const mutable of redoAttributes.mutable) {
|
|
|
|
|
this.shapes[mutable.frame].attributes = mutable.attributes;
|
|
|
|
|
}
|
|
|
|
|
}, [this.clientID]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_saveAttributes(frame, attributes) {
|
|
|
|
|
const current = this.get(frame);
|
|
|
|
|
const labelAttributes = data.label.attributes
|
|
|
|
|
const labelAttributes = this.label.attributes
|
|
|
|
|
.reduce((accumulator, value) => {
|
|
|
|
|
accumulator[value.id] = value;
|
|
|
|
|
return accumulator;
|
|
|
|
|
}, {});
|
|
|
|
|
|
|
|
|
|
if (updated.label) {
|
|
|
|
|
this.label = data.label;
|
|
|
|
|
this.attributes = {};
|
|
|
|
|
for (const shape of Object.values(this.shapes)) {
|
|
|
|
|
shape.attributes = {};
|
|
|
|
|
const wasKeyframe = frame in this.shapes;
|
|
|
|
|
const undoAttributes = this.attributes;
|
|
|
|
|
const undoShape = wasKeyframe ? this.shapes[frame] : undefined;
|
|
|
|
|
|
|
|
|
|
let mutableAttributesUpdated = false;
|
|
|
|
|
const redoAttributes = { ...this.attributes };
|
|
|
|
|
for (const attrID of Object.keys(attributes)) {
|
|
|
|
|
if (!labelAttributes[attrID].mutable) {
|
|
|
|
|
redoAttributes[attrID] = attributes[attrID];
|
|
|
|
|
} else if (attributes[attrID] !== current.attributes[attrID]) {
|
|
|
|
|
mutableAttributesUpdated = mutableAttributesUpdated
|
|
|
|
|
// not keyframe yet
|
|
|
|
|
|| !(frame in this.shapes)
|
|
|
|
|
// keyframe, but without this attrID
|
|
|
|
|
|| !(attrID in this.shapes[frame].attributes)
|
|
|
|
|
// keyframe with attrID, but with another value
|
|
|
|
|
|| (this.shapes[frame].attributes[attrID] !== attributes[attrID]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let redoShape;
|
|
|
|
|
if (mutableAttributesUpdated) {
|
|
|
|
|
if (wasKeyframe) {
|
|
|
|
|
redoShape = {
|
|
|
|
|
...this.shapes[frame],
|
|
|
|
|
attributes: {
|
|
|
|
|
...this.shapes[frame].attributes,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
} else {
|
|
|
|
|
redoShape = {
|
|
|
|
|
frame,
|
|
|
|
|
zOrder: current.zOrder,
|
|
|
|
|
points: current.points,
|
|
|
|
|
outside: current.outside,
|
|
|
|
|
occluded: current.occluded,
|
|
|
|
|
attributes: {},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
this.appendDefaultAttributes(data.label);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mutableAttributesUpdated = false;
|
|
|
|
|
if (updated.attributes) {
|
|
|
|
|
for (const attrID of Object.keys(data.attributes)) {
|
|
|
|
|
if (!labelAttributes[attrID].mutable) {
|
|
|
|
|
this.attributes[attrID] = data.attributes[attrID];
|
|
|
|
|
this.attributes[attrID] = data.attributes[attrID];
|
|
|
|
|
} else if (data.attributes[attrID] !== current.attributes[attrID]) {
|
|
|
|
|
mutableAttributesUpdated = mutableAttributesUpdated
|
|
|
|
|
// not keyframe yet
|
|
|
|
|
|| !(frame in this.shapes)
|
|
|
|
|
// keyframe, but without this attrID
|
|
|
|
|
|| !(attrID in this.shapes[frame])
|
|
|
|
|
// keyframe with attrID, but with another value
|
|
|
|
|
|| (this.shapes[frame][attrID] !== data.attributes[attrID]);
|
|
|
|
|
}
|
|
|
|
|
for (const attrID of Object.keys(attributes)) {
|
|
|
|
|
if (labelAttributes[attrID].mutable
|
|
|
|
|
&& attributes[attrID] !== current.attributes[attrID]) {
|
|
|
|
|
redoShape.attributes[attrID] = attributes[attrID];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.attributes = redoAttributes;
|
|
|
|
|
if (redoShape) {
|
|
|
|
|
this.shapes[frame] = redoShape;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.history.do(HistoryActions.CHANGED_ATTRIBUTES, () => {
|
|
|
|
|
this.attributes = undoAttributes;
|
|
|
|
|
if (undoShape) {
|
|
|
|
|
this.shapes[frame] = undoShape;
|
|
|
|
|
} else if (redoShape) {
|
|
|
|
|
delete this.shapes[frame];
|
|
|
|
|
}
|
|
|
|
|
}, () => {
|
|
|
|
|
this.attributes = redoAttributes;
|
|
|
|
|
if (redoShape) {
|
|
|
|
|
this.shapes[frame] = redoShape;
|
|
|
|
|
}
|
|
|
|
|
}, [this.clientID]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_appendShapeActionToHistory(actionType, frame, undoShape, redoShape) {
|
|
|
|
|
this.history.do(actionType, () => {
|
|
|
|
|
if (!undoShape) {
|
|
|
|
|
delete this.shapes[frame];
|
|
|
|
|
} else {
|
|
|
|
|
this.shapes[frame] = undoShape;
|
|
|
|
|
}
|
|
|
|
|
}, () => {
|
|
|
|
|
if (!redoShape) {
|
|
|
|
|
delete this.shapes[frame];
|
|
|
|
|
} else {
|
|
|
|
|
this.shapes[frame] = redoShape;
|
|
|
|
|
}
|
|
|
|
|
}, [this.clientID]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_savePoints(frame, points) {
|
|
|
|
|
const current = this.get(frame);
|
|
|
|
|
const wasKeyframe = frame in this.shapes;
|
|
|
|
|
const undoShape = wasKeyframe ? this.shapes[frame] : undefined;
|
|
|
|
|
const redoShape = wasKeyframe ? { ...this.shapes[frame], points } : {
|
|
|
|
|
frame,
|
|
|
|
|
points,
|
|
|
|
|
zOrder: current.zOrder,
|
|
|
|
|
outside: current.outside,
|
|
|
|
|
occluded: current.occluded,
|
|
|
|
|
attributes: {},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.shapes[frame] = redoShape;
|
|
|
|
|
this._appendShapeActionToHistory(
|
|
|
|
|
HistoryActions.CHANGED_POINTS,
|
|
|
|
|
frame,
|
|
|
|
|
undoShape,
|
|
|
|
|
redoShape,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_saveOutside(frame, outside) {
|
|
|
|
|
const current = this.get(frame);
|
|
|
|
|
const wasKeyframe = frame in this.shapes;
|
|
|
|
|
const undoShape = wasKeyframe ? this.shapes[frame] : undefined;
|
|
|
|
|
const redoShape = wasKeyframe ? { ...this.shapes[frame], outside } : {
|
|
|
|
|
frame,
|
|
|
|
|
outside,
|
|
|
|
|
zOrder: current.zOrder,
|
|
|
|
|
points: current.points,
|
|
|
|
|
occluded: current.occluded,
|
|
|
|
|
attributes: {},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.shapes[frame] = redoShape;
|
|
|
|
|
this._appendShapeActionToHistory(
|
|
|
|
|
HistoryActions.CHANGED_OUTSIDE,
|
|
|
|
|
frame,
|
|
|
|
|
undoShape,
|
|
|
|
|
redoShape,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_saveOccluded(frame, occluded) {
|
|
|
|
|
const current = this.get(frame);
|
|
|
|
|
const wasKeyframe = frame in this.shapes;
|
|
|
|
|
const undoShape = wasKeyframe ? this.shapes[frame] : undefined;
|
|
|
|
|
const redoShape = wasKeyframe ? { ...this.shapes[frame], occluded } : {
|
|
|
|
|
frame,
|
|
|
|
|
occluded,
|
|
|
|
|
zOrder: current.zOrder,
|
|
|
|
|
points: current.points,
|
|
|
|
|
outside: current.outside,
|
|
|
|
|
attributes: {},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.shapes[frame] = redoShape;
|
|
|
|
|
this._appendShapeActionToHistory(
|
|
|
|
|
HistoryActions.CHANGED_OCCLUDED,
|
|
|
|
|
frame,
|
|
|
|
|
undoShape,
|
|
|
|
|
redoShape,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_saveZOrder(frame, zOrder) {
|
|
|
|
|
const current = this.get(frame);
|
|
|
|
|
const wasKeyframe = frame in this.shapes;
|
|
|
|
|
const undoShape = wasKeyframe ? this.shapes[frame] : undefined;
|
|
|
|
|
const redoShape = wasKeyframe ? { ...this.shapes[frame], zOrder } : {
|
|
|
|
|
frame,
|
|
|
|
|
zOrder,
|
|
|
|
|
occluded: current.occluded,
|
|
|
|
|
points: current.points,
|
|
|
|
|
outside: current.outside,
|
|
|
|
|
attributes: {},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.shapes[frame] = redoShape;
|
|
|
|
|
this._appendShapeActionToHistory(
|
|
|
|
|
HistoryActions.CHANGED_ZORDER,
|
|
|
|
|
frame,
|
|
|
|
|
undoShape,
|
|
|
|
|
redoShape,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_saveKeyframe(frame, keyframe) {
|
|
|
|
|
const current = this.get(frame);
|
|
|
|
|
const wasKeyframe = frame in this.shapes;
|
|
|
|
|
|
|
|
|
|
if ((keyframe && wasKeyframe) || (!keyframe && !wasKeyframe)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const undoShape = wasKeyframe ? this.shapes[frame] : undefined;
|
|
|
|
|
const redoShape = keyframe ? {
|
|
|
|
|
frame,
|
|
|
|
|
zOrder: current.zOrder,
|
|
|
|
|
points: current.points,
|
|
|
|
|
outside: current.outside,
|
|
|
|
|
occluded: current.occluded,
|
|
|
|
|
attributes: {},
|
|
|
|
|
} : undefined;
|
|
|
|
|
|
|
|
|
|
if (redoShape) {
|
|
|
|
|
this.shapes[frame] = redoShape;
|
|
|
|
|
} else {
|
|
|
|
|
delete this.shapes[frame];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this._appendShapeActionToHistory(
|
|
|
|
|
HistoryActions.CHANGED_KEYFRAME,
|
|
|
|
|
frame,
|
|
|
|
|
undoShape,
|
|
|
|
|
redoShape,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
save(frame, data) {
|
|
|
|
|
if (this.lock && data.lock) {
|
|
|
|
|
return objectStateFactory.call(this, frame, this.get(frame));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const updated = data.updateFlags;
|
|
|
|
|
const fittedPoints = this._validateStateBeforeSave(frame, data, updated);
|
|
|
|
|
|
|
|
|
|
if (updated.label) {
|
|
|
|
|
this._saveLabel(data.label);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (updated.lock) {
|
|
|
|
|
this.lock = data.lock;
|
|
|
|
|
this._saveLock(data.lock);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (updated.color) {
|
|
|
|
|
this.color = data.color;
|
|
|
|
|
this._saveColor(data.color);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (updated.hidden) {
|
|
|
|
|
this.hidden = data.hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (updated.points || updated.keyframe || updated.outside
|
|
|
|
|
|| updated.occluded || updated.zOrder || mutableAttributesUpdated) {
|
|
|
|
|
const mutableAttributes = frame in this.shapes ? this.shapes[frame].attributes : {};
|
|
|
|
|
this.shapes[frame] = {
|
|
|
|
|
frame,
|
|
|
|
|
zOrder: data.zOrder,
|
|
|
|
|
points: updated.points && fittedPoints.length ? fittedPoints : current.points,
|
|
|
|
|
outside: data.outside,
|
|
|
|
|
occluded: data.occluded,
|
|
|
|
|
attributes: mutableAttributes,
|
|
|
|
|
};
|
|
|
|
|
this._saveHidden(data.hidden);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const attrID of Object.keys(data.attributes)) {
|
|
|
|
|
if (labelAttributes[attrID].mutable
|
|
|
|
|
&& data.attributes[attrID] !== current.attributes[attrID]) {
|
|
|
|
|
this.shapes[frame].attributes[attrID] = data.attributes[attrID];
|
|
|
|
|
this.shapes[frame].attributes[attrID] = data.attributes[attrID];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (updated.points && fittedPoints.length) {
|
|
|
|
|
this._savePoints(frame, fittedPoints);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (updated.keyframe && !data.keyframe) {
|
|
|
|
|
if (Object.keys(this.shapes).length === 1) {
|
|
|
|
|
throw new DataError('You are not able to remove the latest keyframe for a track. '
|
|
|
|
|
+ 'Consider removing a track instead');
|
|
|
|
|
} else {
|
|
|
|
|
delete this.shapes[frame];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (updated.outside) {
|
|
|
|
|
this._saveOutside(frame, data.outside);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (updated.occluded) {
|
|
|
|
|
this._saveOccluded(frame, data.occluded);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (updated.zOrder) {
|
|
|
|
|
this._saveZOrder(frame, data.zOrder);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (updated.attributes) {
|
|
|
|
|
this._saveAttributes(frame, data.attributes);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (updated.keyframe) {
|
|
|
|
|
this._saveKeyframe(frame, data.keyframe);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.updateTimestamp(updated);
|
|
|
|
|
@ -752,14 +1084,6 @@
|
|
|
|
|
+ `Interpolation impossible. Client ID: ${this.id}`,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
delete(force) {
|
|
|
|
|
if (!this.lock || force) {
|
|
|
|
|
this.removed = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return this.removed;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class Tag extends Annotation {
|
|
|
|
|
@ -810,7 +1134,7 @@
|
|
|
|
|
save(frame, data) {
|
|
|
|
|
if (frame !== this.frame) {
|
|
|
|
|
throw new ScriptingError(
|
|
|
|
|
'Got frame is not equal to the frame of the shape',
|
|
|
|
|
'Got frame is not equal to the frame of the tag',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -818,59 +1142,20 @@
|
|
|
|
|
return objectStateFactory.call(this, frame, this.get(frame));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.lock && data.lock) {
|
|
|
|
|
return objectStateFactory.call(this, frame, this.get(frame));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const updated = data.updateFlags;
|
|
|
|
|
|
|
|
|
|
// First validate all the fields
|
|
|
|
|
if (updated.label) {
|
|
|
|
|
checkObjectType('label', data.label, null, Label);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (updated.attributes) {
|
|
|
|
|
const labelAttributes = data.label.attributes
|
|
|
|
|
.reduce((accumulator, value) => {
|
|
|
|
|
accumulator[value.id] = value;
|
|
|
|
|
return accumulator;
|
|
|
|
|
}, {});
|
|
|
|
|
|
|
|
|
|
for (const attrID of Object.keys(data.attributes)) {
|
|
|
|
|
const value = data.attributes[attrID];
|
|
|
|
|
if (attrID in labelAttributes) {
|
|
|
|
|
if (!validateAttributeValue(value, labelAttributes[attrID])) {
|
|
|
|
|
throw new ArgumentError(
|
|
|
|
|
`Trying to save an attribute attribute with id ${attrID} and invalid value ${value}`,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
throw new ArgumentError(
|
|
|
|
|
`Trying to save unknown attribute with id ${attrID} and value ${value}`,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (updated.lock) {
|
|
|
|
|
checkObjectType('lock', data.lock, 'boolean', null);
|
|
|
|
|
}
|
|
|
|
|
this._validateStateBeforeSave(frame, data, updated);
|
|
|
|
|
|
|
|
|
|
// Now when all fields are validated, we can apply them
|
|
|
|
|
if (updated.label) {
|
|
|
|
|
this.label = data.label;
|
|
|
|
|
this.attributes = {};
|
|
|
|
|
this.appendDefaultAttributes(data.label);
|
|
|
|
|
this._saveLabel(data.label);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (updated.attributes) {
|
|
|
|
|
for (const attrID of Object.keys(data.attributes)) {
|
|
|
|
|
this.attributes[attrID] = data.attributes[attrID];
|
|
|
|
|
}
|
|
|
|
|
this._saveAttributes(data.attributes);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (updated.lock) {
|
|
|
|
|
this.lock = data.lock;
|
|
|
|
|
this._saveLock(data.lock);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.updateTimestamp(updated);
|
|
|
|
|
|