@konfuzio/document-validation-ui 0.1.39-dev.0 → 0.1.39

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.39-dev.0",
3
+ "version": "0.1.39",
4
4
  "repository": "git://github.com:konfuzio-ai/document-validation-ui.git",
5
5
  "main": "dist/app.js",
6
6
  "scripts": {
@@ -49,14 +49,22 @@
49
49
  overflow: auto;
50
50
  height: 100vh;
51
51
 
52
- #annotation-filters {
53
- padding: 16px 16px 0px 16px;
52
+ .annotation-options {
54
53
  display: flex;
55
- flex-direction: row;
56
- justify-content: space-around;
57
- gap: 12px;
58
- span {
59
- font-size: 14px;
54
+ justify-content: space-between;
55
+ flex-direction: column;
56
+ padding: 16px 16px 0px 16px;
57
+ gap: 16px;
58
+
59
+ #annotation-filters {
60
+ display: flex;
61
+ flex-direction: row;
62
+ width: 100%;
63
+ justify-content: space-around;
64
+ gap: 12px;
65
+ span {
66
+ font-size: 14px;
67
+ }
60
68
  }
61
69
  }
62
70
 
@@ -309,6 +309,28 @@
309
309
  }
310
310
  }
311
311
  }
312
+ .taginput-container {
313
+ &.is-focused {
314
+ box-shadow: none !important;
315
+ border-color: $primary !important;
316
+ }
317
+ }
318
+ .field {
319
+ input {
320
+ box-shadow: none !important;
321
+ &:focus {
322
+ border-color: $primary;
323
+ }
324
+ }
325
+ .control.has-icons-left {
326
+ .icon {
327
+ svg {
328
+ height: 20px;
329
+ width: 20px;
330
+ }
331
+ }
332
+ }
333
+ }
312
334
 
