@konfuzio/document-validation-ui 0.1.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 (131) hide show
  1. package/.eslintrc.js +11 -0
  2. package/.prettierrc.json +1 -0
  3. package/LICENSE +21 -0
  4. package/README.md +13 -0
  5. package/dist/css/app.0c8973f8.css +1 -0
  6. package/dist/css/chunk-vendors.053b6b6e.css +5 -0
  7. package/dist/favicon.ico +0 -0
  8. package/dist/index.html +1 -0
  9. package/dist/js/app.17fe48c4.js +2 -0
  10. package/dist/js/app.17fe48c4.js.map +1 -0
  11. package/dist/js/chunk-vendors.a48fca3f.js +47 -0
  12. package/dist/js/chunk-vendors.a48fca3f.js.map +1 -0
  13. package/jest.config.js +4 -0
  14. package/package.json +60 -0
  15. package/src/.DS_Store +0 -0
  16. package/src/api.js +49 -0
  17. package/src/assets/images/AcceptedCheckMark.vue +8 -0
  18. package/src/assets/images/AcceptedUser.vue +8 -0
  19. package/src/assets/images/ActionIcon.vue +60 -0
  20. package/src/assets/images/ArrowDownKey.vue +11 -0
  21. package/src/assets/images/ArrowUpKey.vue +11 -0
  22. package/src/assets/images/CategoryIconImg.vue +13 -0
  23. package/src/assets/images/CheckMark.vue +8 -0
  24. package/src/assets/images/EditDocIcon.vue +12 -0
  25. package/src/assets/images/EmptyStateImg.vue +129 -0
  26. package/src/assets/images/ErrorIcon.vue +28 -0
  27. package/src/assets/images/EyeIcon.vue +11 -0
  28. package/src/assets/images/FileNameNotSavedImage.vue +26 -0
  29. package/src/assets/images/FileNameSavedImage.vue +14 -0
  30. package/src/assets/images/FitZoomIcon.vue +16 -0
  31. package/src/assets/images/KeyboardIcon.vue +16 -0
  32. package/src/assets/images/MinusIcon.vue +13 -0
  33. package/src/assets/images/NotOptimizedIllustration.vue +651 -0
  34. package/src/assets/images/PlusIcon.vue +13 -0
  35. package/src/assets/images/QuestionMark.vue +12 -0
  36. package/src/assets/images/ServerImage.vue +63 -0
  37. package/src/assets/images/SplitLines.vue +18 -0
  38. package/src/assets/images/SplitZigZag.vue +16 -0
  39. package/src/assets/images/StatusImg.vue +14 -0
  40. package/src/assets/images/UserIcon.vue +8 -0
  41. package/src/assets/scss/annotation_details.scss +126 -0
  42. package/src/assets/scss/categorize_modal.scss +42 -0
  43. package/src/assets/scss/choose_label_set_modal.scss +62 -0
  44. package/src/assets/scss/document_action_bar.scss +37 -0
  45. package/src/assets/scss/document_annotations.scss +472 -0
  46. package/src/assets/scss/document_category.scss +80 -0
  47. package/src/assets/scss/document_dashboard.scss +47 -0
  48. package/src/assets/scss/document_dataset_status.scss +46 -0
  49. package/src/assets/scss/document_edit.scss +431 -0
  50. package/src/assets/scss/document_error.scss +81 -0
  51. package/src/assets/scss/document_handover.scss +200 -0
  52. package/src/assets/scss/document_name.scss +62 -0
  53. package/src/assets/scss/document_page.scss +8 -0
  54. package/src/assets/scss/document_thumbnails.scss +41 -0
  55. package/src/assets/scss/document_toolbar.scss +89 -0
  56. package/src/assets/scss/document_top_bar.scss +139 -0
  57. package/src/assets/scss/document_viewport_modal.scss +25 -0
  58. package/src/assets/scss/documents_list.scss +130 -0
  59. package/src/assets/scss/empty_state.scss +34 -0
  60. package/src/assets/scss/extracting_data.scss +35 -0
  61. package/src/assets/scss/imports.scss +1 -0
  62. package/src/assets/scss/main.scss +24 -0
  63. package/src/assets/scss/multi_ann_table_popup.scss +12 -0
  64. package/src/assets/scss/new_annotation.scss +86 -0
  65. package/src/assets/scss/scrolling_document.scss +19 -0
  66. package/src/assets/scss/variables.scss +696 -0
  67. package/src/components/App.vue +112 -0
  68. package/src/components/DocumentAnnotations/ActionButtons.vue +237 -0
  69. package/src/components/DocumentAnnotations/AnnotationContent.vue +249 -0
  70. package/src/components/DocumentAnnotations/AnnotationDetails.vue +292 -0
  71. package/src/components/DocumentAnnotations/AnnotationRow.vue +616 -0
  72. package/src/components/DocumentAnnotations/CategorizeModal.vue +159 -0
  73. package/src/components/DocumentAnnotations/ChooseLabelSetModal.vue +155 -0
  74. package/src/components/DocumentAnnotations/DocumentAnnotations.vue +502 -0
  75. package/src/components/DocumentAnnotations/DocumentLabel.vue +148 -0
  76. package/src/components/DocumentAnnotations/EmptyAnnotation.vue +222 -0
  77. package/src/components/DocumentAnnotations/EmptyState.vue +21 -0
  78. package/src/components/DocumentAnnotations/ExtractingData.vue +29 -0
  79. package/src/components/DocumentAnnotations/LoadingAnnotations.vue +43 -0
  80. package/src/components/DocumentAnnotations/LoadingLabels.vue +43 -0
  81. package/src/components/DocumentAnnotations/RejectedLabels.vue +96 -0
  82. package/src/components/DocumentAnnotations/index.js +8 -0
  83. package/src/components/DocumentCategory.vue +156 -0
  84. package/src/components/DocumentDashboard.vue +159 -0
  85. package/src/components/DocumentEdit/DocumentEdit.vue +279 -0
  86. package/src/components/DocumentEdit/EditPages.vue +213 -0
  87. package/src/components/DocumentEdit/EditSidebar.vue +118 -0
  88. package/src/components/DocumentEdit/SplitOverview.vue +182 -0
  89. package/src/components/DocumentEdit/index.js +4 -0
  90. package/src/components/DocumentError.vue +53 -0
  91. package/src/components/DocumentPage/ActionBar.vue +48 -0
  92. package/src/components/DocumentPage/BoxSelection.vue +149 -0
  93. package/src/components/DocumentPage/DocumentPage.vue +517 -0
  94. package/src/components/DocumentPage/DocumentToolbar.vue +145 -0
  95. package/src/components/DocumentPage/DummyPage.vue +53 -0
  96. package/src/components/DocumentPage/MultiAnnSelection.vue +302 -0
  97. package/src/components/DocumentPage/MultiAnnotationTablePopup.vue +253 -0
  98. package/src/components/DocumentPage/NewAnnotation.vue +283 -0
  99. package/src/components/DocumentPage/ScrollingDocument.vue +108 -0
  100. package/src/components/DocumentPage/ScrollingPage.vue +184 -0
  101. package/src/components/DocumentPage/index.js +5 -0
  102. package/src/components/DocumentThumbnails/DocumentThumbnails.vue +92 -0
  103. package/src/components/DocumentThumbnails/LoadingThumbnail.vue +25 -0
  104. package/src/components/DocumentThumbnails/index.js +1 -0
  105. package/src/components/DocumentTopBar/DocumentDatasetStatus.vue +103 -0
  106. package/src/components/DocumentTopBar/DocumentHandover.vue +202 -0
  107. package/src/components/DocumentTopBar/DocumentName.vue +224 -0
  108. package/src/components/DocumentTopBar/DocumentTopBar.vue +144 -0
  109. package/src/components/DocumentTopBar/DocumentTopBarButtons.vue +148 -0
  110. package/src/components/DocumentTopBar/KeyboardActionsDescription.vue +71 -0
  111. package/src/components/DocumentTopBar/index.js +5 -0
  112. package/src/components/DocumentsList/DocumentsList.vue +126 -0
  113. package/src/components/DocumentsList/index.js +1 -0
  114. package/src/components/ErrorMessage.vue +40 -0
  115. package/src/components/NotOptimizedViewportModal.vue +54 -0
  116. package/src/constants.js +4 -0
  117. package/src/directives/scroll.js +28 -0
  118. package/src/i18n.js +23 -0
  119. package/src/locales/de.json +114 -0
  120. package/src/locales/en.json +114 -0
  121. package/src/locales/es.json +113 -0
  122. package/src/main.js +87 -0
  123. package/src/store/category.js +193 -0
  124. package/src/store/display.js +238 -0
  125. package/src/store/document.js +1057 -0
  126. package/src/store/edit.js +210 -0
  127. package/src/store/index.js +22 -0
  128. package/src/store/project.js +95 -0
  129. package/src/store/selection.js +179 -0
  130. package/src/utils/utils.js +3 -0
  131. package/vue.config.js +13 -0
