React & Antd UI: Export dataset, refactoring & fixes (#872)

* Automatic label matching (by the same name) in model running window
* Improved create task window
* Improved upload model window
* Fixed: error window showed twice
* Updated CONTRIBUTING.md
* Removed token before login, fixed dump submenu (adjustment), fixed case when empty models list displayed
* Export as dataset, better error showing system
* Removed extra requests, improved UI
* Fixed a name of a format
* Show inference progress
* Fixed model loading after a model was uploaded
main
Boris Sekachev 6 years ago committed by Nikita Manovich
parent 9cb48ef2c2
commit 911b4e9229

@ -29,6 +29,7 @@ $ python3 -m venv .env
$ . .env/bin/activate
$ pip install -U pip wheel
$ pip install -r cvat/requirements/development.txt
$ pip install -r datumaro/requirements.txt
$ python manage.py migrate
$ python manage.py collectstatic
```

@ -147,6 +147,7 @@
`${encodeURIComponent('password')}=${encodeURIComponent(password)}`,
]).join('&').replace(/%20/g, '+');
Axios.defaults.headers.common.Authorization = '';
let authenticationResponse = null;
try {
authenticationResponse = await Axios.post(
@ -246,7 +247,7 @@
try {
await Axios.delete(`${backendAPI}/tasks/${id}`);
} catch (errorData) {
throw generateError(errorData, 'Could not delete the task from the server');
throw generateError(errorData, `Could not delete the task ${id} from the server`);
}
}

@ -6,38 +6,53 @@ import getCore from '../core';
const cvat = getCore();
export enum FormatsActionTypes {
GETTING_FORMATS_SUCCESS = 'GETTING_FORMATS_SUCCESS',
GETTING_FORMATS_FAILED = 'GETTING_FORMATS_FAILED',
GET_FORMATS = 'GET_FORMATS',
GET_FORMATS_SUCCESS = 'GET_FORMATS_SUCCESS',
GET_FORMATS_FAILED = 'GET_FORMATS_FAILED',
}
export function gettingFormatsSuccess(formats: any): AnyAction {
function getFormats(): AnyAction {
return {
type: FormatsActionTypes.GETTING_FORMATS_SUCCESS,
type: FormatsActionTypes.GET_FORMATS,
payload: {},
};
}
function getFormatsSuccess(
annotationFormats: any[],
datasetFormats: any[],
): AnyAction {
return {
type: FormatsActionTypes.GET_FORMATS_SUCCESS,
payload: {
formats,
annotationFormats,
datasetFormats,
},
};
}
export function gettingFormatsFailed(error: any): AnyAction {
function getFormatsFailed(error: any): AnyAction {
return {
type: FormatsActionTypes.GETTING_FORMATS_FAILED,
type: FormatsActionTypes.GET_FORMATS_FAILED,
payload: {
error,
},
};
}
export function gettingFormatsAsync(): ThunkAction<Promise<void>, {}, {}, AnyAction> {
export function getFormatsAsync(): ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
let formats = null;
dispatch(getFormats());
let annotationFormats = null;
let datasetFormats = null;
try {
formats = await cvat.server.formats();
annotationFormats = await cvat.server.formats();
datasetFormats = await cvat.server.datasetFormats();
} catch (error) {
dispatch(gettingFormatsFailed(error));
dispatch(getFormatsFailed(error));
return;
}
dispatch(gettingFormatsSuccess(formats));
dispatch(getFormatsSuccess(annotationFormats, datasetFormats));
};
}

@ -3,7 +3,12 @@ import { ThunkAction } from 'redux-thunk';
import getCore from '../core';
import { getCVATStore } from '../store';
import { Model, ModelFiles, CombinedState } from '../reducers/interfaces';
import {
Model,
ModelFiles,
ActiveInference,
CombinedState,
} from '../reducers/interfaces';
export enum ModelsActionTypes {
GET_MODELS = 'GET_MODELS',
@ -324,6 +329,199 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
};
}
function getInferenceStatusSuccess(
taskID: number,
activeInference: ActiveInference,
): AnyAction {
const action = {
type: ModelsActionTypes.GET_INFERENCE_STATUS_SUCCESS,
payload: {
taskID,
activeInference,
},
};
return action;
}
function getInferenceStatusFailed(taskID: number, error: any): AnyAction {
const action = {
type: ModelsActionTypes.GET_INFERENCE_STATUS_FAILED,
payload: {
taskID,
error,
},
};
return action;
}
interface InferenceMeta {
active: boolean;
taskID: number;
requestID: string;
}
const timers: any = {};
async function timeoutCallback(
url: string,
taskID: number,
dispatch: ActionCreator<Dispatch>,
): Promise<void> {
try {
delete timers[taskID];
const response = await core.server.request(url, {
method: 'GET',
});
const activeInference: ActiveInference = {
status: response.status,
progress: +response.progress || 0,
error: response.error || response.stderr || '',
};
if (activeInference.status === 'unknown') {
dispatch(getInferenceStatusFailed(
taskID,
new Error(
`Inference status for the task ${taskID} is unknown.`,
),
));
return;
}
if (activeInference.status === 'failed') {
dispatch(getInferenceStatusFailed(
taskID,
new Error(
`Inference status for the task ${taskID} is failed. ${activeInference.error}`,
),
));
return;
}
if (activeInference.status !== 'finished') {
timers[taskID] = setTimeout(
timeoutCallback.bind(
null,
url,
taskID,
dispatch,
), 3000,
);
}
dispatch(getInferenceStatusSuccess(taskID, activeInference));
} catch (error) {
dispatch(getInferenceStatusFailed(taskID, error));
}
}
function subscribe(
urlPath: string,
inferenceMeta: InferenceMeta,
dispatch: ActionCreator<Dispatch>,
): void {
if (!(inferenceMeta.taskID in timers)) {
const requestURL = `${baseURL}/${urlPath}/${inferenceMeta.requestID}`;
timers[inferenceMeta.taskID] = setTimeout(
timeoutCallback.bind(
null,
requestURL,
inferenceMeta.taskID,
dispatch,
),
);
}
}
export function getInferenceStatusAsync(tasks: number[]):
ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
function parse(response: any): InferenceMeta[] {
return Object.keys(response).map((key: string): InferenceMeta => ({
taskID: +key,
requestID: response[key].rq_id || key,
active: typeof (response[key].active) === 'undefined' ? ['queued', 'started']
.includes(response[key].status.toLowerCase()) : response[key].active,
}));
}
const store = getCVATStore();
const state: CombinedState = store.getState();
const OpenVINO = state.plugins.plugins.AUTO_ANNOTATION;
const RCNN = state.plugins.plugins.TF_ANNOTATION;
const MaskRCNN = state.plugins.plugins.TF_SEGMENTATION;
try {
if (OpenVINO) {
const response = await core.server.request(
`${baseURL}/auto_annotation/meta/get`, {
method: 'POST',
data: JSON.stringify(tasks),
headers: {
'Content-Type': 'application/json',
},
},
);
parse(response.run)
.filter((inferenceMeta: InferenceMeta): boolean => inferenceMeta.active)
.forEach((inferenceMeta: InferenceMeta): void => {
subscribe('auto_annotation/check', inferenceMeta, dispatch);
});
}
if (RCNN) {
const response = await core.server.request(
`${baseURL}/tensorflow/annotation/meta/get`, {
method: 'POST',
data: JSON.stringify(tasks),
headers: {
'Content-Type': 'application/json',
},
},
);
parse(response)
.filter((inferenceMeta: InferenceMeta): boolean => inferenceMeta.active)
.forEach((inferenceMeta: InferenceMeta): void => {
subscribe('tensorflow/annotation/check/task', inferenceMeta, dispatch);
});
}
if (MaskRCNN) {
const response = await core.server.request(
`${baseURL}/tensorflow/segmentation/meta/get`, {
method: 'POST',
data: JSON.stringify(tasks),
headers: {
'Content-Type': 'application/json',
},
},
);
parse(response)
.filter((inferenceMeta: InferenceMeta): boolean => inferenceMeta.active)
.forEach((inferenceMeta: InferenceMeta): void => {
subscribe('tensorflow/segmentation/check/task', inferenceMeta, dispatch);
});
}
} catch (error) {
tasks.forEach((task: number): void => {
dispatch(getInferenceStatusFailed(task, error));
});
}
};
}
function inferModel(): AnyAction {
const action = {
type: ModelsActionTypes.INFER_MODEL,
@ -387,6 +585,8 @@ export function inferModelAsync(
},
);
}
dispatch(getInferenceStatusAsync([taskInstance.id]));
} catch (error) {
dispatch(inferModelFailed(error));
return;

@ -0,0 +1,24 @@
import { AnyAction } from 'redux';
export enum NotificationsActionType {
RESET_ERRORS = 'RESET_ERRORS',
RESET_MESSAGES = 'RESET_MESSAGES',
}
export function resetErrors(): AnyAction {
const action = {
type: NotificationsActionType.RESET_ERRORS,
payload: {},
};
return action;
}
export function resetMessages(): AnyAction {
const action = {
type: NotificationsActionType.RESET_MESSAGES,
payload: {},
};
return action;
}

@ -4,6 +4,7 @@ import { SupportedPlugins } from '../reducers/interfaces';
import PluginChecker from '../utils/plugin-checker';
export enum PluginsActionTypes {
CHECK_PLUGINS = 'CHECK_PLUGINS',
CHECKED_ALL_PLUGINS = 'CHECKED_ALL_PLUGINS'
}
@ -11,6 +12,15 @@ interface PluginObjects {
[plugin: string]: boolean;
}
function checkPlugins(): AnyAction {
const action = {
type: PluginsActionTypes.CHECK_PLUGINS,
payload: {},
};
return action;
}
function checkedAllPlugins(plugins: PluginObjects): AnyAction {
const action = {
type: PluginsActionTypes.CHECKED_ALL_PLUGINS,
@ -25,6 +35,7 @@ function checkedAllPlugins(plugins: PluginObjects): AnyAction {
export function checkPluginsAsync():
ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
dispatch(checkPlugins());
const plugins: PluginObjects = {};
const promises: Promise<boolean>[] = [];

@ -1,6 +1,7 @@
import { AnyAction, Dispatch, ActionCreator } from 'redux';
import { ThunkAction } from 'redux-thunk';
import { TasksQuery } from '../reducers/interfaces';
import { getInferenceStatusAsync } from './models-actions';
import getCore from '../core';
@ -16,6 +17,9 @@ export enum TasksActionTypes {
DUMP_ANNOTATIONS = 'DUMP_ANNOTATIONS',
DUMP_ANNOTATIONS_SUCCESS = 'DUMP_ANNOTATIONS_SUCCESS',
DUMP_ANNOTATIONS_FAILED = 'DUMP_ANNOTATIONS_FAILED',
EXPORT_DATASET = 'EXPORT_DATASET',
EXPORT_DATASET_SUCCESS = 'EXPORT_DATASET_SUCCESS',
EXPORT_DATASET_FAILED = 'EXPORT_DATASET_FAILED',
DELETE_TASK = 'DELETE_TASK',
DELETE_TASK_SUCCESS = 'DELETE_TASK_SUCCESS',
DELETE_TASK_FAILED = 'DELETE_TASK_FAILED',
@ -26,6 +30,7 @@ export enum TasksActionTypes {
UPDATE_TASK = 'UPDATE_TASK',
UPDATE_TASK_SUCCESS = 'UPDATE_TASK_SUCCESS',
UPDATE_TASK_FAILED = 'UPDATE_TASK_FAILED',
RESET_ERROR = 'RESET_ERROR',
}
function getTasks(): AnyAction {
@ -89,6 +94,13 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
const previews = [];
const promises = array
.map((task): string => (task as any).frames.preview());
dispatch(
getInferenceStatusAsync(
array.map(
(task: any): number => task.id,
),
),
);
for (const promise of promises) {
try {
@ -150,7 +162,9 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
try {
dispatch(dumpAnnotation(task, dumper));
const url = await task.annotations.dump(task.name, dumper);
window.location.assign(url);
// false positive
// eslint-disable-next-line security/detect-non-literal-fs-filename
window.open(url);
} catch (error) {
dispatch(dumpAnnotationFailed(task, dumper, error));
return;
@ -210,6 +224,61 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
};
}
function exportDataset(task: any, exporter: any): AnyAction {
const action = {
type: TasksActionTypes.EXPORT_DATASET,
payload: {
task,
exporter,
},
};
return action;
}
function exportDatasetSuccess(task: any, exporter: any): AnyAction {
const action = {
type: TasksActionTypes.EXPORT_DATASET_SUCCESS,
payload: {
task,
exporter,
},
};
return action;
}
function exportDatasetFailed(task: any, exporter: any, error: any): AnyAction {
const action = {
type: TasksActionTypes.EXPORT_DATASET_FAILED,
payload: {
task,
exporter,
error,
},
};
return action;
}
export function exportDatasetAsync(task: any, exporter: any):
ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
dispatch(exportDataset(task, exporter));
try {
const url = await task.annotations.exportDataset(exporter.tag);
// false positive
// eslint-disable-next-line security/detect-non-literal-fs-filename
window.open(url, '_blank');
} catch (error) {
dispatch(exportDatasetFailed(task, exporter, error));
}
dispatch(exportDatasetSuccess(task, exporter));
};
}
function deleteTask(taskID: number): AnyAction {
const action = {
type: TasksActionTypes.DELETE_TASK,

@ -14,7 +14,7 @@ export enum UsersActionTypes {
function getUsers(): AnyAction {
const action = {
type: UsersActionTypes.GET_USERS,
payload: { },
payload: {},
};
return action;
@ -41,8 +41,9 @@ function getUsersFailed(error: any): AnyAction {
export function getUsersAsync():
ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
dispatch(getUsers());
try {
dispatch(getUsers());
const users = await core.users.get();
dispatch(
getUsersSuccess(

@ -5,24 +5,27 @@ import {
Modal,
} from 'antd';
import Text from 'antd/lib/typography/Text';
import { ClickParam } from 'antd/lib/menu/index';
import LoaderItemComponent from './loader-item';
import DumperItemComponent from './dumper-item';
import ExportItemComponent from './export-item';
interface ActionsMenuComponentProps {
taskInstance: any;
loaders: any[];
dumpers: any[];
exporters: any[];
loadActivity: string | null;
dumpActivities: string[] | null;
exportActivities: string[] | null;
installedTFAnnotation: boolean;
installedTFSegmentation: boolean;
installedAutoAnnotation: boolean;
inferenceIsActive: boolean;
onLoadAnnotation: (taskInstance: any, loader: any, file: File) => void;
onDumpAnnotation: (taskInstance: any, dumper: any) => void;
onExportDataset: (taskInstance: any, exporter: any) => void;
onDeleteTask: (taskInstance: any) => void;
onOpenRunWindow: (taskInstance: any) => void;
}
@ -66,24 +69,22 @@ export default function ActionsMenuComponent(props: ActionsMenuComponentProps) {
const tracker = props.taskInstance.bugTracker;
const renderModelRunner = props.installedAutoAnnotation ||
props.installedTFAnnotation || props.installedTFSegmentation;
return (
<Menu selectable={false} className='cvat-actions-menu' onClick={
(params: ClickParam) => handleMenuClick(props, params)
}>
<Menu.SubMenu key='dump' title={<
Text>{'Dump annotations'}</Text>
}>
<Menu.SubMenu key='dump' title='Dump annotations'>
{
props.dumpers.map((dumper) => DumperItemComponent({
dumper,
taskInstance: props.taskInstance,
dumpActivities: props.dumpActivities,
dumpActivity: (props.dumpActivities || [])
.filter((_dumper: string) => _dumper === dumper.name)[0] || null,
onDumpAnnotation: props.onDumpAnnotation,
} ))}
</Menu.SubMenu>
<Menu.SubMenu key='load' title={
<Text>{'Upload annotations'}</Text>
}>
<Menu.SubMenu key='load' title='Upload annotations'>
{
props.loaders.map((loader) => LoaderItemComponent({
loader,
@ -93,8 +94,22 @@ export default function ActionsMenuComponent(props: ActionsMenuComponentProps) {
}))
}
</Menu.SubMenu>
<Menu.SubMenu key='export' title='Export as a dataset'>
{
props.exporters.map((exporter) => ExportItemComponent({
exporter,
taskInstance: props.taskInstance,
exportActivity: (props.exportActivities || [])
.filter((_exporter: string) => _exporter === exporter.name)[0] || null,
onExportDataset: props.onExportDataset,
}))
}
</Menu.SubMenu>
{tracker && <Menu.Item key='tracker'>Open bug tracker</Menu.Item>}
{renderModelRunner && <Menu.Item key='auto_annotation'>Automatic annotation</Menu.Item>}
{
renderModelRunner &&
<Menu.Item disabled={props.inferenceIsActive} key='auto_annotation'>Automatic annotation</Menu.Item>
}
<hr/>
<Menu.Item key='delete'>Delete</Menu.Item>
</Menu>

@ -11,7 +11,7 @@ import Text from 'antd/lib/typography/Text';
interface DumperItemComponentProps {
taskInstance: any;
dumper: any;
dumpActivities: string[] | null;
dumpActivity: string | null;
onDumpAnnotation: (task: any, dumper: any) => void;
}
@ -24,11 +24,7 @@ export default function DumperItemComponent(props: DumperItemComponentProps) {
const task = props.taskInstance;
const { mode } = task;
const { dumper } = props;
const dumpingWithThisDumper = (props.dumpActivities || [])
.filter((_dumper: string) => _dumper === dumper.name)[0];
const pending = !!dumpingWithThisDumper;
const pending = !!props.dumpActivity;
return (
<Menu.Item className='cvat-actions-menu-dump-submenu-item' key={dumper.name}>

@ -0,0 +1,38 @@
import React from 'react';
import {
Menu,
Button,
Icon,
} from 'antd';
import Text from 'antd/lib/typography/Text';
interface DumperItemComponentProps {
taskInstance: any;
exporter: any;
exportActivity: string | null;
onExportDataset: (task: any, exporter: any) => void;
}
export default function DumperItemComponent(props: DumperItemComponentProps) {
const task = props.taskInstance;
const { exporter } = props;
const pending = !!props.exportActivity;
return (
<Menu.Item className='cvat-actions-menu-export-submenu-item' key={exporter.name}>
<Button block={true} type='link' disabled={pending}
onClick={() => {
props.onExportDataset(task, exporter);
}}>
<Icon type='export'/>
<Text strong={props.exporter.is_default}>
{exporter.name}
</Text>
{pending && <Icon type='loading'/>}
</Button>
</Menu.Item>
);
}

@ -9,8 +9,11 @@ import {
Tooltip,
Modal,
message,
notification,
} from 'antd';
import Text from 'antd/lib/typography/Text';
import CreateModelForm, {
CreateModelForm as WrappedCreateModelForm
} from './create-model-form';
@ -22,7 +25,6 @@ import { ModelFiles } from '../../reducers/interfaces';
interface Props {
createModel(name: string, files: ModelFiles, global: boolean): void;
isAdmin: boolean;
modelCreatingError: string;
modelCreatingStatus: string;
}
@ -64,17 +66,17 @@ export default class CreateModelContent extends React.PureComponent<Props> {
if (Object.keys(grouppedFiles)
.map((key: string) => grouppedFiles[key])
.filter((val) => !!val).length !== 4) {
Modal.error({
title: 'Could not upload a model',
content: 'Please, specify correct files',
notification.error({
message: 'Could not upload a model',
description: 'Please, specify correct files',
});
} else {
this.props.createModel(data.name, grouppedFiles, data.global);
}
}).catch(() => {
Modal.error({
title: 'Could not upload a model',
content: 'Please, check input fields',
notification.error({
message: 'Could not upload a model',
description: 'Please, check input fields',
});
})
}
@ -86,13 +88,6 @@ export default class CreateModelContent extends React.PureComponent<Props> {
this.modelForm.resetFields();
this.fileManagerContainer.reset();
}
if (!prevProps.modelCreatingError && this.props.modelCreatingError) {
Modal.error({
title: 'Could not create task',
content: this.props.modelCreatingError,
});
}
}
public render() {
@ -106,7 +101,9 @@ export default class CreateModelContent extends React.PureComponent<Props> {
<Row type='flex' justify='start' align='middle' className='cvat-create-model-content'>
<Col span={24}>
<Tooltip overlay='Click to open guide'>
<Icon onClick={() => {window.open(guideLink, '_blank')}} type='question-circle'/>
<Icon onClick={() => {
window.open(guideLink, '_blank')
}} type='question-circle'/>
</Tooltip>
</Col>
<Col span={24}>
@ -116,11 +113,15 @@ export default class CreateModelContent extends React.PureComponent<Props> {
}
/>
</Col>
<Col span={24}>
<Text type='danger'>* </Text>
<Text className='cvat-black-color'>Select files:</Text>
</Col>
<Col span={24}>
<ConnectedFileManager ref={
(container: FileManagerContainer) =>
this.fileManagerContainer = container
}/>
} withRemote={true}/>
</Col>
<Col span={18}>
{status && <Alert message={`${status}`}/>}

@ -43,14 +43,13 @@ export class CreateModelForm extends React.PureComponent<Props> {
return (
<Form onSubmit={(e: React.FormEvent) => e.preventDefault()}>
<Row type='flex'>
<Col>
<Text type='secondary'>Name</Text>
</Col>
</Row>
<Row>
<Col span={24}>
<Text type='danger'>* </Text>
<Text className='cvat-black-color'>Name:</Text>
</Col>
<Col span={14}>
<Form.Item>
<Form.Item hasFeedback>
{ getFieldDecorator('name', {
rules: [{
required: true,
@ -74,9 +73,6 @@ export class CreateModelForm extends React.PureComponent<Props> {
</Form.Item>
</Col>
</Row>
</Form>
);
}

@ -13,7 +13,6 @@ import { ModelFiles } from '../../reducers/interfaces';
interface Props {
createModel(name: string, files: ModelFiles, global: boolean): void;
isAdmin: boolean;
modelCreatingError: string;
modelCreatingStatus: string;
}
@ -21,10 +20,9 @@ export default function CreateModelPageComponent(props: Props) {
return (
<Row type='flex' justify='center' align='top' className='cvat-create-model-form-wrapper'>
<Col md={20} lg={16} xl={14} xxl={9}>
<Text className='cvat-title'>{`Upload a new model`}</Text>
<Text className='cvat-title'>Upload a new model</Text>
<CreateModelContent
isAdmin={props.isAdmin}
modelCreatingError={props.modelCreatingError}
modelCreatingStatus={props.modelCreatingStatus}
createModel={props.createModel}
/>

@ -58,28 +58,25 @@ class AdvancedConfigurationForm extends React.PureComponent<Props> {
private renderZOrder() {
return (
<Form.Item style={{marginBottom: '0px'}}>
<Tooltip overlay='Enable order for shapes. Useful for segmentation tasks'>
{this.props.form.getFieldDecorator('zOrder', {
initialValue: false,
valuePropName: 'checked',
})(
<Checkbox>
<Text className='cvat-black-color'>
Z-order
</Text>
</Checkbox>
)}
</Tooltip>
<Form.Item help='Enables order for shapes. Useful for segmentation tasks'>
{this.props.form.getFieldDecorator('zOrder', {
initialValue: false,
valuePropName: 'checked',
})(
<Checkbox>
<Text className='cvat-black-color'>
Z-order
</Text>
</Checkbox>
)}
</Form.Item>
);
}
private renderImageQuality() {
return (
<Form.Item style={{marginBottom: '0px'}}>
<Form.Item label='Image quality'>
<Tooltip overlay='Defines image compression level'>
<Text className='cvat-black-color'>{'Image quality'}</Text>
{this.props.form.getFieldDecorator('imageQuality', {
initialValue: 70,
rules: [{
@ -102,9 +99,8 @@ class AdvancedConfigurationForm extends React.PureComponent<Props> {
private renderOverlap() {
return (
<Form.Item style={{marginBottom: '0px'}}>
<Form.Item label='Overlap size'>
<Tooltip overlay='Defines a number of intersected frames between different segments'>
<Text className='cvat-black-color'>{'Overlap size'}</Text>
{this.props.form.getFieldDecorator('overlapSize')(
<Input size='large' type='number'/>
)}
@ -115,9 +111,8 @@ class AdvancedConfigurationForm extends React.PureComponent<Props> {
private renderSegmentSize() {
return (
<Form.Item style={{marginBottom: '0px'}}>
<Form.Item label='Segment size'>
<Tooltip overlay='Defines a number of frames in a segment'>
<Text className='cvat-black-color'>{'Segment size'}</Text>
{this.props.form.getFieldDecorator('segmentSize')(
<Input size='large' type='number'/>
)}
@ -128,8 +123,7 @@ class AdvancedConfigurationForm extends React.PureComponent<Props> {
private renderStartFrame() {
return (
<Form.Item style={{marginBottom: '0px'}}>
<Text className='cvat-black-color'>{'Start frame'}</Text>
<Form.Item label='Start frame'>
{this.props.form.getFieldDecorator('startFrame')(
<Input
size='large'
@ -144,8 +138,7 @@ class AdvancedConfigurationForm extends React.PureComponent<Props> {
private renderStopFrame() {
return (
<Form.Item style={{marginBottom: '0px'}}>
<Text className='cvat-black-color'>{'Stop frame'}</Text>
<Form.Item label='Stop frame'>
{this.props.form.getFieldDecorator('stopFrame')(
<Input
size='large'
@ -160,8 +153,7 @@ class AdvancedConfigurationForm extends React.PureComponent<Props> {
private renderFrameStep() {
return (
<Form.Item style={{marginBottom: '0px'}}>
<Text className='cvat-black-color'>{'Frame step'}</Text>
<Form.Item label='Frame step'>
{this.props.form.getFieldDecorator('frameStep')(
<Input
size='large'
@ -176,32 +168,34 @@ class AdvancedConfigurationForm extends React.PureComponent<Props> {
private renderGitLFSBox() {
return (
<Form.Item style={{marginBottom: '0px'}}>
<Tooltip overlay='If annotation files are large, you can use git LFS feature'>
{this.props.form.getFieldDecorator('lfs', {
valuePropName: 'checked',
initialValue: false,
})(
<Checkbox>
<Text className='cvat-black-color'>
Use LFS (Large File Support)
</Text>
</Checkbox>
)}
</Tooltip>
<Form.Item help='If annotation files are large, you can use git LFS feature'>
{this.props.form.getFieldDecorator('lfs', {
valuePropName: 'checked',
initialValue: false,
})(
<Checkbox>
<Text className='cvat-black-color'>
Use LFS (Large File Support):
</Text>
</Checkbox>
)}
</Form.Item>
);
}
private renderGitRepositoryURL() {
return (
<Form.Item style={{marginBottom: '0px'}}>
<Tooltip overlay={`Attach a git repository to store annotations.
Path is specified in square brackets`}>
<Text className='cvat-black-color'>{'Dataset repository URL'}</Text>
{this.props.form.getFieldDecorator('repository', {
rules: [{
validator: (_, value, callback) => {
<Form.Item
hasFeedback
label='Dataset repository URL'
extra='Attach a repository to store annotations there'
>
{this.props.form.getFieldDecorator('repository', {
rules: [{
validator: (_, value, callback) => {
if (!value) {
callback();
} else {
const [url, path] = value.split(/\s+/);
if (!patterns.validateURL.pattern.test(url)) {
callback('Git URL is not a valid');
@ -213,14 +207,13 @@ class AdvancedConfigurationForm extends React.PureComponent<Props> {
callback();
}
}]
})(
<Input
placeholder='e.g. https//github.com/user/repos [annotation/<anno_file_name>.zip]'
size='large'
/>
)}
</Tooltip>
}
}]
})(
<Input size='large'
placeholder='e.g. https//github.com/user/repos [annotation/<anno_file_name>.zip]'
/>
)}
</Form.Item>
);
}
@ -231,7 +224,6 @@ class AdvancedConfigurationForm extends React.PureComponent<Props> {
<Row>
<Col>
{this.renderGitRepositoryURL()}
</Col>
</Row>
<Row>
@ -245,19 +237,24 @@ class AdvancedConfigurationForm extends React.PureComponent<Props> {
private renderBugTracker() {
return (
<Form.Item style={{marginBottom: '0px'}}>
<Tooltip overlay='Attach issue tracker where the task is described'>
<Text className='cvat-black-color'>{'Issue tracker'}</Text>
{this.props.form.getFieldDecorator('bugTracker', {
rules: [{
...patterns.validateURL,
}]
})(
<Input
size='large'
/>
)}
</Tooltip>
<Form.Item
hasFeedback
label='Issue tracker'
extra='Attach issue tracker where the task is described'
>
{this.props.form.getFieldDecorator('bugTracker', {
rules: [{
validator: (_, value, callback) => {
if (value && !patterns.validateURL.pattern.test(value)) {
callback('Issue tracker must be URL');
} else {
callback();
}
}
}]
})(
<Input size='large'/>
)}
</Form.Item>
)
}

@ -39,8 +39,7 @@ class BasicConfigurationForm extends React.PureComponent<Props> {
const { getFieldDecorator } = this.props.form;
return (
<Form onSubmit={(e: React.FormEvent) => e.preventDefault()}>
<Text type='secondary'>Name</Text>
<Form.Item style={{marginBottom: '0px'}}>
<Form.Item hasFeedback label='Name'>
{ getFieldDecorator('name', {
rules: [{
required: true,

@ -4,10 +4,9 @@ import {
Row,
Col,
Alert,
Modal,
Button,
Collapse,
message,
notification,
} from 'antd';
import Text from 'antd/lib/typography/Text';
@ -28,7 +27,6 @@ export interface CreateTaskData {
interface Props {
onCreate: (data: CreateTaskData) => void;
status: string;
error: string;
installedGit: boolean;
}
@ -90,17 +88,17 @@ export default class CreateTaskContent extends React.PureComponent<Props, State>
private handleSubmitClick = () => {
if (!this.validateLabels()) {
Modal.error({
title: 'Could not create a task',
content: 'A task must contain at least one label',
notification.error({
message: 'Could not create a task',
description: 'A task must contain at least one label',
});
return;
}
if (!this.validateFiles()) {
Modal.error({
title: 'Could not create a task',
content: 'A task must contain at least one file',
notification.error({
message: 'Could not create a task',
description: 'A task must contain at least one file',
});
return;
}
@ -117,9 +115,9 @@ export default class CreateTaskContent extends React.PureComponent<Props, State>
this.props.onCreate(this.state);
})
.catch((_: any) => {
Modal.error({
title: 'Could not create a task',
content: 'Please, check configuration you specified',
notification.error({
message: 'Could not create a task',
description: 'Please, check configuration you specified',
});
});
}
@ -137,7 +135,8 @@ export default class CreateTaskContent extends React.PureComponent<Props, State>
private renderLabelsBlock() {
return (
<Col span={24}>
<Text type='secondary'>Labels</Text>
<Text type='danger'>* </Text>
<Text className='cvat-black-color'>Labels:</Text>
<LabelsEditor
labels={this.state.labels}
onSubmit={
@ -155,6 +154,8 @@ export default class CreateTaskContent extends React.PureComponent<Props, State>
private renderFilesBlock() {
return (
<Col span={24}>
<Text type='danger'>* </Text>
<Text className='cvat-black-color'>Select files:</Text>
<FileManagerContainer ref={
(container: any) =>
this.fileManagerContainer = container
@ -169,7 +170,7 @@ export default class CreateTaskContent extends React.PureComponent<Props, State>
<Collapse>
<Collapse.Panel
header={
<Text className='cvat-title'>{'Advanced configuration'}</Text>
<Text className='cvat-title'>Advanced configuration</Text>
} key='1'>
<AdvancedConfigurationForm
installedGit={this.props.installedGit}
@ -187,15 +188,10 @@ export default class CreateTaskContent extends React.PureComponent<Props, State>
}
public componentDidUpdate(prevProps: Props) {
if (this.props.error && prevProps.error !== this.props.error) {
Modal.error({
title: 'Could not create task',
content: this.props.error,
});
}
if (this.props.status === 'CREATED' && prevProps.status !== 'CREATED') {
message.success('The task has been created');
notification.info({
message: 'The task has been created',
});
this.basicConfigurationComponent.resetFields();
if (this.advancedConfigurationComponent) {
@ -213,12 +209,12 @@ export default class CreateTaskContent extends React.PureComponent<Props, State>
public render() {
const loading = !!this.props.status
&& this.props.status !== 'CREATED'
&& !this.props.error;
&& this.props.status !== 'FAILED';
return (
<Row type='flex' justify='start' align='middle' className='cvat-create-task-content'>
<Col span={24}>
<Text className='cvat-title'>{'Basic configuration'}</Text>
<Text className='cvat-title'>Basic configuration</Text>
</Col>
{ this.renderBasicBlock() }

@ -11,7 +11,6 @@ import CreateTaskContent, { CreateTaskData } from './create-task-content';
interface Props {
onCreate: (data: CreateTaskData) => void;
error: string;
status: string;
installedGit: boolean;
}
@ -20,10 +19,9 @@ export default function CreateTaskPage(props: Props) {
return (
<Row type='flex' justify='center' align='top' className='cvat-create-task-form-wrapper'>
<Col md={20} lg={16} xl={14} xxl={9}>
<Text className='cvat-title'>{'Create a new task'}</Text>
<Text className='cvat-title'>Create a new task</Text>
<CreateTaskContent
status={props.status}
error={props.error}
onCreate={props.onCreate}
installedGit={props.installedGit}
/>

@ -1,12 +1,17 @@
import React from 'react';
import { Switch, Route, Redirect } from 'react-router';
import { BrowserRouter } from 'react-router-dom';
import { Spin, Layout, Modal } from 'antd';
import 'antd/dist/antd.css';
import '../stylesheet.css';
import { BrowserRouter } from 'react-router-dom';
import {
Switch,
Route,
Redirect,
} from 'react-router';
import {
Spin,
Layout,
notification,
} from 'antd';
import TasksPageContainer from '../containers/tasks-page/tasks-page';
import CreateTaskPageContainer from '../containers/create-task-page/create-task-page';
@ -20,22 +25,26 @@ import HeaderContainer from '../containers/header/header';
import ModelRunnerModalContainer from '../containers/model-runner-dialog/model-runner-dialog';
import FeedbackComponent from './feedback';
import { NotificationsState } from '../reducers/interfaces';
type CVATAppProps = {
loadFormats: () => void;
loadUsers: () => void;
verifyAuthorized: () => void;
initPlugins: () => void;
pluginsInitialized: boolean;
resetErrors: () => void;
resetMessages: () => void;
userInitialized: boolean;
pluginsInitialized: boolean;
pluginsFetching: boolean;
formatsInitialized: boolean;
formatsFetching: boolean;
usersInitialized: boolean;
gettingAuthError: string;
gettingFormatsError: string;
gettingUsersError: string;
usersFetching: boolean;
installedAutoAnnotation: boolean;
installedTFAnnotation: boolean;
installedTFSegmentation: boolean;
notifications: NotificationsState;
user: any;
}
@ -44,54 +53,141 @@ export default class CVATApplication extends React.PureComponent<CVATAppProps> {
super(props);
}
public componentDidMount() {
this.props.verifyAuthorized();
}
private showMessages() {
function showMessage(title: string) {
notification.info({
message: title,
duration: null,
});
}
public componentDidUpdate() {
if (!this.props.userInitialized ||
this.props.userInitialized && this.props.user == null) {
return;
const { tasks } = this.props.notifications.messages;
const { models } = this.props.notifications.messages;
let shown = !!tasks.loadingDone || !!models.inferenceDone;
if (tasks.loadingDone) {
showMessage(tasks.loadingDone);
}
if (models.inferenceDone) {
showMessage(models.inferenceDone);
}
if (shown) {
this.props.resetMessages();
}
}
if (this.props.gettingAuthError) {
Modal.error({
title: 'Could not check authorization',
content: `${this.props.gettingAuthError}`,
private showErrors() {
function showError(title: string, _error: any) {
const error = _error.toString();
notification.error({
message: title,
duration: null,
description: error.length > 200 ? '' : error,
});
return;
console.error(error);
}
if (!this.props.formatsInitialized) {
this.props.loadFormats();
return;
const { auth } = this.props.notifications.errors;
const { tasks } = this.props.notifications.errors;
const { formats } = this.props.notifications.errors;
const { users } = this.props.notifications.errors;
const { share } = this.props.notifications.errors;
const { models } = this.props.notifications.errors;
let shown = !!auth.authorized || !!auth.login || !!auth.logout || !!auth.register
|| !!tasks.fetching || !!tasks.updating || !!tasks.dumping || !!tasks.loading
|| !!tasks.exporting || !!tasks.deleting || !!tasks.creating || !!formats.fetching
|| !!users.fetching || !!share.fetching || !!models.creating || !!models.starting
|| !!models.fetching || !!models.deleting || !!models.inferenceStatusFetching;
if (auth.authorized) {
showError('Could not check authorization on the server', auth.authorized);
}
if (auth.login) {
showError('Could not login on the server', auth.login);
}
if (auth.register) {
showError('Could not register on the server', auth.register);
}
if (auth.logout) {
showError('Could not logout on the server', auth.logout);
}
if (tasks.fetching) {
showError('Could not fetch tasks from the server', tasks.fetching);
}
if (tasks.updating) {
showError('Could not update task on the server', tasks.updating);
}
if (tasks.dumping) {
showError('Could not dump annotations from the server', tasks.dumping);
}
if (tasks.loading) {
showError('Could not upload annotations to the server', tasks.loading);
}
if (tasks.exporting) {
showError('Could not export task from the server', tasks.exporting);
}
if (tasks.deleting) {
showError('Could not delete task on the server', tasks.deleting);
}
if (tasks.creating) {
showError('Could not create task on the server', tasks.creating);
}
if (formats.fetching) {
showError('Could not get annotations and dataset formats from the server', formats.fetching);
}
if (users.fetching) {
showError('Could not get users from the server', users.fetching);
}
if (share.fetching) {
showError('Could not get share info from the server', share.fetching);
}
if (models.creating) {
showError('Could not create model on the server', models.creating);
}
if (models.starting) {
showError('Could not run model on the server', models.starting);
}
if (models.fetching) {
showError('Could not get models from the server', models.fetching);
}
if (models.deleting) {
showError('Could not delete model from the server', models.deleting);
}
if (models.inferenceStatusFetching) {
showError('Could not fetch inference status from the server', models.inferenceStatusFetching);
}
if (this.props.gettingFormatsError) {
Modal.error({
title: 'Could not receive annotations formats',
content: `${this.props.gettingFormatsError}`,
});
return;
if (shown) {
this.props.resetErrors();
}
}
if (!this.props.usersInitialized) {
this.props.loadUsers();
public componentDidMount() {
this.props.verifyAuthorized();
}
public componentDidUpdate() {
this.showErrors();
this.showMessages();
if (!this.props.userInitialized || this.props.user == null) {
// not authorized user
return;
}
if (this.props.gettingUsersError) {
Modal.error({
title: 'Could not receive users',
content: `${this.props.gettingUsersError}`,
});
if (!this.props.formatsInitialized && !this.props.formatsFetching) {
this.props.loadFormats();
}
return;
if (!this.props.usersInitialized && !this.props.usersFetching) {
this.props.loadUsers();
}
if (!this.props.pluginsInitialized) {
if (!this.props.pluginsInitialized && !this.props.pluginsFetching) {
this.props.initPlugins();
return;
}
}

@ -98,7 +98,7 @@ export default class Feedback extends React.PureComponent<{}, State> {
<Popover
placement='leftTop'
title={
<Text className='cvat-title'>Help to make CVAT better</Text>
<Text className='cvat-black-color'>Help to make CVAT better</Text>
}
content={this.renderContent()}
visible={this.state.active}

@ -81,13 +81,13 @@ export default class FileManager extends React.PureComponent<Props, State> {
Support for a bulk images or a single video
</p>
</Upload.Dragger>
{ this.state.files.local.length ?
{ !!this.state.files.local.length &&
<>
<br/>
<Text className='cvat-black-color'>
{this.state.files.local.length} file(s) selected
{`${this.state.files.local.length} file(s) selected`}
</Text>
</> : null
</>
}
</Tabs.TabPane>
);
@ -184,13 +184,12 @@ export default class FileManager extends React.PureComponent<Props, State> {
public render() {
return (
<>
<Text type='secondary'>{'Select files'}</Text>
<Tabs type='card' tabBarGutter={5} onChange={(activeKey: string) => this.setState({
active: activeKey as any,
})}>
{ this.renderLocalSelector() }
{ this.renderShareSelector() }
{ this.props.withRemote ? this.renderRemoteSelector() : null }
{ this.props.withRemote && this.renderRemoteSelector() }
</Tabs>
</>
);

@ -8,7 +8,6 @@ import {
Icon,
Button,
Menu,
Modal,
} from 'antd';
import Text from 'antd/lib/typography/Text';
@ -24,89 +23,70 @@ interface HeaderContainerProps {
installedTFAnnotation: boolean;
installedTFSegmentation: boolean;
username: string;
logoutError: string;
}
type Props = HeaderContainerProps & RouteComponentProps;
class HeaderContainer extends React.PureComponent<Props> {
private cvatLogo: React.FunctionComponent;
private userLogo: React.FunctionComponent;
const cvatLogo = () => <img src='/assets/cvat-logo.svg'/>;
const userLogo = () => <img src='/assets/icon-account.svg'/>;
public constructor(props: Props) {
super(props);
this.cvatLogo = () => <img src='/assets/cvat-logo.svg'/>;
this.userLogo = () => <img src='/assets/icon-account.svg'/>;
}
function HeaderContainer(props: Props) {
const renderModels = props.installedAutoAnnotation
|| props.installedTFAnnotation
|| props.installedTFSegmentation;
return (
<Layout.Header className='cvat-header'>
<div className='cvat-left-header'>
<Icon className='cvat-logo-icon' component={cvatLogo}/>
public componentDidUpdate(prevProps: Props) {
if (!prevProps.logoutError && this.props.logoutError) {
Modal.error({
title: 'Could not logout',
content: `${this.props.logoutError}`,
});
}
}
public render() {
const { props } = this;
const renderModels = props.installedAutoAnnotation
|| props.installedTFAnnotation
|| props.installedTFSegmentation;
return (
<Layout.Header className='cvat-header'>
<div className='cvat-left-header'>
<Icon className='cvat-logo-icon' component={this.cvatLogo}/>
<Button className='cvat-header-button' type='link' value='tasks' onClick={
() => props.history.push('/tasks')
}> Tasks </Button>
{ renderModels ?
<Button className='cvat-header-button' type='link' value='models' onClick={
() => props.history.push('/models')
}> Models </Button> : null
}
{ props.installedAnalytics ?
<Button className='cvat-header-button' type='link' onClick={
() => {
const serverHost = core.config.backendAPI.slice(0, -7);
window.open(`${serverHost}/analytics/app/kibana`, '_blank');
}
}> Analytics </Button> : null
}
</div>
<div className='cvat-right-header'>
<Button className='cvat-header-button' type='link' value='tasks' onClick={
() => props.history.push('/tasks')
}> Tasks </Button>
{ renderModels ?
<Button className='cvat-header-button' type='link' value='models' onClick={
() => props.history.push('/models')
}> Models </Button> : null
}
{ props.installedAnalytics ?
<Button className='cvat-header-button' type='link' onClick={
() => window.open('https://github.com/opencv/cvat', '_blank')
}>
<Icon type='github'/>
<Text className='cvat-black-color'>GitHub</Text>
</Button>
<Button className='cvat-header-button' type='link' onClick={
() => {
const serverHost = core.config.backendAPI.slice(0, -7);
window.open(`${serverHost}/documentation/user_guide.html`, '_blank')
}
}> <Icon type='question-circle'/> Help </Button>
<Menu className='cvat-header-menu' subMenuCloseDelay={0.1} mode='horizontal'>
<Menu.SubMenu title={
() => {
const serverHost = core.config.backendAPI.slice(0, -7);
window.open(`${serverHost}/analytics/app/kibana`, '_blank');
}
}> Analytics </Button> : null
}
</div>
<div className='cvat-right-header'>
<Button className='cvat-header-button' type='link' onClick={
() => window.open('https://github.com/opencv/cvat', '_blank')
}>
<Icon type='github'/>
<Text className='cvat-black-color'>GitHub</Text>
</Button>
<Button className='cvat-header-button' type='link' onClick={
() => {
const serverHost = core.config.backendAPI.slice(0, -7);
window.open(`${serverHost}/documentation/user_guide.html`, '_blank')
}
}> <Icon type='question-circle'/> Help </Button>
<Menu className='cvat-header-menu' subMenuCloseDelay={0.1} mode='horizontal'>
<Menu.SubMenu title={
<span>
<Icon className='cvat-header-user-icon' component={userLogo} />
<span>
<Icon className='cvat-header-user-icon' component={this.userLogo} />
<span>
<Text strong>
{props.username.length > 14 ? `${props.username.slice(0, 10)} ...` : props.username}
</Text>
<Icon className='cvat-header-menu-icon' type='caret-down'/>
</span>
<Text strong>
{props.username.length > 14 ? `${props.username.slice(0, 10)} ...` : props.username}
</Text>
<Icon className='cvat-header-menu-icon' type='caret-down'/>
</span>
}>
<Menu.Item onClick={props.onLogout}>Logout</Menu.Item>
</Menu.SubMenu>
</Menu>
</div>
</Layout.Header>
);
}
</span>
}>
<Menu.Item onClick={props.onLogout}>Logout</Menu.Item>
</Menu.SubMenu>
</Menu>
</div>
</Layout.Header>
);
}
export default withRouter(HeaderContainer);

@ -3,7 +3,7 @@ import React from 'react';
import {
Tabs,
Icon,
Modal,
notification,
} from 'antd';
import Text from 'antd/lib/typography/Text';
@ -128,9 +128,9 @@ export default class LabelsEditor
private handleDelete = (label: Label) => {
// the label is saved on the server, cannot delete it
if (typeof(label.id) !== 'undefined' && label.id >= 0) {
Modal.error({
title: 'Could not delete the label',
content: 'It has been already saved on the server',
notification.error({
message: 'Could not delete the label',
description: 'It has been already saved on the server',
});
}

@ -8,13 +8,11 @@ import Text from 'antd/lib/typography/Text';
import {
Col,
Row,
Modal,
} from 'antd';
import LoginForm, { LoginData } from './login-form';
interface LoginPageComponentProps {
loginError: string;
onLogin: (username: string, password: string) => void;
}
@ -27,13 +25,6 @@ function LoginPageComponent(props: LoginPageComponentProps & RouteComponentProps
xl: { span: 4 },
}
if (props.loginError) {
Modal.error({
title: 'Could not login',
content: props.loginError,
});
}
return (
<Row type='flex' justify='center' align='middle'>
<Col {...sizes}>

@ -14,23 +14,23 @@ import {
import { Model } from '../../reducers/interfaces';
interface StringObject {
[index: string]: string;
}
interface Props {
modelsFetching: boolean;
modelsInitialized: boolean;
models: Model[];
activeProcesses: {
[index: string]: string;
};
activeProcesses: StringObject;
visible: boolean;
taskInstance: any;
startingError: string;
getModels(): void;
closeDialog(): void;
runInference(
taskInstance: any,
model: Model,
mapping: {
[index: string]: string
},
mapping: StringObject,
cleanOut: boolean,
): void;
}
@ -38,12 +38,8 @@ interface Props {
interface State {
selectedModel: string | null;
cleanOut: boolean;
mapping: {
[index: string]: string;
};
colors: {
[index: string]: string;
};
mapping: StringObject;
colors: StringObject;
matching: {
model: string,
task: string,
@ -277,7 +273,11 @@ export default class ModelRunnerModalComponent extends React.PureComponent<Props
);
}
public componentDidUpdate(prevProps: Props) {
public componentDidUpdate(prevProps: Props, prevState: State) {
if (!this.props.modelsInitialized && !this.props.modelsFetching) {
this.props.getModels();
}
if (!prevProps.visible && this.props.visible) {
this.setState({
selectedModel: null,
@ -290,17 +290,26 @@ export default class ModelRunnerModalComponent extends React.PureComponent<Props
});
}
if (!prevProps.startingError && this.props.startingError) {
Modal.error({
title: 'Could not start model inference',
content: this.props.startingError,
});
}
}
if (this.state.selectedModel && prevState.selectedModel !== this.state.selectedModel) {
const model = this.props.models
.filter((model) => model.name === this.state.selectedModel)[0];
if (!model.primary) {
let taskLabels: string[] = this.props.taskInstance.labels
.map((label: any) => label.name);
const defaultMapping: StringObject = model.labels
.reduce((acc: StringObject, label) => {
if (taskLabels.includes(label)) {
acc[label] = label;
taskLabels = taskLabels.filter((_label) => _label !== label)
}
public componentDidMount() {
if (!this.props.modelsInitialized) {
this.props.getModels();
return acc;
}, {});
this.setState({
mapping: defaultMapping,
});
}
}
}

@ -14,8 +14,8 @@ interface Props {
installedAutoAnnotation: boolean;
installedTFSegmentation: boolean;
installedTFAnnotation: boolean;
modelsAreBeingFetched: boolean;
modelsFetchingError: any;
modelsInitialized: boolean;
modelsFetching: boolean;
registeredUsers: any[];
models: Model[];
getModels(): void;
@ -23,7 +23,7 @@ interface Props {
}
export default function ModelsPageComponent(props: Props) {
if (props.modelsAreBeingFetched) {
if (!props.modelsInitialized && !props.modelsFetching) {
props.getModels();
return (
<Spin size='large' style={{margin: '25% 45%'}}/>
@ -35,15 +35,18 @@ export default function ModelsPageComponent(props: Props) {
return (
<div className='cvat-models-page'>
<TopBarComponent installedAutoAnnotation={props.installedAutoAnnotation}/>
{ integratedModels.length ?
<BuiltModelsList models={integratedModels}/> : null }
{ uploadedModels.length &&
{ !!integratedModels.length &&
<BuiltModelsList models={integratedModels}/>
}
{ !!uploadedModels.length &&
<UploadedModelsList
registeredUsers={props.registeredUsers}
models={uploadedModels}
deleteModel={props.deleteModel}
/>
} { props.installedAutoAnnotation &&
}
{ props.installedAutoAnnotation &&
!uploadedModels.length &&
!props.installedTFAnnotation &&
!props.installedTFSegmentation &&
<EmptyListComponent/>

@ -7,7 +7,6 @@ import {
Select,
Menu,
Dropdown,
Button,
Icon,
} from 'antd';
@ -62,7 +61,7 @@ export default function UploadedModelItem(props: Props) {
<Col span={2}>
<Text className='cvat-black-color'>Actions</Text>
<Dropdown overlay={
<Menu subMenuCloseDelay={0.15} className='cvat-task-item-menu'>
<Menu className='cvat-task-item-menu'>
<Menu.Item onClick={() => {
props.onDelete();
}}key='delete'>Delete</Menu.Item>

@ -14,7 +14,6 @@ import {
import RegisterForm, { RegisterData } from '../../components/register-page/register-form';
interface RegisterPageComponentProps {
registerError: string;
onRegister: (username: string, firstName: string,
lastName: string, email: string,
password1: string, password2: string) => void;
@ -29,13 +28,6 @@ function RegisterPageComponent(props: RegisterPageComponentProps & RouteComponen
xl: { span: 4 },
}
if (props.registerError) {
Modal.error({
title: 'Could not register',
content: props.registerError,
});
}
return (
<Row type='flex' justify='center' align='middle'>
<Col {...sizes}>

@ -7,6 +7,7 @@ import {
Icon,
Modal,
Button,
notification,
} from 'antd';
import Text from 'antd/lib/typography/Text';
@ -209,12 +210,19 @@ export default class DetailsComponent extends React.PureComponent<Props, State>
const { taskInstance } = this.props;
const { bugTracker } = this.state;
let shown = false;
const onChangeValue = (value: string) => {
if (value && !patterns.validateURL.pattern.test(value)) {
Modal.error({
title: `Could not update the task ${taskInstance.id}`,
content: 'Issue tracker is expected to be URL',
});
if (!shown) {
Modal.error({
title: `Could not update the task ${taskInstance.id}`,
content: 'Issue tracker is expected to be URL',
onOk: (() => {
shown = false;
}),
});
shown = true;
}
} else {
this.setState({
bugTracker: value,
@ -280,9 +288,9 @@ export default class DetailsComponent extends React.PureComponent<Props, State>
.then((data) => {
if (data !== null && this.mounted) {
if (data.status.error) {
Modal.error({
title: 'Could not receive repository status',
content: data.status.error
notification.error({
message: 'Could not receive repository status',
description: data.status.error
});
} else {
this.setState({
@ -296,9 +304,9 @@ export default class DetailsComponent extends React.PureComponent<Props, State>
}
}).catch((error) => {
if (this.mounted) {
Modal.error({
title: 'Could not receive repository status',
content: error.toString(),
notification.error({
message: 'Could not receive repository status',
description: error.toString(),
});
}
});

@ -6,7 +6,7 @@ import {
Col,
Row,
Spin,
Modal,
notification,
} from 'antd';
import TopBarComponent from './top-bar';
@ -15,10 +15,8 @@ import JobListContainer from '../../containers/task-page/job-list';
import { Task } from '../../reducers/interfaces';
interface TaskPageComponentProps {
task: Task;
taskFetchingError: string;
taskUpdatingError: string;
taskDeletingError: string;
task: Task | null;
fetching: boolean;
deleteActivity: boolean | null;
installedGit: boolean;
onFetchTask: (tid: number) => void;
@ -27,55 +25,48 @@ interface TaskPageComponentProps {
type Props = TaskPageComponentProps & RouteComponentProps<{id: string}>;
class TaskPageComponent extends React.PureComponent<Props> {
private attempts: number = 0;
public componentDidUpdate() {
if (this.props.deleteActivity) {
this.props.history.replace('/tasks');
}
const { id } = this.props.match.params;
if (this.props.taskFetchingError) {
Modal.error({
title: `Could not receive the task ${id}`,
content: this.props.taskFetchingError,
});
}
if (this.props.taskUpdatingError) {
Modal.error({
title: `Could not update the task ${id}`,
content: this.props.taskUpdatingError,
});
}
if (this.props.taskDeletingError) {
Modal.error({
title: `Could not delete the task ${id}`,
content: this.props.taskDeletingError,
if (this.attempts > 1) {
notification.warning({
message: 'Something wrong with the task. It cannot be fetched from the server',
});
}
}
public render() {
const { id } = this.props.match.params;
const fetchTask = !this.props.task && !this.props.taskFetchingError;
const fetchTask = !this.props.task;
if (fetchTask) {
this.props.onFetchTask(+id);
if (!this.props.fetching) {
if (!this.attempts) {
this.attempts ++;
this.props.onFetchTask(+id);
} else {
this.attempts ++;
}
}
return (
<Spin size='large' style={{margin: '25% 50%'}}/>
);
} else if (this.props.taskFetchingError) {
} else if (typeof(this.props.task) === 'undefined') {
return (
<div> </div>
)
} else {
const task = this.props.task as Task;
return (
<Row type='flex' justify='center' align='top' className='cvat-task-details-wrapper'>
<Col md={22} lg={18} xl={16} xxl={14}>
<TopBarComponent taskInstance={this.props.task.instance}/>
<DetailsContainer task={this.props.task}/>
<JobListContainer task={this.props.task}/>
<TopBarComponent taskInstance={task.instance}/>
<DetailsContainer task={task}/>
<JobListContainer task={task}/>
</Col>
</Row>
);

@ -15,11 +15,13 @@ import {
import moment from 'moment';
import ActionsMenuContainer from '../../containers/actions-menu/actions-menu';
import { ActiveInference } from '../../reducers/interfaces';
export interface TaskItemProps {
taskInstance: any;
previewImage: string;
deleted: boolean;
activeInference: ActiveInference | null;
}
class TaskItemComponent extends React.PureComponent<TaskItemProps & RouteComponentProps> {
@ -94,7 +96,8 @@ class TaskItemComponent extends React.PureComponent<TaskItemProps & RouteCompone
</Col>
</Row>
<Row>
<Progress
<Col>
<Progress
className={`${progressColor} cvat-task-progress`}
percent={numOfCompleted * 100 / numOfJobs}
strokeColor='#1890FF'
@ -102,7 +105,31 @@ class TaskItemComponent extends React.PureComponent<TaskItemProps & RouteCompone
strokeWidth={5}
size='small'
/>
</Col>
</Row>
{ this.props.activeInference ?
<>
<Row>
<Col>
<Text strong>Automatic annotation</Text>
</Col>
</Row>
<Row>
<Col>
<Progress
percent={Math.floor(this.props.activeInference.progress)}
strokeColor={{
from: '#108ee9',
to: '#87d068',
}}
showInfo={false}
strokeWidth={5}
size='small'
/>
</Col>
</Row>
</> : null
}
</Col>
)
}

@ -4,7 +4,6 @@ import { withRouter } from 'react-router-dom';
import {
Spin,
Modal,
} from 'antd';
import {
@ -16,12 +15,7 @@ import EmptyListComponent from './empty-list';
import TaskListContainer from '../../containers/tasks-page/tasks-list';
interface TasksPageProps {
deletingError: string;
dumpingError: string;
loadingError: string;
tasksFetchingError: string;
loadingDoneMessage: string;
tasksAreBeingFetched: boolean;
tasksFetching: boolean;
gettingQuery: TasksQuery;
numberOfTasks: number;
numberOfVisibleTasks: number;
@ -137,45 +131,8 @@ class TasksPageComponent extends React.PureComponent<TasksPageProps & RouteCompo
this.props.onGetTasks(gettingQuery);
}
public componentDidUpdate() {
if (this.props.tasksFetchingError) {
Modal.error({
title: 'Could not receive tasks',
content: this.props.tasksFetchingError,
});
}
if (this.props.dumpingError) {
Modal.error({
title: 'Could not dump annotations',
content: this.props.dumpingError,
});
}
if (this.props.loadingError) {
Modal.error({
title: 'Could not load annotations',
content: this.props.loadingError,
});
}
if (this.props.deletingError) {
Modal.error({
title: 'Could not delete the task',
content: this.props.deletingError,
});
}
if (this.props.loadingDoneMessage) {
Modal.info({
title: 'Successful loading of annotations',
content: this.props.loadingDoneMessage,
});
}
}
public render() {
if (this.props.tasksAreBeingFetched) {
if (this.props.tasksFetching) {
return (
<Spin size='large' style={{margin: '25% 45%'}}/>
);

@ -2,11 +2,16 @@ import React from 'react';
import { connect } from 'react-redux';
import ActionsMenuComponent from '../../components/actions-menu/actions-menu';
import { CombinedState } from '../../reducers/interfaces';
import {
CombinedState,
ActiveInference,
} from '../../reducers/interfaces';
import { showRunModelDialog } from '../../actions/models-actions';
import {
dumpAnnotationsAsync,
loadAnnotationsAsync,
exportDatasetAsync,
deleteTaskAsync,
} from '../../actions/tasks-actions';
@ -17,24 +22,30 @@ interface OwnProps {
interface StateToProps {
loaders: any[];
dumpers: any[];
exporters: any[];
loadActivity: string | null;
dumpActivities: string[] | null;
exportActivities: string[] | null;
installedTFAnnotation: boolean;
installedTFSegmentation: boolean;
installedAutoAnnotation: boolean;
inferenceIsActive: boolean;
};
interface DispatchToProps {
onLoadAnnotation: (taskInstance: any, loader: any, file: File) => void;
onDumpAnnotation: (taskInstance: any, dumper: any) => void;
onExportDataset: (taskInstance: any, exporter: any) => void;
onDeleteTask: (taskInstance: any) => void;
onOpenRunWindow: (taskInstance: any) => void;
}
function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps {
const { formats } = state;
const { dumps } = state.tasks.activities;
const { loads } = state.tasks.activities;
const { activities } = state.tasks;
const { dumps } = activities;
const { loads } = activities;
const _exports = activities.exports;
const { plugins } = state.plugins;
const id = own.taskInstance.id;
@ -43,10 +54,15 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps {
installedTFSegmentation: plugins.TF_SEGMENTATION,
installedAutoAnnotation: plugins.AUTO_ANNOTATION,
dumpActivities: dumps.byTask[id] ? dumps.byTask[id] : null,
exportActivities: _exports.byTask[id] ? _exports.byTask[id] : null,
loadActivity: loads.byTask[id] ? loads.byTask[id] : null,
loaders: formats.loaders,
dumpers: formats.dumpers,
};
loaders: formats.annotationFormats
.map((format: any): any[] => format.loaders).flat(),
dumpers: formats.annotationFormats
.map((format: any): any[] => format.dumpers).flat(),
exporters: formats.datasetFormats,
inferenceIsActive: id in state.models.inferences,
};
}
function mapDispatchToProps(dispatch: any): DispatchToProps {
@ -57,6 +73,9 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
onDumpAnnotation: (taskInstance: any, dumper: any) => {
dispatch(dumpAnnotationsAsync(taskInstance, dumper));
},
onExportDataset: (taskInstance: any, exporter: any) => {
dispatch(exportDatasetAsync(taskInstance, exporter));
},
onDeleteTask: (taskInstance: any) => {
dispatch(deleteTaskAsync(taskInstance));
},
@ -72,15 +91,19 @@ function ActionsMenuContainer(props: OwnProps & StateToProps & DispatchToProps)
taskInstance={props.taskInstance}
loaders={props.loaders}
dumpers={props.dumpers}
exporters={props.exporters}
loadActivity={props.loadActivity}
dumpActivities={props.dumpActivities}
exportActivities={props.exportActivities}
installedTFAnnotation={props.installedTFAnnotation}
installedTFSegmentation={props.installedTFSegmentation}
installedAutoAnnotation={props.installedAutoAnnotation}
onLoadAnnotation={props.onLoadAnnotation}
onDumpAnnotation={props.onDumpAnnotation}
onExportDataset={props.onExportDataset}
onDeleteTask={props.onDeleteTask}
onOpenRunWindow={props.onOpenRunWindow}
inferenceIsActive={props.inferenceIsActive}
/>
);
}

@ -10,7 +10,6 @@ import {
interface StateToProps {
isAdmin: boolean;
modelCreatingError: any;
modelCreatingStatus: string;
}
@ -23,7 +22,6 @@ function mapStateToProps(state: CombinedState): StateToProps {
return {
isAdmin: state.auth.user.isAdmin,
modelCreatingError: models.creatingError,
modelCreatingStatus: models.creatingStatus,
};
}
@ -40,7 +38,6 @@ function CreateModelPageContainer(props: StateToProps & DispatchToProps) {
return (
<CreateModelPageComponent
isAdmin={props.isAdmin}
modelCreatingError={props.modelCreatingError ? props.modelCreatingError.toString() : ''}
modelCreatingStatus={props.modelCreatingStatus}
createModel={props.createModel}
/>

@ -7,7 +7,6 @@ import { CreateTaskData } from '../../components/create-task-page/create-task-co
import { createTaskAsync } from '../../actions/tasks-actions';
interface StateToProps {
creatingError: string;
status: string;
installedGit: boolean;
}
@ -27,14 +26,12 @@ function mapStateToProps(state: CombinedState): StateToProps {
return {
...creates,
installedGit: state.plugins.plugins.GIT_INTEGRATION,
creatingError: creates.creatingError ? creates.creatingError.toString() : '',
};
}
function CreateTaskPageContainer(props: StateToProps & DispatchToProps) {
return (
<CreateTaskComponent
error={props.creatingError}
status={props.status}
onCreate={props.create}
installedGit={props.installedGit}

@ -11,6 +11,7 @@ import {
} from '../../reducers/interfaces';
interface OwnProps {
ref: any;
withRemote: boolean;
}

@ -15,7 +15,6 @@ interface StateToProps {
installedTFSegmentation: boolean;
installedTFAnnotation: boolean;
username: string;
logoutError: any;
}
interface DispatchToProps {
@ -31,7 +30,6 @@ function mapStateToProps(state: CombinedState): StateToProps {
installedTFSegmentation: plugins[SupportedPlugins.TF_SEGMENTATION],
installedTFAnnotation: plugins[SupportedPlugins.TF_ANNOTATION],
username: auth.user.username,
logoutError: auth.logoutError,
};
}
@ -50,7 +48,6 @@ function HeaderContainer(props: StateToProps & DispatchToProps) {
installedAutoAnnotation={props.installedAutoAnnotation}
onLogout={props.logout}
username={props.username}
logoutError={props.logoutError ? props.logoutError.toString() : ''}
/>
);
}

@ -1,21 +1,16 @@
import React from 'react';
import { connect } from 'react-redux';
import { loginAsync } from '../../actions/auth-actions';
import { CombinedState } from '../../reducers/interfaces';
import LoginPageComponent from '../../components/login-page/login-page';
interface StateToProps {
loginError: any;
}
interface StateToProps {}
interface DispatchToProps {
login(username: string, password: string): void;
}
function mapStateToProps(state: CombinedState): StateToProps {
return {
loginError: state.auth.loginError,
};
function mapStateToProps(): StateToProps {
return {};
}
function mapDispatchToProps(dispatch: any): DispatchToProps {
@ -24,11 +19,10 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
};
}
function LoginPageContainer(props: StateToProps & DispatchToProps) {
function LoginPageContainer(props: DispatchToProps) {
return (
<LoginPageComponent
onLogin={props.login}
loginError={props.loginError ? props.loginError.toString() : ''}
/>
);
}

@ -14,7 +14,7 @@ import {
interface StateToProps {
startingError: any;
modelsFetching: boolean;
modelsInitialized: boolean;
models: Model[];
activeProcesses: {
@ -41,12 +41,12 @@ function mapStateToProps(state: CombinedState): StateToProps {
const { models } = state;
return {
modelsFetching: models.fetching,
modelsInitialized: models.initialized,
models: models.models,
activeProcesses: {},
taskInstance: models.activeRunTask,
visible: models.visibleRunWindows,
startingError: models.startingError,
};
}
@ -74,6 +74,7 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
function ModelRunnerModalContainer(props: StateToProps & DispatchToProps) {
return (
<ModelRunnerModalComponent
modelsFetching={props.modelsFetching}
modelsInitialized={props.modelsInitialized}
models={props.models}
activeProcesses={props.activeProcesses}
@ -82,7 +83,6 @@ function ModelRunnerModalContainer(props: StateToProps & DispatchToProps) {
getModels={props.getModels}
closeDialog={props.closeDialog}
runInference={props.inferModelAsync}
startingError={props.startingError ? props.startingError.toString() : ''}
/>
);
}

@ -15,8 +15,8 @@ interface StateToProps {
installedAutoAnnotation: boolean;
installedTFAnnotation: boolean;
installedTFSegmentation: boolean;
modelsAreBeingFetched: boolean;
modelsFetchingError: any;
modelsInitialized: boolean;
modelsFetching: boolean;
models: Model[];
registeredUsers: any[];
}
@ -34,8 +34,8 @@ function mapStateToProps(state: CombinedState): StateToProps {
installedAutoAnnotation: plugins.AUTO_ANNOTATION,
installedTFAnnotation: plugins.TF_ANNOTATION,
installedTFSegmentation: plugins.TF_SEGMENTATION,
modelsAreBeingFetched: !models.initialized,
modelsFetchingError: models.fetchingError,
modelsInitialized: models.initialized,
modelsFetching: models.fetching,
models: models.models,
registeredUsers: state.users.users,
};
@ -63,8 +63,8 @@ function ModelsPageContainer(props: DispatchToProps & StateToProps) {
installedAutoAnnotation={props.installedAutoAnnotation}
installedTFSegmentation={props.installedTFSegmentation}
installedTFAnnotation={props.installedTFAnnotation}
modelsAreBeingFetched={props.modelsAreBeingFetched}
modelsFetchingError={props.modelsFetchingError}
modelsInitialized={props.modelsInitialized}
modelsFetching={props.modelsFetching}
registeredUsers={props.registeredUsers}
models={props.models}
getModels={props.getModels}

@ -1,12 +1,9 @@
import React from 'react';
import { connect } from 'react-redux';
import { registerAsync } from '../../actions/auth-actions';
import { CombinedState } from '../../reducers/interfaces';
import RegisterPageComponent from '../../components/register-page/register-page';
interface StateToProps {
registerError: any;
}
interface StateToProps {}
interface DispatchToProps {
register: (username: string, firstName: string,
@ -14,10 +11,8 @@ interface DispatchToProps {
password1: string, password2: string) => void;
}
function mapStateToProps(state: CombinedState): StateToProps {
return {
registerError: state.auth.registerError,
};
function mapStateToProps(): StateToProps {
return {};
}
function mapDispatchToProps(dispatch: any): DispatchToProps {
@ -26,11 +21,9 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
}
}
type RegisterPageContainerProps = StateToProps & DispatchToProps;
function RegisterPageContainer(props: RegisterPageContainerProps) {
function RegisterPageContainer(props: StateToProps & DispatchToProps) {
return (
<RegisterPageComponent
registerError={props.registerError ? props.registerError.toString() : ''}
onRegister={props.register}
/>
);

@ -14,10 +14,8 @@ import {
type Props = RouteComponentProps<{id: string}>;
interface StateToProps {
task: Task;
taskFetchingError: any;
taskUpdatingError: any;
taskDeletingError: any;
task: Task | null;
fetching: boolean;
deleteActivity: boolean | null;
installedGit: boolean;
}
@ -29,7 +27,6 @@ interface DispatchToProps {
function mapStateToProps(state: CombinedState, own: Props): StateToProps {
const { plugins } = state.plugins;
const { deletes } = state.tasks.activities;
const taskDeletingError = deletes.deletingError;
const id = +own.match.params.id;
const filtered = state.tasks.current.filter((task) => task.instance.id === id);
@ -42,10 +39,8 @@ function mapStateToProps(state: CombinedState, own: Props): StateToProps {
return {
task,
taskFetchingError: state.tasks.tasksFetchingError,
taskUpdatingError: state.tasks.taskUpdatingError,
taskDeletingError,
deleteActivity,
fetching: state.tasks.fetching,
installedGit: plugins.GIT_INTEGRATION,
};
}
@ -71,9 +66,7 @@ function TaskPageContainer(props: StateToProps & DispatchToProps) {
return (
<TaskPageComponent
task={props.task}
taskDeletingError={props.taskDeletingError ? props.taskDeletingError.toString() : ''}
taskFetchingError={props.taskFetchingError ? props.taskFetchingError.toString() : ''}
taskUpdatingError={props.taskUpdatingError ? props.taskUpdatingError.toString() : ''}
fetching={props.fetching}
deleteActivity={props.deleteActivity}
installedGit={props.installedGit}
onFetchTask={props.fetchTask}

@ -4,6 +4,7 @@ import { connect } from 'react-redux';
import {
TasksQuery,
CombinedState,
ActiveInference,
} from '../../reducers/interfaces';
import TaskItemComponent from '../../components/tasks-page/task-item'
@ -16,6 +17,7 @@ interface StateToProps {
deleteActivity: boolean | null;
previewImage: string;
taskInstance: any;
activeInference: ActiveInference | null;
}
interface DispatchToProps {
@ -36,6 +38,7 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps {
deleteActivity: deletes.byTask[id] ? deletes.byTask[id] : null,
previewImage: task.preview,
taskInstance: task.instance,
activeInference: state.models.inferences[id] || null,
};
}
@ -55,6 +58,7 @@ function TaskItemContainer(props: TasksItemContainerProps) {
deleted={props.deleteActivity === true}
taskInstance={props.taskInstance}
previewImage={props.previewImage}
activeInference={props.activeInference}
/>
);
}

@ -11,12 +11,7 @@ import TasksPageComponent from '../../components/tasks-page/tasks-page';
import { getTasksAsync } from '../../actions/tasks-actions';
interface StateToProps {
deletingError: any;
dumpingError: any;
loadingError: any;
tasksFetchingError: any;
loadingDoneMessage: string;
tasksAreBeingFetched: boolean;
tasksFetching: boolean;
gettingQuery: TasksQuery;
numberOfTasks: number;
numberOfVisibleTasks: number;
@ -28,18 +23,9 @@ interface DispatchToProps {
function mapStateToProps(state: CombinedState): StateToProps {
const { tasks } = state;
const { activities } = tasks;
const { dumps } = activities;
const { loads } = activities;
const { deletes } = activities;
return {
deletingError: deletes.deletingError,
dumpingError: dumps.dumpingError,
loadingError: loads.loadingError,
tasksFetchingError: tasks.tasksFetchingError,
loadingDoneMessage: loads.loadingDoneMessage,
tasksAreBeingFetched: !state.tasks.initialized,
tasksFetching: state.tasks.fetching,
gettingQuery: tasks.gettingQuery,
numberOfTasks: state.tasks.count,
numberOfVisibleTasks: state.tasks.current.length,
@ -57,12 +43,7 @@ type TasksPageContainerProps = StateToProps & DispatchToProps;
function TasksPageContainer(props: TasksPageContainerProps) {
return (
<TasksPageComponent
deletingError={props.deletingError ? props.deletingError.toString() : ''}
dumpingError={props.dumpingError ? props.dumpingError.toString() : ''}
loadingError={props.loadingError ? props.loadingError.toString() : ''}
tasksFetchingError={props.tasksFetchingError ? props.tasksFetchingError.toString(): ''}
loadingDoneMessage={props.loadingDoneMessage}
tasksAreBeingFetched={props.tasksAreBeingFetched}
tasksFetching={props.tasksFetching}
gettingQuery={props.gettingQuery}
numberOfTasks={props.numberOfTasks}
numberOfVisibleTasks={props.numberOfVisibleTasks}

@ -8,26 +8,34 @@ import createRootReducer from './reducers/root-reducer';
import createCVATStore, { getCVATStore } from './store';
import { authorizedAsync } from './actions/auth-actions';
import { gettingFormatsAsync } from './actions/formats-actions';
import { getFormatsAsync } from './actions/formats-actions';
import { checkPluginsAsync } from './actions/plugins-actions';
import { getUsersAsync } from './actions/users-actions';
import {
resetErrors,
resetMessages,
} from './actions/notification-actions';
import { CombinedState } from './reducers/interfaces';
import {
CombinedState,
NotificationsState,
} from './reducers/interfaces';
createCVATStore(createRootReducer);
const cvatStore = getCVATStore();
interface StateToProps {
pluginsInitialized: boolean;
pluginsFetching: boolean;
userInitialized: boolean;
usersInitialized: boolean;
usersFetching: boolean;
formatsInitialized: boolean;
gettingAuthError: any;
gettingFormatsError: any;
gettingUsersError: any;
formatsFetching: boolean;
installedAutoAnnotation: boolean;
installedTFSegmentation: boolean;
installedTFAnnotation: boolean;
notifications: NotificationsState;
user: any;
}
@ -36,6 +44,8 @@ interface DispatchToProps {
verifyAuthorized: () => void;
loadUsers: () => void;
initPlugins: () => void;
resetErrors: () => void;
resetMessages: () => void;
}
function mapStateToProps(state: CombinedState): StateToProps {
@ -45,26 +55,29 @@ function mapStateToProps(state: CombinedState): StateToProps {
const { users } = state;
return {
pluginsInitialized: plugins.initialized,
userInitialized: auth.initialized,
pluginsInitialized: plugins.initialized,
pluginsFetching: plugins.fetching,
usersInitialized: users.initialized,
usersFetching: users.fetching,
formatsInitialized: formats.initialized,
gettingAuthError: auth.authError,
gettingUsersError: users.gettingUsersError,
gettingFormatsError: formats.gettingFormatsError,
formatsFetching: formats.fetching,
installedAutoAnnotation: plugins.plugins.AUTO_ANNOTATION,
installedTFSegmentation: plugins.plugins.TF_SEGMENTATION,
installedTFAnnotation: plugins.plugins.TF_ANNOTATION,
notifications: {...state.notifications},
user: auth.user,
};
}
function mapDispatchToProps(dispatch: any): DispatchToProps {
return {
loadFormats: (): void => dispatch(gettingFormatsAsync()),
loadFormats: (): void => dispatch(getFormatsAsync()),
verifyAuthorized: (): void => dispatch(authorizedAsync()),
initPlugins: (): void => dispatch(checkPluginsAsync()),
loadUsers: (): void => dispatch(getUsersAsync()),
resetErrors: (): void => dispatch(resetErrors()),
resetMessages: (): void => dispatch(resetMessages()),
};
}
@ -75,16 +88,19 @@ function reduxAppWrapper(props: StateToProps & DispatchToProps) {
loadFormats={props.loadFormats}
loadUsers={props.loadUsers}
verifyAuthorized={props.verifyAuthorized}
pluginsInitialized={props.pluginsInitialized}
resetErrors={props.resetErrors}
resetMessages={props.resetMessages}
userInitialized={props.userInitialized}
pluginsInitialized={props.pluginsInitialized}
pluginsFetching={props.pluginsFetching}
usersInitialized={props.usersInitialized}
usersFetching={props.usersFetching}
formatsInitialized={props.formatsInitialized}
gettingAuthError={props.gettingAuthError ? props.gettingAuthError.toString() : ''}
gettingFormatsError={props.gettingFormatsError ? props.gettingFormatsError.toString() : ''}
gettingUsersError={props.gettingUsersError ? props.gettingUsersError.toString() : ''}
formatsFetching={props.formatsFetching}
installedAutoAnnotation={props.installedAutoAnnotation}
installedTFSegmentation={props.installedTFSegmentation}
installedTFAnnotation={props.installedTFAnnotation}
notifications={props.notifications}
user={props.user}
/>
)

@ -5,10 +5,6 @@ import { AuthState } from './interfaces';
const defaultState: AuthState = {
initialized: false,
authError: null,
loginError: null,
logoutError: null,
registerError: null,
user: null,
};
@ -19,48 +15,21 @@ export default (state = defaultState, action: AnyAction): AuthState => {
...state,
initialized: true,
user: action.payload.user,
authError: null,
};
case AuthActionTypes.AUTHORIZED_FAILED:
return {
...state,
initialized: true,
authError: action.payload.error,
};
case AuthActionTypes.LOGIN_SUCCESS:
return {
...state,
user: action.payload.user,
loginError: null,
};
case AuthActionTypes.LOGIN_FAILED:
return {
...state,
user: null,
loginError: action.payload.error,
};
case AuthActionTypes.LOGOUT_SUCCESS:
return {
...state,
user: null,
logoutError: null,
};
case AuthActionTypes.LOGOUT_FAILED:
return {
...state,
logoutError: action.payload.error,
};
case AuthActionTypes.REGISTER_SUCCESS:
return {
...state,
user: action.payload.user,
registerError: null,
};
case AuthActionTypes.REGISTER_FAILED:
return {
...state,
user: null,
registerError: action.payload.error,
};
default:
return state;

@ -4,27 +4,34 @@ import { FormatsActionTypes } from '../actions/formats-actions';
import { FormatsState } from './interfaces';
const defaultState: FormatsState = {
loaders: [],
dumpers: [],
gettingFormatsError: null,
annotationFormats: [],
datasetFormats: [],
initialized: false,
fetching: false,
};
export default (state = defaultState, action: AnyAction): FormatsState => {
switch (action.type) {
case FormatsActionTypes.GETTING_FORMATS_SUCCESS:
case FormatsActionTypes.GET_FORMATS: {
return {
...state,
fetching: true,
initialized: false,
};
}
case FormatsActionTypes.GET_FORMATS_SUCCESS:
return {
...state,
initialized: true,
gettingFormatsError: null,
dumpers: action.payload.formats.map((format: any): any[] => format.dumpers).flat(),
loaders: action.payload.formats.map((format: any): any[] => format.loaders).flat(),
fetching: false,
annotationFormats: action.payload.annotationFormats,
datasetFormats: action.payload.datasetFormats,
};
case FormatsActionTypes.GETTING_FORMATS_FAILED:
case FormatsActionTypes.GET_FORMATS_FAILED:
return {
...state,
initialized: true,
gettingFormatsError: action.payload.error,
fetching: false,
};
default:
return state;

@ -1,9 +1,5 @@
export interface AuthState {
initialized: boolean;
authError: any;
loginError: any;
logoutError: any;
registerError: any;
user: any;
}
@ -26,45 +22,45 @@ export interface Task {
export interface TasksState {
initialized: boolean;
tasksFetchingError: any;
taskUpdatingError: any;
fetching: boolean;
gettingQuery: TasksQuery;
count: number;
current: Task[];
activities: {
dumps: {
dumpingError: any;
byTask: {
// dumps in different formats at the same time
[tid: number]: string[]; // dumper names
};
};
exports: {
byTask: {
// exports in different formats at the same time
[tid: number]: string[]; // dumper names
};
};
loads: {
loadingError: any;
loadingDoneMessage: string;
byTask: {
// only one loading simultaneously
[tid: number]: string; // loader name
};
};
deletes: {
deletingError: any;
byTask: {
[tid: number]: boolean; // deleted (deleting if in dictionary)
};
};
creates: {
creatingError: any;
status: string;
};
};
}
export interface FormatsState {
loaders: any[];
dumpers: any[];
annotationFormats: any[];
datasetFormats: any[];
fetching: boolean;
initialized: boolean;
gettingFormatsError: any;
}
// eslint-disable-next-line import/prefer-default-export
@ -77,6 +73,7 @@ export enum SupportedPlugins {
}
export interface PluginsState {
fetching: boolean;
initialized: boolean;
plugins: {
[name in SupportedPlugins]: boolean;
@ -85,8 +82,8 @@ export interface PluginsState {
export interface UsersState {
users: any[];
fetching: boolean;
initialized: boolean;
gettingUsersError: any;
}
export interface ShareFileInfo { // get this data from cvat-core
@ -102,7 +99,6 @@ export interface ShareItem {
export interface ShareState {
root: ShareItem;
error: any;
}
export interface Model {
@ -115,25 +111,28 @@ export interface Model {
labels: string[];
}
export interface Running {
[tid: string]: {
status: string;
processId: string;
error: any;
};
export enum RQStatus {
unknown = 'unknown',
queued = 'queued',
started = 'started',
finished = 'finished',
failed = 'failed',
}
export interface ActiveInference {
status: RQStatus;
progress: number;
error: string;
}
export interface ModelsState {
initialized: boolean;
fetching: boolean;
creatingStatus: string;
creatingError: any;
startingError: any;
fetchingError: any;
deletingErrors: { // by id
[index: number]: any;
};
models: Model[];
runnings: Running[];
inferences: {
[index: number]: ActiveInference;
};
visibleRunWindows: boolean;
activeRunTask: any;
}
@ -146,6 +145,50 @@ export interface ModelFiles {
json: string | File;
}
export interface NotificationsState {
errors: {
auth: {
authorized: any;
login: any;
logout: any;
register: any;
};
tasks: {
fetching: any;
updating: any;
dumping: any;
loading: any;
exporting: any;
deleting: any;
creating: any;
};
formats: {
fetching: any;
};
users: {
fetching: any;
};
share: {
fetching: any;
};
models: {
creating: any;
starting: any;
fetching: any;
deleting: any;
inferenceStatusFetching: any;
};
};
messages: {
tasks: {
loadingDone: string;
};
models: {
inferenceDone: string;
};
};
}
export interface CombinedState {
auth: AuthState;
tasks: TasksState;
@ -154,4 +197,5 @@ export interface CombinedState {
formats: FormatsState;
plugins: PluginsState;
models: ModelsState;
notifications: NotificationsState;
}

@ -5,15 +5,12 @@ import { ModelsState } from './interfaces';
const defaultState: ModelsState = {
initialized: false,
fetching: false,
creatingStatus: '',
creatingError: null,
startingError: null,
fetchingError: null,
deletingErrors: {},
models: [],
visibleRunWindows: false,
activeRunTask: null,
runnings: [],
inferences: {},
};
export default function (state = defaultState, action: AnyAction): ModelsState {
@ -21,8 +18,8 @@ export default function (state = defaultState, action: AnyAction): ModelsState {
case ModelsActionTypes.GET_MODELS: {
return {
...state,
fetchingError: null,
initialized: false,
fetching: true,
};
}
case ModelsActionTypes.GET_MODELS_SUCCESS: {
@ -30,21 +27,14 @@ export default function (state = defaultState, action: AnyAction): ModelsState {
...state,
models: action.payload.models,
initialized: true,
fetching: false,
};
}
case ModelsActionTypes.GET_MODELS_FAILED: {
return {
...state,
fetchingError: action.payload.error,
initialized: true,
};
}
case ModelsActionTypes.DELETE_MODEL: {
const errors = { ...state.deletingErrors };
delete errors[action.payload.id];
return {
...state,
deletingErrors: errors,
fetching: false,
};
}
case ModelsActionTypes.DELETE_MODEL_SUCCESS: {
@ -55,18 +45,9 @@ export default function (state = defaultState, action: AnyAction): ModelsState {
),
};
}
case ModelsActionTypes.DELETE_MODEL_FAILED: {
const errors = { ...state.deletingErrors };
errors[action.payload.id] = action.payload.error;
return {
...state,
deletingErrors: errors,
};
}
case ModelsActionTypes.CREATE_MODEL: {
return {
...state,
creatingError: null,
creatingStatus: '',
};
}
@ -79,7 +60,6 @@ export default function (state = defaultState, action: AnyAction): ModelsState {
case ModelsActionTypes.CREATE_MODEL_FAILED: {
return {
...state,
creatingError: action.payload.error,
creatingStatus: '',
};
}
@ -90,30 +70,40 @@ export default function (state = defaultState, action: AnyAction): ModelsState {
creatingStatus: 'CREATED',
};
}
case ModelsActionTypes.INFER_MODEL: {
case ModelsActionTypes.SHOW_RUN_MODEL_DIALOG: {
return {
...state,
startingError: null,
visibleRunWindows: true,
activeRunTask: action.payload.taskInstance,
};
}
case ModelsActionTypes.INFER_MODEL_FAILED: {
case ModelsActionTypes.CLOSE_RUN_MODEL_DIALOG: {
return {
...state,
startingError: action.payload.error,
visibleRunWindows: false,
activeRunTask: null,
};
}
case ModelsActionTypes.SHOW_RUN_MODEL_DIALOG: {
case ModelsActionTypes.GET_INFERENCE_STATUS_SUCCESS: {
const inferences = { ...state.inferences };
if (action.payload.activeInference.status === 'finished') {
delete inferences[action.payload.taskID];
} else {
inferences[action.payload.taskID] = action.payload.activeInference;
}
return {
...state,
visibleRunWindows: true,
activeRunTask: action.payload.taskInstance,
inferences,
};
}
case ModelsActionTypes.CLOSE_RUN_MODEL_DIALOG: {
case ModelsActionTypes.GET_INFERENCE_STATUS_FAILED: {
const inferences = { ...state.inferences };
delete inferences[action.payload.taskID];
return {
...state,
visibleRunWindows: false,
activeRunTask: null,
inferences,
};
}
default: {

@ -0,0 +1,340 @@
import { AnyAction } from 'redux';
import { AuthActionTypes } from '../actions/auth-actions';
import { FormatsActionTypes } from '../actions/formats-actions';
import { ModelsActionTypes } from '../actions/models-actions';
import { ShareActionTypes } from '../actions/share-actions';
import { TasksActionTypes } from '../actions/tasks-actions';
import { UsersActionTypes } from '../actions/users-actions';
import { NotificationsActionType } from '../actions/notification-actions';
import { NotificationsState } from './interfaces';
const defaultState: NotificationsState = {
errors: {
auth: {
authorized: null,
login: null,
logout: null,
register: null,
},
tasks: {
fetching: null,
updating: null,
dumping: null,
loading: null,
exporting: null,
deleting: null,
creating: null,
},
formats: {
fetching: null,
},
users: {
fetching: null,
},
share: {
fetching: null,
},
models: {
creating: null,
starting: null,
fetching: null,
deleting: null,
inferenceStatusFetching: null,
},
},
messages: {
tasks: {
loadingDone: '',
},
models: {
inferenceDone: '',
},
},
};
export default function (state = defaultState, action: AnyAction): NotificationsState {
switch (action.type) {
case AuthActionTypes.AUTHORIZED_FAILED: {
return {
...state,
errors: {
...state.errors,
auth: {
...state.errors.auth,
authorized: action.payload.error,
},
},
};
}
case AuthActionTypes.LOGIN_FAILED: {
return {
...state,
errors: {
...state.errors,
auth: {
...state.errors.auth,
login: action.payload.error,
},
},
};
}
case AuthActionTypes.LOGOUT_FAILED: {
return {
...state,
errors: {
...state.errors,
auth: {
...state.errors.auth,
logout: action.payload.error,
},
},
};
}
case AuthActionTypes.REGISTER_FAILED: {
return {
...state,
errors: {
...state.errors,
auth: {
...state.errors.auth,
register: action.payload.error,
},
},
};
}
case TasksActionTypes.EXPORT_DATASET_FAILED: {
return {
...state,
errors: {
...state.errors,
tasks: {
...state.errors.tasks,
exporting: action.payload.error,
},
},
};
}
case TasksActionTypes.GET_TASKS_FAILED: {
return {
...state,
errors: {
...state.errors,
tasks: {
...state.errors.tasks,
fetching: action.payload.error,
},
},
};
}
case TasksActionTypes.LOAD_ANNOTATIONS_FAILED: {
return {
...state,
errors: {
...state.errors,
tasks: {
...state.errors.tasks,
loading: action.payload.error,
},
},
};
}
case TasksActionTypes.LOAD_ANNOTATIONS_SUCCESS: {
const { task } = action.payload;
return {
...state,
messages: {
...state.messages,
tasks: {
...state.messages.tasks,
loadingDone: `Annotations have been loaded to the task ${task.id}`,
},
},
};
}
case TasksActionTypes.UPDATE_TASK_FAILED: {
return {
...state,
errors: {
...state.errors,
tasks: {
...state.errors.tasks,
updating: action.payload.error,
},
},
};
}
case TasksActionTypes.DUMP_ANNOTATIONS_FAILED: {
return {
...state,
errors: {
...state.errors,
tasks: {
...state.errors.tasks,
dumping: action.payload.error,
},
},
};
}
case TasksActionTypes.DELETE_TASK_FAILED: {
return {
...state,
errors: {
...state.errors,
tasks: {
...state.errors.tasks,
deleting: action.payload.error,
},
},
};
}
case TasksActionTypes.CREATE_TASK_FAILED: {
return {
...state,
errors: {
...state.errors,
tasks: {
...state.errors.tasks,
creating: action.payload.error,
},
},
};
}
case FormatsActionTypes.GET_FORMATS_FAILED: {
return {
...state,
errors: {
...state.errors,
formats: {
...state.errors.formats,
fetching: action.payload.error,
},
},
};
}
case UsersActionTypes.GET_USERS_FAILED: {
return {
...state,
errors: {
...state.errors,
users: {
...state.errors.users,
fetching: action.payload.error,
},
},
};
}
case ShareActionTypes.LOAD_SHARE_DATA_FAILED: {
return {
...state,
errors: {
...state.errors,
share: {
...state.errors.share,
fetching: action.payload.error,
},
},
};
}
case ModelsActionTypes.CREATE_MODEL_FAILED: {
return {
...state,
errors: {
...state.errors,
models: {
...state.errors.models,
creating: action.payload.error,
},
},
};
}
case ModelsActionTypes.DELETE_MODEL_FAILED: {
return {
...state,
errors: {
...state.errors,
models: {
...state.errors.models,
deleting: action.payload.error,
},
},
};
}
case ModelsActionTypes.GET_INFERENCE_STATUS_SUCCESS: {
if (action.payload.activeInference.status === 'finished') {
return {
...state,
messages: {
...state.messages,
models: {
...state.messages.models,
inferenceDone: `Automatic annotation finished for the task ${action.payload.taskID}`,
},
},
};
}
return {
...state,
};
}
case ModelsActionTypes.GET_INFERENCE_STATUS_FAILED: {
return {
...state,
errors: {
...state.errors,
models: {
...state.errors.models,
inferenceStatusFetching: action.payload.error,
},
},
};
}
case ModelsActionTypes.GET_MODELS_FAILED: {
return {
...state,
errors: {
...state.errors,
models: {
...state.errors.models,
fetching: action.payload.error,
},
},
};
}
case ModelsActionTypes.INFER_MODEL_FAILED: {
return {
...state,
errors: {
...state.errors,
models: {
...state.errors.models,
starting: action.payload.error,
},
},
};
}
case NotificationsActionType.RESET_ERRORS: {
return {
...state,
errors: {
...defaultState.errors,
},
};
}
case NotificationsActionType.RESET_MESSAGES: {
return {
...state,
messages: {
...defaultState.messages,
},
};
}
default: {
return {
...state,
};
}
}
}

@ -8,6 +8,7 @@ import {
const defaultState: PluginsState = {
fetching: false,
initialized: false,
plugins: {
GIT_INTEGRATION: false,
@ -19,6 +20,13 @@ const defaultState: PluginsState = {
};
export default function (state = defaultState, action: AnyAction): PluginsState {
switch (action.type) {
case PluginsActionTypes.CHECK_PLUGINS: {
return {
...state,
initialized: false,
fetching: true,
};
}
case PluginsActionTypes.CHECKED_ALL_PLUGINS: {
const { plugins } = action.payload;
@ -29,6 +37,7 @@ export default function (state = defaultState, action: AnyAction): PluginsState
return {
...state,
initialized: true,
fetching: false,
plugins,
};
}

@ -6,6 +6,7 @@ import shareReducer from './share-reducer';
import formatsReducer from './formats-reducer';
import pluginsReducer from './plugins-reducer';
import modelsReducer from './models-reducer';
import notificationsReducer from './notifications-reducer';
export default function createRootReducer(): Reducer {
return combineReducers({
@ -16,5 +17,6 @@ export default function createRootReducer(): Reducer {
formats: formatsReducer,
plugins: pluginsReducer,
models: modelsReducer,
notifications: notificationsReducer,
});
}

@ -9,17 +9,10 @@ const defaultState: ShareState = {
type: 'DIR',
children: [],
},
error: null,
};
export default function (state = defaultState, action: AnyAction): ShareState {
switch (action.type) {
case ShareActionTypes.LOAD_SHARE_DATA: {
return {
...state,
error: null,
};
}
case ShareActionTypes.LOAD_SHARE_DATA_SUCCESS: {
const { values } = action.payload;
const { directory } = action.payload;
@ -45,14 +38,6 @@ export default function (state = defaultState, action: AnyAction): ShareState {
...state,
};
}
case ShareActionTypes.LOAD_SHARE_DATA_FAILED: {
const { error } = action.payload;
return {
...state,
error,
};
}
default:
return {
...state,

@ -5,8 +5,7 @@ import { TasksState, Task } from './interfaces';
const defaultState: TasksState = {
initialized: false,
tasksFetchingError: null,
taskUpdatingError: null,
fetching: false,
count: 0,
current: [],
gettingQuery: {
@ -21,52 +20,24 @@ const defaultState: TasksState = {
},
activities: {
dumps: {
dumpingError: null,
byTask: {},
},
exports: {
byTask: {},
},
loads: {
loadingError: null,
loadingDoneMessage: '',
byTask: {},
},
deletes: {
deletingError: null,
byTask: {},
},
creates: {
creatingError: null,
status: '',
},
},
};
export default (inputState: TasksState = defaultState, action: AnyAction): TasksState => {
function cleanupTemporaryInfo(stateToResetErrors: TasksState): TasksState {
return {
...stateToResetErrors,
tasksFetchingError: null,
taskUpdatingError: null,
activities: {
...stateToResetErrors.activities,
dumps: {
...stateToResetErrors.activities.dumps,
dumpingError: null,
},
loads: {
...stateToResetErrors.activities.loads,
loadingError: null,
loadingDoneMessage: '',
},
deletes: {
...stateToResetErrors.activities.deletes,
deletingError: null,
},
},
};
}
const state = cleanupTemporaryInfo(inputState);
export default (state: TasksState = defaultState, action: AnyAction): TasksState => {
switch (action.type) {
case TasksActionTypes.GET_TASKS:
return {
@ -74,11 +45,11 @@ export default (inputState: TasksState = defaultState, action: AnyAction): Tasks
activities: {
...state.activities,
deletes: {
deletingError: null,
byTask: {},
},
},
initialized: false,
fetching: true,
};
case TasksActionTypes.GET_TASKS_SUCCESS: {
const combinedWithPreviews = action.payload.array
@ -90,6 +61,7 @@ export default (inputState: TasksState = defaultState, action: AnyAction): Tasks
return {
...state,
initialized: true,
fetching: false,
count: action.payload.count,
current: combinedWithPreviews,
gettingQuery: { ...action.payload.query },
@ -99,10 +71,10 @@ export default (inputState: TasksState = defaultState, action: AnyAction): Tasks
return {
...state,
initialized: true,
fetching: false,
count: 0,
current: [],
gettingQuery: { ...action.payload.query },
tasksFetchingError: action.payload.error,
};
case TasksActionTypes.DUMP_ANNOTATIONS: {
const { task } = action.payload;
@ -115,8 +87,6 @@ export default (inputState: TasksState = defaultState, action: AnyAction): Tasks
const theTaskDumpingActivities = [...tasksDumpingActivities.byTask[task.id] || []];
if (!theTaskDumpingActivities.includes(dumper.name)) {
theTaskDumpingActivities.push(dumper.name);
} else {
throw Error('Dump with the same dumper for this same task has been already started');
}
tasksDumpingActivities.byTask[task.id] = theTaskDumpingActivities;
@ -152,11 +122,9 @@ export default (inputState: TasksState = defaultState, action: AnyAction): Tasks
case TasksActionTypes.DUMP_ANNOTATIONS_FAILED: {
const { task } = action.payload;
const { dumper } = action.payload;
const dumpingError = action.payload.error;
const tasksDumpingActivities = {
...state.activities.dumps,
dumpingError,
};
const theTaskDumpingActivities = tasksDumpingActivities.byTask[task.id]
@ -172,6 +140,70 @@ export default (inputState: TasksState = defaultState, action: AnyAction): Tasks
},
};
}
case TasksActionTypes.EXPORT_DATASET: {
const { task } = action.payload;
const { exporter } = action.payload;
const tasksExportingActivities = {
...state.activities.exports,
};
const theTaskDumpingActivities = [...tasksExportingActivities.byTask[task.id] || []];
if (!theTaskDumpingActivities.includes(exporter.name)) {
theTaskDumpingActivities.push(exporter.name);
}
tasksExportingActivities.byTask[task.id] = theTaskDumpingActivities;
return {
...state,
activities: {
...state.activities,
exports: tasksExportingActivities,
},
};
}
case TasksActionTypes.EXPORT_DATASET_SUCCESS: {
const { task } = action.payload;
const { exporter } = action.payload;
const tasksExportingActivities = {
...state.activities.exports,
};
const theTaskExportingActivities = tasksExportingActivities.byTask[task.id]
.filter((exporterName: string): boolean => exporterName !== exporter.name);
tasksExportingActivities.byTask[task.id] = theTaskExportingActivities;
return {
...state,
activities: {
...state.activities,
exports: tasksExportingActivities,
},
};
}
case TasksActionTypes.EXPORT_DATASET_FAILED: {
const { task } = action.payload;
const { exporter } = action.payload;
const tasksExportingActivities = {
...state.activities.exports,
};
const theTaskExportingActivities = tasksExportingActivities.byTask[task.id]
.filter((exporterName: string): boolean => exporterName !== exporter.name);
tasksExportingActivities.byTask[task.id] = theTaskExportingActivities;
return {
...state,
activities: {
...state.activities,
exports: tasksExportingActivities,
},
};
}
case TasksActionTypes.LOAD_ANNOTATIONS: {
const { task } = action.payload;
const { loader } = action.payload;
@ -209,14 +241,12 @@ export default (inputState: TasksState = defaultState, action: AnyAction): Tasks
...state.activities,
loads: {
...tasksLoadingActivity,
loadingDoneMessage: `Annotations have been loaded to the task ${task.id}`,
},
},
};
}
case TasksActionTypes.LOAD_ANNOTATIONS_FAILED: {
const { task } = action.payload;
const loadingError = action.payload.error;
const tasksLoadingActivity = {
...state.activities.loads,
@ -230,7 +260,6 @@ export default (inputState: TasksState = defaultState, action: AnyAction): Tasks
...state.activities,
loads: {
...tasksLoadingActivity,
loadingError,
},
},
};
@ -273,7 +302,6 @@ export default (inputState: TasksState = defaultState, action: AnyAction): Tasks
}
case TasksActionTypes.DELETE_TASK_FAILED: {
const { taskID } = action.payload;
const { error } = action.payload;
const deletesActivities = state.activities.deletes;
@ -288,7 +316,6 @@ export default (inputState: TasksState = defaultState, action: AnyAction): Tasks
...state.activities,
deletes: {
...deletesActivities,
deletingError: error,
},
},
};
@ -299,7 +326,6 @@ export default (inputState: TasksState = defaultState, action: AnyAction): Tasks
activities: {
...state.activities,
creates: {
creatingError: null,
status: '',
},
},
@ -332,15 +358,13 @@ export default (inputState: TasksState = defaultState, action: AnyAction): Tasks
};
}
case TasksActionTypes.CREATE_TASK_FAILED: {
const { error } = action.payload;
return {
...state,
activities: {
...state.activities,
creates: {
...state.activities.creates,
creatingError: error,
status: 'FAILED',
},
},
};
@ -348,7 +372,6 @@ export default (inputState: TasksState = defaultState, action: AnyAction): Tasks
case TasksActionTypes.UPDATE_TASK: {
return {
...state,
taskUpdatingError: null,
};
}
case TasksActionTypes.UPDATE_TASK_SUCCESS: {
@ -369,7 +392,6 @@ export default (inputState: TasksState = defaultState, action: AnyAction): Tasks
case TasksActionTypes.UPDATE_TASK_FAILED: {
return {
...state,
taskUpdatingError: action.payload.error,
current: state.current.map((task): Task => {
if (task.instance.id === action.payload.taskInstance.id) {
return {

@ -5,30 +5,31 @@ import { UsersActionTypes } from '../actions/users-actions';
const initialState: UsersState = {
users: [],
fetching: false,
initialized: false,
gettingUsersError: null,
};
export default function (state: UsersState = initialState, action: AnyAction): UsersState {
switch (action.type) {
case UsersActionTypes.GET_USERS:
case UsersActionTypes.GET_USERS: {
return {
...state,
fetching: true,
initialized: false,
gettingUsersError: null,
};
}
case UsersActionTypes.GET_USERS_SUCCESS:
return {
...state,
fetching: false,
initialized: true,
users: action.payload.users,
};
case UsersActionTypes.GET_USERS_FAILED:
return {
...state,
fetching: false,
initialized: true,
users: [],
gettingUsersError: action.payload.error,
};
default:
return {

@ -316,6 +316,18 @@
background-color: rgba(24,144,255,0.05);
}
.cvat-actions-menu-dump-submenu-item > button {
text-align: start;
}
.cvat-actions-menu-export-submenu-item:hover {
background-color: rgba(24,144,255,0.05);
}
.cvat-actions-menu-export-submenu-item > button {
text-align: start;
}
.cvat-actions-menu-dump-submenu-item:hover {
background-color: rgba(24,144,255,0.05);
}
@ -759,7 +771,7 @@ textarea.ant-input.cvat-raw-labels-viewer {
margin-top: 10px;
}
.cvat-create-model-content > div:nth-child(5) > button {
.cvat-create-model-content > div:nth-child(6) > button {
margin-top: 10px;
float: right;
width: 120px;

Loading…
Cancel
Save