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.
286 lines
10 KiB
TypeScript
286 lines
10 KiB
TypeScript
// Copyright (C) 2020-2021 Intel Corporation
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
import './styles.scss';
|
|
import React from 'react';
|
|
import Tabs from 'antd/lib/tabs';
|
|
import Button from 'antd/lib/button';
|
|
import Text from 'antd/lib/typography/Text';
|
|
import ModalConfirm from 'antd/lib/modal/confirm';
|
|
import copy from 'copy-to-clipboard';
|
|
import {
|
|
CopyOutlined, EditOutlined, BuildOutlined, ExclamationCircleOutlined,
|
|
} from '@ant-design/icons';
|
|
|
|
import CVATTooltip from 'components/common/cvat-tooltip';
|
|
import RawViewer from './raw-viewer';
|
|
import ConstructorViewer from './constructor-viewer';
|
|
import ConstructorCreator from './constructor-creator';
|
|
import ConstructorUpdater from './constructor-updater';
|
|
|
|
import { idGenerator, Label, Attribute } from './common';
|
|
|
|
enum ConstructorMode {
|
|
SHOW = 'SHOW',
|
|
CREATE = 'CREATE',
|
|
UPDATE = 'UPDATE',
|
|
}
|
|
|
|
interface LabelsEditortProps {
|
|
labels: Label[];
|
|
onSubmit: (labels: any[]) => void;
|
|
}
|
|
|
|
interface LabelsEditorState {
|
|
constructorMode: ConstructorMode;
|
|
savedLabels: Label[];
|
|
unsavedLabels: Label[];
|
|
labelForUpdate: Label | null;
|
|
}
|
|
|
|
export default class LabelsEditor extends React.PureComponent<LabelsEditortProps, LabelsEditorState> {
|
|
public constructor(props: LabelsEditortProps) {
|
|
super(props);
|
|
|
|
this.state = {
|
|
savedLabels: [],
|
|
unsavedLabels: [],
|
|
constructorMode: ConstructorMode.SHOW,
|
|
labelForUpdate: null,
|
|
};
|
|
}
|
|
|
|
public componentDidMount(): void {
|
|
// just need performe the same code
|
|
this.componentDidUpdate((null as any) as LabelsEditortProps);
|
|
}
|
|
|
|
public componentDidUpdate(prevProps: LabelsEditortProps): void {
|
|
function transformLabel(label: any): Label {
|
|
return {
|
|
name: label.name,
|
|
id: label.id || idGenerator(),
|
|
color: label.color,
|
|
attributes: label.attributes.map(
|
|
(attr: any): Attribute => ({
|
|
id: attr.id || idGenerator(),
|
|
name: attr.name,
|
|
input_type: attr.input_type,
|
|
mutable: attr.mutable,
|
|
values: [...attr.values],
|
|
}),
|
|
),
|
|
};
|
|
}
|
|
|
|
const { labels } = this.props;
|
|
|
|
if (!prevProps || prevProps.labels !== labels) {
|
|
const transformedLabels = labels.map(transformLabel);
|
|
this.setState({
|
|
savedLabels: transformedLabels.filter((label: Label) => label.id >= 0),
|
|
unsavedLabels: transformedLabels.filter((label: Label) => label.id < 0),
|
|
});
|
|
}
|
|
}
|
|
|
|
private handleRawSubmit = (labels: Label[]): void => {
|
|
const unsavedLabels = [];
|
|
const savedLabels = [];
|
|
|
|
for (const label of labels) {
|
|
if (label.id >= 0) {
|
|
savedLabels.push(label);
|
|
} else {
|
|
unsavedLabels.push(label);
|
|
}
|
|
}
|
|
|
|
this.setState({ unsavedLabels, savedLabels });
|
|
this.handleSubmit(savedLabels, unsavedLabels);
|
|
};
|
|
|
|
private handleCreate = (label: Label | null): void => {
|
|
if (label === null) {
|
|
this.setState({ constructorMode: ConstructorMode.SHOW });
|
|
} else {
|
|
const { unsavedLabels, savedLabels } = this.state;
|
|
const newUnsavedLabels = [
|
|
...unsavedLabels,
|
|
{
|
|
...label,
|
|
id: idGenerator(),
|
|
},
|
|
];
|
|
|
|
this.setState({ unsavedLabels: newUnsavedLabels });
|
|
this.handleSubmit(savedLabels, newUnsavedLabels);
|
|
}
|
|
};
|
|
|
|
private handleUpdate = (label: Label | null): void => {
|
|
const { savedLabels, unsavedLabels } = this.state;
|
|
|
|
if (label) {
|
|
const filteredSavedLabels = savedLabels.filter((_label: Label) => _label.id !== label.id);
|
|
const filteredUnsavedLabels = unsavedLabels.filter((_label: Label) => _label.id !== label.id);
|
|
if (label.id >= 0) {
|
|
filteredSavedLabels.push(label);
|
|
this.setState({
|
|
savedLabels: filteredSavedLabels,
|
|
constructorMode: ConstructorMode.SHOW,
|
|
});
|
|
} else {
|
|
filteredUnsavedLabels.push(label);
|
|
this.setState({
|
|
unsavedLabels: filteredUnsavedLabels,
|
|
constructorMode: ConstructorMode.SHOW,
|
|
});
|
|
}
|
|
|
|
this.handleSubmit(filteredSavedLabels, filteredUnsavedLabels);
|
|
} else {
|
|
this.setState({ constructorMode: ConstructorMode.SHOW });
|
|
}
|
|
};
|
|
|
|
private handleDelete = (label: Label): void => {
|
|
const deleteLabel = (): void => {
|
|
const { unsavedLabels, savedLabels } = this.state;
|
|
|
|
const filteredUnsavedLabels = unsavedLabels.filter((_label: Label): boolean => _label.id !== label.id);
|
|
const filteredSavedLabels = savedLabels.filter((_label: Label): boolean => _label.id !== label.id);
|
|
|
|
this.setState({ savedLabels: filteredSavedLabels, unsavedLabels: filteredUnsavedLabels });
|
|
this.handleSubmit(filteredSavedLabels, filteredUnsavedLabels);
|
|
};
|
|
|
|
if (typeof label.id !== 'undefined' && label.id >= 0) {
|
|
ModalConfirm({
|
|
className: 'cvat-modal-delete-label',
|
|
title: `Do you want to delete "${label.name}" label?`,
|
|
icon: <ExclamationCircleOutlined />,
|
|
content: 'This action is irreversible. Annotation corresponding with this label will be deleted.',
|
|
type: 'warning',
|
|
okType: 'danger',
|
|
onOk() {
|
|
deleteLabel();
|
|
},
|
|
});
|
|
} else {
|
|
deleteLabel();
|
|
}
|
|
};
|
|
|
|
private handleSubmit(savedLabels: Label[], unsavedLabels: Label[]): void {
|
|
function transformLabel(label: Label): any {
|
|
return {
|
|
name: label.name,
|
|
id: label.id < 0 ? undefined : label.id,
|
|
color: label.color,
|
|
attributes: label.attributes.map((attr: Attribute): any => ({
|
|
name: attr.name,
|
|
id: attr.id < 0 ? undefined : attr.id,
|
|
input_type: attr.input_type.toLowerCase(),
|
|
default_value: attr.values[0],
|
|
mutable: attr.mutable,
|
|
values: [...attr.values],
|
|
})),
|
|
};
|
|
}
|
|
|
|
const { onSubmit } = this.props;
|
|
const output = savedLabels.concat(unsavedLabels).map((label: Label): any => transformLabel(label));
|
|
|
|
onSubmit(output);
|
|
}
|
|
|
|
public render(): JSX.Element {
|
|
const { labels } = this.props;
|
|
const {
|
|
savedLabels, unsavedLabels, constructorMode, labelForUpdate,
|
|
} = this.state;
|
|
|
|
return (
|
|
<Tabs
|
|
defaultActiveKey='2'
|
|
type='card'
|
|
tabBarStyle={{ marginBottom: '0px' }}
|
|
tabBarExtraContent={(
|
|
<CVATTooltip title='Copied to clipboard!' trigger='click'>
|
|
<Button
|
|
type='link'
|
|
icon={<CopyOutlined />}
|
|
onClick={(): void => {
|
|
copy(
|
|
JSON.stringify(
|
|
savedLabels.concat(unsavedLabels).map((label): any => ({
|
|
...label,
|
|
id: undefined,
|
|
attributes: label.attributes.map((attribute): any => ({
|
|
...attribute,
|
|
id: undefined,
|
|
})),
|
|
})),
|
|
null,
|
|
4,
|
|
),
|
|
);
|
|
}}
|
|
>
|
|
Copy
|
|
</Button>
|
|
</CVATTooltip>
|
|
)}
|
|
>
|
|
<Tabs.TabPane
|
|
tab={(
|
|
<span>
|
|
<EditOutlined />
|
|
<Text>Raw</Text>
|
|
</span>
|
|
)}
|
|
key='1'
|
|
>
|
|
<RawViewer labels={[...savedLabels, ...unsavedLabels]} onSubmit={this.handleRawSubmit} />
|
|
</Tabs.TabPane>
|
|
|
|
<Tabs.TabPane
|
|
tab={(
|
|
<span>
|
|
<BuildOutlined />
|
|
<Text>Constructor</Text>
|
|
</span>
|
|
)}
|
|
key='2'
|
|
>
|
|
{constructorMode === ConstructorMode.SHOW && (
|
|
<ConstructorViewer
|
|
labels={[...savedLabels, ...unsavedLabels]}
|
|
onUpdate={(label: Label): void => {
|
|
this.setState({
|
|
constructorMode: ConstructorMode.UPDATE,
|
|
labelForUpdate: label,
|
|
});
|
|
}}
|
|
onDelete={this.handleDelete}
|
|
onCreate={(): void => {
|
|
this.setState({
|
|
constructorMode: ConstructorMode.CREATE,
|
|
});
|
|
}}
|
|
/>
|
|
)}
|
|
{constructorMode === ConstructorMode.UPDATE && labelForUpdate !== null && (
|
|
<ConstructorUpdater label={labelForUpdate} onUpdate={this.handleUpdate} />
|
|
)}
|
|
{constructorMode === ConstructorMode.CREATE && (
|
|
<ConstructorCreator labelNames={labels.map((l) => l.name)} onCreate={this.handleCreate} />
|
|
)}
|
|
</Tabs.TabPane>
|
|
</Tabs>
|
|
);
|
|
}
|
|
}
|