labels-editor.tsx 8.9 KB
Newer Older
1
// Copyright (C) 2020-2022 Intel Corporation
B
Boris Sekachev 已提交
2 3 4
//
// SPDX-License-Identifier: MIT

B
Boris Sekachev 已提交
5
import './styles.scss';
6
import React from 'react';
7
import Tabs from 'antd/lib/tabs';
8
import Text from 'antd/lib/typography/Text';
D
Dmitry Kalinin 已提交
9 10
import ModalConfirm from 'antd/lib/modal/confirm';
import {
11
    EditOutlined, BuildOutlined, ExclamationCircleOutlined,
D
Dmitry Kalinin 已提交
12
} from '@ant-design/icons';
13

14 15 16 17 18
import RawViewer from './raw-viewer';
import ConstructorViewer from './constructor-viewer';
import ConstructorCreator from './constructor-creator';
import ConstructorUpdater from './constructor-updater';

V
Vitaliy Nishukov 已提交
19
import { idGenerator, Label, Attribute } from './common';
20 21 22 23 24 25 26

enum ConstructorMode {
    SHOW = 'SHOW',
    CREATE = 'CREATE',
    UPDATE = 'UPDATE',
}

27
interface LabelsEditorProps {
28 29 30 31 32 33 34 35 36 37 38
    labels: Label[];
    onSubmit: (labels: any[]) => void;
}

interface LabelsEditorState {
    constructorMode: ConstructorMode;
    savedLabels: Label[];
    unsavedLabels: Label[];
    labelForUpdate: Label | null;
}

39 40
export default class LabelsEditor extends React.PureComponent<LabelsEditorProps, LabelsEditorState> {
    public constructor(props: LabelsEditorProps) {
41 42 43 44 45 46 47 48 49 50
        super(props);

        this.state = {
            savedLabels: [],
            unsavedLabels: [],
            constructorMode: ConstructorMode.SHOW,
            labelForUpdate: null,
        };
    }

B
Boris Sekachev 已提交
51 52
    public componentDidMount(): void {
        // just need performe the same code
53
        this.componentDidUpdate((null as any) as LabelsEditorProps);
B
Boris Sekachev 已提交
54 55
    }

56
    public componentDidUpdate(prevProps: LabelsEditorProps): void {
B
Boris Sekachev 已提交
57
        function transformLabel(label: any): Label {
58 59
            return {
                name: label.name,
B
Boris Sekachev 已提交
60
                id: label.id || idGenerator(),
D
Dmitry Kalinin 已提交
61
                color: label.color,
V
Vitaliy Nishukov 已提交
62 63
                attributes: label.attributes.map(
                    (attr: any): Attribute => ({
B
Boris Sekachev 已提交
64
                        id: attr.id || idGenerator(),
65
                        name: attr.name,
66
                        input_type: attr.input_type,
67 68
                        mutable: attr.mutable,
                        values: [...attr.values],
V
Vitaliy Nishukov 已提交
69 70
                    }),
                ),
B
Boris Sekachev 已提交
71
            };
72 73
        }

B
Boris Sekachev 已提交
74
        const { labels } = this.props;
75

B
Boris Sekachev 已提交
76 77 78
        if (!prevProps || prevProps.labels !== labels) {
            const transformedLabels = labels.map(transformLabel);
            this.setState({
V
Vitaliy Nishukov 已提交
79 80
                savedLabels: transformedLabels.filter((label: Label) => label.id >= 0),
                unsavedLabels: transformedLabels.filter((label: Label) => label.id < 0),
B
Boris Sekachev 已提交
81 82
            });
        }
83 84
    }

B
Boris Sekachev 已提交
85
    private handleRawSubmit = (labels: Label[]): void => {
86 87 88
        const unsavedLabels = [];
        const savedLabels = [];

B
Boris Sekachev 已提交
89
        for (const label of labels) {
90 91 92 93 94 95 96
            if (label.id >= 0) {
                savedLabels.push(label);
            } else {
                unsavedLabels.push(label);
            }
        }

97
        this.setState({ unsavedLabels, savedLabels });
98
        this.handleSubmit(savedLabels, unsavedLabels);
B
Boris Sekachev 已提交
99 100 101 102
    };

    private handleCreate = (label: Label | null): void => {
        if (label === null) {
103
            this.setState({ constructorMode: ConstructorMode.SHOW });
B
Boris Sekachev 已提交
104
        } else {
105
            const { unsavedLabels, savedLabels } = this.state;
B
Boris Sekachev 已提交
106 107 108 109 110 111 112 113
            const newUnsavedLabels = [
                ...unsavedLabels,
                {
                    ...label,
                    id: idGenerator(),
                },
            ];

114
            this.setState({ unsavedLabels: newUnsavedLabels });
B
Boris Sekachev 已提交
115 116 117 118 119
            this.handleSubmit(savedLabels, newUnsavedLabels);
        }
    };

