Basic & advanced configuration forms

main
Boris Sekachev 5 years ago
parent 4a0cf6b0f8
commit 87f360aa13

@ -2,16 +2,17 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React from 'react'; import React, { RefObject } from 'react';
import { Row, Col } from 'antd/lib/grid'; import { Row, Col } from 'antd/lib/grid';
import { PercentageOutlined } from '@ant-design/icons'; import { PercentageOutlined } from '@ant-design/icons';
import Input from 'antd/lib/input'; import Input from 'antd/lib/input';
import Checkbox from 'antd/lib/checkbox'; import Checkbox from 'antd/lib/checkbox';
import Tooltip from 'antd/lib/tooltip'; import Tooltip from 'antd/lib/tooltip';
import Form, { FormComponentProps } from '@ant-design/compatible/lib/form/Form'; import Form, { FormInstance, RuleObject, RuleRender } from 'antd/lib/form';
import Text from 'antd/lib/typography/Text'; import Text from 'antd/lib/typography/Text';
import patterns from 'utils/validation-patterns'; import patterns from 'utils/validation-patterns';
import { Store } from 'antd/lib/form/interface';
export interface AdvancedConfiguration { export interface AdvancedConfiguration {
bugTracker?: string; bugTracker?: string;
@ -29,273 +30,266 @@ export interface AdvancedConfiguration {
copyData?: boolean; copyData?: boolean;
} }
type Props = FormComponentProps & { const initialValues: AdvancedConfiguration = {
imageQuality: 70,
lfs: false,
useZipChunks: true,
useCache: true,
copyData: false,
};
interface Props {
onSubmit(values: AdvancedConfiguration): void; onSubmit(values: AdvancedConfiguration): void;
installedGit: boolean; installedGit: boolean;
activeFileManagerTab: string; activeFileManagerTab: string;
}; }
function isPositiveInteger(_: any, value: any, callback: any): void { function validateURL(_: RuleObject, value: string): Promise<void> {
if (!value) { if (value && !patterns.validateURL.pattern.test(value)) {
callback(); return Promise.reject(new Error('URL is not a valid URL'));
return;
} }
const intValue = +value; return Promise.resolve();
if (Number.isNaN(intValue) || !Number.isInteger(intValue) || intValue < 1) {
callback('Value must be a positive integer');
} }
callback(); function validateRepositoryPath(_: RuleObject, value: string): Promise<void> {
if (value && !patterns.validatePath.pattern.test(value)) {
return Promise.reject(new Error('Repository path is not a valid path'));
} }
function isNonNegativeInteger(_: any, value: any, callback: any): void { return Promise.resolve();
if (!value) {
callback();
return;
} }
const intValue = +value; function validateRepository(_: RuleObject, value: string): Promise<[void, void]> | Promise<void> {
if (Number.isNaN(intValue) || intValue < 0) { if (value) {
callback('Value must be a non negative integer'); const [url, path] = value.split(/\s+/);
return Promise.all([validateURL(_, url), validateRepositoryPath(_, path)]);
} }
callback(); return Promise.resolve();
} }
function isIntegerRange(min: number, max: number, _: any, value: any, callback: any): void { const isInteger = ({ min, max }: { min?: number, max?: number }) => (
if (!value) { _: RuleObject, value?: number | string,
callback(); ): Promise<void> => {
return; if (typeof value === 'undefined' || value === '') {
return Promise.resolve();
} }
const intValue = +value; const intValue = +value;
if (Number.isNaN(intValue) || !Number.isInteger(intValue) || intValue < min || intValue > max) { if (Number.isNaN(intValue) || !Number.isInteger(intValue)) {
callback(`Value must be an integer [${min}, ${max}]`); return Promise.reject(new Error('Value must be a positive integer'));
} }
callback(); if (typeof min !== 'undefined' && intValue < min) {
return Promise.reject(new Error(`Value must be more than ${min}`));
} }
class AdvancedConfigurationForm extends React.PureComponent<Props> { if (typeof max !== 'undefined' && intValue > max) {
public submit(): Promise<void> { return Promise.reject(new Error(`Value must be less than ${max}`));
return new Promise((resolve, reject) => { }
const { form, onSubmit } = this.props;
form.validateFields((error, values): void => { return Promise.resolve();
if (!error) { };
const filteredValues = { ...values };
delete filteredValues.frameStep;
if (values.overlapSize && +values.segmentSize <= +values.overlapSize) { const validateOverlapSize: RuleRender = ({ getFieldValue }): RuleObject => ({
reject(new Error('Segment size must be more than overlap size')); validator(_: RuleObject, value?: string | number): Promise<void> {
if (typeof value !== 'undefined' && value !== '') {
const segmentSize = getFieldValue('segmentSize');
if (typeof segmentSize !== 'undefined' && segmentSize !== '') {
if (+segmentSize <= +value) {
return Promise.reject(new Error('Segment size must be more than overlap size'));
} }
}
}
return Promise.resolve();
},
});
if ( const validateStopFrame: RuleRender = ({ getFieldValue }): RuleObject => ({
typeof values.startFrame !== 'undefined' && validator(_: RuleObject, value?: string | number): Promise<void> {
typeof values.stopFrame !== 'undefined' && if (typeof value !== 'undefined' && value !== '') {
+values.stopFrame < +values.startFrame const startFrame = getFieldValue('startFrame');
) { if (typeof startFrame !== 'undefined' && startFrame !== '') {
reject(new Error('Stop frame must be more or equal start frame')); if (+startFrame > +value) {
return Promise.reject(new Error('Start frame must not be more than stop frame'));
}
}
} }
onSubmit({ return Promise.resolve();
...values, },
frameFilter: values.frameStep ? `step=${values.frameStep}` : undefined,
}); });
resolve();
} else { class AdvancedConfigurationForm extends React.PureComponent<Props> {
reject(); private formRef: RefObject<FormInstance>;
public constructor(props: Props) {
super(props);
this.formRef = React.createRef<FormInstance>();
} }
public submit(): Promise<void> {
const { onSubmit } = this.props;
if (this.formRef.current) {
this.formRef.current.resetFields();
return this.formRef.current.validateFields().then((values: Store): Promise<void> => {
const frameFilter = values.frameStep ? `step=${values.frameStep}` : undefined;
const entries = Object.entries(values)
.filter((entry: [string, unknown]): boolean => entry[0] !== frameFilter);
onSubmit({
...Object.fromEntries(entries) as any as AdvancedConfiguration,
frameFilter,
}); });
return Promise.resolve();
}); });
} }
return Promise.reject(new Error('Form ref is empty'));
}
public resetFields(): void { public resetFields(): void {
const { form } = this.props; if (this.formRef.current) {
form.resetFields(); this.formRef.current.resetFields();
}
} }
renderCopyDataChechbox(): JSX.Element { /* eslint-disable class-methods-use-this */
const { form } = this.props; private renderCopyDataChechbox(): JSX.Element {
return ( return (
<Row> <Form.Item
<Col> help='If you have a low data transfer rate over the network you can copy data into CVAT to speed up work'
<Form.Item help='If you have a low data transfer rate over the network you can copy data into CVAT to speed up work'> name='copyData'
{form.getFieldDecorator('copyData', { valuePropName='checked'
initialValue: false, >
valuePropName: 'checked',
})(
<Checkbox> <Checkbox>
<Text className='cvat-text-color'>Copy data into CVAT</Text> <Text className='cvat-text-color'>Copy data into CVAT</Text>
</Checkbox>, </Checkbox>
)}
</Form.Item> </Form.Item>
</Col>
</Row>
); );
} }
private renderImageQuality(): JSX.Element { private renderImageQuality(): JSX.Element {
const { form } = this.props;
return ( return (
<Form.Item label={<span>Image quality</span>}> <Tooltip title='Defines images compression level' mouseLeaveDelay={0}>
<Tooltip title='Defines image quality level' mouseLeaveDelay={0}> <Form.Item
{form.getFieldDecorator('imageQuality', { label='Image quality'
initialValue: 70, name='imageQuality'
rules: [ rules={[
{ {
required: true, required: true,
message: 'The field is required.', message: 'The field is required.',
}, },
{ { validator: isInteger({ min: 5, max: 100 }) },
validator: isIntegerRange.bind(null, 5, 100), ]}
}, >
], <Input
})(<Input size='large' type='number' suffix={<PercentageOutlined />} />)} size='large'
</Tooltip> type='number'
min={5}
max={100}
suffix={<PercentageOutlined />}
/>
</Form.Item> </Form.Item>
</Tooltip>
); );
} }
private renderOverlap(): JSX.Element { private renderOverlap(): JSX.Element {
const { form } = this.props;
return ( return (
<Form.Item label={<span>Overlap size</span>}>
<Tooltip title='Defines a number of intersected frames between different segments' mouseLeaveDelay={0}> <Tooltip title='Defines a number of intersected frames between different segments' mouseLeaveDelay={0}>
{form.getFieldDecorator('overlapSize', { <Form.Item
rules: [ label='Overlap size'
{ name='overlapSize'
validator: isNonNegativeInteger, dependencies={['segmentSize']}
}, rules={[{ validator: isInteger({ min: 0 }) }, validateOverlapSize]}
], >
})(<Input size='large' type='number' />)} <Input size='large' type='number' min={0} />
</Tooltip>
</Form.Item> </Form.Item>
</Tooltip>
); );
} }
private renderSegmentSize(): JSX.Element { private renderSegmentSize(): JSX.Element {
const { form } = this.props;
return ( return (
<Form.Item label={<span>Segment size</span>}>
<Tooltip title='Defines a number of frames in a segment' mouseLeaveDelay={0}> <Tooltip title='Defines a number of frames in a segment' mouseLeaveDelay={0}>
{form.getFieldDecorator('segmentSize', { <Form.Item
rules: [ label='Segment size'
{ name='segmentSize'
validator: isPositiveInteger, rules={[{ validator: isInteger({ min: 1 }) }]}
}, >
], <Input size='large' type='number' min={1} />
})(<Input size='large' type='number' />)}
</Tooltip>
</Form.Item> </Form.Item>
</Tooltip>
); );
} }
private renderStartFrame(): JSX.Element { private renderStartFrame(): JSX.Element {
const { form } = this.props;
return ( return (
<Form.Item label={<span>Start frame</span>}> <Form.Item
{form.getFieldDecorator('startFrame', { label='Start frame'
rules: [ name='startFrame'
{ rules={[{ validator: isInteger({ min: 0 }) }]}
validator: isNonNegativeInteger, >
}, <Input size='large' type='number' min={0} step={1} />
],
})(<Input size='large' type='number' min={0} step={1} />)}
</Form.Item> </Form.Item>
); );
} }
private renderStopFrame(): JSX.Element { private renderStopFrame(): JSX.Element {
const { form } = this.props;
return ( return (
<Form.Item label={<span>Stop frame</span>}> <Form.Item
{form.getFieldDecorator('stopFrame', { label='Stop frame'
rules: [ name='stopFrame'
{ dependencies={['startFrame']}
validator: isNonNegativeInteger, rules={[{ validator: isInteger({ min: 0 }) }, validateStopFrame]}
}, >
], <Input size='large' type='number' min={0} step={1} />
})(<Input size='large' type='number' min={0} step={1} />)}
</Form.Item> </Form.Item>
); );
} }
private renderFrameStep(): JSX.Element { private renderFrameStep(): JSX.Element {
const { form } = this.props;
return ( return (
<Form.Item label={<span>Frame step</span>}> <Form.Item
{form.getFieldDecorator('frameStep', { label='Frame step'
rules: [ name='frameStep'
{ rules={[{ validator: isInteger({ min: 1 }) }]}
validator: isPositiveInteger, >
}, <Input size='large' type='number' min={1} step={1} />
],
})(<Input size='large' type='number' min={1} step={1} />)}
</Form.Item> </Form.Item>
); );
} }
private renderGitLFSBox(): JSX.Element { private renderGitLFSBox(): JSX.Element {
const { form } = this.props;
return ( return (
<Form.Item help='If annotation files are large, you can use git LFS feature'> <Form.Item
{form.getFieldDecorator('lfs', { help='If annotation files are large, you can use git LFS feature'
valuePropName: 'checked', name='lfs'
initialValue: false, valuePropName='checked'
})( >
<Checkbox> <Checkbox>
<Text className='cvat-text-color'>Use LFS (Large File Support):</Text> <Text className='cvat-text-color'>Use LFS (Large File Support):</Text>
</Checkbox>, </Checkbox>
)}
</Form.Item> </Form.Item>
); );
} }
private renderGitRepositoryURL(): JSX.Element { private renderGitRepositoryURL(): JSX.Element {
const { form } = this.props;
return ( return (
<Form.Item <Form.Item
hasFeedback hasFeedback
label={<span>Dataset repository URL</span>} name='repository'
label='Dataset repository URL'
extra='Attach a repository to store annotations there' extra='Attach a repository to store annotations there'
rules={[{ validator: validateRepository }]}
> >
{form.getFieldDecorator('repository', {
rules: [
{
validator: (_, value, callback): void => {
if (!value) {
callback();
} else {
const [url, path] = value.split(/\s+/);
if (!patterns.validateURL.pattern.test(url)) {
callback('Git URL is not a valid');
}
if (path && !patterns.validatePath.pattern.test(path)) {
callback('Git path is not a valid');
}
callback();
}
},
},
],
})(
<Input <Input
size='large' size='large'
placeholder='e.g. https//github.com/user/repos [annotation/<anno_file_name>.zip]' placeholder='e.g. https//github.com/user/repos [annotation/<anno_file_name>.zip]'
/>, />
)}
</Form.Item> </Form.Item>
); );
} }
@ -304,78 +298,59 @@ class AdvancedConfigurationForm extends React.PureComponent<Props> {
return ( return (
<> <>
<Row> <Row>
<Col>{this.renderGitRepositoryURL()}</Col> <Col span={24}>{this.renderGitRepositoryURL()}</Col>
</Row> </Row>
<Row> <Row>
<Col>{this.renderGitLFSBox()}</Col> <Col span={24}>{this.renderGitLFSBox()}</Col>
</Row> </Row>
</> </>
); );
} }
private renderBugTracker(): JSX.Element { private renderBugTracker(): JSX.Element {
const { form } = this.props;
return ( return (
<Form.Item <Form.Item
hasFeedback hasFeedback
label={<span>Issue tracker</span>} name='bugTracker'
label='Issue tracker'
extra='Attach issue tracker where the task is described' extra='Attach issue tracker where the task is described'
rules={[{ validator: validateURL }]}
> >
{form.getFieldDecorator('bugTracker', { <Input size='large' />
rules: [
{
validator: (_, value, callback): void => {
if (value && !patterns.validateURL.pattern.test(value)) {
callback('Issue tracker must be URL');
} else {
callback();
}
},
},
],
})(<Input size='large' />)}
</Form.Item> </Form.Item>
); );
} }
private renderUzeZipChunks(): JSX.Element { private renderUzeZipChunks(): JSX.Element {
const { form } = this.props;
return ( return (
<Form.Item help='Force to use zip chunks as compressed data. Actual for videos only.'> <Form.Item
{form.getFieldDecorator('useZipChunks', { help='Force to use zip chunks as compressed data. Actual for videos only.'
initialValue: true, name='useZipChunks'
valuePropName: 'checked', valuePropName='checked'
})( >
<Checkbox> <Checkbox>
<Text className='cvat-text-color'>Use zip chunks</Text> <Text className='cvat-text-color'>Use zip chunks</Text>
</Checkbox>, </Checkbox>
)}
</Form.Item> </Form.Item>
); );
} }
private renderCreateTaskMethod(): JSX.Element { private renderCreateTaskMethod(): JSX.Element {
const { form } = this.props;
return ( return (
<Form.Item help='Using cache to store data.'> <Form.Item
{form.getFieldDecorator('useCache', { help='Using cache to store data.'
initialValue: true, name='useCache'
valuePropName: 'checked', valuePropName='checked'
})( >
<Checkbox> <Checkbox>
<Text className='cvat-text-color'>Use cache</Text> <Text className='cvat-text-color'>Use cache</Text>
</Checkbox>, </Checkbox>
)}
</Form.Item> </Form.Item>
); );
} }
private renderChunkSize(): JSX.Element { private renderChunkSize(): JSX.Element {
const { form } = this.props;
return ( return (
<Form.Item label={<span>Chunk size</span>}>
<Tooltip <Tooltip
title={( title={(
<> <>
@ -395,33 +370,34 @@ class AdvancedConfigurationForm extends React.PureComponent<Props> {
)} )}
mouseLeaveDelay={0} mouseLeaveDelay={0}
> >
{form.getFieldDecorator('dataChunkSize', { <Form.Item
rules: [ label='Chunk size'
{ name='dataChunkSize'
validator: isPositiveInteger, rules={[{ validator: isInteger({ min: 1 }) }]}
}, >
], <Input size='large' type='number' />
})(<Input size='large' type='number' />)}
</Tooltip>
</Form.Item> </Form.Item>
</Tooltip>
); );
} }
public render(): JSX.Element { public render(): JSX.Element {
const { installedGit, activeFileManagerTab } = this.props; const { installedGit, activeFileManagerTab } = this.props;
return ( return (
<Form> <Form initialValues={initialValues} ref={this.formRef} layout='vertical'>
{activeFileManagerTab === 'share' ? (
{activeFileManagerTab === 'share' ? this.renderCopyDataChechbox() : null} <Row>
<Col>
{ this.renderCopyDataChechbox() }
</Col>
</Row>
) : null}
<Row> <Row>
<Col>{this.renderUzeZipChunks()}</Col> <Col>{this.renderUzeZipChunks()}</Col>
</Row> </Row>
<Row> <Row>
<Col>{this.renderCreateTaskMethod()}</Col> <Col>{this.renderCreateTaskMethod()}</Col>
</Row> </Row>
<Row type='flex' justify='start'> <Row type='flex' justify='start'>
<Col span={7}>{this.renderImageQuality()}</Col> <Col span={7}>{this.renderImageQuality()}</Col>
<Col span={7} offset={1}> <Col span={7} offset={1}>
@ -449,11 +425,11 @@ class AdvancedConfigurationForm extends React.PureComponent<Props> {
{installedGit ? this.renderGit() : null} {installedGit ? this.renderGit() : null}
<Row> <Row>
<Col>{this.renderBugTracker()}</Col> <Col span={24}>{this.renderBugTracker()}</Col>
</Row> </Row>
</Form> </Form>
); );
} }
} }
export default Form.create<Props>()(AdvancedConfigurationForm); export default AdvancedConfigurationForm;

@ -2,60 +2,62 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React from 'react'; import React, { RefObject } from 'react';
import Input from 'antd/lib/input'; import Input from 'antd/lib/input';
import Form, { FormComponentProps } from '@ant-design/compatible/lib/form/Form'; import Form, { FormInstance } from 'antd/lib/form';
import { Store } from 'antd/lib/form/interface';
export interface BaseConfiguration { export interface BaseConfiguration {
name: string; name: string;
} }
type Props = FormComponentProps & { interface Props {
onSubmit(values: BaseConfiguration): void; onSubmit(values: BaseConfiguration): void;
}; }
class BasicConfigurationForm extends React.PureComponent<Props> { export default class BasicConfigurationForm extends React.PureComponent<Props> {
public submit(): Promise<void> { private formRef: RefObject<FormInstance>;
return new Promise((resolve, reject) => {
const { form, onSubmit } = this.props;
form.validateFields((error, values): void => { public constructor(props: Props) {
if (!error) { super(props);
onSubmit({ this.formRef = React.createRef<FormInstance>();
name: values.name,
});
resolve();
} else {
reject();
} }
});
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 Promise.reject(new Error('Form ref is empty'));
}
public resetFields(): void { public resetFields(): void {
const { form } = this.props; if (this.formRef.current) {
form.resetFields(); this.formRef.current.resetFields();
}
} }
public render(): JSX.Element { public render(): JSX.Element {
const { form } = this.props;
const { getFieldDecorator } = form;
return ( return (
<Form onSubmit={(e: React.FormEvent): void => e.preventDefault()}> <Form ref={this.formRef} layout='vertical'>
<Form.Item hasFeedback label={<span>Name</span>}> <Form.Item
{getFieldDecorator('name', { hasFeedback
rules: [ name='name'
label={<span>Name</span>}
rules={[
{ {
required: true, required: true,
message: 'Please, specify a name', message: 'Task name cannot be empty',
}, },
], ]}
})(<Input />)} >
<Input />
</Form.Item> </Form.Item>
</Form> </Form>
); );
} }
} }
export default Form.create<Props>()(BasicConfigurationForm);

@ -2,7 +2,7 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React from 'react'; import React, { RefObject } from 'react';
import { RouteComponentProps } from 'react-router'; import { RouteComponentProps } from 'react-router';
import { withRouter } from 'react-router-dom'; import { withRouter } from 'react-router-dom';
import { Row, Col } from 'antd/lib/grid'; import { Row, Col } from 'antd/lib/grid';
@ -58,15 +58,15 @@ const defaultState = {
}; };
class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps, State> { class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps, State> {
private basicConfigurationComponent: any; private basicConfigurationComponent: RefObject<BasicConfigurationForm>;
private advancedConfigurationComponent: RefObject<AdvancedConfigurationForm>;
private advancedConfigurationComponent: any;
private fileManagerContainer: any; private fileManagerContainer: any;
public constructor(props: Props & RouteComponentProps) { public constructor(props: Props & RouteComponentProps) {
super(props); super(props);
this.state = { ...defaultState }; this.state = { ...defaultState };
this.basicConfigurationComponent = React.createRef<BasicConfigurationForm>();
this.advancedConfigurationComponent = React.createRef<AdvancedConfigurationForm>();
} }
public componentDidMount(): void { public componentDidMount(): void {
@ -88,9 +88,11 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
btn, btn,
}); });
this.basicConfigurationComponent.resetFields(); if (this.basicConfigurationComponent.current) {
if (this.advancedConfigurationComponent) { this.basicConfigurationComponent.current.resetFields();
this.advancedConfigurationComponent.resetFields(); }
if (this.advancedConfigurationComponent.current) {
this.advancedConfigurationComponent.current.resetFields();
} }
this.fileManagerContainer.reset(); this.fileManagerContainer.reset();
@ -138,7 +140,7 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
const values = this.state; const values = this.state;
this.setState({ this.setState({
...values, ...values,
activeFileManagerTab: key activeFileManagerTab: key,
}); });
}; };
@ -159,18 +161,17 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
return; return;
} }
this.basicConfigurationComponent if (this.basicConfigurationComponent.current) {
.submit() this.basicConfigurationComponent.current.submit()
.then(() => { .then(() => {
if (this.advancedConfigurationComponent) { if (this.advancedConfigurationComponent.current) {
return this.advancedConfigurationComponent.submit(); return this.advancedConfigurationComponent.current.submit();
} }
return new Promise((resolve): void => { return new Promise((resolve): void => {
resolve(); resolve();
}); });
}) }).then((): void => {
.then((): void => {
const { onCreate } = this.props; const { onCreate } = this.props;
onCreate(this.state); onCreate(this.state);
}) })
@ -180,15 +181,14 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
description: error.toString(), description: error.toString(),
}); });
}); });
}
}; };
private renderBasicBlock(): JSX.Element { private renderBasicBlock(): JSX.Element {
return ( return (
<Col span={24}> <Col span={24}>
<BasicConfigurationForm <BasicConfigurationForm
wrappedComponentRef={(component: any): void => { ref={this.basicConfigurationComponent}
this.basicConfigurationComponent = component;
}}
onSubmit={this.handleSubmitBasicConfiguration} onSubmit={this.handleSubmitBasicConfiguration}
/> />
</Col> </Col>
@ -260,16 +260,15 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
private renderAdvancedBlock(): JSX.Element { private renderAdvancedBlock(): JSX.Element {
const { installedGit } = this.props; const { installedGit } = this.props;
const { activeFileManagerTab } = this.state;
return ( return (
<Col span={24}> <Col span={24}>
<Collapse> <Collapse>
<Collapse.Panel key='1' header={<Text className='cvat-title'>Advanced configuration</Text>}> <Collapse.Panel key='1' header={<Text className='cvat-title'>Advanced configuration</Text>}>
<AdvancedConfigurationForm <AdvancedConfigurationForm
installedGit={installedGit} installedGit={installedGit}
activeFileManagerTab={this.state.activeFileManagerTab} activeFileManagerTab={activeFileManagerTab}
wrappedComponentRef={(component: any): void => { ref={this.advancedConfigurationComponent}
this.advancedConfigurationComponent = component;
}}
onSubmit={this.handleSubmitAdvancedConfiguration} onSubmit={this.handleSubmitAdvancedConfiguration}
/> />
</Collapse.Panel> </Collapse.Panel>
@ -283,7 +282,7 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
const loading = !!status && status !== 'CREATED' && status !== 'FAILED'; const loading = !!status && status !== 'CREATED' && status !== 'FAILED';
return ( return (
<Row type='flex' justify='start' align='middle' className='cvat-create-task-content'> <Row justify='start' align='middle' className='cvat-create-task-content'>
<Col span={24}> <Col span={24}>
<Text className='cvat-title'>Basic configuration</Text> <Text className='cvat-title'>Basic configuration</Text>
</Col> </Col>

Loading…
Cancel
Save