Merge branch 'release-1.6.0'
commit
8d03ed7959
@ -1,12 +0,0 @@
|
||||
// Copyright (C) 2019-2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
module.exports = {
|
||||
parser: false,
|
||||
plugins: {
|
||||
'postcss-preset-env': {
|
||||
browsers: '> 2.5%', // https://github.com/browserslist/browserslist
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -1,12 +0,0 @@
|
||||
// Copyright (C) 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
module.exports = {
|
||||
parser: false,
|
||||
plugins: {
|
||||
'postcss-preset-env': {
|
||||
browsers: '> 2.5%', // https://github.com/browserslist/browserslist
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,520 @@
|
||||
// Copyright (C) 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
(() => {
|
||||
const PluginRegistry = require('./plugins');
|
||||
const serverProxy = require('./server-proxy');
|
||||
const { isBrowser, isNode } = require('browser-or-node');
|
||||
const { ArgumentError } = require('./exceptions');
|
||||
const { CloudStorageCredentialsType, CloudStorageProviderType } = require('./enums');
|
||||
|
||||
/**
|
||||
* Class representing a cloud storage
|
||||
* @memberof module:API.cvat.classes
|
||||
*/
|
||||
class CloudStorage {
|
||||
// TODO: add storage availability status (avaliable/unavaliable)
|
||||
constructor(initialData) {
|
||||
const data = {
|
||||
id: undefined,
|
||||
display_name: undefined,
|
||||
description: undefined,
|
||||
credentials_type: undefined,
|
||||
provider_type: undefined,
|
||||
resource: undefined,
|
||||
account_name: undefined,
|
||||
key: undefined,
|
||||
secret_key: undefined,
|
||||
session_token: undefined,
|
||||
specific_attributes: undefined,
|
||||
owner: undefined,
|
||||
created_date: undefined,
|
||||
updated_date: undefined,
|
||||
manifest_path: undefined,
|
||||
manifests: undefined,
|
||||
};
|
||||
|
||||
for (const property in data) {
|
||||
if (Object.prototype.hasOwnProperty.call(data, property) && property in initialData) {
|
||||
data[property] = initialData[property];
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperties(
|
||||
this,
|
||||
Object.freeze({
|
||||
/**
|
||||
* @name id
|
||||
* @type {integer}
|
||||
* @memberof module:API.cvat.classes.CloudStorage
|
||||
* @readonly
|
||||
* @instance
|
||||
*/
|
||||
id: {
|
||||
get: () => data.id,
|
||||
},
|
||||
/**
|
||||
* Storage name
|
||||
* @name displayName
|
||||
* @type {string}
|
||||
* @memberof module:API.cvat.classes.CloudStorage
|
||||
* @instance
|
||||
* @throws {module:API.cvat.exceptions.ArgumentError}
|
||||
*/
|
||||
displayName: {
|
||||
get: () => data.display_name,
|
||||
set: (value) => {
|
||||
if (typeof value !== 'string') {
|
||||
throw new ArgumentError(`Value must be string. ${typeof value} was found`);
|
||||
} else if (!value.trim().length) {
|
||||
throw new ArgumentError('Value must not be empty string');
|
||||
}
|
||||
data.display_name = value;
|
||||
},
|
||||
},
|
||||
/**
|
||||
* Storage description
|
||||
* @name description
|
||||
* @type {string}
|
||||
* @memberof module:API.cvat.classes.CloudStorage
|
||||
* @instance
|
||||
* @throws {module:API.cvat.exceptions.ArgumentError}
|
||||
*/
|
||||
description: {
|
||||
get: () => data.description,
|
||||
set: (value) => {
|
||||
if (typeof value !== 'string') {
|
||||
throw new ArgumentError('Value must be string');
|
||||
}
|
||||
data.description = value;
|
||||
},
|
||||
},
|
||||
/**
|
||||
* Azure account name
|
||||
* @name accountName
|
||||
* @type {string}
|
||||
* @memberof module:API.cvat.classes.CloudStorage
|
||||
* @instance
|
||||
* @throws {module:API.cvat.exceptions.ArgumentError}
|
||||
*/
|
||||
accountName: {
|
||||
get: () => data.account_name,
|
||||
set: (value) => {
|
||||
if (typeof value === 'string') {
|
||||
if (value.trim().length) {
|
||||
data.account_name = value;
|
||||
} else {
|
||||
throw new ArgumentError('Value must not be empty');
|
||||
}
|
||||
} else {
|
||||
throw new ArgumentError(`Value must be a string. ${typeof value} was found`);
|
||||
}
|
||||
},
|
||||
},
|
||||
/**
|
||||
* AWS access key id
|
||||
* @name accessKey
|
||||
* @type {string}
|
||||
* @memberof module:API.cvat.classes.CloudStorage
|
||||
* @instance
|
||||
* @throws {module:API.cvat.exceptions.ArgumentError}
|
||||
*/
|
||||
accessKey: {
|
||||
get: () => data.key,
|
||||
set: (value) => {
|
||||
if (typeof value === 'string') {
|
||||
if (value.trim().length) {
|
||||
data.key = value;
|
||||
} else {
|
||||
throw new ArgumentError('Value must not be empty');
|
||||
}
|
||||
} else {
|
||||
throw new ArgumentError(`Value must be a string. ${typeof value} was found`);
|
||||
}
|
||||
},
|
||||
},
|
||||
/**
|
||||
* AWS secret key
|
||||
* @name secretKey
|
||||
* @type {string}
|
||||
* @memberof module:API.cvat.classes.CloudStorage
|
||||
* @instance
|
||||
* @throws {module:API.cvat.exceptions.ArgumentError}
|
||||
*/
|
||||
secretKey: {
|
||||
get: () => data.secret_key,
|
||||
set: (value) => {
|
||||
if (typeof value === 'string') {
|
||||
if (value.trim().length) {
|
||||
data.secret_key = value;
|
||||
} else {
|
||||
throw new ArgumentError('Value must not be empty');
|
||||
}
|
||||
} else {
|
||||
throw new ArgumentError(`Value must be a string. ${typeof value} was found`);
|
||||
}
|
||||
},
|
||||
},
|
||||
/**
|
||||
* Session token
|
||||
* @name token
|
||||
* @type {string}
|
||||
* @memberof module:API.cvat.classes.CloudStorage
|
||||
* @instance
|
||||
* @throws {module:API.cvat.exceptions.ArgumentError}
|
||||
*/
|
||||
token: {
|
||||
get: () => data.session_token,
|
||||
set: (value) => {
|
||||
if (typeof value === 'string') {
|
||||
if (value.trim().length) {
|
||||
data.session_token = value;
|
||||
} else {
|
||||
throw new ArgumentError('Value must not be empty');
|
||||
}
|
||||
} else {
|
||||
throw new ArgumentError(`Value must be a string. ${typeof value} was found`);
|
||||
}
|
||||
},
|
||||
},
|
||||
/**
|
||||
* Unique resource name
|
||||
* @name resourceName
|
||||
* @type {string}
|
||||
* @memberof module:API.cvat.classes.CloudStorage
|
||||
* @instance
|
||||
* @throws {module:API.cvat.exceptions.ArgumentError}
|
||||
*/
|
||||
resourceName: {
|
||||
get: () => data.resource,
|
||||
set: (value) => {
|
||||
if (typeof value !== 'string') {
|
||||
throw new ArgumentError(`Value must be string. ${typeof value} was found`);
|
||||
} else if (!value.trim().length) {
|
||||
throw new ArgumentError('Value must not be empty');
|
||||
}
|
||||
data.resource = value;
|
||||
},
|
||||
},
|
||||
/**
|
||||
* @name manifestPath
|
||||
* @type {string}
|
||||
* @memberof module:API.cvat.classes.CloudStorage
|
||||
* @instance
|
||||
* @throws {module:API.cvat.exceptions.ArgumentError}
|
||||
*/
|
||||
manifestPath: {
|
||||
get: () => data.manifest_path,
|
||||
set: (value) => {
|
||||
if (typeof value === 'string') {
|
||||
data.manifest_path = value;
|
||||
} else {
|
||||
throw new ArgumentError('Value must be a string');
|
||||
}
|
||||
},
|
||||
},
|
||||
/**
|
||||
* @name providerType
|
||||
* @type {module:API.cvat.enums.ProviderType}
|
||||
* @memberof module:API.cvat.classes.CloudStorage
|
||||
* @instance
|
||||
* @throws {module:API.cvat.exceptions.ArgumentError}
|
||||
*/
|
||||
providerType: {
|
||||
get: () => data.provider_type,
|
||||
set: (key) => {
|
||||
if (key !== undefined && !!CloudStorageProviderType[key]) {
|
||||
data.provider_type = CloudStorageProviderType[key];
|
||||
} else {
|
||||
throw new ArgumentError('Value must be one CloudStorageProviderType keys');
|
||||
}
|
||||
},
|
||||
},
|
||||
/**
|
||||
* @name credentialsType
|
||||
* @type {module:API.cvat.enums.CloudStorageCredentialsType}
|
||||
* @memberof module:API.cvat.classes.CloudStorage
|
||||
* @instance
|
||||
* @throws {module:API.cvat.exceptions.ArgumentError}
|
||||
*/
|
||||
credentialsType: {
|
||||
get: () => data.credentials_type,
|
||||
set: (key) => {
|
||||
if (key !== undefined && !!CloudStorageCredentialsType[key]) {
|
||||
data.credentials_type = CloudStorageCredentialsType[key];
|
||||
} else {
|
||||
throw new ArgumentError('Value must be one CloudStorageCredentialsType keys');
|
||||
}
|
||||
},
|
||||
},
|
||||
/**
|
||||
* @name specificAttributes
|
||||
* @type {string}
|
||||
* @memberof module:API.cvat.classes.CloudStorage
|
||||
* @instance
|
||||
* @throws {module:API.cvat.exceptions.ArgumentError}
|
||||
*/
|
||||
specificAttributes: {
|
||||
get: () => data.specific_attributes,
|
||||
set: (attributesValue) => {
|
||||
if (typeof attributesValue === 'string') {
|
||||
const attrValues = new URLSearchParams(
|
||||
Array.from(new URLSearchParams(attributesValue).entries()).filter(
|
||||
([key, value]) => !!key && !!value,
|
||||
),
|
||||
).toString();
|
||||
if (!attrValues) {
|
||||
throw new ArgumentError('Value must match the key1=value1&key2=value2');
|
||||
}
|
||||
data.specific_attributes = attributesValue;
|
||||
} else {
|
||||
throw new ArgumentError('Value must be a string');
|
||||
}
|
||||
},
|
||||
},
|
||||
/**
|
||||
* Instance of a user who has created the cloud storage
|
||||
* @name owner
|
||||
* @type {module:API.cvat.classes.User}
|
||||
* @memberof module:API.cvat.classes.CloudStorage
|
||||
* @readonly
|
||||
* @instance
|
||||
*/
|
||||
owner: {
|
||||
get: () => data.owner,
|
||||
},
|
||||
/**
|
||||
* @name createdDate
|
||||
* @type {string}
|
||||
* @memberof module:API.cvat.classes.CloudStorage
|
||||
* @readonly
|
||||
* @instance
|
||||
*/
|
||||
createdDate: {
|
||||
get: () => data.created_date,
|
||||
},
|
||||
/**
|
||||
* @name updatedDate
|
||||
* @type {string}
|
||||
* @memberof module:API.cvat.classes.CloudStorage
|
||||
* @readonly
|
||||
* @instance
|
||||
*/
|
||||
updatedDate: {
|
||||
get: () => data.updated_date,
|
||||
},
|
||||
/**
|
||||
* @name manifests
|
||||
* @type {string[]}
|
||||
* @memberof module:API.cvat.classes.CloudStorage
|
||||
* @instance
|
||||
* @throws {module:API.cvat.exceptions.ArgumentError}
|
||||
*/
|
||||
manifests: {
|
||||
get: () => data.manifests,
|
||||
set: (manifests) => {
|
||||
if (Array.isArray(manifests)) {
|
||||
for (const elem of manifests) {
|
||||
if (typeof elem !== 'string') {
|
||||
throw new ArgumentError('Each element of the manifests array must be a string');
|
||||
}
|
||||
}
|
||||
data.manifests = manifests;
|
||||
} else {
|
||||
throw new ArgumentError('Value must be an array');
|
||||
}
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method updates data of a created cloud storage or creates new cloud storage
|
||||
* @method save
|
||||
* @returns {module:API.cvat.classes.CloudStorage}
|
||||
* @memberof module:API.cvat.classes.CloudStorage
|
||||
* @readonly
|
||||
* @instance
|
||||
* @async
|
||||
* @throws {module:API.cvat.exceptions.ServerError}
|
||||
* @throws {module:API.cvat.exceptions.PluginError}
|
||||
*/
|
||||
async save() {
|
||||
const result = await PluginRegistry.apiWrapper.call(this, CloudStorage.prototype.save);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method deletes a cloud storage from a server
|
||||
* @method delete
|
||||
* @memberof module:API.cvat.classes.CloudStorage
|
||||
* @readonly
|
||||
* @instance
|
||||
* @async
|
||||
* @throws {module:API.cvat.exceptions.ServerError}
|
||||
* @throws {module:API.cvat.exceptions.PluginError}
|
||||
*/
|
||||
async delete() {
|
||||
const result = await PluginRegistry.apiWrapper.call(this, CloudStorage.prototype.delete);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method returns cloud storage content
|
||||
* @method getContent
|
||||
* @memberof module:API.cvat.classes.CloudStorage
|
||||
* @readonly
|
||||
* @instance
|
||||
* @async
|
||||
* @throws {module:API.cvat.exceptions.ServerError}
|
||||
* @throws {module:API.cvat.exceptions.PluginError}
|
||||
*/
|
||||
async getContent() {
|
||||
const result = await PluginRegistry.apiWrapper.call(this, CloudStorage.prototype.getContent);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method returns the cloud storage preview
|
||||
* @method getPreview
|
||||
* @memberof module:API.cvat.classes.CloudStorage
|
||||
* @readonly
|
||||
* @instance
|
||||
* @async
|
||||
* @throws {module:API.cvat.exceptions.ServerError}
|
||||
* @throws {module:API.cvat.exceptions.PluginError}
|
||||
*/
|
||||
async getPreview() {
|
||||
const result = await PluginRegistry.apiWrapper.call(this, CloudStorage.prototype.getPreview);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method returns cloud storage status
|
||||
* @method getStatus
|
||||
* @memberof module:API.cvat.classes.CloudStorage
|
||||
* @readonly
|
||||
* @instance
|
||||
* @async
|
||||
* @throws {module:API.cvat.exceptions.ServerError}
|
||||
* @throws {module:API.cvat.exceptions.PluginError}
|
||||
*/
|
||||
async getStatus() {
|
||||
const result = await PluginRegistry.apiWrapper.call(this, CloudStorage.prototype.getStatus);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
CloudStorage.prototype.save.implementation = async function () {
|
||||
function prepareOptionalFields(cloudStorageInstance) {
|
||||
const data = {};
|
||||
if (cloudStorageInstance.description) {
|
||||
data.description = cloudStorageInstance.description;
|
||||
}
|
||||
|
||||
if (cloudStorageInstance.accountName) {
|
||||
data.account_name = cloudStorageInstance.accountName;
|
||||
}
|
||||
|
||||
if (cloudStorageInstance.accessKey) {
|
||||
data.key = cloudStorageInstance.accessKey;
|
||||
}
|
||||
|
||||
if (cloudStorageInstance.secretKey) {
|
||||
data.secret_key = cloudStorageInstance.secretKey;
|
||||
}
|
||||
|
||||
if (cloudStorageInstance.token) {
|
||||
data.session_token = cloudStorageInstance.token;
|
||||
}
|
||||
|
||||
if (cloudStorageInstance.specificAttributes) {
|
||||
data.specific_attributes = cloudStorageInstance.specificAttributes;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
// update
|
||||
if (typeof this.id !== 'undefined') {
|
||||
// providr_type and recource should not change;
|
||||
// send to the server only the values that have changed
|
||||
const initialData = {};
|
||||
if (this.displayName) {
|
||||
initialData.display_name = this.displayName;
|
||||
}
|
||||
if (this.credentialsType) {
|
||||
initialData.credentials_type = this.credentialsType;
|
||||
}
|
||||
|
||||
if (this.manifests) {
|
||||
initialData.manifests = this.manifests;
|
||||
}
|
||||
|
||||
const cloudStorageData = {
|
||||
...initialData,
|
||||
...prepareOptionalFields(this),
|
||||
};
|
||||
|
||||
await serverProxy.cloudStorages.update(this.id, cloudStorageData);
|
||||
return this;
|
||||
}
|
||||
|
||||
// create
|
||||
const initialData = {
|
||||
display_name: this.displayName,
|
||||
credentials_type: this.credentialsType,
|
||||
provider_type: this.providerType,
|
||||
resource: this.resourceName,
|
||||
manifests: this.manifests,
|
||||
};
|
||||
|
||||
const cloudStorageData = {
|
||||
...initialData,
|
||||
...prepareOptionalFields(this),
|
||||
};
|
||||
|
||||
const cloudStorage = await serverProxy.cloudStorages.create(cloudStorageData);
|
||||
return new CloudStorage(cloudStorage);
|
||||
};
|
||||
|
||||
CloudStorage.prototype.delete.implementation = async function () {
|
||||
const result = await serverProxy.cloudStorages.delete(this.id);
|
||||
return result;
|
||||
};
|
||||
|
||||
CloudStorage.prototype.getContent.implementation = async function () {
|
||||
const result = await serverProxy.cloudStorages.getContent(this.id, this.manifestPath);
|
||||
return result;
|
||||
};
|
||||
|
||||
CloudStorage.prototype.getPreview.implementation = async function getPreview() {
|
||||
return new Promise((resolve, reject) => {
|
||||
serverProxy.cloudStorages
|
||||
.getPreview(this.id)
|
||||
.then((result) => {
|
||||
if (isNode) {
|
||||
resolve(global.Buffer.from(result, 'binary').toString('base64'));
|
||||
} else if (isBrowser) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
resolve(reader.result);
|
||||
};
|
||||
reader.readAsDataURL(result);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
CloudStorage.prototype.getStatus.implementation = async function () {
|
||||
const result = await serverProxy.cloudStorages.getStatus(this.id);
|
||||
return result;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
CloudStorage,
|
||||
};
|
||||
})();
|
||||
@ -0,0 +1,74 @@
|
||||
// Copyright (C) 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
(() => {
|
||||
const serverProxy = require('./server-proxy');
|
||||
const { getPreview } = require('./frames');
|
||||
|
||||
const { Project } = require('./project');
|
||||
const { exportDataset } = require('./annotations');
|
||||
|
||||
function implementProject(projectClass) {
|
||||
projectClass.prototype.save.implementation = async function () {
|
||||
const trainingProjectCopy = this.trainingProject;
|
||||
if (typeof this.id !== 'undefined') {
|
||||
// project has been already created, need to update some data
|
||||
const projectData = {
|
||||
name: this.name,
|
||||
assignee_id: this.assignee ? this.assignee.id : null,
|
||||
bug_tracker: this.bugTracker,
|
||||
labels: [...this._internalData.labels.map((el) => el.toJSON())],
|
||||
};
|
||||
|
||||
if (trainingProjectCopy) {
|
||||
projectData.training_project = trainingProjectCopy;
|
||||
}
|
||||
|
||||
await serverProxy.projects.save(this.id, projectData);
|
||||
return this;
|
||||
}
|
||||
|
||||
// initial creating
|
||||
const projectSpec = {
|
||||
name: this.name,
|
||||
labels: [...this.labels.map((el) => el.toJSON())],
|
||||
};
|
||||
|
||||
if (this.bugTracker) {
|
||||
projectSpec.bug_tracker = this.bugTracker;
|
||||
}
|
||||
|
||||
if (trainingProjectCopy) {
|
||||
projectSpec.training_project = trainingProjectCopy;
|
||||
}
|
||||
|
||||
const project = await serverProxy.projects.create(projectSpec);
|
||||
return new Project(project);
|
||||
};
|
||||
|
||||
projectClass.prototype.delete.implementation = async function () {
|
||||
const result = await serverProxy.projects.delete(this.id);
|
||||
return result;
|
||||
};
|
||||
|
||||
projectClass.prototype.preview.implementation = async function () {
|
||||
if (!this._internalData.task_ids.length) {
|
||||
return '';
|
||||
}
|
||||
const frameData = await getPreview(this._internalData.task_ids[0]);
|
||||
return frameData;
|
||||
};
|
||||
|
||||
projectClass.prototype.annotations.exportDataset.implementation = async function (
|
||||
format, saveImages, customName,
|
||||
) {
|
||||
const result = exportDataset(this, format, customName, saveImages);
|
||||
return result;
|
||||
};
|
||||
|
||||
return projectClass;
|
||||
}
|
||||
|
||||
module.exports = implementProject;
|
||||
})();
|
||||
@ -0,0 +1,178 @@
|
||||
// Copyright (C) 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Setup mock for a server
|
||||
jest.mock('../../src/server-proxy', () => {
|
||||
const mock = require('../mocks/server-proxy.mock');
|
||||
return mock;
|
||||
});
|
||||
|
||||
// Initialize api
|
||||
window.cvat = require('../../src/api');
|
||||
|
||||
const { CloudStorage } = require('../../src/cloud-storage');
|
||||
const { cloudStoragesDummyData } = require('../mocks/dummy-data.mock');
|
||||
|
||||
describe('Feature: get cloud storages', () => {
|
||||
test('get all cloud storages', async () => {
|
||||
const result = await window.cvat.cloudStorages.get();
|
||||
expect(Array.isArray(result)).toBeTruthy();
|
||||
expect(result).toHaveLength(cloudStoragesDummyData.count);
|
||||
for (const item of result) {
|
||||
expect(item).toBeInstanceOf(CloudStorage);
|
||||
}
|
||||
});
|
||||
|
||||
test('get cloud storage by id', async () => {
|
||||
const result = await window.cvat.cloudStorages.get({
|
||||
id: 1,
|
||||
});
|
||||
const cloudStorage = result[0];
|
||||
|
||||
expect(Array.isArray(result)).toBeTruthy();
|
||||
expect(result).toHaveLength(1);
|
||||
expect(cloudStorage).toBeInstanceOf(CloudStorage);
|
||||
expect(cloudStorage.id).toBe(1);
|
||||
expect(cloudStorage.providerType).toBe('AWS_S3_BUCKET');
|
||||
expect(cloudStorage.credentialsType).toBe('KEY_SECRET_KEY_PAIR');
|
||||
expect(cloudStorage.resourceName).toBe('bucket');
|
||||
expect(cloudStorage.displayName).toBe('Demonstration bucket');
|
||||
expect(cloudStorage.manifests).toHaveLength(1);
|
||||
expect(cloudStorage.manifests[0]).toBe('manifest.jsonl');
|
||||
expect(cloudStorage.specificAttributes).toBe('');
|
||||
expect(cloudStorage.description).toBe('It is first bucket');
|
||||
});
|
||||
|
||||
test('get a cloud storage by an unknown id', async () => {
|
||||
const result = await window.cvat.cloudStorages.get({
|
||||
id: 10,
|
||||
});
|
||||
expect(Array.isArray(result)).toBeTruthy();
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('get a cloud storage by an invalid id', async () => {
|
||||
expect(
|
||||
window.cvat.cloudStorages.get({
|
||||
id: '1',
|
||||
}),
|
||||
).rejects.toThrow(window.cvat.exceptions.ArgumentError);
|
||||
});
|
||||
|
||||
test('get cloud storages by filters', async () => {
|
||||
const filters = new Map([
|
||||
['providerType', 'AWS_S3_BUCKET'],
|
||||
['resourceName', 'bucket'],
|
||||
['displayName', 'Demonstration bucket'],
|
||||
['credentialsType', 'KEY_SECRET_KEY_PAIR'],
|
||||
['description', 'It is first bucket'],
|
||||
]);
|
||||
|
||||
const result = await window.cvat.cloudStorages.get(Object.fromEntries(filters));
|
||||
|
||||
const [cloudStorage] = result;
|
||||
expect(Array.isArray(result)).toBeTruthy();
|
||||
expect(result).toHaveLength(1);
|
||||
expect(cloudStorage).toBeInstanceOf(CloudStorage);
|
||||
expect(cloudStorage.id).toBe(1);
|
||||
filters.forEach((value, key) => {
|
||||
expect(cloudStorage[key]).toBe(value);
|
||||
});
|
||||
});
|
||||
|
||||
test('get cloud storage by invalid filters', async () => {
|
||||
expect(
|
||||
window.cvat.cloudStorages.get({
|
||||
unknown: '5',
|
||||
}),
|
||||
).rejects.toThrow(window.cvat.exceptions.ArgumentError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Feature: create a cloud storage', () => {
|
||||
test('create new cloud storage without an id', async () => {
|
||||
const cloudStorage = new window.cvat.classes.CloudStorage({
|
||||
display_name: 'new cloud storage',
|
||||
provider_type: 'AZURE_CONTAINER',
|
||||
resource: 'newcontainer',
|
||||
credentials_type: 'ACCOUNT_NAME_TOKEN_PAIR',
|
||||
account_name: 'accountname',
|
||||
session_token: 'x'.repeat(135),
|
||||
manifests: ['manifest.jsonl'],
|
||||
});
|
||||
|
||||
const result = await cloudStorage.save();
|
||||
expect(typeof result.id).toBe('number');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Feature: update a cloud storage', () => {
|
||||
test('update cloud storage with some new field values', async () => {
|
||||
const newValues = new Map([
|
||||
['displayName', 'new display name'],
|
||||
['credentialsType', 'ANONYMOUS_ACCESS'],
|
||||
['description', 'new description'],
|
||||
['specificAttributes', 'region=eu-west-1'],
|
||||
]);
|
||||
|
||||
let result = await window.cvat.cloudStorages.get({
|
||||
id: 1,
|
||||
});
|
||||
|
||||
let [cloudStorage] = result;
|
||||
|
||||
for (const [key, value] of newValues) {
|
||||
cloudStorage[key] = value;
|
||||
}
|
||||
|
||||
cloudStorage.save();
|
||||
|
||||
result = await window.cvat.cloudStorages.get({
|
||||
id: 1,
|
||||
});
|
||||
[cloudStorage] = result;
|
||||
|
||||
newValues.forEach((value, key) => {
|
||||
expect(cloudStorage[key]).toBe(value);
|
||||
});
|
||||
});
|
||||
|
||||
test('Update manifests in a cloud storage', async () => {
|
||||
const newManifests = [
|
||||
'sub1/manifest.jsonl',
|
||||
'sub2/manifest.jsonl',
|
||||
];
|
||||
|
||||
let result = await window.cvat.cloudStorages.get({
|
||||
id: 1,
|
||||
});
|
||||
let [cloudStorage] = result;
|
||||
|
||||
cloudStorage.manifests = newManifests;
|
||||
cloudStorage.save();
|
||||
|
||||
result = await window.cvat.cloudStorages.get({
|
||||
id: 1,
|
||||
});
|
||||
[cloudStorage] = result;
|
||||
|
||||
expect(cloudStorage.manifests).toEqual(newManifests);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Feature: delete a cloud storage', () => {
|
||||
test('delete a cloud storage', async () => {
|
||||
let result = await window.cvat.cloudStorages.get({
|
||||
id: 2,
|
||||
});
|
||||
|
||||
await result[0].delete();
|
||||
result = await window.cvat.cloudStorages.get({
|
||||
id: 2,
|
||||
});
|
||||
|
||||
expect(Array.isArray(result)).toBeTruthy();
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
@ -1,13 +0,0 @@
|
||||
// Copyright (C) 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/* eslint-disable */
|
||||
module.exports = {
|
||||
parser: false,
|
||||
plugins: {
|
||||
'postcss-preset-env': {
|
||||
browsers: '> 2.5%', // https://github.com/browserslist/browserslist
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,49 @@
|
||||
// Copyright (C) 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { ActionUnion, createAction, ThunkAction } from 'utils/redux';
|
||||
|
||||
export enum ExportActionTypes {
|
||||
OPEN_EXPORT_MODAL = 'OPEN_EXPORT_MODAL',
|
||||
CLOSE_EXPORT_MODAL = 'CLOSE_EXPORT_MODAL',
|
||||
EXPORT_DATASET = 'EXPORT_DATASET',
|
||||
EXPORT_DATASET_SUCCESS = 'EXPORT_DATASET_SUCCESS',
|
||||
EXPORT_DATASET_FAILED = 'EXPORT_DATASET_FAILED',
|
||||
}
|
||||
|
||||
export const exportActions = {
|
||||
openExportModal: (instance: any) => createAction(ExportActionTypes.OPEN_EXPORT_MODAL, { instance }),
|
||||
closeExportModal: () => createAction(ExportActionTypes.CLOSE_EXPORT_MODAL),
|
||||
exportDataset: (instance: any, format: string) =>
|
||||
createAction(ExportActionTypes.EXPORT_DATASET, { instance, format }),
|
||||
exportDatasetSuccess: (instance: any, format: string) =>
|
||||
createAction(ExportActionTypes.EXPORT_DATASET_SUCCESS, { instance, format }),
|
||||
exportDatasetFailed: (instance: any, format: string, error: any) =>
|
||||
createAction(ExportActionTypes.EXPORT_DATASET_FAILED, {
|
||||
instance,
|
||||
format,
|
||||
error,
|
||||
}),
|
||||
};
|
||||
|
||||
export const exportDatasetAsync = (
|
||||
instance: any,
|
||||
format: string,
|
||||
name: string,
|
||||
saveImages: boolean,
|
||||
): ThunkAction => async (dispatch) => {
|
||||
dispatch(exportActions.exportDataset(instance, format));
|
||||
|
||||
try {
|
||||
const url = await instance.annotations.exportDataset(format, saveImages, name);
|
||||
const downloadAnchor = window.document.getElementById('downloadAnchor') as HTMLAnchorElement;
|
||||
downloadAnchor.href = url;
|
||||
downloadAnchor.click();
|
||||
dispatch(exportActions.exportDatasetSuccess(instance, format));
|
||||
} catch (error) {
|
||||
dispatch(exportActions.exportDatasetFailed(instance, format, error));
|
||||
}
|
||||
};
|
||||
|
||||
export type ExportActions = ActionUnion<typeof exportActions>;
|
||||
@ -1,54 +0,0 @@
|
||||
// Copyright (C) 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import React from 'react';
|
||||
import Menu from 'antd/lib/menu';
|
||||
import { DownloadOutlined, LoadingOutlined } from '@ant-design/icons';
|
||||
import Text from 'antd/lib/typography/Text';
|
||||
import { DimensionType } from '../../reducers/interfaces';
|
||||
|
||||
function isDefaultFormat(dumperName: string, taskMode: string): boolean {
|
||||
return (
|
||||
(dumperName === 'CVAT for video 1.1' && taskMode === 'interpolation') ||
|
||||
(dumperName === 'CVAT for images 1.1' && taskMode === 'annotation')
|
||||
);
|
||||
}
|
||||
|
||||
interface Props {
|
||||
taskMode: string;
|
||||
menuKey: string;
|
||||
dumpers: any[];
|
||||
dumpActivities: string[] | null;
|
||||
taskDimension: DimensionType;
|
||||
}
|
||||
|
||||
export default function DumpSubmenu(props: Props): JSX.Element {
|
||||
const {
|
||||
taskMode, menuKey, dumpers, dumpActivities, taskDimension,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Menu.SubMenu key={menuKey} title='Dump annotations'>
|
||||
{dumpers
|
||||
.sort((a: any, b: any) => a.name.localeCompare(b.name))
|
||||
.filter((dumper: any): boolean => dumper.dimension === taskDimension)
|
||||
.map(
|
||||
(dumper: any): JSX.Element => {
|
||||
const pending = (dumpActivities || []).includes(dumper.name);
|
||||
const disabled = !dumper.enabled || pending;
|
||||
const isDefault = isDefaultFormat(dumper.name, taskMode);
|
||||
return (
|
||||
<Menu.Item key={dumper.name} disabled={disabled} className='cvat-menu-dump-submenu-item'>
|
||||
<DownloadOutlined />
|
||||
<Text strong={isDefault} disabled={disabled}>
|
||||
{dumper.name}
|
||||
</Text>
|
||||
{pending && <LoadingOutlined style={{ marginLeft: 10 }} />}
|
||||
</Menu.Item>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</Menu.SubMenu>
|
||||
);
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
// Copyright (C) 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import React from 'react';
|
||||
import Menu from 'antd/lib/menu';
|
||||
import Text from 'antd/lib/typography/Text';
|
||||
import { ExportOutlined, LoadingOutlined } from '@ant-design/icons';
|
||||
import { DimensionType } from '../../reducers/interfaces';
|
||||
|
||||
interface Props {
|
||||
menuKey: string;
|
||||
exporters: any[];
|
||||
exportActivities: string[] | null;
|
||||
taskDimension: DimensionType;
|
||||
}
|
||||
|
||||
export default function ExportSubmenu(props: Props): JSX.Element {
|
||||
const {
|
||||
menuKey, exporters, exportActivities, taskDimension,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Menu.SubMenu key={menuKey} title='Export as a dataset'>
|
||||
{exporters
|
||||
.sort((a: any, b: any) => a.name.localeCompare(b.name))
|
||||
.filter((exporter: any): boolean => exporter.dimension === taskDimension)
|
||||
.map(
|
||||
(exporter: any): JSX.Element => {
|
||||
const pending = (exportActivities || []).includes(exporter.name);
|
||||
const disabled = !exporter.enabled || pending;
|
||||
return (
|
||||
<Menu.Item
|
||||
key={exporter.name}
|
||||
disabled={disabled}
|
||||
className='cvat-menu-export-submenu-item'
|
||||
>
|
||||
<ExportOutlined />
|
||||
<Text disabled={disabled}>{exporter.name}</Text>
|
||||
{pending && <LoadingOutlined style={{ marginLeft: 10 }} />}
|
||||
</Menu.Item>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</Menu.SubMenu>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
// Copyright (C) 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import React from 'react';
|
||||
import Image from 'antd/lib/image';
|
||||
import Paragraph from 'antd/lib/typography/Paragraph';
|
||||
import Text from 'antd/lib/typography/Text';
|
||||
|
||||
interface Props {
|
||||
name?: string;
|
||||
gif?: string;
|
||||
message?: string;
|
||||
withNegativePoints?: boolean;
|
||||
}
|
||||
|
||||
function InteractorTooltips(props: Props): JSX.Element {
|
||||
const {
|
||||
name, gif, message, withNegativePoints,
|
||||
} = props;
|
||||
const UNKNOWN_MESSAGE = 'Selected interactor does not have a help message';
|
||||
const desc = message || UNKNOWN_MESSAGE;
|
||||
return (
|
||||
<div className='cvat-interactor-tip-container'>
|
||||
{name ? (
|
||||
<>
|
||||
<Paragraph>{desc}</Paragraph>
|
||||
<Paragraph>
|
||||
<Text>You can prevent server requests holding</Text>
|
||||
<Text strong>{' Ctrl '}</Text>
|
||||
<Text>key</Text>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Text>Positive points can be added by left-clicking the image. </Text>
|
||||
{withNegativePoints ? (
|
||||
<Text>Negative points can be added by right-clicking the image. </Text>
|
||||
) : null}
|
||||
</Paragraph>
|
||||
{gif ? <Image className='cvat-interactor-tip-image' alt='Example gif' src={gif} /> : null}
|
||||
</>
|
||||
) : (
|
||||
<Text>Select an interactor to see help message</Text>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(InteractorTooltips);
|
||||
@ -0,0 +1,150 @@
|
||||
// Copyright (C) 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import './styles.scss';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import Modal from 'antd/lib/modal';
|
||||
import Notification from 'antd/lib/notification';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { DownloadOutlined, LoadingOutlined } from '@ant-design/icons';
|
||||
import Text from 'antd/lib/typography/Text';
|
||||
import Select from 'antd/lib/select';
|
||||
import Checkbox from 'antd/lib/checkbox';
|
||||
import Input from 'antd/lib/input';
|
||||
import Form from 'antd/lib/form';
|
||||
|
||||
import { CombinedState } from 'reducers/interfaces';
|
||||
import { exportActions, exportDatasetAsync } from 'actions/export-actions';
|
||||
import getCore from 'cvat-core-wrapper';
|
||||
|
||||
const core = getCore();
|
||||
|
||||
type FormValues = {
|
||||
selectedFormat: string | undefined;
|
||||
saveImages: boolean;
|
||||
customName: string | undefined;
|
||||
};
|
||||
|
||||
function ExportDatasetModal(): JSX.Element {
|
||||
const [instanceType, setInstanceType] = useState('');
|
||||
const [activities, setActivities] = useState<string[]>([]);
|
||||
const [form] = Form.useForm();
|
||||
const dispatch = useDispatch();
|
||||
const instance = useSelector((state: CombinedState) => state.export.instance);
|
||||
const modalVisible = useSelector((state: CombinedState) => state.export.modalVisible);
|
||||
const dumpers = useSelector((state: CombinedState) => state.formats.annotationFormats.dumpers);
|
||||
const { tasks: taskExportActivities, projects: projectExportActivities } = useSelector(
|
||||
(state: CombinedState) => state.export,
|
||||
);
|
||||
|
||||
const initActivities = (): void => {
|
||||
if (instance instanceof core.classes.Project) {
|
||||
setInstanceType('project');
|
||||
setActivities(projectExportActivities[instance.id] || []);
|
||||
} else if (instance instanceof core.classes.Task) {
|
||||
setInstanceType('task');
|
||||
setActivities(taskExportActivities[instance.id] || []);
|
||||
if (instance.mode === 'interpolation' && instance.dimension === '2d') {
|
||||
form.setFieldsValue({ selectedFormat: 'CVAT for video 1.1' });
|
||||
} else if (instance.mode === 'annotation' && instance.dimension === '2d') {
|
||||
form.setFieldsValue({ selectedFormat: 'CVAT for images 1.1' });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
initActivities();
|
||||
}, [instance?.id, instance instanceof core.classes.Project]);
|
||||
|
||||
const closeModal = (): void => {
|
||||
form.resetFields();
|
||||
dispatch(exportActions.closeExportModal());
|
||||
};
|
||||
|
||||
const handleExport = useCallback(
|
||||
(values: FormValues): void => {
|
||||
// have to validate format before so it would not be undefined
|
||||
dispatch(
|
||||
exportDatasetAsync(
|
||||
instance,
|
||||
values.selectedFormat as string,
|
||||
values.customName ? `${values.customName}.zip` : '',
|
||||
values.saveImages,
|
||||
),
|
||||
);
|
||||
closeModal();
|
||||
Notification.info({
|
||||
message: 'Dataset export started',
|
||||
description:
|
||||
`Dataset export was started for ${instanceType} #${instance?.id}. ` +
|
||||
'Download will start automaticly as soon as the dataset is ready.',
|
||||
className: `cvat-notification-notice-export-${instanceType}-start`,
|
||||
});
|
||||
},
|
||||
[instance?.id, instance instanceof core.classes.Project, instanceType],
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={`Export ${instanceType} #${instance?.id} as a dataset`}
|
||||
visible={modalVisible}
|
||||
onCancel={closeModal}
|
||||
onOk={() => form.submit()}
|
||||
className={`cvat-modal-export-${instanceType}`}
|
||||
>
|
||||
<Form
|
||||
name='Export dataset'
|
||||
form={form}
|
||||
labelCol={{ span: 8 }}
|
||||
wrapperCol={{ span: 16 }}
|
||||
initialValues={
|
||||
{
|
||||
selectedFormat: undefined,
|
||||
saveImages: false,
|
||||
customName: undefined,
|
||||
} as FormValues
|
||||
}
|
||||
onFinish={handleExport}
|
||||
>
|
||||
<Form.Item
|
||||
name='selectedFormat'
|
||||
label='Export format'
|
||||
rules={[{ required: true, message: 'Format must be selected' }]}
|
||||
>
|
||||
<Select placeholder='Select dataset format' className='cvat-modal-export-select'>
|
||||
{dumpers
|
||||
.sort((a: any, b: any) => a.name.localeCompare(b.name))
|
||||
.filter((dumper: any): boolean => dumper.dimension === instance?.dimension)
|
||||
.map(
|
||||
(dumper: any): JSX.Element => {
|
||||
const pending = (activities || []).includes(dumper.name);
|
||||
const disabled = !dumper.enabled || pending;
|
||||
return (
|
||||
<Select.Option
|
||||
value={dumper.name}
|
||||
key={dumper.name}
|
||||
disabled={disabled}
|
||||
className='cvat-modal-export-option-item'
|
||||
>
|
||||
<DownloadOutlined />
|
||||
<Text disabled={disabled}>{dumper.name}</Text>
|
||||
{pending && <LoadingOutlined style={{ marginLeft: 10 }} />}
|
||||
</Select.Option>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item name='saveImages' valuePropName='checked' wrapperCol={{ offset: 8, span: 16 }}>
|
||||
<Checkbox>Save images</Checkbox>
|
||||
</Form.Item>
|
||||
<Form.Item label='Custom name' name='customName'>
|
||||
<Input placeholder='Custom name for a dataset' suffix='.zip' className='cvat-modal-export-filename-input' />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(ExportDatasetModal);
|
||||
@ -0,0 +1,13 @@
|
||||
// Copyright (C) 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
@import '../../base.scss';
|
||||
|
||||
.cvat-modal-export-option-item > .ant-select-item-option-content,
|
||||
.cvat-modal-export-select .ant-select-selection-item {
|
||||
> span[role='img'] {
|
||||
color: $info-icon-color;
|
||||
margin-right: $grid-unit-size;
|
||||
}
|
||||
}
|
||||
@ -1,49 +0,0 @@
|
||||
// Copyright (C) 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import Drawer from 'antd/lib/drawer';
|
||||
import Paragraph from 'antd/lib/typography/Paragraph';
|
||||
import Button from 'antd/lib/button/button';
|
||||
|
||||
import { isPublic } from 'utils/enviroment';
|
||||
|
||||
function CookieDrawer(): JSX.Element {
|
||||
const [drawerVisible, setDrawerVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const cookiePolicyAccepted = localStorage.getItem('cookiePolicyAccepted');
|
||||
if (cookiePolicyAccepted === null && isPublic()) {
|
||||
setDrawerVisible(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onClose = (): void => {
|
||||
localStorage.setItem('cookiePolicyAccepted', 'true');
|
||||
setDrawerVisible(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
title='About Cookies on this site:'
|
||||
placement='bottom'
|
||||
closable={false}
|
||||
visible={drawerVisible}
|
||||
height={200}
|
||||
destroyOnClose
|
||||
>
|
||||
<Paragraph>
|
||||
This site uses cookies for functionality, analytics, and advertising purposes as described in our Cookie
|
||||
and Similar Technologies Notice. To see what cookies we serve and set your preferences, please visit our
|
||||
<a href='https://www.intel.com/cookies'> Cookie Consent Tool</a>. By continuing to use our website, you
|
||||
agree to our use of cookies.
|
||||
</Paragraph>
|
||||
<Button onClick={onClose} size='large' type='primary'>
|
||||
Accept
|
||||
</Button>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
export default CookieDrawer;
|
||||
@ -0,0 +1,27 @@
|
||||
// Copyright (C) 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import React from 'react';
|
||||
import { Layout } from 'antd';
|
||||
|
||||
import { isPublic } from 'utils/enviroment';
|
||||
import consts from 'consts';
|
||||
|
||||
function FooterDrawer(): JSX.Element | null {
|
||||
const { Footer } = Layout;
|
||||
const { INTEL_TERMS_OF_USE_URL, INTEL_COOKIES_URL, INTEL_PRIVACY_URL } = consts;
|
||||
|
||||
return isPublic() ? (
|
||||
<Footer style={{ textAlign: 'center', borderTop: '1px solid #e8e8e8' }}>
|
||||
© Intel Corporation |
|
||||
<a target='_blank' rel='noopener noreferrer' href={INTEL_TERMS_OF_USE_URL}> Terms of Use </a>
|
||||
|
|
||||
<a target='_blank' rel='noopener noreferrer' data-cookie-notice='true' href={INTEL_COOKIES_URL}> Cookies </a>
|
||||
|
|
||||
<a target='_blank' rel='noopener noreferrer' href={INTEL_PRIVACY_URL}> Privacy </a>
|
||||
</Footer>
|
||||
) : null;
|
||||
}
|
||||
|
||||
export default React.memo(FooterDrawer);
|
||||
@ -0,0 +1,67 @@
|
||||
// Copyright (C) 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { ExportActions, ExportActionTypes } from 'actions/export-actions';
|
||||
import getCore from 'cvat-core-wrapper';
|
||||
import deepCopy from 'utils/deep-copy';
|
||||
|
||||
import { ExportState } from './interfaces';
|
||||
|
||||
const core = getCore();
|
||||
|
||||
const defaultState: ExportState = {
|
||||
tasks: {},
|
||||
projects: {},
|
||||
instance: null,
|
||||
modalVisible: false,
|
||||
};
|
||||
|
||||
export default (state: ExportState = defaultState, action: ExportActions): ExportState => {
|
||||
switch (action.type) {
|
||||
case ExportActionTypes.OPEN_EXPORT_MODAL:
|
||||
return {
|
||||
...state,
|
||||
modalVisible: true,
|
||||
instance: action.payload.instance,
|
||||
};
|
||||
case ExportActionTypes.CLOSE_EXPORT_MODAL:
|
||||
return {
|
||||
...state,
|
||||
modalVisible: false,
|
||||
instance: null,
|
||||
};
|
||||
case ExportActionTypes.EXPORT_DATASET: {
|
||||
const { instance, format } = action.payload;
|
||||
const activities = deepCopy(instance instanceof core.classes.Project ? state.projects : state.tasks);
|
||||
|
||||
activities[instance.id] =
|
||||
instance.id in activities && !activities[instance.id].includes(format) ?
|
||||
[...activities[instance.id], format] :
|
||||
activities[instance.id] || [format];
|
||||
|
||||
return {
|
||||
...state,
|
||||
tasks: instance instanceof core.classes.Task ? activities : state.tasks,
|
||||
projects: instance instanceof core.classes.Project ? activities : state.projects,
|
||||
};
|
||||
}
|
||||
case ExportActionTypes.EXPORT_DATASET_FAILED:
|
||||
case ExportActionTypes.EXPORT_DATASET_SUCCESS: {
|
||||
const { instance, format } = action.payload;
|
||||
const activities = deepCopy(instance instanceof core.classes.Project ? state.projects : state.tasks);
|
||||
|
||||
activities[instance.id] = activities[instance.id].filter(
|
||||
(exporterName: string): boolean => exporterName !== format,
|
||||
);
|
||||
|
||||
return {
|
||||
...state,
|
||||
tasks: instance instanceof core.classes.Task ? activities : state.tasks,
|
||||
projects: instance instanceof core.classes.Project ? activities : state.projects,
|
||||
};
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,21 @@
|
||||
// Copyright (C) 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
function deepCopy<T>(obj: T): T {
|
||||
if (typeof obj !== 'object') {
|
||||
return obj;
|
||||
}
|
||||
if (!obj) {
|
||||
return obj;
|
||||
}
|
||||
const container: any = (obj instanceof Array) ? [] : {};
|
||||
for (const i in obj) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, i)) {
|
||||
container[i] = deepCopy(obj[i]);
|
||||
}
|
||||
}
|
||||
return container;
|
||||
}
|
||||
|
||||
export default deepCopy;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue