@konfuzio/document-validation-ui 0.1.59-dev.1 → 0.2.0-dev.1

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 (63) 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 +66 -23
  7. package/dist/js/chunk-vendors.js.map +1 -1
  8. package/jest.config.js +22 -2
  9. package/package.json +32 -38
  10. package/src/assets/scss/ann_set_table_options.scss +4 -4
  11. package/src/assets/scss/annotation_action_buttons.scss +7 -7
  12. package/src/assets/scss/annotation_details.scss +9 -9
  13. package/src/assets/scss/choose_label_set_modal.scss +5 -5
  14. package/src/assets/scss/document_action_bar.scss +3 -3
  15. package/src/assets/scss/document_annotations.scss +45 -43
  16. package/src/assets/scss/document_category.scss +8 -8
  17. package/src/assets/scss/document_dashboard.scss +1 -1
  18. package/src/assets/scss/document_edit.scss +30 -30
  19. package/src/assets/scss/document_error.scss +6 -6
  20. package/src/assets/scss/document_name.scss +6 -6
  21. package/src/assets/scss/document_page.scss +3 -3
  22. package/src/assets/scss/document_search_bar.scss +7 -7
  23. package/src/assets/scss/document_set_chooser.scss +3 -3
  24. package/src/assets/scss/document_thumbnails.scss +7 -7
  25. package/src/assets/scss/document_toolbar.scss +10 -10
  26. package/src/assets/scss/document_top_bar.scss +11 -11
  27. package/src/assets/scss/document_viewport_modal.scss +3 -3
  28. package/src/assets/scss/documents_list.scss +11 -12
  29. package/src/assets/scss/edit_page_thumbnail.scss +6 -6
  30. package/src/assets/scss/empty_state.scss +4 -4
  31. package/src/assets/scss/error_page.scss +2 -2
  32. package/src/assets/scss/extracting_data.scss +3 -3
  33. package/src/assets/scss/multi_ann_table_overlay.scss +3 -3
  34. package/src/assets/scss/multi_ann_table_popup.scss +1 -1
  35. package/src/assets/scss/new_annotation.scss +25 -19
  36. package/src/assets/scss/scrolling_document.scss +1 -1
  37. package/src/assets/scss/theme.scss +64 -52
  38. package/src/assets/scss/variables.scss +2 -0
  39. package/src/components/App.vue +9 -14
  40. package/src/components/DocumentAnnotations/AnnotationActionButtons.vue +4 -6
  41. package/src/components/DocumentAnnotations/AnnotationContent.vue +25 -52
  42. package/src/components/DocumentAnnotations/AnnotationRow.vue +106 -50
  43. package/src/components/DocumentAnnotations/DocumentAnnotations.vue +12 -6
  44. package/src/components/DocumentAnnotations/EmptyAnnotation.vue +31 -70
  45. package/src/components/DocumentDashboard.vue +12 -17
  46. package/src/components/DocumentEdit/EditPages.vue +51 -46
  47. package/src/components/DocumentPage/{NewAnnotation.vue → AnnotationPopup.vue} +122 -94
  48. package/src/components/DocumentPage/BoxSelection.vue +16 -49
  49. package/src/components/DocumentPage/DocumentPage.vue +56 -153
  50. package/src/components/DocumentPage/DocumentToolbar.vue +0 -1
  51. package/src/components/DocumentPage/PlaceholderSelection.vue +51 -0
  52. package/src/components/DocumentPage/SpanSelection.vue +259 -0
  53. package/src/components/DocumentThumbnails/LoadingThumbnail.vue +3 -6
  54. package/src/components/DocumentTopBar/DocumentTopBar.vue +4 -2
  55. package/src/constants.js +1 -7
  56. package/src/i18n.js +2 -5
  57. package/src/main.js +14 -16
  58. package/src/store/display.js +33 -22
  59. package/src/store/document.js +1 -0
  60. package/src/store/index.js +5 -8
  61. package/src/store/selection.js +152 -76
  62. package/src/assets/scss/imports.scss +0 -1
  63. package/src/components/DocumentPage/EditAnnotation.vue +0 -372
package/src/i18n.js CHANGED
@@ -1,7 +1,4 @@
1
- import Vue from "vue";
2
- import VueI18n from "vue-i18n";
3
-
4
- Vue.use(VueI18n);
1
+ import { createI18n } from "vue-i18n";
5
2
 
6
3
  function loadLocaleMessages() {
7
4
  const locales = require.context(
@@ -20,7 +17,7 @@ function loadLocaleMessages() {
20
17
  return messages;
21
18
  }
22
19
 
23
- export default new VueI18n({
20
+ export const i18n = createI18n({
24
21
  locale: process.env.VUE_APP_LOCALE || "en",
25
22
  fallbackLocale: "en",
26
23
  messages: loadLocaleMessages(),
package/src/main.js CHANGED
@@ -1,28 +1,26 @@
1
1
  import App from "./components/App";
2
- import Vue from "vue";
2
+ import { createApp } from "vue";
3
3
  import Buefy from "buefy";
4
4
  import VueKonva from "vue-konva";
5
- import i18n from "./i18n";
6
- import store from "./store";
7
- import VueObserveVisibility from "vue-observe-visibility";
5
+ import { i18n } from "./i18n";
6
+ import { store } from "./store";
7
+ import VueObserveVisibility from "vue3-observe-visibility";
8
8
  import Icons from "./icons";
9
- import VueSplit from "vue-split-panel";
10
9
 
11
- Vue.component("VueFontawesome", Icons);
12
- Vue.component("App", App);
13
- Vue.use(VueKonva);
14
- Vue.use(Buefy, {
10
+ const app = createApp();
11
+
12
+ app.component("App", App);
13
+ app.component("VueFontawesome", Icons);
14
+ app.use(VueKonva);
15
+ app.use(Buefy, {
15
16
  defaultIconPack: "fas",
16
17
  defaultIconComponent: "vue-fontawesome",
17
18
  });
18
- Vue.use(VueObserveVisibility);
19
- Vue.use(VueSplit);
19
+ app.use(VueObserveVisibility);
20
+ app.use(store);
21
+ app.use(i18n);
20
22
 
21
23
  /**
22
24
  * Main entrypoint for the App
23
25
  */
24
- new Vue({
25
- i18n,
26
- store,
27
- el: "#app",
28
- });
26
+ app.mount("#app");
@@ -113,32 +113,43 @@ const getters = {
113
113
  * image rendering.
114
114
  */
115
115
  imageScale: (state) => (page) => {
116
- return new BigNumber(page.size[0]).div(page.original_size[0]).toNumber();
116
+ if (
117
+ page.size &&
118
+ page.size.length > 0 &&
119
+ page.original_size &&
120
+ page.original_size.length > 0
121
+ ) {
122
+ return new BigNumber(page.size[0]).div(page.original_size[0]).toNumber();
123
+ }
124
+ return 0;
117
125
  },
118
126
  bboxToPoint:
119
127
  (state, getters) =>
120
128
  (page, point, hasOffset = false) => {
121
- const imageScale = getters.imageScale(page);
122
- const { x, y } = point;
123
- const pageHeight = new BigNumber(page.original_size[1]);
124
- const newPoint = {
125
- // left
126
- x: new BigNumber(x)
127
- .minus(hasOffset ? 1 : 0)
128
- .times(state.scale)
129
- .times(imageScale)
130
- .div(PIXEL_RATIO)
131
- .toNumber(),
132
- // top
133
- y: pageHeight
134
- .minus(new BigNumber(y))
135
- .minus(hasOffset ? 17.1 : 0)
136
- .times(state.scale)
137
- .times(imageScale)
138
- .div(PIXEL_RATIO)
139
- .toNumber(),
140
- };
141
- return newPoint;
129
+ if (page && page.original_size && page.original_size.length > 1) {
130
+ const imageScale = getters.imageScale(page);
131
+ const { x, y } = point;
132
+ const pageHeight = new BigNumber(page.original_size[1]);
133
+ const newPoint = {
134
+ // left
135
+ x: new BigNumber(x)
136
+ .minus(hasOffset ? 1 : 0)
137
+ .times(state.scale)
138
+ .times(imageScale)
139
+ .div(PIXEL_RATIO)
140
+ .toNumber(),
141
+ // top
142
+ y: pageHeight
143
+ .minus(new BigNumber(y))
144
+ .minus(hasOffset ? 17.1 : 0)
145
+ .times(state.scale)
146
+ .times(imageScale)
147
+ .div(PIXEL_RATIO)
148
+ .toNumber(),
149
+ };
150
+ return newPoint;
151
+ }
152
+ return { x: 0, y: 0 };
142
153
  },
143
154
  bboxToRect:
144
155
  (state, getters) =>
@@ -1134,6 +1134,7 @@ const actions = {
1134
1134
  // Check if we first open the document dashboard or the edit mode
1135
1135
  if (
1136
1136
  !state.publicView &&
1137
+ state.selectedDocument &&
1137
1138
  (!state.selectedDocument.category ||
1138
1139
  (!state.selectedDocument.category_is_revised &&
1139
1140
  !getters.documentHasCorrectAnnotations &&
@@ -1,5 +1,4 @@
1
- import Vue from "vue";
2
- import Vuex from "vuex";
1
+ import { createStore } from "vuex";
3
2
 
4
3
  import display from "./display";
5
4
  import document from "./document";
@@ -8,15 +7,13 @@ import project from "./project";
8
7
  import selection from "./selection";
9
8
  import edit from "./edit";
10
9
 
11
- Vue.use(Vuex);
12
-
13
- export default new Vuex.Store({
10
+ export const store = createStore({
14
11
  modules: {
15
12
  display,
16
13
  document,
17
14
  category,
18
15
  project,
19
16
  selection,
20
- edit
21
- }
22
- });
17
+ edit,
18
+ },
19
+ });
@@ -9,30 +9,24 @@ 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
14
12
  },
15
13
  isSelecting: false,
16
- spanSelection: null,
17
- elementSelected: null, // selected element id
18
- selectedEntities: null,
14
+ spanSelection: [],
15
+ placeholderSelection: [],
16
+ selectedEntities: [],
17
+ spanLoading: false,
19
18
  };
20
19
 
21
20
  const getters = {
22
- isElementSelected: (state) => {
23
- return state.elementSelected;
24
- },
25
- isSelecting: (state) => {
26
- return state.isSelecting;
27
- },
28
21
  isSelectionValid: (state) => {
29
22
  /**
30
23
  * `endSelection` will reset everything in case of invalid selection.
31
24
  * Check the existence of `selection.end` before requesting the
32
25
  * content from the backend.
33
26
  * */
34
- return state.selection && state.selection.end;
27
+ return state.selection && state.selection.end != null;
35
28
  },
29
+
36
30
  getSelectionForPage: (state) => (pageNumber) => {
37
31
  if (state.selection.pageNumber === pageNumber) {
38
32
  return state.selection;
@@ -52,19 +46,24 @@ const getters = {
52
46
  box.y1 >= entity.y1
53
47
  );
54
48
  },
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
+ },
55
59
  };
56
60
 
57
61
  const actions = {
58
- selectElement: ({ commit }, value) => {
59
- commit("RESET_SELECTION");
60
- commit("SET_SPAN_SELECTION", null);
61
- commit("ELEMENT_SELECTED", value);
62
- },
63
-
64
62
  disableSelection: ({ commit }) => {
65
- commit("ELEMENT_SELECTED", null);
66
63
  commit("RESET_SELECTION");
67
- commit("SET_SPAN_SELECTION", null);
64
+ commit("SET_SELECTED_ENTITIES", []);
65
+ commit("SET_SPAN_SELECTION", []);
66
+ commit("SET_PLACEHOLDER_SELECTION", []);
68
67
  },
69
68
 
70
69
  startSelection: ({ commit }, { pageNumber, start }) => {
@@ -81,8 +80,6 @@ const actions = {
81
80
  if (xDiff > 5 && yDiff > 5) {
82
81
  commit("MOVE_SELECTION", points);
83
82
  }
84
-
85
- commit("SET_SELECTED_ENTITIES", null);
86
83
  },
87
84
 
88
85
  endSelection: ({ commit, state }, end) => {
@@ -113,78 +110,138 @@ const actions = {
113
110
  }
114
111
  },
115
112
 
116
- setSelection: ({ commit }, { span, selection }) => {
117
- commit("SET_SELECTION", selection);
118
- commit("SET_SPAN_SELECTION", span);
119
- },
120
-
121
113
  setSelectedEntities: ({ commit }, entities) => {
122
114
  commit("SET_SELECTED_ENTITIES", entities);
123
115
  },
124
116
 
125
- getTextFromBboxes: ({ commit, rootState }, { box, entities }) => {
126
- let span;
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
+ });
150
+ },
127
151
 
128
- if (entities) {
129
- span = box.flatMap((s) => {
130
- return s.original;
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);
165
+
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);
131
180
  });
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
+ ];
132
204
  } else {
133
- span = [box];
205
+ entities.push(entity);
134
206
  }
135
207
 
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");
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,
160
216
  });
161
- },
162
217
 
163
- getTextFromEntities: ({ commit, dispatch }, selectedEntities) => {
164
- if (!selectedEntities) return;
218
+ let span;
165
219
 
166
- return dispatch("getTextFromBboxes", {
167
- box: selectedEntities,
168
- entities: true,
169
- });
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
+ }
170
229
  },
171
230
 
172
231
  setSpanSelection: ({ commit }, span) => {
173
232
  commit("SET_SPAN_SELECTION", span);
174
233
  },
234
+ setPlaceholderSelection: ({ commit }, span) => {
235
+ commit("SET_PLACEHOLDER_SELECTION", span);
236
+ },
175
237
  };
176
238
 
177
239
  const mutations = {
178
- ELEMENT_SELECTED: (state, value) => {
179
- state.elementSelected = value;
180
- },
181
240
  START_SELECTION: (state, { pageNumber, start }) => {
182
241
  state.selection.end = null;
183
- state.isSelecting = true;
184
242
  state.selection.pageNumber = pageNumber;
185
- state.selection.custom = true;
186
243
  state.selection.start = start;
187
- state.selection.placeholderBox = null;
244
+ state.isSelecting = true;
188
245
  },
189
246
  MOVE_SELECTION: (state, points) => {
190
247
  const { start, end } = points;
@@ -200,20 +257,39 @@ const mutations = {
200
257
  state.isSelecting = false;
201
258
  },
202
259
  RESET_SELECTION: (state) => {
203
- state.isSelecting = false;
204
260
  state.selection.pageNumber = null;
205
261
  state.selection.start = null;
206
262
  state.selection.end = null;
207
- state.selection.placeholderBox = null;
208
263
  },
209
264
  SET_SPAN_SELECTION: (state, span) => {
210
- state.spanSelection = span;
265
+ if (!span) {
266
+ state.spanSelection = [];
267
+ } else {
268
+ state.spanSelection = span;
269
+ }
211
270
  },
212
- SET_SELECTION: (state, selection) => {
213
- state.selection = selection;
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);
214
283
  },
215
284
  SET_SELECTED_ENTITIES: (state, entities) => {
216
- state.selectedEntities = 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;
217
293
  },
218
294
  };
219
295
 
@@ -1 +0,0 @@
1
- @import "./variables.scss";