diff --git a/CHANGELOG.md b/CHANGELOG.md index 57dd773edb26..64e8240b5646 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## \[Unreleased] ### Added - Multi-line text attributes supported () +- Now you can configure default attribute value for SELECT, RADIO types on UI + () - \{SDK\] `cvat_sdk.datasets`, a framework-agnostic equivalent of `cvat_sdk.pytorch` () diff --git a/cvat-ui/package.json b/cvat-ui/package.json index ef964a392d1c..43dc9dfc4320 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.53.3", + "version": "1.53.4", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { diff --git a/cvat-ui/src/components/labels-editor/label-form.tsx b/cvat-ui/src/components/labels-editor/label-form.tsx index 72fdbc90b0a5..600dc11af72b 100644 --- a/cvat-ui/src/components/labels-editor/label-form.tsx +++ b/cvat-ui/src/components/labels-editor/label-form.tsx @@ -10,8 +10,10 @@ import Input from 'antd/lib/input'; import Button from 'antd/lib/button'; import Checkbox from 'antd/lib/checkbox'; import Select from 'antd/lib/select'; +import Tag from 'antd/lib/tag'; 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'; @@ -94,6 +96,8 @@ export default class LabelForm extends React.Component { return { ...attribute, values: attrValues, + default_value: attribute.default_value && attrValues.includes(attribute.default_value) ? + attribute.default_value : attrValues[0], input_type: attribute.type.toLowerCase(), }; }), @@ -117,7 +121,18 @@ export default class LabelForm extends React.Component { 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, + }, + ], + }); } }; @@ -131,17 +146,15 @@ export default class LabelForm extends React.Component { }; /* 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 ( { 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(); + }, + }, ]} > - + = 0} className='cvat-attribute-name-input' placeholder='Name' /> ); } - 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 ( - - { + 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 @@ -188,10 +224,10 @@ export default class LabelForm extends React.Component { ); } - 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 => { if (locked && existingValues) { @@ -213,8 +249,6 @@ export default class LabelForm extends React.Component { { mode='tags' placeholder='Attribute values' dropdownStyle={{ display: 'none' }} + tagRender={(props) => { + const attrs = this.formRef.current?.getFieldValue('attributes'); + const isDefault = props.value === attrs[key].default_value; + return ( + + { + const parent = window.document.getElementsByClassName('cvat-attribute-values-input')[0]; + if (parent) { + parent.dispatchEvent(new MouseEvent('mouseout', { bubbles: true })); + } + }} + color={isDefault ? 'blue' : undefined} + onClose={() => { + if (isDefault) { + attrs[key].default_value = undefined; + } + props.onClose(); + }} + onClick={() => { + attrs[key].default_value = props.value; + this.formRef.current?.setFieldsValue({ + attributes: attrs, + }); + }} + closable={props.closable} + > + {props.label} + + + ); + }} /> @@ -248,7 +318,6 @@ export default class LabelForm extends React.Component { message: 'Please, specify a default value', }]} name={[key, 'values']} - fieldKey={[fieldInstance.fieldKey, 'values']} > + + ); } - 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 ( @@ -353,19 +409,31 @@ export default class LabelForm extends React.Component { ); } - 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 (