313
335
  .b-checkbox.checkbox {
314
336
  input[type="checkbox"] + .check {
@@ -1,22 +1,76 @@
1
1
  <template>
2
- <div id="annotation-filters">
3
- <b-switch v-model="annotationFilters.showFeedbackNeeded" class="is-small">{{
4
- $t("human_feedback_needed")
5
- }}</b-switch>
6
- <b-switch v-model="annotationFilters.showEmpty" class="is-small">{{
7
- $t("label_missing_annotations")
8
- }}</b-switch>
9
- <b-switch v-model="annotationFilters.showAccepted" class="is-small">{{
10
- $t("accepted_annotations")
11
- }}</b-switch>
2
+ <div class="annotation-options">
3
+ <div id="annotation-search">
4
+ <b-field>
5
+ <b-taginput
6
+ v-model="search"
7
+ ellipsis
8
+ icon="search"
9
+ :placeholder="$t('search')"
10
+ >
11
+ <template #tag="props">
12
+ <span>{{ labelNameForAnnotationId(props.tag) || props.tag }}</span>
13
+ </template>
14
+ </b-taginput>
15
+ </b-field>
16
+ </div>
17
+ <div id="annotation-filters">
18
+ <b-switch
19
+ v-model="annotationFilters.showFeedbackNeeded"
20
+ class="is-small"
21
+ >{{ $t("human_feedback_needed") }}</b-switch
22
+ >
23
+ <b-switch v-model="annotationFilters.showEmpty" class="is-small">{{
24
+ $t("label_missing_annotations")
25
+ }}</b-switch>
26
+ <b-switch v-model="annotationFilters.showAccepted" class="is-small">{{
27
+ $t("accepted_annotations")
28
+ }}</b-switch>
29
+ </div>
12
30
  </div>
13
31
  </template>
14
32
  <script>
15
- import { mapState } from "vuex";
33
+ import { mapGetters, mapState } from "vuex";
16
34
  export default {
17
35
  name: "AnnotationFilters",
36
+ data() {
37
+ return {
38
+ search: [],
39
+ };
40
+ },
18
41
  computed: {
19
- ...mapState("document", ["annotationSets", "annotationFilters"]),
42
+ ...mapState("document", [
43
+ "annotationSets",
44
+ "annotationFilters",
45
+ "annotationSearch",
46
+ ]),
47
+ ...mapGetters("document", ["annotationById", "labelOfAnnotation"]),
48
+ },
49
+
50
+ watch: {
51
+ search() {
52
+ if (this.search.length > 0) {
53
+ this.$emit("openAll");
54
+ }
55
+ if (this.search != this.annotationSearch) {
56
+ this.$store.dispatch("document/setAnnotationSearch", this.search);
57
+ }
58
+ },
59
+ },
60
+ mounted() {
61
+ this.search = this.annotationSearch;
62
+ },
63
+ methods: {
64
+ labelNameForAnnotationId(annotationId) {
65
+ const annotation = this.annotationById(Number(annotationId));
66
+ if (annotation) {
67
+ const label = this.labelOfAnnotation(annotation);
68
+ if (label) {
69
+ return label.name;
70
+ }
71
+ }
72
+ return false;
73
+ },
20
74
  },
21
75
  };
22
76
  </script>
@@ -21,14 +21,30 @@
21
21
 
22
22
  <!-- When there's no annotation sets -->
23
23
  <div
24
- v-else-if="getAnnotationsFiltered.annotationSets.length === 0"
24
+ v-else-if="
25
+ getAnnotationsFiltered.annotationSets.length === 0 &&
26
+ !isSearchingAnnotationList
27
+ "
25
28
  class="empty-annotation-sets"
26
29
  >
27
30
  <EmptyState />
28
31
  </div>
29
32
 
30
33
  <div v-else ref="annotationList" :class="['annotation-set-list']">
31
- <AnnotationFilters v-if="isDocumentEditable" />
34
+ <AnnotationFilters
35
+ v-if="isDocumentEditable"
36
+ @openAll="openAllAccordions"
37
+ />
38
+
39
+ <div
40
+ v-if="
41
+ getAnnotationsFiltered.annotationSets.length === 0 &&
42
+ isSearchingAnnotationList
43
+ "
44
+ class="empty-annotation-sets"
45
+ >
46
+ <EmptyState :is-search="true" />
47
+ </div>
32
48
 
33
49
  <div
34
50
  v-if="Object.entries(annotationSetsInTable()).length > 0"
@@ -131,10 +147,19 @@
131
147
  </div>
132
148
  </div>
133
149
 
134
- <div v-if="annotationSet.labels.length === 0" class="no-labels">
135
- <span> {{ $t("no_labels_in_set") }}</span>
136
- <!-- eslint-disable-next-line vue/no-v-html -->
137
- <span v-if="isDocumentEditable" v-html="$t('link_to_add_labels')" />
150
+ <div v-else-if="annotationSet.labels.length === 0" class="no-labels">
151
+ <span>
152
+ {{
153
+ isSearchingAnnotationList
154
+ ? $t("no_results")
155
+ : $t("no_labels_in_set")
156
+ }}</span
157
+ >
158
+ <!-- eslint-disable vue/no-v-html -->
159
+ <span
160
+ v-if="isDocumentEditable && !isSearchingAnnotationList"
161
+ v-html="$t('link_to_add_labels')"
162
+ />
138
163
  </div>
139
164
 
140
165
  <div
@@ -205,6 +230,7 @@ export default {
205
230
  "isDocumentReviewed",
206
231
  "annotationSetOfAnnotation",
207
232
  "isAnnotationInAnnotationSet",
233
+ "isSearchingAnnotationList",
208
234
  ]),
209
235
  isAnnotationBeingEdited() {
210
236
  return this.editAnnotation && this.editAnnotation.id;
@@ -231,6 +257,12 @@ export default {
231
257
  oldAnnotationSets
232
258
  );
233
259
  },
260
+ getAnnotationsFiltered(newFiltered, oldFiltered) {
261
+ this.loadAccordions(
262
+ newFiltered.annotationSets,
263
+ oldFiltered.annotationSets
264
+ );
265
+ },
234
266
  annotationId(newAnnotationId) {
235
267
  if (newAnnotationId) {
236
268
  const annotationSet = this.annotationSetOfAnnotation(newAnnotationId);
@@ -255,6 +287,11 @@ export default {
255
287
  window.removeEventListener("keydown", this.keyDownHandler);
256
288
  },
257
289
  methods: {
290
+ annotationSetShouldAppear(annotationSet) {
291
+ return !(
292
+ annotationSet.labels.length === 0 && this.isSearchingAnnotationList
293
+ );
294
+ },
258
295
  toggleAccordion(index) {
259
296
  const newAnnotationSetsAccordion = [...this.annotationSetsAccordion];
260
297
  newAnnotationSetsAccordion[index] = !newAnnotationSetsAccordion[index];
@@ -288,11 +325,13 @@ export default {
288
325
  newAnnotationSets.forEach((newAnnotationSet) => {
289
326
  const existed = oldAnnotationSets.find(
290
327
  (oldAnnotationSet) =>
328
+ oldAnnotationSet &&
329
+ newAnnotationSet &&
291
330
  oldAnnotationSet.id &&
292
331
  newAnnotationSet.id &&
293
332
  oldAnnotationSet.id === newAnnotationSet.id
294
333
  );
295
- if (!existed && newAnnotationSet.id !== null) {
334
+ if (!existed && newAnnotationSet && newAnnotationSet.id !== null) {
296
335
  annotationSetsCreated.push(newAnnotationSet);
297
336
  }
298
337
  });
@@ -301,6 +340,7 @@ export default {
301
340
  newAnnotationSets.forEach((newAnnotationSet, index) => {
302
341
  const wasOpen = annotationSetsOpened.find(
303
342
  (annotationSetOpened) =>
343
+ annotationSetOpened &&
304
344
  annotationSetOpened.id &&
305
345
  newAnnotationSet.id &&
306
346
  newAnnotationSet.id === annotationSetOpened.id
@@ -5,7 +5,7 @@
5
5
  <p class="title">
6
6
  {{ $t("no_label_sets_found") }}
7
7
  </p>
8
- <p class="description">
8
+ <p v-if="!isSearch" class="description">
9
9
  {{ $t("no_label_sets_found_description") }}
10
10
  </p>
11
11
  </div>
@@ -15,7 +15,14 @@
15
15
  import EmptyStateImg from "../../assets/images/EmptyStateImg";
16
16
  export default {
17
17
  name: "EmptyState",
18
- components: { EmptyStateImg }
18
+ components: { EmptyStateImg },
19
+ props: {
20
+ isSearch: {
21
+ type: Boolean,
22
+ required: false,
23
+ default: false,
24
+ },
25
+ },
19
26
  };
20
27
  </script>
21
28
  <style scoped lang="scss" src="../../assets/scss/empty_state.scss"></style>
@@ -7,17 +7,9 @@ import {
7
7
  MINIMUM_OPTIMIZED_APP_WIDTH,
8
8
  } from "../constants";
9
9
 
10
- const HTTP = myImports.HTTP;
10
+ import { debounce } from "../utils/utils";
11
11
 
12
- const debounce = (cb, duration) => {
13
- let timer;
14
- return (...args) => {
15
- clearTimeout(timer);
16
- timer = setTimeout(() => {
17
- cb(...args);
18
- }, duration);
19
- };
20
- };
12
+ const HTTP = myImports.HTTP;
21
13
 
22
14
  const floor = (value, precision) => {
23
15
  const multiplier = Math.pow(10, precision || 0);
@@ -6,6 +6,8 @@ import {
6
6
  navigateToNewDocumentURL,
7
7
  getURLPath,
8
8
  setURLAnnotationHash,
9
+ setURLQueryParam,
10
+ debounce,
9
11
  } from "../utils/utils";
10
12
 
11
13
  const HTTP = myImports.HTTP;
@@ -53,6 +55,8 @@ const state = {
53
55
  ? false
54
56
  : true,
55
57
  },
58
+ annotationSearch:
59
+ (getURLQueryParam("search") && getURLQueryParam("search").split(",")) || [],
56
60
  };
57
61
  const getters = {
58
62
  /**
@@ -302,12 +306,60 @@ const getters = {
302
306
  let processedAnnotationSets = [];
303
307
  let processedLabels = [];
304
308
 
309
+ // search feature
310
+ const addAnnotation = (listToAdd, annotation, force) => {
311
+ if (force) {
312
+ listToAdd.push(annotation);
313
+ return true;
314
+ }
315
+ if (state.annotationSearch.length > 0) {
316
+ if (
317
+ annotation.offset_string &&
318
+ state.annotationSearch.find((search) =>
319
+ annotation.offset_string
320
+ .toLowerCase()
321
+ .includes(search.toLowerCase())
322
+ )
323
+ ) {
324
+ listToAdd.push(annotation);
325
+ return true;
326
+ } else if (
327
+ annotation.id &&
328
+ state.annotationSearch.find((search) => `${annotation.id}` === search)
329
+ ) {
330
+ listToAdd.push(annotation);
331
+ return true;
332
+ }
333
+ } else {
334
+ listToAdd.push(annotation);
335
+ return true;
336
+ }
337
+ return false;
338
+ };
339
+
340
+ const labelHasSearchText = (label) => {
341
+ if (state.annotationSearch.length > 0) {
342
+ if (
343
+ label.name &&
344
+ state.annotationSearch.find((search) =>
345
+ label.name.toLowerCase().includes(search.toLowerCase())
346
+ )
347
+ ) {
348
+ return true;
349
+ }
350
+ } else {
351
+ return false;
352
+ }
353
+ return false;
354
+ };
355
+
305
356
  if (state.annotationSets) {
306
357
  state.annotationSets.forEach((annotationSet) => {
307
358
  labels = [];
308
359
  annotationSet.labels.forEach((label) => {
309
360
  const labelAnnotations = [];
310
361
  let addLabel = false;
362
+ const labelHasSearch = labelHasSearchText(label);
311
363
  if (
312
364
  !state.annotationFilters.showEmpty ||
313
365
  !state.annotationFilters.showFeedbackNeeded ||
@@ -323,22 +375,42 @@ const getters = {
323
375
  state.annotationFilters.showFeedbackNeeded &&
324
376
  annotation.revised === false
325
377
  ) {
326
- labelAnnotations.push(annotation);
327
- addLabel = true;
378
+ const added = addAnnotation(
379
+ labelAnnotations,
380
+ annotation,
381
+ labelHasSearch
382
+ );
383
+ if (added) {
384
+ addLabel = true;
385
+ }
328
386
  }
329
387
  if (
330
388
  state.annotationFilters.showAccepted &&
331
389
  annotation.revised === true
332
390
  ) {
333
- labelAnnotations.push(annotation);
334
- addLabel = true;
391
+ const added = addAnnotation(
392
+ labelAnnotations,
393
+ annotation,
394
+ labelHasSearch
395
+ );
396
+ if (added) {
397
+ addLabel = true;
398
+ }
335
399
  }
336
400
  });
337
401
  }
338
402
  } else {
339
403
  // add annotations to the document array
340
- labelAnnotations.push(...label.annotations);
341
- addLabel = true;
404
+ label.annotations.forEach((annotation) => {
405
+ const added = addAnnotation(
406
+ labelAnnotations,
407
+ annotation,
408
+ labelHasSearch
409
+ );
410
+ if (added) {
411
+ addLabel = true;
412
+ }
413
+ });
342
414
  }
343
415
  if (addLabel) {
344
416
  labels.push({ ...label, annotations: labelAnnotations });
@@ -346,7 +418,11 @@ const getters = {
346
418
  }
347
419
  annotations.push(...labelAnnotations);
348
420
  });
349
- processedAnnotationSets.push({ ...annotationSet, labels });
421
+
422
+ // if in search do not add the annotation set
423
+ if (!(state.annotationSearch.length > 0 && labels.length === 0)) {
424
+ processedAnnotationSets.push({ ...annotationSet, labels });
425
+ }
350
426
  });
351
427
  }
352
428
 
@@ -484,6 +560,13 @@ const getters = {
484
560
  }
485
561
  },
486
562
 
563
+ /**
564
+ * Checks if it's currently searching for annotations
565
+ */
566
+ isSearchingAnnotationList: (state) => {
567
+ return state.annotationSearch && state.annotationSearch.length > 0;
568
+ },
569
+
487
570
  /**
488
571
  * Get number of empty labels per annotation set
489
572
  */
@@ -873,6 +956,9 @@ const actions = {
873
956
  setSplittingSuggestions: ({ commit }, value) => {
874
957
  commit("SET_SPLITTING_SUGGESTIONS", value);
875
958
  },
959
+ setAnnotationSearch: ({ commit }, value) => {
960
+ commit("SET_ANNOTATION_SEARCH", value);
961
+ },
876
962
 
877
963
  /**
878
964
  * Actions that use HTTP requests always return the axios promise,
@@ -1587,6 +1673,10 @@ const mutations = {
1587
1673
  SET_SPLITTING_SUGGESTIONS: (state, array) => {
1588
1674
  state.splittingSuggestions = array;
1589
1675
  },
1676
+ SET_ANNOTATION_SEARCH: (state, search) => {
1677
+ state.annotationSearch = search;
1678
+ setURLQueryParam("search", search);
1679
+ },
1590
1680
  };
1591
1681
 
1592
1682
  export default {
@@ -35,6 +35,19 @@ export function getURLValueFromHash(value) {
35
35
  return id;
36
36
  }
37
37
 
38
+ export function setURLQueryParam(query, value, deleteParam = "") {
39
+ const url = new URL(window.location.href);
40
+ if (value != "") {
41
+ if (deleteParam != "") {
42
+ url.searchParams.delete(deleteParam);
43
+ }
44
+ url.searchParams.set(query, value);
45
+ } else {
46
+ url.searchParams.delete(query);
47
+ }
48
+ window.history.pushState(null, "", url.toString());
49
+ }
50
+
38
51
  export function setURLAnnotationHash(annotationId) {
39
52
  if (annotationId) {
40
53
  window.location.hash = `ann${annotationId}`;
@@ -69,3 +82,13 @@ export function navigateToDocumentsList(path, projectId, userId) {
69
82
  export function isElementArray(element) {
70
83
  return Array.isArray(element);
71
84
  }
85
+
86
+ export function debounce(cb, duration) {
87
+ let timer;
88
+ return (...args) => {
89
+ clearTimeout(timer);
90
+ timer = setTimeout(() => {
91
+ cb(...args);
92
+ }, duration);
93
+ };
94
+ }