@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,517 @@
1
+ <template>
2
+ <div ref="pdfContainer" class="pdf-page-container">
3
+ <NewAnnotation
4
+ v-if="newAnnotation && newAnnotation.length && !editAnnotation"
5
+ :new-annotation="newAnnotation"
6
+ :container-width="scaledViewport.width"
7
+ :container-height="scaledViewport.height"
8
+ @close="closePopups"
9
+ />
10
+ <MultiAnnotationTablePopup
11
+ v-if="newMultiAnnotationSetTable"
12
+ :table-position="newMultiAnnotationSetTable.position"
13
+ :page-size="scaledViewport"
14
+ :label-set="newMultiAnnotationSetTable.labelSet"
15
+ :grouped-entities="newMultiAnnotationSetTable.entities"
16
+ @close="closePopups"
17
+ />
18
+
19
+ <v-stage
20
+ v-if="image && scale"
21
+ ref="stage"
22
+ :config="scaledViewport"
23
+ :style="canvasStyle"
24
+ @mousedown="onMouseDown"
25
+ @mouseup="onMouseUp"
26
+ @mousemove="onMouseMove"
27
+ >
28
+ <v-layer>
29
+ <v-image
30
+ :config="{
31
+ image,
32
+ width: scaledViewport.width,
33
+ height: scaledViewport.height,
34
+ listening: false,
35
+ }"
36
+ />
37
+ <template v-if="pageInVisibleRange && !editMode">
38
+ <v-group v-if="!publicView" ref="entities">
39
+ <v-rect
40
+ v-for="(entity, index) in scaledEntities"
41
+ :key="index"
42
+ :config="entityRect(entity)"
43
+ @click="handleClickedEntity(entity)"
44
+ @mouseenter="onElementEnter"
45
+ @mouseleave="onElementLeave"
46
+ />
47
+ </v-group>
48
+ <template v-for="annotation in pageAnnotations">
49
+ <template
50
+ v-for="(bbox, index) in annotation.span.filter(
51
+ (bbox) => bbox.page_index + 1 == pageNumber
52
+ )"
53
+ >
54
+ <v-rect
55
+ v-if="!isAnnotationInEditMode(annotation.id)"
56
+ :key="'ann' + annotation.id + '-' + index"
57
+ :config="annotationRect(bbox, annotation.id)"
58
+ @click="handleClickedAnnotation(annotation)"
59
+ @mouseenter="onElementEnter"
60
+ @mouseleave="onElementLeave"
61
+ />
62
+ </template>
63
+ </template>
64
+ </template>
65
+ </v-layer>
66
+ <v-layer v-if="showFocusedAnnotation && !isSelecting">
67
+ <v-label
68
+ :key="`label${documentAnnotationSelected.id}`"
69
+ :config="{
70
+ listening: false,
71
+ ...annotationLabelRect(documentAnnotationSelected.span),
72
+ }"
73
+ >
74
+ <v-tag
75
+ :config="{
76
+ fill: '#2B3545',
77
+ lineJoin: 'round',
78
+ hitStrokeWidth: 0,
79
+ listening: false,
80
+ }"
81
+ />
82
+ <v-text
83
+ :config="{
84
+ padding: 4,
85
+ text: documentAnnotationSelected.labelName,
86
+ fill: 'white',
87
+ fontSize: 12,
88
+ listening: false,
89
+ }"
90
+ />
91
+ </v-label>
92
+ </v-layer>
93
+ <v-layer v-if="selection && !isSelecting && isElementSelected">
94
+ <box-selection :page="page" />
95
+ </v-layer>
96
+ <v-layer v-else-if="selection && isSelectionValid">
97
+ <multi-ann-selection
98
+ :page="page"
99
+ @buttonEnter="onElementEnter"
100
+ @buttonLeave="onElementLeave"
101
+ @finished="handleMultiAnnSelectionFinished"
102
+ />
103
+ </v-layer>
104
+ </v-stage>
105
+ <b-skeleton
106
+ v-else
107
+ position="is-centered"
108
+ :width="scaledViewport.width"
109
+ :height="scaledViewport.height"
110
+ />
111
+ </div>
112
+ </template>
113
+ <script>
114
+ import { mapState, mapGetters, mapActions } from "vuex";
115
+ import { PIXEL_RATIO } from "../../constants";
116
+ import api from "../../api";
117
+ import BoxSelection from "./BoxSelection";
118
+ import MultiAnnSelection from "./MultiAnnSelection";
119
+ import NewAnnotation from "./NewAnnotation";
120
+ import MultiAnnotationTablePopup from "./MultiAnnotationTablePopup";
121
+
122
+ export default {
123
+ name: "DocumentPage",
124
+ components: {
125
+ BoxSelection,
126
+ MultiAnnSelection,
127
+ NewAnnotation,
128
+ MultiAnnotationTablePopup,
129
+ },
130
+
131
+ props: {
132
+ page: {
133
+ type: Object,
134
+ required: true,
135
+ },
136
+ },
137
+
138
+ data() {
139
+ return {
140
+ image: null,
141
+ newAnnotation: [],
142
+ newMultiAnnotationSetTable: null,
143
+ };
144
+ },
145
+
146
+ computed: {
147
+ showFocusedAnnotation() {
148
+ return (
149
+ this.documentAnnotationSelected &&
150
+ this.documentAnnotationSelected.page === this.pageNumber &&
151
+ this.visiblePageRange.includes(this.documentAnnotationSelected.page) &&
152
+ !this.isAnnotationInEditMode(this.documentAnnotationSelected.id)
153
+ );
154
+ },
155
+ actualSizeViewport() {
156
+ return {
157
+ width: this.page.size[0] * this.scale,
158
+ height: this.page.size[1] * this.scale,
159
+ };
160
+ },
161
+
162
+ scaledViewport() {
163
+ const { width: actualSizeWidth, height: actualSizeHeight } =
164
+ this.actualSizeViewport;
165
+ const [pixelWidth, pixelHeight] = [actualSizeWidth, actualSizeHeight].map(
166
+ (dim) => dim / PIXEL_RATIO
167
+ );
168
+ return { width: pixelWidth, height: pixelHeight };
169
+ },
170
+
171
+ canvasStyle() {
172
+ const { width, height } = this.scaledViewport;
173
+ return `width: ${width}px; height: ${height}px;`;
174
+ },
175
+
176
+ pageInVisibleRange() {
177
+ return this.visiblePageRange.includes(this.pageNumber);
178
+ },
179
+
180
+ /**
181
+ * We take the entities from the backend and resize them according
182
+ * to the `scale` (zoom), the `imageScale` (proportion between the original
183
+ * document and the served image) and `PIXEL_RATIO` (in case of retina displays).
184
+ * We also change the original bbox format to something that can be used with CSS.
185
+ * The original is stored inside the `original` property, since it can be reused
186
+ * when we're sending the entity to the backend for selection or saving.
187
+ */
188
+ scaledEntities() {
189
+ // entities are either not loaded yet or empty
190
+ if (!this.page.hasOwnProperty("entities") || !this.page.entities) {
191
+ return [];
192
+ }
193
+
194
+ return this.page.entities.map((entity) => {
195
+ const box = this.bboxToRect(this.page, entity);
196
+ return {
197
+ original: entity,
198
+ scaled: {
199
+ ...box,
200
+ },
201
+ clickSelected: false,
202
+ };
203
+ });
204
+ },
205
+
206
+ /**
207
+ * A filtered version of `annotations` for the chosen page.
208
+ * Include annotations that have *at least* one bbox in the page.
209
+ * If the annotation's bboxes span multiple pages, each DocumentPage receives
210
+ * it and only shows the ones that match the pageNumber.
211
+ */
212
+ pageAnnotations() {
213
+ const annotations = [];
214
+ if (this.annotations) {
215
+ this.annotations.map((annotation) => {
216
+ if (
217
+ annotation.span.find(
218
+ (span) => span.page_index + 1 === this.pageNumber
219
+ )
220
+ ) {
221
+ annotations.push(annotation);
222
+ }
223
+ });
224
+ }
225
+ return annotations;
226
+ },
227
+
228
+ pageNumber() {
229
+ if (this.editMode) {
230
+ return this.page.page_number;
231
+ }
232
+ return this.page.number;
233
+ },
234
+
235
+ selection() {
236
+ return this.$store.getters["selection/getSelectionForPage"](
237
+ this.pageNumber
238
+ );
239
+ },
240
+
241
+ ...mapState("selection", [
242
+ "isSelecting",
243
+ "selectionFromBbox",
244
+ "spanSelection",
245
+ ]),
246
+ ...mapState("display", ["scale", "optimalScale"]),
247
+ ...mapState("document", [
248
+ "documentAnnotationSelected",
249
+ "recalculatingAnnotations",
250
+ "annotations",
251
+ "editAnnotation",
252
+ "selectedDocument",
253
+ "publicView",
254
+ "selectedEntities",
255
+ ]),
256
+ ...mapState("edit", ["editMode"]),
257
+ ...mapGetters("display", ["visiblePageRange", "bboxToRect"]),
258
+ ...mapGetters("selection", ["isSelectionValid", "isElementSelected"]),
259
+ ...mapGetters("document", [
260
+ "isAnnotationInEditMode",
261
+ "isDocumentReadyToBeReviewed",
262
+ "entitiesOnSelection",
263
+ ]),
264
+ },
265
+ watch: {
266
+ recalculatingAnnotations(newState) {
267
+ if (!newState) {
268
+ this.drawPage(true);
269
+ }
270
+ },
271
+ scale() {
272
+ this.closePopups(true);
273
+ },
274
+ selectedEntities(newValue) {
275
+ if (!newValue) {
276
+ this.closePopups(true);
277
+ }
278
+ },
279
+ },
280
+ mounted() {
281
+ if (
282
+ this.selectedDocument &&
283
+ this.selectedDocument.labeling_available === 1
284
+ ) {
285
+ this.drawPage();
286
+ }
287
+ },
288
+ methods: {
289
+ ...mapActions("selection", [
290
+ "startSelection",
291
+ "endSelection",
292
+ "moveSelection",
293
+ ]),
294
+ isAnnotationFocused(annotationId) {
295
+ return (
296
+ this.documentAnnotationSelected &&
297
+ !this.isElementSelected &&
298
+ annotationId === this.documentAnnotationSelected.id
299
+ );
300
+ },
301
+
302
+ onMouseDown(event) {
303
+ this.closePopups();
304
+
305
+ // check if element and delegate to it
306
+ if (
307
+ event.target.name() === "entity" ||
308
+ event.target.name() === "annotation" ||
309
+ event.target.name() === "multiAnnBoxSelection" ||
310
+ event.target.name() === "multiAnnBoxTransformer" ||
311
+ event.target.name() === "multiAnnButton" ||
312
+ event.target.name() === "boxSelection" ||
313
+ event.target.name() === "boxTransformer" ||
314
+ (event.target.getParent() &&
315
+ event.target.getParent().className === "Transformer")
316
+ ) {
317
+ return;
318
+ }
319
+ if (this.publicView) {
320
+ return;
321
+ }
322
+
323
+ // anything else, we start selecting
324
+
325
+ const position = this.$refs.stage.getStage().getPointerPosition();
326
+ this.startSelection({
327
+ pageNumber: this.pageNumber,
328
+ start: {
329
+ x: position.x,
330
+ y: position.y,
331
+ },
332
+ });
333
+ },
334
+ onMouseMove() {
335
+ // if we are not editing, do nothing
336
+ if (!this.isSelecting) {
337
+ return;
338
+ }
339
+
340
+ const position = this.$refs.stage.getStage().getPointerPosition();
341
+ this.moveSelection({
342
+ end: {
343
+ x: position.x,
344
+ y: position.y,
345
+ },
346
+ });
347
+ },
348
+
349
+ onMouseUp() {
350
+ // if we are not editing, do nothing
351
+ if (!this.isSelecting) {
352
+ return;
353
+ }
354
+
355
+ const position = this.$refs.stage.getStage().getPointerPosition();
356
+ this.endSelection({
357
+ x: position.x,
358
+ y: position.y,
359
+ });
360
+ },
361
+
362
+ handleClickedAnnotation(annotation) {
363
+ this.closePopups(true);
364
+ this.$store.dispatch("document/resetEditAnnotation");
365
+ this.$store.dispatch("document/setSidebarAnnotationSelected", annotation);
366
+ },
367
+
368
+ handleClickedEntity(entity) {
369
+ if (!entity) return;
370
+
371
+ // Check if we are creating a new Annotation
372
+ // or if we are editing an existing or empty one
373
+ const entityToAdd = {
374
+ entity,
375
+ content: entity.original.offset_string,
376
+ };
377
+
378
+ const found = this.newAnnotation.find(
379
+ (ann) =>
380
+ ann.entity.scaled.width === entityToAdd.entity.scaled.width &&
381
+ ann.content === entityToAdd.content
382
+ );
383
+
384
+ if (found) {
385
+ this.newAnnotation = [
386
+ ...this.newAnnotation.filter(
387
+ (ann) =>
388
+ ann.entity.scaled.width !== entityToAdd.entity.scaled.width &&
389
+ ann.content !== entityToAdd.content
390
+ ),
391
+ ];
392
+ } else {
393
+ this.newAnnotation.push(entityToAdd);
394
+ }
395
+
396
+ if (this.newAnnotation.length > 0) {
397
+ this.$store.dispatch(
398
+ "document/setSelectedEntities",
399
+ this.newAnnotation
400
+ );
401
+ } else {
402
+ this.$store.dispatch("document/setSelectedEntities", null);
403
+ }
404
+ },
405
+
406
+ onElementEnter() {
407
+ this.$refs.stage.$el.style.cursor = "pointer";
408
+ },
409
+
410
+ onElementLeave() {
411
+ this.$refs.stage.$el.style.cursor = "inherit";
412
+ },
413
+
414
+ handleMultiAnnSelectionFinished(newMultiAnnotationSetTable) {
415
+ this.newMultiAnnotationSetTable = newMultiAnnotationSetTable;
416
+ },
417
+
418
+ /**
419
+ * Konva draws pages like this.
420
+ */
421
+ drawPage(force = false) {
422
+ if (this.image && !force) {
423
+ return;
424
+ }
425
+ const image = new Image();
426
+ api.IMG_REQUEST.get(
427
+ `${this.page.image_url}?${this.selectedDocument.downloaded_at}`
428
+ )
429
+ .then((response) => {
430
+ return response.data;
431
+ })
432
+ .then((myBlob) => {
433
+ image.src = URL.createObjectURL(myBlob);
434
+ image.onload = () => {
435
+ // set image only when it is loaded
436
+ this.image = image;
437
+ };
438
+ });
439
+ },
440
+
441
+ /**
442
+ * Builds the konva config object for the entity.
443
+ */
444
+ entityRect(entity) {
445
+ let entityIsSelected = false;
446
+ if (this.newAnnotation) {
447
+ entityIsSelected = this.newAnnotation.find((selectedEntity) => {
448
+ return (
449
+ selectedEntity.entity.original.offset_string ===
450
+ entity.original.offset_string &&
451
+ selectedEntity.entity.original.x0 === entity.original.x0 &&
452
+ selectedEntity.entity.original.y0 === entity.original.y0
453
+ );
454
+ });
455
+ }
456
+
457
+ return {
458
+ stroke: "#ccc",
459
+ strokeWidth: 1,
460
+ dash: [5, 2],
461
+ fill: entityIsSelected ? "#67E9B7" : "transparent",
462
+ globalCompositeOperation: "multiply",
463
+ transformsEnabled: "position",
464
+ hitStrokeWidth: 0,
465
+ shadowForStrokeEnabled: false,
466
+ perfectDrawEnabled: false,
467
+ name: "entity",
468
+ ...entity.scaled,
469
+ };
470
+ },
471
+
472
+ /**
473
+ * Builds the konva config object for the annotation.
474
+ */
475
+ annotationRect(bbox, annotationId, draggable) {
476
+ const focused = this.isAnnotationFocused(annotationId);
477
+ let fillColor = "yellow";
478
+ let strokeWidth = 0;
479
+ let strokeColor = "";
480
+
481
+ // if hovered
482
+ if (focused) {
483
+ fillColor = "#67E9B7";
484
+ strokeWidth = 1;
485
+ strokeColor = "black";
486
+ }
487
+ return {
488
+ fill: fillColor,
489
+ globalCompositeOperation: "multiply",
490
+ strokeWidth: strokeWidth,
491
+ stroke: strokeColor,
492
+ name: "annotation",
493
+ draggable,
494
+ ...this.bboxToRect(this.page, bbox),
495
+ };
496
+ },
497
+ /**
498
+ * Builds the konva config object for the annotation label.
499
+ */
500
+ annotationLabelRect(bbox) {
501
+ const rect = this.bboxToRect(this.page, bbox, true);
502
+ return {
503
+ x: rect.x,
504
+ y: rect.y,
505
+ };
506
+ },
507
+ closePopups(closeNewAnnotaton) {
508
+ this.newMultiAnnotationSetTable = null;
509
+ if (closeNewAnnotaton) {
510
+ this.newAnnotation = [];
511
+ }
512
+ },
513
+ },
514
+ };
515
+ </script>
516
+
517
+ <style scoped lang="scss" src="../../assets/scss/document_page.scss"></style>
@@ -0,0 +1,145 @@
1
+ <template>
2
+ <div class="toolbar-container">
3
+ <div :class="['toolbar', recalculatingAnnotations && 'hidden']">
4
+ <b-tooltip
5
+ :label="tooltipInfo"
6
+ multilined
7
+ type="is-dark"
8
+ :active="editModeDisabled"
9
+ size="is-large"
10
+ >
11
+ <div
12
+ v-if="!editMode"
13
+ :class="[
14
+ 'icons icons-left',
15
+ editModeDisabled && 'edit-mode-disabled'
16
+ ]"
17
+ @click="handleEdit"
18
+ >
19
+ <div class="edit-icon icon">
20
+ <EditDocIcon />
21
+ </div>
22
+ <span class="edit-text">{{ $t("edit") }}</span>
23
+ </div>
24
+ </b-tooltip>
25
+ <div
26
+ v-if="!editMode"
27
+ class="toolbar-divider"
28
+ />
29
+ <div class="icons icons-right">
30
+ <div
31
+ class="fit-zoom icon"
32
+ @click.prevent.stop="fitAuto"
33
+ >
34
+ <FitZoomIcon />
35
+ </div>
36
+ <div
37
+ class="zoom-in icon"
38
+ @click.prevent.stop="zoomIn"
39
+ >
40
+ <PlusIcon />
41
+ </div>
42
+ <div
43
+ class="zoom-out icon"
44
+ @click.prevent.stop="zoomOut"
45
+ >
46
+ <MinusIcon />
47
+ </div>
48
+ <div class="percentage">
49
+ {{ `${currentPercentage}%` }}
50
+ </div>
51
+ </div>
52
+ </div>
53
+ </div>
54
+ </template>
55
+
56
+ <script>
57
+ import { mapState, mapGetters } from "vuex";
58
+ import FitZoomIcon from "../../assets/images/FitZoomIcon";
59
+ import PlusIcon from "../../assets/images/PlusIcon";
60
+ import MinusIcon from "../../assets/images/MinusIcon";
61
+ import EditDocIcon from "../../assets/images/EditDocIcon";
62
+
63
+ export default {
64
+ name: "DocumentToolbar",
65
+ components: {
66
+ FitZoomIcon,
67
+ PlusIcon,
68
+ MinusIcon,
69
+ EditDocIcon
70
+ },
71
+ data() {
72
+ return {
73
+ defaultScale: null,
74
+ currentPercentage: 100,
75
+ defaultPercentage: 0.25,
76
+ fitPercentage: 0.5,
77
+ toolbarModalOpen: true,
78
+ editModeDisabled: false,
79
+ tooltipInfo: null
80
+ };
81
+ },
82
+ computed: {
83
+ ...mapState("display", ["scale"]),
84
+ ...mapState("edit", ["editMode"]),
85
+ ...mapState("document", ["selectedDocument", "recalculatingAnnotations"]),
86
+ ...mapGetters("document", ["documentCannotBeEdited"])
87
+ },
88
+ watch: {
89
+ selectedDocument(newValue) {
90
+ if (this.documentCannotBeEdited(newValue)) {
91
+ this.editModeDisabled = true;
92
+ }
93
+ }
94
+ },
95
+ mounted() {
96
+ this.defaultScale = this.scale;
97
+
98
+ if (this.selectedDocument) {
99
+ if (this.documentCannotBeEdited(this.selectedDocument)) {
100
+ this.editModeDisabled = true;
101
+ }
102
+ }
103
+ },
104
+ updated() {
105
+ if (this.selectedDocument.is_reviewed) {
106
+ this.tooltipInfo = this.$t("document_reviewed");
107
+ } else {
108
+ this.tooltipInfo = this.$t("edit_not_available");
109
+ }
110
+ },
111
+ methods: {
112
+ handleEdit() {
113
+ if (this.editModeDisabled) return;
114
+ this.$store.dispatch("edit/enableEditMode");
115
+ },
116
+ zoomIn() {
117
+ this.currentPercentage += this.defaultPercentage * 100;
118
+ this.updateScale((this.defaultScale * this.currentPercentage) / 100);
119
+ },
120
+ zoomOut() {
121
+ if (this.currentPercentage === 25) {
122
+ return;
123
+ }
124
+
125
+ this.currentPercentage -= this.defaultPercentage * 100;
126
+ this.updateScale((this.defaultScale * this.currentPercentage) / 100);
127
+ },
128
+ fitAuto() {
129
+ if (this.currentPercentage === 50 || !this.defaultScale) return;
130
+
131
+ // Always set to 50%
132
+ this.updateScale(this.defaultScale * this.fitPercentage);
133
+
134
+ this.currentPercentage = this.fitPercentage * 100;
135
+ },
136
+ updateScale(scale) {
137
+ this.$store.dispatch("display/updateFit", "custom").then(() => {
138
+ this.$store.dispatch("display/updateScale", { scale });
139
+ });
140
+ }
141
+ }
142
+ };
143
+ </script>
144
+
145
+ <style scoped lang="scss" src="../../assets/scss/document_toolbar.scss"></style>
@@ -0,0 +1,53 @@
1
+ <template>
2
+ <div :style="canvasStyle">
3
+ <b-skeleton
4
+ :width="`${scaledViewport.width}px`"
5
+ :height="`${scaledViewport.height}px`"
6
+ />
7
+ </div>
8
+ </template>
9
+
10
+ <script>
11
+ /**
12
+ * This component is used to mimick an actual page's height/width to
13
+ * act as a placeholder (mainly for scrolling) instead of rendering the
14
+ * full page canvas when it's not needed (unfocused pages).
15
+ */
16
+
17
+ import { mapState } from "vuex";
18
+ import { PIXEL_RATIO } from "../../constants";
19
+
20
+ export default {
21
+ props: {
22
+ width: {
23
+ default: 0
24
+ },
25
+ height: {
26
+ default: 0
27
+ }
28
+ },
29
+ computed: {
30
+ ...mapState("display", ["scale"]),
31
+ actualSizeViewport() {
32
+ return {
33
+ width: this.width * this.scale,
34
+ height: this.height * this.scale
35
+ };
36
+ },
37
+
38
+ scaledViewport() {
39
+ const { width: actualSizeWidth, height: actualSizeHeight } =
40
+ this.actualSizeViewport;
41
+ const [pixelWidth, pixelHeight] = [actualSizeWidth, actualSizeHeight].map(
42
+ dim => Math.ceil(dim / PIXEL_RATIO)
43
+ );
44
+ return { width: pixelWidth, height: pixelHeight };
45
+ },
46
+
47
+ canvasStyle() {
48
+ const { width, height } = this.scaledViewport;
49
+ return `width: ${width}px; height: ${height}px; margin: 0 auto`;
50
+ }
51
+ }
52
+ };
53
+ </script>