@konfuzio/document-validation-ui 0.1.19-dev.1 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/.eslintrc.js +10 -10
  2. package/.prettierrc.json +1 -1
  3. package/LICENSE +21 -21
  4. package/README.md +24 -24
  5. package/cypress.config.js +13 -13
  6. package/dist/css/app.css +1 -1
  7. package/dist/index.html +1 -1
  8. package/dist/js/app.js +1 -1
  9. package/dist/js/app.js.map +1 -1
  10. package/dist/js/chunk-vendors.js +1 -1
  11. package/dist/js/chunk-vendors.js.map +1 -1
  12. package/jest.config.js +4 -4
  13. package/package.json +66 -66
  14. package/src/api.js +82 -82
  15. package/src/assets/images/AcceptedCheckMark.vue +8 -8
  16. package/src/assets/images/AcceptedUser.vue +8 -8
  17. package/src/assets/images/ActionIcon.vue +60 -60
  18. package/src/assets/images/ArrowDownKey.vue +11 -11
  19. package/src/assets/images/ArrowUpKey.vue +11 -11
  20. package/src/assets/images/CategoryIconImg.vue +13 -13
  21. package/src/assets/images/CheckMark.vue +8 -8
  22. package/src/assets/images/DraggableIcon.vue +14 -14
  23. package/src/assets/images/EditDocIcon.vue +12 -12
  24. package/src/assets/images/EmptyStateImg.vue +129 -129
  25. package/src/assets/images/ErrorIcon.vue +28 -28
  26. package/src/assets/images/EyeIcon.vue +11 -11
  27. package/src/assets/images/FileNameNotSavedImage.vue +26 -26
  28. package/src/assets/images/FileNameSavedImage.vue +14 -14
  29. package/src/assets/images/FitZoomIcon.vue +16 -16
  30. package/src/assets/images/GridIcon.vue +16 -16
  31. package/src/assets/images/KeyboardIcon.vue +16 -16
  32. package/src/assets/images/MagicWandIcon.vue +16 -16
  33. package/src/assets/images/MinusIcon.vue +13 -13
  34. package/src/assets/images/NotFoundIcon.vue +16 -16
  35. package/src/assets/images/NotOptimizedIllustration.vue +651 -651
  36. package/src/assets/images/PlusIcon.vue +13 -13
  37. package/src/assets/images/QuestionMark.vue +12 -12
  38. package/src/assets/images/ServerImage.vue +73 -73
  39. package/src/assets/images/SettingsIcon.vue +14 -14
  40. package/src/assets/images/SplitLines.vue +18 -18
  41. package/src/assets/images/SplitZigZag.vue +49 -49
  42. package/src/assets/images/StarIcon.vue +16 -16
  43. package/src/assets/images/StatusImg.vue +14 -14
  44. package/src/assets/images/TranslateArrows.vue +33 -33
  45. package/src/assets/scss/ann_set_table_options.scss +26 -26
  46. package/src/assets/scss/annotation_details.scss +141 -141
  47. package/src/assets/scss/choose_label_set_modal.scss +65 -65
  48. package/src/assets/scss/document_action_bar.scss +37 -37
  49. package/src/assets/scss/document_annotations.scss +558 -537
  50. package/src/assets/scss/document_category.scss +85 -85
  51. package/src/assets/scss/document_dashboard.scss +52 -52
  52. package/src/assets/scss/document_edit.scss +410 -410
  53. package/src/assets/scss/document_error.scss +81 -81
  54. package/src/assets/scss/document_name.scss +60 -60
  55. package/src/assets/scss/document_page.scss +12 -12
  56. package/src/assets/scss/document_thumbnails.scss +41 -41
  57. package/src/assets/scss/document_toolbar.scss +111 -111
  58. package/src/assets/scss/document_top_bar.scss +171 -171
  59. package/src/assets/scss/document_viewport_modal.scss +25 -25
  60. package/src/assets/scss/documents_list.scss +141 -141
  61. package/src/assets/scss/edit_page_thumbnail.scss +53 -53
  62. package/src/assets/scss/empty_state.scss +34 -34
  63. package/src/assets/scss/extracting_data.scss +35 -35
  64. package/src/assets/scss/imports.scss +1 -1
  65. package/src/assets/scss/multi_ann_table_overlay.scss +38 -38
  66. package/src/assets/scss/multi_ann_table_popup.scss +12 -12
  67. package/src/assets/scss/new_annotation.scss +102 -102
  68. package/src/assets/scss/scrolling_document.scss +19 -19
  69. package/src/assets/scss/theme.scss +801 -801
  70. package/src/assets/scss/variables.scss +66 -66
  71. package/src/components/App.cy.js +7 -7
  72. package/src/components/App.vue +187 -187
  73. package/src/components/DocumentAnnotations/AnnotationActionButtons.vue +152 -168
  74. package/src/components/DocumentAnnotations/AnnotationContent.vue +210 -210
  75. package/src/components/DocumentAnnotations/AnnotationDetails.vue +251 -251
  76. package/src/components/DocumentAnnotations/AnnotationRow.vue +752 -752
  77. package/src/components/DocumentAnnotations/AnnotationSetActionButtons.vue +89 -89
  78. package/src/components/DocumentAnnotations/ChooseLabelSetModal.vue +186 -186
  79. package/src/components/DocumentAnnotations/DocumentAnnotations.cy.js +441 -441
  80. package/src/components/DocumentAnnotations/DocumentAnnotations.vue +534 -534
  81. package/src/components/DocumentAnnotations/DocumentLabel.vue +189 -189
  82. package/src/components/DocumentAnnotations/EmptyAnnotation.vue +193 -193
  83. package/src/components/DocumentAnnotations/EmptyState.vue +21 -21
  84. package/src/components/DocumentAnnotations/ExtractingData.vue +41 -41
  85. package/src/components/DocumentAnnotations/LoadingAnnotations.vue +43 -43
  86. package/src/components/DocumentAnnotations/LoadingLabels.vue +43 -43
  87. package/src/components/DocumentAnnotations/MultiAnnotationTableOverlay.vue +338 -338
  88. package/src/components/DocumentAnnotations/index.js +8 -8
  89. package/src/components/DocumentCategory.vue +281 -281
  90. package/src/components/DocumentDashboard.vue +170 -170
  91. package/src/components/DocumentEdit/DocumentEdit.cy.js +541 -541
  92. package/src/components/DocumentEdit/DocumentEdit.vue +503 -503
  93. package/src/components/DocumentEdit/EditConfirmationModal.vue +55 -55
  94. package/src/components/DocumentEdit/EditPageThumbnail.vue +114 -114
  95. package/src/components/DocumentEdit/EditPages.vue +161 -161
  96. package/src/components/DocumentEdit/EditSidebar.vue +154 -154
  97. package/src/components/DocumentEdit/RenameAndCategorize.vue +184 -184
  98. package/src/components/DocumentEdit/SidebarButtons.vue +53 -53
  99. package/src/components/DocumentEdit/SplitInfoBar.vue +21 -21
  100. package/src/components/DocumentEdit/index.js +4 -4
  101. package/src/components/DocumentModals/DocumentErrorModal.vue +58 -58
  102. package/src/components/DocumentModals/NotOptimizedViewportModal.vue +51 -51
  103. package/src/components/DocumentPage/ActionBar.vue +48 -48
  104. package/src/components/DocumentPage/AnnSetTableOptions.vue +111 -111
  105. package/src/components/DocumentPage/BoxSelection.vue +152 -152
  106. package/src/components/DocumentPage/DocumentPage.cy.js +92 -92
  107. package/src/components/DocumentPage/DocumentPage.vue +568 -568
  108. package/src/components/DocumentPage/DocumentToolbar.cy.js +215 -215
  109. package/src/components/DocumentPage/DocumentToolbar.vue +228 -228
  110. package/src/components/DocumentPage/DummyPage.vue +55 -55
  111. package/src/components/DocumentPage/MultiAnnSelection.vue +371 -371
  112. package/src/components/DocumentPage/NewAnnotation.vue +308 -308
  113. package/src/components/DocumentPage/ScrollingDocument.vue +149 -149
  114. package/src/components/DocumentPage/ScrollingPage.vue +179 -179
  115. package/src/components/DocumentPage/index.js +5 -5
  116. package/src/components/DocumentThumbnails/DocumentThumbnails.cy.js +67 -67
  117. package/src/components/DocumentThumbnails/DocumentThumbnails.vue +132 -132
  118. package/src/components/DocumentThumbnails/LoadingThumbnail.vue +25 -25
  119. package/src/components/DocumentThumbnails/index.js +1 -1
  120. package/src/components/DocumentTopBar/DocumentName.vue +236 -236
  121. package/src/components/DocumentTopBar/DocumentTopBar.cy.js +222 -222
  122. package/src/components/DocumentTopBar/DocumentTopBar.vue +202 -202
  123. package/src/components/DocumentTopBar/DocumentTopBarButtons.vue +183 -183
  124. package/src/components/DocumentTopBar/KeyboardActionsDescription.vue +74 -74
  125. package/src/components/DocumentTopBar/index.js +3 -3
  126. package/src/components/DocumentsList/DocumentsList.vue +121 -121
  127. package/src/components/DocumentsList/index.js +1 -1
  128. package/src/components/ErrorMessage.vue +40 -40
  129. package/src/components/index.js +1 -1
  130. package/src/constants.js +5 -5
  131. package/src/directives/scroll.js +28 -28
  132. package/src/i18n.js +22 -22
  133. package/src/icons.js +45 -45
  134. package/src/locales/de.json +148 -148
  135. package/src/locales/en.json +148 -148
  136. package/src/main.js +26 -26
  137. package/src/store/category.js +191 -191
  138. package/src/store/display.js +311 -311
  139. package/src/store/document.js +1438 -1438
  140. package/src/store/edit.js +316 -316
  141. package/src/store/index.js +21 -21
  142. package/src/store/project.js +143 -143
  143. package/src/store/selection.js +210 -210
  144. package/src/utils/utils.js +54 -54
  145. package/vue.config.js +25 -25
