@konfuzio/document-validation-ui 0.1.5 → 0.1.6-multi-ann-set

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/dist/css/app.css +1 -1
  2. package/dist/index.html +1 -1
  3. package/dist/js/app.js +1 -1
  4. package/dist/js/app.js.map +1 -1
  5. package/package.json +1 -1
  6. package/src/.DS_Store +0 -0
  7. package/src/assets/images/DraggableIcon.vue +14 -0
  8. package/src/assets/images/GridIcon.vue +16 -0
  9. package/src/assets/images/MagicWandIcon.vue +16 -0
  10. package/src/assets/images/NotFoundIcon.vue +16 -0
  11. package/src/assets/images/SettingsIcon.vue +14 -0
  12. package/src/assets/images/SplitZigZag.vue +47 -14
  13. package/src/assets/images/StarIcon.vue +16 -0
  14. package/src/assets/scss/ann_set_table_options.scss +26 -0
  15. package/src/assets/scss/annotation_details.scss +85 -73
  16. package/src/assets/scss/document_annotations.scss +54 -57
  17. package/src/assets/scss/document_category.scss +0 -1
  18. package/src/assets/scss/document_dashboard.scss +7 -2
  19. package/src/assets/scss/document_edit.scss +90 -46
  20. package/src/assets/scss/main.scss +725 -7
  21. package/src/assets/scss/multi_ann_table_overlay.scss +44 -0
  22. package/src/assets/scss/splitting_confirmation_modal.scss +41 -0
  23. package/src/assets/scss/variables.scss +2 -657
  24. package/src/components/App.vue +3 -2
  25. package/src/components/DocumentAnnotations/AnnotationActionButtons.vue +153 -0
  26. package/src/components/DocumentAnnotations/AnnotationDetails.vue +28 -7
  27. package/src/components/DocumentAnnotations/AnnotationRow.vue +131 -40
  28. package/src/components/DocumentAnnotations/AnnotationSetActionButtons.vue +86 -0
  29. package/src/components/DocumentAnnotations/CategorizeModal.vue +24 -2
  30. package/src/components/DocumentAnnotations/DocumentAnnotations.vue +114 -82
  31. package/src/components/DocumentAnnotations/EmptyAnnotation.vue +16 -3
  32. package/src/components/DocumentAnnotations/ExtractingData.vue +3 -3
  33. package/src/components/DocumentAnnotations/index.js +0 -1
  34. package/src/components/DocumentCategory.vue +13 -5
  35. package/src/components/DocumentDashboard.vue +17 -6
  36. package/src/components/DocumentEdit/DocumentEdit.vue +208 -68
  37. package/src/components/DocumentEdit/EditConfirmationModal.vue +54 -0
  38. package/src/components/DocumentEdit/EditPages.vue +29 -18
  39. package/src/components/DocumentEdit/EditSidebar.vue +92 -45
  40. package/src/components/DocumentEdit/SidebarButtons.vue +53 -0
  41. package/src/components/DocumentEdit/SplitInfoBar.vue +19 -0
  42. package/src/components/DocumentEdit/SplitOverview.vue +4 -5
  43. package/src/components/{DocumentError.vue → DocumentModals/DocumentErrorModal.vue} +3 -4
  44. package/src/components/{NotOptimizedViewportModal.vue → DocumentModals/NotOptimizedViewportModal.vue} +2 -2
  45. package/src/components/DocumentModals/SplittingSuggestionsModal.vue +120 -0
  46. package/src/components/DocumentPage/ActionBar.vue +3 -3
  47. package/src/components/DocumentPage/AnnSetTableOptions.vue +107 -0
  48. package/src/components/DocumentPage/DocumentPage.vue +12 -2
  49. package/src/components/DocumentPage/MultiAnnSelection.vue +89 -1
  50. package/src/components/DocumentPage/MultiAnnotationTableOverlay.vue +274 -0
  51. package/src/components/DocumentPage/MultiAnnotationTablePopup.vue +19 -46
  52. package/src/components/DocumentPage/ScrollingDocument.vue +43 -4
  53. package/src/components/DocumentPage/ScrollingPage.vue +4 -5
  54. package/src/components/DocumentThumbnails/DocumentThumbnails.vue +14 -11
  55. package/src/components/DocumentTopBar/DocumentTopBarButtons.vue +35 -30
  56. package/src/components/DocumentTopBar/KeyboardActionsDescription.vue +3 -1
  57. package/src/locales/de.json +23 -6
  58. package/src/locales/en.json +24 -6
  59. package/src/locales/es.json +23 -6
  60. package/src/store/display.js +51 -0
  61. package/src/store/document.js +171 -23
  62. package/src/store/edit.js +67 -48
  63. package/src/store/project.js +14 -14
  64. package/src/components/DocumentAnnotations/ActionButtons.vue +0 -257
  65. package/src/components/DocumentAnnotations/RejectedLabels.vue +0 -96
