Repository: cvat-ai/cvat
Pull request: Create multiple tasks when uploading multiple videos
<!-- Raised an issue to propose your change (https://github.com/cvat-ai/cvat/issues).
It helps to avoid duplication of efforts from multiple independent contributors.
Discuss your ideas with maintainers to be sure that changes will be approved and merged.
Read the [CONTRIBUTION](https://github.com/cvat-ai/cvat/blob/develop/CONTRIBUTING.md)
guide. -->

<!-- Provide a general summary of your changes in the Title above -->

### Motivation and context
<!-- Why is this change required? What problem does it solve? If it fixes an open
issue, please link to the issue here. Describe your changes in detail, add
screenshots. -->

Resolve #916

### How has this been tested?
<!-- Please describe in detail how you tested your changes.
Include details of your testing environment, and the tests you ran to
see how your change affects other areas of the code, etc. -->

### Checklist
<!-- Go over all the following points, and put an `x` in all the boxes that apply.
If an item isn't applicable by a reason then ~~explicitly strikethrough~~ the whole
line. If you don't do that github will show an incorrect process for the pull request.
If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [ ] I submit my changes into the `develop` branch
- [ ] I have added a description of my changes into [CHANGELOG](https://github.com/cvat-ai/cvat/blob/develop/CHANGELOG.md) file
- [ ] I have updated the [documentation](
  https://github.com/cvat-ai/cvat/blob/develop/README.md#documentation) accordingly
- [ ] I have added tests to cover my changes
- [ ] I have linked related issues ([read github docs](
  https://help.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword))
- [ ] I have increased versions of npm packages if it is necessary ([cvat-canvas](https://github.com/cvat-ai/cvat/tree/develop/cvat-canvas#versioning),
[cvat-core](https://github.com/cvat-ai/cvat/tree/develop/cvat-core#versioning), [cvat-data](https://github.com/cvat-ai/cvat/tree/develop/cvat-data#versioning) and [cvat-ui](https://github.com/cvat-ai/cvat/tree/develop/cvat-ui#versioning))

### License

- [ ] I submit _my code changes_ under the same [MIT License](
  https://github.com/cvat-ai/cvat/blob/develop/LICENSE) that covers the project.
  Feel free to contact the maintainers if that's a concern.
  


Problem statement:
Create a lot of tasks with videos at the same time
I have about 240 videos which I want to tag. 

But to create a single task for each video is very time-consuming. I would like to upload several videos and edit them all together.

Hints from issue discussion:
@Dodoooh , you can use command line tools to create tasks semi-automatically https://github.com/opencv/cvat/tree/develop/utils/cli. But it makes sense to support uploading multiple videos in the future for "projects" functionality.
Any progress?
I'd be happy to see this feature too :-)
2020-6-3 loading......
I would also really like this feature given I have 1000+ different videos to label.
Does anyone have any suggestions on how to get around this besides using the cli tools?
I'd also like to be able to create one task with all of the videos in a folder. My solution was to convert all of the videos to `.jpg` files and put them in one folder, then upload these. You can watch them and annotate as if they were movies in cvat. 
@ActiveChooN , it looks like a feature for importing a dataset which contains a lot of videos. For each video we can create a separate task.
Will this be available for cloud storage options as well ? 
@zanjonke , it will. We are working on the feature right now in the repo: https://github.com/cvat-ai/cvat

Commit message: refactoring create queue in mutlitasks case

Patch to review:
diff --git a/Dockerfile b/Dockerfile
index 79fd92ca1d0f..770c65309746 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -84,6 +84,7 @@ RUN apt-get update && \
         libldap-2.4-2 \
         libsasl2-2 \
         libpython3-dev \
+        libmagic-dev \
         tzdata \
         python3-distutils \
         p7zip-full \
diff --git a/cvat-ui/src/actions/share-actions.ts b/cvat-ui/src/actions/share-actions.ts
index 25a4aed50f09..d64f586a87ca 100644
--- a/cvat-ui/src/actions/share-actions.ts
+++ b/cvat-ui/src/actions/share-actions.ts
@@ -28,16 +28,16 @@ const shareActions = {
 
 export type ShareActions = ActionUnion<typeof shareActions>;
 
-export function loadShareDataAsync(directory: string, success: () => void, failure: () => void): ThunkAction {
-    return async (dispatch): Promise<void> => {
+export function loadShareDataAsync(directory: string): ThunkAction {
+    return async (dispatch): Promise<ShareFileInfo[]> => {
         try {
             dispatch(shareActions.loadShareData());
             const values = await core.server.share(directory);
-            success();
             dispatch(shareActions.loadShareDataSuccess(values as ShareFileInfo[], directory));
+            return (values as ShareFileInfo[]);
         } catch (error) {
-            failure();
             dispatch(shareActions.loadShareDataFailed(error));
+            throw error;
         }
     };
 }
diff --git a/cvat-ui/src/actions/tasks-actions.ts b/cvat-ui/src/actions/tasks-actions.ts
index cb9667755910..bd3defd65ea1 100644
--- a/cvat-ui/src/actions/tasks-actions.ts
+++ b/cvat-ui/src/actions/tasks-actions.ts
@@ -21,10 +21,6 @@ export enum TasksActionTypes {
     DELETE_TASK = 'DELETE_TASK',
     DELETE_TASK_SUCCESS = 'DELETE_TASK_SUCCESS',
     DELETE_TASK_FAILED = 'DELETE_TASK_FAILED',
-    CREATE_TASK = 'CREATE_TASK',
-    CREATE_TASK_STATUS_UPDATED = 'CREATE_TASK_STATUS_UPDATED',
-    CREATE_TASK_SUCCESS = 'CREATE_TASK_SUCCESS',
-    CREATE_TASK_FAILED = 'CREATE_TASK_FAILED',
     UPDATE_TASK = 'UPDATE_TASK',
     UPDATE_TASK_SUCCESS = 'UPDATE_TASK_SUCCESS',
     UPDATE_TASK_FAILED = 'UPDATE_TASK_FAILED',
@@ -302,50 +298,9 @@ export function deleteTaskAsync(taskInstance: any): ThunkAction<Promise<void>, {
     };
 }
 
-function createTask(): AnyAction {
-    const action = {
-        type: TasksActionTypes.CREATE_TASK,
-        payload: {},
-    };
-
-    return action;
-}
-
-function createTaskSuccess(taskId: number): AnyAction {
-    const action = {
-        type: TasksActionTypes.CREATE_TASK_SUCCESS,
-        payload: {
-            taskId,
-        },
-    };
-
-    return action;
-}
-
-function createTaskFailed(error: any): AnyAction {
-    const action = {
-        type: TasksActionTypes.CREATE_TASK_FAILED,
-        payload: {
-            error,
-        },
-    };
-
-    return action;
-}
-
-function createTaskUpdateStatus(status: string): AnyAction {
-    const action = {
-        type: TasksActionTypes.CREATE_TASK_STATUS_UPDATED,
-        payload: {
-            status,
-        },
-    };
-
-    return action;
-}
-
-export function createTaskAsync(data: any): ThunkAction<Promise<void>, {}, {}, AnyAction> {
-    return async (dispatch: ActionCreator<Dispatch>): Promise<any> => {
+export function createTaskAsync(data: any, onProgress?: (status: string) => void):
+ThunkAction<Promise<void>, {}, {}, AnyAction> {
+    return async (): Promise<any> => {
         const description: any = {
             name: data.basic.name,
             labels: data.labels,
@@ -402,7 +357,7 @@ export function createTaskAsync(data: any): ThunkAction<Promise<void>, {}, {}, A
 
             if (gitPlugin) {
                 gitPlugin.callbacks.onStatusChange = (status: string): void => {
-                    dispatch(createTaskUpdateStatus(status));
+                    onProgress?.(status);
                 };
                 gitPlugin.data.task = taskInstance;
                 gitPlugin.data.repos = data.advanced.repository;
@@ -410,18 +365,10 @@ export function createTaskAsync(data: any): ThunkAction<Promise<void>, {}, {}, A
                 gitPlugin.data.lfs = data.advanced.lfs;
             }
         }
-
-        dispatch(createTask());
-        try {
-            const savedTask = await taskInstance.save((status: string, progress: number): void => {
-                dispatch(createTaskUpdateStatus(status + (progress !== null ? ` ${Math.floor(progress * 100)}%` : '')));
-            });
-            dispatch(createTaskSuccess(savedTask.id));
-            return savedTask;
-        } catch (error) {
-            dispatch(createTaskFailed(error));
-            throw error;
-        }
+        const savedTask = await taskInstance.save((status: string): void => {
+            onProgress?.(status);
+        });
+        return savedTask;
     };
 }
 
diff --git a/cvat-ui/src/assets/multi-plus-icon.svg b/cvat-ui/src/assets/multi-plus-icon.svg
new file mode 100644
index 000000000000..0f14698159e4
--- /dev/null
+++ b/cvat-ui/src/assets/multi-plus-icon.svg
@@ -0,0 +1,3 @@
+<svg width="20" height="15" viewBox="0 0 20 15" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+<path d="M11.6667 10.8333H13.3333V8.33333H15.8333V6.66667H13.3333V4.16667H11.6667V6.66667H9.16667V8.33333H11.6667V10.8333ZM5 14.5833C3.48611 14.0556 2.27444 13.1422 1.365 11.8433C0.455 10.545 0 9.09722 0 7.5C0 5.90278 0.455 4.45472 1.365 3.15583C2.27444 1.8575 3.48611 0.944444 5 0.416667V2.25C3.97222 2.73611 3.15972 3.45139 2.5625 4.39583C1.96528 5.34028 1.66667 6.375 1.66667 7.5C1.66667 8.625 1.96528 9.65972 2.5625 10.6042C3.15972 11.5486 3.97222 12.2639 5 12.75V14.5833ZM12.5 15C11.4583 15 10.4828 14.8022 9.57333 14.4067C8.66333 14.0106 7.87167 13.4756 7.19833 12.8017C6.52444 12.1283 5.98972 11.3367 5.59417 10.4267C5.19805 9.51722 5 8.54167 5 7.5C5 6.45833 5.19805 5.4825 5.59417 4.5725C5.98972 3.66306 6.52444 2.87139 7.19833 2.1975C7.87167 1.52417 8.66333 0.989444 9.57333 0.593333C10.4828 0.197778 11.4583 0 12.5 0C13.5417 0 14.5175 0.197778 15.4275 0.593333C16.3369 0.989444 17.1286 1.52417 17.8025 2.1975C18.4758 2.87139 19.0106 3.66306 19.4067 4.5725C19.8022 5.4825 20 6.45833 20 7.5C20 8.54167 19.8022 9.51722 19.4067 10.4267C19.0106 11.3367 18.4758 12.1283 17.8025 12.8017C17.1286 13.4756 16.3369 14.0106 15.4275 14.4067C14.5175 14.8022 13.5417 15 12.5 15ZM12.5 13.3333C14.125 13.3333 15.5033 12.7672 16.635 11.635C17.7672 10.5033 18.3333 9.125 18.3333 7.5C18.3333 5.875 17.7672 4.49639 16.635 3.36417C15.5033 2.2325 14.125 1.66667 12.5 1.66667C10.875 1.66667 9.49667 2.2325 8.365 3.36417C7.23278 4.49639 6.66667 5.875 6.66667 7.5C6.66667 9.125 7.23278 10.5033 8.365 11.635C9.49667 12.7672 10.875 13.3333 12.5 13.3333Z" />
+</svg>
diff --git a/cvat-ui/src/components/create-task-page/basic-configuration-form.tsx b/cvat-ui/src/components/create-task-page/basic-configuration-form.tsx
index a30f8b6aa1fb..1b99602e6960 100644
--- a/cvat-ui/src/components/create-task-page/basic-configuration-form.tsx
+++ b/cvat-ui/src/components/create-task-page/basic-configuration-form.tsx
@@ -4,15 +4,19 @@
 
 import React, { RefObject } from 'react';
 import Input from 'antd/lib/input';
+import Text from 'antd/lib/typography/Text';
+import Tooltip from 'antd/lib/tooltip';
 import Form, { FormInstance } from 'antd/lib/form';
-import { Store } from 'antd/lib/form/interface';
 
 export interface BaseConfiguration {
     name: string;
 }
 
 interface Props {
-    onSubmit(values: BaseConfiguration): void;
+    onChange(values: BaseConfiguration): void;
+    isMultiTask: boolean;
+    exampleMultiTaskName?: string;
+    defaultValue?: string;
 }
 
 export default class BasicConfigurationForm extends React.PureComponent<Props> {
@@ -25,13 +29,23 @@ export default class BasicConfigurationForm extends React.PureComponent<Props> {
         this.inputRef = React.createRef<Input>();
     }
 
+    componentDidMount(): void {
+        const { defaultValue, onChange } = this.props;
+        onChange({
+            name: defaultValue || '',
+        });
+    }
+
+    private handleChangeName(e: React.ChangeEvent<HTMLInputElement>): void {
+        const { onChange } = this.props;
+        onChange({
+            name: e.target.value,
+        });
+    }
+
     public submit(): Promise<void> {
-        const { onSubmit } = this.props;
         if (this.formRef.current) {
-            return this.formRef.current.validateFields().then((values: Store): Promise<void> => {
-                onSubmit({ name: values.name });
-                return Promise.resolve();
-            });
+            return this.formRef.current.validateFields();
         }
 
         return Promise.reject(new Error('Form ref is empty'));
@@ -50,6 +64,8 @@ export default class BasicConfigurationForm extends React.PureComponent<Props> {
     }
 
     public render(): JSX.Element {
+        const { isMultiTask, exampleMultiTaskName, defaultValue } = this.props;
+
         return (
             <Form ref={this.formRef} layout='vertical'>
                 <Form.Item
@@ -62,9 +78,46 @@ export default class BasicConfigurationForm extends React.PureComponent<Props> {
                             message: 'Task name cannot be empty',
                         },
                     ]}
+                    initialValue={defaultValue}
                 >
-                    <Input ref={this.inputRef} />
+                    <Input
+                        ref={this.inputRef}
+                        onChange={(e) => this.handleChangeName(e)}
+                    />
                 </Form.Item>
+                {isMultiTask ? (
+                    <Text type='secondary'>
+                        <Tooltip title={() => (
+                            <>
+                                You can substitute in the template:
+                                <ul>
+                                    <li>
+                                        some_text - any text
+                                    </li>
+                                    <li>
+                                        {'{{'}
+                                        index
+                                        {'}}'}
+                                        &nbsp;- index file in set
+                                    </li>
+                                    <li>
+                                        {'{{'}
+                                        file_name
+                                        {'}}'}
+                                        &nbsp;- name of file
+                                    </li>
+                                </ul>
+                                Example:&nbsp;
+                                <i>
+                                    {exampleMultiTaskName || 'Task name 1 - video_1.mp4'}
+                                </i>
+                            </>
+                        )}
+                        >
+                            When forming the name, a template is used.
+                        </Tooltip>
+                    </Text>
+                ) : null}
             </Form>
         );
     }
diff --git a/cvat-ui/src/components/create-task-page/create-task-content.tsx b/cvat-ui/src/components/create-task-page/create-task-content.tsx
index 49cc63f82f5a..b3e318f8da90 100644
--- a/cvat-ui/src/components/create-task-page/create-task-content.tsx
+++ b/cvat-ui/src/components/create-task-page/create-task-content.tsx
@@ -6,22 +6,33 @@ import React, { RefObject } from 'react';
 import { RouteComponentProps } from 'react-router';
 import { withRouter } from 'react-router-dom';
 import { Row, Col } from 'antd/lib/grid';
-import Alert from 'antd/lib/alert';
 import Button from 'antd/lib/button';
 import Collapse from 'antd/lib/collapse';
 import notification from 'antd/lib/notification';
 import Text from 'antd/lib/typography/Text';
+import Alert from 'antd/lib/alert';
 // eslint-disable-next-line import/no-extraneous-dependencies
 import { ValidateErrorEntity } from 'rc-field-form/lib/interface';
 
 import ConnectedFileManager from 'containers/file-manager/file-manager';
 import LabelsEditor from 'components/labels-editor/labels-editor';
 import { Files } from 'components/file-manager/file-manager';
+
+import {
+    getFileContentType,
+    getContentTypeRemoteFile,
+    getNameRemoteFile,
+    getNameShareFile,
+    getFileContentTypeByMimeType,
+} from 'utils/files';
+
 import BasicConfigurationForm, { BaseConfiguration } from './basic-configuration-form';
 import ProjectSearchField from './project-search-field';
 import ProjectSubsetField from './project-subset-field';
+import MultiTasksProgress from './multi-task-progress';
 import AdvancedConfigurationForm, { AdvancedConfiguration, SortingMethod } from './advanced-configuration-form';
 
+type TabName = 'local' | 'share' | 'remote' | 'cloudStorage';
 export interface CreateTaskData {
     projectId: number | null;
     basic: BaseConfiguration;
@@ -29,22 +40,28 @@ export interface CreateTaskData {
     advanced: AdvancedConfiguration;
     labels: any[];
     files: Files;
-    activeFileManagerTab: string;
+    activeFileManagerTab: TabName;
     cloudStorageId: number | null;
 }
 
 interface Props {
-    onCreate: (data: CreateTaskData) => void;
-    status: string;
-    taskId: number | null;
+    onCreate: (data: CreateTaskData, onProgress?: (status: string, progress?: number) => void) => Promise<any>;
     projectId: number | null;
     installedGit: boolean;
-    dumpers:[]
+    dumpers:[];
+    isMultiTask: boolean;
 }
 
-type State = CreateTaskData;
+type State = CreateTaskData & {
+    multiTasks: (CreateTaskData & {
+        status: 'pending' | 'progress' | 'failed' | 'completed' | 'cancelled';
+    })[];
+    uploadFileErrorMessage: string;
+    loading: boolean;
+    statusInProgressTask: string;
+};
 
-const defaultState = {
+const defaultState: State = {
     projectId: null,
     basic: {
         name: '',
@@ -65,6 +82,10 @@ const defaultState = {
     },
     activeFileManagerTab: 'local',
     cloudStorageId: null,
+    multiTasks: [],
+    uploadFileErrorMessage: '',
+    loading: false,
+    statusInProgressTask: '',
 };
 
 class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps, State> {
@@ -124,6 +145,24 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
         return !!totalLen;
     };
 
+    private startLoading = (): void => {
+        this.setState({
+            loading: true,
+        });
+    };
+
+    private stopLoading = (): void => {
+        this.setState({
+            loading: false,
+        });
+    };
+
+    private changeStatusInProgressTask = (status: string): void => {
+        this.setState({
+            statusInProgressTask: status,
+        });
+    };
+
     private handleProjectIdChange = (value: null | number): void => {
         const { projectId, subset } = this.state;
 
@@ -134,7 +173,7 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
         }));
     };
 
-    private handleSubmitBasicConfiguration = (values: BaseConfiguration): void => {
+    private handleChangeBasicConfiguration = (values: BaseConfiguration): void => {
         this.setState({
             basic: { ...values },
         });
@@ -152,11 +191,9 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
         });
     };
 
-    private changeFileManagerTab = (key: string): void => {
-        const values = this.state;
+    private changeFileManagerTab = (value: TabName): void => {
         this.setState({
-            ...values,
-            activeFileManagerTab: key,
+            activeFileManagerTab: value,
         });
     };
 
@@ -164,31 +201,113 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
         this.basicConfigurationComponent.current?.focus();
     };
 
-    private handleSubmitAndOpen = (): void => {
-        const { history } = this.props;
+    private handleUploadLocalFiles = (uploadedFiles: File[]): void => {
+        const { isMultiTask } = this.props;
+        const { files } = this.state;
 
-        this.handleSubmit()
-            .then((createdTask) => {
-                const { id } = createdTask;
-                history.push(`/tasks/${id}`);
-            })
-            .catch(() => {});
+        let uploadFileErrorMessage = '';
+
+        if (!isMultiTask && uploadedFiles.length > 1) {
+            uploadFileErrorMessage = uploadedFiles.every((it) => (getFileContentType(it) === 'image' || it.name === 'manifest.jsonl')) ? '' : 'We can\'t process it. Support for a bulk image or single video';
+        } else if (isMultiTask && uploadedFiles.length > 1) {
+            uploadFileErrorMessage = uploadedFiles.every((it) => getFileContentType(it) === 'video') ? '' : 'We can\'t process it. Support for a bulk videos';
+        }
+
+        this.setState({
+            uploadFileErrorMessage,
+        });
+
+        if (!uploadFileErrorMessage) {
+            this.setState({
+                files: {
+                    ...files,
+                    local: uploadedFiles,
+                },
+            });
+        }
     };
 
-    private handleSubmitAndContinue = (): void => {
-        this.handleSubmit()
-            .then(() => {
-                notification.info({
-                    message: 'The task has been created',
-                    className: 'cvat-notification-create-task-success',
-                });
-            })
-            .then(this.resetState)
-            .then(this.focusToForm)
-            .catch(() => {});
+    private handleUploadRemoteFiles = async (urls: string[]): Promise<void> => {
+        const { isMultiTask } = this.props;
+
+        const { files } = this.state;
+        const { length } = urls;
+
+        let uploadFileErrorMessage = '';
+
+        try {
+            if (!isMultiTask && length > 1) {
+                let index = 0;
+                while (index < length) {
+                    const isImageFile = await getContentTypeRemoteFile(urls[index]) === 'image';
+                    if (!isImageFile) {
+                        uploadFileErrorMessage = 'We can\'t process it. Support for a bulk image or single video';
+                        break;
+                    }
+                    index++;
+                }
+            } else if (isMultiTask && length > 1) {
+                let index = 0;
+                while (index < length) {
+                    const isVideoFile = await getContentTypeRemoteFile(urls[index]) === 'video';
+                    if (!isVideoFile) {
+                        uploadFileErrorMessage = 'We can\'t process it. Support for a bulk videos';
+                        break;
+                    }
+                    index++;
+                }
+            }
+        } catch (err) {
+            uploadFileErrorMessage = `We can't process it. ${err}`;
+        }
+
+        this.setState({
+            uploadFileErrorMessage,
+        });
+
+        if (!uploadFileErrorMessage) {
+            this.setState({
+                files: {
+                    ...files,
+                    remote: urls,
+                },
+            });
+        }
+    };
+
+    private handleUploadShareFiles = (shareFiles: {
+        key: string;
+        type: string;
+        mime_type: string;
+    }[]): void => {
+        const { isMultiTask } = this.props;
+        const { files } = this.state;
+
+        let uploadFileErrorMessage = '';
+
+        if (!isMultiTask && shareFiles.length > 1) {
+            uploadFileErrorMessage = shareFiles.every((it) => getFileContentTypeByMimeType(it.mime_type) === 'image') ?
+                '' : 'We can\'t process it. Support for a bulk image or single video';
+        } else if (isMultiTask && shareFiles.length > 1) {
+            uploadFileErrorMessage = shareFiles.every((it) => getFileContentTypeByMimeType(it.mime_type) === 'video') ?
+                '' : 'We can\'t process it. Support for a bulk videos';
+        }
+
+        this.setState({
+            uploadFileErrorMessage,
+        });
+
+        if (!uploadFileErrorMessage) {
+            this.setState({
+                files: {
+                    ...files,
+                    share: shareFiles.map((it) => it.key),
+                },
+            });
+        }
     };
 
-    private handleSubmit = (): Promise<any> => new Promise((resolve, reject) => {
+    private validateBlocks = (): Promise<any> => new Promise((resolve, reject) => {
         if (!this.validateLabelsOrProject()) {
             notification.error({
                 message: 'Could not create a task',
@@ -222,12 +341,53 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
                 }
                 return Promise.resolve();
             })
-            .then((): void => {
-                const { onCreate } = this.props;
-                return onCreate(this.state);
+            .then(resolve)
+            .catch((error: Error | ValidateErrorEntity): void => {
+                notification.error({
+                    message: 'Could not create a task',
+                    description: (error as ValidateErrorEntity).errorFields ?
+                        (error as ValidateErrorEntity).errorFields
+                            .map((field) => `${field.name} : ${field.errors.join(';')}`)
+                            .map((text: string): JSX.Element => <div>{text}</div>) :
+                        error.toString(),
+                    className: 'cvat-notification-create-task-fail',
+                });
+                reject(error);
+            });
+    });
+
+    private handleSubmitAndOpen = (): void => {
+        const { history } = this.props;
+
+        this.validateBlocks()
+            .then(this.createOneTask)
+            .then((createdTask) => {
+                const { id } = createdTask;
+                history.push(`/tasks/${id}`);
+            })
+            .catch(() => {});
+    };
+
+    private handleSubmitAndContinue = (): void => {
+        this.validateBlocks()
+            .then(this.createOneTask)
+            .then(() => {
+                notification.info({
+                    message: 'The task has been created',
+                    className: 'cvat-notification-create-task-success',
+                });
             })
-            .then((cratedTask) => {
-                resolve(cratedTask);
+            .then(this.resetState)
+            .then(this.focusToForm)
+            .catch(() => {});
+    };
+
+    private createOneTask = (): Promise<any> => new Promise((resolve, reject) => {
+        const { onCreate } = this.props;
+        this.startLoading();
+        onCreate(this.state, this.changeStatusInProgressTask)
+            .then((createdTask) => {
+                resolve(createdTask);
             })
             .catch((error: Error | ValidateErrorEntity): void => {
                 notification.error({
@@ -239,16 +399,239 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
                         error.toString(),
                     className: 'cvat-notification-create-task-fail',
                 });
-                reject(error);
+                reject();
+            })
+            .finally(this.stopLoading);
+    });
+
+    private setStatusOneOfMultiTasks = async (index: number, status: string): Promise<void> => {
+        const { multiTasks } = this.state;
+        const resultTask = {
+            ...multiTasks[index],
+            status,
+        };
+
+        return new Promise((resolve) => {
+            const newMultiTasks: any = [
+                ...multiTasks.slice(0, index),
+                resultTask,
+                ...multiTasks.slice(index + 1),
+            ];
+            this.setState({
+                multiTasks: newMultiTasks,
+            }, resolve);
+        });
+    };
+
+    private createOneOfMultiTasks = async (index: any): Promise<void> => {
+        const { onCreate } = this.props;
+        const { multiTasks } = this.state;
+        const task = multiTasks[index];
+
+        if (task.status !== 'pending') return;
+
+        await this.setStatusOneOfMultiTasks(index, 'progress');
+        try {
+            await onCreate(task);
+            await this.setStatusOneOfMultiTasks(index, 'completed');
+        } catch (err) {
+            console.warn(err);
+            await this.setStatusOneOfMultiTasks(index, 'failed');
+        }
+    };
+
+    private createMultiTasks = async (): Promise<any> => {
+        const { multiTasks } = this.state;
+        this.startLoading();
+        const { length } = multiTasks;
+        let index = 0;
+        const queueSize = 2;
+        const promises = Array(queueSize)
+            .fill(undefined)
+            .map(async (): Promise<void> => {
+                while (true) {
+                    index++; // preliminary increase is needed to avoid using the same index when queueSize > 1
+                    if (index > length) break;
+                    await this.createOneOfMultiTasks(index - 1);
+                }
             });
+        await Promise.allSettled(promises);
+        this.stopLoading();
+    };
+
+    private addMultiTasks = async (): Promise<void> => new Promise((resolve) => {
+        const {
+            projectId,
+            subset,
+            advanced,
+            labels,
+            files: allFiles,
+            activeFileManagerTab,
+            cloudStorageId,
+        } = this.state;
+
+        const files: { file: (File | string), type: TabName }[] = Object
+            .entries(allFiles)
+            .reduce((acc: { file: (File | string), type: TabName }[], prevValue) => {
+                const [tabName, setFiles] = prevValue;
+                return [
+                    ...acc,
+                    ...setFiles.map((it: (File | string)) => ({ file: it, type: tabName })),
+                ];
+            }, []);
+
+        this.setState({
+            multiTasks: files.map(({ file, type }, index) => ({
+                projectId,
+                basic: {
+                    name: this.getTaskName(index, type),
+                },
+                subset,
+                advanced,
+                labels,
+                files: {
+                    ...defaultState.files,
+                    [type]: [file],
+                },
+                activeFileManagerTab,
+                cloudStorageId,
+                status: 'pending',
+            }
+            )),
+        }, resolve);
     });
 
+    private handleSubmitMutliTasks = (): void => {
+        this.validateBlocks()
+            .then(() => {
+                this.addMultiTasks();
+            })
+            .then(() => {
+                notification.info({
+                    message: 'Tasks started to be created',
+                    className: 'cvat-notification-create-task-success',
+                });
+            })
+            .then(this.createMultiTasks)
+            .then(() => {
+                const { multiTasks } = this.state;
+                const countCompleted = multiTasks.filter((item) => item.status === 'completed').length;
+                const countFailed = multiTasks.filter((item) => item.status === 'failed').length;
+                const countCancelled = multiTasks.filter((item) => item.status === 'cancelled').length;
+                const countAll = multiTasks.length;
+
+                notification.info({
+                    message: 'The tasks have been created',
+                    description:
+                        `Completed: ${countCompleted}, failed: ${countFailed},${countCancelled ?
+                            ` cancelled: ${countCancelled},` :
+                            ''} total: ${countAll}, `,
+                    className: 'cvat-notification-create-task-success',
+                });
+            });
+    };
+
+    private handleCancelMultiTasks = (): void => {
+        const { multiTasks } = this.state;
+        let count = 0;
+        const newMultiTasks: any = multiTasks.map((it) => {
+            if (it.status === 'pending') {
+                count++;
+                return {
+                    ...it,
+                    status: 'cancelled',
+                };
+            }
+            return it;
+        });
+        this.setState({
+            multiTasks: newMultiTasks,
+        }, () => {
+            notification.info({
+                message: `Creation of ${count} tasks have been canceled`,
+                className: 'cvat-notification-create-task-success',
+            });
+        });
+    };
+
+    private handleOkMultiTasks = (): void => {
+        const { history } = this.props;
+        history.push('/tasks/');
+    };
+
+    private handleRetryCancelledMultiTasks = (): void => {
+        const { multiTasks } = this.state;
+        const newMultiTasks: any = multiTasks.map((it) => {
+            if (it.status === 'cancelled') {
+                return {
+                    ...it,
+                    status: 'pending',
+                };
+            }
+            return it;
+        });
+        this.setState({
+            multiTasks: newMultiTasks,
+        }, () => {
+            this.createMultiTasks();
+        });
+    };
+
+    private handleRetryFailedMultiTasks = (): void => {
+        const { multiTasks } = this.state;
+        const newMultiTasks: any = multiTasks.map((it) => {
+            if (it.status === 'failed') {
+                return {
+                    ...it,
+                    status: 'pending',
+                };
+            }
+            return it;
+        });
+        this.setState({
+            multiTasks: newMultiTasks,
+        }, () => {
+            this.createMultiTasks();
+        });
+    };
+
+    private getTaskName = (indexFile: number, fileManagerTabName: TabName, defaultFileName = ''): string => {
+        const { isMultiTask } = this.props;
+        const { basic } = this.state;
+        const { files } = this.state;
+        const file = files[fileManagerTabName][indexFile];
+        let fileName = defaultFileName;
+        switch (fileManagerTabName) {
+            case 'remote':
+                fileName = getNameRemoteFile(file as string) || defaultFileName;
+                break;
+            case 'share':
+                fileName = getNameShareFile(file as string) || defaultFileName; // TODO
+                break;
+            default:
+                fileName = (file as File)?.name || (file as string) || defaultFileName;
+                break;
+        }
+        return isMultiTask ?
+            basic.name
+                .replaceAll('{{file_name}}', fileName)
+                .replaceAll('{{index}}', indexFile.toString()) :
+            basic.name;
+    };
+
     private renderBasicBlock(): JSX.Element {
+        const { isMultiTask } = this.props;
+        const exampleMultiTaskName = isMultiTask ? this.getTaskName(0, 'local', 'fileName.mp4') : '';
+        const defaultValue = isMultiTask ? '{{file_name}}' : '';
+
         return (
             <Col span={24}>
                 <BasicConfigurationForm
                     ref={this.basicConfigurationComponent}
-                    onSubmit={this.handleSubmitBasicConfiguration}
+                    isMultiTask={isMultiTask}
+                    defaultValue={defaultValue}
+                    exampleMultiTaskName={exampleMultiTaskName}
+                    onChange={this.handleChangeBasicConfiguration}
                 />
             </Col>
         );
@@ -325,17 +708,36 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
     }
 
     private renderFilesBlock(): JSX.Element {
+        const { isMultiTask } = this.props;
+        const { uploadFileErrorMessage } = this.state;
+
         return (
-            <Col span={24}>
-                <Text type='danger'>* </Text>
-                <Text className='cvat-text-color'>Select files</Text>
-                <ConnectedFileManager
-                    onChangeActiveKey={this.changeFileManagerTab}
-                    ref={(container: any): void => {
-                        this.fileManagerContainer = container;
-                    }}
-                />
-            </Col>
+            <>
+                <Col span={24}>
+                    <Text type='danger'>* </Text>
+                    <Text className='cvat-text-color'>Select files</Text>
+                    <ConnectedFileManager
+                        isMultiTask={isMultiTask}
+                        onChangeActiveKey={this.changeFileManagerTab}
+                        onUploadLocalFiles={this.handleUploadLocalFiles}
+                        onUploadRemoteFiles={this.handleUploadRemoteFiles}
+                        onUploadShareFiles={this.handleUploadShareFiles}
+                        ref={(container: any): void => {
+                            this.fileManagerContainer = container;
+                        }}
+                    />
+                </Col>
+                { uploadFileErrorMessage ? (
+                    <Col span={24}>
+                        <Alert
+                            className='cvat-create-task-content-alert'
+                            type='error'
+                            message={uploadFileErrorMessage}
+                            showIcon
+                        />
+                    </Col>
+                ) : null }
+            </>
         );
     }
 
@@ -359,16 +761,21 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
         );
     }
 
-    private renderActions(): JSX.Element {
+    private renderFooterSingleTask(): JSX.Element {
+        const { uploadFileErrorMessage, loading, statusInProgressTask: status } = this.state;
+
+        if (status === 'FAILED' || loading) {
+            return (<Alert message={status} />);
+        }
         return (
             <Row justify='end' gutter={5}>
                 <Col>
-                    <Button type='primary' onClick={this.handleSubmitAndOpen}>
+                    <Button type='primary' onClick={this.handleSubmitAndOpen} disabled={!!uploadFileErrorMessage}>
                         Submit & Open
                     </Button>
                 </Col>
                 <Col>
-                    <Button type='primary' onClick={this.handleSubmitAndContinue}>
+                    <Button type='primary' onClick={this.handleSubmitAndContinue} disabled={!!uploadFileErrorMessage}>
                         Submit & Continue
                     </Button>
                 </Col>
@@ -376,9 +783,45 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
         );
     }
 
+    private renderFooterMutliTasks(): JSX.Element {
+        const {
+            multiTasks: items,
+            uploadFileErrorMessage,
+            files,
+            activeFileManagerTab,
+            loading,
+        } = this.state;
+        const currentFiles = files[activeFileManagerTab];
+        const countPending = items.filter((item) => item.status === 'pending').length;
+        const countAll = items.length;
+
+        if ((loading || countPending !== countAll) && currentFiles.length) {
+            return (
+                <MultiTasksProgress
+                    tasks={items}
+                    onOk={this.handleOkMultiTasks}
+                    onCancel={this.handleCancelMultiTasks}
+                    onRetryFailedTasks={this.handleRetryFailedMultiTasks}
+                    onRetryCancelledTasks={this.handleRetryCancelledMultiTasks}
+                />
+            );
+        }
+
+        return (
+            <Row justify='end' gutter={5}>
+                <Col>
+                    <Button type='primary' onClick={this.handleSubmitMutliTasks} disabled={!!uploadFileErrorMessage}>
+                        Submit&nbsp;
+                        {currentFiles.length}
+                        &nbsp;tasks
+                    </Button>
+                </Col>
+            </Row>
+        );
+    }
+
     public render(): JSX.Element {
-        const { status } = this.props;
-        const loading = !!status && status !== 'CREATED' && status !== 'FAILED';
+        const { isMultiTask } = this.props;
 
         return (
             <Row justify='start' align='middle' className='cvat-create-task-content'>
@@ -394,7 +837,7 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
                 {this.renderAdvancedBlock()}
 
                 <Col span={24} className='cvat-create-task-content-footer'>
-                    {loading ? <Alert message={status} /> : this.renderActions()}
+                    {isMultiTask ? this.renderFooterMutliTasks() : this.renderFooterSingleTask() }
                 </Col>
             </Row>
         );
diff --git a/cvat-ui/src/components/create-task-page/create-task-page.tsx b/cvat-ui/src/components/create-task-page/create-task-page.tsx
index 31d242d45d22..e3ed3d21e18d 100644
--- a/cvat-ui/src/components/create-task-page/create-task-page.tsx
+++ b/cvat-ui/src/components/create-task-page/create-task-page.tsx
@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: MIT
 
 import './styles.scss';
-import React, { useEffect } from 'react';
+import React, { useEffect, useState } from 'react';
 import { useLocation } from 'react-router';
 import { Row, Col } from 'antd/lib/grid';
 import Modal from 'antd/lib/modal';
@@ -14,26 +14,30 @@ import TextArea from 'antd/lib/input/TextArea';
 import CreateTaskContent, { CreateTaskData } from './create-task-content';
 
 interface Props {
-    onCreate: (data: CreateTaskData) => void;
-    status: string;
-    error: string;
-    taskId: number | null;
+    onCreate: (data: CreateTaskData, onProgress?: (status: string) => void) => Promise<any>;
     installedGit: boolean;
     dumpers: []
 }
 
 export default function CreateTaskPage(props: Props): JSX.Element {
     const {
-        error, status, taskId, onCreate, installedGit, dumpers,
+        onCreate, installedGit, dumpers,
     } = props;
 
     const location = useLocation();
+    const [error, setError] = useState('');
 
     let projectId = null;
     const params = new URLSearchParams(location.search);
     if (params.get('projectId')?.match(/^[1-9]+[0-9]*$/)) {
         projectId = +(params.get('projectId') as string);
     }
+    const isMultiTask = params.get('many') === 'true';
+    const handleCreate: typeof onCreate = (...onCreateParams) => onCreate(...onCreateParams)
+        .catch((err) => {
+            setError(err);
+            throw err;
+        });
 
     useEffect(() => {
         if (error) {
@@ -75,12 +79,11 @@ export default function CreateTaskPage(props: Props): JSX.Element {
             <Col md={20} lg={16} xl={14} xxl={9}>
                 <Text className='cvat-title'>Create a new task</Text>
                 <CreateTaskContent
-                    taskId={taskId}
                     projectId={projectId}
-                    status={status}
-                    onCreate={onCreate}
+                    onCreate={handleCreate}
                     installedGit={installedGit}
                     dumpers={dumpers}
+                    isMultiTask={isMultiTask}
                 />
             </Col>
         </Row>
diff --git a/cvat-ui/src/components/create-task-page/multi-task-progress.tsx b/cvat-ui/src/components/create-task-page/multi-task-progress.tsx
new file mode 100644
index 000000000000..61ff19cf982f
--- /dev/null
+++ b/cvat-ui/src/components/create-task-page/multi-task-progress.tsx
@@ -0,0 +1,154 @@
+// Copyright (C) 2022 Intel Corporation
+//
+// SPDX-License-Identifier: MIT
+
+import React from 'react';
+import Alert from 'antd/lib/alert';
+import Progress from 'antd/lib/progress';
+import Row from 'antd/lib/row';
+import Col from 'antd/lib/col';
+import Button from 'antd/lib/button';
+import Collapse from 'antd/lib/collapse';
+import Text from 'antd/lib/typography/Text';
+import List from 'antd/lib/list';
+
+interface Props {
+    tasks: any[];
+    onCancel: () => void;
+    onOk: () => void;
+    onRetryFailedTasks: () => void;
+    onRetryCancelledTasks: () => void;
+}
+
+export default function MultiTasksProgress(props: Props): JSX.Element {
+    const {
+        tasks: items,
+        onOk,
+        onCancel,
+        onRetryFailedTasks,
+        onRetryCancelledTasks,
+    } = props;
+    let alertType: any = 'info';
+
+    const countPending = items.filter((item) => item.status === 'pending').length;
+    const countProgress = items.filter((item) => item.status === 'progress').length;
+    const countCompleted = items.filter((item) => item.status === 'completed').length;
+    const countFailed = items.filter((item) => item.status === 'failed').length;
+    const countCancelled = items.filter((item) => item.status === 'cancelled').length;
+    const countAll = items.length;
+    const percent = countAll ?
+        Math.ceil(((countAll - (countPending + countProgress)) / countAll) * 100) :
+        0;
+    const failedFiles: string[] = percent === 100 && countFailed ?
+        items.filter((item) => item.status === 'failed').map((item) => item.files.local[0].name) :
+        [];
+
+    if (percent === 100) {
+        if (countFailed === countAll) {
+            alertType = 'error';
+        } else if (countFailed) {
+            alertType = 'warning';
+        }
+    }
+
+    return (
+        <Alert
+            type={alertType}
+            message={(
+                <div>
+                    {percent === 100 ? (
+                        <>
+                            Finished
+                            <br />
+                        </>
+                    ) :
+                        null}
+                    pending:
+                    {countPending}
+                    ,
+                    progress:
+                    {countProgress}
+                    ,
+                    completed:
+                    {countCompleted}
+                    ,
+                    failed:
+                    {countFailed}
+                    {
+                        countCancelled ?
+                            ` cancelled: ${countCancelled}, ` :
+                            null
+                    }
+                    ,
+                    total:
+                    {countAll}
+                    .
+                    <Progress
+                        status='normal'
+                        percent={percent}
+                        strokeWidth={5}
+                        size='small'
+                        trailColor='#d8d8d8'
+                    />
+                    <br />
+                    {percent === 100 && countFailed ? (
+                        <Row>
+                            <Collapse style={{
+                                width: '100%',
+                                marginBottom: 5,
+                            }}
+                            >
+                                <Collapse.Panel
+                                    header={(
+                                        <Text strong>
+                                            Failed files
+                                        </Text>
+                                    )}
+                                    key='appearance'
+                                >
+                                    <List
+                                        size='small'
+                                        dataSource={failedFiles}
+                                        renderItem={(item: string) => <List.Item>{ item }</List.Item>}
+                                    />
+                                </Collapse.Panel>
+                            </Collapse>
+                        </Row>
+                    ) : null }
+                    <Row justify='end' gutter={5}>
+                        {percent === 100 ?
+                            (
+                                <>
+                                    <Col>
+                                        <Button disabled={!countFailed} onClick={onRetryFailedTasks}>
+                                            Retry failed tasks
+                                        </Button>
+                                    </Col>
+                                    {
+                                        countCancelled ? (
+                                            <Col>
+                                                <Button disabled={!countCancelled} onClick={onRetryCancelledTasks}>
+                                                    Retry cancelled tasks
+                                                </Button>
+                                            </Col>
+                                        ) : null
+                                    }
+                                    <Col>
+                                        <Button type='primary' onClick={onOk}>
+                                            Ok
+                                        </Button>
+                                    </Col>
+                                </>
+                            ) : (
+                                <Col>
+                                    <Button onClick={onCancel}>
+                                        Cancel
+                                    </Button>
+                                </Col>
+                            )}
+                    </Row>
+                </div>
+            )}
+        />
+    );
+}
diff --git a/cvat-ui/src/components/file-manager/file-manager.tsx b/cvat-ui/src/components/file-manager/file-manager.tsx
index ebabc7a89483..606a17e68bfc 100644
--- a/cvat-ui/src/components/file-manager/file-manager.tsx
+++ b/cvat-ui/src/components/file-manager/file-manager.tsx
@@ -9,17 +9,17 @@ import Tabs from 'antd/lib/tabs';
 import Input from 'antd/lib/input';
 import Text from 'antd/lib/typography/Text';
 import Paragraph from 'antd/lib/typography/Paragraph';
-import Upload, { RcFile } from 'antd/lib/upload';
+import { RcFile } from 'antd/lib/upload';
 import Empty from 'antd/lib/empty';
 import Tree, { TreeNodeNormal } from 'antd/lib/tree/Tree';
 import { FormInstance } from 'antd/lib/form';
 // eslint-disable-next-line import/no-extraneous-dependencies
 import { EventDataNode } from 'rc-tree/lib/interface';
-import { InboxOutlined } from '@ant-design/icons';
 
 import consts from 'consts';
 import { CloudStorage } from 'reducers';
 import CloudStorageTab from './cloud-storages-tab';
+import LocalFiles from './local-files';
 
 export interface Files {
     local: File[];
@@ -37,9 +37,13 @@ interface State {
 }
 
 interface Props {
-    treeData: TreeNodeNormal[];
-    onLoadData: (key: string, success: () => void, failure: () => void) => void;
+    treeData: (TreeNodeNormal & { mime_type: string })[];
+    isMultiTask: boolean;
+    onLoadData: (key: string) => Promise<any>;
     onChangeActiveKey(key: string): void;
+    onUploadLocalFiles(files: File[]): void;
+    onUploadRemoteFiles(urls: string[]): void;
+    onUploadShareFiles(keys: string[]): Promise<void>;
 }
 
 export class FileManager extends React.PureComponent<Props, State> {
@@ -48,6 +52,7 @@ export class FileManager extends React.PureComponent<Props, State> {
     public constructor(props: Props) {
         super(props);
         this.cloudStorageTabFormRef = React.createRef<FormInstance>();
+        const { onLoadData } = this.props;
 
         this.state = {
             files: {
@@ -62,7 +67,7 @@ export class FileManager extends React.PureComponent<Props, State> {
             active: 'local',
         };
 
-        this.loadData('/');
+        onLoadData('/');
     }
 
     private onSelectCloudStorageFiles = (cloudStorageFiles: string[]): void => {
@@ -90,14 +95,6 @@ export class FileManager extends React.PureComponent<Props, State> {
         };
     }
 
-    private loadData = (key: string): Promise<void> => new Promise<void>((resolve, reject): void => {
-        const { onLoadData } = this.props;
-
-        const success = (): void => resolve();
-        const failure = (): void => reject();
-        onLoadData(key, success, failure);
-    });
-
     public reset(): void {
         const { active } = this.state;
         if (active === 'cloudStorage') {
@@ -118,65 +115,43 @@ export class FileManager extends React.PureComponent<Props, State> {
     }
 
     private renderLocalSelector(): JSX.Element {
+        const { isMultiTask, onUploadLocalFiles } = this.props;
         const { files } = this.state;
 
         return (
             <Tabs.TabPane className='cvat-file-manager-local-tab' key='local' tab='My computer'>
-                <Upload.Dragger
-                    multiple
-                    listType='text'
-                    fileList={files.local as any[]}
-                    showUploadList={
-                        files.local.length < 5 && {
-                            showRemoveIcon: false,
-                        }
-                    }
-                    beforeUpload={(_: RcFile, newLocalFiles: RcFile[]): boolean => {
+                <LocalFiles
+                    files={files.local}
+                    isMultiTask={isMultiTask}
+                    onUpload={(_: RcFile, newLocalFiles: RcFile[]): boolean => {
                         this.setState({
                             files: {
                                 ...files,
                                 local: newLocalFiles,
                             },
                         });
+                        onUploadLocalFiles(newLocalFiles);
                         return false;
                     }}
-                >
-                    <p className='ant-upload-drag-icon'>
-                        <InboxOutlined />
-                    </p>
-                    <p className='ant-upload-text'>Click or drag files to this area</p>
-                    <p className='ant-upload-hint'>Support for a bulk images or a single video</p>
-                </Upload.Dragger>
-                {files.local.length >= 5 && (
-                    <>
-                        <br />
-                        <Text className='cvat-text-color'>{`${files.local.length} files selected`}</Text>
-                    </>
-                )}
+                />
             </Tabs.TabPane>
         );
     }
 
     private renderShareSelector(): JSX.Element {
-        function renderTreeNodes(data: TreeNodeNormal[]): JSX.Element[] {
+        function getTreeNodes(data: TreeNodeNormal[]): TreeNodeNormal[] {
             // sort alphabetically
-            data.sort((a: TreeNodeNormal, b: TreeNodeNormal): number => (
-                a.key.toLocaleString().localeCompare(b.key.toLocaleString())));
-            return data.map((item: TreeNodeNormal) => {
-                if (item.children) {
-                    return (
-                        <Tree.TreeNode title={item.title} key={item.key} data={item} isLeaf={item.isLeaf}>
-                            {renderTreeNodes(item.children)}
-                        </Tree.TreeNode>
-                    );
-                }
-
-                return <Tree.TreeNode {...item} key={item.key} data={item} />;
-            });
+            return data
+                .sort((a: TreeNodeNormal, b: TreeNodeNormal): number => (
+                    a.key.toLocaleString().localeCompare(b.key.toLocaleString())))
+                .map((it) => ({
+                    ...it,
+                    children: it.children ? getTreeNodes(it.children) : undefined,
+                }));
         }
 
         const { SHARE_MOUNT_GUIDE_URL } = consts;
-        const { treeData } = this.props;
+        const { treeData, onUploadShareFiles, onLoadData } = this.props;
         const { expandedKeys, files } = this.state;
 
         return (
@@ -190,7 +165,7 @@ export class FileManager extends React.PureComponent<Props, State> {
                         checkStrictly={false}
                         expandedKeys={expandedKeys}
                         checkedKeys={files.share}
-                        loadData={(event: EventDataNode): Promise<void> => this.loadData(event.key.toLocaleString())}
+                        loadData={(event: EventDataNode): Promise<void> => onLoadData(event.key.toLocaleString())}
                         onExpand={(newExpandedKeys: ReactText[]): void => {
                             this.setState({
                                 expandedKeys: newExpandedKeys.map((text: ReactText): string => text.toLocaleString()),
@@ -212,10 +187,10 @@ export class FileManager extends React.PureComponent<Props, State> {
                                     share: keys,
                                 },
                             });
+                            onUploadShareFiles(keys).then().catch();
                         }}
-                    >
-                        {renderTreeNodes(treeData)}
-                    </Tree>
+                        treeData={getTreeNodes(treeData)}
+                    />
                 ) : (
                     <div className='cvat-empty-share-tree'>
                         <Empty />
@@ -233,6 +208,7 @@ export class FileManager extends React.PureComponent<Props, State> {
     }
 
     private renderRemoteSelector(): JSX.Element {
+        const { onUploadRemoteFiles } = this.props;
         const { files } = this.state;
 
         return (
@@ -243,12 +219,14 @@ export class FileManager extends React.PureComponent<Props, State> {
                     rows={6}
                     value={[...files.remote].join('\n')}
                     onChange={(event: React.ChangeEvent<HTMLTextAreaElement>): void => {
+                        const urls = event.target.value.split('\n');
                         this.setState({
                             files: {
                                 ...files,
-                                remote: event.target.value.split('\n'),
+                                remote: urls,
                             },
                         });
+                        onUploadRemoteFiles(urls.filter(Boolean));
                     }}
                 />
             </Tabs.TabPane>
@@ -281,7 +259,7 @@ export class FileManager extends React.PureComponent<Props, State> {
     }
 
     public render(): JSX.Element {
-        const { onChangeActiveKey } = this.props;
+        const { onChangeActiveKey, isMultiTask } = this.props;
         const { active } = this.state;
 
         return (
@@ -300,7 +278,7 @@ export class FileManager extends React.PureComponent<Props, State> {
                     {this.renderLocalSelector()}
                     {this.renderShareSelector()}
                     {this.renderRemoteSelector()}
-                    {this.renderCloudStorageSelector()}
+                    {!isMultiTask && this.renderCloudStorageSelector()}
                 </Tabs>
             </>
         );
diff --git a/cvat-ui/src/components/file-manager/local-files.tsx b/cvat-ui/src/components/file-manager/local-files.tsx
new file mode 100644
index 000000000000..cae3de7982ef
--- /dev/null
+++ b/cvat-ui/src/components/file-manager/local-files.tsx
@@ -0,0 +1,47 @@
+// Copyright (C) 2022 Intel Corporation
+//
+// SPDX-License-Identifier: MIT
+import React from 'react';
+
+import Text from 'antd/lib/typography/Text';
+import Upload, { RcFile } from 'antd/lib/upload';
+import { InboxOutlined } from '@ant-design/icons';
+
+interface Props {
+    files: File[];
+    isMultiTask: boolean;
+    onUpload: (_: RcFile, uploadedFiles: RcFile[]) => boolean;
+}
+
+export default function LocalFiles(props: Props): JSX.Element {
+    const { files, onUpload, isMultiTask } = props;
+    const hintText = isMultiTask ? 'Support for a bulk videos' : 'Support for a bulk images or a single video';
+
+    return (
+        <>
+            <Upload.Dragger
+                multiple
+                listType='text'
+                fileList={files as any[]}
+                showUploadList={
+                    files.length < 5 && {
+                        showRemoveIcon: false,
+                    }
+                }
+                beforeUpload={onUpload}
+            >
+                <p className='ant-upload-drag-icon'>
+                    <InboxOutlined />
+                </p>
+                <p className='ant-upload-text'>Click or drag files to this area</p>
+                <p className='ant-upload-hint'>{ hintText }</p>
+            </Upload.Dragger>
+            {files.length >= 5 && (
+                <>
+                    <br />
+                    <Text className='cvat-text-color'>{`${files.length} files selected`}</Text>
+                </>
+            )}
+        </>
+    );
+}
diff --git a/cvat-ui/src/components/tasks-page/top-bar.tsx b/cvat-ui/src/components/tasks-page/top-bar.tsx
index cdd9fcb4e55b..b3a1f839304d 100644
--- a/cvat-ui/src/components/tasks-page/top-bar.tsx
+++ b/cvat-ui/src/components/tasks-page/top-bar.tsx
@@ -17,6 +17,7 @@ import { usePrevious } from 'utils/hooks';
 import {
     localStorageRecentKeyword, localStorageRecentCapacity, predefinedFilterValues, config,
 } from './tasks-filter-configuration';
+import { MutliPlusIcon } from '../../icons';
 
 const FilteringComponent = ResourceFilterHOC(
     config, localStorageRecentKeyword, localStorageRecentCapacity, predefinedFilterValues,
@@ -99,6 +100,14 @@ export default function TopBarComponent(props: VisibleTopBarProps): JSX.Element
                                 >
                                     Create a new task
                                 </Button>
+                                <Button
+                                    className='cvat-create-multi-tasks-button'
+                                    type='primary'
+                                    onClick={(): void => history.push('/tasks/create?many=true')}
+                                    icon={<span className='anticon'><MutliPlusIcon /></span>}
+                                >
+                                    Create multi tasks
+                                </Button>
                                 <Upload
                                     accept='.zip'
                                     multiple={false}
diff --git a/cvat-ui/src/containers/create-task-page/create-task-page.tsx b/cvat-ui/src/containers/create-task-page/create-task-page.tsx
index f8a427ce547c..1ee50e4e5cde 100644
--- a/cvat-ui/src/containers/create-task-page/create-task-page.tsx
+++ b/cvat-ui/src/containers/create-task-page/create-task-page.tsx
@@ -10,27 +10,22 @@ import { CreateTaskData } from 'components/create-task-page/create-task-content'
 import { createTaskAsync } from 'actions/tasks-actions';
 
 interface StateToProps {
-    taskId: number | null;
-    status: string;
-    error: string;
     installedGit: boolean;
     dumpers:[]
 }
 
 interface DispatchToProps {
-    onCreate: (data: CreateTaskData) => void;
+    onCreate: (data: CreateTaskData, onProgress?: (status: string) => void) => Promise<any>;
 }
 
 function mapDispatchToProps(dispatch: any): DispatchToProps {
     return {
-        onCreate: (data: CreateTaskData): Promise<any> => dispatch(createTaskAsync(data)),
+        onCreate: (data, onProgress) => dispatch(createTaskAsync(data, onProgress)),
     };
 }
 
 function mapStateToProps(state: CombinedState): StateToProps {
-    const { creates } = state.tasks.activities;
     return {
-        ...creates,
         installedGit: state.plugins.list.GIT_INTEGRATION,
         dumpers: state.formats.annotationFormats.dumpers,
     };
diff --git a/cvat-ui/src/containers/file-manager/file-manager.tsx b/cvat-ui/src/containers/file-manager/file-manager.tsx
index 54800596f358..976d64b17d36 100644
--- a/cvat-ui/src/containers/file-manager/file-manager.tsx
+++ b/cvat-ui/src/containers/file-manager/file-manager.tsx
@@ -5,35 +5,46 @@
 import React from 'react';
 import { connect } from 'react-redux';
 
+import { uniqBy } from 'lodash';
+
 import { TreeNodeNormal } from 'antd/lib/tree/Tree';
 import FileManagerComponent, { Files } from 'components/file-manager/file-manager';
 
 import { loadShareDataAsync } from 'actions/share-actions';
-import { ShareItem, CombinedState } from 'reducers';
+import { ShareItem, CombinedState, ShareFileInfo } from 'reducers';
 
 interface OwnProps {
     ref: any;
+    isMultiTask: boolean
     onChangeActiveKey(key: string): void;
+    onUploadLocalFiles(files: File[]): void;
+    onUploadRemoteFiles(urls: string[]): void;
+    onUploadShareFiles(shareFiles: {
+        key: string;
+        type: string;
+        mime_type: string;
+    }[]): void;
 }
 
 interface StateToProps {
-    treeData: TreeNodeNormal[];
+    treeData: (TreeNodeNormal & { mime_type: string })[];
 }
 
 interface DispatchToProps {
-    getTreeData(key: string, success: () => void, failure: () => void): void;
+    getTreeData(key: string): Promise<ShareFileInfo[]>;
 }
 
 function mapStateToProps(state: CombinedState): StateToProps {
-    function convert(items: ShareItem[], path?: string): TreeNodeNormal[] {
+    function convert(items: ShareItem[], path?: string): (TreeNodeNormal & { mime_type: string })[] {
         return items.map(
-            (item): TreeNodeNormal => {
+            (item): (TreeNodeNormal & { mime_type: string }) => {
                 const isLeaf = item.type !== 'DIR';
                 const key = `${path}${item.name}${isLeaf ? '' : '/'}`;
                 return {
                     key,
                     isLeaf,
                     title: item.name || 'root',
+                    mime_type: item.mime_type,
                     children: convert(item.children, key),
                 };
             },
@@ -48,9 +59,8 @@ function mapStateToProps(state: CombinedState): StateToProps {
 
 function mapDispatchToProps(dispatch: any): DispatchToProps {
     return {
-        getTreeData: (key: string, success: () => void, failure: () => void): void => {
-            dispatch(loadShareDataAsync(key, success, failure));
-        },
+        getTreeData: (key: string):
+        Promise<ShareFileInfo[]> => dispatch(loadShareDataAsync(key)),
     };
 }
 
@@ -65,6 +75,61 @@ export class FileManagerContainer extends React.PureComponent<Props> {
         this.managerComponentRef = React.createRef();
     }
 
+    private handleUploadShareFiles = (keys: string[]): Promise<void> => new Promise(() => {
+        const { onUploadShareFiles, getTreeData } = this.props;
+        const getItemTreeDataByPath = (data: any, partsPath: string[]): any => {
+            if (partsPath.length === 1) return data.children.find((child: any) => child.title === partsPath[0]);
+            return getItemTreeDataByPath(
+                data.children.find((child: any) => child.title === partsPath[0]),
+                [...partsPath].filter((it, index) => index !== 0),
+            );
+        };
+
+        const getShareFiles = async (localKeys: string[]): Promise<{
+            key: string;
+            type: string;
+            mime_type: string;
+        }[]> => {
+            const { treeData } = this.props;
+            let files: {
+                key: string;
+                type: string;
+                mime_type: string;
+            }[] = [];
+            for await (const key of localKeys) {
+                const partsPath = key.split('/').filter(Boolean);
+                const itemTreeData =
+                getItemTreeDataByPath(treeData[0], partsPath);
+                if (itemTreeData.isLeaf) {
+                    files = [...files, {
+                        key,
+                        type: itemTreeData.type,
+                        mime_type: itemTreeData.mime_type,
+                    }];
+                } else {
+                    const children: {
+                        key: string;
+                        type: string;
+                        mime_type: string;
+                    }[] = await getTreeData(key)
+                        .then((items) => items.map(
+                            (item): string => {
+                                const isLeaf = item.type !== 'DIR';
+                                return `${key}${item.name}${isLeaf ? '' : '/'}`;
+                            },
+                        ))
+                        .then(getShareFiles);
+                    files = [...files, ...children];
+                }
+            }
+            return files;
+        };
+
+        getShareFiles(keys).then((data) => {
+            onUploadShareFiles(uniqBy(data, 'key'));
+        });
+    });
+
     public getFiles(): Files {
         return this.managerComponentRef.getFiles();
     }
@@ -78,12 +143,23 @@ export class FileManagerContainer extends React.PureComponent<Props> {
     }
 
     public render(): JSX.Element {
-        const { treeData, getTreeData, onChangeActiveKey } = this.props;
+        const {
+            treeData,
+            getTreeData,
+            isMultiTask,
+            onChangeActiveKey,
+            onUploadLocalFiles,
+            onUploadRemoteFiles,
+        } = this.props;
 
         return (
             <FileManagerComponent
                 treeData={treeData}
+                isMultiTask={isMultiTask}
                 onLoadData={getTreeData}
+                onUploadLocalFiles={onUploadLocalFiles}
+                onUploadRemoteFiles={onUploadRemoteFiles}
+                onUploadShareFiles={this.handleUploadShareFiles}
                 onChangeActiveKey={onChangeActiveKey}
                 ref={(component): void => {
                     this.managerComponentRef = component;
diff --git a/cvat-ui/src/icons.tsx b/cvat-ui/src/icons.tsx
index 36005a5040c9..aa17fe44a036 100644
--- a/cvat-ui/src/icons.tsx
+++ b/cvat-ui/src/icons.tsx
@@ -52,6 +52,7 @@ import SVGCVATAzureProvider from './assets/vscode-icons_file-type-azure.svg';
 import SVGCVATS3Provider from './assets/S3.svg';
 import SVGCVATGoogleCloudProvider from './assets/google-cloud.svg';
 import SVGRestoreIcon from './assets/restore-icon.svg';
+import SVGMultiPlusIcon from './assets/multi-plus-icon.svg';
 
 export const CVATLogo = React.memo((): JSX.Element => <SVGCVATLogo />);
 export const CursorIcon = React.memo((): JSX.Element => <SVGCursorIcon />);
@@ -101,3 +102,4 @@ export const AzureProvider = React.memo((): JSX.Element => <SVGCVATAzureProvider
 export const S3Provider = React.memo((): JSX.Element => <SVGCVATS3Provider />);
 export const GoogleCloudProvider = React.memo((): JSX.Element => <SVGCVATGoogleCloudProvider />);
 export const RestoreIcon = React.memo((): JSX.Element => <SVGRestoreIcon />);
+export const MutliPlusIcon = React.memo((): JSX.Element => <SVGMultiPlusIcon />);
diff --git a/cvat-ui/src/reducers/index.ts b/cvat-ui/src/reducers/index.ts
index cdba4e342a54..ad3302162170 100644
--- a/cvat-ui/src/reducers/index.ts
+++ b/cvat-ui/src/reducers/index.ts
@@ -108,11 +108,6 @@ export interface TasksState {
         deletes: {
             [tid: number]: boolean; // deleted (deleting if in dictionary)
         };
-        creates: {
-            taskId: number | null;
-            status: string;
-            error: string;
-        };
         backups: {
             [tid: number]: boolean;
         };
@@ -246,11 +241,13 @@ export interface ShareFileInfo {
     // get this data from cvat-core
     name: string;
     type: 'DIR' | 'REG';
+    mime_type: string;
 }
 
 export interface ShareItem {
     name: string;
     type: 'DIR' | 'REG';
+    mime_type: string;
     children: ShareItem[];
 }
 
diff --git a/cvat-ui/src/reducers/tasks-reducer.ts b/cvat-ui/src/reducers/tasks-reducer.ts
index 716b64685fd8..a09816e3c727 100644
--- a/cvat-ui/src/reducers/tasks-reducer.ts
+++ b/cvat-ui/src/reducers/tasks-reducer.ts
@@ -33,11 +33,6 @@ const defaultState: TasksState = {
     activities: {
         loads: {},
         deletes: {},
-        creates: {
-            taskId: null,
-            status: '',
-            error: '',
-        },
         backups: {},
         jobUpdates: {},
     },
@@ -207,60 +202,6 @@ export default (state: TasksState = defaultState, action: AnyAction): TasksState
                 importing: false,
             };
         }
-        case TasksActionTypes.CREATE_TASK: {
-            return {
-                ...state,
-                activities: {
-                    ...state.activities,
-                    creates: {
-                        taskId: null,
-                        status: '',
-                        error: '',
-                    },
-                },
-            };
-        }
-        case TasksActionTypes.CREATE_TASK_STATUS_UPDATED: {
-            const { status } = action.payload;
-
-            return {
-                ...state,
-                activities: {
-                    ...state.activities,
-                    creates: {
-                        ...state.activities.creates,
-                        status,
-                    },
-                },
-            };
-        }
-        case TasksActionTypes.CREATE_TASK_SUCCESS: {
-            const { taskId } = action.payload;
-            return {
-                ...state,
-                activities: {
-                    ...state.activities,
-                    creates: {
-                        ...state.activities.creates,
-                        taskId,
-                        status: 'CREATED',
-                    },
-                },
-            };
-        }
-        case TasksActionTypes.CREATE_TASK_FAILED: {
-            return {
-                ...state,
-                activities: {
-                    ...state.activities,
-                    creates: {
-                        ...state.activities.creates,
-                        status: 'FAILED',
-                        error: action.payload.error.toString(),
-                    },
-                },
-            };
-        }
         case TasksActionTypes.UPDATE_TASK: {
             return {
                 ...state,
diff --git a/cvat-ui/src/utils/files.ts b/cvat-ui/src/utils/files.ts
new file mode 100644
index 000000000000..8d7b25f0d97d
--- /dev/null
+++ b/cvat-ui/src/utils/files.ts
@@ -0,0 +1,111 @@
+// Copyright (C) 2022 Intel Corporation
+//
+// SPDX-License-Identifier: MIT
+
+export function getFileContentTypeByMimeType(mime_type: string): string {
+    return mime_type.split('/')[0];
+}
+
+export function getFileContentType(file: File): string {
+    return getFileContentTypeByMimeType(file.type);
+}
+
+export function checkFileTypesEqual(files: File[]): boolean {
+    if (!files.length) return true;
+    const typeFirstFile: string = getFileContentType(files[0]);
+    return files.every((file) => getFileContentType(file) === typeFirstFile);
+}
+
+function checkCreatingVideoElement(url: string): Promise<void> {
+    return new Promise((resolve, reject) => {
+        const el = document.createElement('video');
+        el.src = url;
+        el.onloadedmetadata = () => {
+            el.remove();
+            resolve();
+        };
+        el.onerror = reject;
+    });
+}
+
+function checkCreatingImageElement(url: string): Promise<void> {
+    return new Promise((resolve, reject) => {
+        const el = document.createElement('img');
+        el.src = url;
+        el.onload = () => {
+            el.remove();
+            resolve();
+        };
+        el.onerror = reject;
+    });
+}
+
+function getUrlExtension(url: string): string {
+    return url.split(/[#?]/)[0].split('.').pop()?.trim() || '';
+}
+
+// source https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types
+const IMAGE_EXTENSIONS = ['apng', 'avif', 'gif', 'jpg', 'jpeg', 'jfif', 'pjpeg', 'pjp', 'png', 'webp'];
+
+// source https://en.wikipedia.org/wiki/Video_file_format
+const VIDEO_EXTENSIONS = ['webm', 'mkv', 'flv', 'flv', 'vob', 'ogv', 'ogg', 'drc', 'gifv', 'mng', 'avi', 'MTS',
+    'M2TS', 'TS', 'mov', 'qt', 'wmv', 'yuv', 'rm', 'rmvb', 'viv', 'asf', 'amv', 'mp4', 'm4p', 'm4v',
+    'mpg', 'mp2', 'mpeg', 'mpe', 'mpv', 'mpg', 'mpeg', 'm2v', 'm4v', 'svi', '3gp', '3g2', 'mxf', 'roq',
+    'nsv', 'flv', 'f4v', 'f4p', 'f4a', 'f4b',
+];
+
+export function getContentTypeRemoteFile(url: string): Promise<string> {
+    return new Promise((resolve): void => {
+        const extention = getUrlExtension(url);
+        if (IMAGE_EXTENSIONS.includes(extention)) {
+            resolve('image');
+            return;
+        }
+        if (VIDEO_EXTENSIONS.includes(extention)) {
+            resolve('video');
+            return;
+        }
+        const xhr = new XMLHttpRequest();
+        xhr.open('HEAD', url, true);
+
+        xhr.onreadystatechange = () => {
+            const contentType = xhr.getResponseHeader('Content-Type');
+            if (xhr.status > 299) {
+                xhr.abort();
+                resolve('unknown');
+                return;
+            }
+            if (contentType) {
+                xhr.abort();
+                resolve(contentType.split('/')[0]);
+            }
+        };
+
+        xhr.onerror = () => {
+            checkCreatingVideoElement(url)
+                .then(() => {
+                    resolve('video');
+                })
+                .catch(() => {
+                    checkCreatingImageElement(url);
+                })
+                .then(() => {
+                    resolve('image');
+                })
+                .catch(() => {
+                    resolve('unknown');
+                });
+        };
+
+        xhr.send();
+    });
+}
+
+export function getNameRemoteFile(url: string): string {
+    return url.split('/').filter(Boolean).pop()?.split(/[#?]/)?.[0] || '';
+}
+
+export function getNameShareFile(url: string): string {
+    console.log(url);
+    return url; // TODO
+}
diff --git a/cvat/apps/engine/serializers.py b/cvat/apps/engine/serializers.py
index 519434077af1..dbe4d5c420d1 100644
--- a/cvat/apps/engine/serializers.py
+++ b/cvat/apps/engine/serializers.py
@@ -925,6 +925,7 @@ class LabeledDataSerializer(serializers.Serializer):
 class FileInfoSerializer(serializers.Serializer):
     name = serializers.CharField(max_length=1024)
     type = serializers.ChoiceField(choices=["REG", "DIR"])
+    mime_type = serializers.CharField(max_length=16)
 
 class LogEventSerializer(serializers.Serializer):
     job_id = serializers.IntegerField(required=False)
diff --git a/cvat/apps/engine/views.py b/cvat/apps/engine/views.py
index 2fa21a349b57..ad0a8c9d8326 100644
--- a/cvat/apps/engine/views.py
+++ b/cvat/apps/engine/views.py
@@ -14,6 +14,7 @@
 from distutils.util import strtobool
 from tempfile import mkstemp, NamedTemporaryFile
 
+import magic
 import cv2
 from django.db.models.query import Prefetch
 from django.shortcuts import get_object_or_404
@@ -183,13 +184,16 @@ def share(request):
             content = os.scandir(directory)
             for entry in content:
                 entry_type = None
+                entry_mime_type = None
                 if entry.is_file():
                     entry_type = "REG"
+                    entry_mime_type = magic.from_file(os.path.join(settings.SHARE_ROOT, entry), mime=True)
                 elif entry.is_dir():
                     entry_type = "DIR"
+                    entry_mime_type = "DIR"
 
                 if entry_type:
-                    data.append({"name": entry.name, "type": entry_type})
+                    data.append({"name": entry.name, "type": entry_type, "mime_type": entry_mime_type})
 
             serializer = FileInfoSerializer(many=True, data=data)
             if serializer.is_valid(raise_exception=True):
diff --git a/cvat/requirements/base.txt b/cvat/requirements/base.txt
index 54178cc96e07..a7d36ef538b7 100644
--- a/cvat/requirements/base.txt
+++ b/cvat/requirements/base.txt
@@ -52,3 +52,4 @@ urllib3>=1.26.5 # not directly required, pinned by Snyk to avoid a vulnerability
 natsort==8.0.0
 mistune>=2.0.1 # not directly required, pinned by Snyk to avoid a vulnerability
 dnspython==2.2.0
+python-magic==0.4.27


Code review comments:
diff_hunk: @@ -4,15 +4,19 @@
 
 import React, { RefObject } from 'react';
 import Input from 'antd/lib/input';
+import Text from 'antd/lib/typography/Text';
+import Tooltip from 'antd/lib/tooltip';
 import Form, { FormInstance } from 'antd/lib/form';
-import { Store } from 'antd/lib/form/interface';
 
 export interface BaseConfiguration {
     name: string;
 }
 
 interface Props {
-    onSubmit(values: BaseConfiguration): void;
+    onChange(values: BaseConfiguration): void;
+    isMultiTask: boolean;
path: cvat-ui/src/components/create-task-page/basic-configuration-form.tsx
text: @user1:
Let's have ``many: boolean;``

diff_hunk: @@ -4,15 +4,19 @@
 
 import React, { RefObject } from 'react';
 import Input from 'antd/lib/input';
+import Text from 'antd/lib/typography/Text';
+import Tooltip from 'antd/lib/tooltip';
 import Form, { FormInstance } from 'antd/lib/form';
-import { Store } from 'antd/lib/form/interface';
 
 export interface BaseConfiguration {
     name: string;
 }
 
 interface Props {
-    onSubmit(values: BaseConfiguration): void;
+    onChange(values: BaseConfiguration): void;
+    isMultiTask: boolean;
+    exampleMultiTaskName?: string;
+    defaultValue?: string;
path: cvat-ui/src/components/create-task-page/basic-configuration-form.tsx
text: @user1:
I see the following logic: 
`` const defaultValue = isMultiTask ? '{{file_name}}' : '';``

isMultiTask is also a prop of this component, so, let's put this logic inside and remove this extra prop.