@konfuzio/document-validation-ui 0.1.19-dev.2 → 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 (143) 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/js/app.js +1 -1
  7. package/dist/js/app.js.map +1 -1
  8. package/dist/js/chunk-vendors.js +1 -1
  9. package/dist/js/chunk-vendors.js.map +1 -1
  10. package/jest.config.js +4 -4
  11. package/package.json +66 -66
  12. package/src/api.js +82 -82
  13. package/src/assets/images/AcceptedCheckMark.vue +8 -8
  14. package/src/assets/images/AcceptedUser.vue +8 -8
  15. package/src/assets/images/ActionIcon.vue +60 -60
  16. package/src/assets/images/ArrowDownKey.vue +11 -11
  17. package/src/assets/images/ArrowUpKey.vue +11 -11
  18. package/src/assets/images/CategoryIconImg.vue +13 -13
  19. package/src/assets/images/CheckMark.vue +8 -8
  20. package/src/assets/images/DraggableIcon.vue +14 -14
  21. package/src/assets/images/EditDocIcon.vue +12 -12
  22. package/src/assets/images/EmptyStateImg.vue +129 -129
  23. package/src/assets/images/ErrorIcon.vue +28 -28
  24. package/src/assets/images/EyeIcon.vue +11 -11
  25. package/src/assets/images/FileNameNotSavedImage.vue +26 -26
  26. package/src/assets/images/FileNameSavedImage.vue +14 -14
  27. package/src/assets/images/FitZoomIcon.vue +16 -16
  28. package/src/assets/images/GridIcon.vue +16 -16
  29. package/src/assets/images/KeyboardIcon.vue +16 -16
  30. package/src/assets/images/MagicWandIcon.vue +16 -16
  31. package/src/assets/images/MinusIcon.vue +13 -13
  32. package/src/assets/images/NotFoundIcon.vue +16 -16
  33. package/src/assets/images/NotOptimizedIllustration.vue +651 -651
  34. package/src/assets/images/PlusIcon.vue +13 -13
  35. package/src/assets/images/QuestionMark.vue +12 -12
  36. package/src/assets/images/ServerImage.vue +73 -73
  37. package/src/assets/images/SettingsIcon.vue +14 -14
  38. package/src/assets/images/SplitLines.vue +18 -18
  39. package/src/assets/images/SplitZigZag.vue +49 -49
  40. package/src/assets/images/StarIcon.vue +16 -16
  41. package/src/assets/images/StatusImg.vue +14 -14
  42. package/src/assets/images/TranslateArrows.vue +33 -33
  43. package/src/assets/scss/ann_set_table_options.scss +26 -26
  44. package/src/assets/scss/annotation_details.scss +141 -141
  45. package/src/assets/scss/choose_label_set_modal.scss +65 -65
  46. package/src/assets/scss/document_action_bar.scss +37 -37
  47. package/src/assets/scss/document_annotations.scss +558 -558
  48. package/src/assets/scss/document_category.scss +85 -85
  49. package/src/assets/scss/document_dashboard.scss +52 -52
  50. package/src/assets/scss/document_edit.scss +410 -410
  51. package/src/assets/scss/document_error.scss +81 -81
  52. package/src/assets/scss/document_name.scss +60 -60
  53. package/src/assets/scss/document_page.scss +12 -12
  54. package/src/assets/scss/document_thumbnails.scss +41 -41
  55. package/src/assets/scss/document_toolbar.scss +111 -111
  56. package/src/assets/scss/document_top_bar.scss +171 -171
  57. package/src/assets/scss/document_viewport_modal.scss +25 -25
  58. package/src/assets/scss/documents_list.scss +141 -141
  59. package/src/assets/scss/edit_page_thumbnail.scss +53 -53
  60. package/src/assets/scss/empty_state.scss +34 -34
  61. package/src/assets/scss/extracting_data.scss +35 -35
  62. package/src/assets/scss/imports.scss +1 -1
  63. package/src/assets/scss/multi_ann_table_overlay.scss +38 -38
  64. package/src/assets/scss/multi_ann_table_popup.scss +12 -12
  65. package/src/assets/scss/new_annotation.scss +102 -102
  66. package/src/assets/scss/scrolling_document.scss +19 -19
  67. package/src/assets/scss/theme.scss +801 -801
  68. package/src/assets/scss/variables.scss +66 -66
  69. package/src/components/App.cy.js +7 -7
  70. package/src/components/App.vue +187 -187
  71. package/src/components/DocumentAnnotations/AnnotationActionButtons.vue +152 -152
  72. package/src/components/DocumentAnnotations/AnnotationContent.vue +210 -210
  73. package/src/components/DocumentAnnotations/AnnotationDetails.vue +251 -251
  74. package/src/components/DocumentAnnotations/AnnotationRow.vue +752 -752
  75. package/src/components/DocumentAnnotations/AnnotationSetActionButtons.vue +89 -89
  76. package/src/components/DocumentAnnotations/ChooseLabelSetModal.vue +186 -186
  77. package/src/components/DocumentAnnotations/DocumentAnnotations.cy.js +441 -441
  78. package/src/components/DocumentAnnotations/DocumentAnnotations.vue +534 -534
  79. package/src/components/DocumentAnnotations/DocumentLabel.vue +189 -189
  80. package/src/components/DocumentAnnotations/EmptyAnnotation.vue +193 -193
  81. package/src/components/DocumentAnnotations/EmptyState.vue +21 -21
  82. package/src/components/DocumentAnnotations/ExtractingData.vue +41 -41
  83. package/src/components/DocumentAnnotations/LoadingAnnotations.vue +43 -43
  84. package/src/components/DocumentAnnotations/LoadingLabels.vue +43 -43
  85. package/src/components/DocumentAnnotations/MultiAnnotationTableOverlay.vue +338 -338
  86. package/src/components/DocumentAnnotations/index.js +8 -8
  87. package/src/components/DocumentCategory.vue +281 -281
  88. package/src/components/DocumentDashboard.vue +170 -170
  89. package/src/components/DocumentEdit/DocumentEdit.cy.js +541 -541
  90. package/src/components/DocumentEdit/DocumentEdit.vue +503 -503
  91. package/src/components/DocumentEdit/EditConfirmationModal.vue +55 -55
  92. package/src/components/DocumentEdit/EditPageThumbnail.vue +114 -114
  93. package/src/components/DocumentEdit/EditPages.vue +161 -161
  94. package/src/components/DocumentEdit/EditSidebar.vue +154 -154
  95. package/src/components/DocumentEdit/RenameAndCategorize.vue +184 -184
  96. package/src/components/DocumentEdit/SidebarButtons.vue +53 -53
  97. package/src/components/DocumentEdit/SplitInfoBar.vue +21 -21
  98. package/src/components/DocumentEdit/index.js +4 -4
  99. package/src/components/DocumentModals/DocumentErrorModal.vue +58 -58
  100. package/src/components/DocumentModals/NotOptimizedViewportModal.vue +51 -51
  101. package/src/components/DocumentPage/ActionBar.vue +48 -48
  102. package/src/components/DocumentPage/AnnSetTableOptions.vue +111 -111
  103. package/src/components/DocumentPage/BoxSelection.vue +152 -152
  104. package/src/components/DocumentPage/DocumentPage.cy.js +92 -92
  105. package/src/components/DocumentPage/DocumentPage.vue +568 -568
  106. package/src/components/DocumentPage/DocumentToolbar.cy.js +215 -215
  107. package/src/components/DocumentPage/DocumentToolbar.vue +228 -228
  108. package/src/components/DocumentPage/DummyPage.vue +55 -55
  109. package/src/components/DocumentPage/MultiAnnSelection.vue +371 -371
  110. package/src/components/DocumentPage/NewAnnotation.vue +308 -308
  111. package/src/components/DocumentPage/ScrollingDocument.vue +149 -149
  112. package/src/components/DocumentPage/ScrollingPage.vue +179 -179
  113. package/src/components/DocumentPage/index.js +5 -5
  114. package/src/components/DocumentThumbnails/DocumentThumbnails.cy.js +67 -67
  115. package/src/components/DocumentThumbnails/DocumentThumbnails.vue +132 -132
  116. package/src/components/DocumentThumbnails/LoadingThumbnail.vue +25 -25
  117. package/src/components/DocumentThumbnails/index.js +1 -1
  118. package/src/components/DocumentTopBar/DocumentName.vue +236 -236
  119. package/src/components/DocumentTopBar/DocumentTopBar.cy.js +222 -222
  120. package/src/components/DocumentTopBar/DocumentTopBar.vue +202 -202
  121. package/src/components/DocumentTopBar/DocumentTopBarButtons.vue +183 -183
  122. package/src/components/DocumentTopBar/KeyboardActionsDescription.vue +74 -74
  123. package/src/components/DocumentTopBar/index.js +3 -3
  124. package/src/components/DocumentsList/DocumentsList.vue +121 -121
  125. package/src/components/DocumentsList/index.js +1 -1
  126. package/src/components/ErrorMessage.vue +40 -40
  127. package/src/components/index.js +1 -1
  128. package/src/constants.js +5 -5
  129. package/src/directives/scroll.js +28 -28
  130. package/src/i18n.js +22 -22
  131. package/src/icons.js +45 -45
  132. package/src/locales/de.json +148 -148
  133. package/src/locales/en.json +148 -148
  134. package/src/main.js +26 -26
  135. package/src/store/category.js +191 -191
  136. package/src/store/display.js +311 -311
  137. package/src/store/document.js +1438 -1438
  138. package/src/store/edit.js +316 -316
  139. package/src/store/index.js +21 -21
  140. package/src/store/project.js +143 -143
  141. package/src/store/selection.js +210 -210
  142. package/src/utils/utils.js +54 -54
  143. package/vue.config.js +25 -25
