Repo for the search and displace core module including the interface to select files and search and displace operations to run on them. https://searchanddisplace.com
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.

297 lines
8.7 KiB

3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
  1. import marked from 'marked';
  2. import {Vue, Component, Prop, Watch} from 'vue-property-decorator';
  3. import {FileData} from '@/interfaces/FileData';
  4. import { isServerError, getServerErrorMessage } from '@/SearchDisplace/helpers';
  5. @Component
  6. export default class ProcessFile extends Vue {
  7. /**
  8. * Props
  9. */
  10. // The data for the file we are processing
  11. @Prop({default: {id: -1, file: '', path: ''}})
  12. public readonly file!: FileData;
  13. // The list of available searchers
  14. @Prop({default: []})
  15. public readonly searchers!: [];
  16. /**
  17. * Class members
  18. */
  19. // The id of the interval used to query the file status
  20. private intervalId!: any;
  21. // The content of the file we are processing
  22. private fileContent: string = '';
  23. // The processed document content
  24. private processedFileContent: string = '';
  25. private processedFileContentPreview: string = '';
  26. private documentDiffIndexes: { [key: string]: Array<{ start: number; end: number; }>; } = {};
  27. // Flag to determine whether the text is processing or not
  28. private processing: boolean = false;
  29. // Toggles the visibility of the selected searchers sidebar
  30. private searchersSidebarVisible: boolean = false;
  31. // Toggles the visibility of the available searchers dialog
  32. private searchersDialogVisible: boolean = false;
  33. // Toggles the visibility of the document upload dialog
  34. private uploadDialogVisible: boolean = false;
  35. // The list of filters/searchers in a format usable by the datatable
  36. // private searchersData: Array<{ id: string; name: string; type: string; }> = [];
  37. // The list of filters applied to the selected searchers
  38. private searchersFilters: any = [];
  39. // The list of selected filters/searchers
  40. private selectedSearchers: any = {};
  41. //The list of expanded rows in the selected filters/searchers table
  42. private expandedRows: Array<any> = [];
  43. // The list of options applied to the searchers (for the moment, only replace_with)
  44. private searchersOptions: { [key: string]: string } = {};
  45. // Flag to determine whether or not we will show the diff highlights
  46. private showDiffHighlight: boolean = false;
  47. private newlySelectedSearchers: Array<{ id: string; name: string; }> = [];
  48. /**
  49. *
  50. */
  51. created() {
  52. this.intervalId = setInterval(this.waitForFile, 3000);
  53. }
  54. /**
  55. * MD-to-HTML compiled file content
  56. */
  57. get compiledFileContent(): string {
  58. return marked(this.fileContent);
  59. }
  60. /**
  61. * MD-to-HTML compiled processed file content
  62. */
  63. get compiledProcessedFileContent(): string {
  64. return marked(this.processedFileContent);
  65. }
  66. /**
  67. * MD-to-HTML compiled processed file content with diff highlight
  68. */
  69. get compiledProcessedFileContentPreview(): string {
  70. return marked(this.processedFileContentPreview);
  71. }
  72. /**
  73. * Toggle the sidebar containing the searchers
  74. */
  75. private toggleSearchersSidebar() {
  76. this.searchersSidebarVisible = !this.searchersSidebarVisible;
  77. }
  78. /**
  79. * Toggle the menu containing the list of available searchers
  80. *
  81. * @param {string} newValue The new value for the dialog visibility
  82. */
  83. private toggleSearchersDialog(newValue?: boolean) {
  84. if (typeof newValue !== 'undefined') {
  85. this.searchersDialogVisible = newValue;
  86. } else {
  87. this.searchersDialogVisible = !this.searchersDialogVisible;
  88. }
  89. if ( ! this.searchersDialogVisible) {
  90. for (let selectedSearcher of this.newlySelectedSearchers) {
  91. // this.selectedSearchers[selectedSearcher.id] = selectedSearcher;
  92. this.$set(this.selectedSearchers, selectedSearcher.id, selectedSearcher);
  93. this.expandedRows = Object.values(this.selectedSearchers).filter((p: any) => p.id);
  94. }
  95. this.newlySelectedSearchers = [];
  96. }
  97. }
  98. /**
  99. * Toggle the dialog which lets the user upload a new document
  100. *
  101. * @param {boolean} newValue
  102. */
  103. toggleUploadDialog(newValue?: boolean) {
  104. if (typeof newValue !== 'undefined') {
  105. this.uploadDialogVisible = newValue;
  106. } else {
  107. this.uploadDialogVisible = !this.searchersDialogVisible;
  108. }
  109. }
  110. /**
  111. * A method which uploads the files to the server for processing
  112. *
  113. * @param event The event containing the uploaded files information
  114. */
  115. public async uploadFile(event: any): Promise<void> {
  116. this.$confirm.require({
  117. message: 'You will lose any progress on the current uploaded document. Are you sure you want to proceed?',
  118. header: 'Confirmation',
  119. icon: 'pi pi-exclamation-triangle',
  120. accept: () => {
  121. this.fileContent = this.processedFileContent = '';
  122. let file = event.files[0];
  123. this.toggleUploadDialog(false);
  124. this.$emit('newFile', file);
  125. },
  126. reject: () => {
  127. // TODO: Show a message to the user that the action was cancelled.
  128. }
  129. });
  130. }
  131. /**
  132. * Wait for the file to be processed in ingest
  133. */
  134. private async waitForFile() {
  135. const response = await this.$api.getFileData(this.file.id);
  136. if (response.status === 'processing') {
  137. return;
  138. }
  139. clearInterval(this.intervalId);
  140. if (response.status === 'success') {
  141. this.fileContent = response.content ? response.content : '';
  142. this.$toast.add({
  143. severity: 'success',
  144. summary: 'File loaded',
  145. detail: 'The file has been processed by ingest.',
  146. life: 3000
  147. });
  148. }
  149. if (response.status === 'fail') {
  150. const error = 'There was an error processing the file in ingest';
  151. this.$toast.add({
  152. severity: 'error',
  153. summary: 'File error',
  154. detail: error,
  155. life: 3000
  156. });
  157. this.$emit('error', error);
  158. }
  159. }
  160. /**
  161. *
  162. * @param $event
  163. */
  164. private onSelectedSearchersReorder($event: any) {
  165. Object.assign({}, this.selectedSearchers, $event.value);
  166. }
  167. private confirmDeleteProduct(searcher: any) {
  168. this.$delete(this.selectedSearchers, searcher.id);
  169. }
  170. /**
  171. * Run the searchers
  172. */
  173. private async runSearchers() {
  174. this.processing = true;
  175. this.processedFileContent = '';
  176. let searchers: Array<{ key: string; replace_with: string; }> = [];
  177. Object.values(this.selectedSearchers).forEach((searcher: any) => {
  178. searchers.push({
  179. 'key': searcher.id,
  180. 'replace_with': this.searchersOptions[searcher.id] || ''
  181. });
  182. });
  183. try {
  184. const response = await this.$api.filterDocument(this.fileContent, searchers);
  185. this.processedFileContent = response.content;
  186. this.documentDiffIndexes = response.indexes;
  187. this.createDiffPreview();
  188. this.processing = false;
  189. } catch (e) {
  190. this.$emit('error', 'Server error.');
  191. // if (isServerError(e)) {
  192. // this.$emit('error', getServerErrorMessage(e));
  193. // }
  194. }
  195. }
  196. /**
  197. * Create the diff preview for the document
  198. */
  199. private createDiffPreview() {
  200. console.log('CREATING DIFF PREVIEW: ', this.processedFileContent);
  201. this.processedFileContentPreview = this.processedFileContent;
  202. let indexes: Array<{ start: number; end: number }> = [];
  203. for (let searcher in this.documentDiffIndexes) {
  204. const searcherIndexes = this.documentDiffIndexes[searcher];
  205. searcherIndexes.forEach(index => {
  206. indexes.push(index);
  207. });
  208. }
  209. indexes.sort((a, b) => {
  210. return b.start - a.start;
  211. });
  212. this.processedFileContentPreview = indexes.reduce(
  213. (r, a) => {
  214. r[a.start] = '<mark>' + r[a.start];
  215. r[a.end] += '</mark>';
  216. return r;
  217. },
  218. this.processedFileContent.split('')
  219. ).join('');
  220. }
  221. /**
  222. * Download the document in ODT format
  223. */
  224. private async downloadOdt() {
  225. let response = await this.$api.convertFile(this.processedFileContent);
  226. window.open(`${window.location.origin}/file/download/` + response.path);
  227. }
  228. /**
  229. * Watch the `showDiffHighlight` property for changes
  230. *
  231. * @param {boolean} newValue
  232. * @param {boolean} oldValue
  233. */
  234. @Watch('showDiffHighlight')
  235. private onDiffHighlightChanged(newValue: boolean, oldValue: boolean): void {
  236. //
  237. }
  238. }