@@ -45,6 +45,7 @@
45
45
  <script>
46
46
  import { mapGetters, mapState, mapActions } from "vuex";
47
47
  import { ChooseLabelSetModal } from "../DocumentAnnotations";
48
+ import { table_reference_api } from "../../store/document";
48
49
 
49
50
  export default {
50
51
  props: {
@@ -114,6 +115,7 @@ export default {
114
115
  };
115
116
  },
116
117
  ...mapState("selection", ["selection", "isSelecting"]),
118
+ ...mapState("document", ["documentId"]),
117
119
  ...mapGetters("display", ["clientToBbox"]),
118
120
  ...mapGetters("document", ["entitiesOnSelection"]),
119
121
  },
@@ -137,7 +139,7 @@ export default {
137
139
  customClass: "invisible-parent-modal",
138
140
  props: { isMultipleAnnotations: true },
139
141
  events: {
140
- labelSet: this.chooseLabelSet,
142
+ labelSet: this.submitAnnotations,
141
143
  },
142
144
  });
143
145
  },
@@ -156,6 +158,48 @@ export default {
156
158
  this.$emit("finished", tableSelection);
157
159
  },
158
160
 
161
+ async submitAnnotations(labelSet) {
162
+ const columns = labelSet.labels.map((label) => {
163
+ return {
164
+ field: `${label.id}`,
165
+ label: label.name,
166
+ centered: true,
167
+ };
168
+ });
169
+
170
+ const orderedEntities = this.processRows(columns);
171
+
172
+ const annotations = [];
173
+
174
+ orderedEntities.forEach((orderedEntity) => {
175
+ annotations.push({
176
+ document: this.documentId,
177
+ span: orderedEntity.spans,
178
+ label: orderedEntity.label_id,
179
+ is_correct: true,
180
+ revised: false,
181
+ label_set: labelSet.id,
182
+ set_reference: orderedEntity.row_index,
183
+ origin: table_reference_api,
184
+ });
185
+ });
186
+
187
+ this.$store
188
+ .dispatch("document/createAnnotation", annotations)
189
+ .then(() => {
190
+ this.$store.dispatch("selection/disableSelection");
191
+ this.$emit("finished");
192
+ })
193
+ .catch((error) => {
194
+ this.$store.dispatch("document/createErrorMessage", {
195
+ error,
196
+ serverErrorMessage: this.$t("server_error"),
197
+ defaultErrorMessage: this.$t("error_creating_multi_ann"),
198
+ });
199
+ this.$emit("finished");
200
+ });
201
+ },
202
+
159
203
  onButtonEnter() {
160
204
  this.$emit("buttonEnter");
161
205
  },
@@ -296,6 +340,50 @@ export default {
296
340
 
297
341
  this.entities = cols;
298
342
  },
