@konfuzio/document-validation-ui 0.1.59-dev.3 → 0.1.60-dev.0

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 (65) hide show
  1. package/cypress.config.js +6 -6
  2. package/dist/css/app.css +1 -1
  3. package/dist/index.html +1 -1
  4. package/dist/js/app.js +1 -1
  5. package/dist/js/app.js.map +1 -1
  6. package/dist/js/chunk-vendors.js +23 -66
  7. package/dist/js/chunk-vendors.js.map +1 -1
  8. package/jest.config.js +2 -22
  9. package/package.json +38 -32
  10. package/src/api.js +12 -0
  11. package/src/assets/scss/ann_set_table_options.scss +4 -4
  12. package/src/assets/scss/annotation_action_buttons.scss +7 -7
  13. package/src/assets/scss/annotation_details.scss +9 -9
  14. package/src/assets/scss/choose_label_set_modal.scss +5 -5
  15. package/src/assets/scss/document_action_bar.scss +3 -3
  16. package/src/assets/scss/document_annotations.scss +43 -45
  17. package/src/assets/scss/document_category.scss +8 -8
  18. package/src/assets/scss/document_dashboard.scss +1 -1
  19. package/src/assets/scss/document_edit.scss +30 -30
  20. package/src/assets/scss/document_error.scss +6 -6
  21. package/src/assets/scss/document_name.scss +6 -6
  22. package/src/assets/scss/document_page.scss +3 -3
  23. package/src/assets/scss/document_search_bar.scss +7 -7
  24. package/src/assets/scss/document_set_chooser.scss +3 -3
  25. package/src/assets/scss/document_thumbnails.scss +7 -7
  26. package/src/assets/scss/document_toolbar.scss +10 -10
  27. package/src/assets/scss/document_top_bar.scss +11 -11
  28. package/src/assets/scss/document_viewport_modal.scss +3 -3
  29. package/src/assets/scss/documents_list.scss +12 -11
  30. package/src/assets/scss/edit_page_thumbnail.scss +6 -6
  31. package/src/assets/scss/empty_state.scss +4 -4
  32. package/src/assets/scss/error_page.scss +2 -2
  33. package/src/assets/scss/extracting_data.scss +3 -3
  34. package/src/assets/scss/imports.scss +1 -0
  35. package/src/assets/scss/multi_ann_table_overlay.scss +3 -3
  36. package/src/assets/scss/multi_ann_table_popup.scss +1 -1
  37. package/src/assets/scss/new_annotation.scss +19 -25
  38. package/src/assets/scss/scrolling_document.scss +1 -1
  39. package/src/assets/scss/theme.scss +52 -64
  40. package/src/assets/scss/variables.scss +0 -2
  41. package/src/components/App.vue +14 -9
  42. package/src/components/DocumentAnnotations/AnnotationActionButtons.vue +6 -4
  43. package/src/components/DocumentAnnotations/AnnotationContent.vue +52 -25
  44. package/src/components/DocumentAnnotations/AnnotationRow.vue +50 -106
  45. package/src/components/DocumentAnnotations/DocumentAnnotations.vue +6 -12
  46. package/src/components/DocumentAnnotations/EmptyAnnotation.vue +70 -31
  47. package/src/components/DocumentDashboard.vue +17 -12
  48. package/src/components/DocumentEdit/EditPages.vue +46 -51
  49. package/src/components/DocumentPage/BoxSelection.vue +49 -16
  50. package/src/components/DocumentPage/DocumentPage.vue +153 -56
  51. package/src/components/DocumentPage/DocumentToolbar.vue +15 -5
  52. package/src/components/DocumentPage/EditAnnotation.vue +372 -0
  53. package/src/components/DocumentPage/{AnnotationPopup.vue → NewAnnotation.vue} +94 -122
  54. package/src/components/DocumentPage/ScrollingPage.vue +10 -2
  55. package/src/components/DocumentThumbnails/LoadingThumbnail.vue +6 -3
  56. package/src/components/DocumentTopBar/DocumentTopBar.vue +2 -4
  57. package/src/constants.js +7 -1
  58. package/src/i18n.js +5 -2
  59. package/src/main.js +16 -14
  60. package/src/store/display.js +24 -38
  61. package/src/store/document.js +6 -1
  62. package/src/store/index.js +8 -5
  63. package/src/store/selection.js +76 -152
  64. package/src/components/DocumentPage/PlaceholderSelection.vue +0 -51
  65. package/src/components/DocumentPage/SpanSelection.vue +0 -259
