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.

307 lines
7.3 KiB

5 years ago
  1. import * as marked from 'marked';
  2. import hljs from 'highlight.js';
  3. import VueJsonPretty from 'vue-json-pretty';
  4. import isEmpty from 'lodash.isempty';
  5. import HTTP from './http';
  6. import Messages from './messages.vue';
  7. const getOffsetFromUrl = (url) => {
  8. const offsetMatch = url.match(/[?#].*offset=(\d+)/);
  9. if (offsetMatch == null) {
  10. return 0;
  11. }
  12. return parseInt(offsetMatch[1], 10);
  13. };
  14. const storeOffsetInUrl = (offset) => {
  15. let href = window.location.href;
  16. const fragmentStart = href.indexOf('#') + 1;
  17. if (fragmentStart === 0) {
  18. href += '#offset=' + offset;
  19. } else {
  20. const prefix = href.substring(0, fragmentStart);
  21. const fragment = href.substring(fragmentStart);
  22. const newFragment = fragment.split('&').map((fragmentPart) => {
  23. const keyValue = fragmentPart.split('=');
  24. return keyValue[0] === 'offset'
  25. ? 'offset=' + offset
  26. : fragmentPart;
  27. }).join('&');
  28. href = prefix + newFragment;
  29. }
  30. window.location.href = href;
  31. };
  32. export const annotationMixin = {
  33. components: { VueJsonPretty },
  34. data() {
  35. return {
  36. pageNumber: 0,
  37. docs: [],
  38. annotations: [],
  39. labels: [],
  40. guideline: '',
  41. total: 0,
  42. remaining: 0,
  43. searchQuery: '',
  44. url: '',
  45. offset: getOffsetFromUrl(window.location.href),
  46. picked: 'all',
  47. count: 0,
  48. isMetadataActive: false,
  49. isAnnotationGuidelineActive: false,
  50. };
  51. },
  52. methods: {
  53. async nextPage() {
  54. this.pageNumber += 1;
  55. if (this.pageNumber === this.docs.length) {
  56. if (this.next) {
  57. this.url = this.next;
  58. await this.search();
  59. this.pageNumber = 0;
  60. } else {
  61. this.pageNumber = this.docs.length - 1;
  62. }
  63. }
  64. },
  65. async prevPage() {
  66. this.pageNumber -= 1;
  67. if (this.pageNumber === -1) {
  68. if (this.prev) {
  69. this.url = this.prev;
  70. await this.search();
  71. this.pageNumber = this.docs.length - 1;
  72. } else {
  73. this.pageNumber = 0;
  74. }
  75. }
  76. },
  77. async search() {
  78. await HTTP.get(this.url).then((response) => {
  79. this.docs = response.data.results;
  80. this.next = response.data.next;
  81. this.prev = response.data.previous;
  82. this.count = response.data.count;
  83. this.annotations = [];
  84. for (let i = 0; i < this.docs.length; i++) {
  85. const doc = this.docs[i];
  86. this.annotations.push(doc.annotations);
  87. }
  88. this.offset = getOffsetFromUrl(this.url);
  89. });
  90. },
  91. getState() {
  92. if (this.picked === 'all') {
  93. return '';
  94. }
  95. if (this.picked === 'active') {
  96. return 'true';
  97. }
  98. return 'false';
  99. },
  100. async submit() {
  101. const state = this.getState();
  102. this.url = `docs?q=${this.searchQuery}&is_checked=${state}&offset=${this.offset}`;
  103. await this.search();
  104. this.pageNumber = 0;
  105. },
  106. removeLabel(annotation) {
  107. const docId = this.docs[this.pageNumber].id;
  108. HTTP.delete(`docs/${docId}/annotations/${annotation.id}`).then(() => {
  109. const index = this.annotations[this.pageNumber].indexOf(annotation);
  110. this.annotations[this.pageNumber].splice(index, 1);
  111. });
  112. },
  113. replaceNull(shortcut) {
  114. if (shortcut === null) {
  115. shortcut = '';
  116. }
  117. shortcut = shortcut.split(' ');
  118. return shortcut;
  119. },
  120. shortcutKey(label) {
  121. let shortcut = label.suffix_key;
  122. if (label.prefix_key) {
  123. shortcut = `${label.prefix_key} ${shortcut}`;
  124. }
  125. return shortcut;
  126. },
  127. },
  128. watch: {
  129. picked() {
  130. this.submit();
  131. },
  132. annotations() {
  133. // fetch progress info.
  134. HTTP.get('statistics').then((response) => {
  135. this.total = response.data.total;
  136. this.remaining = response.data.remaining;
  137. });
  138. },
  139. offset() {
  140. storeOffsetInUrl(this.offset);
  141. },
  142. },
  143. created() {
  144. HTTP.get('labels').then((response) => {
  145. this.labels = response.data;
  146. });
  147. HTTP.get().then((response) => {
  148. this.guideline = response.data.guideline;
  149. });
  150. this.submit();
  151. },
  152. computed: {
  153. achievement() {
  154. const done = this.total - this.remaining;
  155. const percentage = Math.round(done / this.total * 100);
  156. return this.total > 0 ? percentage : 0;
  157. },
  158. compiledMarkdown() {
  159. return marked(this.guideline, {
  160. sanitize: true,
  161. });
  162. },
  163. documentMetadata() {
  164. const document = this.docs[this.pageNumber];
  165. if (document == null || document.meta == null) {
  166. return null;
  167. }
  168. const metadata = JSON.parse(document.meta);
  169. if (isEmpty(metadata)) {
  170. return null;
  171. }
  172. return metadata;
  173. },
  174. id2label() {
  175. const id2label = {};
  176. for (let i = 0; i < this.labels.length; i++) {
  177. const label = this.labels[i];
  178. id2label[label.id] = label;
  179. }
  180. return id2label;
  181. },
  182. progressColor() {
  183. if (this.achievement < 30) {
  184. return 'is-danger';
  185. }
  186. if (this.achievement < 70) {
  187. return 'is-warning';
  188. }
  189. return 'is-primary';
  190. },
  191. },
  192. };
  193. export const uploadMixin = {
  194. components: { Messages },
  195. data: () => ({
  196. file: '',
  197. messages: [],
  198. format: 'json',
  199. isLoading: false,
  200. }),
  201. mounted() {
  202. hljs.initHighlighting();
  203. },
  204. methods: {
  205. upload() {
  206. this.isLoading = true;
  207. this.file = this.$refs.file.files[0];
  208. const formData = new FormData();
  209. formData.append('file', this.file);
  210. formData.append('format', this.format);
  211. HTTP.post('docs/upload',
  212. formData,
  213. {
  214. headers: {
  215. 'Content-Type': 'multipart/form-data',
  216. },
  217. })
  218. .then((response) => {
  219. console.log(response); // eslint-disable-line no-console
  220. this.messages = [];
  221. window.location = window.location.pathname.split('/').slice(0, -1).join('/');
  222. })
  223. .catch((error) => {
  224. this.isLoading = false;
  225. this.handleError(error);
  226. });
  227. },
  228. handleError(error) {
  229. const problems = Array.isArray(error.response.data)
  230. ? error.response.data
  231. : [error.response.data];
  232. problems.forEach((problem) => {
  233. if ('detail' in problem) {
  234. this.messages.push(problem.detail);
  235. } else if ('text' in problem) {
  236. this.messages = problem.text;
  237. }
  238. });
  239. },
  240. download() {
  241. this.isLoading = true;
  242. const headers = {};
  243. if (this.format === 'csv') {
  244. headers.Accept = 'text/csv; charset=utf-8';
  245. headers['Content-Type'] = 'text/csv; charset=utf-8';
  246. } else {
  247. headers.Accept = 'application/json';
  248. headers['Content-Type'] = 'application/json';
  249. }
  250. HTTP({
  251. url: 'docs/download',
  252. method: 'GET',
  253. responseType: 'blob',
  254. params: {
  255. q: this.format,
  256. },
  257. headers,
  258. }).then((response) => {
  259. const url = window.URL.createObjectURL(new Blob([response.data]));
  260. const link = document.createElement('a');
  261. link.href = url;
  262. link.setAttribute('download', 'file.' + this.format); // or any other extension
  263. document.body.appendChild(link);
  264. this.isLoading = false;
  265. link.click();
  266. }).catch((error) => {
  267. this.isLoading = false;
  268. this.handleError(error);
  269. });
  270. },
  271. },
  272. };