@konfuzio/document-validation-ui 0.1.10-dev.13 → 0.1.10-dev.15

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.1.10-dev.13",
3
+ "version": "0.1.10-dev.15",
4
4
  "repository": "git://github.com:konfuzio-ai/document-validation-ui.git",
5
5
  "main": "dist/app.js",
6
6
  "scripts": {
@@ -32,6 +32,23 @@
32
32
  flex: 1;
33
33
  }
34
34
 
35
+ .center-bar-components {
36
+ display: flex;
37
+ align-items: center;
38
+ gap: 38px;
39
+
40
+ .navigation-arrow {
41
+ &:hover {
42
+ cursor: pointer;
43
+ }
44
+
45
+ &.navigation-disabled {
46
+ cursor: not-allowed;
47
+ filter: opacity(0.5);
48
+ }
49
+ }
50
+ }
51
+
35
52
  .right-bar-components {
36
53
  align-items: center;
37
54
  justify-content: flex-end;
@@ -69,7 +69,6 @@ export default {
69
69
  locale: {
70
70
  type: String,
71
71
  required: false,
72
- default: "en",
73
72
  },
74
73
  // eslint-disable-next-line vue/prop-name-casing
75
74
  documents_list_path: {
@@ -53,10 +53,6 @@ export default {
53
53
  type: Object,
54
54
  required: true,
55
55
  },
56
- saveChanges: {
57
- type: Boolean,
58
- required: false,
59
- },
60
56
  },
61
57
  data() {
62
58
  return {
@@ -107,12 +103,6 @@ export default {
107
103
  this.handleCancel(true);
108
104
  }
109
105
  },
110
-
111
- saveChanges(newValue) {
112
- if (newValue) {
113
- this.saveAnnotationChanges();
114
- }
115
- },
116
106
  },
117
107
  methods: {
118
108
  setText(text) {
@@ -205,14 +195,40 @@ export default {
205
195
  index = this.spanIndex;
206
196
  }
207
197
 
198
+ let spans = [];
199
+
200
+ // Validate if we are deleting an Annotation that it's not multi-lined
201
+ let isToDelete =
202
+ this.annotationText.length === 0 &&
203
+ (!isElementArray(this.annotation.span) ||
204
+ this.annotation.span.length === 1);
205
+
206
+ if (!isToDelete) {
207
+ const span = this.createSpan();
208
+
209
+ spans = [...annotation.span];
210
+
211
+ spans[index] = span;
212
+
213
+ if (this.annotationText.length === 0) {
214
+ spans.splice(index, 1);
215
+ }
216
+ }
208
217
  // API call handled in parent component - AnnotationRow
209
- this.$emit(
210
- "save-annotation-changes",
211
- this.annotation,
212
- index,
213
- this.span,
214
- this.annotationText
215
- );
218
+ this.$emit("save-annotation-changes", spans, isToDelete);
219
+ },
220
+
221
+ createSpan() {
222
+ return {
223
+ offset_string: this.annotationText,
224
+ page_index: this.span.page_index,
225
+ x0: this.span.x0,
226
+ x1: this.span.x1,
227
+ y0: this.span.y0,
228
+ y1: this.span.y1,
229
+ start_offset: this.span.start_offset,
230
+ end_offset: this.span.end_offset,
231
+ };
216
232
  },
217
233
  },
218
234
  };
@@ -49,14 +49,14 @@
49
49
  @mouseleave="onAnnotationHoverLeave"
50
50
  >
51
51
  <AnnotationContent
52
+ :ref="`span_${annotation.id}_${index}`"
52
53
  :annotation="annotation"
53
54
  :span="span"
54
55
  :span-index="index"
55
56
  :label="label"
56
57
  :annotation-set="annotationSet"
57
58
  :is-hovered="hoveredAnnotation"
58
- :save-changes="saveChanges"
59
- @save-annotation-changes="handleSaveAnnotationChanges"
59
+ @save-annotation-changes="saveAnnotationChanges"
60
60
  />
61
61
  </div>
62
62
  </div>
@@ -70,7 +70,6 @@
70
70
  :label="label"
71
71
  :annotation-set="annotationSet"
72
72
  :is-hovered="hoveredAnnotation"
73
- :save-changes="saveChanges"
74
73
  :is-missing-annotation="
75
74
  annotationIsNotFound(annotationSet, label)
76
75
  "
@@ -82,7 +81,6 @@
82
81
  :label="label"
83
82
  :annotation-set="annotationSet"
84
83
  :is-hovered="hoveredAnnotation"
85
- :save-changes="saveChanges"
86
84
  :is-missing-annotation="annotationIsNotFound(annotationSet, label)"
87
85
  @save-empty-annotation-changes="saveEmptyAnnotationChanges"
88
86
  />
@@ -156,8 +154,6 @@ export default {
156
154
  isSelected: false,
157
155
  annotationAnimationTimeout: null,
158
156
  hoveredAnnotation: null,
159
- saveChanges: false,
160
- toDecline: false,
161
157
  };
162
158
  },
