Projekt

Obecné

Profil

Stáhnout (14.2 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
    changeDocumentNote: (newValue: string) => void;
67

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

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

    
82
    makeOccurrenceFinal: (occurrence: TagInstanceInfo, final: boolean) => void;
83

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

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

    
94
    selectedInstanceId: string | null;
95
    setSelectedInstanceId: (newId: string | null) => void;
96

    
97
    selectedOccurrenceId: string | null;
98
    setSelectedOccurrenceId: (newId: string | null) => void;
99

    
100
    isFinal: boolean;
101
}
102

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

    
112
    /**
113
     * Submitting boolean
114
     */
115
    submitting: false,
116

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

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

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

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

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

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

    
166
    changeDocumentNote: (newValue: string) => {
167
        return;
168
    },
169

    
170
    /**
171
     * Changes a note of an occurrence of an annotation.
172
     * @param occurrence Occurrence whose note should be changed.
173
     * @param newValue New value of the note.
174
     */
175
    changeNote: (occurrence: TagInstanceInfo, newValue: string) => {
176
        return;
177
    },
178

    
179
    /**
180
     * Changes sentiment of an annotation.
181
     * @param occurrence Occurrence whose sentiment should be changed.
182
     * @param newValue New value of the sentiment.
183
     */
184
    changeSentiment: (occurrence: TagInstanceInfo, newValue: ETagSentiment) => {
185
        return;
186
    },
187

    
188
    annotation: null,
189
    mappedTags: null,
190
    refreshAnnotation: () => {
191
        return;
192
    },
193

    
194
    markSelectedText: () => {
195
        return;
196
    },
197

    
198
    selectedInstanceId: null,
199
    setSelectedInstanceId: (newId: string | null) => {
200
        return;
201
    },
202

    
203
    selectedOccurrenceId: null,
204
    setSelectedOccurrenceId: (newId: string | null) => {
205
        return;
206
    },
207

    
208
    isFinal: false,
209
    makeOccurrenceFinal: (occurrence: TagInstanceInfo, final: boolean) => {
210
        return;
211
    },
212
});
213

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

    
226
    /**
227
     * Tags managed by the context.
228
     */
229
    const [tags, setTags] = useState<TagInstanceInfo[] | null>(null);
230

    
231
    const [mappedTags, setMappedTags] = useState<Tag[] | null>(null);
232
    const [selectedInstanceId, setSelectedInstanceId] = useState<string | null>(null);
233
    const [selectedOccurrenceId, setSelectedOccurrenceId] = useState<string | null>(null);
234

    
235
    const [submitting, setSubmitting] = useState(false);
236

    
237
    const router = useRouter();
238

    
239
    async function markSelectedText(
240
        tagId: string,
241
        subtagId: string | null,
242
        instanceId: string | null
243
    ) {
244
        setSubmitting(true);
245
        if (!annotation) {
246
            console.log('annotation not found');
247
            return;
248
        }
249

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

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

    
275
        await refreshAnnotation();
276
    }
277

    
278
    /**
279
     * Default implementation of addOccurrence method.
280
     * @param tag The tag with new occurrence.
281
     */
282
    const addOccurrence = async (tag: Tag) => {
283
        await markSelectedText(tag.tagId, tag.subtagId ?? null, tag.instanceId);
284
    };
285

    
286
    /**
287
     * Deletes an occurrence of an annotation.
288
     * @param occurrence Occurrence that should be deleted.
289
     */
290
    const deleteOccurrence = async (occurrence: TagInstanceInfo) => {
291
        if (!occurrence.occurenceId) {
292
            console.log('invalid occurrence');
293
            return;
294
        }
295

    
296
        ShowConfirmDelete(() => {
297
            annotationController
298
                .annotationAnnotationIdOccurenceIdDelete(
299
                    props.annotationId,
300
                    occurrence.occurenceId ?? '',
301
                    props.isFinal
302
                )
303
                .then(() => refreshAnnotation());
304
        }, 'značku');
305
    };
306

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

    
323
                        ShowToast('Anotování bylo úspěšně dokončeno');
324
                    }),
325
            'dokončit anotování'
326
        );
327
    };
328

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

    
338
    /**
339
     * Changes a length of an occurrence of an annotation.
340
     * @param occurrence Occurrence whose length should be changed.
341
     * @param newValue New value of the length.
342
     */
343
    const changeLength = (occurrence: TagInstanceInfo, newValue: number) => {
344
        //TODO: Implement method (should use objects from server API)
345
    };
346

    
347
    const changeDocumentNote = async (newValue: string) => {
348
        const posRes = await annotationController.annotationAnnotationIdNotePost(
349
            props.annotationId,
350
            props.isFinal,
351
            { note: newValue }
352
        );
353

    
354
        if (annotation) {
355
            annotation.note = newValue;
356
        }
357
    };
358

    
359
    /**
360
     * Changes a note of an occurrence of an annotation.
361
     * @param occurrence Occurrence whose note should be changed.
362
     * @param newValue New value of the note.
363
     */
364
    const changeNote = async (occurrence: TagInstanceInfo, newValue: string) => {
365
        if (!occurrence.occurenceId) {
366
            console.log('invalid occurrence');
367
            return;
368
        }
369

    
370
        const postRes =
371
            await annotationController.annotationAnnotationIdTagOccurenceIdNotePost(
372
                props.annotationId,
373
                occurrence.occurenceId,
374
                props.isFinal,
375
                { note: newValue }
376
            );
377

    
378
        occurrence.note = newValue;
379
    };
380

    
381
    /**
382
     * Changes sentiment of an annotation.
383
     * @param occurrence Occurrence whose sentiment should be changed.
384
     * @param newValue New value of the sentiment.
385
     */
386
    const changeSentiment = async (
387
        occurrence: TagInstanceInfo,
388
        newValue: ETagSentiment
389
    ) => {
390
        if (!occurrence.instance) {
391
            console.log('invalid instance');
392
            return;
393
        }
394

    
395
        const putRes =
396
            await annotationController.annotationAnnotationIdInstanceIdSentimentPut(
397
                props.annotationId,
398
                occurrence.instance,
399
                props.isFinal,
400
                { sentiment: newValue }
401
            );
402

    
403
        occurrence.sentiment = newValue;
404
    };
405

    
406
    const remapAnnotations = (data: AnnotationInfo) => {
407
        let map = new Map<string, Tag>();
408
        data.tagInstances?.forEach((tagInstance) => {
409
            if (map.has(tagInstance.instance ?? '-')) {
410
                let tag = map.get(tagInstance.instance ?? '-');
411
                tag!.occurrences = [...tag!.occurrences, tagInstance];
412
            } else {
413
                map.set(tagInstance.instance ?? '-', {
414
                    tagName: tagInstance.tagName ?? '',
415
                    subtagName: tagInstance.subTagName ?? null,
416
                    category: tagInstance.tagCategoryName ?? '',
417
                    occurrences: [tagInstance],
418
                    tagId: tagInstance.tagId ?? '',
419
                    instanceId: tagInstance.instance ?? '',
420
                    subtagId: tagInstance.subTagId ?? null,
421
                });
422
            }
423
        });
424

    
425
        setMappedTags(Array.from(map.values()));
426
    };
427

    
428
    async function makeOccurrenceFinal(
429
        occurrence: TagInstanceInfo,
430
        final: boolean = true
431
    ) {
432
        if (!occurrence?.occurenceId) {
433
            return;
434
        }
435

    
436
        await annotationController.annotationAnnotationIdOccurenceIdFinalPut(
437
            props.annotationId,
438
            occurrence.occurenceId,
439
            { isFinal: final }
440
        );
441

    
442
        await refreshAnnotation();
443
    }
444

    
445
    async function refreshAnnotation() {
446
        const data = await annotationController.annotationAnnotationIdGet(
447
            props.annotationId,
448
            props.isFinal
449
        );
450

    
451
        remapAnnotations(data.data);
452
        setAnnotation(data.data ?? null);
453
        setSubmitting(false);
454
    }
455

    
456
    /**
457
     * Initializes the context.
458
     */
459
    useEffect(() => {
460
        refreshAnnotation();
461
    }, [props.annotationId]);
462

    
463
    return (
464
        <AnnotationContext.Provider
465
            value={{
466
                tags,
467
                submitting,
468
                setTags,
469
                addOccurrence,
470
                deleteOccurrence,
471
                finishAnnotation,
472
                changeLength,
473
                changePosition,
474
                changeDocumentNote,
475
                changeNote,
476
                changeSentiment,
477
                refreshAnnotation,
478
                annotation,
479
                mappedTags,
480
                markSelectedText,
481
                selectedInstanceId,
482
                setSelectedInstanceId,
483
                selectedOccurrenceId,
484
                setSelectedOccurrenceId,
485
                isFinal: props.isFinal,
486
                makeOccurrenceFinal,
487
            }}
488
        >
489
            {props.children}
490
        </AnnotationContext.Provider>
491
    );
492
};
493

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