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) => 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) => {
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(occurrence: TagInstanceInfo) {
429
        if (!occurrence?.occurenceId) {
430
            return;
431
        }
432

    
433
        await annotationController.annotationAnnotationIdOccurenceIdFinalPut(
434
            props.annotationId,
435
            occurrence.occurenceId,
436
            { isFinal: true }
437
        );
438

    
439
        await refreshAnnotation();
440
    }
441

    
442
    async function refreshAnnotation() {
443
        const data = await annotationController.annotationAnnotationIdGet(
444
            props.annotationId,
445
            props.isFinal
446
        );
447

    
448
        remapAnnotations(data.data);
449
        setAnnotation(data.data ?? null);
450
        setSubmitting(false);
451
    }
452

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

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

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