163
159
  computed: {
@@ -261,7 +257,6 @@ export default {
261
257
  },
262
258
  editAnnotation(newValue) {
263
259
  if (!newValue) {
264
- this.saveChanges = false;
265
260
  this.isLoading = false;
266
261
  }
267
262
  },
@@ -451,20 +446,24 @@ export default {
451
446
  if (this.publicView || this.isDocumentReviewed) return;
452
447
 
453
448
  if (
454
- this.showAcceptButton() ||
455
- this.showDeclineButton() ||
456
- this.isAnnotationInEditMode(
457
- this.annotationId(),
458
- this.editAnnotation.index
459
- )
449
+ this.annotation &&
450
+ (this.showAcceptButton() ||
451
+ this.showDeclineButton() ||
452
+ this.isAnnotationInEditMode(
453
+ this.annotationId(),
454
+ this.editAnnotation.index
455
+ ))
460
456
  ) {
461
- this.saveChanges = true;
462
- if (decline) {
463
- this.toDecline = true;
464
- }
465
- }
466
-
467
- if (
457
+ // retrieve all edited spans from every AnnotationContent component
458
+ let spans = [];
459
+ Object.keys(this.$refs).forEach((ref) => {
460
+ if (ref.includes(`span_${this.annotation.id}`)) {
461
+ // call child component createSpan method
462
+ spans.push(this.$refs[ref][0].createSpan());
463
+ }
464
+ });
465
+ this.saveAnnotationChanges(spans, decline);
466
+ } else if (
468
467
  !this.annotation &&
469
468
  this.isAnnotationInEditMode(this.annotationId())
470
469
  ) {
@@ -495,12 +494,7 @@ export default {
495
494
  this.closedTag = null;
496
495
  });
497
496
  },
498
- handleSaveAnnotationChanges(
499
- annotation,
500
- index,
501
- annotationSpan,
502
- annotationContent
503
- ) {
497
+ saveAnnotationChanges(spans, isToDeleteOrDecline) {
504
498
  // This function deals with declining Annotations
505
499
  // or editing an Annotation or a part of it (if multi line)
506
500
  this.isLoading = true;
@@ -508,29 +502,14 @@ export default {
508
502
  let updatedString; // what will be sent to the API
509
503
  let storeAction; // if it will be 'delete' or 'patch'
510
504
 
511
- // Validate if we are deleting an Annotation that it's not multi-lined
512
- let isToDelete =
513
- annotationContent.length === 0 &&
514
- (!isElementArray(annotation.span) || annotation.span.length === 1);
515
-
516
505
  // Verify if we delete the entire Annotation or a part of the text
517
- if (isToDelete || this.toDecline) {
506
+ if (isToDeleteOrDecline) {
518
507
  storeAction = "document/deleteAnnotation";
519
508
  } else {
520
509
  // Editing the Annotation
521
510
  // Deleting part of multi-line Annotation
522
511
  storeAction = "document/updateAnnotation";
523
512
 
524
- let spans = [...annotation.span];
525
-
526
- const span = this.createSpan(annotationSpan, annotationContent);
527
-
528
- spans[index] = span;
529
-
530
- if (annotationContent.length === 0) {
531
- spans.splice(index, 1);
532
- }
533
-
534
513
  updatedString = {
535
514
  is_correct: true,
536
515
  revised: true,
@@ -555,21 +534,8 @@ export default {
555
534
  this.$store.dispatch("document/resetEditAnnotation");
556
535
  this.$store.dispatch("selection/disableSelection");
557
536
  this.$store.dispatch("selection/setSelectedEntities", null);
558
- this.toDecline = false;
559
537
  });
560
538
  },
561
- createSpan(span, annotationContent) {
562
- return {
563
- offset_string: annotationContent,
564
- page_index: span.page_index,
565
- x0: span.x0,
566
- x1: span.x1,
567
- y0: span.y0,
568
- y1: span.y1,
569
- start_offset: span.start_offset,
570
- end_offset: span.end_offset,
571
- };
572
- },
573
539
  saveEmptyAnnotationChanges() {
574
540
  let annotationToCreate;
575
541
 
@@ -624,12 +590,10 @@ export default {
624
590
 
625
591
  if (found) {
626
592
  this.isLoading = true;
627
- this.saveChanges = false;
628
593
  return;
629
594
  }
630
595
 
631
596
  this.isLoading = false;
632
- this.saveChanges = false;
633
597
  return;
634
598
  }
635
599
 
@@ -637,7 +601,6 @@ export default {
637
601
  // while waiting for it to be removed from the row
638
602
  if (!this.annotationsMarkedAsMissing) {
639
603
  this.isLoading = false;
640
- this.saveChanges = false;
641
604
  return;
642
605
  }
643
606
 
@@ -652,7 +615,6 @@ export default {
652
615
  // Check if we wanna add loading to all empty annotations
653
616
  if (this.hoveredAnnotationSet) {
654
617
  this.isLoading = true;
655
- this.saveChanges = false;
656
618
  return;
657
619
  }
658
620
 
@@ -662,7 +624,6 @@ export default {
662
624
  annotation.label === this.label.id
663
625
  ) {
664
626
  this.isLoading = true;
665
- this.saveChanges = false;
666
627
  return;
667
628
  }
668
629
  }
@@ -67,10 +67,6 @@ export default {
67
67
  required: false,
68
68
  default: 0,
69
69
  },
70
- saveChanges: {
71
- type: Boolean,
72
- required: false,
73
- },
74
70
  isMissingAnnotation: {
75
71
  type: Boolean,
76
72
  required: true,
@@ -10,7 +10,31 @@
10
10
  />
11
11
  </div>
12
12
 
13
- <DocumentName :data-file-name="selectedDocument.data_file_name" />
13
+ <div class="center-bar-components">
14
+ <div
15
+ :class="[
16
+ 'left-arrow navigation-arrow',
17
+ !previousDocument && 'navigation-disabled',
18
+ ]"
19
+ type="button"
20
+ @click="navigateToDocument(previousDocument)"
21
+ >
22
+ <b-icon icon="angle-left" size="is-small" />
23
+ </div>
24
+
25
+ <DocumentName :data-file-name="selectedDocument.data_file_name" />
26
+
27
+ <div
28
+ :class="[
29
+ 'right-arrow navigation-arrow',
30
+ !nextDocument && 'navigation-disabled',
31
+ ]"
32
+ type="button"
33
+ @click="navigateToDocument(nextDocument)"
34
+ >
35
+ <b-icon icon="angle-right" size="is-small" />
36
+ </div>
37
+ </div>
14
38
 
15
39
  <div v-if="!recalculatingAnnotations" class="right-bar-components">
16
40
  <div
@@ -87,6 +111,8 @@ export default {
87
111
  data() {
88
112
  return {
89
113
  categoryError: false,
114
+ previousDocument: null,
115
+ nextDocument: null,
90
116
  };
91
117
  },
92
118
  computed: {
@@ -98,7 +124,19 @@ export default {
98
124
  ]),
99
125
  ...mapState("category", ["categories"]),
100
126
  ...mapState("edit", ["editMode"]),
101
- ...mapGetters("document", ["isDocumentReviewed"]),
127
+ ...mapState("project", ["documentsInProject"]),
128
+ ...mapGetters("document", [
129
+ "isDocumentReviewed",
130
+ "isDocumentReadyToBeReviewed",
131
+ "waitingForSplittingConfirmation",
132
+ ]),
133
+ },
134
+ watch: {
135
+ documentsInProject(newValue) {
136
+ if (newValue && this.selectedDocument) {
137
+ this.getPreviousAndNextDocuments();
138
+ }
139
+ },
102
140
  },
103
141
  created() {
104
142
  window.addEventListener("resize", this.handleResize);
@@ -120,6 +158,42 @@ export default {
120
158
  handleResize() {
121
159
  this.setComponentWidth(this.$refs.documentTopBar.offsetWidth);
122
160
  },
161
+ getPreviousAndNextDocuments() {
162
+ // Only consider documents who have a status of "ready"
163
+ const filteredDocuments = this.documentsInProject.filter(
164
+ (document) =>
165
+ this.isDocumentReadyToBeReviewed(document) ||
166
+ this.waitingForSplittingConfirmation(document)
167
+ );
168
+
169
+ if (!filteredDocuments) return;
170
+
171
+ const found = filteredDocuments.find(
172
+ (document) => document.id === this.selectedDocument.id
173
+ );
174
+
175
+ const indexOfCurrentDocument = filteredDocuments.indexOf(found);
176
+
177
+ if (!(indexOfCurrentDocument < 0)) {
178
+ this.previousDocument = filteredDocuments[indexOfCurrentDocument - 1];
179
+ this.nextDocument = filteredDocuments[indexOfCurrentDocument + 1];
180
+ } else {
181
+ this.previousDocument = filteredDocuments[0];
182
+ this.nextDocument = filteredDocuments[1];
183
+ }
184
+ },
185
+ navigateToDocument(document) {
186
+ if (!document) return;
187
+
188
+ this.$store.dispatch("document/changeCurrentDocument", document.id);
189
+
190
+ if (this.editMode) {
191
+ // Reset edit mode when changing the document,
192
+ // in case the change was made from the arrows in the Edit Mode
193
+ // so that the user does not get stuck in this interface
194
+ this.$store.dispatch("edit/disableEditMode");
195
+ }
196
+ },
123
197
  },
124
198
  };
125
199
  </script>
@@ -110,12 +110,7 @@ export default {
110
110
  },
111
111
  methods: {
112
112
  changeDocument(documentId) {
113
- if (getURLQueryParam("document") || getURLPath("docs")) {
114
- navigateToNewDocumentURL(this.selectedDocument.id, documentId);
115
- } else {
116
- this.$store.dispatch("document/setDocId", documentId);
117
- this.$store.dispatch("document/fetchDocument");
118
- }
113
+ this.$store.dispatch("document/changeCurrentDocument", documentId);
119
114
  },
120
115
  requestTrialAccess() {
121
116
  window.open("https://konfuzio.com", "_blank");
@@ -3,7 +3,7 @@ import myImports from "../api";
3
3
  const HTTP = myImports.HTTP;
4
4
 
5
5
  const state = {
6
- documentsInProject: [],
6
+ createAvailableListOfDocuments: false,
7
7
  documentsAvailableToReview: [], // filtered by user
8
8
  categories: null,
9
9
  };
@@ -39,37 +39,21 @@ const getters = {
39
39
  };
40
40
 
41
41
  const actions = {
42
- setDocumentsInProject: ({ commit }, documents) => {
43
- commit("SET_DOCUMENTS_IN_PROJECT", documents);
44
- },
45
42
  setDocumentsAvailableToReview: ({ commit }, documentsAvailableToReview) => {
46
43
  commit("SET_AVAILABLE_DOCUMENTS", documentsAvailableToReview);
47
44
  },
48
45
  setCategories: ({ commit }, categories) => {
49
46
  commit("SET_CATEGORIES", categories);
50
47
  },
51
- /**
52
- * Actions that use HTTP requests always return the axios promise,
53
- * so they can be `await`ed (useful to set the `loading` status).
54
- */
55
- fetchDocumentList: ({ commit, rootState }, categoryId) => {
56
- return HTTP.get(
57
- `documents/?category=${categoryId}&assignee=${rootState.project.currentUser}&limit=100`
58
- )
59
- .then((response) => {
60
- if (response.data.results) {
61
- commit("SET_DOCUMENTS_IN_PROJECT", response.data.results);
62
- }
63
- })
64
- .catch((error) => {
65
- console.log(error, "Could not fetch document list from the backend");
66
- });
67
- },
68
48
 
69
49
  createAvailableDocumentsList: (
70
- { commit, state, dispatch, rootGetters },
50
+ { commit, state, dispatch, rootState, rootGetters },
71
51
  { categoryId, user, poll }
72
52
  ) => {
53
+ if (!state.createAvailableListOfDocuments) return;
54
+
55
+ const parameters = `category=${categoryId}`;
56
+
73
57
  const sleep = (duration) =>
74
58
  new Promise((resolve) => setTimeout(resolve, duration));
75
59
 
@@ -80,10 +64,10 @@ const actions = {
80
64
  let errors = 0;
81
65
  count += 1;
82
66
 
83
- return dispatch("fetchDocumentList", categoryId).then(() => {
84
- for (let i = 0; i < state.documentsInProject.length; i++) {
67
+ return dispatch("project/fetchDocumentList", parameters).then(() => {
68
+ for (let i = 0; i < rootState.project.documentsInProject.length; i++) {
85
69
  const found = state.documentsAvailableToReview.find(
86
- (doc) => doc.id === state.documentsInProject[i].id
70
+ (doc) => doc.id === rootState.project.documentsInProject[i].id
87
71
  );
88
72
 
89
73
  if (found) {
@@ -92,14 +76,17 @@ const actions = {
92
76
  continue;
93
77
  } else if (
94
78
  rootGetters["document/isDocumentReadyToBeReviewed"](
95
- state.documentsInProject[i]
79
+ rootState.project.documentsInProject[i]
96
80
  )
97
81
  ) {
98
82
  // add available doc to the end of the array
99
- commit("ADD_AVAILABLE_DOCUMENT", state.documentsInProject[i]);
83
+ commit(
84
+ "ADD_AVAILABLE_DOCUMENT",
85
+ rootState.project.documentsInProject[i]
86
+ );
100
87
  } else if (
101
88
  rootGetters["document/documentHadErrorDuringExtraction"](
102
- state.documentsInProject[i]
89
+ rootState.project.documentsInProject[i]
103
90
  )
104
91
  ) {
105
92
  dispatch("document/setDocumentError", null, { root: true });
@@ -118,10 +105,10 @@ const actions = {
118
105
  // And if the difference is due to errors or to docs not ready
119
106
  if (
120
107
  poll &&
121
- state.documentsInProject.length !==
108
+ rootState.project.documentsInProject.length !==
122
109
  state.documentsAvailableToReview.length &&
123
110
  state.documentsAvailableToReview.length + errors !==
124
- state.documentsInProject.length
111
+ rootState.project.documentsInProject.length
125
112
  ) {
126
113
  if (count >= 10) return true;
127
114
 
@@ -137,8 +124,8 @@ const actions = {
137
124
 
138
125
  // Poll as long as the lengths are different
139
126
  if (
140
- state.documentsInProject.length === 0 ||
141
- state.documentsInProject.length !==
127
+ rootState.project.documentsInProject.length === 0 ||
128
+ rootState.project.documentsInProject.length !==
142
129
  state.documentsAvailableToReview.length
143
130
  ) {
144
131
  let duration;
@@ -169,9 +156,6 @@ const actions = {
169
156
  };
170
157
 
171
158
  const mutations = {
172
- SET_DOCUMENTS_IN_PROJECT: (state, documents) => {
173
- state.documentsInProject = documents;
174
- },
175
159
  SET_AVAILABLE_DOCUMENTS: (state, documentsAvailableToReview) => {
176
160
  state.documentsAvailableToReview = documentsAvailableToReview;
177
161
  },
@@ -1,5 +1,10 @@
1
1
  import myImports from "../api";
2
- import { sleep } from "../utils/utils";
2
+ import {
3
+ sleep,
4
+ getURLQueryParam,
5
+ navigateToNewDocumentURL,
6
+ getURLPath,
7
+ } from "../utils/utils";
3
8
 
4
9
  const HTTP = myImports.HTTP;
5
10
  const documentPollDuration = 1000;
@@ -758,8 +763,13 @@ const actions = {
758
763
  await dispatch("category/fetchCategories", projectId, {
759
764
  root: true,
760
765
  });
766
+
767
+ // get list of documents not reviewed
768
+ await dispatch("project/fetchDocumentList", "is_reviewed=false", {
769
+ root: true,
770
+ });
761
771
  }
762
- if (categoryId) {
772
+ if (categoryId && rootState.category.createAvailableListOfDocuments) {
763
773
  await dispatch(
764
774
  "category/createAvailableDocumentsList",
765
775
  {
@@ -1088,6 +1098,20 @@ const actions = {
1088
1098
 
1089
1099
  window.open(fullUrl, "_blank");
1090
1100
  },
1101
+
1102
+ changeCurrentDocument: ({ commit, state, dispatch }, newDocumentId) => {
1103
+ // reset splitting suggestions
1104
+ if (state.splittingSuggestions) {
1105
+ commit("SET_SPLITTING_SUGGESTIONS", null);
1106
+ }
1107
+
1108
+ if (getURLQueryParam("document") || getURLPath("d")) {
1109
+ navigateToNewDocumentURL(state.selectedDocument.id, newDocumentId);
1110
+ } else {
1111
+ commit("SET_DOC_ID", newDocumentId);
1112
+ dispatch("fetchDocument");
1113
+ }
1114
+ },
1091
1115
  };
1092
1116
 
1093
1117
  const mutations = {
package/src/store/edit.js CHANGED
@@ -271,7 +271,6 @@ const mutations = {
271
271
  },
272
272
  ADD_SELECTED_PAGE: (state, selectedPage) => {
273
273
  state.selectedPages.push(selectedPage);
274
- console.log(state.selectedPages);
275
274
  },
276
275
  SET_SHOW_EDIT_CONFIRMATION_MODAL: (state, value) => {
277
276
  state.showEditConfirmationModal = value;
@@ -5,6 +5,7 @@ const state = {
5
5
  projectId: null,
6
6
  currentUser: null,
7
7
  documentsListPath: null,
8
+ documentsInProject: null,
8
9
  };
9
10
 
10
11
  const getters = {
@@ -80,6 +81,24 @@ const actions = {
80
81
  setDocumentsListPath: ({ commit }, path) => {
81
82
  commit("SET_DOCUMENTS_LIST_PATH", path);
82
83
  },
84
+
85
+ setDocumentsInProject: ({ commit }, documents) => {
86
+ commit("SET_DOCUMENTS_IN_PROJECT", documents);
87
+ },
88
+
89
+ fetchDocumentList: ({ commit, state }, parameters) => {
90
+ return HTTP.get(
91
+ `documents/?project=${state.projectId}&assignee=${state.currentUser}&limit=100&${parameters}`
92
+ )
93
+ .then((response) => {
94
+ if (response.data.results) {
95
+ commit("SET_DOCUMENTS_IN_PROJECT", response.data.results);
96
+ }
97
+ })
98
+ .catch((error) => {
99
+ console.log(error, "Could not fetch document list from the backend");
100
+ });
101
+ },
83
102
  };
84
103
 
85
104
  const mutations = {
@@ -92,6 +111,9 @@ const mutations = {
92
111
  SET_DOCUMENTS_LIST_PATH: (state, path) => {
93
112
  state.documentsListPath = path;
94
113
  },
114
+ SET_DOCUMENTS_IN_PROJECT: (state, documents) => {
115
+ state.documentsInProject = documents;
116
+ },
95
117
  };
96
118
 
97
119
  export default {