Projekt

Obecné

Profil

Stáhnout (13.7 KB) Statistiky
| Větev: | Tag: | Revize:
1
import React, { createContext, useEffect, useState } from 'react';
2
import {
3
    AnnotationInfo,
4
    ETagSentiment,
5
    ETagType,
6
    MarkAnnotationDoneRequest,
7
    TagInstanceInfo,
8
} from '../api';
9
import { Tag } from '../components/types/tag';
10
import { annotationController } from '../controllers';
11
import { GetSelectionInfo } from '../utils/selectionUtils';
12
import { ShowConfirm, ShowConfirmDelete, ShowToast } from '../utils/alerts';
13
import { useRouter } from 'next/router';
14

    
15
/**
16
 * Interface of an annotation context provider.
17
 */
18
interface IAnnotationContextProvider {
19
    /**
20
     * Tags managed by the context.
21
     */
22
    tags: TagInstanceInfo[] | null;
23

    
24
    /**
25
     * Submitting boolean
26
     */
27
    submitting: boolean;
28

    
29
    /**
30
     * Sets new tags.
31
     * @param newTags An array of new tags.
32
     */
33
    setTags: (newTags: TagInstanceInfo[] | null) => void;
34

    
35
    /**
36
     * Adds occurrence to an annotation.
37
     * @param tag Tag whose occurrence should be added.
38
     */
39
    addOccurrence: (tag: Tag) => void;
40

    
41
    /**
42
     * Deletes an occurrence of an annotation.
43
     * @param occurrence Occurrence that should be deleted.
44
     */
45
    deleteOccurrence: (occurrence: TagInstanceInfo) => void;
46

    
47
    /**
48
     * Finishes annotation of document (marked as DONE).
49
     */
50
    finishAnnotation: () => void;
51

    
52
    /**
53
     * Changes a position of an occurrence of an annotation.
54
     * @param occurrence Occurrence whose position should be changed.
55
     * @param newValue New value of the position.
56
     */
57
    changePosition: (occurrence: TagInstanceInfo, newValue: number) => void;
58

    
59
    /**
60
     * Changes a length of an occurrence of an annotation.
61
     * @param occurrence Occurrence whose length should be changed.
62
     * @param newValue New value of the length.
63
     */
64
    changeLength: (occurrence: TagInstanceInfo, newValue: number) => void;
65

    
66
    /**
67
     * Changes a note of an occurrence of an annotation.
68
     * @param occurrence Occurrence whose note should be changed.
69
     * @param newValue New value of the note.
70
     */
71
    changeNote: (occurrence: TagInstanceInfo, newValue: string) => void;
72

    
73
    /**
74
     * Changes sentiment of an annotation.
75
     * @param occurrence Occurrence whose sentiment should be changed.
76
     * @param newValue New value of the sentiment.
77
     */
78
    changeSentiment: (occurrence: TagInstanceInfo, newValue: ETagSentiment) => void;
79

    
80
    makeOccurrenceFinal: (occurrence: TagInstanceInfo) => void;
81

    
82
    annotation: AnnotationInfo | null;
83
    mappedTags: Tag[] | null;
84
    refreshAnnotation: () => void;
85

    
86
    markSelectedText: (
87
        tagId: string,
88
        subtagId: string | null,
89
        instanceID: string | null
90
    ) => void;
91

    
92
    selectedInstanceId: string | null;
93
    setSelectedInstanceId: (newId: string | null) => void;
94

    
95
    selectedOccurrenceId: string | null;
96
    setSelectedOccurrenceId: (newId: string | null) => void;
97

    
98
    isFinal: boolean;
99
}
100

    
101
/**
102
 * The annotation context that manages active annotations.
103
 */
