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 $ . .env/bin/activate
$ pip install -U pip wheel $ pip install -U pip wheel
$ pip install -r cvat/requirements/development.txt $ pip install -r cvat/requirements/development.txt
$ pip install -r datumaro/requirements.txt
$ python manage.py migrate $ python manage.py migrate
$ python manage.py collectstatic $ python manage.py collectstatic
``` ```

@ -147,6 +147,7 @@
`${encodeURIComponent('password')}=${encodeURIComponent(password)}`, `${encodeURIComponent('password')}=${encodeURIComponent(password)}`,
]).join('&').replace(/%20/g, '+'); ]).join('&').replace(/%20/g, '+');
Axios.defaults.headers.common.Authorization = '';
let authenticationResponse = null; let authenticationResponse = null;
try { try {
authenticationResponse = await Axios.post( authenticationResponse = await Axios.post(
@ -246,7 +247,7 @@
try { try {
await Axios.delete(`${backendAPI}/tasks/${id}`); await Axios.delete(`${backendAPI}/tasks/${id}`);
} catch (errorData) { } 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(); const cvat = getCore();
export enum FormatsActionTypes { export enum FormatsActionTypes {
GETTING_FORMATS_SUCCESS = 'GETTING_FORMATS_SUCCESS', GET_FORMATS = 'GET_FORMATS',
GETTING_FORMATS_FAILED = 'GETTING_FORMATS_FAILED', GET_FORMATS_SUCCESS = 'GET_FORMATS_SUCCESS',
GET_FORMATS_FAILED = 'GET_FORMATS_FAILED',
} }
export function gettingFormatsSuccess(formats: any): AnyAction { function getFormats(): AnyAction {
return { 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: { payload: {
formats, annotationFormats,
datasetFormats,
}, },
}; };
} }
export function gettingFormatsFailed(error: any): AnyAction { function getFormatsFailed(error: any): AnyAction {
return { return {
type: FormatsActionTypes.GETTING_FORMATS_FAILED, type: FormatsActionTypes.GET_FORMATS_FAILED,
payload: { payload: {
error, error,
}, },
}; };
} }
export function gettingFormatsAsync(): ThunkAction<Promise<void>, {}, {}, AnyAction> { export function getFormatsAsync(): ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
let formats = null; dispatch(getFormats());
let annotationFormats = null;
let datasetFormats = null;
try { try {
formats = await cvat.server.formats(); annotationFormats = await cvat.server.formats();
datasetFormats = await cvat.server.datasetFormats();
} catch (error) { } catch (error) {
dispatch(gettingFormatsFailed(error)); dispatch(getFormatsFailed(error));
return; return;
} }
dispatch(gettingFormatsSuccess(formats)); dispatch(getFormatsSuccess(annotationFormats, datasetFormats));
}; };
} }

@ -3,7 +3,12 @@ import { ThunkAction } from 'redux-thunk';
import getCore from '../core'; import getCore from '../core';
import { getCVATStore } from '../store'; import { getCVATStore } from '../store';
import { Model, ModelFiles, CombinedState } from '../reducers/interfaces'; import {
Model,
ModelFiles,
ActiveInference,
CombinedState,
} from '../reducers/interfaces';
export enum ModelsActionTypes { export enum ModelsActionTypes {
GET_MODELS = 'GET_MODELS', 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 { function inferModel(): AnyAction {
const action = { const action = {
type: ModelsActionTypes.INFER_MODEL, type: ModelsActionTypes.INFER_MODEL,
@ -387,6 +585,8 @@ export function inferModelAsync(
}, },
); );
} }
dispatch(getInferenceStatusAsync([taskInstance.id]));
} catch (error) { } catch (error) {
dispatch(inferModelFailed(error)); dispatch(inferModelFailed(error));
return; 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'; import PluginChecker from '../utils/plugin-checker';
export enum PluginsActionTypes { export enum PluginsActionTypes {
CHECK_PLUGINS = 'CHECK_PLUGINS',
CHECKED_ALL_PLUGINS = 'CHECKED_ALL_PLUGINS' CHECKED_ALL_PLUGINS = 'CHECKED_ALL_PLUGINS'
} }
@ -11,6 +12,15 @@ interface PluginObjects {
[plugin: string]: boolean; [plugin: string]: boolean;
} }
function checkPlugins(): AnyAction {
const action = {
type: PluginsActionTypes.CHECK_PLUGINS,
payload: {},
};
return action;
}
function checkedAllPlugins(plugins: PluginObjects): AnyAction { function checkedAllPlugins(plugins: PluginObjects): AnyAction {
const action = { const action = {
type: PluginsActionTypes.CHECKED_ALL_PLUGINS, type: PluginsActionTypes.CHECKED_ALL_PLUGINS,
@ -25,6 +35,7 @@ function checkedAllPlugins(plugins: PluginObjects): AnyAction {
export function checkPluginsAsync(): export function checkPluginsAsync():
ThunkAction<Promise<void>, {}, {}, AnyAction> { ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
dispatch(checkPlugins());
const plugins: PluginObjects = {}; const plugins: PluginObjects = {};
const promises: Promise<boolean>[] = []; const promises: Promise<boolean>[] = [];

@ -1,6 +1,7 @@
import { AnyAction, Dispatch, ActionCreator } from 'redux'; import { AnyAction, Dispatch, ActionCreator } from 'redux';
import { ThunkAction } from 'redux-thunk'; import { ThunkAction } from 'redux-thunk';
import { TasksQuery } from '../reducers/interfaces'; import { TasksQuery } from '../reducers/interfaces';
import { getInferenceStatusAsync } from './models-actions';
import getCore from '../core'; import getCore from '../core';
@ -16,6 +17,9 @@ export enum TasksActionTypes {
DUMP_ANNOTATIONS = 'DUMP_ANNOTATIONS', DUMP_ANNOTATIONS = 'DUMP_ANNOTATIONS',
DUMP_ANNOTATIONS_SUCCESS = 'DUMP_ANNOTATIONS_SUCCESS', DUMP_ANNOTATIONS_SUCCESS = 'DUMP_ANNOTATIONS_SUCCESS',
DUMP_ANNOTATIONS_FAILED = 'DUMP_ANNOTATIONS_FAILED', 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 = 'DELETE_TASK',
DELETE_TASK_SUCCESS = 'DELETE_TASK_SUCCESS', DELETE_TASK_SUCCESS = 'DELETE_TASK_SUCCESS',
DELETE_TASK_FAILED = 'DELETE_TASK_FAILED', DELETE_TASK_FAILED = 'DELETE_TASK_FAILED',
@ -26,6 +30,7 @@ export enum TasksActionTypes {
UPDATE_TASK = 'UPDATE_TASK', UPDATE_TASK = 'UPDATE_TASK',
UPDATE_TASK_SUCCESS = 'UPDATE_TASK_SUCCESS', UPDATE_TASK_SUCCESS = 'UPDATE_TASK_SUCCESS',
UPDATE_TASK_FAILED = 'UPDATE_TASK_FAILED', UPDATE_TASK_FAILED = 'UPDATE_TASK_FAILED',
RESET_ERROR = 'RESET_ERROR',
} }
function getTasks(): AnyAction { function getTasks(): AnyAction {
@ -89,6 +94,13 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
const previews = []; const previews = [];
const promises = array const promises = array
.map((task): string => (task as any).frames.preview()); .map((task): string => (task as any).frames.preview());
dispatch(
getInferenceStatusAsync(
array.map(
(task: any): number => task.id,
),
),
);
for (const promise of promises) { for (const promise of promises) {
try { try {
@ -150,7 +162,9 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
try { try {
dispatch(dumpAnnotation(task, dumper)); dispatch(dumpAnnotation(task, dumper));
const url = await task.annotations.dump(task.name, 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) { } catch (error) {
dispatch(dumpAnnotationFailed(task, dumper, error)); dispatch(dumpAnnotationFailed(task, dumper, error));
return; 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 { function deleteTask(taskID: number): AnyAction {
const action = { const action = {
type: TasksActionTypes.DELETE_TASK, type: TasksActionTypes.DELETE_TASK,

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

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

@ -11,7 +11,7 @@ import Text from 'antd/lib/typography/Text';
interface DumperItemComponentProps { interface DumperItemComponentProps {
taskInstance: any; taskInstance: any;
dumper: any; dumper: any;
dumpActivities: string[] | null; dumpActivity: string | null;
onDumpAnnotation: (task: any, dumper: any) => void; onDumpAnnotation: (task: any, dumper: any) => void;
} }
@ -24,11 +24,7 @@ export default function DumperItemComponent(props: DumperItemComponentProps) {
const task = props.taskInstance; const task = props.taskInstance;
const { mode } = task; const { mode } = task;
const { dumper } = props; const { dumper } = props;
const pending = !!props.dumpActivity;
const dumpingWithThisDumper = (props.dumpActivities || [])
.filter((_dumper: string) => _dumper === dumper.name)[0];
const pending = !!dumpingWithThisDumper;
return ( return (
<Menu.Item className='cvat-actions-menu-dump-submenu-item' key={dumper.name}> <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, Tooltip,
Modal, Modal,
message, message,
notification,
} from 'antd'; } from 'antd';
import Text from 'antd/lib/typography/Text';
import CreateModelForm, { import CreateModelForm, {
CreateModelForm as WrappedCreateModelForm CreateModelForm as WrappedCreateModelForm
} from './create-model-form'; } from './create-model-form';
@ -22,7 +25,6 @@ import { ModelFiles } from '../../reducers/interfaces';
interface Props { interface Props {
createModel(name: string, files: ModelFiles, global: boolean): void; createModel(name: string, files: ModelFiles, global: boolean): void;
isAdmin: boolean; isAdmin: boolean;
modelCreatingError: string;
modelCreatingStatus: string; modelCreatingStatus: string;
} }
@ -64,17 +66,17 @@ export default class CreateModelContent extends React.PureComponent<Props> {
if (Object.keys(grouppedFiles) if (Object.keys(grouppedFiles)
.map((key: string) => grouppedFiles[key]) .map((key: string) => grouppedFiles[key])
.filter((val) => !!val).length !== 4) { .filter((val) => !!val).length !== 4) {
Modal.error({ notification.error({
title: 'Could not upload a model', message: 'Could not upload a model',
content: 'Please, specify correct files', description: 'Please, specify correct files',
}); });
} else { } else {
this.props.createModel(data.name, grouppedFiles, data.global); this.props.createModel(data.name, grouppedFiles, data.global);
} }
}).catch(() => { }).catch(() => {
Modal.error({ notification.error({
title: 'Could not upload a model', message: 'Could not upload a model',
content: 'Please, check input fields', description: 'Please, check input fields',
}); });
}) })
} }
@ -86,13 +88,6 @@ export default class CreateModelContent extends React.PureComponent<Props> {
this.modelForm.resetFields(); this.modelForm.resetFields();
this.fileManagerContainer.reset(); this.fileManagerContainer.reset();
} }
if (!prevProps.modelCreatingError && this.props.modelCreatingError) {
Modal.error({
title: 'Could not create task',
content: this.props.modelCreatingError,
});
}
} }
public render() { 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'> <Row type='flex' justify='start' align='middle' className='cvat-create-model-content'>
<Col span={24}> <Col span={24}>
<Tooltip overlay='Click to open guide'> <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> </Tooltip>
</Col> </Col>
<Col span={24}> <Col span={24}>
@ -116,11 +113,15 @@ export default class CreateModelContent extends React.PureComponent<Props> {
} }
/> />
</Col> </Col>
<Col span={24}>
<Text type='danger'>* </Text>
<Text className='cvat-black-color'>Select files:</Text>
</Col>
<Col span={24}> <Col span={24}>
<ConnectedFileManager ref={ <ConnectedFileManager ref={
(container: FileManagerContainer) => (container: FileManagerContainer) =>
this.fileManagerContainer = container this.fileManagerContainer = container
}/> } withRemote={true}/>
</Col> </Col>
<Col span={18}> <Col span={18}>
{status && <Alert message={`${status}`}/>} {status && <Alert message={`${status}`}/>}

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

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

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

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

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

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

@ -1,12 +1,17 @@
import React from 'react'; 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 'antd/dist/antd.css';
import '../stylesheet.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 TasksPageContainer from '../containers/tasks-page/tasks-page';
import CreateTaskPageContainer from '../containers/create-task-page/create-task-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 ModelRunnerModalContainer from '../containers/model-runner-dialog/model-runner-dialog';
import FeedbackComponent from './feedback'; import FeedbackComponent from './feedback';
import { NotificationsState } from '../reducers/interfaces';
type CVATAppProps = { type CVATAppProps = {
loadFormats: () => void; loadFormats: () => void;
loadUsers: () => void; loadUsers: () => void;
verifyAuthorized: () => void; verifyAuthorized: () => void;
initPlugins: () => void; initPlugins: () => void;
pluginsInitialized: boolean; resetErrors: () => void;
resetMessages: () => void;
userInitialized: boolean; userInitialized: boolean;
pluginsInitialized: boolean;
pluginsFetching: boolean;
formatsInitialized: boolean; formatsInitialized: boolean;
formatsFetching: boolean;
usersInitialized: boolean; usersInitialized: boolean;
gettingAuthError: string; usersFetching: boolean;
gettingFormatsError: string;
gettingUsersError: string;
installedAutoAnnotation: boolean; installedAutoAnnotation: boolean;
installedTFAnnotation: boolean; installedTFAnnotation: boolean;
installedTFSegmentation: boolean; installedTFSegmentation: boolean;
notifications: NotificationsState;
user: any; user: any;
} }
@ -44,54 +53,141 @@ export default class CVATApplication extends React.PureComponent<CVATAppProps> {
super(props); super(props);
} }
public componentDidMount() { private showMessages() {
this.props.verifyAuthorized(); function showMessage(title: string) {
} notification.info({
message: title,
duration: null,
});
}
public componentDidUpdate() { const { tasks } = this.props.notifications.messages;
if (!this.props.userInitialized || const { models } = this.props.notifications.messages;
this.props.userInitialized && this.props.user == null) { let shown = !!tasks.loadingDone || !!models.inferenceDone;
return;
if (tasks.loadingDone) {
showMessage(tasks.loadingDone);
} }
if (models.inferenceDone) {
showMessage(models.inferenceDone);
}
if (shown) {
this.props.resetMessages();
}
}
if (this.props.gettingAuthError) { private showErrors() {
Modal.error({ function showError(title: string, _error: any) {
title: 'Could not check authorization', const error = _error.toString();
content: `${this.props.gettingAuthError}`, notification.error({
message: title,
duration: null,
description: error.length > 200 ? '' : error,
}); });
return;
console.error(error);
} }
if (!this.props.formatsInitialized) { const { auth } = this.props.notifications.errors;
this.props.loadFormats(); const { tasks } = this.props.notifications.errors;
return; 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) { if (shown) {
Modal.error({ this.props.resetErrors();
title: 'Could not receive annotations formats',
content: `${this.props.gettingFormatsError}`,
});
return;
} }
}
if (!this.props.usersInitialized) { public componentDidMount() {
this.props.loadUsers(); this.props.verifyAuthorized();
}
public componentDidUpdate() {
this.showErrors();
this.showMessages();
if (!this.props.userInitialized || this.props.user == null) {
// not authorized user
return; return;
} }
if (this.props.gettingUsersError) { if (!this.props.formatsInitialized && !this.props.formatsFetching) {
Modal.error({ this.props.loadFormats();
title: 'Could not receive users', }
content: `${this.props.gettingUsersError}`,
});
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(); this.props.initPlugins();
return;
} }
} }

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

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

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

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

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

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

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

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

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

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

@ -15,11 +15,13 @@ import {
import moment from 'moment'; import moment from 'moment';
import ActionsMenuContainer from '../../containers/actions-menu/actions-menu'; import ActionsMenuContainer from '../../containers/actions-menu/actions-menu';
import { ActiveInference } from '../../reducers/interfaces';
export interface TaskItemProps { export interface TaskItemProps {
taskInstance: any; taskInstance: any;
previewImage: string; previewImage: string;
deleted: boolean; deleted: boolean;
activeInference: ActiveInference | null;
} }
class TaskItemComponent extends React.PureComponent<TaskItemProps & RouteComponentProps> { class TaskItemComponent extends React.PureComponent<TaskItemProps & RouteComponentProps> {
@ -94,7 +96,8 @@ class TaskItemComponent extends React.PureComponent<TaskItemProps & RouteCompone
</Col> </Col>
</Row> </Row>
<Row> <Row>
<Progress <Col>
<Progress
className={`${progressColor} cvat-task-progress`} className={`${progressColor} cvat-task-progress`}
percent={numOfCompleted * 100 / numOfJobs} percent={numOfCompleted * 100 / numOfJobs}
strokeColor='#1890FF' strokeColor='#1890FF'
@ -102,7 +105,31 @@ class TaskItemComponent extends React.PureComponent<TaskItemProps & RouteCompone
strokeWidth={5} strokeWidth={5}
size='small' size='small'
/> />
</Col>
</Row> </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> </Col>
) )
} }

@ -4,7 +4,6 @@ import { withRouter } from 'react-router-dom';
import { import {
Spin, Spin,
Modal,
} from 'antd'; } from 'antd';
import { import {
@ -16,12 +15,7 @@ import EmptyListComponent from './empty-list';
import TaskListContainer from '../../containers/tasks-page/tasks-list'; import TaskListContainer from '../../containers/tasks-page/tasks-list';
interface TasksPageProps { interface TasksPageProps {
deletingError: string; tasksFetching: boolean;
dumpingError: string;
loadingError: string;
tasksFetchingError: string;
loadingDoneMessage: string;
tasksAreBeingFetched: boolean;
gettingQuery: TasksQuery; gettingQuery: TasksQuery;
numberOfTasks: number; numberOfTasks: number;
numberOfVisibleTasks: number; numberOfVisibleTasks: number;
@ -137,45 +131,8 @@ class TasksPageComponent extends React.PureComponent<TasksPageProps & RouteCompo
this.props.onGetTasks(gettingQuery); 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() { public render() {
if (this.props.tasksAreBeingFetched) { if (this.props.tasksFetching) {
return ( return (
<Spin size='large' style={{margin: '25% 45%'}}/> <Spin size='large' style={{margin: '25% 45%'}}/>
); );

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -5,15 +5,12 @@ import { ModelsState } from './interfaces';
const defaultState: ModelsState = { const defaultState: ModelsState = {
initialized: false, initialized: false,
fetching: false,
creatingStatus: '', creatingStatus: '',
creatingError: null,
startingError: null,
fetchingError: null,
deletingErrors: {},
models: [], models: [],
visibleRunWindows: false, visibleRunWindows: false,
activeRunTask: null, activeRunTask: null,
runnings: [], inferences: {},
}; };
export default function (state = defaultState, action: AnyAction): ModelsState { export default function (state = defaultState, action: AnyAction): ModelsState {
@ -21,8 +18,8 @@ export default function (state = defaultState, action: AnyAction): ModelsState {
case ModelsActionTypes.GET_MODELS: { case ModelsActionTypes.GET_MODELS: {
return { return {
...state, ...state,
fetchingError: null,
initialized: false, initialized: false,
fetching: true,
}; };
} }
case ModelsActionTypes.GET_MODELS_SUCCESS: { case ModelsActionTypes.GET_MODELS_SUCCESS: {
@ -30,21 +27,14 @@ export default function (state = defaultState, action: AnyAction): ModelsState {
...state, ...state,
models: action.payload.models, models: action.payload.models,
initialized: true, initialized: true,
fetching: false,
}; };
} }
case ModelsActionTypes.GET_MODELS_FAILED: { case ModelsActionTypes.GET_MODELS_FAILED: {
return { return {
...state, ...state,
fetchingError: action.payload.error,
initialized: true, initialized: true,
}; fetching: false,
}
case ModelsActionTypes.DELETE_MODEL: {
const errors = { ...state.deletingErrors };
delete errors[action.payload.id];
return {
...state,
deletingErrors: errors,
}; };
} }
case ModelsActionTypes.DELETE_MODEL_SUCCESS: { 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: { case ModelsActionTypes.CREATE_MODEL: {
return { return {
...state, ...state,
creatingError: null,
creatingStatus: '', creatingStatus: '',
}; };
} }
@ -79,7 +60,6 @@ export default function (state = defaultState, action: AnyAction): ModelsState {
case ModelsActionTypes.CREATE_MODEL_FAILED: { case ModelsActionTypes.CREATE_MODEL_FAILED: {
return { return {
...state, ...state,
creatingError: action.payload.error,
creatingStatus: '', creatingStatus: '',
}; };
} }
@ -90,30 +70,40 @@ export default function (state = defaultState, action: AnyAction): ModelsState {
creatingStatus: 'CREATED', creatingStatus: 'CREATED',
}; };
} }
case ModelsActionTypes.INFER_MODEL: { case ModelsActionTypes.SHOW_RUN_MODEL_DIALOG: {
return { return {
...state, ...state,
startingError: null, visibleRunWindows: true,
activeRunTask: action.payload.taskInstance,
}; };
} }
case ModelsActionTypes.INFER_MODEL_FAILED: { case ModelsActionTypes.CLOSE_RUN_MODEL_DIALOG: {
return { return {
...state, ...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 { return {
...state, ...state,
visibleRunWindows: true, inferences,
activeRunTask: action.payload.taskInstance,
}; };
} }
case ModelsActionTypes.CLOSE_RUN_MODEL_DIALOG: { case ModelsActionTypes.GET_INFERENCE_STATUS_FAILED: {
const inferences = { ...state.inferences };
delete inferences[action.payload.taskID];
return { return {
...state, ...state,
visibleRunWindows: false, inferences,
activeRunTask: null,
}; };
} }
default: { 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 = { const defaultState: PluginsState = {
fetching: false,
initialized: false, initialized: false,
plugins: { plugins: {
GIT_INTEGRATION: false, GIT_INTEGRATION: false,
@ -19,6 +20,13 @@ const defaultState: PluginsState = {
}; };
export default function (state = defaultState, action: AnyAction): PluginsState { export default function (state = defaultState, action: AnyAction): PluginsState {
switch (action.type) { switch (action.type) {
case PluginsActionTypes.CHECK_PLUGINS: {
return {
...state,
initialized: false,
fetching: true,
};
}
case PluginsActionTypes.CHECKED_ALL_PLUGINS: { case PluginsActionTypes.CHECKED_ALL_PLUGINS: {
const { plugins } = action.payload; const { plugins } = action.payload;
@ -29,6 +37,7 @@ export default function (state = defaultState, action: AnyAction): PluginsState
return { return {
...state, ...state,
initialized: true, initialized: true,
fetching: false,
plugins, plugins,
}; };
} }

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

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

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

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

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

Loading…
Cancel
Save