[NEW-UI]: Create and update task (#612)
- Create task - Update task - Upload annotations - Download annotationsmain
parent
935d380d36
commit
b7609cae91
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,72 @@
|
||||
export const dumpAnnotation = () => (dispatch: any) => {
|
||||
dispatch({
|
||||
type: 'DUMP_ANNOTATION',
|
||||
});
|
||||
}
|
||||
|
||||
export const dumpAnnotationSuccess = (downloadLink: string) => (dispatch: any) => {
|
||||
dispatch({
|
||||
type: 'DUMP_ANNOTATION_SUCCESS',
|
||||
payload: downloadLink,
|
||||
});
|
||||
}
|
||||
|
||||
export const dumpAnnotationError = (error = {}) => (dispatch: any) => {
|
||||
dispatch({
|
||||
type: 'DUMP_ANNOTATION_ERROR',
|
||||
payload: error,
|
||||
});
|
||||
}
|
||||
|
||||
export const uploadAnnotation = () => (dispatch: any) => {
|
||||
dispatch({
|
||||
type: 'UPLOAD_ANNOTATION',
|
||||
});
|
||||
}
|
||||
|
||||
export const uploadAnnotationSuccess = () => (dispatch: any) => {
|
||||
dispatch({
|
||||
type: 'UPLOAD_ANNOTATION_SUCCESS',
|
||||
});
|
||||
}
|
||||
|
||||
export const uploadAnnotationError = (error = {}) => (dispatch: any) => {
|
||||
dispatch({
|
||||
type: 'UPLOAD_ANNOTATION_ERROR',
|
||||
payload: error,
|
||||
});
|
||||
}
|
||||
|
||||
export const dumpAnnotationAsync = (task: any, dumper: any) => {
|
||||
return (dispatch: any) => {
|
||||
dispatch(dumpAnnotation());
|
||||
|
||||
return task.annotations.dump(task.name, dumper).then(
|
||||
(downloadLink: string) => {
|
||||
dispatch(dumpAnnotationSuccess(downloadLink));
|
||||
},
|
||||
(error: any) => {
|
||||
dispatch(dumpAnnotationError(error));
|
||||
|
||||
throw error;
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export const uploadAnnotationAsync = (task: any, file: File, loader: any) => {
|
||||
return (dispatch: any) => {
|
||||
dispatch(uploadAnnotation());
|
||||
|
||||
return task.annotations.upload(file, loader).then(
|
||||
(response: any) => {
|
||||
dispatch(uploadAnnotationSuccess());
|
||||
},
|
||||
(error: any) => {
|
||||
dispatch(uploadAnnotationError(error));
|
||||
|
||||
throw error;
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,106 @@
|
||||
export const getServerInfo = () => (dispatch: any) => {
|
||||
dispatch({
|
||||
type: 'GET_SERVER_INFO',
|
||||
});
|
||||
}
|
||||
|
||||
export const getServerInfoSuccess = (information: null) => (dispatch: any) => {
|
||||
dispatch({
|
||||
type: 'GET_SERVER_INFO_SUCCESS',
|
||||
payload: information,
|
||||
});
|
||||
}
|
||||
|
||||
export const getServerInfoError = (error = {}) => (dispatch: any) => {
|
||||
dispatch({
|
||||
type: 'GET_SERVER_INFO_ERROR',
|
||||
payload: error,
|
||||
});
|
||||
}
|
||||
|
||||
export const getShareFiles = () => (dispatch: any) => {
|
||||
dispatch({
|
||||
type: 'GET_SHARE_FILES',
|
||||
});
|
||||
}
|
||||
|
||||
export const getShareFilesSuccess = (files: []) => (dispatch: any) => {
|
||||
dispatch({
|
||||
type: 'GET_SHARE_FILES_SUCCESS',
|
||||
payload: files,
|
||||
});
|
||||
}
|
||||
|
||||
export const getShareFilesError = (error = {}) => (dispatch: any) => {
|
||||
dispatch({
|
||||
type: 'GET_SHARE_FILES_ERROR',
|
||||
payload: error,
|
||||
});
|
||||
}
|
||||
|
||||
export const getAnnotationFormats = () => (dispatch: any) => {
|
||||
dispatch({
|
||||
type: 'GET_ANNOTATION_FORMATS',
|
||||
});
|
||||
}
|
||||
|
||||
export const getAnnotationFormatsSuccess = (annotationFormats: []) => (dispatch: any) => {
|
||||
dispatch({
|
||||
type: 'GET_ANNOTATION_FORMATS_SUCCESS',
|
||||
payload: annotationFormats,
|
||||
});
|
||||
}
|
||||
|
||||
export const getAnnotationFormatsError = (error = {}) => (dispatch: any) => {
|
||||
dispatch({
|
||||
type: 'GET_ANNOTATION_FORMATS_ERROR',
|
||||
payload: error,
|
||||
});
|
||||
}
|
||||
|
||||
export const getServerInfoAsync = () => {
|
||||
return (dispatch: any) => {
|
||||
dispatch(getServerInfo());
|
||||
|
||||
return (window as any).cvat.server.about().then(
|
||||
(information: any) => {
|
||||
dispatch(getServerInfoSuccess(information));
|
||||
},
|
||||
(error: any) => {
|
||||
dispatch(getServerInfoError(error));
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export const getShareFilesAsync = (directory: string) => {
|
||||
return (dispatch: any) => {
|
||||
dispatch(getShareFiles());
|
||||
|
||||
return (window as any).cvat.server.share(directory).then(
|
||||
(files: any) => {
|
||||
dispatch(getShareFilesSuccess(files));
|
||||
},
|
||||
(error: any) => {
|
||||
dispatch(getShareFilesError(error));
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export const getAnnotationFormatsAsync = () => {
|
||||
return (dispatch: any) => {
|
||||
dispatch(getAnnotationFormats());
|
||||
|
||||
return (window as any).cvat.server.formats().then(
|
||||
(formats: any) => {
|
||||
dispatch(getAnnotationFormatsSuccess(formats));
|
||||
},
|
||||
(error: any) => {
|
||||
dispatch(getAnnotationFormatsError(error));
|
||||
|
||||
throw error;
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
.ant-badge {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ant-tree.ant-tree-directory {
|
||||
height: 108px;
|
||||
overflow: auto;
|
||||
}
|
||||
@ -0,0 +1,364 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { Form, Input, Icon, Checkbox, Radio, Upload, Badge, Tree, TreeSelect, InputNumber } from 'antd';
|
||||
import { UploadFile, UploadChangeParam } from 'antd/lib/upload/interface';
|
||||
|
||||
import configureStore from '../../../store';
|
||||
import { getShareFilesAsync } from '../../../actions/server.actions';
|
||||
|
||||
import { validateLabels, FileSource, fileModel } from '../../../utils/tasks-dto';
|
||||
|
||||
import './task-create.scss';
|
||||
|
||||
|
||||
const { TreeNode } = Tree;
|
||||
const { SHOW_PARENT } = TreeSelect;
|
||||
const { Dragger } = Upload;
|
||||
|
||||
const formItemLayout = {
|
||||
labelCol: {
|
||||
xs: { span: 24 },
|
||||
sm: { span: 8 },
|
||||
},
|
||||
wrapperCol: {
|
||||
xs: { span: 24 },
|
||||
sm: { span: 16 },
|
||||
},
|
||||
};
|
||||
|
||||
const formItemTailLayout = {
|
||||
labelCol: {
|
||||
xs: { span: 24 },
|
||||
},
|
||||
wrapperCol: {
|
||||
xs: { span: 24 },
|
||||
},
|
||||
};
|
||||
|
||||
class TaskCreateForm extends PureComponent<any, any> {
|
||||
store: any;
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
|
||||
this.store = configureStore();
|
||||
|
||||
this.state = {
|
||||
confirmDirty: false,
|
||||
selectedFileList: [],
|
||||
filesCounter: 0,
|
||||
treeData: [],
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getSharedFiles('').then(
|
||||
(data: any) => {
|
||||
this.setState({ treeData: fileModel('', this.store.getState().server.files) });
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
private renderTreeNodes = (data: any) => {
|
||||
return data.map((item: any) => {
|
||||
if (!item.isLeaf) {
|
||||
return (
|
||||
<TreeNode title={ item.name } key={ item.id } value={ item.id } dataRef={ item }>
|
||||
{ item.children ? this.renderTreeNodes(item.children) : '' }
|
||||
</TreeNode>
|
||||
);
|
||||
}
|
||||
|
||||
return <TreeNode isLeaf title={ item.name } key={ item.id } value={ item.id } dataRef={ item } />;
|
||||
});
|
||||
}
|
||||
|
||||
private renderUploader = () => {
|
||||
const { getFieldDecorator } = this.props.form;
|
||||
|
||||
switch (this.props.form.getFieldValue('source')) {
|
||||
case FileSource.Local:
|
||||
return (
|
||||
<Form.Item
|
||||
{ ...formItemTailLayout }
|
||||
extra='Only one video, archive, pdf or many image, directory can be used simultaneously'>
|
||||
<Badge
|
||||
count={ this.state.filesCounter }
|
||||
overflowCount={999}>
|
||||
<div onClick={ this.resetUploader }>
|
||||
{getFieldDecorator('localUpload', {
|
||||
rules: [{ required: true, message: 'Please, add some files!' }],
|
||||
})(
|
||||
<Dragger
|
||||
multiple
|
||||
showUploadList={ false }
|
||||
fileList={ this.state.selectedFileList }
|
||||
customRequest={ this.simulateRequest }
|
||||
onChange={ this.onUploaderChange }>
|
||||
<p className="ant-upload-drag-icon">
|
||||
<Icon type="inbox" />
|
||||
</p>
|
||||
<p className="ant-upload-text">Click or drag file to this area to upload</p>
|
||||
</Dragger>
|
||||
)}
|
||||
</div>
|
||||
</Badge>
|
||||
</Form.Item>
|
||||
);
|
||||
case FileSource.Remote:
|
||||
return (
|
||||
<Form.Item { ...formItemLayout } label="URLs">
|
||||
{getFieldDecorator('remoteURL', {
|
||||
rules: [],
|
||||
})(
|
||||
<Input.TextArea
|
||||
name="remote-url"
|
||||
/>
|
||||
)}
|
||||
</Form.Item>
|
||||
);
|
||||
case FileSource.Share:
|
||||
return (
|
||||
<Form.Item { ...formItemLayout } label="Shared files"
|
||||
extra='Only one video, archive, pdf or many image, directory can be used simultaneously'>
|
||||
{getFieldDecorator('sharedFiles', {
|
||||
rules: [{ required: true, message: 'Please, add some files!' }],
|
||||
})(
|
||||
<TreeSelect
|
||||
multiple
|
||||
treeCheckable={ true }
|
||||
showCheckedStrategy={ SHOW_PARENT }
|
||||
loadData={ this.onLoadData }>
|
||||
{ this.renderTreeNodes(this.state.treeData) }
|
||||
</TreeSelect>
|
||||
)}
|
||||
</Form.Item>
|
||||
);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { getFieldDecorator } = this.props.form;
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<Form.Item { ...formItemLayout } label="Name">
|
||||
{getFieldDecorator('name', {
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
pattern: new RegExp('[a-zA-Z0-9_]+'),
|
||||
message: 'Bad task name!',
|
||||
},
|
||||
],
|
||||
})(
|
||||
<Input
|
||||
prefix={ <Icon type="profile" /> }
|
||||
type="text"
|
||||
name="name"
|
||||
/>
|
||||
)}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item { ...formItemLayout } label="Labels">
|
||||
{getFieldDecorator('labels', {
|
||||
rules: [
|
||||
{ required: true, message: 'Please add some labels!' },
|
||||
{ validator: validateLabels, message: 'Bad labels format!' },
|
||||
],
|
||||
})(
|
||||
<Input
|
||||
prefix={ <Icon type="tag" /> }
|
||||
type="text"
|
||||
name="labels"
|
||||
/>
|
||||
)}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item { ...formItemLayout } label="Bug tracker">
|
||||
{getFieldDecorator('bugTracker', {
|
||||
rules: [{ type: 'url', message: 'Bad bug tracker link!' }],
|
||||
})(
|
||||
<Input
|
||||
prefix={ <Icon type="tool" /> }
|
||||
type="text"
|
||||
name="bug-tracker"
|
||||
/>
|
||||
)}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item { ...formItemLayout } label="Use LFS">
|
||||
{getFieldDecorator('useLFS', {
|
||||
rules: [],
|
||||
initialValue: true,
|
||||
})(
|
||||
<Checkbox
|
||||
defaultChecked
|
||||
name="use-lfs"
|
||||
/>
|
||||
)}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item { ...formItemLayout } label="Source">
|
||||
{getFieldDecorator('source', {
|
||||
rules: [],
|
||||
initialValue: 1,
|
||||
})(
|
||||
<Radio.Group onChange={ this.resetUploader }>
|
||||
<Radio.Button value={1}>Local</Radio.Button>
|
||||
<Radio.Button value={2}>Remote</Radio.Button>
|
||||
<Radio.Button value={3}>Share</Radio.Button>
|
||||
</Radio.Group>
|
||||
)}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item { ...formItemLayout } label="Z-Order">
|
||||
{getFieldDecorator('zOrder', {
|
||||
rules: [],
|
||||
initialValue: false,
|
||||
})(
|
||||
<Checkbox
|
||||
name="z-order"
|
||||
/>
|
||||
)}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item { ...formItemLayout } label="Segment size" hasFeedback>
|
||||
{getFieldDecorator('segmentSize', {
|
||||
rules: [],
|
||||
})(
|
||||
<InputNumber
|
||||
min={100}
|
||||
max={50000}
|
||||
name="segment-size"
|
||||
/>
|
||||
)}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item { ...formItemLayout } label="Overlap size" hasFeedback>
|
||||
{getFieldDecorator('overlapSize', {
|
||||
rules: [],
|
||||
initialValue: 0,
|
||||
})(
|
||||
<InputNumber
|
||||
min={0}
|
||||
max={ this.props.form.getFieldValue('segmentSize') ? this.props.form.getFieldValue('segmentSize') - 1 : 0 }
|
||||
name="overlap-size"
|
||||
/>
|
||||
)}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item { ...formItemLayout } label="Image quality">
|
||||
{getFieldDecorator('imageQuality', {
|
||||
rules: [{ required: true }],
|
||||
initialValue: 50,
|
||||
})(
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={95}
|
||||
name="image-quality"
|
||||
/>
|
||||
)}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item { ...formItemLayout } label="Start frame" hasFeedback>
|
||||
{getFieldDecorator('startFrame', {
|
||||
rules: [],
|
||||
initialValue: 0,
|
||||
})(
|
||||
<InputNumber
|
||||
min={0}
|
||||
name="start-frame"
|
||||
/>
|
||||
)}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item { ...formItemLayout } label="Stop frame" hasFeedback>
|
||||
{getFieldDecorator('stopFrame', {
|
||||
rules: [],
|
||||
})(
|
||||
<InputNumber
|
||||
min={ this.props.form.getFieldValue('startFrame') }
|
||||
name="stop-frame"
|
||||
/>
|
||||
)}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item { ...formItemLayout } label="Frame filter">
|
||||
{getFieldDecorator('frameFilter', {
|
||||
rules: [],
|
||||
})(
|
||||
<Input
|
||||
prefix={ <Icon type="profile" /> }
|
||||
type="text"
|
||||
name="frame-filter"
|
||||
/>
|
||||
)}
|
||||
</Form.Item>
|
||||
|
||||
{ this.renderUploader() }
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
private onLoadData = (treeNode: any) => {
|
||||
return new Promise<void>(resolve => {
|
||||
if (treeNode.props.children) {
|
||||
resolve();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.getSharedFiles(treeNode.props.dataRef.id).then(
|
||||
(data: any) => {
|
||||
treeNode.props.dataRef.children = fileModel(treeNode, this.store.getState().server.files);
|
||||
|
||||
this.setState({
|
||||
treeData: [...this.state.treeData],
|
||||
});
|
||||
|
||||
resolve();
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private getSharedFiles = (directory: string) => {
|
||||
return this.store.dispatch(getShareFilesAsync(directory));
|
||||
}
|
||||
|
||||
private onUploaderChange = (info: UploadChangeParam) => {
|
||||
const nextState: { selectedFileList: UploadFile[], filesCounter: number } = {
|
||||
selectedFileList: this.state.selectedFileList,
|
||||
filesCounter: this.state.filesCounter,
|
||||
};
|
||||
|
||||
switch (info.file.status) {
|
||||
case 'uploading':
|
||||
nextState.selectedFileList.push(info.file);
|
||||
nextState.filesCounter += 1;
|
||||
break;
|
||||
case 'done':
|
||||
break;
|
||||
default:
|
||||
// INFO: error or removed
|
||||
nextState.selectedFileList = info.fileList;
|
||||
}
|
||||
|
||||
this.setState(() => nextState);
|
||||
}
|
||||
|
||||
private resetUploader = () => {
|
||||
this.setState({ selectedFileList: [], filesCounter: 0 });
|
||||
}
|
||||
|
||||
private simulateRequest = ({ file, onSuccess }: any) => {
|
||||
setTimeout(() => {
|
||||
onSuccess(file);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
export default Form.create()(TaskCreateForm);
|
||||
@ -0,0 +1,51 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { Form, Input, Icon } from 'antd';
|
||||
|
||||
import { serializeLabels, validateLabels } from '../../../utils/tasks-dto'
|
||||
|
||||
import './task-update.scss';
|
||||
|
||||
|
||||
class TaskUpdateForm extends PureComponent<any, any> {
|
||||
render() {
|
||||
const { getFieldDecorator } = this.props.form;
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<Form.Item>
|
||||
{getFieldDecorator('oldLabels', {
|
||||
rules: [],
|
||||
initialValue: serializeLabels(this.props.task),
|
||||
})(
|
||||
<Input
|
||||
disabled
|
||||
prefix={ <Icon type="tag" /> }
|
||||
type="text"
|
||||
name="oldLabels"
|
||||
placeholder="Old labels"
|
||||
/>,
|
||||
)}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
{getFieldDecorator('newLabels', {
|
||||
rules: [
|
||||
{ required: true, message: 'Please input new labels!' },
|
||||
{ validator: validateLabels, message: 'Bad labels format!' },
|
||||
],
|
||||
})(
|
||||
<Input
|
||||
prefix={ <Icon type="tag" /> }
|
||||
type="text"
|
||||
name="new-labels"
|
||||
placeholder="Expand the specification here"
|
||||
/>,
|
||||
)}
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Form.create()(TaskUpdateForm) as any;
|
||||
@ -0,0 +1,40 @@
|
||||
export default (
|
||||
state = {
|
||||
downloadLink: null,
|
||||
isFetching: false,
|
||||
error: null,
|
||||
},
|
||||
action: any,
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case 'DUMP_ANNOTATION':
|
||||
return Object.assign({}, state, {
|
||||
isFetching: true,
|
||||
});
|
||||
case 'DUMP_ANNOTATION_SUCCESS':
|
||||
return Object.assign({}, state, {
|
||||
isFetching: false,
|
||||
downloadLink: action.payload,
|
||||
});
|
||||
case 'DUMP_ANNOTATION_ERROR':
|
||||
return Object.assign({}, state, {
|
||||
isFetching: false,
|
||||
error: action.payload,
|
||||
});
|
||||
case 'UPLOAD_ANNOTATION':
|
||||
return Object.assign({}, state, {
|
||||
isFetching: true,
|
||||
});
|
||||
case 'UPLOAD_ANNOTATION_SUCCESS':
|
||||
return Object.assign({}, state, {
|
||||
isFetching: false,
|
||||
});
|
||||
case 'UPLOAD_ANNOTATION_ERROR':
|
||||
return Object.assign({}, state, {
|
||||
isFetching: false,
|
||||
error: action.payload,
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
export default (
|
||||
state = {
|
||||
info: null,
|
||||
files: [],
|
||||
annotationFormats: [],
|
||||
isFetching: false,
|
||||
error: null,
|
||||
},
|
||||
action: any,
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case 'GET_SERVER_INFO':
|
||||
return Object.assign({}, state, {
|
||||
isFetching: true,
|
||||
});
|
||||
case 'GET_SERVER_INFO_SUCCESS':
|
||||
return Object.assign({}, state, {
|
||||
isFetching: false,
|
||||
info: action.payload,
|
||||
});
|
||||
case 'GET_SERVER_INFO_ERROR':
|
||||
return Object.assign({}, state, {
|
||||
isFetching: false,
|
||||
error: action.payload,
|
||||
});
|
||||
case 'GET_SHARE_FILES':
|
||||
return Object.assign({}, state, {
|
||||
isFetching: true,
|
||||
});
|
||||
case 'GET_SHARE_FILES_SUCCESS':
|
||||
return Object.assign({}, state, {
|
||||
isFetching: false,
|
||||
files: action.payload,
|
||||
});
|
||||
case 'GET_SHARE_FILES_ERROR':
|
||||
return Object.assign({}, state, {
|
||||
isFetching: false,
|
||||
error: action.payload,
|
||||
});
|
||||
case 'GET_ANNOTATION_FORMATS':
|
||||
return Object.assign({}, state, {
|
||||
isFetching: true,
|
||||
});
|
||||
case 'GET_ANNOTATION_FORMATS_SUCCESS':
|
||||
return Object.assign({}, state, {
|
||||
isFetching: false,
|
||||
annotationFormats: action.payload,
|
||||
});
|
||||
case 'GET_ANNOTATION_FORMATS_ERROR':
|
||||
return Object.assign({}, state, {
|
||||
isFetching: false,
|
||||
error: action.payload,
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,152 @@
|
||||
export enum FileSource {
|
||||
Local = 1,
|
||||
Remote = 2,
|
||||
Share = 3,
|
||||
}
|
||||
|
||||
export function fileModel(parentNode: any, files: any) {
|
||||
return files.map(
|
||||
(file: any) => {
|
||||
return {
|
||||
id: parentNode.props ? `${parentNode.props.value}/${file.name}` : file.name,
|
||||
isLeaf: file.type !== 'DIR',
|
||||
name: file.name,
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function taskDTO(values: any) {
|
||||
const newTaskDTO = {
|
||||
name: values.name,
|
||||
labels: deserializeLabels(values.labels),
|
||||
image_quality: values.imageQuality,
|
||||
z_order: values.zOrder,
|
||||
bug_tracker: values.bugTracker,
|
||||
segment_size: values.segmentSize,
|
||||
overlap: values.overlapSize,
|
||||
frame_filter: values.frameFilter,
|
||||
start_frame: values.startFrame,
|
||||
stop_frame: values.stopFrame,
|
||||
};
|
||||
|
||||
const newTask = new (window as any).cvat.classes.Task(newTaskDTO);
|
||||
|
||||
if (values.source === FileSource.Local) {
|
||||
newTask.clientFiles = values.localUpload.fileList.map((file: any) => file.response);
|
||||
} else if (values.source === FileSource.Remote) {
|
||||
newTask.remoteFiles = values.remoteURL
|
||||
.split(/\r?\n/)
|
||||
.map((url: string) => url.trim())
|
||||
.filter((url: string) => url.length > 0);
|
||||
} else if (values.source === FileSource.Share) {
|
||||
newTask.serverFiles = values.sharedFiles;
|
||||
}
|
||||
|
||||
return newTask;
|
||||
}
|
||||
|
||||
export function validateLabels(rule: any, value: string, callback: Function) {
|
||||
if (value) {
|
||||
try {
|
||||
deserializeLabels(value);
|
||||
} catch (error) {
|
||||
callback(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
callback();
|
||||
}
|
||||
|
||||
export function serializeLabels(task: any) {
|
||||
const labels = task.labels.map((label: any) => label.toJSON());
|
||||
|
||||
let serialized = '';
|
||||
|
||||
for (const label of labels) {
|
||||
serialized += ` ${label.name}`;
|
||||
|
||||
for (const attr of label.attributes) {
|
||||
serialized += ` ${attr.mutable ? '~' : '@'}`;
|
||||
serialized += `${attr.input_type}=${attr.name}:`;
|
||||
serialized += attr.values.join(',');
|
||||
}
|
||||
}
|
||||
|
||||
return serialized.trim();
|
||||
}
|
||||
|
||||
export function deserializeLabels(serialized: string) {
|
||||
const normalized = serialized.replace(/'+/g, '\'').replace(/"+/g, '"').replace(/\s+/g, ' ').trim();
|
||||
const fragments = customSplit(normalized, ' ');
|
||||
const deserialized = [];
|
||||
|
||||
let latest: any = null;
|
||||
|
||||
for (let fragment of fragments) {
|
||||
fragment = fragment.trim();
|
||||
|
||||
if ((fragment.startsWith('~')) || (fragment.startsWith('@'))) {
|
||||
const regex = /(@|~)(checkbox|select|number|text|radio)=([-,?!_0-9a-zA-Z()\s"]+):([-,?!_0-9a-zA-Z()"\s]+)/g;
|
||||
const result = regex.exec(fragment);
|
||||
|
||||
if (result === null || latest === null) {
|
||||
throw Error('Bad labels format');
|
||||
}
|
||||
|
||||
const values = customSplit(result[4], ',');
|
||||
|
||||
latest.attributes.push({
|
||||
name: result[3].replace(/^"/, '').replace(/"$/, ''),
|
||||
mutable: result[1] === '~',
|
||||
input_type: result[2],
|
||||
default_value: values[0].replace(/^"/, '').replace(/"$/, ''),
|
||||
values: values.map(val => val.replace(/^"/, '').replace(/"$/, '')),
|
||||
});
|
||||
} else {
|
||||
latest = {
|
||||
name: fragment.replace(/^"/, '').replace(/"$/, ''),
|
||||
attributes: [],
|
||||
};
|
||||
|
||||
deserialized.push(latest);
|
||||
}
|
||||
}
|
||||
|
||||
return deserialized;
|
||||
}
|
||||
|
||||
export function customSplit(string: any, separator: any) {
|
||||
const regex = /"/gi;
|
||||
const occurences = [];
|
||||
|
||||
let occurence = regex.exec(string);
|
||||
|
||||
while (occurence) {
|
||||
occurences.push(occurence.index);
|
||||
occurence = regex.exec(string);
|
||||
}
|
||||
|
||||
if (occurences.length % 2) {
|
||||
occurences.pop();
|
||||
}
|
||||
|
||||
let copy = '';
|
||||
|
||||
if (occurences.length) {
|
||||
let start = 0;
|
||||
|
||||
for (let idx = 0; idx < occurences.length; idx += 2) {
|
||||
copy += string.substr(start, occurences[idx] - start);
|
||||
copy += string.substr(occurences[idx], occurences[idx + 1] - occurences[idx] + 1)
|
||||
.replace(new RegExp(separator, 'g'), '\0');
|
||||
start = occurences[idx + 1] + 1;
|
||||
}
|
||||
|
||||
copy += string.substr(occurences[occurences.length - 1] + 1);
|
||||
} else {
|
||||
copy = string;
|
||||
}
|
||||
|
||||
return copy.split(new RegExp(separator, 'g')).map(x => x.replace(/\0/g, separator));
|
||||
}
|
||||
Loading…
Reference in New Issue