Create multiple tasks when uploading multiple videos (#4824)
* add alert when uploading photo and video together * add creating multiple tasks for My computer tabs * add creating multiple tasks for Remote source tab * add creating multi tasks for File share tab * add libmagic in dockerfile * fix lint * change class name of create multi tasks button * fix incorrect deletion of validation error notification * add opportunity upload manifest.jsonl with image files for single task create * remove status showing of task from multitasks case * refactoring create queue in mutlitasks case * fix warning * revert incorrect remove notification about error * fix showing error of clone the repository * fix esling error * move of initialValue creating for task name * rename isMultiTask properti to many * return incorrect deleted progress value * add source and license on icon file * fix unhandled promise rejection * change mime_type getter method * add hint how to see these template rules * refactoring of multi task progress markup * remove unnecessary processings for share * remove unnecessary notification * remove opportunity upload no video on multi mode * correct formation of the task name * rename function * change queueSize to 1 * fix root selecting on share tab * refactoring selectCloudStorageFiles logic * add debig info. temporarily * Some fixes * Tried to fix unstable test * Adjusted messages * Fixed license headers Co-authored-by: Boris <sekachev.bs@gmail.com>main
parent
9f89787f95
commit
1db247aedd
@ -0,0 +1,5 @@
|
||||
<!-- The icon received from: https://github.com/Templarian/MaterialDesign/blob/master/svg/plus-circle-multiple-outline.svg -->
|
||||
<!-- License: Apache 2.0 -->
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@ -0,0 +1,159 @@
|
||||
// Copyright (C) 2022 CVAT.ai 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): string => {
|
||||
const tabs = Object.keys(item.files);
|
||||
const itemType = tabs.find((key) => (item.files[key][0])) || 'local';
|
||||
return item.files[itemType][0]?.name || item.files[itemType][0] || '';
|
||||
})
|
||||
.filter(Boolean) :
|
||||
[];
|
||||
|
||||
if (percent === 100) {
|
||||
if (countFailed === countAll) {
|
||||
alertType = 'error';
|
||||
} else if (countFailed) {
|
||||
alertType = 'warning';
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Alert
|
||||
type={alertType}
|
||||
message={(
|
||||
<div>
|
||||
{percent === 100 ? (
|
||||
<Row>
|
||||
<Col>
|
||||
Finished
|
||||
</Col>
|
||||
</Row>
|
||||
) : null}
|
||||
<Row>
|
||||
<Col>
|
||||
{`Pending: ${countPending} `}
|
||||
</Col>
|
||||
<Col offset={1}>
|
||||
{`Progress: ${countProgress} `}
|
||||
</Col>
|
||||
<Col offset={1}>
|
||||
{`Completed: ${countCompleted} `}
|
||||
</Col>
|
||||
<Col offset={1}>
|
||||
{`Failed: ${countFailed} `}
|
||||
</Col>
|
||||
{countCancelled ? (<Col offset={1}>{`Cancelled: ${countCancelled} `}</Col>) : null}
|
||||
<Col offset={1}>
|
||||
{`Total: ${countAll}.`}
|
||||
</Col>
|
||||
</Row>
|
||||
<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} disabled={!countPending}>
|
||||
Cancel pending tasks
|
||||
</Button>
|
||||
</Col>
|
||||
)}
|
||||
</Row>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
// Copyright (C) 2022 Intel Corporation
|
||||
// Copyright (C) 2022 CVAT.ai 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[];
|
||||
many: boolean;
|
||||
onUpload: (_: RcFile, uploadedFiles: RcFile[]) => boolean;
|
||||
}
|
||||
|
||||
export default function LocalFiles(props: Props): JSX.Element {
|
||||
const { files, onUpload, many } = props;
|
||||
const hintText = many ? 'You can upload one or more videos' :
|
||||
'You can upload an archive with images, a video, or multiple images';
|
||||
|
||||
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>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,106 @@
|
||||
// Copyright (C) 2022 CVAT.ai 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 getFileNameFromPath(path: string): string {
|
||||
return path.split('/').filter(Boolean).pop()?.split(/[#?]/)?.[0] || '';
|
||||
}
|
||||
Loading…
Reference in New Issue