diff --git a/cvatjs/.eslintrc.js b/cvatjs/.eslintrc.js index ff874af1..6989c97e 100644 --- a/cvatjs/.eslintrc.js +++ b/cvatjs/.eslintrc.js @@ -48,5 +48,6 @@ "indent": ["warn", 4], "no-useless-constructor": 0, "func-names": [0], + "valid-typeof": [0], }, }; diff --git a/cvatjs/src/annotations.js b/cvatjs/src/annotations.js index 66c02cc7..0fdf5ee3 100644 --- a/cvatjs/src/annotations.js +++ b/cvatjs/src/annotations.js @@ -11,17 +11,85 @@ const serverProxy = require('./server-proxy'); const ObjectState = require('./object-state'); + function objectStateFactory(frame, data) { + const objectState = new ObjectState(data); + + // Rewrite default implementations of save/delete + objectState.updateInCollection = this.save.bind(this, frame, objectState); + objectState.deleteFromCollection = this.delete.bind(this); + + return objectState; + } + + function checkObjectType(name, value, type, instance) { + if (type) { + if (typeof (value) !== type) { + // specific case for integers which aren't native type in JS + if (type === 'integer' && Number.isInteger(value)) { + return; + } + + if (value !== undefined) { + throw new window.cvat.exceptions.ArgumentError( + `Got ${typeof (value)} value for ${name}. ` + + `Expected ${type}`, + ); + } + + throw new window.cvat.exceptions.ArgumentError( + `Got undefined value for ${name}. ` + + `Expected ${type}`, + ); + } + } else if (instance) { + if (!(value instanceof instance)) { + if (value !== undefined) { + throw new window.cvat.exceptions.ArgumentError( + `Got ${value.constructor.name} value for ${name}. ` + + `Expected instance of ${instance.name}`, + ); + } + + throw new window.cvat.exceptions.ArgumentError( + `Got undefined value for ${name}. ` + + `Expected instance of ${instance.name}`, + ); + } + } + } + class Annotation { constructor(data, clientID, injection) { + this.taskLabels = injection.labels; this.clientID = clientID; this.serverID = data.id; - this.labelID = data.label_id; + this.group = data.group; + this.label = this.taskLabels[data.label_id]; this.frame = data.frame; + this.removed = false; + this.lock = false; this.attributes = data.attributes.reduce((attributeAccumulator, attr) => { attributeAccumulator[attr.spec_id] = attr.value; return attributeAccumulator; }, {}); - this.taskLabels = injection.labels; + this.appendDefaultAttributes(this.label); + } + + appendDefaultAttributes(label) { + const labelAttributes = label.attributes; + for (const attribute of labelAttributes) { + if (!(attribute.id in this.attributes)) { + this.attributes[attribute.id] = attribute.defaultValue; + } + } + } + + delete(force) { + if (!this.lock || force) { + this.removed = true; + } + + return true; } } @@ -31,11 +99,11 @@ this.points = data.points; this.occluded = data.occluded; this.zOrder = data.z_order; - this.group = data.group; this.color = color; this.shape = null; } + // Method is used to export data to the server toJSON() { return { occluded: this.occluded, @@ -51,11 +119,12 @@ }, []), id: this.serverID, frame: this.frame, - label_id: this.labelID, + label_id: this.label.id, group: this.group, }; } + // Method is used to construct ObjectState objects get(frame) { if (frame !== this.frame) { throw new window.cvat.exceptions.ScriptingError( @@ -68,13 +137,99 @@ shape: this.shape, clientID: this.clientID, occluded: this.occluded, + lock: this.lock, zOrder: this.zOrder, points: [...this.points], attributes: Object.assign({}, this.attributes), - label: this.taskLabels[this.labelID], + label: this.label, group: this.group, + color: this.color, }; } + + save(frame, data) { + if (frame !== this.frame) { + throw new window.cvat.exceptions.ScriptingError( + 'Got frame is not equal to the frame of the shape', + ); + } + + if (this.lock && data.lock) { + return objectStateFactory.call(this, frame, this.get(frame)); + } + + // All changes are done in this temporary object + const copy = this.get(frame); + const updated = data.updateFlags; + + if (updated.label) { + checkObjectType('label', data.label, null, window.cvat.classes.Label); + copy.label = data.label; + copy.attributes = {}; + this.appendDefaultAttributes.call(copy, copy.label); + } + + if (updated.attributes) { + const labelAttributes = copy.label + .attributes.map(attr => `${attr.id}`); + + for (const attrID of Object.keys(data.attributes)) { + if (labelAttributes.includes(attrID)) { + copy.attributes[attrID] = data.attributes[attrID]; + } + } + } + + if (updated.points) { + checkObjectType('points', data.points, null, Array); + copy.points = []; + for (const coordinate of data.points) { + checkObjectType('coordinate', coordinate, 'number', null); + copy.points.push(coordinate); + } + } + + if (updated.occluded) { + checkObjectType('occluded', data.occluded, 'boolean', null); + copy.occluded = data.occluded; + } + + if (updated.group) { + checkObjectType('group', data.group, 'integer', null); + copy.group = data.group; + } + + if (updated.zOrder) { + checkObjectType('zOrder', data.zOrder, 'integer', null); + copy.zOrder = data.zOrder; + } + + if (updated.lock) { + checkObjectType('lock', data.lock, 'boolean', null); + copy.lock = data.lock; + } + + if (updated.color) { + checkObjectType('color', data.color, 'string', null); + if (/^#[0-9A-F]{6}$/i.test(data.color)) { + throw new window.cvat.exceptions.ArgumentError( + `Got invalid color value: "${data.color}"`, + ); + } + + copy.color = data.color; + } + + // Reset flags and commit all changes + updated.reset(); + for (const prop of Object.keys(copy)) { + if (prop in this) { + this[prop] = copy[prop]; + } + } + + return objectStateFactory.call(this, frame, this.get(frame)); + } } class Track extends Annotation { @@ -86,7 +241,6 @@ occluded: value.occluded, zOrder: value.z_order, points: value.points, - id: value.id, frame: value.frame, outside: value.outside, attributes: value.attributes.reduce((attributeAccumulator, attr) => { @@ -98,15 +252,17 @@ return shapeAccumulator; }, {}); - this.group = data.group; this.attributes = data.attributes.reduce((attributeAccumulator, attr) => { attributeAccumulator[attr.spec_id] = attr.value; return attributeAccumulator; }, {}); + + this.cache = {}; this.color = color; this.shape = null; } + // Method is used to export data to the server toJSON() { return { occluded: this.occluded, @@ -123,7 +279,7 @@ id: this.serverID, frame: this.frame, - label_id: this.labelID, + label_id: this.label.id, group: this.group, shapes: Object.keys(this.shapes).reduce((shapesAccumulator, frame) => { shapesAccumulator.push({ @@ -150,18 +306,27 @@ }; } - get(targetFrame) { - return Object.assign( - {}, this.getPosition(targetFrame), - { - attributes: this.getAttributes(targetFrame), - label: this.taskLabels[this.labelID], - group: this.group, - type: window.cvat.enums.ObjectType.TRACK, - shape: this.shape, - clientID: this.clientID, - }, - ); + // Method is used to construct ObjectState objects + get(frame) { + if (!(frame in this.cache)) { + const interpolation = Object.assign( + {}, this.getPosition(frame), + { + attributes: this.getAttributes(frame), + label: this.label, + group: this.group, + type: window.cvat.enums.ObjectType.TRACK, + shape: this.shape, + clientID: this.clientID, + lock: this.lock, + color: this.color, + }, + ); + + this.cache[frame] = interpolation; + } + + return JSON.parse(JSON.stringify(this.cache[frame])); } neighborsFrames(targetFrame) { @@ -211,20 +376,173 @@ } } - // Finally fill up remained attributes if they exist - const labelAttributes = this.taskLabels[this.labelID].attributes; - const defValuesByID = labelAttributes.reduce((accumulator, attr) => { - accumulator[attr.id] = attr.defaultValue; - return accumulator; - }, {}); + return result; + } + + save(frame, data) { + if (this.lock || data.lock) { + this.lock = data.lock; + return objectStateFactory.call(this, frame, this.get(frame)); + } + + // All changes are done in this temporary object + const copy = Object.assign(this.get(frame)); + copy.attributes = Object.assign(copy.attributes); + copy.points = [...copy.points]; + + const updated = data.updateFlags; + let positionUpdated = false; + + if (updated.label) { + checkObjectType('label', data.label, null, window.cvat.classes.Label); + copy.label = data.label; + copy.attributes = {}; - for (const attrID of Object.keys(defValuesByID)) { - if (!(attrID in result)) { - result[attrID] = defValuesByID[attrID]; + // Shape attributes will be removed later after all checks + this.appendDefaultAttributes.call(copy, copy.label); + } + + if (updated.attributes) { + const labelAttributes = copy.label.attributes + .reduce((accumulator, value) => { + accumulator[value.id] = value; + return accumulator; + }, {}); + + for (const attrID of Object.keys(data.attributes)) { + if (attrID in labelAttributes) { + copy.attributes[attrID] = data.attributes[attrID]; + if (!labelAttributes[attrID].mutable) { + this.attributes[attrID] = data.attributes[attrID]; + } else { + // Mutable attributes will be updated later + positionUpdated = true; + } + } } } - return result; + if (updated.points) { + checkObjectType('points', data.points, null, Array); + copy.points = []; + for (const coordinate of data.points) { + checkObjectType('coordinate', coordinate, 'number', null); + copy.points.push(coordinate); + } + positionUpdated = true; + } + + if (updated.occluded) { + checkObjectType('occluded', data.occluded, 'boolean', null); + copy.occluded = data.occluded; + positionUpdated = true; + } + + if (updated.outside) { + checkObjectType('outside', data.outside, 'boolean', null); + copy.outside = data.outside; + positionUpdated = true; + } + + if (updated.group) { + checkObjectType('group', data.group, 'integer', null); + copy.group = data.group; + } + + if (updated.zOrder) { + checkObjectType('zOrder', data.zOrder, 'integer', null); + copy.zOrder = data.zOrder; + positionUpdated = true; + } + + if (updated.lock) { + checkObjectType('lock', data.lock, 'boolean', null); + copy.lock = data.lock; + } + + if (updated.color) { + checkObjectType('color', data.color, 'string', null); + if (/^#[0-9A-F]{6}$/i.test(data.color)) { + throw new window.cvat.exceptions.ArgumentError( + `Got invalid color value: "${data.color}"`, + ); + } + + copy.color = data.color; + } + + // Commit all changes + for (const prop of Object.keys(copy)) { + if (prop in this) { + this[prop] = copy[prop]; + } + + this.cache[frame][prop] = copy[prop]; + } + + if (updated.label) { + for (const shape of this.shapes) { + shape.attributes = {}; + } + } + + // Remove keyframe + if (updated.keyframe && !data.keyframe) { + // Remove all cache after this keyframe because it have just become outdated + for (const cacheFrame in this.cache) { + if (+cacheFrame > frame) { + delete this.cache[frame]; + } + } + + this.cache[frame].keyframe = false; + delete this.shapes[frame]; + updated.reset(); + + return objectStateFactory.call(this, frame, this.get(frame)); + } + + // Add/update keyframe + if (positionUpdated || (updated.keyframe && data.keyframe)) { + // Remove all cache after this keyframe because it have just become outdated + for (const cacheFrame in this.cache) { + if (+cacheFrame > frame) { + delete this.cache[frame]; + } + } + + this.cache[frame].keyframe = true; + data.keyframe = true; + + this.shapes[frame] = { + frame, + zOrder: copy.zOrder, + points: copy.points, + outside: copy.outside, + occluded: copy.occluded, + attributes: {}, + }; + + if (updated.attributes) { + const labelAttributes = this.label.attributes + .reduce((accumulator, value) => { + accumulator[value.id] = value; + return accumulator; + }, {}); + + // Unmutable attributes were updated above + for (const attrID of Object.keys(data.attributes)) { + if (attrID in labelAttributes && labelAttributes[attrID].mutable) { + this.shapes[frame].attributes[attrID] = data.attributes[attrID]; + this.shapes[frame].attributes[attrID] = data.attributes[attrID]; + } + } + } + } + + updated.reset(); + + return objectStateFactory.call(this, frame, this.get(frame)); } getPosition(targetFrame) { @@ -242,15 +560,18 @@ occluded: leftPosition.occluded, outside: leftPosition.outside, zOrder: leftPosition.zOrder, + keyframe: true, }; } if (rightPosition && leftPosition) { - return this.interpolatePosition( + return Object.assign({}, this.interpolatePosition( leftPosition, rightPosition, targetFrame, - ); + ), { + keyframe: false, + }); } if (rightPosition) { @@ -259,6 +580,7 @@ occluded: rightPosition.occluded, outside: true, zOrder: 0, + keyframe: false, }; } @@ -268,6 +590,7 @@ occluded: leftPosition.occluded, outside: leftPosition.outside, zOrder: 0, + keyframe: false, }; } @@ -282,17 +605,94 @@ super(data, clientID, injection); } + // Method is used to export data to the server toJSON() { - // TODO: Tags support - return {}; + return { + id: this.serverID, + frame: this.frame, + label_id: this.label.id, + group: this.group, + attributes: Object.keys(this.attributes).reduce((attributeAccumulator, attrId) => { + attributeAccumulator.push({ + spec_id: attrId, + value: this.attributes[attrId], + }); + + return attributeAccumulator; + }, []), + }; } + // Method is used to construct ObjectState objects get(frame) { if (frame !== this.frame) { throw new window.cvat.exceptions.ScriptingError( 'Got frame is not equal to the frame of the shape', ); } + + return { + type: window.cvat.enums.ObjectType.TAG, + clientID: this.clientID, + lock: this.lock, + attributes: Object.assign({}, this.attributes), + label: this.label, + group: this.group, + }; + } + + save(frame, data) { + if (frame !== this.frame) { + throw new window.cvat.exceptions.ScriptingError( + 'Got frame is not equal to the frame of the shape', + ); + } + + if (this.lock && data.lock) { + return objectStateFactory.call(this, frame, this.get(frame)); + } + + // All changes are done in this temporary object + const copy = this.get(frame); + const updated = data.updateFlags; + + if (updated.label) { + checkObjectType('label', data.label, null, window.cvat.classes.Label); + copy.label = data.label; + copy.attributes = {}; + this.appendDefaultAttributes.call(copy, copy.label); + } + + if (updated.attributes) { + const labelAttributes = copy.label + .attributes.map(attr => `${attr.id}`); + + for (const attrID of Object.keys(data.attributes)) { + if (labelAttributes.includes(attrID)) { + copy.attributes[attrID] = data.attributes[attrID]; + } + } + } + + if (updated.group) { + checkObjectType('group', data.group, 'integer', null); + copy.group = data.group; + } + + if (updated.lock) { + checkObjectType('lock', data.lock, 'boolean', null); + copy.lock = data.lock; + } + + // Reset flags and commit all changes + updated.reset(); + for (const prop of Object.keys(copy)) { + if (prop in this) { + this[prop] = copy[prop]; + } + } + + return objectStateFactory.call(this, frame, this.get(frame)); } } @@ -915,15 +1315,12 @@ const shapes = this.shapes[frame] || []; const tags = this.tags[frame] || []; - const states = tracks.map(track => track.get(frame)) - .concat(shapes.map(shape => shape.get(frame))) - .concat(tags.map(tag => tag.get(frame))); - + const objects = tracks.concat(shapes).concat(tags).filter(object => !object.removed); // filtering here const objectStates = []; - for (const state of states) { - const objectState = new ObjectState(state); + for (const object of objects) { + const objectState = objectStateFactory.call(object, frame, object.get(frame)); objectStates.push(objectState); } diff --git a/cvatjs/src/api-implementation.js b/cvatjs/src/api-implementation.js index 0a4d4961..e2d7505e 100644 --- a/cvatjs/src/api-implementation.js +++ b/cvatjs/src/api-implementation.js @@ -116,7 +116,7 @@ if ('taskID' in filter) { task = await serverProxy.tasks.getTasks(`id=${filter.taskID}`); } else { - const [job] = await serverProxy.jobs.getJob(filter.jobID); + const job = await serverProxy.jobs.getJob(filter.jobID); task = await serverProxy.tasks.getTasks(`id=${job.task_id}`); } diff --git a/cvatjs/src/object-state.js b/cvatjs/src/object-state.js index a44c1c48..44acce92 100644 --- a/cvatjs/src/object-state.js +++ b/cvatjs/src/object-state.js @@ -16,25 +16,69 @@ */ class ObjectState { /** - * @param {Object} type - an object which contains initialization information - * about points, group, zOrder, outside, occluded, - * attributes, lock, type, label, mode, etc. - * Types of data equal to listed below + * @param {Object} serialized - is an dictionary which contains + * initial information about an ObjectState; + * Necessary fields: type, shape + * Necessary fields for objects which haven't been added to collection yet: frame + * Optional fields: points, group, zOrder, outside, occluded, + * attributes, lock, label, mode, color, keyframe + * These fields can be set later via setters */ constructor(serialized) { const data = { + label: null, + attributes: {}, + points: null, - group: null, - zOrder: null, outside: null, occluded: null, + keyframe: null, + + group: null, + zOrder: null, lock: null, - attributes: {}, + color: null, + + frame: serialized.frame, type: serialized.type, shape: serialized.shape, + updateFlags: {}, }; + // Shows whether any properties updated since last reset() or interpolation + Object.defineProperty(data.updateFlags, 'reset', { + value: function reset() { + this.label = false; + this.attributes = false; + + this.points = false; + this.outside = false; + this.occluded = false; + this.keyframe = false; + + this.group = false; + this.zOrder = false; + this.lock = false; + this.color = false; + }, + writable: false, + }); + Object.defineProperties(this, Object.freeze({ + // Internal property. We don't need document it. + updateFlags: { + get: () => data.updateFlags, + }, + frame: { + /** + * @name frame + * @type {integer} + * @memberof module:API.cvat.classes.ObjectState + * @readonly + * @instance + */ + get: () => data.frame, + }, type: { /** * @name type @@ -61,51 +105,37 @@ * @type {module:API.cvat.classes.Label} * @memberof module:API.cvat.classes.ObjectState * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} */ get: () => data.label, set: (labelInstance) => { - if (!(labelInstance instanceof window.cvat.classes.Label)) { - throw new window.cvat.exceptions.ArgumentError( - `Expected Label instance, but got "${typeof (labelInstance.constructor.name)}"`, - ); - } - + data.updateFlags.label = true; data.label = labelInstance; }, }, - points: { + color: { /** - * @typedef {Object} Point - * @property {number} x - * @property {number} y - * @global + * @name color + * @type {string} + * @memberof module:API.cvat.classes.ObjectState + * @instance */ + get: () => data.color, + set: (color) => { + data.updateFlags.color = true; + data.color = color; + }, + }, + points: { /** * @name points - * @type {Point[]} + * @type {number[]} * @memberof module:API.cvat.classes.ObjectState * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} */ - get: () => data.position, - set: (position) => { - if (Array.isArray(position)) { - for (const point of position) { - if (typeof (point) !== 'object' - || !('x' in point) || !('y' in point)) { - throw new window.cvat.exceptions.ArgumentError( - `Got invalid point ${point}`, - ); - } - } - } else { - throw new window.cvat.exceptions.ArgumentError( - `Got invalid type "${typeof (position.constructor.name)}"`, - ); - } - - data.position = position; + get: () => data.points, + set: (points) => { + data.updateFlags.points = true; + data.points = [...points]; }, }, group: { @@ -114,17 +144,11 @@ * @type {integer} * @memberof module:API.cvat.classes.ObjectState * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} */ get: () => data.group, - set: (groupID) => { - if (!Number.isInteger(groupID)) { - throw new window.cvat.exceptions.ArgumentError( - `Expected integer, but got ${groupID.constructor.name}`, - ); - } - - data.group = groupID; + set: (group) => { + data.updateFlags.group = true; + data.group = group; }, }, zOrder: { @@ -133,16 +157,10 @@ * @type {integer} * @memberof module:API.cvat.classes.ObjectState * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} */ get: () => data.zOrder, set: (zOrder) => { - if (!Number.isInteger(zOrder)) { - throw new window.cvat.exceptions.ArgumentError( - `Expected integer, but got ${zOrder.constructor.name}`, - ); - } - + data.updateFlags.zOrder = true; data.zOrder = zOrder; }, }, @@ -152,35 +170,36 @@ * @type {boolean} * @memberof module:API.cvat.classes.ObjectState * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} */ get: () => data.outside, set: (outside) => { - if (typeof (outside) !== 'boolean') { - throw new window.cvat.exceptions.ArgumentError( - `Expected boolean, but got ${outside.constructor.name}`, - ); - } - + data.updateFlags.outside = true; data.outside = outside; }, }, + keyframe: { + /** + * @name keyframe + * @type {boolean} + * @memberof module:API.cvat.classes.ObjectState + * @instance + */ + get: () => data.keyframe, + set: (keyframe) => { + data.updateFlags.keyframe = true; + data.keyframe = keyframe; + }, + }, occluded: { /** * @name occluded * @type {boolean} * @memberof module:API.cvat.classes.ObjectState * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} */ get: () => data.occluded, set: (occluded) => { - if (typeof (occluded) !== 'boolean') { - throw new window.cvat.exceptions.ArgumentError( - `Expected boolean, but got ${occluded.constructor.name}`, - ); - } - + data.updateFlags.occluded = true; data.occluded = occluded; }, }, @@ -190,16 +209,10 @@ * @type {boolean} * @memberof module:API.cvat.classes.ObjectState * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} */ get: () => data.lock, set: (lock) => { - if (typeof (lock) !== 'boolean') { - throw new window.cvat.exceptions.ArgumentError( - `Expected boolean, but got ${lock.constructor.name}`, - ); - } - + data.updateFlags.lock = true; data.lock = lock; }, }, @@ -210,60 +223,55 @@ * @name attributes * @type {Object} * @memberof module:API.cvat.classes.ObjectState - * @instance * @throws {module:API.cvat.exceptions.ArgumentError} + * @instance */ get: () => data.attributes, set: (attributes) => { if (typeof (attributes) !== 'object') { + if (typeof (attributes) === 'undefined') { + throw new window.cvat.exceptions.ArgumentError( + 'Expected attributes are object, but got undefined', + ); + } + throw new window.cvat.exceptions.ArgumentError( - `Expected object, but got ${attributes.constructor.name}`, + `Expected attributes are object, but got ${attributes.constructor.name}`, ); } - for (let attrId in attributes) { - if (Object.prototype.hasOwnProperty.call(attributes, attrId)) { - attrId = +attrId; - if (!Number.isInteger(attrId)) { - throw new window.cvat.exceptions.ArgumentError( - `Expected integer attribute id, but got ${attrId.constructor.name}`, - ); - } - - data.attributes[attrId] = attributes[attrId]; - } + for (const attrID of Object.keys(attributes)) { + data.updateFlags.attributes = true; + data.attributes[attrID] = attributes[attrID]; } }, }, - })); this.label = serialized.label; this.group = serialized.group; this.zOrder = serialized.zOrder; this.outside = serialized.outside; + this.keyframe = serialized.keyframe; this.occluded = serialized.occluded; this.attributes = serialized.attributes; - this.lock = false; + this.points = serialized.points; + this.color = serialized.color; + this.lock = serialized.lock; - const points = []; - for (let i = 0; i < serialized.points.length; i += 2) { - points.push({ - x: serialized.points[i], - y: serialized.points[i + 1], - }); - } - this.points = points; + data.updateFlags.reset(); } /** - * Method saves object state in a collection + * Method saves/updates an object state in a collection * @method save * @memberof module:API.cvat.classes.ObjectState * @readonly * @instance * @async * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} + * @returns {module:API.cvat.classes.ObjectState} updated state of an object */ async save() { const result = await PluginRegistry @@ -272,21 +280,41 @@ } /** - * Method deletes object from a collection + * Method deletes an object from a collection * @method delete * @memberof module:API.cvat.classes.ObjectState * @readonly * @instance + * @param {boolean} [force=false] delete object even if it is locked * @async + * @returns {boolean} wheter object was deleted * @throws {module:API.cvat.exceptions.PluginError} */ - async delete() { + async delete(force = false) { const result = await PluginRegistry - .apiWrapper.call(this, ObjectState.prototype.delete); + .apiWrapper.call(this, ObjectState.prototype.delete, force); return result; } } + // Default implementation saves element in collection + ObjectState.prototype.save.implementation = async function () { + if (this.updateInCollection) { + return this.updateInCollection(); + } + + return this; + }; + + // Default implementation do nothing + ObjectState.prototype.delete.implementation = async function (force) { + if (this.deleteFromCollection) { + return this.deleteFromCollection(force); + } + + return false; + }; + module.exports = ObjectState; })(); diff --git a/cvatjs/src/server-proxy.js b/cvatjs/src/server-proxy.js index 65380a03..df983621 100644 --- a/cvatjs/src/server-proxy.js +++ b/cvatjs/src/server-proxy.js @@ -179,10 +179,10 @@ } async function logout() { - const { backendAPI } = window.cvat.config; + const host = window.cvat.config.backendAPI.slice(0, -7); try { - await Axios.get(`${backendAPI}/auth/logout`, { + await Axios.get(`${host}/auth/logout`, { proxy: window.cvat.config.proxy, }); } catch (errorData) {