104
export const AnnotationContext = createContext<IAnnotationContextProvider>({
105
    /**
106
     * Default tags.
107
     */
108
    tags: null,
109

    
110
    /**
111
     * Submitting boolean
112
     */
113
    submitting: false,
114

    
115
    /**
116
     * Default implementation of setTags method.
117
     * @param v Array of new tags.
118
     */
119
    setTags: (v) => {
120
        return;
121
    },
122

    
123
    /**
124
     * Default implementation of addOccurrence method.
125
     * @param tag The tag with new occurrence.
126
     */
127
    addOccurrence: (tag: Tag) => {
128
        return;
129
    },
130

    
131
    /**
132
     * Default implementation of deleteOccurrence method.
133
     * @param occurrence Occurrence that should be deleted.
134
     */
135
    deleteOccurrence: (occurrence: TagInstanceInfo) => {
136
        return;
137
    },
138

    
139
    /**
140
     * Finishes annotation of document (marked as DONE).
141
     */
142
    finishAnnotation: () => {
143
        return;
144
    },
145

    
146
    /**
147
     * Default implementation of changePosition method.
148
     * @param occurrence Occurrence whose position should be changed.
149
     * @param newValue A new position.
150
     */
151
    changePosition: (occurrence: TagInstanceInfo, newValue: number) => {
152
        return;
153
    },
154

    
155
    /**
156
     * Default implementation of changeLength method.
157
     * @param occurrence Occurrence whose length should be changed.
158
     * @param newValue A new length.
159
     */
160
    changeLength: (occurrence: TagInstanceInfo, newValue: number) => {
161
        return;
162
    },
163

    
164
    /**
165
     * Changes a note of an occurrence of an annotation.
166
     * @param occurrence Occurrence whose note should be changed.
167
     * @param newValue New value of the note.
168
     */
169
    changeNote: (occurrence: TagInstanceInfo, newValue: string) => {
170
        return;
171
    },
172

    
173
    /**
174
     * Changes sentiment of an annotation.
175
     * @param occurrence Occurrence whose sentiment should be changed.
176
     * @param newValue New value of the sentiment.
177
     */
178
    changeSentiment: (occurrence: TagInstanceInfo, newValue: ETagSentiment) => {
179
        return;
180
    },
181

    
182
    annotation: null,
183
    mappedTags: null,
184
    refreshAnnotation: () => {
185
        return;
186
    },
187

    
188
    markSelectedText: () => {
189
        return;
190
    },
191

    
192
    selectedInstanceId: null,
193
    setSelectedInstanceId: (newId: string | null) => {
194
        return;
195
    },
196

    
197
    selectedOccurrenceId: null,
198
    setSelectedOccurrenceId: (newId: string | null) => {
199
        return;
200
    },
201

    
202
    isFinal: false,
203
    makeOccurrenceFinal: (occurrence: TagInstanceInfo) => {
204
        return;
205
    },
206
});
207

    
208
/**
209
 * Provider of the annotation context.
210
 * @param props Children that should have access to the annotation context.
211
 * @returns Prepared html of the provider.
212
 */
