You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
274 lines
8.9 KiB
TypeScript
274 lines
8.9 KiB
TypeScript
// Copyright (C) 2020-2021 Intel Corporation
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
import React from 'react';
|
|
import Text from 'antd/lib/typography/Text';
|
|
import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox';
|
|
import Select, { SelectValue } from 'antd/lib/select';
|
|
import Radio, { RadioChangeEvent } from 'antd/lib/radio';
|
|
import Input from 'antd/lib/input';
|
|
|
|
import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react';
|
|
|
|
import consts from 'consts';
|
|
|
|
interface InputElementParameters {
|
|
clientID: number;
|
|
attrID: number;
|
|
inputType: string;
|
|
values: string[];
|
|
currentValue: string;
|
|
onChange(value: string): void;
|
|
}
|
|
|
|
function renderInputElement(parameters: InputElementParameters): JSX.Element {
|
|
const {
|
|
inputType, attrID, clientID, values, currentValue, onChange,
|
|
} = parameters;
|
|
|
|
const renderCheckbox = (): JSX.Element => (
|
|
<>
|
|
<Text strong>Checkbox: </Text>
|
|
<div className='attribute-annotation-sidebar-attr-elem-wrapper'>
|
|
<Checkbox
|
|
onChange={(event: CheckboxChangeEvent): void => onChange(event.target.checked ? 'true' : 'false')}
|
|
checked={currentValue === 'true'}
|
|
/>
|
|
</div>
|
|
</>
|
|
);
|
|
|
|
const renderSelect = (): JSX.Element => (
|
|
<>
|
|
<Text strong>Values: </Text>
|
|
<div className='attribute-annotation-sidebar-attr-elem-wrapper'>
|
|
<Select
|
|
value={currentValue}
|
|
style={{ width: '80%' }}
|
|
onChange={(value: SelectValue) => onChange(value as string)}
|
|
>
|
|
{values.map(
|
|
(value: string): JSX.Element => (
|
|
<Select.Option key={value} value={value}>
|
|
{value === consts.UNDEFINED_ATTRIBUTE_VALUE ? consts.NO_BREAK_SPACE : value}
|
|
</Select.Option>
|
|
),
|
|
)}
|
|
</Select>
|
|
</div>
|
|
</>
|
|
);
|
|
|
|
const renderRadio = (): JSX.Element => (
|
|
<>
|
|
<Text strong>Values: </Text>
|
|
<div className='attribute-annotation-sidebar-attr-elem-wrapper'>
|
|
<Radio.Group value={currentValue} onChange={(event: RadioChangeEvent) => onChange(event.target.value)}>
|
|
{values.map(
|
|
(value: string): JSX.Element => (
|
|
<Radio style={{ display: 'block' }} key={value} value={value}>
|
|
{value === consts.UNDEFINED_ATTRIBUTE_VALUE ? consts.NO_BREAK_SPACE : value}
|
|
</Radio>
|
|
),
|
|
)}
|
|
</Radio.Group>
|
|
</div>
|
|
</>
|
|
);
|
|
|
|
const handleKeydown = (event: React.KeyboardEvent<HTMLInputElement>): void => {
|
|
if (['ArrowDown', 'ArrowUp', 'ArrowLeft', 'ArrowRight', 'Tab', 'Shift', 'Control'].includes(event.key)) {
|
|
event.preventDefault();
|
|
const copyEvent = new KeyboardEvent('keydown', event);
|
|
window.document.dispatchEvent(copyEvent);
|
|
}
|
|
};
|
|
|
|
const renderText = (): JSX.Element => (
|
|
<>
|
|
{inputType === 'number' ? <Text strong>Number: </Text> : <Text strong>Text: </Text>}
|
|
<div className='attribute-annotation-sidebar-attr-elem-wrapper'>
|
|
<Input
|
|
autoFocus
|
|
key={`${clientID}:${attrID}`}
|
|
defaultValue={currentValue}
|
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
const { value } = event.target;
|
|
if (inputType === 'number') {
|
|
if (value !== '') {
|
|
const numberValue = +value;
|
|
if (!Number.isNaN(numberValue)) {
|
|
onChange(`${numberValue}`);
|
|
}
|
|
}
|
|
} else {
|
|
onChange(value);
|
|
}
|
|
}}
|
|
onKeyDown={handleKeydown}
|
|
/>
|
|
</div>
|
|
</>
|
|
);
|
|
|
|
let element = null;
|
|
if (inputType === 'checkbox') {
|
|
element = renderCheckbox();
|
|
} else if (inputType === 'select') {
|
|
element = renderSelect();
|
|
} else if (inputType === 'radio') {
|
|
element = renderRadio();
|
|
} else {
|
|
element = renderText();
|
|
}
|
|
|
|
return <div className='cvat-attribute-annotation-sidebar-attr-editor'>{element}</div>;
|
|
}
|
|
|
|
interface ListParameters {
|
|
inputType: string;
|
|
values: string[];
|
|
onChange(value: string): void;
|
|
}
|
|
|
|
function renderList(parameters: ListParameters): JSX.Element | null {
|
|
const { inputType, values, onChange } = parameters;
|
|
|
|
if (inputType === 'checkbox') {
|
|
const sortedValues = ['true', 'false'];
|
|
if (values[0].toLowerCase() !== 'true') {
|
|
sortedValues.reverse();
|
|
}
|
|
|
|
const keyMap: KeyMap = {};
|
|
const handlers: {
|
|
[key: string]: (keyEvent?: KeyboardEvent) => void;
|
|
} = {};
|
|
|
|
sortedValues.forEach((value: string, index: number): void => {
|
|
const key = `SET_${index}_VALUE`;
|
|
keyMap[key] = {
|
|
name: `Set value "${value}"`,
|
|
description: `Change current value for the attribute to "${value}"`,
|
|
sequences: [`${index}`],
|
|
action: 'keydown',
|
|
};
|
|
|
|
handlers[key] = (event: KeyboardEvent | undefined) => {
|
|
if (event) {
|
|
event.preventDefault();
|
|
}
|
|
|
|
onChange(value);
|
|
};
|
|
});
|
|
|
|
return (
|
|
<div className='attribute-annotation-sidebar-attr-list-wrapper'>
|
|
<GlobalHotKeys keyMap={keyMap} handlers={handlers} />
|
|
<div>
|
|
<Text strong>0:</Text>
|
|
<Text>{` ${sortedValues[0]}`}</Text>
|
|
</div>
|
|
<div>
|
|
<Text strong>1:</Text>
|
|
<Text>{` ${sortedValues[1]}`}</Text>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (inputType === 'radio' || inputType === 'select') {
|
|
const keyMap: KeyMap = {};
|
|
const handlers: {
|
|
[key: string]: (keyEvent?: KeyboardEvent) => void;
|
|
} = {};
|
|
|
|
const filteredValues = values.filter((value: string): boolean => value !== consts.UNDEFINED_ATTRIBUTE_VALUE);
|
|
filteredValues.slice(0, 10).forEach((value: string, index: number): void => {
|
|
const key = `SET_${index}_VALUE`;
|
|
keyMap[key] = {
|
|
name: `Set value "${value}"`,
|
|
description: `Change current value for the attribute to "${value}"`,
|
|
sequences: [`${index}`],
|
|
action: 'keydown',
|
|
};
|
|
|
|
handlers[key] = (event: KeyboardEvent | undefined) => {
|
|
if (event) {
|
|
event.preventDefault();
|
|
}
|
|
|
|
onChange(value);
|
|
};
|
|
});
|
|
|
|
return (
|
|
<div className='attribute-annotation-sidebar-attr-list-wrapper'>
|
|
<GlobalHotKeys keyMap={keyMap} handlers={handlers} />
|
|
{filteredValues.map(
|
|
(value: string, index: number): JSX.Element => (
|
|
<div key={value}>
|
|
<Text strong>{`${index}:`}</Text>
|
|
<Text>{` ${value}`}</Text>
|
|
</div>
|
|
),
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (inputType === 'number') {
|
|
return (
|
|
<div className='attribute-annotation-sidebar-attr-list-wrapper'>
|
|
<div>
|
|
<Text strong>From:</Text>
|
|
<Text>{` ${values[0]}`}</Text>
|
|
</div>
|
|
<div>
|
|
<Text strong>To:</Text>
|
|
<Text>{` ${values[1]}`}</Text>
|
|
</div>
|
|
<div>
|
|
<Text strong>Step:</Text>
|
|
<Text>{` ${values[2]}`}</Text>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
interface Props {
|
|
clientID: number;
|
|
attribute: any;
|
|
currentValue: string;
|
|
onChange(value: string): void;
|
|
}
|
|
|
|
function AttributeEditor(props: Props): JSX.Element {
|
|
const {
|
|
attribute, currentValue, onChange, clientID,
|
|
} = props;
|
|
const { inputType, values, id: attrID } = attribute;
|
|
|
|
return (
|
|
<div>
|
|
{renderList({ values, inputType, onChange })}
|
|
<hr />
|
|
{renderInputElement({
|
|
clientID,
|
|
attrID,
|
|
inputType,
|
|
currentValue,
|
|
values,
|
|
onChange,
|
|
})}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default React.memo(AttributeEditor);
|