343
+
344
+ processRows(columns) {
345
+ const orderedEntities = []; // this will match the order of entities in the table so we have a way of tracking them once we submit
346
+ let rowIndex = 0;
347
+
348
+ Object.entries(this.entities).forEach(([key, groupedEntity]) => {
349
+ let row = null;
350
+ columns.forEach((column, index) => {
351
+ let spans = [];
352
+ if (
353
+ Object.entries(groupedEntity)[index] &&
354
+ Object.entries(groupedEntity)[index].length > 0
355
+ ) {
356
+ spans = Object.entries(groupedEntity)[index][1];
357
+ }
358
+ const entityExists = spans.length > 0;
359
+
360
+ let textContent = "";
361
+
362
+ spans.forEach((entity) => {
363
+ textContent = `${textContent} ${entity.offset_string}`;
364
+ });
365
+
366
+ row = {
367
+ ...row,
368
+ [column.field]: textContent,
369
+ };
370
+ if (entityExists) {
371
+ const customEntity = {
372
+ spans: [...spans],
373
+ label_id: column.field,
374
+ row_index: rowIndex,
375
+ };
376
+
377
+ orderedEntities.push(customEntity);
378
+ }
379
+ });
380
+ if (row !== null) {
381
+ rowIndex++;
382
+ }
383
+ });
384
+ return orderedEntities;
385
+ },
386
+
299
387
  ...mapActions("selection", ["moveSelection"]),
300
388
  },
301
389
  };
