You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

168 lines
4.7 KiB

6 years ago
  1. import Vue from 'vue';
  2. import annotationMixin from './mixin';
  3. import HTTP from './http';
  4. Vue.use(require('vue-shortkey'), {
  5. prevent: ['input', 'textarea'],
  6. });
  7. Vue.component('annotator', {
  8. template: '<div @click="setSelectedRange">\
  9. <span v-for="r in chunks"\
  10. v-if="id2label[r.label]"\
  11. v-bind:class="{tag: id2label[r.label].text_color}"\
  12. v-bind:style="{ color: id2label[r.label].text_color, backgroundColor: id2label[r.label].background_color }"\
  13. >{{ text.slice(r.start_offset, r.end_offset) }}<button class="delete is-small"\
  14. v-if="id2label[r.label].text_color"\
  15. @click="removeLabel(r)"></button></span>\
  16. </div>',
  17. props: {
  18. labels: Array, // [{id: Integer, color: String, text: String}]
  19. text: String,
  20. entityPositions: Array, // [{'startOffset': 10, 'endOffset': 15, 'label_id': 1}]
  21. },
  22. data() {
  23. return {
  24. startOffset: 0,
  25. endOffset: 0,
  26. };
  27. },
  28. methods: {
  29. setSelectedRange(e) {
  30. let start;
  31. let end;
  32. if (window.getSelection) {
  33. const range = window.getSelection().getRangeAt(0);
  34. const preSelectionRange = range.cloneRange();
  35. preSelectionRange.selectNodeContents(this.$el);
  36. preSelectionRange.setEnd(range.startContainer, range.startOffset);
  37. start = preSelectionRange.toString().length;
  38. end = start + range.toString().length;
  39. } else if (document.selection && document.selection.type !== 'Control') {
  40. const selectedTextRange = document.selection.createRange();
  41. const preSelectionTextRange = document.body.createTextRange();
  42. preSelectionTextRange.moveToElementText(this.$el);
  43. preSelectionTextRange.setEndPoint('EndToStart', selectedTextRange);
  44. start = preSelectionTextRange.text.length;
  45. end = start + selectedTextRange.text.length;
  46. }
  47. this.startOffset = start;
  48. this.endOffset = end;
  49. console.log(start, end);
  50. },
  51. validRange() {
  52. if (this.startOffset === this.endOffset) {
  53. return false;
  54. }
  55. if (this.startOffset > this.text.length || this.endOffset > this.text.length) {
  56. return false;
  57. }
  58. if (this.startOffset < 0 || this.endOffset < 0) {
  59. return false;
  60. }
  61. for (let i = 0; i < this.entityPositions.length; i++) {
  62. const e = this.entityPositions[i];
  63. if ((e.start_offset <= this.startOffset) && (this.startOffset <= e.end_offset)) {
  64. return false;
  65. }
  66. if ((e.start_offset <= this.endOffset) && (this.endOffset <= e.end_offset)) {
  67. return false;
  68. }
  69. }
  70. return true;
  71. },
  72. resetRange() {
  73. this.startOffset = 0;
  74. this.endOffset = 0;
  75. },
  76. addLabel(labelId) {
  77. if (this.validRange()) {
  78. const label = {
  79. start_offset: this.startOffset,
  80. end_offset: this.endOffset,
  81. label: labelId,
  82. };
  83. this.$emit('add-label', label);
  84. }
  85. },
  86. removeLabel(index) {
  87. this.$emit('remove-label', index);
  88. },
  89. makeLabel(startOffset, endOffset) {
  90. const label = {
  91. id: 0,
  92. label: -1,
  93. start_offset: startOffset,
  94. end_offset: endOffset,
  95. };
  96. return label;
  97. },
  98. },
  99. watch: {
  100. entityPositions() {
  101. this.resetRange();
  102. },
  103. },
  104. computed: {
  105. sortedEntityPositions() {
  106. this.entityPositions = this.entityPositions.sort((a, b) => a.start_offset - b.start_offset);
  107. return this.entityPositions;
  108. },
  109. chunks() {
  110. const res = [];
  111. let left = 0;
  112. for (let i = 0; i < this.sortedEntityPositions.length; i++) {
  113. const e = this.sortedEntityPositions[i];
  114. const l = this.makeLabel(left, e.start_offset);
  115. res.push(l);
  116. res.push(e);
  117. left = e.end_offset;
  118. }
  119. const l = this.makeLabel(left, this.text.length);
  120. res.push(l);
  121. return res;
  122. },
  123. id2label() {
  124. let id2label = {};
  125. // default value;
  126. id2label[-1] = {
  127. text_color: '',
  128. background_color: '',
  129. };
  130. for (let i = 0; i < this.labels.length; i++) {
  131. const label = this.labels[i];
  132. id2label[label.id] = label;
  133. }
  134. return id2label;
  135. },
  136. },
  137. });
  138. const vm = new Vue({
  139. el: '#mail-app',
  140. delimiters: ['[[', ']]'],
  141. mixins: [annotationMixin],
  142. methods: {
  143. annotate(labelId) {
  144. this.$refs.annotator.addLabel(labelId);
  145. },
  146. addLabel(annotation) {
  147. const docId = this.docs[this.pageNumber].id;
  148. HTTP.post(`docs/${docId}/annotations/`, annotation).then((response) => {
  149. this.annotations[this.pageNumber].push(response.data);
  150. });
  151. },
  152. },
  153. });