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.

242 lines
8.2 KiB

  1. //import Vue from 'vue';
  2. //Vue.use(require('vue-shortkey'));
  3. axios.defaults.xsrfCookieName = 'csrftoken';
  4. axios.defaults.xsrfHeaderName = 'X-CSRFToken';
  5. var base_url = window.location.href.split('/').slice(3, 5).join('/');
  6. const HTTP = axios.create({
  7. baseURL: `/api/${base_url}/`
  8. })
  9. const Annotator = {
  10. template: '<div @click="setSelectedRange">\
  11. <span v-for="r in chunks" v-bind:class="{tag: r.color}" v-bind:style="{ color: r.color, backgroundColor: r.background }">{{ r.word }}<button v-if="r.color" class="delete is-small" @click="deleteLabel(r.index)"></button></span>\
  12. </div>',
  13. props: {
  14. 'labels': Array, // [{id: Integer, color: String, text: String}]
  15. 'text': String,
  16. 'entityPositions': Array, //[{'startOffset': 10, 'endOffset': 15, 'label_id': 1}]
  17. },
  18. data() {
  19. return {
  20. startOffset: 0,
  21. endOffset: 0,
  22. }
  23. },
  24. methods: {
  25. setSelectedRange: function (e) {
  26. if (window.getSelection) {
  27. var range = window.getSelection().getRangeAt(0);
  28. var preSelectionRange = range.cloneRange();
  29. preSelectionRange.selectNodeContents(this.$el);
  30. preSelectionRange.setEnd(range.startContainer, range.startOffset);
  31. var start = preSelectionRange.toString().length;
  32. var end = start + range.toString().length;
  33. } else if (document.selection && document.selection.type != 'Control') {
  34. var selectedTextRange = document.selection.createRange();
  35. var preSelectionTextRange = document.body.createTextRange();
  36. preSelectionTextRange.moveToElementText(this.$el);
  37. preSelectionTextRange.setEndPoint('EndToStart', selectedTextRange);
  38. var start = preSelectionTextRange.text.length;
  39. var end = start + selectedTextRange.text.length;
  40. }
  41. this.startOffset = start;
  42. this.endOffset = end;
  43. console.log(start, end);
  44. },
  45. validRange: function () {
  46. if (this.startOffset == this.endOffset) {
  47. return false
  48. } else if (this.startOffset > this.text.length || this.endOffset > this.text.length) {
  49. return false
  50. } else if (this.startOffset < 0 || this.endOffset < 0) {
  51. return false
  52. } else {
  53. return true
  54. }
  55. },
  56. resetRange: function () {
  57. this.startOffset = 0;
  58. this.endOffset = 0
  59. },
  60. addLabel: function (label_id) {
  61. if (this.validRange()) {
  62. var label = {
  63. start_offset: this.startOffset,
  64. end_offset: this.endOffset,
  65. label_id: label_id
  66. };
  67. this.entityPositions.push(label);
  68. return label
  69. }
  70. },
  71. deleteLabel: function (index) {
  72. this.$emit('delete-label', index);
  73. this.entityPositions.splice(index, 1)
  74. },
  75. getBackgroundColor: function (label_id) {
  76. for (item of this.labels) {
  77. if (item.id == label_id) {
  78. return item.background_color
  79. }
  80. }
  81. },
  82. getTextColor: function (label_id) {
  83. for (item of this.labels) {
  84. if (item.id == label_id) {
  85. return item.text_color
  86. }
  87. }
  88. }
  89. },
  90. watch: {
  91. entityPositions: function () {
  92. this.resetRange()
  93. }
  94. },
  95. computed: {
  96. sortedEntityPositions: function () {
  97. return this.entityPositions.sort((a, b) => a.start_offset - b.start_offset)
  98. },
  99. chunks: function () {
  100. var res = [];
  101. var left = 0;
  102. for (let [i, e] of this.sortedEntityPositions.entries()) {
  103. var text = this.text.slice(left, e['start_offset']);
  104. res.push({
  105. 'word': text,
  106. 'color': '',
  107. 'background': ''
  108. });
  109. var text = this.text.slice(e['start_offset'], e['end_offset']);
  110. res.push({
  111. 'word': text,
  112. 'color': this.getTextColor(e.label.id),
  113. 'background': this.getBackgroundColor(e.label.id),
  114. 'index': i
  115. });
  116. left = e['end_offset'];
  117. }
  118. var text = this.text.slice(left, this.text.length);
  119. res.push({
  120. 'word': text,
  121. 'color': '',
  122. 'background': ''
  123. });
  124. console.log(res);
  125. console.log(this.labels);
  126. console.log(this.entityPositions);
  127. return res
  128. }
  129. }
  130. }
  131. var vm = new Vue({
  132. el: '#mail-app',
  133. delimiters: ['[[', ']]'],
  134. components: {
  135. 'annotator': Annotator,
  136. },
  137. data: {
  138. cur: 0,
  139. items: [{id: null, text: '', labels: []}],
  140. labels: [],
  141. guideline: 'Here is the Annotation Guideline Text',
  142. total: 0,
  143. remaining: 0,
  144. searchQuery: '',
  145. url: '',
  146. },
  147. methods: {
  148. annotate: function (label_id) {
  149. var payload = this.$refs.annotator.addLabel(label_id);
  150. var doc_id = this.items[this.cur].id;
  151. HTTP.post(`docs/${doc_id}/annotations/`, payload).then(response => {
  152. this.items[this.cur]['labels'].push(response.data);
  153. });
  154. this.updateProgress()
  155. },
  156. addLabel: function (label_id) {
  157. },
  158. deleteLabel: async function (index) {
  159. var doc_id = this.items[this.cur].id;
  160. var annotation_id = this.items[this.cur]['labels'][index].id;
  161. HTTP.delete(`docs/${doc_id}/annotations/${annotation_id}`).then(response => {
  162. this.items[this.cur]['labels'].splice(index, 1)
  163. });
  164. this.updateProgress();
  165. },
  166. nextPage: async function () {
  167. this.cur += 1;
  168. if (this.cur == this.items.length) {
  169. if (this.next) {
  170. this.url = this.next;
  171. await this.search();
  172. this.cur = 0;
  173. } else {
  174. this.cur = this.items.length - 1;
  175. }
  176. }
  177. this.showMessage(this.cur);
  178. },
  179. prevPage: async function () {
  180. this.cur -= 1;
  181. if (this.cur == -1) {
  182. if (this.prev) {
  183. this.url = this.prev;
  184. await this.search();
  185. this.cur = this.items.length - 1;
  186. } else {
  187. this.cur = 0;
  188. }
  189. }
  190. this.showMessage(this.cur);
  191. },
  192. submit: async function () {
  193. this.url = `docs/?q=${this.searchQuery}`;
  194. await this.search();
  195. this.cur = 0;
  196. },
  197. search: async function () {
  198. await HTTP.get(this.url).then(response => {
  199. this.items = response.data['results'];
  200. this.next = response.data['next'];
  201. this.prev = response.data['previous'];
  202. })
  203. },
  204. showMessage: function (index) {
  205. this.cur = index;
  206. },
  207. updateProgress: function () {
  208. HTTP.get('progress').then(response => {
  209. this.total = response.data['total'];
  210. this.remaining = response.data['remaining'];
  211. })
  212. }
  213. },
  214. created: function () {
  215. HTTP.get('labels').then(response => {
  216. this.labels = response.data
  217. });
  218. this.updateProgress();
  219. this.submit();
  220. },
  221. computed: {
  222. achievement: function () {
  223. var done = this.total - this.remaining;
  224. var percentage = Math.round(done / this.total * 100);
  225. return this.total > 0 ? percentage : 0;
  226. },
  227. progressColor: function () {
  228. if (this.achievement < 30) {
  229. return 'is-danger'
  230. } else if (this.achievement < 70) {
  231. return 'is-warning'
  232. } else {
  233. return 'is-primary'
  234. }
  235. }
  236. }
  237. });