@konfuzio/document-validation-ui 0.2.6-dev.1 → 0.2.6
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.
- package/dist/css/app.css +1 -1
- package/dist/index.html +1 -1
- package/dist/js/app.js +1 -1
- package/dist/js/app.js.map +1 -1
- package/dist/js/chunk-vendors.js +17 -17
- package/dist/js/chunk-vendors.js.map +1 -1
- package/package.json +6 -2
- package/src/assets/scss/document_toolbar.scss +5 -1
- package/src/assets/scss/theme.scss +1 -0
- package/src/components/DocumentAnnotations/AnnotationRow.vue +3 -1
- package/src/components/DocumentAnnotations/DocumentAnnotations.vue +23 -0
- package/src/components/DocumentPage/DocumentPage.vue +58 -1
- package/src/components/DocumentPage/DocumentToolbar.vue +33 -1
- package/src/icons.js +3 -1
- package/src/locales/en.json +3 -1
- package/src/store/display.js +5 -5
- package/src/store/document.js +83 -0
- package/src/store/selection.js +7 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@konfuzio/document-validation-ui",
|
|
3
|
-
"version": "0.2.6
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"repository": "https://github.com/konfuzio-ai/document-validation-ui",
|
|
5
5
|
"main": "dist/app.js",
|
|
6
6
|
"scripts": {
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@4tw/cypress-drag-drop": "^2.3.0",
|
|
46
46
|
"@babel/preset-env": "^7.26.9",
|
|
47
|
-
"@vue/cli-service": "^5.0.
|
|
47
|
+
"@vue/cli-service": "^5.0.9",
|
|
48
48
|
"@vue/compiler-sfc": "^3.1.0",
|
|
49
49
|
"@vue/test-utils": "^2.4.6",
|
|
50
50
|
"@vue/vue3-jest": "^29.2.6",
|
|
@@ -57,5 +57,9 @@
|
|
|
57
57
|
"jest": "^29.2.6",
|
|
58
58
|
"jest-environment-jsdom": "^29.7.0",
|
|
59
59
|
"prettier": "2.8.1"
|
|
60
|
+
},
|
|
61
|
+
"overrides": {
|
|
62
|
+
"postcss": "8.4.31",
|
|
63
|
+
"webpack-dev-server": "5.2.1"
|
|
60
64
|
}
|
|
61
65
|
}
|
|
@@ -96,7 +96,8 @@
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
.download-file,
|
|
99
|
-
.search-icon
|
|
99
|
+
.search-icon,
|
|
100
|
+
.doc-faq {
|
|
100
101
|
color: variables.$toolbar-elements;
|
|
101
102
|
|
|
102
103
|
.is-active {
|
|
@@ -108,5 +109,8 @@
|
|
|
108
109
|
}
|
|
109
110
|
}
|
|
110
111
|
}
|
|
112
|
+
.doc-faq {
|
|
113
|
+
cursor: pointer;
|
|
114
|
+
}
|
|
111
115
|
}
|
|
112
116
|
}
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
v-for="(
|
|
49
49
|
annotationSet, indexGroup
|
|
50
50
|
) in getAnnotationsFiltered.annotationSets"
|
|
51
|
+
:id="`annset_${annotationSet.id}`"
|
|
51
52
|
:key="indexGroup"
|
|
52
53
|
:class="[
|
|
53
54
|
'annotation-set-group',
|
|
@@ -198,6 +199,7 @@ export default {
|
|
|
198
199
|
computed: {
|
|
199
200
|
...mapState("display", ["showAnnSetTable", "showBranding"]),
|
|
200
201
|
...mapState("edit", ["editMode"]),
|
|
202
|
+
...mapState("selection", ["annotationSetSelection"]),
|
|
201
203
|
...mapState("document", [
|
|
202
204
|
"annotationSets",
|
|
203
205
|
"documentId",
|
|
@@ -265,6 +267,27 @@ export default {
|
|
|
265
267
|
}
|
|
266
268
|
}
|
|
267
269
|
},
|
|
270
|
+
annotationSetSelection(newAnnotationSet) {
|
|
271
|
+
if (newAnnotationSet) {
|
|
272
|
+
const newAnnotationSetsAccordion = {
|
|
273
|
+
...this.annotationSetsAccordion,
|
|
274
|
+
};
|
|
275
|
+
newAnnotationSetsAccordion[
|
|
276
|
+
newAnnotationSet.id || newAnnotationSet.label_set.id
|
|
277
|
+
] = true;
|
|
278
|
+
this.annotationSetsAccordion = newAnnotationSetsAccordion;
|
|
279
|
+
|
|
280
|
+
// scroll to element
|
|
281
|
+
const annotationSetElement = document.getElementById(
|
|
282
|
+
`annset_${newAnnotationSet.id}`
|
|
283
|
+
);
|
|
284
|
+
if (annotationSetElement) {
|
|
285
|
+
annotationSetElement.scrollIntoView({ behavior: "smooth" });
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
this.$store.dispatch("selection/setAnnotationSetSelection", null);
|
|
289
|
+
}
|
|
290
|
+
},
|
|
268
291
|
},
|
|
269
292
|
created() {
|
|
270
293
|
window.addEventListener("keydown", this.keyDownHandler);
|
|
@@ -74,6 +74,16 @@
|
|
|
74
74
|
@mouseleave="onElementLeave"
|
|
75
75
|
/>
|
|
76
76
|
</v-group>
|
|
77
|
+
<template v-for="annotationSet in pageAnnotationSets">
|
|
78
|
+
<v-group>
|
|
79
|
+
<v-rect
|
|
80
|
+
:config="groupAnnotationRect(annotationSet)"
|
|
81
|
+
@click="handleClickedAnnotationSet(annotationSet)"
|
|
82
|
+
@mouseenter="onElementEnter(null, null)"
|
|
83
|
+
@mouseleave="onElementLeave"
|
|
84
|
+
/>
|
|
85
|
+
</v-group>
|
|
86
|
+
</template>
|
|
77
87
|
<template v-for="annotation in pageAnnotations">
|
|
78
88
|
<template
|
|
79
89
|
v-for="(bbox, index) in annotation.span.filter(
|
|
@@ -217,6 +227,7 @@ export default {
|
|
|
217
227
|
"isDocumentReadyToBeReviewed",
|
|
218
228
|
"isDocumentReviewed",
|
|
219
229
|
"labelOfAnnotation",
|
|
230
|
+
"annotationSetBoxForPageNumber",
|
|
220
231
|
]),
|
|
221
232
|
selectionPage() {
|
|
222
233
|
return this.selection && this.selection.pageNumber;
|
|
@@ -275,6 +286,31 @@ export default {
|
|
|
275
286
|
return annotations;
|
|
276
287
|
},
|
|
277
288
|
|
|
289
|
+
pageAnnotationSets() {
|
|
290
|
+
const annotationSets = [];
|
|
291
|
+
|
|
292
|
+
if (this.getAnnotationsFiltered.annotationSets) {
|
|
293
|
+
this.getAnnotationsFiltered.annotationSets.forEach((annotationSet) => {
|
|
294
|
+
let samePage = false;
|
|
295
|
+
annotationSet.labels.forEach((label) => {
|
|
296
|
+
label.annotations.forEach((annotation) => {
|
|
297
|
+
if (
|
|
298
|
+
annotation.span.find(
|
|
299
|
+
(span) => span.page_index + 1 === this.page.number
|
|
300
|
+
)
|
|
301
|
+
) {
|
|
302
|
+
samePage = true;
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
if (samePage) {
|
|
307
|
+
annotationSets.push(annotationSet);
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
return annotationSets;
|
|
312
|
+
},
|
|
313
|
+
|
|
278
314
|
selection() {
|
|
279
315
|
return this.$store.getters["selection/getSelectionForPage"](
|
|
280
316
|
this.page.number
|
|
@@ -372,6 +408,7 @@ export default {
|
|
|
372
408
|
if (
|
|
373
409
|
event.target.name() === "entity" ||
|
|
374
410
|
event.target.name() === "annotation" ||
|
|
411
|
+
event.target.name() === "annotationSet" ||
|
|
375
412
|
event.target.name() === "multiAnnBoxSelection" ||
|
|
376
413
|
event.target.name() === "multiAnnBoxTransformer" ||
|
|
377
414
|
event.target.name() === "multiAnnButton" ||
|
|
@@ -432,6 +469,13 @@ export default {
|
|
|
432
469
|
this.$store.dispatch("selection/entityClick", entity);
|
|
433
470
|
},
|
|
434
471
|
|
|
472
|
+
handleClickedAnnotationSet(annotationSet) {
|
|
473
|
+
this.$store.dispatch(
|
|
474
|
+
"selection/setAnnotationSetSelection",
|
|
475
|
+
annotationSet
|
|
476
|
+
);
|
|
477
|
+
},
|
|
478
|
+
|
|
435
479
|
onElementEnter(annotation = null, span = null) {
|
|
436
480
|
if (
|
|
437
481
|
!this.categorizeModalIsActive &&
|
|
@@ -551,7 +595,20 @@ export default {
|
|
|
551
595
|
name: "annotation",
|
|
552
596
|
draggable,
|
|
553
597
|
cornerRadius: 2,
|
|
554
|
-
...this.bboxToRect(this.page, bbox, focused),
|
|
598
|
+
...this.bboxToRect(this.page, bbox, focused ? 2 : 0),
|
|
599
|
+
};
|
|
600
|
+
},
|
|
601
|
+
groupAnnotationRect(annotationSet) {
|
|
602
|
+
const box = this.annotationSetBoxForPageNumber(annotationSet);
|
|
603
|
+
return {
|
|
604
|
+
fill: "#2f80ed",
|
|
605
|
+
globalCompositeOperation: "multiply",
|
|
606
|
+
strokeWidth: 1,
|
|
607
|
+
stroke: "black",
|
|
608
|
+
name: "annotationSet",
|
|
609
|
+
cornerRadius: 4,
|
|
610
|
+
opacity: 0.1,
|
|
611
|
+
...this.bboxToRect(this.page, box, 1),
|
|
555
612
|
};
|
|
556
613
|
},
|
|
557
614
|
getAnnotationLabelPosition(annotation) {
|
|
@@ -82,7 +82,36 @@
|
|
|
82
82
|
{{ `${currentPercentage}%` }}
|
|
83
83
|
</div>
|
|
84
84
|
</div>
|
|
85
|
+
<div v-if="!publicView" class="toolbar-divider" />
|
|
86
|
+
<div v-if="!publicView && !editMode">
|
|
87
|
+
<b-tooltip
|
|
88
|
+
class="doc-faq"
|
|
89
|
+
position="is-top"
|
|
90
|
+
:label="$t('document_faq_title')"
|
|
91
|
+
>
|
|
92
|
+
<b-icon
|
|
93
|
+
size="is-small"
|
|
94
|
+
icon="question"
|
|
95
|
+
@click="isFaqModalActive = true"
|
|
96
|
+
/>
|
|
97
|
+
</b-tooltip>
|
|
98
|
+
</div>
|
|
85
99
|
</div>
|
|
100
|
+
<section class="faq-modal">
|
|
101
|
+
<b-modal v-model="isFaqModalActive" :width="500">
|
|
102
|
+
<section class="modal-card-body">
|
|
103
|
+
<div class="content">
|
|
104
|
+
<h3>{{ $t("document_faq_title") }}</h3>
|
|
105
|
+
<ul>
|
|
106
|
+
<li>
|
|
107
|
+
<p>{{ $t("document_faq_content") }}</p>
|
|
108
|
+
</li>
|
|
109
|
+
</ul>
|
|
110
|
+
</div>
|
|
111
|
+
</section>
|
|
112
|
+
<footer class="modal-card-foot"></footer>
|
|
113
|
+
</b-modal>
|
|
114
|
+
</section>
|
|
86
115
|
</div>
|
|
87
116
|
</template>
|
|
88
117
|
|
|
@@ -112,6 +141,7 @@ export default {
|
|
|
112
141
|
toolbarModalOpen: true,
|
|
113
142
|
editModeDisabled: false,
|
|
114
143
|
tooltipInfo: null,
|
|
144
|
+
isFaqModalActive: false,
|
|
115
145
|
};
|
|
116
146
|
},
|
|
117
147
|
computed: {
|
|
@@ -139,7 +169,9 @@ export default {
|
|
|
139
169
|
},
|
|
140
170
|
scale(newScale) {
|
|
141
171
|
if (this.fitWidthScale > 0) {
|
|
142
|
-
this.currentPercentage = Math.round(
|
|
172
|
+
this.currentPercentage = Math.round(
|
|
173
|
+
(newScale / this.fitWidthScale) * 100
|
|
174
|
+
);
|
|
143
175
|
} else {
|
|
144
176
|
this.currentPercentage = Math.round(newScale * 100);
|
|
145
177
|
}
|
package/src/icons.js
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
faCircleExclamation,
|
|
28
28
|
faLink,
|
|
29
29
|
faSquareCheck,
|
|
30
|
+
faQuestion,
|
|
30
31
|
} from "@fortawesome/free-solid-svg-icons";
|
|
31
32
|
import { FontAwesomeIcon as Icons } from "@fortawesome/vue-fontawesome";
|
|
32
33
|
|
|
@@ -57,7 +58,8 @@ library.add(
|
|
|
57
58
|
faFloppyDisk,
|
|
58
59
|
faCircleExclamation,
|
|
59
60
|
faLink,
|
|
60
|
-
faSquareCheck
|
|
61
|
+
faSquareCheck,
|
|
62
|
+
faQuestion
|
|
61
63
|
);
|
|
62
64
|
|
|
63
65
|
export default Icons;
|
package/src/locales/en.json
CHANGED
|
@@ -168,5 +168,7 @@
|
|
|
168
168
|
"document_section": "Document Section",
|
|
169
169
|
"label_size": "Column size:",
|
|
170
170
|
"powered_by": "powered by Konfuzio",
|
|
171
|
-
"nav_label_anns": "Navigate through annotations"
|
|
171
|
+
"nav_label_anns": "Navigate through annotations",
|
|
172
|
+
"document_faq_title": "Document FAQ",
|
|
173
|
+
"document_faq_content": "Annotation Sets grouping only appear when there's no overlap between annotations."
|
|
172
174
|
}
|
package/src/store/display.js
CHANGED
|
@@ -153,7 +153,7 @@ const getters = {
|
|
|
153
153
|
},
|
|
154
154
|
bboxToRect:
|
|
155
155
|
(state, getters) =>
|
|
156
|
-
(page, bbox, hasPadding =
|
|
156
|
+
(page, bbox, hasPadding = 0) => {
|
|
157
157
|
const imageScale = getters.imageScale(page);
|
|
158
158
|
if (bbox.x0 && bbox.y0) {
|
|
159
159
|
const { x0, x1, y0, y1 } = bbox;
|
|
@@ -161,7 +161,7 @@ const getters = {
|
|
|
161
161
|
const rect = {
|
|
162
162
|
// left
|
|
163
163
|
x: new BigNumber(x0)
|
|
164
|
-
.minus(hasPadding
|
|
164
|
+
.minus(hasPadding)
|
|
165
165
|
.times(state.scale)
|
|
166
166
|
.times(imageScale)
|
|
167
167
|
.div(PIXEL_RATIO)
|
|
@@ -169,14 +169,14 @@ const getters = {
|
|
|
169
169
|
// top
|
|
170
170
|
y: pageHeight
|
|
171
171
|
.minus(new BigNumber(y1))
|
|
172
|
-
.minus(hasPadding
|
|
172
|
+
.minus(hasPadding)
|
|
173
173
|
.times(state.scale)
|
|
174
174
|
.times(imageScale)
|
|
175
175
|
.div(PIXEL_RATIO)
|
|
176
176
|
.toNumber(),
|
|
177
177
|
width: new BigNumber(x1)
|
|
178
178
|
.minus(x0)
|
|
179
|
-
.plus(hasPadding
|
|
179
|
+
.plus(hasPadding * 2)
|
|
180
180
|
.abs()
|
|
181
181
|
.times(state.scale)
|
|
182
182
|
.times(imageScale)
|
|
@@ -184,7 +184,7 @@ const getters = {
|
|
|
184
184
|
.toNumber(),
|
|
185
185
|
height: new BigNumber(y1)
|
|
186
186
|
.minus(y0)
|
|
187
|
-
.plus(hasPadding
|
|
187
|
+
.plus(hasPadding * 2)
|
|
188
188
|
.times(state.scale)
|
|
189
189
|
.times(imageScale)
|
|
190
190
|
.div(PIXEL_RATIO)
|
package/src/store/document.js
CHANGED
|
@@ -278,6 +278,89 @@ const getters = {
|
|
|
278
278
|
return foundAnnotationSet;
|
|
279
279
|
},
|
|
280
280
|
|
|
281
|
+
/* Get annotation set box to cover all annotations */
|
|
282
|
+
annotationSetBoxForPageNumber: (state, getters) => (annotationSet) => {
|
|
283
|
+
let box = {
|
|
284
|
+
x0: null,
|
|
285
|
+
y0: null,
|
|
286
|
+
x1: null,
|
|
287
|
+
y1: null,
|
|
288
|
+
};
|
|
289
|
+
const annotationIdsOfAnnSet = [];
|
|
290
|
+
annotationSet.labels.forEach((label) => {
|
|
291
|
+
if (getters.isLabelMultiFalseAndGroupOfAnns(label)) {
|
|
292
|
+
if (label.annotations && label.annotations[0]) {
|
|
293
|
+
const annotation = label.annotations[0];
|
|
294
|
+
annotation.span.forEach((span) => {
|
|
295
|
+
if (!box.x0 || box.x0 > span.x0) {
|
|
296
|
+
box.x0 = span.x0;
|
|
297
|
+
}
|
|
298
|
+
if (!box.x1 || box.x1 < span.x1) {
|
|
299
|
+
box.x1 = span.x1;
|
|
300
|
+
}
|
|
301
|
+
if (!box.y0 || box.y0 > span.y0) {
|
|
302
|
+
box.y0 = span.y0;
|
|
303
|
+
}
|
|
304
|
+
if (!box.y1 || box.y1 < span.y1) {
|
|
305
|
+
box.y1 = span.y1;
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
// add all annotations to not be checked
|
|
310
|
+
label.annotations.forEach((annotation) => {
|
|
311
|
+
annotationIdsOfAnnSet.push(annotation.id);
|
|
312
|
+
});
|
|
313
|
+
} else {
|
|
314
|
+
label.annotations.forEach((annotation) => {
|
|
315
|
+
annotationIdsOfAnnSet.push(annotation.id);
|
|
316
|
+
annotation.span.forEach((span) => {
|
|
317
|
+
if (!box.x0 || box.x0 > span.x0) {
|
|
318
|
+
box.x0 = span.x0;
|
|
319
|
+
}
|
|
320
|
+
if (!box.x1 || box.x1 < span.x1) {
|
|
321
|
+
box.x1 = span.x1;
|
|
322
|
+
}
|
|
323
|
+
if (!box.y0 || box.y0 > span.y0) {
|
|
324
|
+
box.y0 = span.y0;
|
|
325
|
+
}
|
|
326
|
+
if (!box.y1 || box.y1 < span.y1) {
|
|
327
|
+
box.y1 = span.y1;
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// check if doesn't cover any other annotation
|
|
335
|
+
if (box.x0) {
|
|
336
|
+
state.annotations.forEach((annotation) => {
|
|
337
|
+
// don't check for annotations of the intended annotation set
|
|
338
|
+
if (!annotationIdsOfAnnSet.includes(annotation.id)) {
|
|
339
|
+
annotation.span.forEach((span) => {
|
|
340
|
+
if (
|
|
341
|
+
span.x0 >= box.x0 &&
|
|
342
|
+
span.x1 <= box.x1 &&
|
|
343
|
+
span.y0 >= box.y0 &&
|
|
344
|
+
span.y1 <= box.y1
|
|
345
|
+
) {
|
|
346
|
+
box = {
|
|
347
|
+
x0: null,
|
|
348
|
+
y0: null,
|
|
349
|
+
x1: null,
|
|
350
|
+
y1: null,
|
|
351
|
+
};
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
if (!box.x0) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
return box;
|
|
362
|
+
},
|
|
363
|
+
|
|
281
364
|
/* Get label for a given annotation */
|
|
282
365
|
labelOfAnnotation: (state) => (annotationToFind) => {
|
|
283
366
|
let foundLabel = null;
|
package/src/store/selection.js
CHANGED
|
@@ -15,6 +15,7 @@ const state = {
|
|
|
15
15
|
placeholderSelection: [],
|
|
16
16
|
selectedEntities: [],
|
|
17
17
|
spanLoading: false,
|
|
18
|
+
annotationSetSelection: null,
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
const getters = {
|
|
@@ -238,6 +239,9 @@ const actions = {
|
|
|
238
239
|
setPlaceholderSelection: ({ commit }, span) => {
|
|
239
240
|
commit("SET_PLACEHOLDER_SELECTION", span);
|
|
240
241
|
},
|
|
242
|
+
setAnnotationSetSelection: ({ commit }, annotationSet) => {
|
|
243
|
+
commit("SET_ANNOTATION_SET_SELECTED", annotationSet);
|
|
244
|
+
},
|
|
241
245
|
};
|
|
242
246
|
|
|
243
247
|
const mutations = {
|
|
@@ -295,6 +299,9 @@ const mutations = {
|
|
|
295
299
|
SET_SPAN_LOADING: (state, loading) => {
|
|
296
300
|
state.spanLoading = loading;
|
|
297
301
|
},
|
|
302
|
+
SET_ANNOTATION_SET_SELECTED: (state, annotationSet) => {
|
|
303
|
+
state.annotationSetSelection = annotationSet;
|
|
304
|
+
},
|
|
298
305
|
};
|
|
299
306
|
|
|
300
307
|
export default {
|