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