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.

185 lines
4.9 KiB

  1. <template lang="pug">
  2. div(@click="setSelectedRange")
  3. span.text-sequence(
  4. v-for="r in chunksWithLabel"
  5. v-bind:class="getChunkClass(r)"
  6. v-bind:data-tooltip="id2label[r.label].text"
  7. v-bind:style="{ \
  8. color: id2label[r.label].text_color, \
  9. backgroundColor: id2label[r.label].background_color \
  10. }"
  11. ) {{ textPart(r) }}
  12. button.delete.is-small(v-if="id2label[r.label].text_color", v-on:click="removeLabel(r)")
  13. </template>
  14. <script>
  15. export default {
  16. props: {
  17. labels: {
  18. type: Array, // [{id: Integer, color: String, text: String}]
  19. default: () => [],
  20. },
  21. text: {
  22. type: String,
  23. default: '',
  24. },
  25. entityPositions: {
  26. type: Array, // [{'startOffset': 10, 'endOffset': 15, 'label_id': 1}]
  27. default: () => [],
  28. },
  29. },
  30. data: () => ({
  31. startOffset: 0,
  32. endOffset: 0,
  33. }),
  34. computed: {
  35. sortedEntityPositions() {
  36. /* eslint-disable vue/no-side-effects-in-computed-properties */
  37. this.entityPositions = this.entityPositions.sort((a, b) => a.start_offset - b.start_offset);
  38. return this.entityPositions;
  39. /* eslint-enable vue/no-side-effects-in-computed-properties */
  40. },
  41. chunks() {
  42. const res = [];
  43. let left = 0;
  44. for (let i = 0; i < this.sortedEntityPositions.length; i++) {
  45. const e = this.sortedEntityPositions[i];
  46. const l = this.makeLabel(left, e.start_offset);
  47. res.push(l);
  48. res.push(e);
  49. left = e.end_offset;
  50. }
  51. const l = this.makeLabel(left, this.text.length);
  52. res.push(l);
  53. return res;
  54. },
  55. chunksWithLabel() {
  56. return this.chunks.filter(r => this.id2label[r.label]);
  57. },
  58. id2label() {
  59. const id2label = {};
  60. // default value;
  61. id2label[-1] = {
  62. text_color: '',
  63. background_color: '',
  64. };
  65. for (let i = 0; i < this.labels.length; i++) {
  66. const label = this.labels[i];
  67. id2label[label.id] = label;
  68. }
  69. return id2label;
  70. },
  71. },
  72. watch: {
  73. entityPositions() {
  74. this.resetRange();
  75. },
  76. },
  77. methods: {
  78. getChunkClass(chunk) {
  79. if (!chunk.id) {
  80. return {};
  81. }
  82. const label = this.id2label[chunk.label];
  83. return [
  84. 'tooltip is-tooltip-bottom',
  85. { tag: label.text_color },
  86. ];
  87. },
  88. setSelectedRange() {
  89. let start;
  90. let end;
  91. if (window.getSelection) {
  92. const range = window.getSelection().getRangeAt(0);
  93. const preSelectionRange = range.cloneRange();
  94. preSelectionRange.selectNodeContents(this.$el);
  95. preSelectionRange.setEnd(range.startContainer, range.startOffset);
  96. start = [...preSelectionRange.toString()].length;
  97. end = start + [...range.toString()].length;
  98. } else if (document.selection && document.selection.type !== 'Control') {
  99. const selectedTextRange = document.selection.createRange();
  100. const preSelectionTextRange = document.body.createTextRange();
  101. preSelectionTextRange.moveToElementText(this.$el);
  102. preSelectionTextRange.setEndPoint('EndToStart', selectedTextRange);
  103. start = [...preSelectionTextRange.text].length;
  104. end = start + [...selectedTextRange.text].length;
  105. }
  106. this.startOffset = start;
  107. this.endOffset = end;
  108. console.log(start, end); // eslint-disable-line no-console
  109. },
  110. validRange() {
  111. if (this.startOffset === this.endOffset) {
  112. return false;
  113. }
  114. if (this.startOffset > this.text.length || this.endOffset > this.text.length) {
  115. return false;
  116. }
  117. if (this.startOffset < 0 || this.endOffset < 0) {
  118. return false;
  119. }
  120. for (let i = 0; i < this.entityPositions.length; i++) {
  121. const e = this.entityPositions[i];
  122. if ((e.start_offset <= this.startOffset) && (this.startOffset < e.end_offset)) {
  123. return false;
  124. }
  125. if ((e.start_offset < this.endOffset) && (this.endOffset < e.end_offset)) {
  126. return false;
  127. }
  128. if ((this.startOffset < e.start_offset) && (e.start_offset < this.endOffset)) {
  129. return false;
  130. }
  131. if ((this.startOffset < e.end_offset) && (e.end_offset < this.endOffset)) {
  132. return false;
  133. }
  134. }
  135. return true;
  136. },
  137. resetRange() {
  138. this.startOffset = 0;
  139. this.endOffset = 0;
  140. },
  141. textPart(r) {
  142. return [...this.text].slice(r.start_offset, r.end_offset).join('');
  143. },
  144. addLabel(labelId) {
  145. if (this.validRange()) {
  146. const label = {
  147. start_offset: this.startOffset,
  148. end_offset: this.endOffset,
  149. label: labelId,
  150. };
  151. this.$emit('add-label', label);
  152. }
  153. },
  154. removeLabel(index) {
  155. this.$emit('remove-label', index);
  156. },
  157. makeLabel(startOffset, endOffset) {
  158. const label = {
  159. id: 0,
  160. label: -1,
  161. start_offset: startOffset,
  162. end_offset: endOffset,
  163. };
  164. return label;
  165. },
  166. },
  167. };
  168. </script>