    private handleUpdate = (label: Label | null): void => {
120
        const { savedLabels, unsavedLabels } = this.state;
121 122

        if (label) {
V
Vitaliy Nishukov 已提交
123 124
            const filteredSavedLabels = savedLabels.filter((_label: Label) => _label.id !== label.id);
            const filteredUnsavedLabels = unsavedLabels.filter((_label: Label) => _label.id !== label.id);
125
            if (label.id >= 0) {
B
Boris Sekachev 已提交
126
                filteredSavedLabels.push(label);
127
                this.setState({
B
Boris Sekachev 已提交
128
                    savedLabels: filteredSavedLabels,
129 130 131
                    constructorMode: ConstructorMode.SHOW,
                });
            } else {
B
Boris Sekachev 已提交
132
                filteredUnsavedLabels.push(label);
133
                this.setState({
B
Boris Sekachev 已提交
134
                    unsavedLabels: filteredUnsavedLabels,
135 136 137 138
                    constructorMode: ConstructorMode.SHOW,
                });
            }

B
Boris Sekachev 已提交
139
            this.handleSubmit(filteredSavedLabels, filteredUnsavedLabels);
140
        } else {
141
            this.setState({ constructorMode: ConstructorMode.SHOW });
142 143 144
        }
    };

B
Boris Sekachev 已提交
145
    private handleDelete = (label: Label): void => {
D
Dmitry Kalinin 已提交
146 147
        const deleteLabel = (): void => {
            const { unsavedLabels, savedLabels } = this.state;
148

D
Dmitry Kalinin 已提交
149 150
            const filteredUnsavedLabels = unsavedLabels.filter((_label: Label): boolean => _label.id !== label.id);
            const filteredSavedLabels = savedLabels.filter((_label: Label): boolean => _label.id !== label.id);
B
Boris Sekachev 已提交
151

D
Dmitry Kalinin 已提交
152 153 154
            this.setState({ savedLabels: filteredSavedLabels, unsavedLabels: filteredUnsavedLabels });
            this.handleSubmit(filteredSavedLabels, filteredUnsavedLabels);
        };
155

D
Dmitry Kalinin 已提交
156 157
        if (typeof label.id !== 'undefined' && label.id >= 0) {
            ModalConfirm({
158
                className: 'cvat-modal-delete-label',
D
Dmitry Kalinin 已提交
159 160 161 162 163 164 165 166 167 168 169 170
                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();
        }
171 172
    };

B
Boris Sekachev 已提交
173 174
    private handleSubmit(savedLabels: Label[], unsavedLabels: Label[]): void {
        function transformLabel(label: Label): any {
175 176
            return {
                name: label.name,
B
Boris Sekachev 已提交
177
                id: label.id < 0 ? undefined : label.id,
D
Dmitry Kalinin 已提交
178
                color: label.color,
V
Vitaliy Nishukov 已提交
179 180 181 182 183 184 185 186
                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],
                })),
B
Boris Sekachev 已提交
187
            };
188 189
        }

B
Boris Sekachev 已提交
190
        const { onSubmit } = this.props;
V
Vitaliy Nishukov 已提交
191
        const output = savedLabels.concat(unsavedLabels).map((label: Label): any => transformLabel(label));
B
Boris Sekachev 已提交
192 193

        onSubmit(output);
194 195
    }

B
Boris Sekachev 已提交
196
    public render(): JSX.Element {
D
Dmitry Kalinin 已提交
197
        const { labels } = this.props;
B
Boris Sekachev 已提交
198 199 200
        const {
            savedLabels, unsavedLabels, constructorMode, labelForUpdate,
        } = this.state;
B
Boris Sekachev 已提交
201

202
        return (
203 204 205 206 207
            <Tabs
                defaultActiveKey='2'
                type='card'
                tabBarStyle={{ marginBottom: '0px' }}
            >
B
Boris Sekachev 已提交
208
                <Tabs.TabPane
B
Boris Sekachev 已提交
209
                    tab={(
V
Vitaliy Nishukov 已提交
210
                        <span>
B
Boris Sekachev 已提交
211
                            <EditOutlined />
V
Vitaliy Nishukov 已提交
212 213
                            <Text>Raw</Text>
                        </span>
B
Boris Sekachev 已提交
214
                    )}
B
Boris Sekachev 已提交
215 216
                    key='1'
                >
V
Vitaliy Nishukov 已提交
217
                    <RawViewer labels={[...savedLabels, ...unsavedLabels]} onSubmit={this.handleRawSubmit} />
218 219
                </Tabs.TabPane>

B
Boris Sekachev 已提交
220
                <Tabs.TabPane
B
Boris Sekachev 已提交
221
                    tab={(
V
Vitaliy Nishukov 已提交
222
                        <span>
B
Boris Sekachev 已提交
223
                            <BuildOutlined />
V
Vitaliy Nishukov 已提交
224 225
                            <Text>Constructor</Text>
                        </span>
B
Boris Sekachev 已提交
226
                    )}
B
Boris Sekachev 已提交
227 228
                    key='2'
                >
V
Vitaliy Nishukov 已提交
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
                    {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} />
                    )}
252 253 254 255 256
                </Tabs.TabPane>
            </Tabs>
        );
    }
}