Merge branch 'develop' into dk/point-deletion
commit
b0dbbb3faa
@ -0,0 +1,92 @@
|
|||||||
|
// Copyright (C) 2020 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import Select, { SelectValue, LabeledValue } from 'antd/lib/select';
|
||||||
|
import Icon from 'antd/lib/icon';
|
||||||
|
|
||||||
|
import {
|
||||||
|
changeAnnotationsFilters as changeAnnotationsFiltersAction,
|
||||||
|
fetchAnnotationsAsync,
|
||||||
|
} from 'actions/annotation-actions';
|
||||||
|
import { CombinedState } from 'reducers/interfaces';
|
||||||
|
|
||||||
|
interface StateToProps {
|
||||||
|
annotationsFilters: string[];
|
||||||
|
annotationsFiltersHistory: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DispatchToProps {
|
||||||
|
changeAnnotationsFilters(value: SelectValue): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state: CombinedState): StateToProps {
|
||||||
|
const {
|
||||||
|
annotation: {
|
||||||
|
annotations: {
|
||||||
|
filters: annotationsFilters,
|
||||||
|
filtersHistory: annotationsFiltersHistory,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} = state;
|
||||||
|
|
||||||
|
return {
|
||||||
|
annotationsFilters,
|
||||||
|
annotationsFiltersHistory,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch: any): DispatchToProps {
|
||||||
|
return {
|
||||||
|
changeAnnotationsFilters(value: SelectValue) {
|
||||||
|
if (typeof (value) === 'string') {
|
||||||
|
dispatch(changeAnnotationsFiltersAction([value]));
|
||||||
|
dispatch(fetchAnnotationsAsync());
|
||||||
|
} else if (Array.isArray(value)
|
||||||
|
&& value.every((element: string | number | LabeledValue): boolean => (
|
||||||
|
typeof (element) === 'string'
|
||||||
|
))
|
||||||
|
) {
|
||||||
|
dispatch(changeAnnotationsFiltersAction(value as string[]));
|
||||||
|
dispatch(fetchAnnotationsAsync());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function AnnotationsFiltersInput(props: StateToProps & DispatchToProps): JSX.Element {
|
||||||
|
const {
|
||||||
|
annotationsFilters,
|
||||||
|
annotationsFiltersHistory,
|
||||||
|
changeAnnotationsFilters,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
className='cvat-annotations-filters-input'
|
||||||
|
allowClear
|
||||||
|
value={annotationsFilters}
|
||||||
|
mode='tags'
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
placeholder={(
|
||||||
|
<>
|
||||||
|
<Icon type='filter' />
|
||||||
|
<span style={{ marginLeft: 5 }}>Annotations filter</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
onChange={changeAnnotationsFilters}
|
||||||
|
>
|
||||||
|
{annotationsFiltersHistory.map((element: string): JSX.Element => (
|
||||||
|
<Select.Option key={element} value={element}>{element}</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps,
|
||||||
|
)(AnnotationsFiltersInput);
|
||||||
@ -0,0 +1,300 @@
|
|||||||
|
// Copyright (C) 2020 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { GlobalHotKeys, KeyMap } from 'react-hotkeys';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import Layout, { SiderProps } from 'antd/lib/layout';
|
||||||
|
import { SelectValue } from 'antd/lib/select';
|
||||||
|
import { CheckboxChangeEvent } from 'antd/lib/checkbox';
|
||||||
|
import { Row, Col } from 'antd/lib/grid';
|
||||||
|
import Text from 'antd/lib/typography/Text';
|
||||||
|
|
||||||
|
import {
|
||||||
|
activateObject as activateObjectAction,
|
||||||
|
updateAnnotationsAsync,
|
||||||
|
} from 'actions/annotation-actions';
|
||||||
|
import { CombinedState } from 'reducers/interfaces';
|
||||||
|
import AnnotationsFiltersInput from 'components/annotation-page/annotations-filters-input';
|
||||||
|
import ObjectSwitcher from './object-switcher';
|
||||||
|
import AttributeSwitcher from './attribute-switcher';
|
||||||
|
import ObjectBasicsEditor from './object-basics-edtior';
|
||||||
|
import AttributeEditor from './attribute-editor';
|
||||||
|
|
||||||
|
|
||||||
|
interface StateToProps {
|
||||||
|
activatedStateID: number | null;
|
||||||
|
activatedAttributeID: number | null;
|
||||||
|
states: any[];
|
||||||
|
labels: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DispatchToProps {
|
||||||
|
activateObject(clientID: number | null, attrID: number | null): void;
|
||||||
|
updateAnnotations(statesToUpdate: any[]): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LabelAttrMap {
|
||||||
|
[index: number]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state: CombinedState): StateToProps {
|
||||||
|
const {
|
||||||
|
annotation: {
|
||||||
|
annotations: {
|
||||||
|
activatedStateID,
|
||||||
|
activatedAttributeID,
|
||||||
|
states,
|
||||||
|
},
|
||||||
|
job: {
|
||||||
|
labels,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} = state;
|
||||||
|
|
||||||
|
return {
|
||||||
|
labels,
|
||||||
|
activatedStateID,
|
||||||
|
activatedAttributeID,
|
||||||
|
states,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch: any): DispatchToProps {
|
||||||
|
return {
|
||||||
|
activateObject(clientID: number, attrID: number): void {
|
||||||
|
dispatch(activateObjectAction(clientID, attrID));
|
||||||
|
},
|
||||||
|
updateAnnotations(states): void {
|
||||||
|
dispatch(updateAnnotationsAsync(states));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Element {
|
||||||
|
const {
|
||||||
|
labels,
|
||||||
|
states,
|
||||||
|
activatedStateID,
|
||||||
|
activatedAttributeID,
|
||||||
|
updateAnnotations,
|
||||||
|
activateObject,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const [labelAttrMap, setLabelAttrMap] = useState(
|
||||||
|
labels.reduce((acc, label): LabelAttrMap => {
|
||||||
|
acc[label.id] = label.attributes.length ? label.attributes[0] : null;
|
||||||
|
return acc;
|
||||||
|
}, {}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const [activeObjectState] = activatedStateID === null
|
||||||
|
? [null] : states.filter((objectState: any): boolean => (
|
||||||
|
objectState.clientID === activatedStateID
|
||||||
|
));
|
||||||
|
const activeAttribute = activeObjectState
|
||||||
|
? labelAttrMap[activeObjectState.label.id]
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (activeObjectState) {
|
||||||
|
const attribute = labelAttrMap[activeObjectState.label.id];
|
||||||
|
if (attribute && attribute.id !== activatedAttributeID) {
|
||||||
|
activateObject(activatedStateID, attribute ? attribute.id : null);
|
||||||
|
}
|
||||||
|
} else if (states.length) {
|
||||||
|
const attribute = labelAttrMap[states[0].label.id];
|
||||||
|
activateObject(states[0].clientID, attribute ? attribute.id : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextObject = (step: number): void => {
|
||||||
|
if (states.length) {
|
||||||
|
const index = states.indexOf(activeObjectState);
|
||||||
|
let nextIndex = index + step;
|
||||||
|
if (nextIndex > states.length - 1) {
|
||||||
|
nextIndex = 0;
|
||||||
|
} else if (nextIndex < 0) {
|
||||||
|
nextIndex = states.length - 1;
|
||||||
|
}
|
||||||
|
if (nextIndex !== index) {
|
||||||
|
const attribute = labelAttrMap[states[nextIndex].label.id];
|
||||||
|
activateObject(states[nextIndex].clientID, attribute ? attribute.id : null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const nextAttribute = (step: number): void => {
|
||||||
|
if (activeObjectState) {
|
||||||
|
const { label } = activeObjectState;
|
||||||
|
const { attributes } = label;
|
||||||
|
if (attributes.length) {
|
||||||
|
const index = attributes.indexOf(activeAttribute);
|
||||||
|
let nextIndex = index + step;
|
||||||
|
if (nextIndex > attributes.length - 1) {
|
||||||
|
nextIndex = 0;
|
||||||
|
} else if (nextIndex < 0) {
|
||||||
|
nextIndex = attributes.length - 1;
|
||||||
|
}
|
||||||
|
if (index !== nextIndex) {
|
||||||
|
const updatedLabelAttrMap = { ...labelAttrMap };
|
||||||
|
updatedLabelAttrMap[label.id] = attributes[nextIndex];
|
||||||
|
setLabelAttrMap(updatedLabelAttrMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (document.activeElement instanceof HTMLElement) {
|
||||||
|
document.activeElement.blur();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const siderProps: SiderProps = {
|
||||||
|
className: 'attribute-annotation-sidebar',
|
||||||
|
theme: 'light',
|
||||||
|
width: 300,
|
||||||
|
collapsedWidth: 0,
|
||||||
|
reverseArrow: true,
|
||||||
|
collapsible: true,
|
||||||
|
trigger: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const keyMap = {
|
||||||
|
NEXT_ATTRIBUTE: {
|
||||||
|
name: 'Next attribute',
|
||||||
|
description: 'Go to the next attribute',
|
||||||
|
sequence: 'ArrowDown',
|
||||||
|
action: 'keydown',
|
||||||
|
},
|
||||||
|
PREVIOUS_ATTRIBUTE: {
|
||||||
|
name: 'Previous attribute',
|
||||||
|
description: 'Go to the previous attribute',
|
||||||
|
sequence: 'ArrowUp',
|
||||||
|
action: 'keydown',
|
||||||
|
},
|
||||||
|
NEXT_OBJECT: {
|
||||||
|
name: 'Next object',
|
||||||
|
description: 'Go to the next object',
|
||||||
|
sequence: 'Tab',
|
||||||
|
action: 'keydown',
|
||||||
|
},
|
||||||
|
PREVIOUS_OBJECT: {
|
||||||
|
name: 'Previous object',
|
||||||
|
description: 'Go to the previous object',
|
||||||
|
sequence: 'Shift+Tab',
|
||||||
|
action: 'keydown',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlers = {
|
||||||
|
NEXT_ATTRIBUTE: (event: KeyboardEvent | undefined) => {
|
||||||
|
if (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
nextAttribute(1);
|
||||||
|
},
|
||||||
|
PREVIOUS_ATTRIBUTE: (event: KeyboardEvent | undefined) => {
|
||||||
|
if (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
nextAttribute(-1);
|
||||||
|
},
|
||||||
|
NEXT_OBJECT: (event: KeyboardEvent | undefined) => {
|
||||||
|
if (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
nextObject(1);
|
||||||
|
},
|
||||||
|
PREVIOUS_OBJECT: (event: KeyboardEvent | undefined) => {
|
||||||
|
if (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
nextObject(-1);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (activeObjectState) {
|
||||||
|
return (
|
||||||
|
<Layout.Sider {...siderProps}>
|
||||||
|
<GlobalHotKeys keyMap={keyMap as any as KeyMap} handlers={handlers} allowChanges />
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
<AnnotationsFiltersInput />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<ObjectSwitcher
|
||||||
|
currentLabel={activeObjectState.label.name}
|
||||||
|
clientID={activeObjectState.clientID}
|
||||||
|
occluded={activeObjectState.occluded}
|
||||||
|
objectsCount={states.length}
|
||||||
|
currentIndex={states.indexOf(activeObjectState)}
|
||||||
|
nextObject={nextObject}
|
||||||
|
/>
|
||||||
|
<ObjectBasicsEditor
|
||||||
|
currentLabel={activeObjectState.label.name}
|
||||||
|
labels={labels}
|
||||||
|
occluded={activeObjectState.occluded}
|
||||||
|
changeLabel={(value: SelectValue): void => {
|
||||||
|
const labelName = value as string;
|
||||||
|
const [newLabel] = labels
|
||||||
|
.filter((_label): boolean => _label.name === labelName);
|
||||||
|
activeObjectState.label = newLabel;
|
||||||
|
updateAnnotations([activeObjectState]);
|
||||||
|
}}
|
||||||
|
setOccluded={(event: CheckboxChangeEvent): void => {
|
||||||
|
activeObjectState.occluded = event.target.checked;
|
||||||
|
updateAnnotations([activeObjectState]);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{
|
||||||
|
activeAttribute
|
||||||
|
? (
|
||||||
|
<>
|
||||||
|
<AttributeSwitcher
|
||||||
|
currentAttribute={activeAttribute.name}
|
||||||
|
currentIndex={activeObjectState.label.attributes
|
||||||
|
.indexOf(activeAttribute)}
|
||||||
|
attributesCount={activeObjectState.label.attributes.length}
|
||||||
|
nextAttribute={nextAttribute}
|
||||||
|
/>
|
||||||
|
<AttributeEditor
|
||||||
|
attribute={activeAttribute}
|
||||||
|
currentValue={activeObjectState.attributes[activeAttribute.id]}
|
||||||
|
onChange={(value: string) => {
|
||||||
|
const { attributes } = activeObjectState;
|
||||||
|
attributes[activeAttribute.id] = value;
|
||||||
|
activeObjectState.attributes = attributes;
|
||||||
|
updateAnnotations([activeObjectState]);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className='attribute-annotations-sidebar-not-found-wrapper'>
|
||||||
|
<Text strong>No attributes found</Text>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</Layout.Sider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout.Sider {...siderProps}>
|
||||||
|
<div className='attribute-annotations-sidebar-not-found-wrapper'>
|
||||||
|
<Text strong>No objects found</Text>
|
||||||
|
</div>
|
||||||
|
</Layout.Sider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps,
|
||||||
|
)(AttributeAnnotationSidebar);
|
||||||
@ -0,0 +1,281 @@
|
|||||||
|
// Copyright (C) 2020 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { GlobalHotKeys, KeyMap } from 'react-hotkeys';
|
||||||
|
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 InputNumber from 'antd/lib/input-number';
|
||||||
|
|
||||||
|
interface InputElementParameters {
|
||||||
|
attrID: number;
|
||||||
|
inputType: string;
|
||||||
|
values: string[];
|
||||||
|
currentValue: string;
|
||||||
|
onChange(value: string): void;
|
||||||
|
ref: React.RefObject<Input | InputNumber>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderInputElement(parameters: InputElementParameters): JSX.Element {
|
||||||
|
const {
|
||||||
|
inputType,
|
||||||
|
attrID,
|
||||||
|
values,
|
||||||
|
currentValue,
|
||||||
|
onChange,
|
||||||
|
ref,
|
||||||
|
} = 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}</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}</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={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}
|
||||||
|
ref={ref as React.RefObject<Input>}
|
||||||
|
/>
|
||||||
|
</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='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}"`,
|
||||||
|
sequence: `${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 as KeyMap} handlers={handlers} allowChanges />
|
||||||
|
<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;
|
||||||
|
} = {};
|
||||||
|
|
||||||
|
values.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}"`,
|
||||||
|
sequence: `${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 as KeyMap} handlers={handlers} allowChanges />
|
||||||
|
{values.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 {
|
||||||
|
attribute: any;
|
||||||
|
currentValue: string;
|
||||||
|
onChange(value: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function AttributeEditor(props: Props): JSX.Element {
|
||||||
|
const { attribute, currentValue, onChange } = props;
|
||||||
|
const { inputType, values, id: attrID } = attribute;
|
||||||
|
const ref = inputType === 'number' ? React.createRef<InputNumber>()
|
||||||
|
: React.createRef<Input>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{renderList({ values, inputType, onChange })}
|
||||||
|
<hr />
|
||||||
|
{renderInputElement({
|
||||||
|
attrID,
|
||||||
|
ref,
|
||||||
|
inputType,
|
||||||
|
currentValue,
|
||||||
|
values,
|
||||||
|
onChange,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(AttributeEditor);
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
// Copyright (C) 2020 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import Icon from 'antd/lib/icon';
|
||||||
|
import Text from 'antd/lib/typography/Text';
|
||||||
|
import Tooltip from 'antd/lib/tooltip';
|
||||||
|
import Button from 'antd/lib/button';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
currentAttribute: string;
|
||||||
|
currentIndex: number;
|
||||||
|
attributesCount: number;
|
||||||
|
nextAttribute(step: number): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function AttributeSwitcher(props: Props): JSX.Element {
|
||||||
|
const {
|
||||||
|
currentAttribute,
|
||||||
|
currentIndex,
|
||||||
|
attributesCount,
|
||||||
|
nextAttribute,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const title = `${currentAttribute} [${currentIndex + 1}/${attributesCount}]`;
|
||||||
|
return (
|
||||||
|
<div className='attribute-annotation-sidebar-switcher'>
|
||||||
|
<Button disabled={attributesCount <= 1} onClick={() => nextAttribute(-1)}>
|
||||||
|
<Icon type='left' />
|
||||||
|
</Button>
|
||||||
|
<Tooltip title={title}>
|
||||||
|
<Text className='cvat-text'>{currentAttribute}</Text>
|
||||||
|
<Text strong>{` [${currentIndex + 1}/${attributesCount}]`}</Text>
|
||||||
|
</Tooltip>
|
||||||
|
<Button disabled={attributesCount <= 1} onClick={() => nextAttribute(1)}>
|
||||||
|
<Icon type='right' />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(AttributeSwitcher);
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
// Copyright (C) 2020 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import Select, { SelectValue } from 'antd/lib/select';
|
||||||
|
import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
currentLabel: string;
|
||||||
|
labels: any[];
|
||||||
|
occluded: boolean;
|
||||||
|
setOccluded(event: CheckboxChangeEvent): void;
|
||||||
|
changeLabel(value: SelectValue): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ObjectBasicsEditor(props: Props): JSX.Element {
|
||||||
|
const {
|
||||||
|
currentLabel,
|
||||||
|
occluded,
|
||||||
|
labels,
|
||||||
|
setOccluded,
|
||||||
|
changeLabel,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='attribute-annotation-sidebar-basics-editor'>
|
||||||
|
<Select value={currentLabel} onChange={changeLabel} style={{ width: '50%' }}>
|
||||||
|
{labels.map((label: any): JSX.Element => (
|
||||||
|
<Select.Option
|
||||||
|
value={label.name}
|
||||||
|
key={label.name}
|
||||||
|
>
|
||||||
|
{label.name}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
<Checkbox checked={occluded} onChange={setOccluded}>Occluded</Checkbox>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(ObjectBasicsEditor);
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright (C) 2020 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import Icon from 'antd/lib/icon';
|
||||||
|
import Text from 'antd/lib/typography/Text';
|
||||||
|
import Tooltip from 'antd/lib/tooltip';
|
||||||
|
import Button from 'antd/lib/button';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
currentLabel: string;
|
||||||
|
clientID: number;
|
||||||
|
occluded: boolean;
|
||||||
|
objectsCount: number;
|
||||||
|
currentIndex: number;
|
||||||
|
nextObject(step: number): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ObjectSwitcher(props: Props): JSX.Element {
|
||||||
|
const {
|
||||||
|
currentLabel,
|
||||||
|
clientID,
|
||||||
|
objectsCount,
|
||||||
|
currentIndex,
|
||||||
|
nextObject,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
|
||||||
|
const title = `${currentLabel} ${clientID} [${currentIndex + 1}/${objectsCount}]`;
|
||||||
|
return (
|
||||||
|
<div className='attribute-annotation-sidebar-switcher'>
|
||||||
|
<Button disabled={objectsCount <= 1} onClick={() => nextObject(-1)}>
|
||||||
|
<Icon type='left' />
|
||||||
|
</Button>
|
||||||
|
<Tooltip title={title}>
|
||||||
|
<Text className='cvat-text'>{currentLabel}</Text>
|
||||||
|
<Text className='cvat-text'>{` ${clientID} `}</Text>
|
||||||
|
<Text strong>{`[${currentIndex + 1}/${objectsCount}]`}</Text>
|
||||||
|
</Tooltip>
|
||||||
|
<Button disabled={objectsCount <= 1} onClick={() => nextObject(1)}>
|
||||||
|
<Icon type='right' />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(ObjectSwitcher);
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
// Copyright (C) 2020 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import './styles.scss';
|
||||||
|
import React from 'react';
|
||||||
|
import Layout from 'antd/lib/layout';
|
||||||
|
|
||||||
|
import CanvasWrapperContainer from 'containers/annotation-page/standard-workspace/canvas-wrapper';
|
||||||
|
import AttributeAnnotationSidebar from './attribute-annotation-sidebar/attribute-annotation-sidebar';
|
||||||
|
|
||||||
|
export default function AttributeAnnotationWorkspace(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<Layout hasSider className='attribute-annotation-workspace'>
|
||||||
|
<CanvasWrapperContainer />
|
||||||
|
<AttributeAnnotationSidebar />
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
// Copyright (C) 2020 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
@import 'base.scss';
|
||||||
|
|
||||||
|
.attribute-annotation-workspace.ant-layout {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attribute-annotation-sidebar {
|
||||||
|
background: $background-color-2;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attribute-annotation-sidebar-switcher {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 18px;
|
||||||
|
margin-top: 10px;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
max-width: 60%;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
> button > i {
|
||||||
|
color: $objects-bar-icons-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.attribute-annotation-sidebar-basics-editor {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 18px;
|
||||||
|
margin: 10px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attribute-annotations-sidebar-not-found-wrapper {
|
||||||
|
margin-top: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attribute-annotation-sidebar-attr-list-wrapper {
|
||||||
|
margin: 10px 0px 10px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.attribute-annotation-sidebar-attr-elem-wrapper {
|
||||||
|
display: inline-block;
|
||||||
|
width: 60%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attribute-annotation-sidebar-number-list {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attribute-annotation-sidebar-attr-editor {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright (C) 2020 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
Icon,
|
||||||
|
} from 'antd';
|
||||||
|
|
||||||
|
import { Canvas } from 'cvat-canvas';
|
||||||
|
import { TagIcon } from 'icons';
|
||||||
|
|
||||||
|
import SetupTagPopoverContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
canvasInstance: Canvas;
|
||||||
|
isDrawing: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SetupTagControl(props: Props): JSX.Element {
|
||||||
|
const {
|
||||||
|
isDrawing,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const dynamcPopoverPros = isDrawing ? {
|
||||||
|
overlayStyle: {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
} : {};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
{...dynamcPopoverPros}
|
||||||
|
placement='right'
|
||||||
|
overlayClassName='cvat-draw-shape-popover'
|
||||||
|
content={(
|
||||||
|
<SetupTagPopoverContainer />
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
component={TagIcon}
|
||||||
|
/>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(SetupTagControl);
|
||||||
@ -0,0 +1,75 @@
|
|||||||
|
// Copyright (C) 2020 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Select,
|
||||||
|
Button,
|
||||||
|
} from 'antd';
|
||||||
|
|
||||||
|
import Text from 'antd/lib/typography/Text';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
labels: any[];
|
||||||
|
selectedLabeID: number;
|
||||||
|
onChangeLabel(value: string): void;
|
||||||
|
onSetup(
|
||||||
|
labelID: number,
|
||||||
|
): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupTagPopover(props: Props): JSX.Element {
|
||||||
|
const {
|
||||||
|
labels,
|
||||||
|
selectedLabeID,
|
||||||
|
onChangeLabel,
|
||||||
|
onSetup,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='cvat-draw-shape-popover-content'>
|
||||||
|
<Row type='flex' justify='start'>
|
||||||
|
<Col>
|
||||||
|
<Text className='cvat-text-color' strong>Setup tag</Text>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row type='flex' justify='start'>
|
||||||
|
<Col>
|
||||||
|
<Text className='cvat-text-color'>Label</Text>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row type='flex' justify='center'>
|
||||||
|
<Col span={24}>
|
||||||
|
<Select
|
||||||
|
value={`${selectedLabeID}`}
|
||||||
|
onChange={onChangeLabel}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
labels.map((label: any) => (
|
||||||
|
<Select.Option
|
||||||
|
key={label.id}
|
||||||
|
value={`${label.id}`}
|
||||||
|
>
|
||||||
|
{label.name}
|
||||||
|
</Select.Option>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row type='flex' justify='space-around'>
|
||||||
|
<Col span={24}>
|
||||||
|
<Button onClick={() => onSetup(selectedLabeID)}>
|
||||||
|
Tag
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(setupTagPopover);
|
||||||
@ -0,0 +1,144 @@
|
|||||||
|
// Copyright (C) 2020 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import {
|
||||||
|
CombinedState,
|
||||||
|
ObjectType,
|
||||||
|
} from 'reducers/interfaces';
|
||||||
|
|
||||||
|
import {
|
||||||
|
createAnnotationsAsync,
|
||||||
|
rememberObject,
|
||||||
|
} from 'actions/annotation-actions';
|
||||||
|
import SetupTagPopoverComponent from 'components/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover';
|
||||||
|
|
||||||
|
import { Canvas } from 'cvat-canvas';
|
||||||
|
import getCore from 'cvat-core';
|
||||||
|
|
||||||
|
const cvat = getCore();
|
||||||
|
interface DispatchToProps {
|
||||||
|
onAnnotationCreate(sessionInstance: any, frame: number, states: any[]): void;
|
||||||
|
onRememberObject(labelID: number): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StateToProps {
|
||||||
|
canvasInstance: Canvas;
|
||||||
|
jobInstance: any;
|
||||||
|
labels: any[];
|
||||||
|
frame: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch: any): DispatchToProps {
|
||||||
|
return {
|
||||||
|
onAnnotationCreate(sessionInstance: any, frame: number, states: any[]): void {
|
||||||
|
dispatch(createAnnotationsAsync(sessionInstance, frame, states));
|
||||||
|
},
|
||||||
|
onRememberObject(labelID: number): void {
|
||||||
|
dispatch(rememberObject(ObjectType.TAG, labelID));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state: CombinedState): StateToProps {
|
||||||
|
const {
|
||||||
|
annotation: {
|
||||||
|
canvas: {
|
||||||
|
instance: canvasInstance,
|
||||||
|
},
|
||||||
|
job: {
|
||||||
|
instance: jobInstance,
|
||||||
|
labels,
|
||||||
|
},
|
||||||
|
player: {
|
||||||
|
frame: {
|
||||||
|
number: frame,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} = state;
|
||||||
|
|
||||||
|
return {
|
||||||
|
canvasInstance,
|
||||||
|
jobInstance,
|
||||||
|
labels,
|
||||||
|
frame,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = StateToProps & DispatchToProps;
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
selectedLabelID: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
class DrawShapePopoverContainer extends React.PureComponent<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
const defaultLabelID = props.labels[0].id;
|
||||||
|
this.state = {
|
||||||
|
selectedLabelID: defaultLabelID,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private onChangeLabel = (value: string): void => {
|
||||||
|
this.setState({
|
||||||
|
selectedLabelID: +value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private onSetup(): void {
|
||||||
|
const {
|
||||||
|
frame,
|
||||||
|
labels,
|
||||||
|
jobInstance,
|
||||||
|
canvasInstance,
|
||||||
|
onAnnotationCreate,
|
||||||
|
onRememberObject,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const { selectedLabelID } = this.state;
|
||||||
|
|
||||||
|
canvasInstance.cancel();
|
||||||
|
|
||||||
|
onRememberObject(selectedLabelID);
|
||||||
|
|
||||||
|
const objectState = new cvat.classes.ObjectState({
|
||||||
|
objectType: ObjectType.TAG,
|
||||||
|
label: labels.filter((label: any) => label.id === selectedLabelID)[0],
|
||||||
|
frame,
|
||||||
|
});
|
||||||
|
|
||||||
|
onAnnotationCreate(jobInstance, frame, [objectState]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): JSX.Element {
|
||||||
|
const {
|
||||||
|
selectedLabelID,
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
const {
|
||||||
|
labels,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
this.onSetup = this.onSetup.bind(this);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SetupTagPopoverComponent
|
||||||
|
labels={labels}
|
||||||
|
selectedLabeID={selectedLabelID}
|
||||||
|
onChangeLabel={this.onChangeLabel}
|
||||||
|
onSetup={this.onSetup}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps,
|
||||||
|
)(DrawShapePopoverContainer);
|
||||||
@ -1,10 +1,9 @@
|
|||||||
|
# Copyright (C) 2018-2020 Intel Corporation
|
||||||
# Copyright (C) 2018 Intel Corporation
|
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
from cvat.utils.version import get_version
|
from cvat.utils.version import get_version
|
||||||
|
|
||||||
VERSION = (0, 6, 0, 'alpha', 0)
|
VERSION = (1, 0, 0, 'alpha', 0)
|
||||||
|
|
||||||
__version__ = get_version(VERSION)
|
__version__ = get_version(VERSION)
|
||||||
|
|||||||
Loading…
Reference in New Issue