@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@konfuzio/document-validation-ui",
3
- "version": "0.2.6-dev.1",
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.8",
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
  }
@@ -600,6 +600,7 @@
600
600
  }
601
601
  &:before {
602
602
  border-bottom-color: variables.$text-color !important;
603
+ border-top-color: variables.$text-color !important;
603
604
  }
604
605
  }
605
606
 
@@ -441,7 +441,9 @@ export default {
441
441
  inline: "nearest",
442
442
  });
443
443
  };
444
- runAnimation();
444
+ setTimeout(() => {
445
+ runAnimation();
446
+ }, 300);
445
447
  } else {
446
448
  this.isSelected = false;
447
449
  }
@@ -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((newScale / this.fitWidthScale) * 100);
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;
@@ -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
  }
@@ -153,7 +153,7 @@ const getters = {
153
153
  },
154
154
  bboxToRect:
155
155
  (state, getters) =>
156
- (page, bbox, hasPadding = false) => {
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 ? 1 : 0)
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 ? 1 : 0)
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 ? 2 : 0)
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 ? 2 : 0)
187
+ .plus(hasPadding * 2)
188
188
  .times(state.scale)
189
189
  .times(imageScale)
190
190
  .div(PIXEL_RATIO)
@@ -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;
@@ -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 {