@@ -0,0 +1,274 @@
1
+ <template>
2
+ <div class="multi-ann-table-overlay">
3
+ <b-table
4
+ ref="table"
5
+ class="multi-ann-set-table dark-header header-32"
6
+ detail-icon="faScissors"
7
+ :data="rows"
8
+ :sticky-header="true"
9
+ :narrowed="true"
10
+ :bordered="false"
11
+ draggable-column
12
+ @columndragstart="columndragstart"
13
+ @columndrop="columndrop"
14
+ @columndragover="columndragover"
15
+ @columndragleave="columndragleave"
16
+ >
17
+ <b-table-column
18
+ v-for="(item, index) in columns"
19
+ :key="index"
20
+ :field="item.field"
21
+ :label="item.label.name"
22
+ >
23
+ <template #header="{ column }">
24
+ <b-dropdown
25
+ :ref="getDropdownReference(item)"
26
+ aria-role="list"
27
+ class="header-dropdown"
28
+ position="is-top-left"
29
+ :close-on-click="false"
30
+ @active-change="(e) => onDropdownChange(item, e)"
31
+ >
32
+ <template #trigger="{ active }">
33
+ <DraggableIcon class="draggable" />
34
+ <span :class="active ? 'active' : ''">{{ column.label }} </span>
35
+ <b-icon
36
+ :icon="active ? 'angle-up' : 'angle-down'"
37
+ size="is-small"
38
+ class="arrow"
39
+ />
40
+ </template>
41
+
42
+ <div v-if="editingLabels.length === 0">
43
+ <b-dropdown-item aria-role="listitem" @click="editLabel(item)"
44
+ ><span>{{ $t("edit_label") }}</span></b-dropdown-item
45
+ >
46
+ <b-dropdown-item
47
+ aria-role="listitem"
48
+ class="delete-action"
49
+ @click="deleteColumn(item)"
50
+ >
51
+ <span>{{ $t("delete_label") }}</span></b-dropdown-item
52
+ >
53
+ </div>
54
+ <div v-else>
55
+ <b-dropdown-item
56
+ v-for="label in editingLabels"
57
+ :key="label.id"
58
+ aria-role="listitem"
59
+ :disabled="label.disabled"
60
+ ><span @click="changeLabel(item, label)">{{
61
+ label.name
62
+ }}</span></b-dropdown-item
63
+ >
64
+ </div>
65
+ </b-dropdown>
66
+ </template>
67
+
68
+ <template #default="props">
69
+ <div class="annotations-table">
70
+ <AnnotationRow
71
+ :annotation="props.row[item.field]"
72
+ :label="item.label"
73
+ :annotation-set="item.annotationSet"
74
+ :show-label="false"
75
+ :show-buttons="false"
76
+ :is-small="true"
77
+ :from-table="true"
78
+ />
79
+ </div>
80
+ </template>
81
+ </b-table-column>
82
+ </b-table>
83
+ </div>
84
+ </template>
85
+
86
+ <script>
87
+ import { mapState } from "vuex";
88
+ import AnnotationRow from "../DocumentAnnotations/AnnotationRow";
89
+ import DraggableIcon from "../../assets/images/DraggableIcon";
90
+
91
+ export default {
92
+ name: "MultiAnnotationTablePopup",
93
+ components: {
94
+ AnnotationRow,
95
+ DraggableIcon,
96
+ },
97
+ data() {
98
+ return {
99
+ rows: [],
100
+ columns: [],
101
+ orderedAnnotations: [],
102
+ editingLabels: [],
103
+ openDropdown: null,
104
+ draggingColumnIndex: null,
105
+ };
106
+ },
107
+ computed: {
108
+ ...mapState("display", ["showAnnSetTable"]),
109
+ },
110
+ watch: {
111
+ showAnnSetTable() {
112
+ this.handleColumns();
113
+ this.handleRows();
114
+ },
115
+ },
116
+ mounted() {
117
+ this.handleColumns();
118
+ this.handleRows();
119
+ },
120
+ methods: {
121
+ getDropdownReference(column) {
122
+ return `editDropdown_${column.field}`;
123
+ },
124
+ handleColumns() {
125
+ this.columns = [];
126
+ const labelAlreadyExists = (label) => {
127
+ return (
128
+ this.columns.length > 0 &&
129
+ this.columns.find((a) => a.field === `${label.id}`) != undefined
130
+ );
131
+ };
132
+
133
+ this.showAnnSetTable.forEach((annotationSet) => {
134
+ annotationSet.labels.forEach((label) => {
135
+ if (!labelAlreadyExists(label) && label.annotations.length > 0) {
136
+ const column = {
137
+ field: `${label.id}`,
138
+ label: label,
139
+ annotationSet,
140
+ centered: false,
141
+ };
142
+ this.columns.push(column);
143
+ }
144
+ });
145
+ });
146
+ },
147
+
148
+ handleRows() {
149
+ this.rows = [];
150
+ this.orderedAnnotations = [];
151
+
152
+ this.showAnnSetTable.forEach((annotationSet) => {
153
+ let row = {};
154
+
155
+ annotationSet.labels.forEach((label) => {
156
+ if (label.annotations.length > 0) {
157
+ row[label.id] = label.annotations[0];
158
+ this.orderedAnnotations.push(label.annotations[0]);
159
+ }
160
+ });
161
+ this.rows.push(row);
162
+ });
163
+ },
164
+
165
+ async editLabel(column) {
166
+ this.$store
167
+ .dispatch(
168
+ "project/fetchLabelSetDetails",
169
+ column.annotationSet.label_set.id
170
+ )
171
+ .then(async (labelSet) => {
172
+ this.editingLabels = [];
173
+
174
+ labelSet.labels.forEach((label) => {
175
+ const dropdownLabel = {
176
+ ...label,
177
+ disabled:
178
+ this.columns.find((column) => column.label.id === label.id) !==
179
+ undefined,
180
+ };
181
+ this.editingLabels.push(dropdownLabel);
182
+ });
183
+ });
184
+ },
185
+
186
+ async changeLabel(column, label) {
187
+ for (let i = 0; i < this.rows.length; i++) {
188
+ const annotationToUpdate = this.rows[i][column.label.id];
189
+ await this.$store
190
+ .dispatch("document/updateAnnotation", {
191
+ annotationId: annotationToUpdate.id,
192
+ updatedValues: { label: label.id },
193
+ })
194
+ .catch((error) => {
195
+ this.$store.dispatch("document/createErrorMessage", {
196
+ error,
197
+ serverErrorMessage: this.$t("server_error"),
198
+ defaultErrorMessage: this.$t("edit_error"),
199
+ });
200
+ });
201
+ }
202
+ this.closeDropdown(column);
203
+ },
204
+
205
+ async deleteColumn(column) {
206
+ for (let i = 0; i < this.rows.length; i++) {
207
+ const annotationToDelete = this.rows[i][column.label.id];
208
+ await this.$store
209
+ .dispatch("document/deleteAnnotation", {
210
+ annotationId: annotationToDelete.id,
211
+ })
212
+ .catch((error) => {
213
+ this.$store.dispatch("document/createErrorMessage", {
214
+ error,
215
+ serverErrorMessage: this.$t("server_error"),
216
+ defaultErrorMessage: this.$t("edit_error"),
217
+ });
218
+ });
219
+ }
220
+ this.closeDropdown(column);
221
+ },
222
+
223
+ onDropdownChange(column, open) {
224
+ this.editingLabels = [];
225
+ if (open) {
226
+ if (this.openDropdown) {
227
+ this.$refs[this.openDropdown][0].toggle();
228
+ }
229
+ this.openDropdown = this.getDropdownReference(column);
230
+ } else {
231
+ if (this.openDropdown === this.getDropdownReference(column)) {
232
+ this.openDropdown = null;
233
+ }
234
+ }
235
+ },
236
+
237
+ closeDropdown(column) {
238
+ if (this.openDropdown) {
239
+ this.$refs[this.getDropdownReference(column)][0].toggle();
240
+ this.openDropdown = null;
241
+ }
242
+ },
243
+
244
+ columndragstart(payload) {
245
+ this.draggingColumnIndex = payload.index;
246
+ payload.event.dataTransfer.effectAllowed = "copy";
247
+ },
248
+ columndragover(payload) {
249
+ payload.event.dataTransfer.dropEffect = "copy";
250
+ payload.event.target.closest("th").classList.add("is-selected");
251
+ payload.event.preventDefault();
252
+ },
253
+ columndragleave(payload) {
254
+ payload.event.target.closest("th").classList.remove("is-selected");
255
+ payload.event.preventDefault();
256
+ },
257
+ async columndrop(payload) {
258
+ payload.event.target.closest("th").classList.remove("is-selected");
259
+ const droppedOnColumnIndex = payload.index;
260
+
261
+ const draggingColumn = this.columns[this.draggingColumnIndex];
262
+ const droppedColumn = this.columns[droppedOnColumnIndex];
263
+
264
+ await this.changeLabel(draggingColumn, droppedColumn.label);
265
+ await this.changeLabel(droppedColumn, draggingColumn.label);
266
+ },
267
+ },
268
+ };
269
+ </script>
270
+ <style
271
+ scoped
272
+ lang="scss"
273
+ src="../../assets/scss/multi_ann_table_overlay.scss"
274
+ ></style>
@@ -156,63 +156,36 @@ export default {
156
156
  }