@@ -1,371 +1,371 @@
1
- <template>
2
- <v-group>
3
- <v-rect
4
- ref="multiAnnBoxSelection"
5
- :config="tableConfig"
6
- :stroke-scale-enabled="false"
7
- @dragstart="changeStart"
8
- @dragend="onChange"
9
- @transformstart="changeStart"
10
- @transformend="onChange"
11
- />
12
- <v-transformer ref="multiAnnBoxTransformer" :config="transformerConfig" />
13
- <v-label
14
- v-if="showButton"
15
- :config="buttonConfig"
16
- @mouseenter="onButtonEnter"
17
- @mouseleave="onButtonLeave"
18
- >
19
- <v-tag
20
- :config="{
21
- fill: '#7B61FF',
22
- cornerRadius: 4,
23
- name: 'tag',
24
- }"
25
- />
26
- <v-text
27
- :config="{
28
- align: 'right',
29
- width: buttonWidth,
30
- ellipsis: true,
31
- wrap: 'none',
32
- padding: 8,
33
- text: $t('new_multi_ann_title'),
34
- fill: 'white',
35
- fontSize: 14,
36
- cursor: 'pointer',
37
- name: 'multiAnnButton',
38
- }"
39
- @click="openMultiAnnotationModal"
40
- />
41
- </v-label>
42
- </v-group>
43
- </template>
44
-
45
- <script>
46
- import { mapGetters, mapState, mapActions } from "vuex";
47
- import { table_reference_api } from "../../store/document";
48
-
49
- export default {
50
- props: {
51
- page: {
52
- required: true,
53
- type: Object,
54
- },
55
- },
56
- data() {
57
- const BUTTON_HEIGHT = 36;
58
- const BUTTON_WIDTH = this.$i18n.locale === "de" ? 238 : 187;
59
- return {
60
- buttonWidth: BUTTON_WIDTH,
61
- buttonHeight: BUTTON_HEIGHT,
62
- isEditing: false,
63
- showTable: false,
64
- entities: [],
65
- };
66
- },
67
- computed: {
68
- showButton() {
69
- return !this.isSelecting && !this.isEditing;
70
- },
71
- transformerConfig() {
72
- return {
73
- borderEnabled: false,
74
- rotateEnabled: false,
75
- ignoreStroke: true,
76
- keepRatio: false,
77
- anchorStroke: "#7B61FF",
78
- anchorSize: 6,
79
- };
80
- },
81
- tableConfig() {
82
- return {
83
- x: this.selection.start.x,
84
- y: this.selection.start.y,
85
- width: this.selection.end.x - this.selection.start.x,
86
- height: this.selection.end.y - this.selection.start.y,
87
- fill: this.isSelecting ? "#7B61FFB3" : "#7B61FF33",
88
- stroke: this.isSelecting ? "transparent" : "#7B61FFB3",
89
- strokeWidth: 1,
90
- globalCompositeOperation: "multiply",
91
- shadowForStrokeEnabled: false,
92
- name: "multiAnnBoxSelection",
93
- draggable: true,
94
- };
95
- },
96
- buttonConfig() {
97
- let x = this.selection.end.x;
98
- let y = this.selection.end.y;
99
- const marginTop = 4;
100
-
101
- // check if selection was made from right to left
102
- if (x < this.selection.start.x) {
103
- x = this.selection.start.x;
104
- }
105
- if (y < this.selection.start.y) {
106
- y = this.selection.start.y;
107
- }
108
- x = x - this.buttonWidth;
109
- y = y + marginTop;
110
-
111
- return {
112
- x: x > 0 ? x : 0,
113
- y: y > 0 ? y : 0,
114
- height: this.buttonHeight,
115
- width: this.buttonWidth,
116
- };
117
- },
118
- ...mapState("selection", ["selection", "isSelecting"]),
119
- ...mapState("document", ["documentId"]),
120
- ...mapGetters("display", ["clientToBbox"]),
121
- ...mapGetters("document", ["entitiesOnSelection"]),
122
- },
123
- watch: {
124
- isSelecting(newValue, oldValue) {
125
- // if it ends selection
126
- if (!newValue && oldValue) {
127
- this.calculateEntities();
128
- this.updateTransformer();
129
- }
130
- },
131
- },
132
- methods: {
133
- openMultiAnnotationModal() {
134
- this.$store.dispatch("display/showChooseLabelSetModal", {
135
- show: true,
136
- isMultipleAnnotations: true,
137
- finish: this.submitAnnotations,
138
- });
139
- },
140
-
141
- async submitAnnotations(labelSet) {
142
- const columns = labelSet.labels.map((label) => {
143
- return {
144
- field: `${label.id}`,
145
- label: label.name,
146
- centered: true,
147
- };
148
- });
149
-
150
- const orderedEntities = this.processRows(columns);
151
-
152
- const annotations = [];
153
-
154
- orderedEntities.forEach((orderedEntity) => {
155
- annotations.push({
156
- document: this.documentId,
157
- span: orderedEntity.spans,
158
- label: orderedEntity.label_id,
159
- is_correct: true,
160
- revised: false,
161
- label_set: labelSet.id,
162
- set_reference: orderedEntity.row_index,
163
- origin: table_reference_api,
164
- });
165
- });
166
-
167
- this.$store
168
- .dispatch("document/createAnnotation", annotations)
169
- .then(() => {
170
- this.$store.dispatch("selection/disableSelection");
171
- this.$emit("finished");
172
- })
173
- .catch((error) => {
174
- this.$store.dispatch("document/createErrorMessage", {
175
- error,
176
- serverErrorMessage: this.$t("server_error"),
177
- defaultErrorMessage: this.$t("error_creating_multi_ann"),
178
- });
179
- this.$store.dispatch("selection/disableSelection");
180
- this.$emit("finished");
181
- });
182
- },
183
-
184
- onButtonEnter() {
185
- this.$emit("buttonEnter");
186
- },
187
-
188
- onButtonLeave() {
189
- this.$emit("buttonLeave");
190
- },
191
- changeStart() {
192
- this.isEditing = true;
193
- },
194
- /**
195
- * This method is used for both transforms and drags since it just
196
- * retrieves the rect's new attributes from the event and uses those
197
- * to set the new selection state.
198
- */
199
- onChange(event) {
200
- this.isEditing = false;
201
-
202
- const { x, y, scaleX, scaleY, skewX, width, height } = event.target.attrs;
203
- const realWidth = width * scaleX;
204
- const realHeight = height * scaleY;
205
- let start;
206
- let end;
207
-
208
- // we need to figure out if there's skewing going on, to fix start/end points
209
- // (other cases appear to fix themselves automatically)
210
- if (skewX >= 0) {
211
- start = { x, y };
212
- end = {
213
- x: start.x + realWidth,
214
- y: start.y + realHeight,
215
- };
216
- } else {
217
- end = { x, y };
218
- start = { x: end.x - realWidth, y: end.y - realHeight };
219
- }
220
-
221
- this.moveSelection({ start, end });
222
-
223
- // reset node's everything after transform (we don't want to deal with that,
224
- // just with regular x/y/width/height)
225
- const node = this.$refs.multiAnnBoxSelection.getNode();
226
- node.skewX(0);
227
- node.skewY(0);
228
- node.rotation(0);
229
- node.scaleX(1);
230
- node.scaleY(1);
231
-
232
- this.calculateEntities();
233
- this.updateTransformer();
234
- },
235
-
236
- updateTransformer() {
237
- // here we need to manually attach or detach Transformer node
238
- const transformer = this.$refs.multiAnnBoxTransformer;
239
-
240
- // maybe we're out of sync and the transformer is not available, just return
241
- if (!transformer) {
242
- return;
243
- }
244
-
245
- const transformerNode = transformer.getNode();
246
- const stage = transformerNode.getStage();
247
- const selectedNode = stage.findOne(".multiAnnBoxSelection");
248
-
249
- // do nothing if selected node is already attached
250
- if (selectedNode === transformerNode.node()) {
251
- return;
252
- }
253
-
254
- if (selectedNode) {
255
- // attach to another node
256
- transformerNode.nodes([selectedNode]);
257
- } else {
258
- // remove transformer
259
- transformerNode.nodes([]);
260
- }
261
-
262
- transformerNode.getLayer().batchDraw();
263
- },
264
-
265
- calculateEntities() {
266
- const box = this.clientToBbox(
267
- this.page,
268
- this.selection.start,
269
- this.selection.end
270
- );
271
- const entities = this.entitiesOnSelection(box, this.page);
272
-
273
- const offset = 4;
274
- if (entities.length === 0) {
275
- return;
276
- }
277
-
278
- const rows = [...new Set(entities.map((o) => o["top"]))];
279
- rows.sort();
280
-
281
- let joinRow = 0;
282
- const jointRows = [];
283
-
284
- // group entities that are near each other on Y axis
285
- rows.forEach((row) => {
286
- if (row - joinRow > offset) {
287
- joinRow = row;
288
- jointRows.push(row);
289
- }
290
- });
291
-
292
- let cols = {};
293
- jointRows.forEach((row) => {
294
- const entityRow = [];
295
- entities.forEach((item) => {
296
- if (item.top === row || Math.abs(item.top - row) <= offset) {
297
- entityRow.push(item);
298
- }
299
- });
300
- entityRow.sort((a, b) => a.x0 - b.x0);
301
-
302
- const finalRow = {};
303
- let previousEntity = null;
304
-
305
- // group entities that are near each other on X axis
306
- entityRow.forEach((entity) => {
307
- let xGroup = entity.x0;
308
- if (previousEntity && previousEntity.x1 + offset > entity.x0) {
309
- // compare to previous one
310
- finalRow[previousEntity.xGroup].push(entity);
311
- xGroup = previousEntity.xGroup;
312
- } else {
313
- finalRow[entity.x0] = [entity];
314
- }
315
- previousEntity = entity;
316
- previousEntity.xGroup = xGroup;
317
- });
318
-
319
- cols[row] = finalRow;
320
- });
321
-
322
- this.entities = cols;
323
- },
324
-
325
- processRows(columns) {
326
- const orderedEntities = []; // this will match the order of entities in the table so we have a way of tracking them once we submit
327
- let rowIndex = 0;
328
-
329
- Object.entries(this.entities).forEach(([key, groupedEntity]) => {
330
- let row = null;
331
- columns.forEach((column, index) => {
332
- let spans = [];
333
- if (
334
- Object.entries(groupedEntity)[index] &&
335
- Object.entries(groupedEntity)[index].length > 0
336
- ) {
337
- spans = Object.entries(groupedEntity)[index][1];
338
- }
339
- const entityExists = spans.length > 0;
340
-
341
- let textContent = "";
342
-
343
- spans.forEach((entity) => {
344
- textContent = `${textContent} ${entity.offset_string}`;
345
- });
346
-
347
- row = {
348
- ...row,
349
- [column.field]: textContent,
350
- };
351
- if (entityExists) {
352
- const customEntity = {
353
- spans: [...spans],
354
- label_id: column.field,
355
- row_index: rowIndex,
356
- };
357
-
358
- orderedEntities.push(customEntity);
359
- }
360
- });
361
- if (row !== null) {
362
- rowIndex++;
363
- }
364
- });
365
- return orderedEntities;
366
- },
367
-
368
- ...mapActions("selection", ["moveSelection"]),
369
- },
370
- };
371
- </script>
1
+ <template>
2
+ <v-group>
3
+ <v-rect
4
+ ref="multiAnnBoxSelection"
5
+ :config="tableConfig"
6
+ :stroke-scale-enabled="false"
7
+ @dragstart="changeStart"
8
+ @dragend="onChange"
9
+ @transformstart="changeStart"
10
+ @transformend="onChange"
11
+ />
12
+ <v-transformer ref="multiAnnBoxTransformer" :config="transformerConfig" />
13
+ <v-label
14
+ v-if="showButton"
15
+ :config="buttonConfig"
16
+ @mouseenter="onButtonEnter"
17
+ @mouseleave="onButtonLeave"
18
+ >
19
+ <v-tag
20
+ :config="{
21
+ fill: '#7B61FF',
22
+ cornerRadius: 4,
23
+ name: 'tag',
24
+ }"
25
+ />
26
+ <v-text
27
+ :config="{
28
+ align: 'right',
29
+ width: buttonWidth,
30
+ ellipsis: true,
31
+ wrap: 'none',
32
+ padding: 8,
33
+ text: $t('new_multi_ann_title'),
34
+ fill: 'white',
35
+ fontSize: 14,
36
+ cursor: 'pointer',
37
+ name: 'multiAnnButton',
38
+ }"
39
+ @click="openMultiAnnotationModal"
40
+ />
41
+ </v-label>
42
+ </v-group>
43
+ </template>
44
+
45
+ <script>
46
+ import { mapGetters, mapState, mapActions } from "vuex";
47
+ import { table_reference_api } from "../../store/document";
48
+
49
+ export default {
50
+ props: {
51
+ page: {
52
+ required: true,
53
+ type: Object,
54
+ },
55
+ },
56
+ data() {
57
+ const BUTTON_HEIGHT = 36;
58
+ const BUTTON_WIDTH = this.$i18n.locale === "de" ? 238 : 187;
59
+ return {
60
+ buttonWidth: BUTTON_WIDTH,
61
+ buttonHeight: BUTTON_HEIGHT,
62
+ isEditing: false,
63
+ showTable: false,
64
+ entities: [],
65
+ };
66
+ },
67
+ computed: {
68
+ showButton() {
69
+ return !this.isSelecting && !this.isEditing;
70
+ },
71
+ transformerConfig() {
72
+ return {
73
+ borderEnabled: false,
74
+ rotateEnabled: false,
75
+ ignoreStroke: true,
76
+ keepRatio: false,
77
+ anchorStroke: "#7B61FF",
78
+ anchorSize: 6,
79
+ };
80
+ },
81
+ tableConfig() {
82
+ return {
83
+ x: this.selection.start.x,
84
+ y: this.selection.start.y,
85
+ width: this.selection.end.x - this.selection.start.x,
86
+ height: this.selection.end.y - this.selection.start.y,
87
+ fill: this.isSelecting ? "#7B61FFB3" : "#7B61FF33",
88
+ stroke: this.isSelecting ? "transparent" : "#7B61FFB3",
89
+ strokeWidth: 1,
90
+ globalCompositeOperation: "multiply",
91
+ shadowForStrokeEnabled: false,
92
+ name: "multiAnnBoxSelection",
93
+ draggable: true,
94
+ };
95
+ },
96
+ buttonConfig() {
97
+ let x = this.selection.end.x;
98
+ let y = this.selection.end.y;
99
+ const marginTop = 4;
100
+
101
+ // check if selection was made from right to left
102
+ if (x < this.selection.start.x) {
103
+ x = this.selection.start.x;
104
+ }
105
+ if (y < this.selection.start.y) {
106
+ y = this.selection.start.y;
107
+ }
108
+ x = x - this.buttonWidth;
109
+ y = y + marginTop;
110
+
111
+ return {
112
+ x: x > 0 ? x : 0,
113
+ y: y > 0 ? y : 0,
114
+ height: this.buttonHeight,
115
+ width: this.buttonWidth,
116
+ };
117
+ },
118
+ ...mapState("selection", ["selection", "isSelecting"]),
119
+ ...mapState("document", ["documentId"]),
120
+ ...mapGetters("display", ["clientToBbox"]),
121
+ ...mapGetters("document", ["entitiesOnSelection"]),
122
+ },
123
+ watch: {
124
+ isSelecting(newValue, oldValue) {
125
+ // if it ends selection
126
+ if (!newValue && oldValue) {
127
+ this.calculateEntities();
128
+ this.updateTransformer();
129
+ }
130
+ },
131
+ },
132
+ methods: {
133
+ openMultiAnnotationModal() {
134
+ this.$store.dispatch("display/showChooseLabelSetModal", {
135
+ show: true,
136
+ isMultipleAnnotations: true,
137
+ finish: this.submitAnnotations,
138
+ });
139
+ },
140
+
141
+ async submitAnnotations(labelSet) {
142
+ const columns = labelSet.labels.map((label) => {
143
+ return {
144
+ field: `${label.id}`,
145
+ label: label.name,
146
+ centered: true,
147
+ };
148
+ });
149
+
150
+ const orderedEntities = this.processRows(columns);
151
+
152
+ const annotations = [];
153
+
154
+ orderedEntities.forEach((orderedEntity) => {
155
+ annotations.push({
156
+ document: this.documentId,
157
+ span: orderedEntity.spans,
158
+ label: orderedEntity.label_id,
159
+ is_correct: true,
160
+ revised: false,
161
+ label_set: labelSet.id,
162
+ set_reference: orderedEntity.row_index,
163
+ origin: table_reference_api,
164
+ });
165
+ });
166
+
167
+ this.$store
168
+ .dispatch("document/createAnnotation", annotations)
169
+ .then(() => {
170
+ this.$store.dispatch("selection/disableSelection");
171
+ this.$emit("finished");
172
+ })
173
+ .catch((error) => {
174
+ this.$store.dispatch("document/createErrorMessage", {
175
+ error,
176
+ serverErrorMessage: this.$t("server_error"),
177
+ defaultErrorMessage: this.$t("error_creating_multi_ann"),
178
+ });
179
+ this.$store.dispatch("selection/disableSelection");
180
+ this.$emit("finished");
181
+ });
182
+ },
183
+
184
+ onButtonEnter() {
185
+ this.$emit("buttonEnter");
186
+ },
187
+
188
+ onButtonLeave() {
189
+ this.$emit("buttonLeave");
190
+ },
191
+ changeStart() {
192
+ this.isEditing = true;
193
+ },
194
+ /**
195
+ * This method is used for both transforms and drags since it just
196
+ * retrieves the rect's new attributes from the event and uses those
197
+ * to set the new selection state.
198
+ */
199
+ onChange(event) {
200
+ this.isEditing = false;
201
+
202
+ const { x, y, scaleX, scaleY, skewX, width, height } = event.target.attrs;
203
+ const realWidth = width * scaleX;
204
+ const realHeight = height * scaleY;
205
+ let start;
206
+ let end;
207
+
208
+ // we need to figure out if there's skewing going on, to fix start/end points
209
+ // (other cases appear to fix themselves automatically)
210
+ if (skewX >= 0) {
211
+ start = { x, y };
212
+ end = {
213
+ x: start.x + realWidth,
214
+ y: start.y + realHeight,
215
+ };
216
+ } else {
217
+ end = { x, y };
218
+ start = { x: end.x - realWidth, y: end.y - realHeight };
219
+ }
220
+
221
+ this.moveSelection({ start, end });
222
+
223
+ // reset node's everything after transform (we don't want to deal with that,
224
+ // just with regular x/y/width/height)
225
+ const node = this.$refs.multiAnnBoxSelection.getNode();
226
+ node.skewX(0);
227
+ node.skewY(0);
228
+ node.rotation(0);
229
+ node.scaleX(1);
230
+ node.scaleY(1);
231
+
232
+ this.calculateEntities();
233
+ this.updateTransformer();
234
+ },
235
+
236
+ updateTransformer() {
237
+ // here we need to manually attach or detach Transformer node
238
+ const transformer = this.$refs.multiAnnBoxTransformer;
239
+
240
+ // maybe we're out of sync and the transformer is not available, just return
241
+ if (!transformer) {
242
+ return;
243
+ }
244
+
245
+ const transformerNode = transformer.getNode();
246
+ const stage = transformerNode.getStage();
247
+ const selectedNode = stage.findOne(".multiAnnBoxSelection");
248
+
249
+ // do nothing if selected node is already attached
250
+ if (selectedNode === transformerNode.node()) {
251
+ return;
252
+ }
253
+
254
+ if (selectedNode) {
255
+ // attach to another node
256
+ transformerNode.nodes([selectedNode]);
257
+ } else {
258
+ // remove transformer
259
+ transformerNode.nodes([]);
260
+ }
261
+
262
+ transformerNode.getLayer().batchDraw();
263
+ },
264
+
265
+ calculateEntities() {
266
+ const box = this.clientToBbox(
267
+ this.page,
268
+ this.selection.start,
269
+ this.selection.end
270
+ );
271
+ const entities = this.entitiesOnSelection(box, this.page);
272
+
273
+ const offset = 4;
274
+ if (entities.length === 0) {
275
+ return;
276
+ }
277
+
278
+ const rows = [...new Set(entities.map((o) => o["top"]))];
279
+ rows.sort();
280
+
281
+ let joinRow = 0;
282
+ const jointRows = [];
283
+
284
+ // group entities that are near each other on Y axis
285
+ rows.forEach((row) => {
286
+ if (row - joinRow > offset) {
287
+ joinRow = row;
288
+ jointRows.push(row);
289
+ }
290
+ });
291
+
292
+ let cols = {};
293
+ jointRows.forEach((row) => {
294
+ const entityRow = [];
295
+ entities.forEach((item) => {
296
+ if (item.top === row || Math.abs(item.top - row) <= offset) {
297
+ entityRow.push(item);
298
+ }
299
+ });
300
+ entityRow.sort((a, b) => a.x0 - b.x0);
301
+
302
+ const finalRow = {};
303
+ let previousEntity = null;
304
+
305
+ // group entities that are near each other on X axis
306
+ entityRow.forEach((entity) => {
307
+ let xGroup = entity.x0;
308
+ if (previousEntity && previousEntity.x1 + offset > entity.x0) {
309
+ // compare to previous one
310
+ finalRow[previousEntity.xGroup].push(entity);
311
+ xGroup = previousEntity.xGroup;
312
+ } else {
313
+ finalRow[entity.x0] = [entity];
314
+ }
315
+ previousEntity = entity;
316
+ previousEntity.xGroup = xGroup;
317
+ });
318
+
319
+ cols[row] = finalRow;
320
+ });
321
+
322
+ this.entities = cols;
323
+ },
324
+
325
+ processRows(columns) {
326
+ const orderedEntities = []; // this will match the order of entities in the table so we have a way of tracking them once we submit
327
+ let rowIndex = 0;
328
+
329
+ Object.entries(this.entities).forEach(([key, groupedEntity]) => {
330
+ let row = null;
331
+ columns.forEach((column, index) => {
332
+ let spans = [];
333
+ if (
334
+ Object.entries(groupedEntity)[index] &&
335
+ Object.entries(groupedEntity)[index].length > 0
336
+ ) {
337
+ spans = Object.entries(groupedEntity)[index][1];
338
+ }
339
+ const entityExists = spans.length > 0;
340
+
341
+ let textContent = "";
342
+
343
+ spans.forEach((entity) => {
344
+ textContent = `${textContent} ${entity.offset_string}`;
345
+ });
346
+
347
+ row = {
348
+ ...row,
349
+ [column.field]: textContent,
350
+ };
351
+ if (entityExists) {
352
+ const customEntity = {
353
+ spans: [...spans],
354
+ label_id: column.field,
355
+ row_index: rowIndex,
356
+ };
357
+
358
+ orderedEntities.push(customEntity);
359
+ }
360
+ });
361
+ if (row !== null) {
362
+ rowIndex++;
363
+ }
364
+ });
365
+ return orderedEntities;
366
+ },
367
+
368
+ ...mapActions("selection", ["moveSelection"]),
369
+ },
370
+ };
371
+ </script>