|
|
|
|
@ -2,7 +2,7 @@
|
|
|
|
|
//
|
|
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
|
|
|
|
|
|
import React from 'react';
|
|
|
|
|
import React, { RefObject } from 'react';
|
|
|
|
|
import { Row, Col } from 'antd/lib/grid';
|
|
|
|
|
import Icon, { CloseCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
|
|
|
|
import Input from 'antd/lib/input';
|
|
|
|
|
@ -10,11 +10,11 @@ import Button from 'antd/lib/button';
|
|
|
|
|
import Checkbox from 'antd/lib/checkbox';
|
|
|
|
|
import Tooltip from 'antd/lib/tooltip';
|
|
|
|
|
import Select from 'antd/lib/select';
|
|
|
|
|
import Form, { FormComponentProps } from '@ant-design/compatible/lib/form/Form';
|
|
|
|
|
import Text from 'antd/lib/typography/Text';
|
|
|
|
|
import Form, { FormInstance } from 'antd/lib/form';
|
|
|
|
|
import Badge from 'antd/lib/badge';
|
|
|
|
|
import ColorPicker from 'components/annotation-page/standard-workspace/objects-side-bar/color-picker';
|
|
|
|
|
import { Store } from 'antd/lib/form/interface';
|
|
|
|
|
|
|
|
|
|
import ColorPicker from 'components/annotation-page/standard-workspace/objects-side-bar/color-picker';
|
|
|
|
|
import { ColorizeIcon } from 'icons';
|
|
|
|
|
import patterns from 'utils/validation-patterns';
|
|
|
|
|
import consts from 'consts';
|
|
|
|
|
@ -30,136 +30,123 @@ export enum AttributeType {
|
|
|
|
|
NUMBER = 'NUMBER',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Props = FormComponentProps & {
|
|
|
|
|
interface Props {
|
|
|
|
|
label: Label | null;
|
|
|
|
|
labelNames?: string[];
|
|
|
|
|
onSubmit: (label: Label | null) => void;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class LabelForm extends React.PureComponent<Props, {}> {
|
|
|
|
|
export default class LabelForm extends React.Component<Props> {
|
|
|
|
|
private continueAfterSubmit: boolean;
|
|
|
|
|
private formRef: RefObject<FormInstance>;
|
|
|
|
|
|
|
|
|
|
constructor(props: Props) {
|
|
|
|
|
super(props);
|
|
|
|
|
this.continueAfterSubmit = false;
|
|
|
|
|
this.formRef = React.createRef<FormInstance>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private handleSubmit = (e: React.FormEvent): void => {
|
|
|
|
|
const { form, label, onSubmit } = this.props;
|
|
|
|
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
form.validateFields((error, formValues): void => {
|
|
|
|
|
if (!error) {
|
|
|
|
|
onSubmit({
|
|
|
|
|
name: formValues.labelName,
|
|
|
|
|
id: label ? label.id : idGenerator(),
|
|
|
|
|
color: formValues.labelColor,
|
|
|
|
|
attributes: formValues.keys.map(
|
|
|
|
|
(key: number, index: number): Attribute => {
|
|
|
|
|
let attrValues = formValues.values[key];
|
|
|
|
|
if (!Array.isArray(attrValues)) {
|
|
|
|
|
if (formValues.type[key] === AttributeType.NUMBER) {
|
|
|
|
|
attrValues = attrValues.split(';');
|
|
|
|
|
} else {
|
|
|
|
|
attrValues = [attrValues];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
attrValues = attrValues.map((value: string) => value.trim());
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
name: formValues.attrName[key],
|
|
|
|
|
input_type: formValues.type[key],
|
|
|
|
|
mutable: formValues.mutable[key],
|
|
|
|
|
id: label && index < label.attributes.length ? label.attributes[index].id : key,
|
|
|
|
|
values: attrValues,
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
form.resetFields();
|
|
|
|
|
|
|
|
|
|
if (!this.continueAfterSubmit) {
|
|
|
|
|
onSubmit(null);
|
|
|
|
|
private handleSubmit = (values: Store): void => {
|
|
|
|
|
const { label, onSubmit } = this.props;
|
|
|
|
|
|
|
|
|
|
onSubmit({
|
|
|
|
|
name: values.labelName,
|
|
|
|
|
id: label ? label.id : idGenerator(),
|
|
|
|
|
color: values.labelColor,
|
|
|
|
|
attributes: values.attributes.map((attribute: Store) => {
|
|
|
|
|
let attrValues: any = attribute.values;
|
|
|
|
|
if (attribute.input_type === AttributeType.NUMBER) {
|
|
|
|
|
attrValues = attrValues.split(';');
|
|
|
|
|
} else {
|
|
|
|
|
attrValues = [attrValues];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
attrValues = attrValues.map((value: string) => value.trim());
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...attribute,
|
|
|
|
|
values: attrValues,
|
|
|
|
|
input_type: attribute.type,
|
|
|
|
|
};
|
|
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (this.formRef.current) {
|
|
|
|
|
this.formRef.current.resetFields();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!this.continueAfterSubmit) {
|
|
|
|
|
onSubmit(null);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private addAttribute = (): void => {
|
|
|
|
|
const { form } = this.props;
|
|
|
|
|
const keys = form.getFieldValue('keys');
|
|
|
|
|
const nextKeys = keys.concat(idGenerator());
|
|
|
|
|
form.setFieldsValue({
|
|
|
|
|
keys: nextKeys,
|
|
|
|
|
});
|
|
|
|
|
if (this.formRef.current) {
|
|
|
|
|
const attributes = this.formRef.current.getFieldValue('attributes');
|
|
|
|
|
this.formRef.current.setFieldsValue({ attributes: [...attributes, { id: idGenerator() }] });
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private removeAttribute = (key: number): void => {
|
|
|
|
|
const { form } = this.props;
|
|
|
|
|
const keys = form.getFieldValue('keys');
|
|
|
|
|
form.setFieldsValue({
|
|
|
|
|
keys: keys.filter((_key: number) => _key !== key),
|
|
|
|
|
});
|
|
|
|
|
if (this.formRef.current) {
|
|
|
|
|
const attributes = this.formRef.current.getFieldValue('attributes');
|
|
|
|
|
this.formRef.current.setFieldsValue({
|
|
|
|
|
attributes: attributes.filter((_: any, id: number) => id !== key),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private renderAttributeNameInput(key: number, attr: Attribute | null): JSX.Element {
|
|
|
|
|
/* eslint-disable class-methods-use-this */
|
|
|
|
|
private renderAttributeNameInput(fieldInstance: any, attr: Attribute | null): JSX.Element {
|
|
|
|
|
const { key } = fieldInstance;
|
|
|
|
|
const locked = attr ? attr.id >= 0 : false;
|
|
|
|
|
const value = attr ? attr.name : '';
|
|
|
|
|
const { form } = this.props;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Col span={5}>
|
|
|
|
|
<Form.Item hasFeedback>
|
|
|
|
|
{form.getFieldDecorator(`attrName[${key}]`, {
|
|
|
|
|
initialValue: value,
|
|
|
|
|
rules: [
|
|
|
|
|
{
|
|
|
|
|
required: true,
|
|
|
|
|
message: 'Please specify a name',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
pattern: patterns.validateAttributeName.pattern,
|
|
|
|
|
message: patterns.validateAttributeName.message,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
})(<Input className='cvat-attribute-name-input' disabled={locked} placeholder='Name' />)}
|
|
|
|
|
</Form.Item>
|
|
|
|
|
</Col>
|
|
|
|
|
<Form.Item
|
|
|
|
|
hasFeedback
|
|
|
|
|
name={[key, 'name']}
|
|
|
|
|
fieldKey={[fieldInstance.fieldKey, 'name']}
|
|
|
|
|
initialValue={value}
|
|
|
|
|
rules={[
|
|
|
|
|
{
|
|
|
|
|
required: true,
|
|
|
|
|
message: 'Please specify a name',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
pattern: patterns.validateAttributeName.pattern,
|
|
|
|
|
message: patterns.validateAttributeName.message,
|
|
|
|
|
},
|
|
|
|
|
]}
|
|
|
|
|
>
|
|
|
|
|
<Input className='cvat-attribute-name-input' disabled={locked} placeholder='Name' />
|
|
|
|
|
</Form.Item>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private renderAttributeTypeInput(key: number, attr: Attribute | null): JSX.Element {
|
|
|
|
|
private renderAttributeTypeInput(fieldInstance: any, attr: Attribute | null): JSX.Element {
|
|
|
|
|
const { key } = fieldInstance;
|
|
|
|
|
const locked = attr ? attr.id >= 0 : false;
|
|
|
|
|
const type = attr ? attr.input_type.toUpperCase() : AttributeType.SELECT;
|
|
|
|
|
const { form } = this.props;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Col span={4}>
|
|
|
|
|
<Form.Item>
|
|
|
|
|
<Tooltip title='An HTML element representing the attribute' mouseLeaveDelay={0}>
|
|
|
|
|
{form.getFieldDecorator(`type[${key}]`, {
|
|
|
|
|
initialValue: type,
|
|
|
|
|
})(
|
|
|
|
|
<Select className='cvat-attribute-type-input' disabled={locked}>
|
|
|
|
|
<Select.Option value={AttributeType.SELECT}>Select</Select.Option>
|
|
|
|
|
<Select.Option value={AttributeType.RADIO}>Radio</Select.Option>
|
|
|
|
|
<Select.Option value={AttributeType.CHECKBOX}>Checkbox</Select.Option>
|
|
|
|
|
<Select.Option value={AttributeType.TEXT}>Text</Select.Option>
|
|
|
|
|
<Select.Option value={AttributeType.NUMBER}>Number</Select.Option>
|
|
|
|
|
</Select>,
|
|
|
|
|
)}
|
|
|
|
|
</Tooltip>
|
|
|
|
|
<Tooltip title='An HTML element representing the attribute' mouseLeaveDelay={0}>
|
|
|
|
|
<Form.Item name={[key, 'type']} fieldKey={[fieldInstance.fieldKey, 'type']} initialValue={type}>
|
|
|
|
|
<Select className='cvat-attribute-type-input' disabled={locked}>
|
|
|
|
|
<Select.Option value={AttributeType.SELECT}>Select</Select.Option>
|
|
|
|
|
<Select.Option value={AttributeType.RADIO}>Radio</Select.Option>
|
|
|
|
|
<Select.Option value={AttributeType.CHECKBOX}>Checkbox</Select.Option>
|
|
|
|
|
<Select.Option value={AttributeType.TEXT}>Text</Select.Option>
|
|
|
|
|
<Select.Option value={AttributeType.NUMBER}>Number</Select.Option>
|
|
|
|
|
</Select>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
</Col>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private renderAttributeValuesInput(key: number, attr: Attribute | null): JSX.Element {
|
|
|
|
|
private renderAttributeValuesInput(fieldInstance: any, attr: Attribute | null): JSX.Element {
|
|
|
|
|
const { key } = fieldInstance;
|
|
|
|
|
const locked = attr ? attr.id >= 0 : false;
|
|
|
|
|
const existedValues = attr ? attr.values : [];
|
|
|
|
|
const { form } = this.props;
|
|
|
|
|
|
|
|
|
|
const validator = (_: any, values: string[], callback: any): void => {
|
|
|
|
|
if (locked && existedValues) {
|
|
|
|
|
@ -179,55 +166,51 @@ class LabelForm extends React.PureComponent<Props, {}> {
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Tooltip title='Press enter to add a new value' mouseLeaveDelay={0}>
|
|
|
|
|
<Form.Item>
|
|
|
|
|
{form.getFieldDecorator(`values[${key}]`, {
|
|
|
|
|
initialValue: existedValues,
|
|
|
|
|
rules: [
|
|
|
|
|
{
|
|
|
|
|
required: true,
|
|
|
|
|
message: 'Please specify values',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
validator,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
})(
|
|
|
|
|
<Select
|
|
|
|
|
className='cvat-attribute-values-input'
|
|
|
|
|
mode='tags'
|
|
|
|
|
dropdownMenuStyle={{ display: 'none' }}
|
|
|
|
|
placeholder='Attribute values'
|
|
|
|
|
/>,
|
|
|
|
|
)}
|
|
|
|
|
<Form.Item
|
|
|
|
|
name={[key, 'values']}
|
|
|
|
|
fieldKey={[fieldInstance.fieldKey, 'values']}
|
|
|
|
|
initialValue={existedValues}
|
|
|
|
|
rules={[
|
|
|
|
|
{
|
|
|
|
|
required: true,
|
|
|
|
|
message: 'Please specify values',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
validator,
|
|
|
|
|
},
|
|
|
|
|
]}
|
|
|
|
|
>
|
|
|
|
|
<Select
|
|
|
|
|
className='cvat-attribute-values-input'
|
|
|
|
|
mode='tags'
|
|
|
|
|
placeholder='Attribute values'
|
|
|
|
|
dropdownStyle={{ display: 'none' }}
|
|
|
|
|
/>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private renderBooleanValueInput(key: number, attr: Attribute | null): JSX.Element {
|
|
|
|
|
private renderBooleanValueInput(fieldInstance: any, attr: Attribute | null): JSX.Element {
|
|
|
|
|
const { key } = fieldInstance;
|
|
|
|
|
const value = attr ? attr.values[0] : 'false';
|
|
|
|
|
const { form } = this.props;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Tooltip title='Specify a default value' mouseLeaveDelay={0}>
|
|
|
|
|
<Form.Item>
|
|
|
|
|
{form.getFieldDecorator(`values[${key}]`, {
|
|
|
|
|
initialValue: value,
|
|
|
|
|
})(
|
|
|
|
|
<Select className='cvat-attribute-values-input'>
|
|
|
|
|
<Select.Option value='false'> False </Select.Option>
|
|
|
|
|
<Select.Option value='true'> True </Select.Option>
|
|
|
|
|
</Select>,
|
|
|
|
|
)}
|
|
|
|
|
<Form.Item name={[key, 'values']} fieldKey={[fieldInstance.fieldKey, 'values']} initialValue={value}>
|
|
|
|
|
<Select className='cvat-attribute-values-input'>
|
|
|
|
|
<Select.Option value='false'> False </Select.Option>
|
|
|
|
|
<Select.Option value='true'> True </Select.Option>
|
|
|
|
|
</Select>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private renderNumberRangeInput(key: number, attr: Attribute | null): JSX.Element {
|
|
|
|
|
private renderNumberRangeInput(fieldInstance: any, attr: Attribute | null): JSX.Element {
|
|
|
|
|
const { key } = fieldInstance;
|
|
|
|
|
const locked = attr ? attr.id >= 0 : false;
|
|
|
|
|
const value = attr ? attr.values.join(';') : '';
|
|
|
|
|
const { form } = this.props;
|
|
|
|
|
|
|
|
|
|
const validator = (_: any, strNumbers: string, callback: any): void => {
|
|
|
|
|
const numbers = strNumbers.split(';').map((number): number => Number.parseFloat(number));
|
|
|
|
|
@ -259,65 +242,57 @@ class LabelForm extends React.PureComponent<Props, {}> {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Form.Item>
|
|
|
|
|
{form.getFieldDecorator(`values[${key}]`, {
|
|
|
|
|
initialValue: value,
|
|
|
|
|
rules: [
|
|
|
|
|
{
|
|
|
|
|
required: true,
|
|
|
|
|
message: 'Please set a range',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
validator,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
})(<Input className='cvat-attribute-values-input' disabled={locked} placeholder='min;max;step' />)}
|
|
|
|
|
<Form.Item
|
|
|
|
|
name={[key, 'values']}
|
|
|
|
|
fieldKey={[fieldInstance.fieldKey, 'values']}
|
|
|
|
|
initialValue={value}
|
|
|
|
|
rules={[
|
|
|
|
|
{
|
|
|
|
|
required: true,
|
|
|
|
|
message: 'Please set a range',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
validator,
|
|
|
|
|
},
|
|
|
|
|
]}
|
|
|
|
|
>
|
|
|
|
|
<Input className='cvat-attribute-values-input' disabled={locked} placeholder='min;max;step' />
|
|
|
|
|
</Form.Item>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private renderDefaultValueInput(key: number, attr: Attribute | null): JSX.Element {
|
|
|
|
|
private renderDefaultValueInput(fieldInstance: any, attr: Attribute | null): JSX.Element {
|
|
|
|
|
const { key } = fieldInstance;
|
|
|
|
|
const value = attr ? attr.values[0] : '';
|
|
|
|
|
const { form } = this.props;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Form.Item>
|
|
|
|
|
{form.getFieldDecorator(`values[${key}]`, {
|
|
|
|
|
initialValue: value,
|
|
|
|
|
})(<Input className='cvat-attribute-values-input' placeholder='Default value' />)}
|
|
|
|
|
<Form.Item name={[key, 'values']} fieldKey={[fieldInstance.fieldKey, 'values']} initialValue={value}>
|
|
|
|
|
<Input className='cvat-attribute-values-input' placeholder='Default value' />
|
|
|
|
|
</Form.Item>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private renderMutableAttributeInput(key: number, attr: Attribute | null): JSX.Element {
|
|
|
|
|
private renderMutableAttributeInput(fieldInstance: any, attr: Attribute | null): JSX.Element {
|
|
|
|
|
const { key } = fieldInstance;
|
|
|
|
|
const locked = attr ? attr.id >= 0 : false;
|
|
|
|
|
const value = attr ? attr.mutable : false;
|
|
|
|
|
const { form } = this.props;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Form.Item>
|
|
|
|
|
<Tooltip title='Can this attribute be changed frame to frame?' mouseLeaveDelay={0}>
|
|
|
|
|
{form.getFieldDecorator(`mutable[${key}]`, {
|
|
|
|
|
initialValue: value,
|
|
|
|
|
valuePropName: 'checked',
|
|
|
|
|
})(
|
|
|
|
|
<Checkbox className='cvat-attribute-mutable-checkbox' disabled={locked}>
|
|
|
|
|
{' '}
|
|
|
|
|
Mutable
|
|
|
|
|
{' '}
|
|
|
|
|
</Checkbox>,
|
|
|
|
|
)}
|
|
|
|
|
</Tooltip>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
<Tooltip title='Can this attribute be changed frame to frame?' mouseLeaveDelay={0}>
|
|
|
|
|
<Form.Item name={[key, 'mutable']} fieldKey={[fieldInstance.fieldKey, 'mutable']} initialValue={value} valuePropName='checked'>
|
|
|
|
|
<Checkbox className='cvat-attribute-mutable-checkbox' disabled={locked}>Mutable</Checkbox>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private renderDeleteAttributeButton(key: number, attr: Attribute | null): JSX.Element {
|
|
|
|
|
private renderDeleteAttributeButton(fieldInstance: any, attr: Attribute | null): JSX.Element {
|
|
|
|
|
const { key } = fieldInstance;
|
|
|
|
|
const locked = attr ? attr.id >= 0 : false;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Form.Item>
|
|
|
|
|
<Tooltip title='Delete the attribute' mouseLeaveDelay={0}>
|
|
|
|
|
<Tooltip title='Delete the attribute' mouseLeaveDelay={0}>
|
|
|
|
|
<Form.Item>
|
|
|
|
|
<Button
|
|
|
|
|
type='link'
|
|
|
|
|
className='cvat-delete-attribute-button'
|
|
|
|
|
@ -328,135 +303,138 @@ class LabelForm extends React.PureComponent<Props, {}> {
|
|
|
|
|
>
|
|
|
|
|
<CloseCircleOutlined />
|
|
|
|
|
</Button>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private renderAttribute = (key: number): JSX.Element => {
|
|
|
|
|
const { label, form } = this.props;
|
|
|
|
|
const attr = label ? label.attributes.filter((_attr: any): boolean => _attr.id === key)[0] : null;
|
|
|
|
|
private renderAttribute = (fieldInstance: any): JSX.Element => {
|
|
|
|
|
const { label } = this.props;
|
|
|
|
|
const { key } = fieldInstance;
|
|
|
|
|
const fieldValue = this.formRef.current?.getFieldValue('attributes')[key];
|
|
|
|
|
const attr = label ? label.attributes.filter((_attr: any): boolean => _attr.id === fieldValue.id)[0] : null;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Form.Item key={key}>
|
|
|
|
|
<Row
|
|
|
|
|
type='flex'
|
|
|
|
|
justify='space-between'
|
|
|
|
|
align='middle'
|
|
|
|
|
cvat-attribute-id={key}
|
|
|
|
|
className='cvat-attribute-inputs-wrapper'
|
|
|
|
|
>
|
|
|
|
|
{this.renderAttributeNameInput(key, attr)}
|
|
|
|
|
{this.renderAttributeTypeInput(key, attr)}
|
|
|
|
|
<Col span={6}>
|
|
|
|
|
{((): JSX.Element => {
|
|
|
|
|
const type = form.getFieldValue(`type[${key}]`);
|
|
|
|
|
let element = null;
|
|
|
|
|
if ([AttributeType.SELECT, AttributeType.RADIO].includes(type)) {
|
|
|
|
|
element = this.renderAttributeValuesInput(key, attr);
|
|
|
|
|
} else if (type === AttributeType.CHECKBOX) {
|
|
|
|
|
element = this.renderBooleanValueInput(key, attr);
|
|
|
|
|
} else if (type === AttributeType.NUMBER) {
|
|
|
|
|
element = this.renderNumberRangeInput(key, attr);
|
|
|
|
|
} else {
|
|
|
|
|
element = this.renderDefaultValueInput(key, attr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return element;
|
|
|
|
|
})()}
|
|
|
|
|
</Col>
|
|
|
|
|
<Col span={5}>{this.renderMutableAttributeInput(key, attr)}</Col>
|
|
|
|
|
<Col span={2}>{this.renderDeleteAttributeButton(key, attr)}</Col>
|
|
|
|
|
</Row>
|
|
|
|
|
<Form.Item
|
|
|
|
|
noStyle
|
|
|
|
|
key={key}
|
|
|
|
|
shouldUpdate
|
|
|
|
|
>
|
|
|
|
|
{() => ((
|
|
|
|
|
<Row
|
|
|
|
|
justify='space-between'
|
|
|
|
|
align='middle'
|
|
|
|
|
cvat-attribute-id={key}
|
|
|
|
|
className='cvat-attribute-inputs-wrapper'
|
|
|
|
|
>
|
|
|
|
|
<Col span={5}>{this.renderAttributeNameInput(fieldInstance, attr)}</Col>
|
|
|
|
|
<Col span={4}>{this.renderAttributeTypeInput(fieldInstance, attr)}</Col>
|
|
|
|
|
<Col span={6}>
|
|
|
|
|
{((): JSX.Element => {
|
|
|
|
|
const currentFieldValue = this.formRef.current?.getFieldValue('attributes')[key];
|
|
|
|
|
const type = currentFieldValue.type || AttributeType.SELECT;
|
|
|
|
|
let element = null;
|
|
|
|
|
if ([AttributeType.SELECT, AttributeType.RADIO].includes(type)) {
|
|
|
|
|
element = this.renderAttributeValuesInput(fieldInstance, attr);
|
|
|
|
|
} else if (type === AttributeType.CHECKBOX) {
|
|
|
|
|
element = this.renderBooleanValueInput(fieldInstance, attr);
|
|
|
|
|
} else if (type === AttributeType.NUMBER) {
|
|
|
|
|
element = this.renderNumberRangeInput(fieldInstance, attr);
|
|
|
|
|
} else {
|
|
|
|
|
element = this.renderDefaultValueInput(fieldInstance, attr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return element;
|
|
|
|
|
})()}
|
|
|
|
|
</Col>
|
|
|
|
|
<Col span={5}>{this.renderMutableAttributeInput(fieldInstance, attr)}</Col>
|
|
|
|
|
<Col span={2}>{this.renderDeleteAttributeButton(fieldInstance, attr)}</Col>
|
|
|
|
|
</Row>
|
|
|
|
|
))}
|
|
|
|
|
</Form.Item>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private renderLabelNameInput(): JSX.Element {
|
|
|
|
|
const { label, form, labelNames } = this.props;
|
|
|
|
|
const { label, labelNames } = this.props;
|
|
|
|
|
const value = label ? label.name : '';
|
|
|
|
|
const locked = label ? label.id >= 0 : false;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Col span={10}>
|
|
|
|
|
<Form.Item hasFeedback>
|
|
|
|
|
{form.getFieldDecorator('labelName', {
|
|
|
|
|
initialValue: value,
|
|
|
|
|
rules: [
|
|
|
|
|
{
|
|
|
|
|
required: true,
|
|
|
|
|
message: 'Please specify a name',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
pattern: patterns.validateAttributeName.pattern,
|
|
|
|
|
message: patterns.validateAttributeName.message,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
validator: async (_rule: any, labelName: string, callback: Function) => {
|
|
|
|
|
if (labelNames && labelNames.includes(labelName)) {
|
|
|
|
|
callback('Label name must be unique for the task');
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
<Form.Item
|
|
|
|
|
hasFeedback
|
|
|
|
|
name='labelName'
|
|
|
|
|
initialValue={value}
|
|
|
|
|
rules={
|
|
|
|
|
[
|
|
|
|
|
{
|
|
|
|
|
required: true,
|
|
|
|
|
message: 'Please specify a name',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
pattern: patterns.validateAttributeName.pattern,
|
|
|
|
|
message: patterns.validateAttributeName.message,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
validator: async (_rule: any, labelName: string, callback: Function) => {
|
|
|
|
|
if (labelNames && labelNames.includes(labelName)) {
|
|
|
|
|
callback('Label name must be unique for the task');
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
})(<Input disabled={locked} placeholder='Label name' />)}
|
|
|
|
|
</Form.Item>
|
|
|
|
|
</Col>
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
>
|
|
|
|
|
<Input disabled={locked} placeholder='Label name' />
|
|
|
|
|
</Form.Item>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private renderNewAttributeButton(): JSX.Element {
|
|
|
|
|
return (
|
|
|
|
|
<Col span={6}>
|
|
|
|
|
<Form.Item>
|
|
|
|
|
<Button type='ghost' onClick={this.addAttribute} className='cvat-new-attribute-button'>
|
|
|
|
|
Add an attribute
|
|
|
|
|
<PlusOutlined />
|
|
|
|
|
</Button>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
</Col>
|
|
|
|
|
<Form.Item>
|
|
|
|
|
<Button type='ghost' onClick={this.addAttribute} className='cvat-new-attribute-button'>
|
|
|
|
|
Add an attribute
|
|
|
|
|
<PlusOutlined />
|
|
|
|
|
</Button>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private renderDoneButton(): JSX.Element {
|
|
|
|
|
return (
|
|
|
|
|
<Col>
|
|
|
|
|
<Tooltip title='Save the label and return' mouseLeaveDelay={0}>
|
|
|
|
|
<Button
|
|
|
|
|
style={{ width: '150px' }}
|
|
|
|
|
type='primary'
|
|
|
|
|
htmlType='submit'
|
|
|
|
|
onClick={(): void => {
|
|
|
|
|
this.continueAfterSubmit = false;
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
Done
|
|
|
|
|
</Button>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
</Col>
|
|
|
|
|
<Tooltip title='Save the label and return' mouseLeaveDelay={0}>
|
|
|
|
|
<Button
|
|
|
|
|
style={{ width: '150px' }}
|
|
|
|
|
type='primary'
|
|
|
|
|
htmlType='submit'
|
|
|
|
|
onClick={(): void => {
|
|
|
|
|
this.continueAfterSubmit = false;
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
Done
|
|
|
|
|
</Button>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private renderContinueButton(): JSX.Element {
|
|
|
|
|
private renderContinueButton(): JSX.Element | null {
|
|
|
|
|
const { label } = this.props;
|
|
|
|
|
|
|
|
|
|
return label ? (
|
|
|
|
|
<div />
|
|
|
|
|
) : (
|
|
|
|
|
<Col offset={1}>
|
|
|
|
|
<Tooltip title='Save the label and create one more' mouseLeaveDelay={0}>
|
|
|
|
|
<Button
|
|
|
|
|
style={{ width: '150px' }}
|
|
|
|
|
type='primary'
|
|
|
|
|
htmlType='submit'
|
|
|
|
|
onClick={(): void => {
|
|
|
|
|
this.continueAfterSubmit = true;
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
Continue
|
|
|
|
|
</Button>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
</Col>
|
|
|
|
|
if (label) return null;
|
|
|
|
|
return (
|
|
|
|
|
<Tooltip title='Save the label and create one more' mouseLeaveDelay={0}>
|
|
|
|
|
<Button
|
|
|
|
|
style={{ width: '150px' }}
|
|
|
|
|
type='primary'
|
|
|
|
|
htmlType='submit'
|
|
|
|
|
onClick={(): void => {
|
|
|
|
|
this.continueAfterSubmit = true;
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
Continue
|
|
|
|
|
</Button>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -464,83 +442,93 @@ class LabelForm extends React.PureComponent<Props, {}> {
|
|
|
|
|
const { onSubmit } = this.props;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Col offset={1}>
|
|
|
|
|
<Tooltip title='Do not save the label and return' mouseLeaveDelay={0}>
|
|
|
|
|
<Button
|
|
|
|
|
danger
|
|
|
|
|
style={{ width: '150px' }}
|
|
|
|
|
onClick={(): void => {
|
|
|
|
|
onSubmit(null);
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
Cancel
|
|
|
|
|
</Button>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
</Col>
|
|
|
|
|
<Tooltip title='Do not save the label and return' mouseLeaveDelay={0}>
|
|
|
|
|
<Button
|
|
|
|
|
danger
|
|
|
|
|
style={{ width: '150px' }}
|
|
|
|
|
onClick={(): void => {
|
|
|
|
|
onSubmit(null);
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
Cancel
|
|
|
|
|
</Button>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private renderChangeColorButton(): JSX.Element {
|
|
|
|
|
const { label, form } = this.props;
|
|
|
|
|
const { label } = this.props;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Col span={3}>
|
|
|
|
|
<Form.Item>
|
|
|
|
|
{form.getFieldDecorator('labelColor', {
|
|
|
|
|
initialValue: label && label.color ? label.color : undefined,
|
|
|
|
|
})(
|
|
|
|
|
<ColorPicker placement='bottom'>
|
|
|
|
|
<Tooltip title='Change color of the label'>
|
|
|
|
|
<Button type='default' className='cvat-change-task-label-color-button'>
|
|
|
|
|
<Badge
|
|
|
|
|
className='cvat-change-task-label-color-badge'
|
|
|
|
|
color={form.getFieldValue('labelColor') || consts.NEW_LABEL_COLOR}
|
|
|
|
|
text={<Icon component={ColorizeIcon} />}
|
|
|
|
|
/>
|
|
|
|
|
</Button>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
</ColorPicker>,
|
|
|
|
|
)}
|
|
|
|
|
</Form.Item>
|
|
|
|
|
</Col>
|
|
|
|
|
<Form.Item name='labelColor' initialValue={label && label.color ? label.color : undefined}>
|
|
|
|
|
<ColorPicker placement='bottom'>
|
|
|
|
|
<Tooltip title='Change color of the label'>
|
|
|
|
|
<Button type='default' className='cvat-change-task-label-color-button'>
|
|
|
|
|
<Badge
|
|
|
|
|
className='cvat-change-task-label-color-badge'
|
|
|
|
|
color={this.formRef.current?.getFieldValue('labelColor') || consts.NEW_LABEL_COLOR}
|
|
|
|
|
text={<Icon component={ColorizeIcon} />}
|
|
|
|
|
/>
|
|
|
|
|
</Button>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
</ColorPicker>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public render(): JSX.Element {
|
|
|
|
|
const { label, form } = this.props;
|
|
|
|
|
|
|
|
|
|
form.getFieldDecorator('keys', {
|
|
|
|
|
initialValue: label ? label.attributes.map((attr: Attribute): number => attr.id) : [],
|
|
|
|
|
});
|
|
|
|
|
private renderAttributes() {
|
|
|
|
|
return (fieldInstances: any[]): JSX.Element[] => fieldInstances.map(this.renderAttribute);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const keys = form.getFieldValue('keys');
|
|
|
|
|
const attributeItems = keys.map(this.renderAttribute);
|
|
|
|
|
// eslint-disable-next-line react/sort-comp
|
|
|
|
|
public componentDidMount(): void {
|
|
|
|
|
const { label } = this.props;
|
|
|
|
|
if (this.formRef.current) {
|
|
|
|
|
this.formRef.current.setFieldsValue({
|
|
|
|
|
attributes: label ? label.attributes
|
|
|
|
|
.map((attribute: Attribute): Store => ({
|
|
|
|
|
...attribute,
|
|
|
|
|
type: attribute.input_type,
|
|
|
|
|
})) : [],
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public render(): JSX.Element {
|
|
|
|
|
return (
|
|
|
|
|
<Form onSubmit={this.handleSubmit}>
|
|
|
|
|
<Row type='flex' justify='start' align='middle'>
|
|
|
|
|
{this.renderLabelNameInput()}
|
|
|
|
|
<Form onFinish={this.handleSubmit} layout='vertical' ref={this.formRef}>
|
|
|
|
|
<Row justify='start' align='middle'>
|
|
|
|
|
<Col span={10}>
|
|
|
|
|
{this.renderLabelNameInput()}
|
|
|
|
|
</Col>
|
|
|
|
|
<Col span={1} />
|
|
|
|
|
{this.renderChangeColorButton()}
|
|
|
|
|
<Col span={3}>
|
|
|
|
|
{this.renderChangeColorButton()}
|
|
|
|
|
</Col>
|
|
|
|
|
<Col span={1} />
|
|
|
|
|
{this.renderNewAttributeButton()}
|
|
|
|
|
<Col span={6}>
|
|
|
|
|
{this.renderNewAttributeButton()}
|
|
|
|
|
</Col>
|
|
|
|
|
</Row>
|
|
|
|
|
{attributeItems.length > 0 && (
|
|
|
|
|
<Row type='flex' justify='start' align='middle'>
|
|
|
|
|
<Col>
|
|
|
|
|
<Text>Attributes</Text>
|
|
|
|
|
</Col>
|
|
|
|
|
</Row>
|
|
|
|
|
)}
|
|
|
|
|
{attributeItems.reverse()}
|
|
|
|
|
<Row type='flex' justify='start' align='middle'>
|
|
|
|
|
{this.renderDoneButton()}
|
|
|
|
|
{this.renderContinueButton()}
|
|
|
|
|
{this.renderCancelButton()}
|
|
|
|
|
<Row justify='start' align='middle'>
|
|
|
|
|
<Col span={24}>
|
|
|
|
|
<Form.List name='attributes'>
|
|
|
|
|
{ this.renderAttributes() }
|
|
|
|
|
</Form.List>
|
|
|
|
|
</Col>
|
|
|
|
|
</Row>
|
|
|
|
|
<Row justify='start' align='middle'>
|
|
|
|
|
<Col>
|
|
|
|
|
{this.renderDoneButton()}
|
|
|
|
|
</Col>
|
|
|
|
|
<Col offset={1}>
|
|
|
|
|
{this.renderContinueButton()}
|
|
|
|
|
</Col>
|
|
|
|
|
<Col offset={1}>
|
|
|
|
|
{this.renderCancelButton()}
|
|
|
|
|
</Col>
|
|
|
|
|
</Row>
|
|
|
|
|
</Form>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default Form.create<Props>()(LabelForm);
|
|
|
|
|
|