[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