Skip to content

Commit

Permalink
Applied changes to labels constructor
Browse files Browse the repository at this point in the history
  • Loading branch information
bsekachev committed Jul 18, 2023
1 parent 56a28ec commit 5706fc8
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 68 deletions.
174 changes: 107 additions & 67 deletions cvat-ui/src/components/labels-editor/label-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import Checkbox from 'antd/lib/checkbox';
import Select from 'antd/lib/select';
import Form, { FormInstance } from 'antd/lib/form';
import Badge from 'antd/lib/badge';
import Modal from 'antd/lib/modal';
import { Store } from 'antd/lib/form/interface';

import { SerializedAttribute, LabelType } from 'cvat-core-wrapper';
Expand Down Expand Up @@ -94,6 +95,7 @@ export default class LabelForm extends React.Component<Props> {
return {
...attribute,
values: attrValues,
default_value: attrValues[0],
input_type: attribute.type.toLowerCase(),
};
}),
Expand All @@ -117,7 +119,18 @@ export default class LabelForm extends React.Component<Props> {
private addAttribute = (): void => {
if (this.formRef.current) {
const attributes = this.formRef.current.getFieldValue('attributes');
this.formRef.current.setFieldsValue({ attributes: [...(attributes || []), { id: idGenerator() }] });
this.formRef.current.setFieldsValue({
attributes: [
...(attributes || []),
{
id: idGenerator(),
type: AttributeType.SELECT,
name: '',
values: [],
mutable: false,
},
],
});
}
};

Expand All @@ -131,17 +144,15 @@ export default class LabelForm extends React.Component<Props> {
};

/* eslint-disable class-methods-use-this */
private renderAttributeNameInput(fieldInstance: any, attr: SerializedAttribute | null): JSX.Element {
private renderAttributeNameInput(fieldInstance: any, attr: any): JSX.Element {
const { key } = fieldInstance;
const locked = attr ? attr.id as number >= 0 : false;
const value = attr ? attr.name : '';
const attrNames = this.formRef.current?.getFieldValue('attributes')
.filter((_attr: any) => _attr.id !== attr.id).map((_attr: any) => _attr.name);

return (
<Form.Item
hasFeedback
name={[key, 'name']}
fieldKey={[fieldInstance.fieldKey, 'name']}
initialValue={value}
rules={[
{
required: true,
Expand All @@ -151,22 +162,45 @@ export default class LabelForm extends React.Component<Props> {
pattern: patterns.validateAttributeName.pattern,
message: patterns.validateAttributeName.message,
},
{
validator: (_rule: any, attrName: string) => {
if (attrNames.includes(attrName) && attr.name !== attrName) {
return Promise.reject(new Error('Attribute name must be unique for the label'));
}
return Promise.resolve();
},
},
]}
>
<Input className='cvat-attribute-name-input' disabled={locked} placeholder='Name' />
<Input className='cvat-attribute-name-input' placeholder='Name' />
</Form.Item>
);
}

private renderAttributeTypeInput(fieldInstance: any, attr: SerializedAttribute | null): JSX.Element {
private renderAttributeTypeInput(fieldInstance: any, attr: any): JSX.Element {
const { key } = fieldInstance;
const locked = attr ? attr.id as number >= 0 : false;
const type = attr ? attr.input_type.toUpperCase() : AttributeType.SELECT;
const locked = attr.id as number >= 0;

return (
<CVATTooltip title='An HTML element representing the attribute'>
<Form.Item name={[key, 'type']} fieldKey={[fieldInstance.fieldKey, 'type']} initialValue={type}>
<Select className='cvat-attribute-type-input' disabled={locked}>
<Form.Item name={[key, 'type']}>
<Select
className='cvat-attribute-type-input'
disabled={locked}
onChange={(value: AttributeType) => {
const attrs = this.formRef.current?.getFieldValue('attributes');
if (value === AttributeType.CHECKBOX) {
attrs[key].values = ['false'];
} else if (value === AttributeType.TEXT && !attrs[key].values.length) {
attrs[key].values = '';
} else if (value === AttributeType.NUMBER || attr.type === AttributeType.CHECKBOX) {
attrs[key].values = [];
}
this.formRef.current?.setFieldsValue({
attributes: attrs,
});
}}
>
<Select.Option value={AttributeType.SELECT} className='cvat-attribute-type-input-select'>
Select
</Select.Option>
Expand All @@ -188,10 +222,10 @@ export default class LabelForm extends React.Component<Props> {
);
}

private renderAttributeValuesInput(fieldInstance: any, attr: SerializedAttribute | null): JSX.Element {
private renderAttributeValuesInput(fieldInstance: any, attr: any): JSX.Element {
const { key } = fieldInstance;
const locked = attr ? attr.id as number >= 0 : false;
const existingValues = attr ? attr.values : [];
const locked = attr.id as number >= 0;
const existingValues = attr.values;

const validator = (_: any, values: string[]): Promise<void> => {
if (locked && existingValues) {
Expand All @@ -213,8 +247,6 @@ export default class LabelForm extends React.Component<Props> {
<CVATTooltip title='Press enter to add a new value'>
<Form.Item
name={[key, 'values']}
fieldKey={[fieldInstance.fieldKey, 'values']}
initialValue={existingValues}
rules={[
{
required: true,
Expand Down Expand Up @@ -248,7 +280,6 @@ export default class LabelForm extends React.Component<Props> {
message: 'Please, specify a default value',
}]}
name={[key, 'values']}
fieldKey={[fieldInstance.fieldKey, 'values']}
>
<Select className='cvat-attribute-values-input'>
<Select.Option value='false'>False</Select.Option>
Expand All @@ -259,12 +290,13 @@ export default class LabelForm extends React.Component<Props> {
);
}

private renderNumberRangeInput(fieldInstance: any, attr: SerializedAttribute | null): JSX.Element {
private renderNumberRangeInput(fieldInstance: any, attr: any): JSX.Element {
const { key } = fieldInstance;
const locked = attr ? attr.id as number >= 0 : false;
const value = attr ? attr.values : '';
const locked = attr.id as number >= 0;

const validator = (_: any, strNumbers: string): Promise<void> => {
if (typeof strNumbers !== 'string') return Promise.resolve();

const numbers = strNumbers.split(';').map((number): number => Number.parseFloat(number));
if (numbers.length !== 3) {
return Promise.reject(new Error('Three numbers are expected'));
Expand Down Expand Up @@ -296,8 +328,6 @@ export default class LabelForm extends React.Component<Props> {
return (
<Form.Item
name={[key, 'values']}
fieldKey={[fieldInstance.fieldKey, 'values']}
initialValue={value}
rules={[
{
required: true,
Expand All @@ -313,36 +343,24 @@ export default class LabelForm extends React.Component<Props> {
);
}

private renderDefaultValueInput(fieldInstance: any, attr: SerializedAttribute | null): JSX.Element {
private renderDefaultValueInput(fieldInstance: any): JSX.Element {
const { key } = fieldInstance;
const value = attr ? attr.values[0] : '';

if (attr?.input_type.toUpperCase() === 'TEXT') {
return (
<Form.Item name={[key, 'values']} fieldKey={[fieldInstance.fieldKey, 'values']} initialValue={value}>
<Input.TextArea className='cvat-attribute-values-input' placeholder='Default value' />
</Form.Item>
);
}

return (
<Form.Item name={[key, 'values']} fieldKey={[fieldInstance.fieldKey, 'values']} initialValue={value}>
<Input className='cvat-attribute-values-input' placeholder='Default value' />
<Form.Item name={[key, 'values']}>
<Input.TextArea className='cvat-attribute-values-input' placeholder='Default value' />
</Form.Item>
);
}

private renderMutableAttributeInput(fieldInstance: any, attr: SerializedAttribute | null): JSX.Element {
private renderMutableAttributeInput(fieldInstance: any, attr: any): JSX.Element {
const { key } = fieldInstance;
const locked = attr ? attr.id as number >= 0 : false;
const value = attr ? attr.mutable : false;
const locked = attr.id as number >= 0;

return (
<CVATTooltip title='Can this attribute be changed frame to frame?'>
<Form.Item
name={[key, 'mutable']}
fieldKey={[fieldInstance.fieldKey, 'mutable']}
initialValue={value}
valuePropName='checked'
>
<Checkbox className='cvat-attribute-mutable-checkbox' disabled={locked}>
Expand All @@ -353,19 +371,31 @@ export default class LabelForm extends React.Component<Props> {
);
}

private renderDeleteAttributeButton(fieldInstance: any, attr: SerializedAttribute | null): JSX.Element {
private renderDeleteAttributeButton(fieldInstance: any, attr: any): JSX.Element {
const { key } = fieldInstance;
const locked = attr ? attr.id as number >= 0 : false;

return (
<CVATTooltip title='Delete the attribute'>
<Form.Item>
<Button
disabled={attr.id >= 0} // temporary disabled, does not work on the server
type='link'
className='cvat-delete-attribute-button'
disabled={locked}
onClick={(): void => {
this.removeAttribute(key);
if (attr.id >= 0) {
Modal.confirm({
title: `Do you want to remove the "${attr.name}" attribute?`,
content: 'This action is irreversible. It will remove corresponding annotations.',
onOk: () => {
this.removeAttribute(key);
setTimeout(() => {
this.formRef.current?.submit();
});
},
});
} else {
this.removeAttribute(key);
}
}}
>
<DeleteOutlined />
Expand All @@ -375,19 +405,17 @@ export default class LabelForm extends React.Component<Props> {
);
}

private renderAttribute = (fieldInstance: any): JSX.Element => {
const { label } = this.props;
private renderAttribute = (fieldInstance: any): JSX.Element | null => {
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;
const attr = this.formRef.current?.getFieldValue('attributes')[key];

return (
return attr ? (
<Form.Item noStyle key={key} shouldUpdate>
{() => (
<Row
justify='space-between'
align='top'
cvat-attribute-id={fieldValue.id}
cvat-attribute-id={attr.id}
className='cvat-attribute-inputs-wrapper'
>
<Col span={5}>{this.renderAttributeNameInput(fieldInstance, attr)}</Col>
Expand All @@ -404,7 +432,7 @@ export default class LabelForm extends React.Component<Props> {
} else if (type === AttributeType.NUMBER) {
element = this.renderNumberRangeInput(fieldInstance, attr);
} else {
element = this.renderDefaultValueInput(fieldInstance, attr);
element = this.renderDefaultValueInput(fieldInstance);
}

return element;
Expand All @@ -415,18 +443,16 @@ export default class LabelForm extends React.Component<Props> {
</Row>
)}
</Form.Item>
);
) : null;
};

private renderLabelNameInput(): JSX.Element {
const { label, labelNames, onCancel } = this.props;
const value = label ? label.name : '';

return (
<Form.Item
hasFeedback
name='name'
initialValue={value}
rules={[
{
required: !!label,
Expand Down Expand Up @@ -464,30 +490,26 @@ export default class LabelForm extends React.Component<Props> {
private renderLabelTypeInput(): JSX.Element {
const { onSkeletonSubmit } = this.props;
const isSkeleton = !!onSkeletonSubmit;

const types = Object.values(LabelType)
.filter((type: string) => type !== LabelType.SKELETON);
const { label } = this.props;
const locked = !!label?.has_parent;
const defaultType = isSkeleton ? LabelType.SKELETON : LabelType.ANY;
const value = label ? label.type : defaultType;

return (
<Form.Item name='type' initialValue={value}>
<Form.Item name='type'>
<Select className='cvat-label-type-input' disabled={isSkeleton || locked} showSearch={false}>
{isSkeleton && (
{isSkeleton ? (
<Select.Option
className='cvat-label-type-option-skeleton'
value='skeleton'
>
Skeleton
</Select.Option>
)}
{ types.map((type: string): JSX.Element => (
) : types.map((type: string): JSX.Element => (
<Select.Option className={`cvat-label-type-option-${type}`} key={type} value={type}>
{`${type[0].toUpperCase()}${type.slice(1)}`}
</Select.Option>
)) }
))}
</Select>
</Form.Item>
);
Expand Down Expand Up @@ -544,12 +566,10 @@ export default class LabelForm extends React.Component<Props> {
}

private renderChangeColorButton(): JSX.Element {
const { label } = this.props;

return (
<Form.Item noStyle shouldUpdate>
{() => (
<Form.Item name='color' initialValue={label ? label?.color : undefined}>
<Form.Item name='color'>
<ColorPicker placement='bottom'>
<CVATTooltip title='Change color of the label'>
<Button type='default' className='cvat-change-task-label-color-button'>
Expand All @@ -568,7 +588,7 @@ export default class LabelForm extends React.Component<Props> {
}

private renderAttributes() {
return (fieldInstances: any[]): JSX.Element[] => fieldInstances.map(this.renderAttribute);
return (fieldInstances: any[]): (JSX.Element | null)[] => fieldInstances.map(this.renderAttribute);
}

// eslint-disable-next-line react/sort-comp
Expand All @@ -595,8 +615,28 @@ export default class LabelForm extends React.Component<Props> {
}

public render(): JSX.Element {
const { label, onSkeletonSubmit } = this.props;
const isSkeleton = !!onSkeletonSubmit;

return (
<Form onFinish={this.handleSubmit} layout='vertical' ref={this.formRef}>
<Form
initialValues={{
name: label?.name || '',
type: label?.type || (isSkeleton ? LabelType.SKELETON : LabelType.ANY),
color: label?.color || undefined,
attributes: (label?.attributes || []).map((attr) => ({
id: attr.id,
name: attr.name,
type: attr.input_type,
values: attr.values,
mutable: attr.mutable,
default_value: attr.default_value,
})),
}}
onFinish={this.handleSubmit}
layout='vertical'
ref={this.formRef}
>
<Row justify='start' align='top'>
<Col span={8}>{this.renderLabelNameInput()}</Col>
<Col span={3} offset={1}>{this.renderLabelTypeInput()}</Col>
Expand Down
2 changes: 1 addition & 1 deletion cvat-ui/src/components/labels-editor/labels-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ export default class LabelsEditor extends React.PureComponent<LabelsEditorProps,
name: attr.name,
id: attr.id as number < 0 ? undefined : attr.id,
input_type: attr.input_type.toLowerCase() as SerializedAttribute['input_type'],
default_value: attr.default_value || attr.values[0],
default_value: attr.default_value,
mutable: attr.mutable,
values: [...attr.values],
})),
Expand Down

0 comments on commit 5706fc8

Please sign in to comment.