@@ -9,24 +9,30 @@ const state = {
9
9
  pageNumber: null,
10
10
  start: null,
11
11
  end: null,
12
+ custom: false, // if the box was created by user in document or it comes from an annotation
13
+ placeholderBox: null, // show a not editable placeholder box
12
14
  },
13
15
  isSelecting: false,
14
- spanSelection: [],
15
- placeholderSelection: [],
16
- selectedEntities: [],
17
- spanLoading: false,
16
+ spanSelection: null,
17
+ elementSelected: null, // selected element id
18
+ selectedEntities: null,
18
19
  };
19
20
 
20
21
  const getters = {
22
+ isElementSelected: (state) => {
23
+ return state.elementSelected;
24
+ },
25
+ isSelecting: (state) => {
26
+ return state.isSelecting;
27
+ },
21
28
  isSelectionValid: (state) => {
22
29
  /**
23
30
  * `endSelection` will reset everything in case of invalid selection.
24
31
  * Check the existence of `selection.end` before requesting the
25
32
  * content from the backend.
26
33
  * */
27
- return state.selection && state.selection.end != null;
34
+ return state.selection && state.selection.end;
28
35
  },
29
-
30
36
  getSelectionForPage: (state) => (pageNumber) => {
31
37
  if (state.selection.pageNumber === pageNumber) {
32
38
  return state.selection;
@@ -46,24 +52,19 @@ const getters = {
46
52
  box.y1 >= entity.y1
47
53
  );
48
54
  },
49
- spanSelectionsForPage: (state) => (page) => {
50
- return state.spanSelection.filter(
51
- (span) => page.number === span.page_index + 1
52
- );
53
- },
54
- placeholderSelectionForPage: (state) => (page) => {
55
- return state.placeholderSelection.filter(
56
- (span) => page.number === span.page_index + 1
57
- );
58
- },
59
55
  };
60
56
 
61
57
  const actions = {
58
+ selectElement: ({ commit }, value) => {
59
+ commit("RESET_SELECTION");
60
+ commit("SET_SPAN_SELECTION", null);
61
+ commit("ELEMENT_SELECTED", value);
62
+ },
63
+
62
64
  disableSelection: ({ commit }) => {
65
+ commit("ELEMENT_SELECTED", null);
63
66
  commit("RESET_SELECTION");
64
- commit("SET_SELECTED_ENTITIES", []);
65
- commit("SET_SPAN_SELECTION", []);
66
- commit("SET_PLACEHOLDER_SELECTION", []);
67
+ commit("SET_SPAN_SELECTION", null);
67
68
  },
68
69
 
69
70
  startSelection: ({ commit }, { pageNumber, start }) => {
@@ -80,6 +81,8 @@ const actions = {
80
81
  if (xDiff > 5 && yDiff > 5) {
81
82
  commit("MOVE_SELECTION", points);
82
83
  }
84
+
85
+ commit("SET_SELECTED_ENTITIES", null);
83
86
  },
84
87
 
85
88
  endSelection: ({ commit, state }, end) => {
@@ -110,138 +113,78 @@ const actions = {
110
113
  }
111
114
  },
112
115
 
113
- setSelectedEntities: ({ commit }, entities) => {
114
- commit("SET_SELECTED_ENTITIES", entities);
116
+ setSelection: ({ commit }, { span, selection }) => {
117
+ commit("SET_SELECTION", selection);
118
+ commit("SET_SPAN_SELECTION", span);
115
119
  },
116
120
 
117
- getTextFromBboxes: ({ commit, rootState }, span) => {
118
- commit("SET_SPAN_LOADING", true);
119
- return new Promise((resolve, reject) => {
120
- HTTP.post(`documents/${rootState.document.documentId}/bbox/`, {
121
- span,
122
- })
123
- .then((response) => {
124
- if (response.data.span.length && response.data.span.length > 0) {
125
- /**
126
- * If we have a non-empty bboxes list, we assume there
127
- * is text here on the backend, so we just set
128
- * spanSelection to the response.
129
- */
130
- resolve(response.data.span);
131
- } else {
132
- /**
133
- * Otherwise, we assume the backend can't identify text
134
- * on this area, so we set our bbox into spanSelection
135
- * ready to be passed back to the backend when creating
136
- * an annotation on this empty area, adding the offset_string
137
- * attribute, ready to be filled.
138
- */
139
- resolve(span);
140
- }
141
- })
142
- .catch((error) => {
143
- alert("Could not fetch the selected text from the backend");
144
- reject(error);
145
- })
146
- .finally(() => {
147
- commit("SET_SPAN_LOADING", false);
148
- });
149
- });
121
+ setSelectedEntities: ({ commit }, entities) => {
122
+ commit("SET_SELECTED_ENTITIES", entities);
150
123
  },
151
124
 
152
- entitySelection: ({ commit, dispatch, state }, { entities, selection }) => {
153
- if (entities.length === 0) {
154
- if (selection) {
155
- dispatch("getTextFromBboxes", [selection]).then((spans) => {
156
- commit("SET_SPAN_SELECTION", spans);
157
- });
158
- } else {
159
- commit("RESET_SELECTION");
160
- commit("SET_SPAN_SELECTION", []);
161
- }
162
- commit("SET_SELECTED_ENTITIES", []);
163
- } else {
164
- commit("SET_SELECTED_ENTITIES", entities);
125
+ getTextFromBboxes: ({ commit, rootState }, { box, entities }) => {
126
+ let span;
165
127
 
166
- dispatch("document/setAnnotationId", null, {
167
- root: true,
168
- });
169
- let span;
170
- if (entities) {
171
- span = entities.flatMap((s) => {
172
- return s.original;
173
- });
174
- } else {
175
- span = [entities];
176
- }
177
- commit("SET_SPAN_SELECTION", span);
178
- dispatch("getTextFromBboxes", span).then((spans) => {
179
- commit("SET_SPAN_SELECTION", spans);
128
+ if (entities) {
129
+ span = box.flatMap((s) => {
130
+ return s.original;
180
131
  });
181
- }
182
- },
183
-
184
- entityClick: ({ commit, dispatch, state }, entity) => {
185
- // Check if we are creating a new Annotation
186
- // or if we are removing a previously selected entity
187
- // or editing empty one
188
- const found = state.selectedEntities.find(
189
- (entityToFind) =>
190
- entity.scaled.width === entityToFind.scaled.width &&
191
- entity.original.offset_string === entityToFind.original.offset_string
192
- );
193
-
194
- let entities = state.selectedEntities;
195
- if (found) {
196
- entities = [
197
- ...state.selectedEntities.filter(
198
- (entityToFilter) =>
199
- entityToFilter.scaled.width !== entity.scaled.width &&
200
- entityToFilter.original.offset_string !==
201
- entity.original.offset_string
202
- ),
203
- ];
204
132
  } else {
205
- entities.push(entity);
133
+ span = [box];
206
134
  }
207
135
 
208
- if (entities.length === 0) {
209
- commit("SET_SELECTED_ENTITIES", []);
210
- commit("SET_SPAN_SELECTION", []);
211
- } else {
212
- commit("SET_SELECTED_ENTITIES", entities);
213
-
214
- dispatch("document/setAnnotationId", null, {
215
- root: true,
136
+ return HTTP.post(`documents/${rootState.document.documentId}/bbox/`, {
137
+ span,
138
+ })
139
+ .then((response) => {
140
+ if (response.data.span.length && response.data.span.length > 0) {
141
+ /**
142
+ * If we have a non-empty bboxes list, we assume there
143
+ * is text here on the backend, so we just set
144
+ * spanSelection to the response.
145
+ */
146
+ commit("SET_SPAN_SELECTION", response.data.span);
147
+ } else {
148
+ /**
149
+ * Otherwise, we assume the backend can't identify text
150
+ * on this area, so we set our bbox into spanSelection
151
+ * ready to be passed back to the backend when creating
152
+ * an annotation on this empty area, adding the offset_string
153
+ * attribute, ready to be filled.
154
+ */
155
+ commit("SET_SPAN_SELECTION", span);
156
+ }
157
+ })
158
+ .catch((error) => {
159
+ alert("Could not fetch the selected text from the backend");
216
160
  });
161
+ },
217
162
 
218
- let span;
163
+ getTextFromEntities: ({ commit, dispatch }, selectedEntities) => {
164
+ if (!selectedEntities) return;
219
165
 
220
- if (entities) {
221
- span = entities.flatMap((s) => {
222
- return s.original;
223
- });
224
- } else {
225
- span = [entities];
226
- }
227
- commit("SET_SPAN_SELECTION", span);
228
- }
166
+ return dispatch("getTextFromBboxes", {
167
+ box: selectedEntities,
168
+ entities: true,
169
+ });
229
170
  },
230
171
 
231
172
  setSpanSelection: ({ commit }, span) => {
232
173
  commit("SET_SPAN_SELECTION", span);
233
174
  },
234
- setPlaceholderSelection: ({ commit }, span) => {
235
- commit("SET_PLACEHOLDER_SELECTION", span);
236
- },
237
175
  };
238
176
 
239
177
  const mutations = {
178
+ ELEMENT_SELECTED: (state, value) => {
179
+ state.elementSelected = value;
180
+ },
240
181
  START_SELECTION: (state, { pageNumber, start }) => {
241
182
  state.selection.end = null;
183
+ state.isSelecting = true;
242
184
  state.selection.pageNumber = pageNumber;
185
+ state.selection.custom = true;
243
186
  state.selection.start = start;
244
- state.isSelecting = true;
187
+ state.selection.placeholderBox = null;
245
188
  },
246
189
  MOVE_SELECTION: (state, points) => {
247
190
  const { start, end } = points;
@@ -257,39 +200,20 @@ const mutations = {
257
200
  state.isSelecting = false;
258
201
  },
259
202
  RESET_SELECTION: (state) => {
203
+ state.isSelecting = false;
260
204
  state.selection.pageNumber = null;
261
205
  state.selection.start = null;
262
206
  state.selection.end = null;
207
+ state.selection.placeholderBox = null;
263
208
  },
264
209
  SET_SPAN_SELECTION: (state, span) => {
265
- if (!span) {
266
- state.spanSelection = [];
267
- } else {
268
- state.spanSelection = span;
269
- }
210
+ state.spanSelection = span;
270
211
  },
271
- ADD_SPAN_SELECTION: (state, span) => {
272
- state.spanSelection.push(span);
273
- },
274
- SET_PLACEHOLDER_SELECTION: (state, span) => {
275
- if (!span) {
276
- state.placeholderSelection = [];
277
- } else {
278
- state.placeholderSelection = span;
279
- }
280
- },
281
- ADD_PLACEHOLDER_SELECTION: (state, span) => {
282
- state.placeholderSelection.push(span);
212
+ SET_SELECTION: (state, selection) => {
213
+ state.selection = selection;
283
214
  },
284
215
  SET_SELECTED_ENTITIES: (state, entities) => {
285
- if (!entities) {
286
- state.selectedEntities = [];
287
- } else {
288
- state.selectedEntities = entities;
289
- }
290
- },
291
- SET_SPAN_LOADING: (state, loading) => {
292
- state.spanLoading = loading;
216
+ state.selectedEntities = entities;
293
217
  },
294
218
  };
295
219
 
@@ -1,51 +0,0 @@
1
- <template>
2
- <v-group>
3
- <v-rect ref="placeholderSelection" :config="placeholderConfig" />
4
- </v-group>
5
- </template>
6
-
7
- <script>
8
- import { mapGetters } from "vuex";
9
- export default {
10
- props: {
11
- span: {
12
- required: true,
13
- type: Object,
14
- },
15
- page: {
16
- required: true,
17
- type: Object,
18
- },
19
- },
20
- data() {
21
- return {
22
- selection: {
23
- start: null,
24
- end: null,
25
- },
26
- };
27
- },
28
- computed: {
29
- placeholderConfig() {
30
- const box = this.bboxToRect(this.page, this.span);
31
- const primaryColor = window
32
- .getComputedStyle(document.body)
33
- .getPropertyValue("--primary-color");
34
- return {
35
- x: box.x,
36
- y: box.y,
37
- width: box.width,
38
- height: box.height,
39
- fill: "transparent",
40
- stroke: primaryColor,
41
- strokeWidth: 3,
42
- globalCompositeOperation: "multiply",
43
- shadowForStrokeEnabled: false,
44
- name: "placeholderSelection",
45
- draggable: false,
46
- };
47
- },
48
- ...mapGetters("display", ["bboxToRect"]),
49
- },
50
- };
51
- </script>
@@ -1,259 +0,0 @@
1
- <template>
2
- <v-group>
3
- <v-rect
4
- ref="spanSelection"
5
- :config="config"
6
- :stroke-scale-enabled="false"
7
- @dragend="onChange"
8
- @transformend="onChange"
9
- />
10
- <v-transformer
11
- v-if="!isSelectionValid"
12
- ref="spanTransformer"
13
- :config="transformerConfig"
14
- />
15
- </v-group>
16
- </template>
17
-
18
- <script>
19
- import { mapGetters, mapState } from "vuex";
20
-
21
- export default {
22
- props: {
23
- id: {
24
- required: true,
25
- type: Number,
26
- },
27
- page: {
28
- required: true,
29
- type: Object,
30
- },
31
- span: {
32
- required: true,
33
- type: Object,
34
- },
35
- },
36
- data() {
37
- return {
38
- selection: {
39
- start: null,
40
- end: null,
41
- },
42
- box: {
43
- x: 0,
44
- y: 0,
45
- width: 0,
46
- height: 0,
47
- },
48
- };
49
- },
50
- computed: {
51
- /**
52
- * Konva options of the selection rectangle, based on the
53
- * `selection` object.
54
- */
55
- config() {
56
- const primaryColor = window
57
- .getComputedStyle(document.body)
58
- .getPropertyValue("--primary-color");
59
- return {
60
- x: this.box.x,
61
- y: this.box.y,
62
- width: this.box.width,
63
- height: this.box.height,
64
- stroke: "#7B61FFB3",
65
- fill: `${primaryColor}77`,
66
- strokeWidth: 1,
67
- globalCompositeOperation: "multiply",
68
- shadowForStrokeEnabled: false,
69
- perfectDrawEnabled: false,
70
- name: `spanSelection_${this.id}`,
71
- draggable: true,
72
- };
73
- },
74
- transformerConfig() {
75
- return {
76
- borderEnabled: false,
77
- rotateEnabled: false,
78
- ignoreStroke: true,
79
- keepRatio: false,
80
- anchorStroke: "#7B61FF",
81
- anchorSize: 6,
82
- name: `spanTransformer_${this.id}`,
83
- };
84
- },
85
- ...mapState("document", ["editAnnotation"]),
86
- ...mapGetters("display", ["clientToBbox", "bboxToRect", "scaledEntities"]),
87
- ...mapGetters("selection", ["entitiesOnSelection", "isSelectionValid"]),
88
- },
89
- watch: {
90
- box() {
91
- this.updateTransformer();
92
- this.setSelection();
93
- },
94
- span() {
95
- this.box = this.bboxToRect(this.page, this.span);
96
- },
97
- },
98
- mounted() {
99
- this.box = this.bboxToRect(this.page, this.span);
100
- this.setSelection();
101
- this.$nextTick(() => {
102
- this.updateTransformer();
103
- });
104
- },
105
- methods: {
106
- setSelection() {
107
- this.selection = {
108
- start: {
109
- x: this.box.x,
110
- y: this.box.y,
111
- },
112
- end: {
113
- x: this.box.x + this.box.width,
114
- y: this.box.y + this.box.height,
115
- },
116
- };
117
- },
118
- startSelection(start) {
119
- this.selection.start = start;
120
- },
121
-
122
- moveSelection(points) {
123
- // only apply when we have a large enough selection, otherwise we risk counting misclicks
124
- const xDiff = Math.abs(this.selection.start.x - points.end.x);
125
- const yDiff = Math.abs(this.selection.start.y - points.end.y);
126
- if (xDiff > 5 && yDiff > 5) {
127
- const { start, end } = points;
128
- if (start) {
129
- this.selection.start = start;
130
- }
131
- if (end) {
132
- this.selection.end = end;
133
- }
134
- }
135
- },
136
-
137
- endSelection(end) {
138
- let xDiff;
139
- let yDiff;
140
-
141
- if (end) {
142
- xDiff = Math.abs(this.selection.start.x - end.x);
143
- yDiff = Math.abs(this.selection.start.y - end.y);
144
- }
145
-
146
- // if "end" is not provided, start and end points are the same, or if we have a selection smaller than 5x5,
147
- // just reset
148
- if (
149
- !end ||
150
- (yDiff <= 5 && xDiff <= 5) ||
151
- (this.selection.start.x === end.x && this.selection.start.y == end.y)
152
- ) {
153
- this.selection.start = null;
154
- this.selection.end = null;
155
- } else {
156
- this.selection.start.x = this.selection.start.x - selectionPadding;
157
- this.selection.start.y = this.selection.start.y - selectionPadding;
158
-
159
- end.x = end.x + selectionPadding;
160
- end.y = end.y + selectionPadding;
161
-
162
- this.selection.end = end;
163
- }
164
- },
165
- handleSelection() {
166
- this.box = {
167
- x: this.selection.start.x,
168
- y: this.selection.start.y,
169
- width: this.selection.end.x - this.selection.start.x,
170
- height: this.selection.end.y - this.selection.start.y,
171
- };
172
-
173
- const box = this.clientToBbox(
174
- this.page,
175
- this.selection.start,
176
- this.selection.end
177
- );
178
-
179
- this.$store.dispatch("selection/entitySelection", {
180
- entities: this.scaledEntities(
181
- this.entitiesOnSelection(box, this.page),
182
- this.page
183
- ),
184
- selection: box,
185
- });
186
- },
187
- updateTransformer() {
188
- // here we need to manually attach or detach Transformer node
189
- const transformer = this.$refs.spanTransformer;
190
-
191
- // maybe we're out of sync and the transformer is not available, just return
192
- if (!transformer) {
193
- return;
194
- }
195
-
196
- const transformerNode = transformer.getNode();
197
- const stage = transformerNode.getStage();
198
- let selectedNode;
199
- if (stage) {
200
- selectedNode = stage.findOne(`.spanSelection_${this.id}`);
201
- }
202
-
203
- // do nothing if selected node is already attached
204
- if (selectedNode === transformerNode.node()) {
205
- return;
206
- }
207
-
208
- if (selectedNode) {
209
- // attach to another node
210
- transformerNode.nodes([selectedNode]);
211
- } else {
212
- // remove transformer
213
- transformerNode.nodes([]);
214
- }
215
-
216
- transformerNode.getLayer().batchDraw();
217
- },
218
-
219
- /**
220
- * This method is used for both transforms and drags since it just
221
- * retrieves the rect's new attributes from the event and uses those
222
- * to set the new selection state.
223
- */
224
- onChange(event) {
225
- const { x, y, scaleX, scaleY, skewX, width, height } = event.target.attrs;
226
- const realWidth = width * scaleX;
227
- const realHeight = height * scaleY;
228
- let start;
229
- let end;
230
-
231
- // we need to figure out if there's skewing going on, to fix start/end points
232
- // (other cases appear to fix themselves automatically)
233
- if (skewX >= 0) {
234
- start = { x, y };
235
- end = {
236
- x: start.x + realWidth,
237
- y: start.y + realHeight,
238
- };
239
- } else {
240
- end = { x, y };
241
- start = { x: end.x - realWidth, y: end.y - realHeight };
242
- }
243
-
244
- this.moveSelection({ start, end });
245
-
246
- // reset node's everything after transform (we don't want to deal with that,
247
- // just with regular x/y/width/height)
248
- const node = this.$refs.spanSelection.getNode();
249
- node.skewX(0);
250
- node.skewY(0);
251
- node.rotation(0);
252
- node.scaleX(1);
253
- node.scaleY(1);
254
-
255
- this.handleSelection();
256
- },
257
- },
258
- };
259
- </script>