1
|
import { Col, Container, Row } from 'react-bootstrap';
|
2
|
import { Tag } from '../types/tag';
|
3
|
import { ChangeEvent, useContext } from 'react';
|
4
|
import 'antd/dist/antd.css';
|
5
|
import { Button, Input, Select } from 'antd';
|
6
|
import { DeleteOutlined, EyeOutlined, LikeOutlined } from '@ant-design/icons';
|
7
|
import { AnnotationContext } from '../../contexts/AnnotationContext';
|
8
|
import { ETagSentiment, TagInstanceInfo } from '../../api';
|
9
|
import { getNameTruncated, getTextMaxLength } from '../../utils/strings';
|
10
|
import {
|
11
|
COLOR_ALL_OCCURRENCES_ACCEPTED,
|
12
|
COLOR_HIGHLIGHTED_OCCURRENCE,
|
13
|
COLOR_HIGHLIGHTED_TAG,
|
14
|
COLOR_OCCURRENCE_ACCEPTED,
|
15
|
} from '../../constants';
|
16
|
import { ShowConfirm } from '../../utils/alerts';
|
17
|
|
18
|
const { Option } = Select;
|
19
|
|
20
|
/**
|
21
|
* Creates a single item in an annotation panel.
|
22
|
* @param props Properties should contain a tag that will be shown in this annotation.
|
23
|
* @returns The item that represents an annotation.
|
24
|
*/
|
25
|
export function AnnotationOccurrenceItem(props: {
|
26
|
tag: Tag;
|
27
|
occurrence: TagInstanceInfo;
|
28
|
}) {
|
29
|
/**
|
30
|
* Context that manages annotations.
|
31
|
*/
|
32
|
const {
|
33
|
deleteOccurrence,
|
34
|
changeNote,
|
35
|
changeSentiment,
|
36
|
selectedOccurrenceId,
|
37
|
setSelectedOccurrenceId,
|
38
|
setSelectedInstanceId,
|
39
|
isFinal,
|
40
|
makeOccurrenceFinal,
|
41
|
} = useContext(AnnotationContext);
|
42
|
|
43
|
const onChangeSentiment = (occurrence: TagInstanceInfo) => (val: ETagSentiment) => {
|
44
|
changeSentiment(occurrence, val);
|
45
|
};
|
46
|
|
47
|
/**
|
48
|
* Removes an occurrence of this annotation from the context.
|
49
|
* @param occurrence The occurrence that should be removed.
|
50
|
*/
|
51
|
const onDeleteOccurrence = (occurrence: TagInstanceInfo) => (e: any) => {
|
52
|
deleteOccurrence(occurrence);
|
53
|
};
|
54
|
|
55
|
const onChangeNote =
|
56
|
(occurrence: TagInstanceInfo) => (e: ChangeEvent<HTMLTextAreaElement>) => {
|
57
|
changeNote(occurrence, e.currentTarget.value);
|
58
|
};
|
59
|
|
60
|
function getBackgroundColor(): string {
|
61
|
if (selectedOccurrenceId === props.occurrence.occurenceId) {
|
62
|
// highlighted occurrence
|
63
|
return COLOR_HIGHLIGHTED_OCCURRENCE;
|
64
|
}
|
65
|
|
66
|
if (isFinal) {
|
67
|
const allAccepted =
|
68
|
props.tag.occurrences.filter((o) => !o.isFinal).length === 0;
|
69
|
if (allAccepted) {
|
70
|
return COLOR_OCCURRENCE_ACCEPTED;
|
71
|
}
|
72
|
}
|
73
|
|
74
|
return 'white';
|
75
|
}
|
76
|
|
77
|
return (
|
78
|
<Container
|
79
|
className="shadow-sm"
|
80
|
style={{
|
81
|
backgroundColor: getBackgroundColor(),
|
82
|
}}
|
83
|
>
|
84
|
<Row className="mb-1 mt-1">
|
85
|
<Col>
|
86
|
<Row>
|
87
|
<Col>Pozice: {props.occurrence.position}</Col>
|
88
|
<Col>Délka: {props.occurrence.length}</Col>
|
89
|
</Row>
|
90
|
<Row>
|
91
|
<i title={props.occurrence.selectedText ?? ''}>
|
92
|
{getTextMaxLength(props.occurrence.selectedText ?? '', 35)}
|
93
|
</i>
|
94
|
</Row>
|
95
|
<Row>
|
96
|
<Col className="d-flex align-items-center" sm="4">
|
97
|
Poznámka:
|
98
|
</Col>
|
99
|
<Col>
|
100
|
<Input.TextArea
|
101
|
defaultValue={props.occurrence.note ?? ''}
|
102
|
onBlur={onChangeNote(props.occurrence)}
|
103
|
rows={1}
|
104
|
/>
|
105
|
</Col>
|
106
|
</Row>
|
107
|
{props.occurrence.sentiment && (
|
108
|
<Row>
|
109
|
<Col className="d-flex align-items-center" sm="4">
|
110
|
Sentiment:
|
111
|
</Col>
|
112
|
<Col>
|
113
|
<Select
|
114
|
defaultValue={props.occurrence.sentiment}
|
115
|
style={{ width: '100%' }}
|
116
|
onChange={onChangeSentiment(props.occurrence)}
|
117
|
>
|
118
|
<Option value={ETagSentiment.Positive}>
|
119
|
<span style={{ color: 'green' }}>Pozitivní</span>
|
120
|
</Option>
|
121
|
<Option value={ETagSentiment.Neutral}>
|
122
|
Neutrální
|
123
|
</Option>
|
124
|
<Option value={ETagSentiment.Negative}>
|
125
|
<span
|
126
|
style={{
|
127
|
color: '#ff4d4f',
|
128
|
}}
|
129
|
>
|
130
|
Negativní
|
131
|
</span>
|
132
|
</Option>
|
133
|
</Select>
|
134
|
</Col>
|
135
|
</Row>
|
136
|
)}
|
137
|
</Col>
|
138
|
<Col
|
139
|
sm="auto"
|
140
|
className="d-flex align-items-center flex-column justify-content-sm-evenly"
|
141
|
>
|
142
|
<Button
|
143
|
icon={<EyeOutlined />}
|
144
|
onClick={() => {
|
145
|
setSelectedOccurrenceId(props.occurrence.occurenceId ?? null);
|
146
|
setSelectedInstanceId(props.tag.instanceId);
|
147
|
}}
|
148
|
title={'Zvýraznit výskyt'}
|
149
|
/>
|
150
|
<Button
|
151
|
icon={<DeleteOutlined />}
|
152
|
onClick={onDeleteOccurrence(props.occurrence)}
|
153
|
danger
|
154
|
className={'mt-1'}
|
155
|
/>
|
156
|
{isFinal && (
|
157
|
<Button
|
158
|
icon={<LikeOutlined />}
|
159
|
title={'Označit jako správné řešení'}
|
160
|
onClick={() => {
|
161
|
ShowConfirm(() => {
|
162
|
makeOccurrenceFinal(props.occurrence);
|
163
|
}, 'označit toto řešení jako správné');
|
164
|
}}
|
165
|
className={'mt-1'}
|
166
|
/>
|
167
|
)}
|
168
|
</Col>
|
169
|
</Row>
|
170
|
{isFinal && (
|
171
|
<Row className="mb-1 mt-1">
|
172
|
<Col>
|
173
|
Označili:{' '}
|
174
|
{props.occurrence.users?.map((u) => (
|
175
|
<span
|
176
|
title={u.name + ' ' + u.surname + ' (' + u.username + ')'}
|
177
|
key={props.occurrence.occurenceId + '.' + u.id}
|
178
|
style={{ marginRight: 10 }}
|
179
|
>
|
180
|
{getNameTruncated(u)}
|
181
|
</span>
|
182
|
))}
|
183
|
</Col>
|
184
|
</Row>
|
185
|
)}
|
186
|
</Container>
|
187
|
);
|
188
|
}
|