Revize b80a6919
Přidáno uživatelem Lukáš Vlček před asi 2 roky(ů)
webapp/components/annotation/AnnotationItem.tsx | ||
---|---|---|
1 | 1 |
import { Col, Container, Row, Stack } from 'react-bootstrap'; |
2 | 2 |
import { Tag } from '../types/tag'; |
3 |
import { ChangeEvent, useContext, useState } from 'react';
|
|
3 |
import { useContext, useState } from 'react'; |
|
4 | 4 |
import 'antd/dist/antd.css'; |
5 |
import { Button, Input, Select } from 'antd'; |
|
6 |
import { |
|
7 |
PlusOutlined, |
|
8 |
DownOutlined, |
|
9 |
DeleteOutlined, |
|
10 |
TagOutlined, |
|
11 |
EyeOutlined, |
|
12 |
} from '@ant-design/icons'; |
|
5 |
import { Button, Select } from 'antd'; |
|
6 |
import { DownOutlined, PlusOutlined, TagOutlined } from '@ant-design/icons'; |
|
13 | 7 |
import { AnnotationContext } from '../../contexts/AnnotationContext'; |
14 |
import { ETagSentiment, TagInstanceInfo } from '../../api'; |
|
15 |
import { getTextMaxLength } from '../../utils/strings'; |
|
8 |
import { AnnotationOccurrenceItem } from './AnnotationOccurrenceItem'; |
|
9 |
import { COLOR_ALL_OCCURRENCES_ACCEPTED, COLOR_HIGHLIGHTED_TAG } from '../../constants'; |
|
10 |
import { ABadge, BadgeStyle } from '../common/ABadge'; |
|
11 |
import { getNameTruncated } from '../../utils/strings'; |
|
12 |
import { ShowConfirm } from '../../utils/alerts'; |
|
16 | 13 |
|
17 | 14 |
const { Option } = Select; |
18 | 15 |
|
... | ... | |
25 | 22 |
/** |
26 | 23 |
* Should properties of this annotation be visible? |
27 | 24 |
*/ |
28 |
const [visibleProperties, setVisibleProperties] = useState(false);
|
|
25 |
const [detailsVisible, setDetailsVisible] = useState(false);
|
|
29 | 26 |
|
30 | 27 |
/** |
31 | 28 |
* Context that manages annotations. |
32 | 29 |
*/ |
33 | 30 |
const { |
34 | 31 |
addOccurrence, |
35 |
deleteOccurrence, |
|
36 |
changePosition, |
|
37 |
changeNote, |
|
38 |
changeSentiment, |
|
39 |
changeLength, |
|
40 |
selectedOccurrenceId, |
|
41 | 32 |
selectedInstanceId, |
42 | 33 |
setSelectedOccurrenceId, |
43 | 34 |
setSelectedInstanceId, |
35 |
isFinal, |
|
36 |
makeOccurrenceFinal, |
|
44 | 37 |
} = useContext(AnnotationContext); |
45 | 38 |
|
46 | 39 |
/** |
... | ... | |
50 | 43 |
addOccurrence(props.tag); |
51 | 44 |
}; |
52 | 45 |
|
53 |
/** |
|
54 |
* Removes an occurrence of this annotation from the context. |
|
55 |
* @param occurrence The occurrence that should be removed. |
|
56 |
*/ |
|
57 |
const onDeleteOccurrence = (occurrence: TagInstanceInfo) => (e: any) => { |
|
58 |
deleteOccurrence(occurrence); |
|
59 |
}; |
|
60 |
|
|
61 |
/** |
|
62 |
* Changes a position of an occurrence of this annotation in the context. |
|
63 |
* @param occurrence The occurrence that should be changed. |
|
64 |
*/ |
|
65 |
const onChangePosition = |
|
66 |
(occurrence: TagInstanceInfo) => (e: ChangeEvent<HTMLInputElement>) => { |
|
67 |
changePosition(occurrence, Number(e.currentTarget.value)); |
|
68 |
}; |
|
69 |
|
|
70 |
/** |
|
71 |
* Changes a length of an occurrence of this annotation in the context. |
|
72 |
* @param occurrence The occurrence that should be changed. |
|
73 |
*/ |
|
74 |
const onChangeLength = |
|
75 |
(occurrence: TagInstanceInfo) => (e: ChangeEvent<HTMLInputElement>) => { |
|
76 |
changeLength(occurrence, Number(e.currentTarget.value)); |
|
77 |
}; |
|
78 |
|
|
79 |
const onChangeSentiment = (occurrence: TagInstanceInfo) => (val: ETagSentiment) => { |
|
80 |
changeSentiment(occurrence, val); |
|
81 |
}; |
|
82 |
|
|
83 |
const onChangeNote = |
|
84 |
(occurrence: TagInstanceInfo) => (e: ChangeEvent<HTMLTextAreaElement>) => { |
|
85 |
changeNote(occurrence, e.currentTarget.value); |
|
86 |
}; |
|
87 |
|
|
88 | 46 |
/** |
89 | 47 |
* Changes visibility of properties of this annotation. |
90 | 48 |
*/ |
91 | 49 |
const changePropertiesVisibility = () => { |
92 |
setVisibleProperties(!visibleProperties);
|
|
50 |
setDetailsVisible(!detailsVisible);
|
|
93 | 51 |
}; |
94 | 52 |
|
53 |
function isAllAccepted() { |
|
54 |
return props.tag.occurrences.filter((o) => !o.isFinal).length === 0; |
|
55 |
} |
|
56 |
function getBackgroundColor(): string { |
|
57 |
if (selectedInstanceId === props.tag.instanceId) { |
|
58 |
// highlighted tag |
|
59 |
return COLOR_HIGHLIGHTED_TAG; |
|
60 |
} |
|
61 |
|
|
62 |
if (isFinal) { |
|
63 |
if (isAllAccepted()) { |
|
64 |
return COLOR_ALL_OCCURRENCES_ACCEPTED; |
|
65 |
} else { |
|
66 |
return '#E4C8CC'; |
|
67 |
} |
|
68 |
} |
|
69 |
|
|
70 |
return 'white'; |
|
71 |
} |
|
95 | 72 |
return ( |
96 |
<Container> |
|
97 |
<Row |
|
98 |
className="border rounded" |
|
99 |
style={{ |
|
100 |
backgroundColor: |
|
101 |
selectedInstanceId === props.tag.instanceId ? '#FCF3CF' : 'white', |
|
102 |
}} |
|
103 |
> |
|
73 |
<Container |
|
74 |
className="border rounded" |
|
75 |
style={{ |
|
76 |
backgroundColor: getBackgroundColor(), |
|
77 |
}} |
|
78 |
> |
|
79 |
<Row className=""> |
|
104 | 80 |
<Col sm="auto" className="d-flex align-items-center"> |
105 | 81 |
<Button |
106 | 82 |
icon={<TagOutlined style={{ marginLeft: 0 }} />} |
... | ... | |
111 | 87 |
style={{ |
112 | 88 |
border: 'none', |
113 | 89 |
paddingLeft: 0, |
114 |
backgroundColor: |
|
115 |
selectedInstanceId === props.tag.instanceId |
|
116 |
? '#FCF3CF' |
|
117 |
: 'white', |
|
90 |
backgroundColor: getBackgroundColor(), |
|
118 | 91 |
}} |
119 | 92 |
title={'Zvýraznit značky'} |
120 | 93 |
/> |
... | ... | |
124 | 97 |
{props.tag.subtagName ? ' (' + props.tag.subtagName + ')' : ''} |
125 | 98 |
</Col> |
126 | 99 |
<Col sm="auto"> |
100 |
{props.tag.occurrences.length > 1 && ( |
|
101 |
<ABadge style={BadgeStyle.GENERAL}> |
|
102 |
{props.tag.occurrences.length} |
|
103 |
</ABadge> |
|
104 |
)} |
|
105 |
|
|
127 | 106 |
<Button |
128 | 107 |
type="text" |
129 | 108 |
shape="circle" |
... | ... | |
138 | 117 |
/> |
139 | 118 |
</Col> |
140 | 119 |
</Row> |
141 |
{visibleProperties && ( |
|
142 |
<Stack gap={1} className="mb-2"> |
|
143 |
<div>Kategorie: {props.tag.category}</div> |
|
144 |
<div>Výskyty:</div> |
|
145 |
{props.tag.occurrences.map((occurrence, index) => { |
|
146 |
return ( |
|
147 |
<Container |
|
148 |
key={index} |
|
149 |
className="shadow-sm" |
|
150 |
style={{ |
|
151 |
backgroundColor: |
|
152 |
selectedOccurrenceId === occurrence.occurenceId |
|
153 |
? '#D8E1E9' |
|
154 |
: 'white', |
|
155 |
}} |
|
156 |
> |
|
157 |
<Row className="mb-1 mt-1"> |
|
158 |
<Col> |
|
159 |
<Row> |
|
160 |
<Col>Pozice: {occurrence.position}</Col> |
|
161 |
<Col>Délka: {occurrence.length}</Col> |
|
162 |
</Row> |
|
163 |
<Row> |
|
164 |
<i title={occurrence.selectedText ?? ''}> |
|
165 |
{getTextMaxLength( |
|
166 |
occurrence.selectedText ?? '', |
|
167 |
35 |
|
168 |
)} |
|
169 |
</i> |
|
170 |
</Row> |
|
171 |
<Row> |
|
172 |
<Col |
|
173 |
className="d-flex align-items-center" |
|
174 |
sm="4" |
|
120 |
{isFinal && !isAllAccepted() && ( |
|
121 |
<> |
|
122 |
<Row |
|
123 |
style={{ |
|
124 |
backgroundColor: getBackgroundColor(), |
|
125 |
}} |
|
126 |
> |
|
127 |
<Col sm="12" className="d-flex align-items-center"> |
|
128 |
{props.tag.occurrences.length === 1 && ( |
|
129 |
<div |
|
130 |
style={{ |
|
131 |
display: 'flex', |
|
132 |
flexDirection: 'row', |
|
133 |
width: '100%', |
|
134 |
justifyContent: 'space-between', |
|
135 |
}} |
|
136 |
> |
|
137 |
<div style={{ width: '60%' }}> |
|
138 |
Anotovali:{' '} |
|
139 |
{props.tag.occurrences[0].users?.map((u) => ( |
|
140 |
<span |
|
141 |
title={ |
|
142 |
u.name + |
|
143 |
' ' + |
|
144 |
u.surname + |
|
145 |
' (' + |
|
146 |
u.username + |
|
147 |
')' |
|
148 |
} |
|
149 |
key={ |
|
150 |
props.tag.occurrences[0].occurenceId + |
|
151 |
'.' + |
|
152 |
u.id |
|
153 |
} |
|
154 |
style={{ marginRight: 10 }} |
|
175 | 155 |
> |
176 |
Poznámka: |
|
177 |
</Col> |
|
178 |
<Col> |
|
179 |
<Input.TextArea |
|
180 |
defaultValue={occurrence.note ?? ''} |
|
181 |
onBlur={onChangeNote(occurrence)} |
|
182 |
rows={1} |
|
183 |
/> |
|
184 |
</Col> |
|
185 |
</Row> |
|
186 |
{occurrence.sentiment && ( |
|
187 |
<Row> |
|
188 |
<Col |
|
189 |
className="d-flex align-items-center" |
|
190 |
sm="4" |
|
191 |
> |
|
192 |
Sentiment: |
|
193 |
</Col> |
|
194 |
<Col> |
|
195 |
<Select |
|
196 |
defaultValue={ |
|
197 |
occurrence.sentiment |
|
198 |
} |
|
199 |
style={{ width: '100%' }} |
|
200 |
onChange={onChangeSentiment( |
|
201 |
occurrence |
|
202 |
)} |
|
203 |
> |
|
204 |
<Option |
|
205 |
value={ETagSentiment.Positive} |
|
206 |
> |
|
207 |
<span |
|
208 |
style={{ color: 'green' }} |
|
209 |
> |
|
210 |
Pozitivní |
|
211 |
</span> |
|
212 |
</Option> |
|
213 |
<Option |
|
214 |
value={ETagSentiment.Neutral} |
|
215 |
> |
|
216 |
Neutrální |
|
217 |
</Option> |
|
218 |
<Option |
|
219 |
value={ETagSentiment.Negative} |
|
220 |
> |
|
221 |
<span |
|
222 |
style={{ |
|
223 |
color: '#ff4d4f', |
|
224 |
}} |
|
225 |
> |
|
226 |
Negativní |
|
227 |
</span> |
|
228 |
</Option> |
|
229 |
</Select> |
|
230 |
</Col> |
|
231 |
</Row> |
|
232 |
)} |
|
233 |
</Col> |
|
234 |
<Col |
|
235 |
sm="auto" |
|
236 |
className="d-flex align-items-center flex-column justify-content-sm-evenly" |
|
237 |
> |
|
156 |
{getNameTruncated(u)} |
|
157 |
</span> |
|
158 |
))} |
|
159 |
</div> |
|
160 |
<div> |
|
238 | 161 |
<Button |
239 |
icon={<EyeOutlined />} |
|
240 | 162 |
onClick={() => { |
241 |
setSelectedOccurrenceId( |
|
242 |
occurrence.occurenceId ?? null |
|
243 |
); |
|
244 |
setSelectedInstanceId( |
|
245 |
props.tag.instanceId |
|
246 |
); |
|
163 |
ShowConfirm(() => { |
|
164 |
makeOccurrenceFinal( |
|
165 |
props.tag.occurrences[0] |
|
166 |
); |
|
167 |
}, 'označit toto řešení jako správné'); |
|
247 | 168 |
}} |
248 |
title={'Zvýraznit výskyt'} |
|
249 |
/> |
|
250 |
<Button |
|
251 |
icon={<DeleteOutlined />} |
|
252 |
onClick={onDeleteOccurrence(occurrence)} |
|
253 |
danger |
|
254 |
/> |
|
255 |
</Col> |
|
256 |
</Row> |
|
257 |
</Container> |
|
169 |
> |
|
170 |
Přijmout toto řešení |
|
171 |
</Button> |
|
172 |
</div> |
|
173 |
</div> |
|
174 |
)} |
|
175 |
</Col> |
|
176 |
</Row> |
|
177 |
</> |
|
178 |
)} |
|
179 |
|
|
180 |
{detailsVisible && ( |
|
181 |
<Stack gap={1} className="mb-2"> |
|
182 |
<div>Kategorie: {props.tag.category}</div> |
|
183 |
<div>Výskyty (části):</div> |
|
184 |
{props.tag.occurrences.map((occurrence, index) => { |
|
185 |
return ( |
|
186 |
<AnnotationOccurrenceItem |
|
187 |
occurrence={occurrence} |
|
188 |
tag={props.tag} |
|
189 |
key={'occ-' + occurrence.occurenceId} |
|
190 |
/> |
|
258 | 191 |
); |
259 | 192 |
})} |
260 | 193 |
</Stack> |
Také k dispozici: Unified diff
Document finalization