@@ -0,0 +1,283 @@
1
+ <template>
2
+ <div class="annotation-popup" :style="{ left: `${left}px`, top: `${top}px` }">
3
+ <input v-model="textFromEntities" class="popup-input" type="text" />
4
+ <b-dropdown
5
+ v-model="selectedSet"
6
+ aria-role="list"
7
+ :class="[
8
+ 'no-padding-bottom',
9
+ setsList.length === 0 ? 'no-padding-top' : '',
10
+ ]"
11
+ >
12
+ <template #trigger>
13
+ <b-button
14
+ :class="['popup-input', selectedSet ? '' : 'not-selected']"
15
+ type="is-text"
16
+ >
17
+ {{
18
+ selectedSet
19
+ ? `${selectedSet.label_set.name} ${
20
+ selectedSet.id
21
+ ? numberOfAnnotationSetGroup(selectedSet)
22
+ : `(${$t("new")})`
23
+ }`
24
+ : $t("select_annotation_set")
25
+ }}
26
+ <span class="caret-icon">
27
+ <b-icon icon="angle-down" size="is-small" class="caret" />
28
+ </span>
29
+ </b-button>
30
+ </template>
31
+ <b-dropdown-item
32
+ v-for="(set, index) in setsList"
33
+ :key="`${set.label_set.id}_${index}`"
34
+ aria-role="listitem"
35
+ :value="set"
36
+ >
37
+ <span>{{
38
+ `${set.label_set.name} ${
39
+ set.id ? numberOfAnnotationSetGroup(set) : `(${$t("new")})`
40
+ }`
41
+ }}</span>
42
+ </b-dropdown-item>
43
+ <b-button
44
+ type="is-ghost"
45
+ :class="[
46
+ 'add-ann-set',
47
+ 'dropdown-item',
48
+ 'no-icon-margin',
49
+ setsList.length > 0 ? 'has-border' : '',
50
+ ]"
51
+ icon-left="plus"
52
+ @click="openAnnotationSetCreation"
53
+ >
54
+ {{ $t("new_ann_set_title") }}
55
+ </b-button>
56
+ </b-dropdown>
57
+ <b-tooltip
58
+ :label="$t('no_labels_available')"
59
+ multilined
60
+ type="is-dark"
61
+ :active="selectedSet && (!labels || labels.length === 0)"
62
+ size="is-large"
63
+ position="is-bottom"
64
+ >
65
+ <b-dropdown
66
+ v-model="selectedLabel"
67
+ aria-role="list"
68
+ :disabled="!labels || labels.length === 0"
69
+ class="label-dropdown"
70
+ >
71
+ <template #trigger>
72
+ <b-button
73
+ :class="['popup-input', selectedLabel ? '' : 'not-selected']"
74
+ type="is-text"
75
+ >
76
+ {{
77
+ selectedLabel
78
+ ? selectedLabel.name
79
+ : labels && labels.length === 0
80
+ ? $t("no_labels_to_choose")
81
+ : $t("select_label")
82
+ }}
83
+ <span class="caret-icon">
84
+ <b-icon icon="angle-down" size="is-small" class="caret" />
85
+ </span>
86
+ </b-button>
87
+ </template>
88
+ <b-dropdown-item
89
+ v-for="label in labels"
90
+ :key="label.id"
91
+ aria-role="listitem"
92
+ :value="label"
93
+ >
94
+ <span>{{ label.name }}</span>
95
+ </b-dropdown-item>
96
+ </b-dropdown>
97
+ </b-tooltip>
98
+ <div class="annotation-buttons">
99
+ <b-button
100
+ type="is-text"
101
+ class="cancel-button popup-button"
102
+ :label="$t('cancel')"
103
+ :disabled="loading"
104
+ @click.prevent="close"
105
+ />
106
+ <b-button
107
+ type="is-primary"
108
+ class="popup-button"
109
+ :label="$t('save')"
110
+ :disabled="loading || !getTextFromEntities || !selectedLabel"
111
+ @click.prevent="save"
112
+ />
113
+ </div>
114
+ </div>
115
+ </template>
116
+ <script>
117
+ /**
118
+ * This component is used to show a popup
119
+ * for creating a new annotation.
120
+ */
121
+ const heightOfPopup = 192;
122
+ const margin = 12;
123
+ const widthOfPopup = 205;
124
+
125
+ import { mapGetters, mapState } from "vuex";
126
+ import { ChooseLabelSetModal } from "../DocumentAnnotations";
127
+
128
+ export default {
129
+ props: {
130
+ newAnnotation: {
131
+ required: true,
132
+ type: Array,
133
+ },
134
+ containerWidth: {
135
+ type: Number,
136
+ required: true,
137
+ },
138
+ containerHeight: {
139
+ type: Number,
140
+ required: true,
141
+ },
142
+ },
143
+ data() {
144
+ return {
145
+ selectedLabel: null,
146
+ selectedSet: null,
147
+ labels: null,
148
+ loading: false,
149
+ isAnnSetModalShowing: false,
150
+ setsList: [],
151
+ };
152
+ },
153
+ computed: {
154
+ ...mapState("document", ["annotationSets", "documentId"]),
155
+ ...mapGetters("document", [
156
+ "numberOfAnnotationSetGroup",
157
+ "labelsFilteredForAnnotationCreation",
158
+ "getTextFromEntities",
159
+ ]),
160
+ top() {
161
+ const top = this.newAnnotation[0].entity.scaled.y - heightOfPopup; // subtract the height of the popup plus some margin
162
+
163
+ //check if the popup will not go off the container on the top
164
+ return this.newAnnotation[0].entity.scaled.y > heightOfPopup
165
+ ? top
166
+ : this.newAnnotation[0].entity.scaled.y +
167
+ this.newAnnotation[0].entity.scaled.height +
168
+ margin;
169
+ },
170
+ left() {
171
+ const left =
172
+ this.newAnnotation[0].entity.scaled.x +
173
+ this.newAnnotation[0].entity.scaled.width / 2 -
174
+ widthOfPopup / 2; // add the entity half width to be centered and then subtract half the width of the popup
175
+
176
+ //check if the popup will not go off the container
177
+ if (left + widthOfPopup > this.containerWidth) {
178
+ // on the right side
179
+ return this.containerWidth - widthOfPopup;
180
+ } else {
181
+ // on the left side
182
+ return left > 0 ? left : 0;
183
+ }
184
+ },
185
+ textFromEntities() {
186
+ return this.getTextFromEntities();
187
+ },
188
+ },
189
+ watch: {
190
+ selectedSet(newValue) {
191
+ this.selectedLabel = null;
192
+ this.labels = this.labelsFilteredForAnnotationCreation(newValue);
193
+ },
194
+ },
195
+ mounted() {
196
+ this.setsList = [...this.annotationSets];
197
+
198
+ setTimeout(() => {
199
+ // prevent click propagation when opening the popup
200
+ document.body.addEventListener("click", this.clickOutside);
201
+ }, 200);
202
+ },
203
+ destroyed() {
204
+ document.body.removeEventListener("click", this.clickOutside);
205
+ },
206
+ methods: {
207
+ close() {
208
+ this.$store.dispatch("document/setSelectedEntities", null);
209
+ this.$emit("close");
210
+ },
211
+ save() {
212
+ this.loading = true;
213
+ const span = this.newAnnotation.flatMap((ann) => {
214
+ return { ...ann.entity.original, offset_string: ann.content };
215
+ });
216
+
217
+ const annotationToCreate = {
218
+ document: this.documentId,
219
+ span: span,
220
+ label: this.selectedLabel.id,
221
+ is_correct: true,
222
+ revised: false,
223
+ };
224
+
225
+ if (this.selectedSet.id) {
226
+ annotationToCreate.annotation_set = this.selectedSet.id;
227
+ } else {
228
+ annotationToCreate.label_set = this.selectedSet.label_set.id;
229
+ }
230
+
231
+ this.$store
232
+ .dispatch("document/createAnnotation", annotationToCreate)
233
+ .catch((error) => {
234
+ this.$store.dispatch("document/createErrorMessage", {
235
+ error,
236
+ serverErrorMessage: this.$t("server_error"),
237
+ defaultErrorMessage: this.$t("error_creating_annotation"),
238
+ });
239
+ })
240
+ .finally(() => {
241
+ this.close();
242
+ this.loading = false;
243
+ });
244
+ },
245
+ disableLabelSetModalShowing() {
246
+ // timeout to stop propagation of click event
247
+ setTimeout(() => {
248
+ this.isAnnSetModalShowing = false;
249
+ }, 500);
250
+ },
251
+ chooseLabelSet(labelSet) {
252
+ this.disableLabelSetModalShowing();
253
+
254
+ const newSet = {
255
+ label_set: labelSet,
256
+ labels: labelSet.labels,
257
+ id: null,
258
+ };
259
+ this.setsList.push(newSet);
260
+ this.selectedSet = newSet;
261
+ },
262
+ openAnnotationSetCreation() {
263
+ this.isAnnSetModalShowing = true;
264
+
265
+ this.$buefy.modal.open({
266
+ parent: this.$parent,
267
+ component: ChooseLabelSetModal,
268
+ hasModalCard: true,
269
+ trapFocus: true,
270
+ canCancel: false,
271
+ onCancel: this.disableLabelSetModalShowing,
272
+ customClass: "invisible-parent-modal",
273
+ events: {
274
+ labelSet: this.chooseLabelSet,
275
+ close: this.disableLabelSetModalShowing,
276
+ },
277
+ });
278
+ },
279
+ },
280
+ };
281
+ </script>
282
+
283
+ <style scoped lang="scss" src="../../assets/scss/new_annotation.scss"></style>
@@ -0,0 +1,108 @@
1
+ <template>
2
+ <div>
3
+ <div
4
+ ref="scrollingDocument"
5
+ v-scroll.immediate="updateScrollBounds"
6
+ class="scrolling-document"
7
+ >
8
+ <div
9
+ v-if="
10
+ selectedDocument && scale && !loading && !recalculatingAnnotations
11
+ "
12
+ >
13
+ <ScrollingPage
14
+ v-for="page in editMode ? documentPagesListForEditMode : pages"
15
+ :key="page.number"
16
+ :page="page"
17
+ :client-height="clientHeight"
18
+ :scroll-top="scrollTop"
19
+ class="scrolling-page"
20
+ @page-jump="onPageJump"
21
+ />
22
+ </div>
23
+ <div v-else class="loading-page">
24
+ <b-skeleton width="100%" height="1000px" />
25
+ </div>
26
+ </div>
27
+ <Toolbar v-if="showToolbar" />
28
+ <ActionBar v-if="showActionBar" />
29
+ </div>
30
+ </template>
31
+ <script>
32
+ import { mapState } from "vuex";
33
+ import scroll from "../../directives/scroll";
34
+ import ScrollingPage from "./ScrollingPage";
35
+ import Toolbar from "./DocumentToolbar";
36
+ import ActionBar from "./ActionBar";
37
+
38
+ export default {
39
+ components: {
40
+ ScrollingPage,
41
+ Toolbar,
42
+ ActionBar,
43
+ },
44
+ directives: {
45
+ scroll,
46
+ },
47
+
48
+ data() {
49
+ return {
50
+ scrollTop: 0,
51
+ clientHeight: 0,
52
+ };
53
+ },
54
+
55
+ computed: {
56
+ ...mapState("document", [
57
+ "recalculatingAnnotations",
58
+ "selectedDocument",
59
+ "loading",
60
+ ]),
61
+ ...mapState("edit", ["editMode", "documentPagesListForEditMode"]),
62
+ ...mapState("display", ["scale", "documentActionBar"]),
63
+
64
+ pages() {
65
+ if (this.selectedDocument) {
66
+ return this.selectedDocument.pages;
67
+ } else {
68
+ return [];
69
+ }
70
+ },
71
+ showToolbar() {
72
+ return this.pages.length > 0 && this.scale && !this.documentActionBar;
73
+ },
74
+ showActionBar() {
75
+ return this.documentActionBar !== null;
76
+ },
77
+ },
78
+ watch: {
79
+ loading() {
80
+ this.scrollTop = 0;
81
+ },
82
+ },
83
+
84
+ methods: {
85
+ updateScrollBounds() {
86
+ const { scrollTop, clientHeight } = this.$refs.scrollingDocument;
87
+ this.scrollTop = scrollTop;
88
+ this.clientHeight = clientHeight;
89
+ },
90
+ /**
91
+ * Scrolls the ScrollingDocument to the offset specified by scrollTop
92
+ * (i.e., another page).
93
+ */
94
+ onPageJump(scrollTop) {
95
+ const actualScroll = scrollTop;
96
+ this.$refs.scrollingDocument.scrollTop =
97
+ // the 4 comes from the margin between pages
98
+ actualScroll - (this.$refs.scrollingDocument.offsetTop + 4);
99
+ },
100
+ },
101
+ };
102
+ </script>
103
+
104
+ <style
105
+ scoped
106
+ lang="scss"
107
+ src="../../assets/scss/scrolling_document.scss"
108
+ ></style>
@@ -0,0 +1,184 @@
1
+ <template>
2
+ <div>
3
+ <DocumentPage v-if="editMode" :page="page" />
4
+ <DummyPage
5
+ v-else-if="!loadedPage || !pageInVisibleRange(page)"
6
+ :width="page.size[0]"
7
+ :height="page.size[1]"
8
+ />
9
+ <DocumentPage v-else :page="loadedPage" />
10
+ </div>
11
+ </template>
12
+
13
+ <script>
14
+ import { mapGetters, mapState } from "vuex";
15
+ import DocumentPage from "../DocumentPage/DocumentPage";
16
+ import DummyPage from "../DocumentPage/DummyPage";
17
+
18
+ export default {
19
+ name: "ScrollingPage",
20
+
21
+ components: {
22
+ DocumentPage,
23
+ DummyPage,
24
+ },
25
+
26
+ props: {
27
+ page: {
28
+ type: Object,
29
+ required: true,
30
+ },
31
+ scrollTop: {
32
+ type: Number,
33
+ required: true,
34
+ },
35
+ clientHeight: {
36
+ type: Number,
37
+ required: true,
38
+ },
39
+ },
40
+
41
+ data() {
42
+ return {
43
+ elementTop: 0,
44
+ elementHeight: 0,
45
+ previousFocusedAnnotation: null,
46
+ previousY: null,
47
+ pageBeingLoaded: false,
48
+ };
49
+ },
50
+
51
+ computed: {
52
+ ...mapGetters("display", ["visiblePageRange", "bboxToRect"]),
53
+ ...mapGetters("document", ["scrollDocumentToAnnotation"]),
54
+
55
+ loadedPage() {
56
+ let loadedPage = null;
57
+ if (this.page && this.pages) {
58
+ loadedPage = this.pages.find((p) => p.number === this.page.number);
59
+ }
60
+ if (!loadedPage && this.pageInVisibleRange(this.page)) {
61
+ if (!this.pageBeingLoaded) {
62
+ this.loadPage();
63
+ }
64
+ }
65
+ return loadedPage;
66
+ },
67
+
68
+ isElementFocused() {
69
+ const { elementTop, bottom, elementHeight, scrollTop, clientHeight } =
70
+ this;
71
+ if (!elementHeight) return;
72
+
73
+ const halfHeight = elementHeight / 2;
74
+ const halfScreen = clientHeight / 2;
75
+ const delta = elementHeight >= halfScreen ? halfScreen : halfHeight;
76
+ const threshold = scrollTop + delta;
77
+
78
+ return elementTop < threshold && bottom >= threshold;
79
+ },
80
+
81
+ bottom() {
82
+ return this.elementTop + this.elementHeight;
83
+ },
84
+
85
+ scrollBottom() {
86
+ return this.scrollTop + this.clientHeight;
87
+ },
88
+
89
+ ...mapState("display", ["scale", "currentPage"]),
90
+ ...mapState("document", ["pages", "documentAnnotationSelected", "loading"]),
91
+ ...mapState("edit", ["editMode"]),
92
+ },
93
+
94
+ watch: {
95
+ scrollTop: "updateElementBounds",
96
+ clientHeight: "updateElementBounds",
97
+
98
+ /**
99
+ * Scroll to the focused annotation if it changes and it's on this page.
100
+ */
101
+ scrollDocumentToAnnotation(isToScroll) {
102
+ if (
103
+ isToScroll &&
104
+ this.documentAnnotationSelected.page === this.page.number
105
+ ) {
106
+ // We wait for the page to be focused before actually scrolling
107
+ // to the focused annotation.
108
+ this.$nextTick(() => {
109
+ // Scroll to the annotation
110
+ this.scrollTo(this.getYForBbox(this.documentAnnotationSelected.span));
111
+ });
112
+ }
113
+ },
114
+ isElementFocused(focused) {
115
+ if (!this.loading && focused) {
116
+ let pageNumber;
117
+
118
+ // TODO: have the same name for page.number in the edit mode so there's no need to do this validations
119
+ if (this.editMode) {
120
+ pageNumber = this.page.page_number;
121
+ } else {
122
+ pageNumber = this.page.number;
123
+ }
124
+ this.$store.dispatch("display/updateCurrentPage", pageNumber);
125
+ }
126
+ },
127
+ currentPage(number) {
128
+ if (
129
+ (this.page.number === number || this.page.page_number === number) &&
130
+ !this.isElementFocused
131
+ ) {
132
+ this.$emit("page-jump", this.elementTop);
133
+ }
134
+ },
135
+ },
136
+ mounted() {
137
+ this.updateElementBounds();
138
+ },
139
+
140
+ methods: {
141
+ loadPage() {
142
+ this.pageBeingLoaded = true;
143
+ this.$store
144
+ .dispatch("document/fetchDocumentPage", this.page.number)
145
+ .then(() => {
146
+ this.pageBeingLoaded = false;
147
+ });
148
+ },
149
+ pageInVisibleRange(page) {
150
+ let number;
151
+ if (this.editMode) {
152
+ number = page.page_number;
153
+ } else {
154
+ number = page.number;
155
+ }
156
+ return (
157
+ this.currentPage === number || this.visiblePageRange.includes(number)
158
+ );
159
+ },
160
+ updateElementBounds() {
161
+ const { offsetTop, offsetHeight } = this.$el;
162
+ this.elementTop = offsetTop;
163
+ this.elementHeight = offsetHeight;
164
+ },
165
+
166
+ /**
167
+ * Calculate the y-position of this bbox on the page
168
+ * from its top, the scale and the image scale (calculated
169
+ * from the page object).
170
+ */
171
+ getYForBbox(bbox) {
172
+ return this.bboxToRect(this.page, bbox).y;
173
+ },
174
+
175
+ /**
176
+ * Scroll to a relative position in the page. It gets added
177
+ * the page's element top and a padding margin.
178
+ */
179
+ scrollTo(y) {
180
+ this.$emit("page-jump", this.elementTop + y - 80);
181
+ },
182
+ },
183
+ };
184
+ </script>
@@ -0,0 +1,5 @@
1
+ export { default as DocumentPage } from "./DocumentPage";
2
+ export { default as DummyPage } from "./DummyPage";
3
+ export { default as ScrollingDocument } from "./ScrollingDocument";
4
+ export { default as ScrollingPage } from "./ScrollingPage";
5
+ export { default as ToolBar } from "./DocumentToolbar";
@@ -0,0 +1,92 @@
1
+ <template>
2
+ <div class="document-pages">
3
+ <div v-if="selectedDocument">
4
+ <div
5
+ v-for="page in selectedDocument.pages"
6
+ :key="page.id"
7
+ :class="[
8
+ 'document-thumbnail',
9
+ currentPage == page.number && 'selected'
10
+ ]"
11
+ @click="changePage(page.number)"
12
+ >
13
+ <div class="image-section">
14
+ <div class="image-container">
15
+ <ServerImage
16
+ v-if="!loading && !recalculatingAnnotations"
17
+ :class="[
18
+ 'img-thumbnail',
19
+ currentPage == page.number && 'selected'
20
+ ]"
21
+ :width="'40px'"
22
+ :image-url="`${page.thumbnail_url}?${selectedDocument.downloaded_at}`"
23
+ >
24
+ <LoadingThumbnail />
25
+ </ServerImage>
26
+ <LoadingThumbnail v-else />
27
+ </div>
28
+ </div>
29
+ <div class="number-thumbnail">
30
+ {{ page.number }}
31
+ </div>
32
+ </div>
33
+ </div>
34
+ <div v-else>
35
+ <div class="document-thumbnail">
36
+ <div class="image-section">
37
+ <div class="image-container">
38
+ <LoadingThumbnail />
39
+ </div>
40
+ </div>
41
+ </div>
42
+ </div>
43
+ </div>
44
+ </template>
45
+ <script>
46
+ import { mapState } from "vuex";
47
+ import ServerImage from "../../assets/images/ServerImage";
48
+ import LoadingThumbnail from "./LoadingThumbnail.vue";
49
+
50
+ /**
51
+ * This component creates a vertical list of the document pages
52
+ * with thumbnail pictures of it which are also clickable.
53
+ * It also creates a grid list of the pages in the toolbar modal
54
+ * to allow the user to rotate them.
55
+ */
56
+ export default {
57
+ name: "DocumentThumbnails",
58
+ components: {
59
+ ServerImage,
60
+ LoadingThumbnail
61
+ },
62
+ computed: {
63
+ ...mapState("document", [
64
+ "selectedDocument",
65
+ "recalculatingAnnotations",
66
+ "loading"
67
+ ]),
68
+ ...mapState("display", ["currentPage"])
69
+ },
70
+ methods: {
71
+ /* Change page if not the currently open and not in modal */
72
+ changePage(pageNumber) {
73
+ if (
74
+ !this.loading &&
75
+ !this.recalculatingAnnotations &&
76
+ pageNumber != this.currentPage
77
+ ) {
78
+ this.$store.dispatch(
79
+ "display/updateCurrentPage",
80
+ parseInt(pageNumber, 10)
81
+ );
82
+ }
83
+ }
84
+ }
85
+ };
86
+ </script>
87
+
88
+ <style
89
+ scoped
90
+ lang="scss"
91
+ src="../../assets/scss/document_thumbnails.scss"
92
+ ></style>
@@ -0,0 +1,25 @@
1
+ <template>
2
+ <div>
3
+ <b-skeleton
4
+ width="40px"
5
+ height="57px"
6
+ />
7
+ </div>
8
+ </template>
9
+
10
+ <script>
11
+ /**
12
+ * This component shows a skeleton instead of the thumbnails
13
+ * while some document data is still loading
14
+ */
15
+
16
+ export default {
17
+ name: "LoadingThumbnail"
18
+ };
19
+ </script>
20
+
21
+ <style
22
+ scoped
23
+ lang="scss"
24
+ src="../../assets/scss/document_thumbnails.scss"
25
+ ></style>
@@ -0,0 +1 @@
1
+ export { default as DocumentThumbnails } from "./DocumentThumbnails";