@konfuzio/document-validation-ui 0.1.21-dev.0 → 0.1.21

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/jest.config.js CHANGED
@@ -1,4 +1,5 @@
1
1
  module.exports = {
2
2
  preset: "@vue/cli-plugin-unit-jest/presets/no-babel",
3
3
  setupFiles: ["./tests/setup.js"],
4
+ transformIgnorePatterns: ["node_modules/(?!axios)"],
4
5
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@konfuzio/document-validation-ui",
3
- "version": "0.1.21-dev.0",
3
+ "version": "0.1.21",
4
4
  "repository": "git://github.com:konfuzio-ai/document-validation-ui.git",
5
5
  "main": "dist/app.js",
6
6
  "scripts": {
@@ -29,8 +29,7 @@
29
29
  "@fortawesome/vue-fontawesome": "^2.0.10",
30
30
  "@sentry/tracing": "^6.19.4",
31
31
  "@sentry/vue": "^6.2.0",
32
- "axios": "^0.21.1",
33
- "axios-extensions": "^3.1.6",
32
+ "axios": "^1.6.0",
34
33
  "bignumber.js": "^9.1.0",
35
34
  "buefy": "^0.9.22",
36
35
  "konva": "^8.3.13",
package/src/api.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import axios from "axios";
2
- import { cacheAdapterEnhancer } from "axios-extensions";
3
2
 
4
3
  let HTTP, FILE_REQUEST, authToken, appLocale;
5
4
  const DEFAULT_URL = "https://app.konfuzio.com";
@@ -15,7 +14,6 @@ HTTP = axios.create({
15
14
  FILE_REQUEST = axios.create({
16
15
  baseURL: FILE_URL || `${DEFAULT_URL}`,
17
16
  responseType: "blob",
18
- adapter: cacheAdapterEnhancer(axios.defaults.adapter),
19
17
  });
20
18
 
21
19
  const setAuthToken = (token) => {
@@ -78,5 +76,5 @@ export default {
78
76
  setLocale,
79
77
  FILE_REQUEST,
80
78
  DEFAULT_URL,
81
- FILE_URL
79
+ FILE_URL,
82
80
  };
@@ -14,6 +14,10 @@
14
14
  padding: 8px;
15
15
  cursor: default;
16
16
 
17
+ &.small {
18
+ height: 142px;
19
+ }
20
+
17
21
  .popup-input {
18
22
  background-color: $text-color;
19
23
  color: $white;
@@ -11,8 +11,8 @@
11
11
  editAnnotation &&
12
12
  editAnnotation.id === annotation.id &&
13
13
  'error-editing',
14
- isAnnotationBeingEdited && 'clicked-ann'
15
- ]"
14
+ isAnnotationBeingEdited && 'clicked-ann',
15
+ ]"
16
16
  role="textbox"
17
17
  :contenteditable="isAnnotationBeingEdited"
18
18
  @click="handleEditAnnotation"
@@ -119,9 +119,10 @@ export default {
119
119
  .dispatch("document/setEditAnnotation", {
120
120
  id: this.annotation.id,
121
121
  index: this.spanIndex,
122
- label: this.label.id,
123
- labelSet: this.annotationSet.label_set.id,
124
- annotationSet: this.annotationSet.id,
122
+ label: this.label,
123
+ labelSet: this.annotationSet.label_set,
124
+ annotationSet: this.annotationSet,
125
+ pageNumber: this.span.page_index + 1,
125
126
  })
126
127
  .then(() => {
127
128
  this.$refs.contentEditable.focus();
@@ -420,9 +420,9 @@ export default {
420
420
  missing = [
421
421
  {
422
422
  document: parseInt(this.documentId),
423
- label: this.editAnnotation.label,
424
- label_set: this.editAnnotation.labelSet,
425
- annotation_set: this.editAnnotation.annotationSet,
423
+ label: this.editAnnotation.label.id,
424
+ label_set: this.editAnnotation.labelSet.id,
425
+ annotation_set: this.editAnnotation.annotationSet.id,
426
426
  },
427
427
  ];
428
428
  } else if (annotationSet && markAllMissing) {
@@ -152,9 +152,9 @@ export default {
152
152
  this.$store.dispatch("document/setEditAnnotation", {
153
153
  id: this.emptyAnnotationId(),
154
154
  index: this.spanIndex,
155
- label: this.label.id,
156
- labelSet: this.annotationSet.label_set.id,
157
- annotationSet: this.annotationSet.id,
155
+ label: this.label,
156
+ labelSet: this.annotationSet.label_set,
157
+ annotationSet: this.annotationSet,
158
158
  });
159
159
  }
160
160
  },
@@ -18,6 +18,17 @@
18
18
  :container-height="scaledViewport.height"
19
19
  @close="closePopups"
20
20
  />
21
+ <EditAnnotation
22
+ v-if="
23
+ editAnnotation &&
24
+ editAnnotation.pageNumber &&
25
+ editAnnotation.pageNumber === currentPage
26
+ "
27
+ :edit-annotation="editAnnotation"
28
+ :page="page"
29
+ :container-width="scaledViewport.width"
30
+ :container-height="scaledViewport.height"
31
+ />
21
32
 
22
33
  <AnnSetTableOptions v-if="showAnnSetTable" :page="page" />
23
34
 
@@ -127,6 +138,7 @@ import api from "../../api";
127
138
  import BoxSelection from "./BoxSelection";
128
139
  import MultiAnnSelection from "./MultiAnnSelection";
129
140
  import NewAnnotation from "./NewAnnotation";
141
+ import EditAnnotation from "./EditAnnotation";
130
142
  import AnnSetTableOptions from "./AnnSetTableOptions";
131
143
 
132
144
  export default {
@@ -135,6 +147,7 @@ export default {
135
147
  BoxSelection,
136
148
  MultiAnnSelection,
137
149
  NewAnnotation,
150
+ EditAnnotation,
138
151
  AnnSetTableOptions,
139
152
  },
140
153
 
@@ -498,6 +511,7 @@ export default {
498
511
  if (this.newAnnotation && this.newAnnotation.length > 0) {
499
512
  entityIsSelected = this.newAnnotation.find((selectedEntity) => {
500
513
  return (
514
+ selectedEntity.original &&
501
515
  selectedEntity.original.offset_string ===
502
516
  entity.original.offset_string &&
503
517
  selectedEntity.original.x0 === entity.original.x0 &&
@@ -0,0 +1,340 @@
1
+ <template>
2
+ <div
3
+ v-if="annotation"
4
+ class="annotation-popup small"
5
+ :style="{ left: `${left}px`, top: `${top}px` }"
6
+ >
7
+ <b-dropdown
8
+ v-model="selectedSet"
9
+ aria-role="list"
10
+ :class="[
11
+ 'annotation-dropdown',
12
+ 'no-padding-bottom',
13
+ setsList.length === 0 ? 'no-padding-top' : '',
14
+ ]"
15
+ scrollable
16
+ >
17
+ <template #trigger>
18
+ <b-button
19
+ :class="['popup-input', selectedSet ? '' : 'not-selected']"
20
+ type="is-text"
21
+ >
22
+ {{
23
+ selectedSet
24
+ ? `${selectedSet.label_set.name} ${
25
+ selectedSet.id
26
+ ? numberOfAnnotationSetGroup(selectedSet)
27
+ : `(${$t("new")})`
28
+ }`
29
+ : $t("select_annotation_set")
30
+ }}
31
+ <span class="caret-icon">
32
+ <b-icon icon="angle-down" size="is-small" class="caret" />
33
+ </span>
34
+ </b-button>
35
+ </template>
36
+ <b-dropdown-item
37
+ v-for="(set, index) in setsList"
38
+ :key="`${set.label_set.id}_${index}`"
39
+ aria-role="listitem"
40
+ :value="set"
41
+ >
42
+ <span>{{
43
+ `${set.label_set.name} ${
44
+ set.id ? numberOfAnnotationSetGroup(set) : `(${$t("new")})`
45
+ }`
46
+ }}</span>
47
+ </b-dropdown-item>
48
+ <b-button
49
+ type="is-ghost"
50
+ :class="[
51
+ 'add-ann-set',
52
+ 'dropdown-item',
53
+ 'no-icon-margin',
54
+ setsList.length > 0 ? 'has-border' : '',
55
+ ]"
56
+ icon-left="plus"
57
+ @click="openAnnotationSetCreation"
58
+ >
59
+ {{ $t("new_ann_set_title") }}
60
+ </b-button>
61
+ </b-dropdown>
62
+ <b-tooltip
63
+ multilined
64
+ :active="selectedSet && (!labelsFiltered || labelsFiltered.length === 0)"
65
+ size="is-large"
66
+ position="is-bottom"
67
+ class="bottom-aligned"
68
+ :close-delay="5000"
69
+ >
70
+ <b-dropdown
71
+ v-if="selectedLabel"
72
+ v-model="selectedLabel"
73
+ aria-role="list"
74
+ :disabled="!labelsFiltered || labelsFiltered.length === 0"
75
+ scrollable
76
+ class="label-dropdown annotation-dropdown"
77
+ >
78
+ <template #trigger>
79
+ <b-button
80
+ class="popup-input"
81
+ :disabled="!labelsFiltered"
82
+ type="is-text"
83
+ >
84
+ {{ selectedLabel.name }}
85
+ <span class="caret-icon">
86
+ <b-icon icon="angle-down" size="is-small" class="caret" />
87
+ </span>
88
+ </b-button>
89
+ </template>
90
+ <b-dropdown-item
91
+ v-for="label in labelsFiltered"
92
+ :key="label.id"
93
+ aria-role="listitem"
94
+ :value="label"
95
+ >
96
+ <span>{{ label.name }}</span>
97
+ </b-dropdown-item>
98
+ </b-dropdown>
99
+ </b-tooltip>
100
+ <div class="annotation-buttons">
101
+ <b-button
102
+ type="is-text"
103
+ class="cancel-button popup-button primary-button"
104
+ :label="$t('cancel')"
105
+ :disabled="loading"
106
+ @click.prevent="close"
107
+ />
108
+ <b-button
109
+ type="is-primary"
110
+ class="popup-button primary-button"
111
+ :label="$t('save')"
112
+ :disabled="loading || !spanSelection || !selectedLabel"
113
+ @click.prevent="save"
114
+ />
115
+ </div>
116
+ </div>
117
+ </template>
118
+ <script>
119
+ /**
120
+ * This component is used to show a popup
121
+ * for creating a new annotation.
122
+ */
123
+ const heightOfPopup = 142;
124
+ const margin = 12;
125
+ const widthOfPopup = 205;
126
+
127
+ import { mapGetters, mapState } from "vuex";
128
+ import { MULTI_ANN_TABLE_FEATURE } from "../../constants";
129
+
130
+ export default {
131
+ props: {
132
+ editAnnotation: {
133
+ required: true,
134
+ type: Object,
135
+ },
136
+ page: {
137
+ required: true,
138
+ type: Object,
139
+ },
140
+ containerWidth: {
141
+ type: Number,
142
+ required: true,
143
+ },
144
+ containerHeight: {
145
+ type: Number,
146
+ required: true,
147
+ },
148
+ },
149
+ data() {
150
+ return {
151
+ annotation: null,
152
+ selectedLabel: null,
153
+ selectedSet: null,
154
+ labelsFiltered: null,
155
+ loading: false,
156
+ isAnnSetModalShowing: false,
157
+ setsList: [],
158
+ };
159
+ },
160
+ computed: {
161
+ ...mapState("document", [
162
+ "annotationSets",
163
+ "annotations",
164
+ "labels",
165
+ "documentId",
166
+ ]),
167
+ ...mapGetters("document", [
168
+ "numberOfAnnotationSetGroup",
169
+ "labelsFilteredForAnnotationCreation",
170
+ "isNegative",
171
+ ]),
172
+ ...mapGetters("display", ["bboxToRect"]),
173
+ ...mapState("selection", ["selection", "spanSelection"]),
174
+ top() {
175
+ const top = this.selection.start.y - heightOfPopup; // subtract the height of the popup plus some margin
176
+
177
+ const height = this.selection.end.y - this.selection.start.y;
178
+
179
+ //check if the popup will not go off the container on the top
180
+ return this.selection.start.y > heightOfPopup
181
+ ? top
182
+ : this.selection.start.y + height + margin;
183
+ },
184
+ left() {
185
+ const width = this.selection.end.x - this.selection.start.x;
186
+
187
+ const left = this.selection.start.x + width / 2 - widthOfPopup / 2; // add the entity half width to be centered and then subtract half the width of the popup
188
+
189
+ //check if the popup will not go off the container
190
+ if (left + widthOfPopup > this.containerWidth) {
191
+ // on the right side
192
+ return this.containerWidth - widthOfPopup;
193
+ } else {
194
+ // on the left side
195
+ return left > 0 ? left : 0;
196
+ }
197
+ },
198
+ },
199
+ watch: {
200
+ selectedSet(newValue) {
201
+ this.labelsFiltered = this.labelsFilteredForAnnotationCreation(newValue);
202
+ },
203
+ editAnnotation() {
204
+ this.loadInfo();
205
+ },
206
+ },
207
+ mounted() {
208
+ this.loadInfo();
209
+
210
+ setTimeout(() => {
211
+ // prevent click propagation when opening the popup
212
+ document.body.addEventListener("click", this.clickOutside);
213
+ }, 200);
214
+ },
215
+ destroyed() {
216
+ document.body.removeEventListener("click", this.clickOutside);
217
+ },
218
+ methods: {
219
+ loadInfo() {
220
+ this.setsList = [...this.annotationSets];
221
+
222
+ this.selectedSet = this.annotationSets.find(
223
+ (annSet) => annSet.id === this.editAnnotation.annotationSet.id
224
+ );
225
+
226
+ this.labelsFiltered = this.labelsFilteredForAnnotationCreation(
227
+ this.selectedSet
228
+ );
229
+
230
+ // if existing label is not able to be chosen we add it manually
231
+ if (!this.labelsFiltered.includes(this.editAnnotation.label)) {
232
+ this.labelsFiltered.push(this.editAnnotation.label);
233
+ }
234
+
235
+ this.selectedLabel = this.editAnnotation.label;
236
+
237
+ this.annotation = this.annotations.find(
238
+ (ann) => ann.id === this.editAnnotation.id
239
+ );
240
+ },
241
+ close() {
242
+ this.$store.dispatch("document/resetEditAnnotation");
243
+ this.$store.dispatch("selection/disableSelection");
244
+ this.$store.dispatch("selection/setSelectedEntities", null);
245
+ this.$emit("close");
246
+ },
247
+ async save() {
248
+ this.loading = true;
249
+
250
+ if (
251
+ this.editAnnotation.labelSet.id !== this.selectedSet.id ||
252
+ this.editAnnotation.label.id !== this.selectedLabel.id
253
+ ) {
254
+ // first delete annotation, then create new one
255
+ await this.$store
256
+ .dispatch("document/deleteAnnotation", {
257
+ annotationId: this.annotation.id,
258
+ })
259
+ .catch((error) => {
260
+ this.$store.dispatch("document/createErrorMessage", {
261
+ error,
262
+ serverErrorMessage: this.$t("server_error"),
263
+ defaultErrorMessage: this.$t("edit_error"),
264
+ });
265
+ });
266
+
267
+ const spans = this.annotation.span;
268
+ spans[this.editAnnotation.index] = this.spanSelection;
269
+
270
+ const annotationToCreate = {
271
+ document: this.documentId,
272
+ span: spans,
273
+ label: this.selectedLabel.id,
274
+ is_correct: true,
275
+ revised: false,
276
+ };
277
+
278
+ if (this.selectedSet.id) {
279
+ annotationToCreate.annotation_set = this.selectedSet.id;
280
+ } else {
281
+ annotationToCreate.label_set = this.selectedSet.label_set.id;
282
+ }
283
+
284
+ // check if the selected label already has a negative annotation
285
+ let negativeAnnotationId;
286
+
287
+ if (
288
+ this.selectedLabel.annotations &&
289
+ this.selectedLabel.annotations.length > 0
290
+ ) {
291
+ const negativeAnnotation = this.selectedLabel.annotations.find(
292
+ (annotation) => this.isNegative(annotation)
293
+ );
294
+
295
+ if (negativeAnnotation) {
296
+ negativeAnnotationId = negativeAnnotation.id;
297
+ }
298
+ }
299
+
300
+ this.$store
301
+ .dispatch("document/createAnnotation", {
302
+ annotation: annotationToCreate,
303
+ negativeAnnotationId: negativeAnnotationId,
304
+ })
305
+ .catch((error) => {
306
+ this.$store.dispatch("document/createErrorMessage", {
307
+ error,
308
+ serverErrorMessage: this.$t("server_error"),
309
+ defaultErrorMessage: this.$t("error_creating_annotation"),
310
+ });
311
+ })
312
+ .finally(() => {
313
+ this.close();
314
+ this.loading = false;
315
+ });
316
+ } else {
317
+ this.close();
318
+ }
319
+ },
320
+ chooseLabelSet(labelSet) {
321
+ const newSet = {
322
+ label_set: labelSet,
323
+ labels: labelSet.labels,
324
+ id: null,
325
+ };
326
+ this.setsList.push(newSet);
327
+ this.selectedSet = newSet;
328
+ },
329
+ openAnnotationSetCreation() {
330
+ this.$store.dispatch("display/showChooseLabelSetModal", {
331
+ show: true,
332
+ isMultipleAnnotations: MULTI_ANN_TABLE_FEATURE,
333
+ finish: this.chooseLabelSet,
334
+ });
335
+ },
336
+ },
337
+ };
338
+ </script>
339
+
340
+ <style scoped lang="scss" src="../../assets/scss/new_annotation.scss"></style>
@@ -112,7 +112,7 @@ export default {
112
112
  this.$nextTick(() => {
113
113
  // Scroll to the annotation
114
114
  this.scrollTo(
115
- this.getYForBbox(this.documentAnnotationSelected.span),
115
+ this.getYForBbox(this.documentAnnotationSelected.span) - 100, // offset for edit annotation popup
116
116
  this.getXForBbox(this.documentAnnotationSelected.span)
117
117
  );
118
118
  });
@@ -90,39 +90,42 @@ const getters = {
90
90
  (state, getters) =>
91
91
  (page, bbox, hasOffset = false) => {
92
92
  const imageScale = getters.imageScale(page);
93
- const { x0, x1, y0, y1 } = bbox;
94
- const pageHeight = new BigNumber(page.original_size[1]);
95
- const rect = {
96
- // left
97
- x: new BigNumber(x0)
98
- .minus(hasOffset ? 1 : 0)
99
- .times(state.scale)
100
- .times(imageScale)
101
- .div(PIXEL_RATIO)
102
- .toNumber(),
103
- // top
104
- y: pageHeight
105
- .minus(new BigNumber(y1))
106
- .minus(hasOffset ? 17.1 : 0)
107
- .times(state.scale)
108
- .times(imageScale)
109
- .div(PIXEL_RATIO)
110
- .toNumber(),
111
- width: new BigNumber(x1)
112
- .minus(x0)
113
- .abs()
114
- .times(state.scale)
115
- .times(imageScale)
116
- .div(PIXEL_RATIO)
117
- .toNumber(),
118
- height: new BigNumber(y1)
119
- .minus(y0)
120
- .times(state.scale)
121
- .times(imageScale)
122
- .div(PIXEL_RATIO)
123
- .toNumber(),
124
- };
125
- return rect;
93
+ if (bbox.x0 && bbox.y0) {
94
+ const { x0, x1, y0, y1 } = bbox;
95
+ const pageHeight = new BigNumber(page.original_size[1]);
96
+ const rect = {
97
+ // left
98
+ x: new BigNumber(x0)
99
+ .minus(hasOffset ? 1 : 0)
100
+ .times(state.scale)
101
+ .times(imageScale)
102
+ .div(PIXEL_RATIO)
103
+ .toNumber(),
104
+ // top
105
+ y: pageHeight
106
+ .minus(new BigNumber(y1))
107
+ .minus(hasOffset ? 17.1 : 0)
108
+ .times(state.scale)
109
+ .times(imageScale)
110
+ .div(PIXEL_RATIO)
111
+ .toNumber(),
112
+ width: new BigNumber(x1)
113
+ .minus(x0)
114
+ .abs()
115
+ .times(state.scale)
116
+ .times(imageScale)
117
+ .div(PIXEL_RATIO)
118
+ .toNumber(),
119
+ height: new BigNumber(y1)
120
+ .minus(y0)
121
+ .times(state.scale)
122
+ .times(imageScale)
123
+ .div(PIXEL_RATIO)
124
+ .toNumber(),
125
+ };
126
+ return rect;
127
+ }
128
+ return { x: 0, y: 0, width: 0, height: 0 };
126
129
  },
127
130
  clientToBbox: (state, getters) => (page, start, end) => {
128
131
  /**