@@ -1,752 +1,752 @@
1
- <template>
2
- <div
3
- :class="[
4
- 'annotation-row',
5
- isSelected && 'selected',
6
- hoverEmptyLabelRows && 'hovered-empty-labels',
7
- hoverPendingAnnotationRows && 'hovered-pending-annotations',
8
- annotationIsNotFound(annotationSet, label) && 'missing',
9
- isAnnotationInEditMode(annotationId()) && 'editing',
10
- publicView && 'clickable-cursor',
11
- ]"
12
- @click="onAnnotationClick"
13
- @mouseover="hoveredAnnotation = annotationId()"
14
- @mouseleave="hoveredAnnotation = null"
15
- >
16
- <div
17
- class="annotation-row-left"
18
- @mouseenter="onAnnotationHoverEnter(defaultSpan)"
19
- @mouseleave="onAnnotationHoverLeave"
20
- >
21
- <div class="annotation-icon">
22
- <AnnotationDetails
23
- :description="label.description"
24
- :annotation="annotation"
25
- :annotation-set="annotationSet"
26
- :label="label"
27
- :from-table="fromTable"
28
- />
29
- </div>
30
-
31
- <div
32
- v-if="showLabel"
33
- :class="[
34
- 'label-name',
35
- annotationIsNotFound(annotationSet, label) && 'not-found-text',
36
- ]"
37
- >
38
- <span>{{ label.name }} </span>
39
- </div>
40
-
41
- <div
42
- v-if="showTranslationsDetails"
43
- :class="['annotation-translation', !isDocumentReviewed && 'pointer']"
44
- @click="editAnnotationTranslation(annotation.id)"
45
- >
46
- <b-tooltip :animated="false" position="is-bottom">
47
- <div class="icon">
48
- <TranslateArrows :translation="annotation.translated_string" />
49
- </div>
50
-
51
- <template #content>
52
- <div class="translation-details">
53
- <div class="translation-title">
54
- <span>{{ $t("translated_string_title") }}</span>
55
- <span
56
- :class="[
57
- 'translated-string',
58
- !annotation.translated_string && 'no-translation',
59
- ]"
60
- >
61
- {{
62
- annotation.translated_string
63
- ? annotation.translated_string
64
- : $t("no_translated_string")
65
- }}
66
- </span>
67
- </div>
68
- </div>
69
- <div class="translation-info">
70
- <div v-if="!isDocumentReviewed" class="annotation-details-link">
71
- {{
72
- annotation.translated_string
73
- ? $t("edit_translation")
74
- : $t("add_translation")
75
- }}
76
- </div>
77
- </div>
78
- </template>
79
- </b-tooltip>
80
- </div>
81
- </div>
82
- <div class="annotation-row-right">
83
- <div class="annotation-content">
84
- <div v-if="annotation && !isNegative(annotation)">
85
- <div
86
- v-for="(span, index) in spanForEditing
87
- ? spanSelection
88
- : annotation.span"
89
- :key="index"
90
- @mouseenter="onAnnotationHoverEnter(span)"
91
- @mouseleave="onAnnotationHoverLeave"
92
- >
93
- <AnnotationContent
94
- :ref="`span_${annotation.id}_${index}`"
95
- :annotation="annotation"
96
- :span="span"
97
- :span-index="index"
98
- :label="label"
99
- :annotation-set="annotationSet"
100
- :is-hovered="hoveredAnnotation"
101
- @save-annotation-changes="handleSaveChanges"
102
- />
103
- </div>
104
- </div>
105
- <div v-else>
106
- <div v-if="spanSelection && isAnnotationInEditMode(annotationId())">
107
- <EmptyAnnotation
108
- v-for="(span, index) in spanSelection"
109
- :key="index"
110
- :span="span"
111
- :span-index="index"
112
- :label="label"
113
- :annotation-set="annotationSet"
114
- :is-hovered="hoveredAnnotation"
115
- :is-missing-annotation="
116
- annotationIsNotFound(annotationSet, label)
117
- "
118
- @save-empty-annotation-changes="saveEmptyAnnotationChanges"
119
- />
120
- </div>
121
- <EmptyAnnotation
122
- v-else-if="!fromTable"
123
- :label="label"
124
- :annotation-set="annotationSet"
125
- :is-hovered="hoveredAnnotation"
126
- :is-missing-annotation="annotationIsNotFound(annotationSet, label)"
127
- @save-empty-annotation-changes="saveEmptyAnnotationChanges"
128
- />
129
- </div>
130
- </div>
131
- <div class="buttons-container">
132
- <AnnotationActionButtons
133
- :cancel-btn="showCancelButton()"
134
- :accept-btn="showAcceptButton()"
135
- :decline-btn="showDeclineButton()"
136
- :show-missing-btn="showMissingButton()"
137
- :save-btn="showSaveButton()"
138
- :restore-btn="showRestoreButton()"
139
- :is-loading="isLoading"
140
- @mark-as-missing="handleMissingAnnotation()"
141
- @save="handleSaveChanges()"
142
- @accept="handleSaveChanges()"
143
- @decline="handleSaveChanges(true)"
144
- @cancel="handleCancelButton()"
145
- @restore="handleRestore()"
146
- />
147
- </div>
148
- </div>
149
- </div>
150
- </template>
151
- <script>
152
- import { mapGetters, mapState } from "vuex";
153
- import AnnotationDetails from "./AnnotationDetails";
154
- import AnnotationContent from "./AnnotationContent";
155
- import EmptyAnnotation from "./EmptyAnnotation";
156
- import AnnotationActionButtons from "./AnnotationActionButtons";
157
- import TranslateArrows from "../../assets/images/TranslateArrows";
158
-
159
- import { isElementArray } from "../../utils/utils";
160
- import api from "../../api";
161
-
162
- export default {
163
- name: "AnnotationRow",
164
- components: {
165
- AnnotationDetails,
166
- AnnotationContent,
167
- EmptyAnnotation,
168
- AnnotationActionButtons,
169
- TranslateArrows,
170
- },
171
- props: {
172
- annotationSet: {
173
- type: Object,
174
- required: true,
175
- },
176
- label: {
177
- type: Object,
178
- required: true,
179
- },
180
- annotation: {
181
- type: Object,
182
- default: null,
183
- },
184
- showLabel: {
185
- type: Boolean,
186
- default: true,
187
- },
188
- fromTable: {
189
- type: Boolean,
190
- default: false,
191
- },
192
- },
193
- data() {
194
- return {
195
- isLoading: false,
196
- isSelected: false,
197
- annotationAnimationTimeout: null,
198
- hoveredAnnotation: null,
199
- };
200
- },
201
- computed: {
202
- ...mapState("document", [
203
- "editAnnotation",
204
- "sidebarAnnotationSelected",
205
- "hoveredAnnotationSet",
206
- "enableGroupingFeature",
207
- "publicView",
208
- "newAcceptedAnnotations",
209
- "annotationsMarkedAsMissing",
210
- "documentId",
211
- "showActionError",
212
- "missingAnnotations",
213
- ]),
214
- ...mapState("selection", [
215
- "spanSelection",
216
- "elementSelected",
217
- "selectedEntities",
218
- ]),
219
- ...mapState("project", ["translationsEnabled"]),
220
- ...mapGetters("document", [
221
- "isAnnotationInEditMode",
222
- "annotationIsNotFound",
223
- "isDocumentReviewed",
224
- "isNegative",
225
- ]),
226
- defaultSpan() {
227
- if (
228
- this.annotation &&
229
- !this.isNegative(this.annotation) &&
230
- this.annotation.span &&
231
- this.annotation.span.length > 0
232
- ) {
233
- return this.annotation.span[0];
234
- }
235
- return null;
236
- },
237
- spanForEditing() {
238
- return (
239
- this.spanSelection &&
240
- isElementArray(this.spanSelection) &&
241
- this.isAnnotationInEditMode(this.annotationId())
242
- );
243
- },
244
- isAnnotation() {
245
- return (
246
- this.annotation &&
247
- !this.isNegative(this.annotation) &&
248
- this.isAnnotationInEditMode(
249
- this.annotationId(),
250
- this.editAnnotation.index
251
- )
252
- );
253
- },
254
- hoverEmptyLabelRows() {
255
- return (
256
- this.hoveredAnnotationSet &&
257
- this.hoveredAnnotationSet.type == "missing" &&
258
- !this.annotationIsNotFound(this.annotationSet, this.label) &&
259
- this.annotationSet.id === this.hoveredAnnotationSet.annotationSet.id &&
260
- this.annotationSet.label_set.id ===
261
- this.hoveredAnnotationSet.annotationSet.label_set.id &&
262
- this.hoveredEmptyLabels() === this.label.id
263
- );
264
- },
265
- hoverPendingAnnotationRows() {
266
- return (
267
- this.hoveredAnnotationSet &&
268
- this.hoveredAnnotationSet.type == "accept" &&
269
- this.annotation &&
270
- !this.isNegative(this.annotation) &&
271
- this.hoveredNotCorrectAnnotations() === this.annotation.id
272
- );
273
- },
274
- showTranslationsDetails() {
275
- // Only show translation option for filled annotations and if the feature is enabled for the project
276
- return (
277
- this.annotation &&
278
- !this.isNegative(this.annotation) &&
279
- this.translationsEnabled &&
280
- !this.publicView
281
- );
282
- },
283
- },
284
- watch: {
285
- sidebarAnnotationSelected(newSidebarAnnotationSelected) {
286
- if (!newSidebarAnnotationSelected) return;
287
-
288
- let annotationSelected;
289
-
290
- if (newSidebarAnnotationSelected.annotation) {
291
- annotationSelected = newSidebarAnnotationSelected.annotation;
292
- } else {
293
- annotationSelected = newSidebarAnnotationSelected;
294
- }
295
-
296
- if (
297
- this.annotation &&
298
- !this.isNegative(this.annotation) &&
299
- this.annotation.id === annotationSelected.id
300
- ) {
301
- clearTimeout(this.annotationAnimationTimeout);
302
-
303
- let timeout;
304
-
305
- if (!newSidebarAnnotationSelected.trigger) {
306
- timeout = 600;
307
- } else {
308
- timeout = 1200;
309
- }
310
-
311
- this.isSelected = true;
312
- // remove annotation selection after some time
313
- this.annotationAnimationTimeout = setTimeout(() => {
314
- this.$store.dispatch("document/setSidebarAnnotationSelected", null);
315
- this.isSelected = false;
316
- }, timeout);
317
-
318
- // Check if sidebarAnnotationSelected changed from a click or hover
319
- if (newSidebarAnnotationSelected.trigger === "click") {
320
- const runAnimation = () => {
321
- this.$el.scrollIntoView({
322
- behavior: "smooth",
323
- block: "center",
324
- inline: "nearest",
325
- });
326
- };
327
- runAnimation();
328
- }
329
- }
330
- },
331
- editAnnotation(newValue) {
332
- if (!newValue) {
333
- this.isLoading = false;
334
- }
335
- },
336
- newAcceptedAnnotations(newValue) {
337
- if (newValue) {
338
- this.enableLoading(newValue);
339
- } else {
340
- this.isLoading = false;
341
- }
342
- },
343
- annotationsMarkedAsMissing(newValue) {
344
- if (newValue) {
345
- this.enableLoading();
346
- } else {
347
- this.isLoading = false;
348
- }
349
- },
350
- showActionError(newValue) {
351
- if (newValue) {
352
- this.isLoading = false;
353
- }
354
- },
355
- selectedEntities(newValue) {
356
- if (!newValue) return;
357
-
358
- if (this.isAnnotationInEditMode(this.annotationId())) {
359
- this.isLoading = true;
360
- }
361
- },
362
- spanSelection(newValue) {
363
- // check if spanSelection has new value from entity selection
364
- // to stop loading after the text appears in the field
365
- if (newValue) {
366
- this.isLoading = false;
367
- }
368
- },
369
- },
370
- methods: {
371
- annotationId() {
372
- if (!this.annotationSet || !this.label) return;
373
-
374
- if (
375
- this.annotation &&
376
- this.annotation.id &&
377
- !this.isNegative(this.annotation)
378
- )
379
- return this.annotation.id;
380
-
381
- let emptyAnnotationId;
382
-
383
- if (this.annotationSet.id) {
384
- emptyAnnotationId = `${this.annotationSet.id}_${this.label.id}`;
385
- } else {
386
- emptyAnnotationId = `${this.annotationSet.label_set.id}_${this.label.id}`;
387
- }
388
- return emptyAnnotationId;
389
- },
390
- onAnnotationHoverEnter(span) {
391
- if (span) {
392
- this.$store.dispatch("document/setDocumentAnnotationSelected", {
393
- annotation: this.annotation,
394
- label: this.fromTable ? null : this.label,
395
- span,
396
- scrollTo: false,
397
- });
398
- }
399
- },
400
- onAnnotationHoverLeave() {
401
- this.$store.dispatch("document/disableDocumentAnnotationSelected");
402
- },
403
- onAnnotationClick() {
404
- if (!this.fromTable) {
405
- this.$store.dispatch("display/showAnnSetTable", null);
406
- }
407
- this.$store.dispatch("document/scrollToDocumentAnnotationSelected");
408
- },
409
- hoveredEmptyLabels() {
410
- // This method will change the style of the Empty Annotations in the same Label Set
411
- // when the "mark all as missing" button is hovered
412
- if (!this.hoveredAnnotationSet) return;
413
-
414
- const labels = this.hoveredAnnotationSet.annotationSet.labels.map(
415
- (label) => {
416
- return JSON.parse(JSON.stringify(label));
417
- }
418
- );
419
- const found = labels.find((l) => l.id === this.label.id);
420
- const negativeAnnotations = found.annotations.find((annotation) =>
421
- this.isNegative(annotation)
422
- );
423
-
424
- if ((found && found.annotations.length === 0) || negativeAnnotations)
425
- return found.id;
426
- return null;
427
- },
428
- hoveredNotCorrectAnnotations() {
429
- // This method will change the style of Annotations in the same Label Set
430
- // when the "Accept all" button is hovered
431
- if (!this.hoveredAnnotationSet) return;
432
-
433
- const annotations =
434
- this.hoveredAnnotationSet.annotationSet.labels.flatMap((label) => {
435
- return label.annotations;
436
- });
437
-
438
- // Check if there are no annotations
439
- if (annotations.length === 0) return;
440
-
441
- const found = annotations.find(
442
- (ann) => ann.id === this.annotation.id && !ann.is_correct
443
- );
444
-
445
- if (found) {
446
- return found.id;
447
- } else {
448
- return null;
449
- }
450
- },
451
- showAcceptButton() {
452
- return (
453
- !this.editAnnotation &&
454
- !this.isAnnotationInEditMode(this.annotationId()) &&
455
- this.annotation &&
456
- !this.isNegative(this.annotation) &&
457
- !this.annotation.is_correct &&
458
- this.hoveredAnnotation === this.annotation.id
459
- );
460
- },
461
- showDeclineButton() {
462
- return (
463
- !this.editAnnotation &&
464
- !this.isAnnotationInEditMode(this.annotationId()) &&
465
- this.annotation &&
466
- !this.isNegative(this.annotation) &&
467
- this.hoveredAnnotation === this.annotation.id
468
- );
469
- },
470
- showMissingButton() {
471
- return (
472
- !this.editAnnotation &&
473
- this.hoveredAnnotation &&
474
- !this.isAnnotationInEditMode(this.annotationId()) &&
475
- (!this.annotation || this.isNegative(this.annotation)) &&
476
- !this.annotationIsNotFound(this.annotationSet, this.label)
477
- );
478
- },
479
- showRestoreButton() {
480
- return (
481
- !this.editAnnotation &&
482
- this.hoveredAnnotation &&
483
- !this.isAnnotationInEditMode(this.annotationId()) &&
484
- this.annotationIsNotFound(this.annotationSet, this.label)
485
- );
486
- },
487
- showCancelButton() {
488
- if (!this.editAnnotation || this.isLoading) return;
489
- if (this.isAnnotationInEditMode(this.annotationId())) {
490
- return true;
491
- }
492
- },
493
- showSaveButton() {
494
- if (!this.editAnnotation || this.isLoading) return;
495
-
496
- // Check if it's an Annotation or Empty Annotation
497
- if (this.isAnnotation) {
498
- return true;
499
- } else {
500
- if (!this.isAnnotationInEditMode(this.annotationId())) return;
501
-
502
- return (
503
- this.elementSelected === this.annotationId() &&
504
- this.spanSelection &&
505
- Array.isArray(this.spanSelection)
506
- );
507
- }
508
- },
509
- handleMissingAnnotation() {
510
- if (!this.label || !this.annotationSet) return;
511
-
512
- this.isLoading = true;
513
-
514
- // will emit to the DocumentAnnotations component, where the method is handled
515
- // & dispatched to the store
516
- this.$parent.$emit(
517
- "handle-missing-annotation",
518
- this.label.id,
519
- this.annotationSet.label_set.id,
520
- this.annotationSet.id,
521
- false
522
- );
523
- },
524
- handleSaveChanges(decline) {
525
- if (this.publicView || this.isDocumentReviewed) return;
526
-
527
- // Verify if we are editing a filled or empty annotation
528
- if (
529
- this.annotation &&
530
- !this.isNegative(this.annotation) &&
531
- (this.showAcceptButton() ||
532
- this.showDeclineButton() ||
533
- this.isAnnotationInEditMode(
534
- this.annotationId(),
535
- this.editAnnotation.index
536
- ))
537
- ) {
538
- let spans = [];
539
-
540
- if (!decline) {
541
- Object.keys(this.$refs).forEach((ref) => {
542
- if (ref.includes(`span_${this.annotation.id}`)) {
543
- const refElement = this.$refs[ref][0];
544
- // call child component createSpan method
545
- if (!refElement) return;
546
-
547
- const span = refElement.createSpan();
548
-
549
- // only add span if it's not null (offset_string not empty)
550
- if (span) {
551
- spans.push(span);
552
- }
553
- }
554
- });
555
- }
556
-
557
- this.saveAnnotationChanges(spans, decline);
558
- } else if (
559
- (!this.annotation || this.isNegative(this.annotation)) &&
560
- this.isAnnotationInEditMode(this.annotationId())
561
- ) {
562
- this.saveEmptyAnnotationChanges();
563
- }
564
- },
565
- handleRestore() {
566
- this.isLoading = true;
567
-
568
- const foundItem = this.missingAnnotations.find(
569
- (item) =>
570
- item.annotation_set === this.annotationSet.id &&
571
- item.label === this.label.id &&
572
- item.label_set === this.annotationSet.label_set.id
573
- );
574
-
575
- this.$store
576
- .dispatch("document/deleteMissingAnnotation", foundItem.id)
577
- .catch((error) => {
578
- this.$store.dispatch("document/createErrorMessage", {
579
- error,
580
- serverErrorMessage: this.$t("server_error"),
581
- defaultErrorMessage: this.$t("edit_error"),
582
- });
583
- })
584
- .finally(() => {
585
- this.isLoading = false;
586
- this.closedTag = null;
587
- });
588
- },
589
- saveAnnotationChanges(spans, isToDecline) {
590
- // This function deals with declining Annotations
591
- // or editing an Annotation or a part of it (if multi line)
592
- this.isLoading = true;
593
-
594
- let updatedString; // what will be sent to the API
595
- let storeAction; // if it will be 'delete' or 'patch'
596
-
597
- // Verify if we delete the entire Annotation or a part of the text
598
- if (isToDecline || spans.length === 0) {
599
- storeAction = "document/deleteAnnotation";
600
- } else {
601
- // Editing the Annotation
602
- // Deleting part of multi-line Annotation
603
- storeAction = "document/updateAnnotation";
604
-
605
- updatedString = {
606
- is_correct: true,
607
- revised: true,
608
- span: spans,
609
- };
610
- }
611
-
612
- // Send to the store for the http patch/delete request
613
- this.$store
614
- .dispatch(storeAction, {
615
- updatedValues: updatedString,
616
- annotationId: this.annotation.id,
617
- annotationSet: this.annotationSet,
618
- })
619
- .catch((error) => {
620
- this.$store.dispatch("document/createErrorMessage", {
621
- error,
622
- serverErrorMessage: this.$t("server_error"),
623
- defaultErrorMessage: this.$t("edit_error"),
624
- });
625
- })
626
- .finally(() => {
627
- this.$store.dispatch("document/resetEditAnnotation");
628
- this.$store.dispatch("selection/disableSelection");
629
- this.$store.dispatch("selection/setSelectedEntities", null);
630
- });
631
- },
632
- saveEmptyAnnotationChanges() {
633
- let annotationToCreate;
634
-
635
- if (this.annotationSet.id) {
636
- annotationToCreate = {
637
- document: this.documentId,
638
- span: this.spanSelection,
639
- label: this.label.id,
640
- annotation_set: this.annotationSet.id,
641
- is_correct: true,
642
- revised: true,
643
- };
644
- } else {
645
- // if annotation set id is null
646
- annotationToCreate = {
647
- document: this.documentId,
648
- span: this.spanSelection,
649
- label: this.label.id,
650
- label_set: this.annotationSet.label_set.id,
651
- is_correct: true,
652
- revised: true,
653
- };
654
- }
655
- this.isLoading = true;
656
- let negativeAnnotationId;
657
-
658
- // check if the annotation to create comes from a negative annotation
659
- // so we can create the new one and remove the negative one from the annotations array
660
- if (this.isNegative(this.annotation)) {
661
- negativeAnnotationId = this.annotation.id;
662
- }
663
-
664
- this.$store
665
- .dispatch("document/createAnnotation", {
666
- annotation: annotationToCreate,
667
- negativeAnnotationId: negativeAnnotationId,
668
- })
669
- .catch((error) => {
670
- this.$store.dispatch("document/createErrorMessage", {
671
- error,
672
- serverErrorMessage: this.$t("server_error"),
673
- defaultErrorMessage: this.$t("edit_error"),
674
- });
675
- })
676
- .finally(() => {
677
- this.$store.dispatch("document/resetEditAnnotation");
678
- this.handleCancelButton();
679
- });
680
- },
681
- handleCancelButton() {
682
- this.$store.dispatch("document/resetEditAnnotation");
683
- if (this.elementSelected) {
684
- this.$store.dispatch("selection/disableSelection");
685
- this.$store.dispatch("selection/setSelectedEntities", null);
686
- }
687
- },
688
- enableLoading(annotations) {
689
- if (annotations && this.annotation && !this.annotation.is_correct) {
690
- const found = annotations.find(
691
- (annotation) => annotation === this.annotation.id
692
- );
693
-
694
- if (found) {
695
- this.isLoading = true;
696
- return;
697
- }
698
-
699
- this.isLoading = false;
700
- return;
701
- }
702
-
703
- // Check for what empty annotations we want to show the loading
704
- // while waiting for it to be removed from the row
705
- if (!this.annotationsMarkedAsMissing) {
706
- this.isLoading = false;
707
- return;
708
- }
709
-
710
- if (this.annotationsMarkedAsMissing.length > 0) {
711
- this.annotationsMarkedAsMissing.map((annotation) => {
712
- // Check if the annotation set and label are marked as missing
713
- if (
714
- annotation.label_set === this.annotationSet.label_set.id &&
715
- annotation.annotation_set === this.annotationSet.id &&
716
- annotation.label === this.label.id
717
- ) {
718
- // Check if we wanna add loading to all empty annotations
719
- if (this.hoveredAnnotationSet) {
720
- this.isLoading = true;
721
- return;
722
- }
723
-
724
- // or we want to add loading to a single one
725
- if (
726
- !this.hoveredAnnotationSet &&
727
- annotation.label === this.label.id
728
- ) {
729
- this.isLoading = true;
730
- return;
731
- }
732
- }
733
- });
734
- }
735
- },
736
- editAnnotationTranslation(annotationId) {
737
- if (!annotationId || this.isDocumentReviewed) return;
738
-
739
- const baseUrl = api.FILE_URL ? api.FILE_URL : api.DEFAULT_URL;
740
-
741
- const annotationDetailsUrl = `${baseUrl}/admin/server/sequenceannotation/${annotationId}/change/`;
742
-
743
- window.open(annotationDetailsUrl, "_blank");
744
- },
745
- },
746
- };
747
- </script>
748
- <style
749
- scoped
750
- lang="scss"
751
- src="../../assets/scss/document_annotations.scss"
752
- ></style>
1
+ <template>
2
+ <div
3
+ :class="[
4
+ 'annotation-row',
5
+ isSelected && 'selected',
6
+ hoverEmptyLabelRows && 'hovered-empty-labels',
7
+ hoverPendingAnnotationRows && 'hovered-pending-annotations',
8
+ annotationIsNotFound(annotationSet, label) && 'missing',
9
+ isAnnotationInEditMode(annotationId()) && 'editing',
10
+ publicView && 'clickable-cursor',
11
+ ]"
12
+ @click="onAnnotationClick"
13
+ @mouseover="hoveredAnnotation = annotationId()"
14
+ @mouseleave="hoveredAnnotation = null"
15
+ >
16
+ <div
17
+ class="annotation-row-left"
18
+ @mouseenter="onAnnotationHoverEnter(defaultSpan)"
19
+ @mouseleave="onAnnotationHoverLeave"
20
+ >
21
+ <div class="annotation-icon">
22
+ <AnnotationDetails
23
+ :description="label.description"
24
+ :annotation="annotation"
25
+ :annotation-set="annotationSet"
26
+ :label="label"
27
+ :from-table="fromTable"
28
+ />
29
+ </div>
30
+
31
+ <div
32
+ v-if="showLabel"
33
+ :class="[
34
+ 'label-name',
35
+ annotationIsNotFound(annotationSet, label) && 'not-found-text',
36
+ ]"
37
+ >
38
+ <span>{{ label.name }} </span>
39
+ </div>
40
+
41
+ <div
42
+ v-if="showTranslationsDetails"
43
+ :class="['annotation-translation', !isDocumentReviewed && 'pointer']"
44
+ @click="editAnnotationTranslation(annotation.id)"
45
+ >
46
+ <b-tooltip :animated="false" position="is-bottom">
47
+ <div class="icon">
48
+ <TranslateArrows :translation="annotation.translated_string" />
49
+ </div>
50
+
51
+ <template #content>
52
+ <div class="translation-details">
53
+ <div class="translation-title">
54
+ <span>{{ $t("translated_string_title") }}</span>
55
+ <span
56
+ :class="[
57
+ 'translated-string',
58
+ !annotation.translated_string && 'no-translation',
59
+ ]"
60
+ >
61
+ {{
62
+ annotation.translated_string
63
+ ? annotation.translated_string
64
+ : $t("no_translated_string")
65
+ }}
66
+ </span>
67
+ </div>
68
+ </div>
69
+ <div class="translation-info">
70
+ <div v-if="!isDocumentReviewed" class="annotation-details-link">
71
+ {{
72
+ annotation.translated_string
73
+ ? $t("edit_translation")
74
+ : $t("add_translation")
75
+ }}
76
+ </div>
77
+ </div>
78
+ </template>
79
+ </b-tooltip>
80
+ </div>
81
+ </div>
82
+ <div class="annotation-row-right">
83
+ <div class="annotation-content">
84
+ <div v-if="annotation && !isNegative(annotation)">
85
+ <div
86
+ v-for="(span, index) in spanForEditing
87
+ ? spanSelection
88
+ : annotation.span"
89
+ :key="index"
90
+ @mouseenter="onAnnotationHoverEnter(span)"
91
+ @mouseleave="onAnnotationHoverLeave"
92
+ >
93
+ <AnnotationContent
94
+ :ref="`span_${annotation.id}_${index}`"
95
+ :annotation="annotation"
96
+ :span="span"
97
+ :span-index="index"
98
+ :label="label"
99
+ :annotation-set="annotationSet"
100
+ :is-hovered="hoveredAnnotation"
101
+ @save-annotation-changes="handleSaveChanges"
102
+ />
103
+ </div>
104
+ </div>
105
+ <div v-else>
106
+ <div v-if="spanSelection && isAnnotationInEditMode(annotationId())">
107
+ <EmptyAnnotation
108
+ v-for="(span, index) in spanSelection"
109
+ :key="index"
110
+ :span="span"
111
+ :span-index="index"
112
+ :label="label"
113
+ :annotation-set="annotationSet"
114
+ :is-hovered="hoveredAnnotation"
115
+ :is-missing-annotation="
116
+ annotationIsNotFound(annotationSet, label)
117
+ "
118
+ @save-empty-annotation-changes="saveEmptyAnnotationChanges"
119
+ />
120
+ </div>
121
+ <EmptyAnnotation
122
+ v-else-if="!fromTable"
123
+ :label="label"
124
+ :annotation-set="annotationSet"
125
+ :is-hovered="hoveredAnnotation"
126
+ :is-missing-annotation="annotationIsNotFound(annotationSet, label)"
127
+ @save-empty-annotation-changes="saveEmptyAnnotationChanges"
128
+ />
129
+ </div>
130
+ </div>
131
+ <div class="buttons-container">
132
+ <AnnotationActionButtons
133
+ :cancel-btn="showCancelButton()"
134
+ :accept-btn="showAcceptButton()"
135
+ :decline-btn="showDeclineButton()"
136
+ :show-missing-btn="showMissingButton()"
137
+ :save-btn="showSaveButton()"
138
+ :restore-btn="showRestoreButton()"
139
+ :is-loading="isLoading"
140
+ @mark-as-missing="handleMissingAnnotation()"
141
+ @save="handleSaveChanges()"
142
+ @accept="handleSaveChanges()"
143
+ @decline="handleSaveChanges(true)"
144
+ @cancel="handleCancelButton()"
145
+ @restore="handleRestore()"
146
+ />
147
+ </div>
148
+ </div>
149
+ </div>
150
+ </template>
151
+ <script>
152
+ import { mapGetters, mapState } from "vuex";
153
+ import AnnotationDetails from "./AnnotationDetails";
154
+ import AnnotationContent from "./AnnotationContent";
155
+ import EmptyAnnotation from "./EmptyAnnotation";
156
+ import AnnotationActionButtons from "./AnnotationActionButtons";
157
+ import TranslateArrows from "../../assets/images/TranslateArrows";
158
+
159
+ import { isElementArray } from "../../utils/utils";
160
+ import api from "../../api";
161
+
162
+ export default {
163
+ name: "AnnotationRow",
164
+ components: {
165
+ AnnotationDetails,
166
+ AnnotationContent,
167
+ EmptyAnnotation,
168
+ AnnotationActionButtons,
169
+ TranslateArrows,
170
+ },
171
+ props: {
172
+ annotationSet: {
173
+ type: Object,
174
+ required: true,
175
+ },
176
+ label: {
177
+ type: Object,
178
+ required: true,
179
+ },
180
+ annotation: {
181
+ type: Object,
182
+ default: null,
183
+ },
184
+ showLabel: {
185
+ type: Boolean,
186
+ default: true,
187
+ },
188
+ fromTable: {
189
+ type: Boolean,
190
+ default: false,
191
+ },
192
+ },
193
+ data() {
194
+ return {
195
+ isLoading: false,
196
+ isSelected: false,
197
+ annotationAnimationTimeout: null,
198
+ hoveredAnnotation: null,
199
+ };
200
+ },
201
+ computed: {
202
+ ...mapState("document", [
203
+ "editAnnotation",
204
+ "sidebarAnnotationSelected",
205
+ "hoveredAnnotationSet",
206
+ "enableGroupingFeature",
207
+ "publicView",
208
+ "newAcceptedAnnotations",
209
+ "annotationsMarkedAsMissing",
210
+ "documentId",
211
+ "showActionError",
212
+ "missingAnnotations",
213
+ ]),
214
+ ...mapState("selection", [
215
+ "spanSelection",
216
+ "elementSelected",
217
+ "selectedEntities",
218
+ ]),
219
+ ...mapState("project", ["translationsEnabled"]),
220
+ ...mapGetters("document", [
221
+ "isAnnotationInEditMode",
222
+ "annotationIsNotFound",
223
+ "isDocumentReviewed",
224
+ "isNegative",
225
+ ]),
226
+ defaultSpan() {
227
+ if (
228
+ this.annotation &&
229
+ !this.isNegative(this.annotation) &&
230
+ this.annotation.span &&
231
+ this.annotation.span.length > 0
232
+ ) {
233
+ return this.annotation.span[0];
234
+ }
235
+ return null;
236
+ },
237
+ spanForEditing() {
238
+ return (
239
+ this.spanSelection &&
240
+ isElementArray(this.spanSelection) &&
241
+ this.isAnnotationInEditMode(this.annotationId())
242
+ );
243
+ },
244
+ isAnnotation() {
245
+ return (
246
+ this.annotation &&
247
+ !this.isNegative(this.annotation) &&
248
+ this.isAnnotationInEditMode(
249
+ this.annotationId(),
250
+ this.editAnnotation.index
251
+ )
252
+ );
253
+ },
254
+ hoverEmptyLabelRows() {
255
+ return (
256
+ this.hoveredAnnotationSet &&
257
+ this.hoveredAnnotationSet.type == "missing" &&
258
+ !this.annotationIsNotFound(this.annotationSet, this.label) &&
259
+ this.annotationSet.id === this.hoveredAnnotationSet.annotationSet.id &&
260
+ this.annotationSet.label_set.id ===
261
+ this.hoveredAnnotationSet.annotationSet.label_set.id &&
262
+ this.hoveredEmptyLabels() === this.label.id
263
+ );
264
+ },
265
+ hoverPendingAnnotationRows() {
266
+ return (
267
+ this.hoveredAnnotationSet &&
268
+ this.hoveredAnnotationSet.type == "accept" &&
269
+ this.annotation &&
270
+ !this.isNegative(this.annotation) &&
271
+ this.hoveredNotCorrectAnnotations() === this.annotation.id
272
+ );
273
+ },
274
+ showTranslationsDetails() {
275
+ // Only show translation option for filled annotations and if the feature is enabled for the project
276
+ return (
277
+ this.annotation &&
278
+ !this.isNegative(this.annotation) &&
279
+ this.translationsEnabled &&
280
+ !this.publicView
281
+ );
282
+ },
283
+ },
284
+ watch: {
285
+ sidebarAnnotationSelected(newSidebarAnnotationSelected) {
286
+ if (!newSidebarAnnotationSelected) return;
287
+
288
+ let annotationSelected;
289
+
290
+ if (newSidebarAnnotationSelected.annotation) {
291
+ annotationSelected = newSidebarAnnotationSelected.annotation;
292
+ } else {
293
+ annotationSelected = newSidebarAnnotationSelected;
294
+ }
295
+
296
+ if (
297
+ this.annotation &&
298
+ !this.isNegative(this.annotation) &&
299
+ this.annotation.id === annotationSelected.id
300
+ ) {
301
+ clearTimeout(this.annotationAnimationTimeout);
302
+
303
+ let timeout;
304
+
305
+ if (!newSidebarAnnotationSelected.trigger) {
306
+ timeout = 600;
307
+ } else {
308
+ timeout = 1200;
309
+ }
310
+
311
+ this.isSelected = true;
312
+ // remove annotation selection after some time
313
+ this.annotationAnimationTimeout = setTimeout(() => {
314
+ this.$store.dispatch("document/setSidebarAnnotationSelected", null);
315
+ this.isSelected = false;
316
+ }, timeout);
317
+
318
+ // Check if sidebarAnnotationSelected changed from a click or hover
319
+ if (newSidebarAnnotationSelected.trigger === "click") {
320
+ const runAnimation = () => {
321
+ this.$el.scrollIntoView({
322
+ behavior: "smooth",
323
+ block: "center",
324
+ inline: "nearest",
325
+ });
326
+ };
327
+ runAnimation();
328
+ }
329
+ }
330
+ },
331
+ editAnnotation(newValue) {
332
+ if (!newValue) {
333
+ this.isLoading = false;
334
+ }
335
+ },
336
+ newAcceptedAnnotations(newValue) {
337
+ if (newValue) {
338
+ this.enableLoading(newValue);
339
+ } else {
340
+ this.isLoading = false;
341
+ }
342
+ },
343
+ annotationsMarkedAsMissing(newValue) {
344
+ if (newValue) {
345
+ this.enableLoading();
346
+ } else {
347
+ this.isLoading = false;
348
+ }
349
+ },
350
+ showActionError(newValue) {
351
+ if (newValue) {
352
+ this.isLoading = false;
353
+ }
354
+ },
355
+ selectedEntities(newValue) {
356
+ if (!newValue) return;
357
+
358
+ if (this.isAnnotationInEditMode(this.annotationId())) {
359
+ this.isLoading = true;
360
+ }
361
+ },
362
+ spanSelection(newValue) {
363
+ // check if spanSelection has new value from entity selection
364
+ // to stop loading after the text appears in the field
365
+ if (newValue) {
366
+ this.isLoading = false;
367
+ }
368
+ },
369
+ },
370
+ methods: {
371
+ annotationId() {
372
+ if (!this.annotationSet || !this.label) return;
373
+
374
+ if (
375
+ this.annotation &&
376
+ this.annotation.id &&
377
+ !this.isNegative(this.annotation)
378
+ )
379
+ return this.annotation.id;
380
+
381
+ let emptyAnnotationId;
382
+
383
+ if (this.annotationSet.id) {
384
+ emptyAnnotationId = `${this.annotationSet.id}_${this.label.id}`;
385
+ } else {
386
+ emptyAnnotationId = `${this.annotationSet.label_set.id}_${this.label.id}`;
387
+ }
388
+ return emptyAnnotationId;
389
+ },
390
+ onAnnotationHoverEnter(span) {
391
+ if (span) {
392
+ this.$store.dispatch("document/setDocumentAnnotationSelected", {
393
+ annotation: this.annotation,
394
+ label: this.fromTable ? null : this.label,
395
+ span,
396
+ scrollTo: false,
397
+ });
398
+ }
399
+ },
400
+ onAnnotationHoverLeave() {
401
+ this.$store.dispatch("document/disableDocumentAnnotationSelected");
402
+ },
403
+ onAnnotationClick() {
404
+ if (!this.fromTable) {
405
+ this.$store.dispatch("display/showAnnSetTable", null);
406
+ }
407
+ this.$store.dispatch("document/scrollToDocumentAnnotationSelected");
408
+ },
409
+ hoveredEmptyLabels() {
410
+ // This method will change the style of the Empty Annotations in the same Label Set
411
+ // when the "mark all as missing" button is hovered
412
+ if (!this.hoveredAnnotationSet) return;
413
+
414
+ const labels = this.hoveredAnnotationSet.annotationSet.labels.map(
415
+ (label) => {
416
+ return JSON.parse(JSON.stringify(label));
417
+ }
418
+ );
419
+ const found = labels.find((l) => l.id === this.label.id);
420
+ const negativeAnnotations = found.annotations.find((annotation) =>
421
+ this.isNegative(annotation)
422
+ );
423
+
424
+ if ((found && found.annotations.length === 0) || negativeAnnotations)
425
+ return found.id;
426
+ return null;
427
+ },
428
+ hoveredNotCorrectAnnotations() {
429
+ // This method will change the style of Annotations in the same Label Set
430
+ // when the "Accept all" button is hovered
431
+ if (!this.hoveredAnnotationSet) return;
432
+
433
+ const annotations =
434
+ this.hoveredAnnotationSet.annotationSet.labels.flatMap((label) => {
435
+ return label.annotations;
436
+ });
437
+
438
+ // Check if there are no annotations
439
+ if (annotations.length === 0) return;
440
+
441
+ const found = annotations.find(
442
+ (ann) => ann.id === this.annotation.id && !ann.is_correct
443
+ );
444
+
445
+ if (found) {
446
+ return found.id;
447
+ } else {
448
+ return null;
449
+ }
450
+ },
451
+ showAcceptButton() {
452
+ return (
453
+ !this.editAnnotation &&
454
+ !this.isAnnotationInEditMode(this.annotationId()) &&
455
+ this.annotation &&
456
+ !this.isNegative(this.annotation) &&
457
+ !this.annotation.is_correct &&
458
+ this.hoveredAnnotation === this.annotation.id
459
+ );
460
+ },
461
+ showDeclineButton() {
462
+ return (
463
+ !this.editAnnotation &&
464
+ !this.isAnnotationInEditMode(this.annotationId()) &&
465
+ this.annotation &&
466
+ !this.isNegative(this.annotation) &&
467
+ this.hoveredAnnotation === this.annotation.id
468
+ );
469
+ },
470
+ showMissingButton() {
471
+ return (
472
+ !this.editAnnotation &&
473
+ this.hoveredAnnotation &&
474
+ !this.isAnnotationInEditMode(this.annotationId()) &&
475
+ (!this.annotation || this.isNegative(this.annotation)) &&
476
+ !this.annotationIsNotFound(this.annotationSet, this.label)
477
+ );
478
+ },
479
+ showRestoreButton() {
480
+ return (
481
+ !this.editAnnotation &&
482
+ this.hoveredAnnotation &&
483
+ !this.isAnnotationInEditMode(this.annotationId()) &&
484
+ this.annotationIsNotFound(this.annotationSet, this.label)
485
+ );
486
+ },
487
+ showCancelButton() {
488
+ if (!this.editAnnotation || this.isLoading) return;
489
+ if (this.isAnnotationInEditMode(this.annotationId())) {
490
+ return true;
491
+ }
492
+ },
493
+ showSaveButton() {
494
+ if (!this.editAnnotation || this.isLoading) return;
495
+
496
+ // Check if it's an Annotation or Empty Annotation
497
+ if (this.isAnnotation) {
498
+ return true;
499
+ } else {
500
+ if (!this.isAnnotationInEditMode(this.annotationId())) return;
501
+
502
+ return (
503
+ this.elementSelected === this.annotationId() &&
504
+ this.spanSelection &&
505
+ Array.isArray(this.spanSelection)
506
+ );
507
+ }
508
+ },
509
+ handleMissingAnnotation() {
510
+ if (!this.label || !this.annotationSet) return;
511
+
512
+ this.isLoading = true;
513
+
514
+ // will emit to the DocumentAnnotations component, where the method is handled
515
+ // & dispatched to the store
516
+ this.$parent.$emit(
517
+ "handle-missing-annotation",
518
+ this.label.id,
519
+ this.annotationSet.label_set.id,
520
+ this.annotationSet.id,
521
+ false
522
+ );
523
+ },
524
+ handleSaveChanges(decline) {
525
+ if (this.publicView || this.isDocumentReviewed) return;
526
+
527
+ // Verify if we are editing a filled or empty annotation
528
+ if (
529
+ this.annotation &&
530
+ !this.isNegative(this.annotation) &&
531
+ (this.showAcceptButton() ||
532
+ this.showDeclineButton() ||
533
+ this.isAnnotationInEditMode(
534
+ this.annotationId(),
535
+ this.editAnnotation.index
536
+ ))
537
+ ) {
538
+ let spans = [];
539
+
540
+ if (!decline) {
541
+ Object.keys(this.$refs).forEach((ref) => {
542
+ if (ref.includes(`span_${this.annotation.id}`)) {
543
+ const refElement = this.$refs[ref][0];
544
+ // call child component createSpan method
545
+ if (!refElement) return;
546
+
547
+ const span = refElement.createSpan();
548
+
549
+ // only add span if it's not null (offset_string not empty)
550
+ if (span) {
551
+ spans.push(span);
552
+ }
553
+ }
554
+ });
555
+ }
556
+
557
+ this.saveAnnotationChanges(spans, decline);
558
+ } else if (
559
+ (!this.annotation || this.isNegative(this.annotation)) &&
560
+ this.isAnnotationInEditMode(this.annotationId())
561
+ ) {
562
+ this.saveEmptyAnnotationChanges();
563
+ }
564
+ },
565
+ handleRestore() {
566
+ this.isLoading = true;
567
+
568
+ const foundItem = this.missingAnnotations.find(
569
+ (item) =>
570
+ item.annotation_set === this.annotationSet.id &&
571
+ item.label === this.label.id &&
572
+ item.label_set === this.annotationSet.label_set.id
573
+ );
574
+
575
+ this.$store
576
+ .dispatch("document/deleteMissingAnnotation", foundItem.id)
577
+ .catch((error) => {
578
+ this.$store.dispatch("document/createErrorMessage", {
579
+ error,
580
+ serverErrorMessage: this.$t("server_error"),
581
+ defaultErrorMessage: this.$t("edit_error"),
582
+ });
583
+ })
584
+ .finally(() => {
585
+ this.isLoading = false;
586
+ this.closedTag = null;
587
+ });
588
+ },
589
+ saveAnnotationChanges(spans, isToDecline) {
590
+ // This function deals with declining Annotations
591
+ // or editing an Annotation or a part of it (if multi line)
592
+ this.isLoading = true;
593
+
594
+ let updatedString; // what will be sent to the API
595
+ let storeAction; // if it will be 'delete' or 'patch'
596
+
597
+ // Verify if we delete the entire Annotation or a part of the text
598
+ if (isToDecline || spans.length === 0) {
599
+ storeAction = "document/deleteAnnotation";
600
+ } else {
601
+ // Editing the Annotation
602
+ // Deleting part of multi-line Annotation
603
+ storeAction = "document/updateAnnotation";
604
+
605
+ updatedString = {
606
+ is_correct: true,
607
+ revised: true,
608
+ span: spans,
609
+ };
610
+ }
611
+
612
+ // Send to the store for the http patch/delete request
613
+ this.$store
614
+ .dispatch(storeAction, {
615
+ updatedValues: updatedString,
616
+ annotationId: this.annotation.id,
617
+ annotationSet: this.annotationSet,
618
+ })
619
+ .catch((error) => {
620
+ this.$store.dispatch("document/createErrorMessage", {
621
+ error,
622
+ serverErrorMessage: this.$t("server_error"),
623
+ defaultErrorMessage: this.$t("edit_error"),
624
+ });
625
+ })
626
+ .finally(() => {
627
+ this.$store.dispatch("document/resetEditAnnotation");
628
+ this.$store.dispatch("selection/disableSelection");
629
+ this.$store.dispatch("selection/setSelectedEntities", null);
630
+ });
631
+ },
632
+ saveEmptyAnnotationChanges() {
633
+ let annotationToCreate;
634
+
635
+ if (this.annotationSet.id) {
636
+ annotationToCreate = {
637
+ document: this.documentId,
638
+ span: this.spanSelection,
639
+ label: this.label.id,
640
+ annotation_set: this.annotationSet.id,
641
+ is_correct: true,
642
+ revised: true,
643
+ };
644
+ } else {
645
+ // if annotation set id is null
646
+ annotationToCreate = {
647
+ document: this.documentId,
648
+ span: this.spanSelection,
649
+ label: this.label.id,
650
+ label_set: this.annotationSet.label_set.id,
651
+ is_correct: true,
652
+ revised: true,
653
+ };
654
+ }
655
+ this.isLoading = true;
656
+ let negativeAnnotationId;
657
+
658
+ // check if the annotation to create comes from a negative annotation
659
+ // so we can create the new one and remove the negative one from the annotations array
660
+ if (this.isNegative(this.annotation)) {
661
+ negativeAnnotationId = this.annotation.id;
662
+ }
663
+
664
+ this.$store
665
+ .dispatch("document/createAnnotation", {
666
+ annotation: annotationToCreate,
667
+ negativeAnnotationId: negativeAnnotationId,
668
+ })
669
+ .catch((error) => {
670
+ this.$store.dispatch("document/createErrorMessage", {
671
+ error,
672
+ serverErrorMessage: this.$t("server_error"),
673
+ defaultErrorMessage: this.$t("edit_error"),
674
+ });
675
+ })
676
+ .finally(() => {
677
+ this.$store.dispatch("document/resetEditAnnotation");
678
+ this.handleCancelButton();
679
+ });
680
+ },
681
+ handleCancelButton() {
682
+ this.$store.dispatch("document/resetEditAnnotation");
683
+ if (this.elementSelected) {
684
+ this.$store.dispatch("selection/disableSelection");
685
+ this.$store.dispatch("selection/setSelectedEntities", null);
686
+ }
687
+ },
688
+ enableLoading(annotations) {
689
+ if (annotations && this.annotation && !this.annotation.is_correct) {
690
+ const found = annotations.find(
691
+ (annotation) => annotation === this.annotation.id
692
+ );
693
+
694
+ if (found) {
695
+ this.isLoading = true;
696
+ return;
697
+ }
698
+
699
+ this.isLoading = false;
700
+ return;
701
+ }
702
+
703
+ // Check for what empty annotations we want to show the loading
704
+ // while waiting for it to be removed from the row
705
+ if (!this.annotationsMarkedAsMissing) {
706
+ this.isLoading = false;
707
+ return;
708
+ }
709
+
710
+ if (this.annotationsMarkedAsMissing.length > 0) {
711
+ this.annotationsMarkedAsMissing.map((annotation) => {
712
+ // Check if the annotation set and label are marked as missing
713
+ if (
714
+ annotation.label_set === this.annotationSet.label_set.id &&
715
+ annotation.annotation_set === this.annotationSet.id &&
716
+ annotation.label === this.label.id
717
+ ) {
718
+ // Check if we wanna add loading to all empty annotations
719
+ if (this.hoveredAnnotationSet) {
720
+ this.isLoading = true;
721
+ return;
722
+ }
723
+
724
+ // or we want to add loading to a single one
725
+ if (
726
+ !this.hoveredAnnotationSet &&
727
+ annotation.label === this.label.id
728
+ ) {
729
+ this.isLoading = true;
730
+ return;
731
+ }
732
+ }
733
+ });
734
+ }
735
+ },
736
+ editAnnotationTranslation(annotationId) {
737
+ if (!annotationId || this.isDocumentReviewed) return;
738
+
739
+ const baseUrl = api.FILE_URL ? api.FILE_URL : api.DEFAULT_URL;
740
+
741
+ const annotationDetailsUrl = `${baseUrl}/admin/server/sequenceannotation/${annotationId}/change/`;
742
+
743
+ window.open(annotationDetailsUrl, "_blank");
744
+ },
745
+ },
746
+ };
747
+ </script>
748
+ <style
749
+ scoped
750
+ lang="scss"
751
+ src="../../assets/scss/document_annotations.scss"
752
+ ></style>