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.

374 lines
11 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
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. import { eventBus } from '@/app';
  6. @Component
  7. export default class ProcessFile extends Vue {
  8. /**
  9. * Props
  10. */
  11. // The data for the file we are processing
  12. @Prop({default: {id: -1, file: '', path: ''}})
  13. public readonly file!: FileData;
  14. // The list of available searchers
  15. @Prop({default: []})
  16. public readonly searchers!: [];
  17. /**
  18. * Class members
  19. */
  20. // The id of the interval used to query the file status
  21. private intervalId!: any;
  22. // The content of the file we are processing
  23. private fileContent: string = '';
  24. // The processed document content
  25. private processedFileContent: string = '';
  26. private processedFileContentPreview: string = '';
  27. private documentDiffIndexes: { [key: string]: Array<{ start: number; end: number; }>; } = {};
  28. // Flag to determine whether the text is processing or not
  29. private processing: boolean = false;
  30. // Toggles the visibility of the selected searchers sidebar
  31. private searchersSidebarVisible: boolean = false;
  32. // Toggles the visibility of the available searchers dialog
  33. private searchersDialogVisible: boolean = false;
  34. // Toggles the visibility of the document upload dialog
  35. private uploadDialogVisible: boolean = false;
  36. // The list of filters/searchers in a format usable by the datatable
  37. // private searchersData: Array<{ id: string; name: string; type: string; }> = [];
  38. // The list of filters applied to the selected searchers
  39. private searchersFilters: any = [];
  40. // The list of selected filters/searchers
  41. private selectedSearchers: any = {};
  42. //The list of expanded rows in the selected filters/searchers table
  43. private expandedRows: Array<any> = [];
  44. // The list of options applied to the searchers (for the moment, only replace_with)
  45. private searchersOptions: { [key: string]: string } = {};
  46. // Flag to determine whether or not we will show the diff highlights
  47. private showDiffHighlight: boolean = false;
  48. private newlySelectedSearchers: Array<{ id: string; name: string; }> = [];
  49. /**
  50. *
  51. */
  52. created() {
  53. let storedSearchers = localStorage.getItem('searchers');
  54. if (storedSearchers !== null) {
  55. this.selectedSearchers = JSON.parse(storedSearchers);
  56. localStorage.removeItem('searchers');
  57. let searchersOptions = localStorage.getItem('searchersOptions');
  58. if (searchersOptions !== null) {
  59. this.searchersOptions = JSON.parse(searchersOptions);
  60. localStorage.removeItem('searchersOptions');
  61. }
  62. }
  63. this.intervalId = setInterval(this.waitForFile, 3000);
  64. window.addEventListener('beforeunload', async (event) => {
  65. // Cancel the event as stated by the standard.
  66. event.preventDefault();
  67. const response = await this.$api.discardFile(this.file.id);
  68. return event;
  69. });
  70. eventBus.$on('changeRoute', this.changeRoute);
  71. }
  72. /**
  73. * MD-to-HTML compiled file content
  74. */
  75. get compiledFileContent(): string {
  76. return marked(this.fileContent);
  77. }
  78. /**
  79. * MD-to-HTML compiled processed file content
  80. */
  81. get compiledProcessedFileContent(): string {
  82. return marked(this.processedFileContent);
  83. }
  84. /**
  85. * MD-to-HTML compiled processed file content with diff highlight
  86. */
  87. get compiledProcessedFileContentPreview(): string {
  88. return marked(this.processedFileContentPreview);
  89. }
  90. public changeRoute(url: string) {
  91. const el = document.body;
  92. setTimeout(() => {
  93. el.classList.remove('p-overflow-hidden');
  94. }, 10);
  95. this.$confirm.require({
  96. message: 'You will lose any progress on the current uploaded document. Are you sure you want to proceed?',
  97. header: 'Confirmation',
  98. icon: 'pi pi-exclamation-triangle',
  99. blockScroll: false,
  100. accept: () => {
  101. window.location.href = url;
  102. },
  103. reject: () => {
  104. // TODO: Show a message to the user that the action was cancelled.
  105. }
  106. });
  107. }
  108. /**
  109. * Toggle the sidebar containing the searchers
  110. */
  111. private toggleSearchersSidebar() {
  112. this.searchersSidebarVisible = !this.searchersSidebarVisible;
  113. }
  114. /**
  115. * Toggle the menu containing the list of available searchers
  116. *
  117. * @param {string} newValue The new value for the dialog visibility
  118. */
  119. private toggleSearchersDialog(newValue?: boolean) {
  120. if (typeof newValue !== 'undefined') {
  121. this.searchersDialogVisible = newValue;
  122. } else {
  123. this.searchersDialogVisible = !this.searchersDialogVisible;
  124. }
  125. if ( ! this.searchersDialogVisible) {
  126. for (let selectedSearcher of this.newlySelectedSearchers) {
  127. // this.selectedSearchers[selectedSearcher.id] = selectedSearcher;
  128. this.$set(this.selectedSearchers, selectedSearcher.id, selectedSearcher);
  129. this.expandedRows = Object.values(this.selectedSearchers).filter((p: any) => p.id);
  130. }
  131. this.newlySelectedSearchers = [];
  132. }
  133. }
  134. /**
  135. * Toggle the dialog which lets the user upload a new document
  136. *
  137. * @param {boolean} newValue
  138. */
  139. toggleUploadDialog(newValue?: boolean) {
  140. if (typeof newValue !== 'undefined') {
  141. this.uploadDialogVisible = newValue;
  142. } else {
  143. this.uploadDialogVisible = !this.uploadDialogVisible;
  144. }
  145. }
  146. /**
  147. * A method which uploads the files to the server for processing
  148. *
  149. * @param event The event containing the uploaded files information
  150. */
  151. public async uploadFile(event: any): Promise<void> {
  152. localStorage.setItem('searchers', JSON.stringify(this.selectedSearchers));
  153. localStorage.setItem('searchersOptions', JSON.stringify(this.searchersOptions));
  154. this.$confirm.require({
  155. message: 'You will lose any progress on the current uploaded document. Are you sure you want to proceed?',
  156. header: 'Confirmation',
  157. icon: 'pi pi-exclamation-triangle',
  158. accept: () => {
  159. this.fileContent = this.processedFileContent = '';
  160. let file = event.files[0];
  161. this.toggleUploadDialog(false);
  162. this.$api.discardFile(this.file.id);
  163. this.$emit('newFile', file);
  164. },
  165. reject: () => {
  166. // TODO: Show a message to the user that the action was cancelled.
  167. }
  168. });
  169. }
  170. /**
  171. * Wait for the file to be processed in ingest
  172. */
  173. private async waitForFile() {
  174. const response = await this.$api.getFileData(this.file.id);
  175. if (response.status === 'processing') {
  176. return;
  177. }
  178. clearInterval(this.intervalId);
  179. if (response.status === 'success') {
  180. this.fileContent = response.content ? response.content : '';
  181. this.$toast.add({
  182. severity: 'success',
  183. summary: 'File loaded',
  184. detail: 'The file has been processed by ingest.',
  185. life: 3000
  186. });
  187. }
  188. if (response.status === 'fail') {
  189. const error = 'There was an error processing the file in ingest';
  190. this.$toast.add({
  191. severity: 'error',
  192. summary: 'File error',
  193. detail: error,
  194. life: 3000
  195. });
  196. this.$emit('error', error);
  197. }
  198. }
  199. /**
  200. *
  201. * @param $event
  202. */
  203. private onSelectedSearchersReorder($event: any) {
  204. Object.assign({}, this.selectedSearchers, $event.value);
  205. }
  206. private confirmDeleteProduct(searcher: any) {
  207. this.$delete(this.selectedSearchers, searcher.id);
  208. }
  209. /**
  210. * Run the searchers
  211. */
  212. private async runSearchers() {
  213. this.processing = true;
  214. this.processedFileContent = '';
  215. let searchers: Array<{ key: string; replace_with: string; }> = [];
  216. Object.values(this.selectedSearchers).forEach((searcher: any) => {
  217. searchers.push({
  218. 'key': searcher.id,
  219. 'replace_with': this.searchersOptions[searcher.id] || ''
  220. });
  221. });
  222. try {
  223. const response = await this.$api.filterDocument(this.fileContent, searchers);
  224. this.processedFileContent = response.content;
  225. this.documentDiffIndexes = response.indexes;
  226. this.createDiffPreview();
  227. this.processing = false;
  228. } catch (e) {
  229. this.$emit('error', 'Server error.');
  230. // if (isServerError(e)) {
  231. // this.$emit('error', getServerErrorMessage(e));
  232. // }
  233. }
  234. }
  235. /**
  236. * Create the diff preview for the document
  237. */
  238. private createDiffPreview() {
  239. this.processedFileContentPreview = this.processedFileContent;
  240. let indexes: Array<{ start: number; end: number }> = [];
  241. for (let searcher in this.documentDiffIndexes) {
  242. const searcherIndexes = this.documentDiffIndexes[searcher];
  243. searcherIndexes.forEach(index => {
  244. indexes.push(index);
  245. });
  246. }
  247. indexes.sort((a, b) => {
  248. return b.start - a.start;
  249. });
  250. this.processedFileContentPreview = indexes.reduce(
  251. (r, a) => {
  252. r[a.start] = '<mark>' + r[a.start];
  253. r[a.end] += '</mark>';
  254. return r;
  255. },
  256. this.processedFileContent.split('')
  257. ).join('');
  258. }
  259. /**
  260. * Download the document in ODT format
  261. */
  262. private async downloadOdt() {
  263. let response = await this.$api.convertFile(this.processedFileContent, this.file.id);
  264. window.open(`${window.location.origin}/file/download/` + response.path);
  265. }
  266. private canRunSearchers(): boolean {
  267. if (this.fileContent == '' || Object.keys(this.selectedSearchers).length === 0) {
  268. return false;
  269. }
  270. for (let key of Object.keys(this.selectedSearchers)) {
  271. const searcher = this.selectedSearchers[key];
  272. if (!this.isValidParam(searcher.id, searcher.param)) {
  273. return false;
  274. }
  275. }
  276. return true;
  277. }
  278. /**
  279. * Check if a param is valid or not.
  280. *
  281. * @param {string} paramId
  282. * @param {string} paramType
  283. * @returns {boolean}
  284. */
  285. private isValidParam(paramId: string, paramType: string): boolean {
  286. if (
  287. paramType === 'required' &&
  288. (this.searchersOptions[paramId] === '' || this.searchersOptions[paramId] === undefined)
  289. ) {
  290. return false;
  291. }
  292. return true;
  293. }
  294. /**
  295. * Watch the `showDiffHighlight` property for changes
  296. *
  297. * @param {boolean} newValue
  298. * @param {boolean} oldValue
  299. */
  300. @Watch('showDiffHighlight')
  301. private onDiffHighlightChanged(newValue: boolean, oldValue: boolean): void {
  302. //
  303. }
  304. }