157
157
  },
158
158
  async submitAnnotations() {
159
- let errorMessageShown = false;
160
- let previousAnnotationSetId = null;
161
- let previousRowIndex = 0;
162
-
163
159
  this.$store.dispatch("display/showDocumentActionBar", {
164
160
  show: true,
165
161
  loading: false,
166
162
  action: true,
167
163
  });
168
164
 
169
- // traditional for to await for every request
170
- for (let i = 0; i < this.orderedEntities.length; i++) {
171
- const groupedEntity = this.orderedEntities[i];
172
-
173
- const annotationToCreate = {
165
+ this.orderedEntities.forEach((orderedEntity) => {
166
+ annotations.push({
174
167
  document: this.documentId,
175
- span: groupedEntity.spans,
176
- label: groupedEntity.label_id,
168
+ span: orderedEntity.spans,
169
+ label: orderedEntity.label_id,
177
170
  is_correct: true,
178
171
  revised: false,
179
- };
180
-
181
- if (groupedEntity.row_index !== previousRowIndex) {
182
- // if line changed then reset annotation set
183
- previousAnnotationSetId = null;
184
- }
185
- previousRowIndex = groupedEntity.row_index;
186
-
187
- if (previousAnnotationSetId) {
188
- annotationToCreate.annotation_set = previousAnnotationSetId;
189
- } else {
190
- annotationToCreate.label_set = this.labelSet.id;
191
- }
192
-
193
- await this.$store
194
- .dispatch("document/createAnnotation", annotationToCreate)
195
- .then((response) => {
196
- if (response) {
197
- // set ann set id to use on the next labels on the same row
198
- previousAnnotationSetId = response.data.annotation_set;
199
- }
200
- })
201
- .catch((error) => {
202
- if (!errorMessageShown) {
203
- this.$store.dispatch("document/createErrorMessage", {
204
- error,
205
- serverErrorMessage: this.$t("server_error"),
206
- defaultErrorMessage: this.$t("error_creating_multi_ann"),
207
- });
172
+ label_set: labelSet.id,
173
+ set_reference: orderedEntity.row_index,
174
+ });
175
+ });
208
176
 
209
- // set to true to only show 1 error
210
- // the first time it appears
211
- errorMessageShown = true;
212
- }
177
+ this.$store
178
+ .dispatch("document/createAnnotation", annotations)
179
+ .then(() => {
180
+ this.$emit("close");
181
+ })
182
+ .catch((error) => {
183
+ this.$store.dispatch("document/createErrorMessage", {
184
+ error,
185
+ serverErrorMessage: this.$t("server_error"),
186
+ defaultErrorMessage: this.$t("error_creating_multi_ann"),
213
187
  });
214
- }
215
- this.$emit("close");
188
+ });
216
189
  },
217
190
  deleteRow(index) {
218
191
  this.rows.splice(index, 1);
@@ -11,8 +11,9 @@
11
11
  "
12
12
  >
13
13
  <ScrollingPage
14
- v-for="page in editMode ? documentPagesListForEditMode : pages"
14
+ v-for="page in editMode ? pagesForPostprocess : pages"
15
15
  :key="page.number"
16
+ ref="scrollingPage"
16
17
  :page="page"
17
18
  :client-height="clientHeight"
18
19
  :scroll-top="scrollTop"
@@ -26,20 +27,23 @@
26
27
  </div>
27
28
  <Toolbar v-if="showToolbar" />
28
29
  <ActionBar v-if="showActionBar" />
30
+ <MultiAnnotationTableOverlay v-if="showAnnSetTable" />
29
31
  </div>
30
32
  </template>
31
33
  <script>
32
- import { mapState } from "vuex";
34
+ import { mapState, mapGetters } from "vuex";
33
35
  import scroll from "../../directives/scroll";
34
36
  import ScrollingPage from "./ScrollingPage";
35
37
  import Toolbar from "./DocumentToolbar";
36
38
  import ActionBar from "./ActionBar";
39
+ import MultiAnnotationTableOverlay from "./MultiAnnotationTableOverlay";
37
40
 
38
41
  export default {
39
42
  components: {
40
43
  ScrollingPage,
41
44
  Toolbar,
42
45
  ActionBar,
46
+ MultiAnnotationTableOverlay,
43
47
  },
44
48
  directives: {
45
49
  scroll,
@@ -49,6 +53,8 @@ export default {
49
53
  return {
50
54
  scrollTop: 0,
51
55
  clientHeight: 0,
56
+ isScolling: false,
57
+ scrollTimeout: null,
52
58
  };
53
59
  },
54
60
 
@@ -57,9 +63,21 @@ export default {
57
63
  "recalculatingAnnotations",
58
64
  "selectedDocument",
59
65
  "loading",
66
+ "annotationSets",
60
67
  ]),
61
- ...mapState("edit", ["editMode", "documentPagesListForEditMode"]),
62
- ...mapState("display", ["scale", "documentActionBar"]),
68
+ ...mapState("edit", [
69
+ "editMode",
70
+ "documentPagesListForEditMode",
71
+ "pagesForPostprocess",
72
+ ]),
73
+ ...mapState("display", [
74
+ "scale",
75
+ "documentActionBar",
76
+ "pageChangedFromThumbnail",
77
+ "currentPage",
78
+ "showAnnSetTable",
79
+ ]),
80
+ ...mapGetters("display", ["visiblePageRange"]),
63
81
 
64
82
  pages() {
65
83
  if (this.selectedDocument) {
@@ -85,6 +103,9 @@ export default {
85
103
  this.scrollTop = 0;
86
104
  },
87
105
  },
106
+ mounted() {
107
+ this.$refs.scrollingDocument.addEventListener("scroll", this.handleScroll);
108
+ },
88
109
 
89
110
  methods: {
90
111
  updateScrollBounds() {
@@ -102,6 +123,24 @@ export default {
102
123
 
103
124
  this.$refs.scrollingDocument.scroll(scrollX, scrollY);
104
125
  },
126
+ handleScroll() {
127
+ if (this.pages.length === 1) return;
128
+
129
+ this.isScrolling = true;
130
+
131
+ clearTimeout(this.scrollTimeout);
132
+
133
+ this.scrollTimeout = setTimeout(() => {
134
+ this.isScrolling = false;
135
+
136
+ if (
137
+ this.pageChangedFromThumbnail &&
138
+ this.visiblePageRange[1] === this.currentPage
139
+ ) {
140
+ this.$store.dispatch("display/setPageChangedFromThumbnail", false);
141
+ }
142
+ }, 300);
143
+ },
105
144
  },
106
145
  };
107
146
  </script>
@@ -44,10 +44,12 @@ export default {
44
44
  previousFocusedAnnotation: null,
45
45
  previousY: null,
46
46
  pageBeingLoaded: false,
47
+ isScrolling: false,
47
48
  };
48
49
  },
49
50
 
50
51
  computed: {
52
+ ...mapState("display", ["pageChangedFromThumbnail"]),
51
53
  ...mapGetters("display", ["visiblePageRange", "bboxToRect"]),
52
54
  ...mapGetters("document", ["scrollDocumentToAnnotation"]),
53
55
 
@@ -117,15 +119,12 @@ export default {
117
119
  }
118
120
  },
119
121
  isElementFocused(focused) {
120
- if (!this.loading && focused) {
122
+ if (!this.loading && focused && !this.pageChangedFromThumbnail) {
121
123
  this.$store.dispatch("display/updateCurrentPage", this.page.number);
122
124
  }
123
125
  },
124
126
  currentPage(number) {
125
- if (
126
- (this.page.number === number || this.page.number === number) &&
127
- !this.isElementFocused
128
- ) {
127
+ if (this.page.number === number && !this.isElementFocused) {
129
128
  this.$emit("page-jump", this.elementTop, 0);
130
129
  }
131
130
  },
@@ -62,7 +62,7 @@ export default {
62
62
  },
63
63
  data() {
64
64
  return {
65
- thumbnailClicked: false,
65
+ thumbnailClicked: null,
66
66
  previousScrollPosition: 0,
67
67
  };
68
68
  },
@@ -76,9 +76,10 @@ export default {
76
76
  },
77
77
  watch: {
78
78
  currentPage(newPage) {
79
- if (newPage && !this.thumbnailClicked) {
80
- this.scrollToThumbnail();
81
- }
79
+ if (!newPage) return;
80
+
81
+ // handle thumbnail selection when scrolling the document
82
+ this.scrollToThumbnail(newPage);
82
83
  },
83
84
  },
84
85
  mounted() {
@@ -94,27 +95,29 @@ export default {
94
95
  methods: {
95
96
  /* Change page if not the currently open and not in modal */
96
97
  changePage(pageNumber) {
97
- this.thumbnailClicked = true;
98
+ this.thumbnailClicked = pageNumber;
98
99
 
99
100
  if (
100
101
  !this.loading &&
101
102
  !this.recalculatingAnnotations &&
102
103
  pageNumber != this.currentPage
103
104
  ) {
104
- this.$store.dispatch(
105
- "display/updateCurrentPage",
106
- parseInt(pageNumber, 10)
107
- );
105
+ this.$store.dispatch("display/setPageChangedFromThumbnail", true);
106
+ this.$store.dispatch("display/updateCurrentPage", pageNumber);
108
107
  }
109
108
  },
110
109
 
111
- scrollToThumbnail() {
110
+ scrollToThumbnail(page) {
112
111
  // select only the active thumbnail
113
112
  const selectedPage = this.$refs.docPage.filter((image) =>
114
113
  image.className.includes("selected")
115
114
  );
116
115
 
117
- if (selectedPage && selectedPage[0]) {
116
+ if (page == this.thumbnailClicked) {
117
+ this.thumbnailClicked = null;
118
+ }
119
+
120
+ if (!this.thumbnailClicked && selectedPage && selectedPage[0]) {
118
121
  selectedPage[0].scrollIntoView();
119
122
  }
120
123
  },