213
const AnnotationProvider = (props: {
214
    children: React.ReactNode;
215
    annotationId: string;
216
    isFinal: boolean;
217
}) => {
218
    const [annotation, setAnnotation] = useState<AnnotationInfo | null>(null);
219

    
220
    /**
221
     * Tags managed by the context.
222
     */
223
    const [tags, setTags] = useState<TagInstanceInfo[] | null>(null);
224

    
225
    const [mappedTags, setMappedTags] = useState<Tag[] | null>(null);
226
    const [selectedInstanceId, setSelectedInstanceId] = useState<string | null>(null);
227
    const [selectedOccurrenceId, setSelectedOccurrenceId] = useState<string | null>(null);
228

    
229
    const [submitting, setSubmitting] = useState(false);
230

    
231
    const router = useRouter();
232

    
233
    async function markSelectedText(
234
        tagId: string,
235
        subtagId: string | null,
236
        instanceId: string | null
237
    ) {
238
        setSubmitting(true);
239
        if (!annotation) {
240
            console.log('annotation not found');
241
            return;
242
        }
243

    
244
        const selectionInfo = GetSelectionInfo(annotation);
245
        if (!selectionInfo) {
246
            console.log(
247
                'not able to continue, selection processing not completed successfully'
248
            );
249
            ShowToast('Není označen žádný text pro přidělení značky', 'warning');
250
            setSubmitting(false);
251
            return;
252
        }
253

    
254
        const id = subtagId ?? tagId;
255
        const type: ETagType = subtagId == null ? ETagType.Tag : ETagType.Subtag;
256
        const res = await annotationController
257
            .annotationAnnotationIdPost(props.annotationId, props.isFinal, {
258
                id: id,
259
                instanceId,
260
                type,
261
                position: selectionInfo.startPositionOriginalDocument,
262
                length: selectionInfo.selectionLengthOriginalDocument,
263
                selectedText: selectionInfo.selectedText,
264
            })
265
            .catch((e) =>
266
                ShowToast('Tato část textu je již touto značkou anotována', 'warning')
267
            );
268

    
269
        await refreshAnnotation();
270
    }
271

    
272
    /**
273
     * Default implementation of addOccurrence method.
274
     * @param tag The tag with new occurrence.
275
     */
276
    const addOccurrence = async (tag: Tag) => {
277
        await markSelectedText(tag.tagId, tag.subtagId ?? null, tag.instanceId);
278
    };
279

    
280
    /**
281
     * Deletes an occurrence of an annotation.
282
     * @param occurrence Occurrence that should be deleted.
283
     */
284
    const deleteOccurrence = async (occurrence: TagInstanceInfo) => {
285
        if (!occurrence.occurenceId) {
286
            console.log('invalid occurrence');
287
            return;
288
        }
289

    
290
        ShowConfirmDelete(() => {
291
            annotationController
292
                .annotationAnnotationIdOccurenceIdDelete(
293
                    props.annotationId,
294
                    occurrence.occurenceId ?? '',
295
                    props.isFinal
296
                )
297
                .then(() => refreshAnnotation());
298
        }, 'značku');
299
    };
300

    
301
    /**
302
     * Finishes annotation of document (marked as DONE).
303
     */
304
    const finishAnnotation = () => {
305
        const req: MarkAnnotationDoneRequest = { done: true };
306
        ShowConfirm(
307
            () =>
308
                annotationController
309
                    .annotationAnnotationIdDonePut(props.annotationId, props.isFinal, req)
310
                    .then(() => {
311
                        if (props.isFinal) {
312
                            router.push('/documents/admin');
313
                        } else {
314
                            router.push('/documents/annotator');
315
                        }
316

    
317
                        ShowToast('Anotování bylo úspěšně dokončeno');
318
                    }),
319
            'dokončit anotování'
320
        );
321
    };
322

    
323
    /**
324
     * Changes a position of an occurrence of an annotation.
325
     * @param occurrence Occurrence whose position should be changed.
326
     * @param newValue New value of the position.
327
     */
328
    const changePosition = (occurrence: TagInstanceInfo, newValue: number) => {
329
        //TODO: Implement method (should use objects from server API)
330
    };
331

    
332
    /**
333
     * Changes a length of an occurrence of an annotation.
334
     * @param occurrence Occurrence whose length should be changed.
335
     * @param newValue New value of the length.
336
     */
337
    const changeLength = (occurrence: TagInstanceInfo, newValue: number) => {
338
        //TODO: Implement method (should use objects from server API)
339
    };
340

    
341
    /**
342
     * Changes a note of an occurrence of an annotation.
343
     * @param occurrence Occurrence whose note should be changed.
344
     * @param newValue New value of the note.
345
     */
346
    const changeNote = async (occurrence: TagInstanceInfo, newValue: string) => {
347
        if (!occurrence.occurenceId) {
348
            console.log('invalid occurrence');
349
            return;
350
        }
351

    
352
        const postRes =
353
            await annotationController.annotationAnnotationIdTagOccurenceIdNotePost(
354
                props.annotationId,
355
                occurrence.occurenceId,
356
                props.isFinal,
357
                { note: newValue }
358
            );
359

    
360
        occurrence.note = newValue;
361
    };
362

    
363
    /**
364
     * Changes sentiment of an annotation.
365
     * @param occurrence Occurrence whose sentiment should be changed.
366
     * @param newValue New value of the sentiment.
367
     */
368
    const changeSentiment = async (
369
        occurrence: TagInstanceInfo,
370
        newValue: ETagSentiment
371
    ) => {
372
        if (!occurrence.instance) {
373
            console.log('invalid instance');
374
            return;
375
        }
376

    
377
        const putRes =
378
            await annotationController.annotationAnnotationIdInstanceIdSentimentPut(
379
                props.annotationId,
380
                occurrence.instance,
381
                props.isFinal,
382
                { sentiment: newValue }
383
            );
384

    
385
        occurrence.sentiment = newValue;
386
    };
387

    
388
    const remapAnnotations = (data: AnnotationInfo) => {
389
        let map = new Map<string, Tag>();
390
        data.tagInstances?.forEach((tagInstance) => {
391
            if (map.has(tagInstance.instance ?? '-')) {
392
                let tag = map.get(tagInstance.instance ?? '-');
393
                tag!.occurrences = [...tag!.occurrences, tagInstance];
394
            } else {
395
                map.set(tagInstance.instance ?? '-', {
396
                    tagName: tagInstance.tagName ?? '',
397
                    subtagName: tagInstance.subTagName ?? null,
398
                    category: tagInstance.tagCategoryName ?? '',
399
                    occurrences: [tagInstance],
400
                    tagId: tagInstance.tagId ?? '',
401
                    instanceId: tagInstance.instance ?? '',
402
                    subtagId: tagInstance.subTagId ?? null,
403
                });
404
            }
405
        });
406

    
407
        setMappedTags(Array.from(map.values()));
408
    };
409

    
410
    async function makeOccurrenceFinal(occurrence: TagInstanceInfo) {
411
        if (!occurrence?.occurenceId) {
412
            return;
413
        }
414

    
415
        await annotationController.annotationAnnotationIdOccurenceIdFinalPut(
416
            props.annotationId,
417
            occurrence.occurenceId,
418
            { isFinal: true }
419
        );
420

    
421
        await refreshAnnotation();
422
    }
423

    
424
    async function refreshAnnotation() {
425
        const data = await annotationController.annotationAnnotationIdGet(
426
            props.annotationId,
427
            props.isFinal
428
        );
429

    
430
        remapAnnotations(data.data);
431
        setAnnotation(data.data ?? null);
432
        setSubmitting(false);
433
    }
434

    
435
    /**
436
     * Initializes the context.
437
     */
438
    useEffect(() => {
439
        refreshAnnotation();
440
    }, [props.annotationId]);
441

    
442
    return (
443
        <AnnotationContext.Provider
444
            value={{
445
                tags,
446
                submitting,
447
                setTags,
448
                addOccurrence,
449
                deleteOccurrence,
450
                finishAnnotation,
451
                changeLength,
452
                changePosition,
453
                changeNote,
454
                changeSentiment,
455
                refreshAnnotation,
456
                annotation,
457
                mappedTags,
458
                markSelectedText,
459
                selectedInstanceId,
460
                setSelectedInstanceId,
461
                selectedOccurrenceId,
462
                setSelectedOccurrenceId,
463
                isFinal: props.isFinal,
464
                makeOccurrenceFinal,
465
            }}
466
        >
467
            {props.children}
468
        </AnnotationContext.Provider>
469
    );
470
};
471

    
472
export default AnnotationProvider;
(1-1/3)