|
|
import {Vue, Component, Prop, Watch} from 'vue-property-decorator'; import {FileData} from '@/interfaces/FileData'; import { eventBus } from '@/app'; import DefineSearcher from '../Searchers/DefineSearcher.vue'; import RadioButton from "primevue/radiobutton"; import {isServerError} from "@/SearchDisplace/helpers";
@Component({ components: { RadioButton, DefineSearcher, }, }) export default class ProcessFile extends Vue {
/** * Props */ // The data for the file we are processing
@Prop({default: {id: -1, file: '', path: ''}}) public readonly file!: FileData;
// The list of available searchers
@Prop({default: []}) public readonly searchers!: [];
@Prop({default: null}) public readonly document!: File | null;
/** * Class members */ // The id of the interval used to query the file status
private intervalId!: any;
// The content of the file we are processing
private fileContent: string = '';
// The processed document content
private processedFileContent: string = '';
private processedFileContentPreview: string = '';
private documentDiffIndexes: { [key: string]: Array<{ start: number; end: number; }>; } = {};
// Flag to determine whether the text is processing or not
private processing: boolean = false;
// Toggles the visibility of the selected searchers sidebar
private searchersSidebarVisible: boolean = false;
// Toggles the visibility of the available searchers dialog
private searchersDialogVisible: boolean = false;
// Toggles the visibility of the document upload dialog
private uploadDialogVisible: boolean = false;
// The list of filters/searchers in a format usable by the datatable
// private searchersData: Array<{ id: string; name: string; type: string; }> = [];
// The list of filters applied to the selected searchers
private searchersFilters: any = {};
// The list of selected filters/searchers
private selectedSearchers: any = {};
//The list of expanded rows in the selected filters/searchers table
private expandedRows: Array<any> = [];
// The list of options applied to the searchers (for the moment, only replace_with)
private searchersOptions: { [key: string]: { type: string, value: string, } } = {};
// Flag to determine whether or not we will show the diff highlights
private showDiffHighlight: boolean = false;
private newlySelectedSearchers: Array<{ id: string; name: string; }> = [];
private showDefineSearcher: boolean = false;
private searcherToDefineText: string = '';
private applyingOnOriginalDocument: boolean = false;
/** * */ created() { let storedSearchers = localStorage.getItem('searchers'); if (storedSearchers !== null) { this.selectedSearchers = JSON.parse(storedSearchers); localStorage.removeItem('searchers');
let searchersOptions = localStorage.getItem('searchersOptions'); if (searchersOptions !== null) { this.searchersOptions = JSON.parse(searchersOptions); localStorage.removeItem('searchersOptions'); } }
this.intervalId = setInterval(this.waitForFile, 3000);
window.addEventListener('onbeforeunload', async (event) => { // Cancel the event as stated by the standard.
event.preventDefault();
const response = await this.$api.discardFile(this.file.id);
return event; });
eventBus.$on('changeRoute', this.changeRoute); }
/** * HTML compiled file content */ get compiledFileContent(): string { return this.fileContent; }
/** * HTML compiled processed file content */ get compiledProcessedFileContent(): string { return this.processedFileContent; }
/** * MD-to-HTML compiled processed file content with diff highlight */ get compiledProcessedFileContentPreview(): string { return this.processedFileContentPreview; }
public changeRoute(url: string) { const el = document.body; setTimeout(() => { el.classList.remove('p-overflow-hidden'); }, 10); this.$confirm.require({ message: 'You will lose any progress on the current uploaded document. Are you sure you want to proceed?', header: 'Confirmation', icon: 'pi pi-exclamation-triangle', blockScroll: false, accept: () => { window.location.href = url; this.$api.discardFile(this.file.id); }, reject: () => { // TODO: Show a message to the user that the action was cancelled.
} }); }
/** * Toggle the sidebar containing the searchers */ private toggleSearchersSidebar() { this.searchersSidebarVisible = !this.searchersSidebarVisible; }
/** * Toggle the menu containing the list of available searchers * * @param {string} newValue The new value for the dialog visibility */ private toggleSearchersDialog(newValue?: boolean) { if (typeof newValue !== 'undefined') { this.searchersDialogVisible = newValue; } else { this.searchersDialogVisible = !this.searchersDialogVisible; }
if ( ! this.searchersDialogVisible) { for (let selectedSearcher of this.newlySelectedSearchers) { // this.selectedSearchers[selectedSearcher.id] = selectedSearcher;
this.$set(this.selectedSearchers, selectedSearcher.id, selectedSearcher);
this.expandedRows = Object.values(this.selectedSearchers).filter((p: any) => p.id); }
this.newlySelectedSearchers = []; } }
/** * Toggle the dialog which lets the user upload a new document * * @param {boolean} newValue */ toggleUploadDialog(newValue?: boolean) { if (typeof newValue !== 'undefined') { this.uploadDialogVisible = newValue; } else { this.uploadDialogVisible = !this.uploadDialogVisible; } }
/** * A method which uploads the files to the server for processing * * @param event The event containing the uploaded files information */ public async uploadFile(event: any): Promise<void> { localStorage.setItem('searchers', JSON.stringify(this.selectedSearchers)); localStorage.setItem('searchersOptions', JSON.stringify(this.searchersOptions)); this.toggleUploadDialog(false);
this.$confirm.require({ message: 'You will lose any progress on the current uploaded document. Are you sure you want to proceed?', header: 'Confirmation', icon: 'pi pi-exclamation-triangle', accept: () => { this.fileContent = this.processedFileContent = ''; let file = event.files[0]; this.toggleUploadDialog(false); this.$api.discardFile(this.file.id); this.$emit('newFile', file); }, reject: () => { // TODO: Show a message to the user that the action was cancelled.
} }); }
/** * Wait for the file to be processed in ingest */ private async waitForFile() { const response = await this.$api.getFileData(this.file.id);
if (response.status === 'processing') { return; }
clearInterval(this.intervalId);
if (response.status === 'success') { this.fileContent = response.content ? response.content : '';
this.$toast.add({ severity: 'success', summary: 'File loaded', detail: 'The file has been processed by ingest.', life: 3000 }); }
if (response.status === 'fail') { const error = 'There was an error processing the file in ingest';
this.$toast.add({ severity: 'error', summary: 'File error', detail: error, life: 3000 });
this.$emit('error', error); } }
private async verifySdOnOriginalDocumentIsDone(id: string) { const response = await this.$api.verifySdOnOriginalDocumentIsDone(id);
if (response.status === 'processing') { return; }
clearInterval(this.intervalId);
if (response.status === 'fail') { this.$toast.add({ severity: 'error', summary: 'Something went wrong.', detail: 'Document could not have been processed.', life: 7000, });
this.applyingOnOriginalDocument = false;
return; }
this.$toast.add({ severity: 'success', summary: 'File loaded', detail: 'The file has been processed by ingest.', life: 7000, });
this.downloadFinishedOriginalDocument(id);
this.applyingOnOriginalDocument = false;
// @TODO Send request to backend to delete file if no other way..
}
private downloadFinishedOriginalDocument(id: string) { const url = `${window.location.origin}/search-and-displace/original-document/${id}/download`;
const link = window.document.createElement('a'); link.setAttribute('download', 'Document.doxc'); link.href = url; window.document.body.appendChild(link); link.click(); link.remove(); }
/** * * @param $event */ private onSelectedSearchersReorder($event: any) { Object.assign({}, this.selectedSearchers, $event.value); }
private confirmDeleteProduct(searcher: any) { this.$delete(this.selectedSearchers, searcher.id); }
/** * Run the searchers */ private async runSearchers() { this.processing = true; this.processedFileContent = '';
let searchers: Array<{ key: string; type: string; value: string; }> = [];
Object.values(this.selectedSearchers).forEach((searcher: any) => { searchers.push({ key: searcher.id, type: this.searchersOptions[searcher.id].type, value: this.searchersOptions[searcher.id].value || '', }); });
try { const response = await this.$api.filterDocument(this.file.id, searchers);
this.processedFileContent = response.content; this.documentDiffIndexes = response.indexes; this.createDiffPreview();
this.processing = false; } catch (e) { this.$emit('error', 'Server error.');
// if (isServerError(e)) {
// this.$emit('error', getServerErrorMessage(e));
// }
} }
private async runSearchersWithoutDisplacing() { this.processing = true;
let searchers: Array<{ key: string; type: string; value: string; }> = [];
Object.values(this.selectedSearchers).forEach((searcher: any) => { searchers.push({ key: searcher.id, type: this.searchersOptions[searcher.id].type, value: this.searchersOptions[searcher.id].value || '' }); });
try { const response = await this.$api.filterDocument(this.file.id, searchers, true);
this.processedFileContent = response.content; // this.documentDiffIndexes = response;
this.createDiffPreview();
this.processing = false; } catch (e) { this.$emit('error', 'Server error.');
// if (isServerError(e)) {
// this.$emit('error', getServerErrorMessage(e));
// }
} }
/** * Create the diff preview for the document */ private createDiffPreview() { this.processedFileContentPreview = this.processedFileContent;
let indexes: Array<{ start: number; end: number }> = [];
for (let searcher in this.documentDiffIndexes) { const searcherIndexes = this.documentDiffIndexes[searcher];
searcherIndexes.forEach(index => { indexes.push(index); }); }
indexes.sort((a, b) => { return b.start - a.start; });
this.processedFileContentPreview = indexes.reduce( (r, a) => { r[a.start] = '<mark>' + r[a.start]; r[a.end] += '</mark>'; return r; }, this.processedFileContent.split('') ).join(''); }
/** * Download the document in ODT format */ private async downloadOdt() { let searchers: Array<{ key: string; type: string; value: string; }> = [];
Object.values(this.selectedSearchers).forEach((searcher: any) => { searchers.push({ key: searcher.id, type: this.searchersOptions[searcher.id].type, value: this.searchersOptions[searcher.id].value || '', }); });
let response = await this.$api.convertFile({id: this.file.id, name: this.file.file_name || 'filename.odt'}, searchers);
window.open(`${window.location.origin}/file/download/` + response.path); }
private async downloadOriginal() { if ( ! this.document) { return; }
this.applyingOnOriginalDocument = true;
this.$toast.add({ severity: 'info', summary: 'Processing...', detail: 'This operation may take a while..', life: 7000, });
let searchers: Array<{ key: string; type: string; value: string; }> = [];
Object.values(this.selectedSearchers).forEach((searcher: any) => { searchers.push({ key: searcher.id, type: this.searchersOptions[searcher.id].type, value: this.searchersOptions[searcher.id].value || '', }); });
try { const data = await this.$api.sdOnOriginalDocument(this.document, searchers);
this.intervalId = setInterval(() => { this.verifySdOnOriginalDocumentIsDone(data.id); }, 5000); } catch (e) { this.applyingOnOriginalDocument = false;
if (isServerError(e)) { if (e.response.data.hasOwnProperty('errors')) { const errors = e.response.data.errors;
if (errors.hasOwnProperty('file')) { this.$toast.add({ severity: 'error', summary: errors.file[0], detail: 'There was an error processing your file. Please try again later.', life: 7000, });
return; } }
if (e.response.data.hasOwnProperty('message')) { this.$toast.add({ severity: 'error', summary: e.response.data.message, detail: 'There was an error processing your file. Please try again later.', life: 7000, });
return; } }
this.$toast.add({ severity: 'error', summary: 'Something went wrong.', detail: 'There was an error processing your file. Please try again later.', life: 7000, }); } }
private canRunSearchers(): boolean { if (this.fileContent == '' || Object.keys(this.selectedSearchers).length === 0) { return false; }
for (let key of Object.keys(this.selectedSearchers)) { const searcher = this.selectedSearchers[key];
if (!this.isValidParam(searcher.id, searcher.param)) { return false; } }
return true; }
/** * Check if a param is valid or not. * * @param {string} paramId * @param {string} paramType * @returns {boolean} */ private isValidParam(paramId: string, paramType: string): boolean { if ( paramType === 'required' && (Object.keys(this.searchersOptions[paramId]).length === 0 || this.searchersOptions[paramId] === undefined) ) { return false; } return true; }
private onDefineSearcher(): void { const selection = window.getSelection(); const selectedText = selection ? selection.toString() : '';
if ( ! selectedText) { this.$toast.add({ severity: 'info', summary: 'No text selected.', detail: 'You need to select some text in order to define a new searcher.', life: 6000, });
return; }
this.showDefineSearcher = true; this.searcherToDefineText = selectedText; }
private onAddNewSearcher(): void { this.showDefineSearcher = true; this.searcherToDefineText = ''; }
private onSearcherDefined(definedSearcher: Object): void { this.$toast.add({ severity: 'success', summary: 'Searcher defined.', detail: 'You can use this newly defined searcher right away.', life: 6000, });
this.$emit('newSearcher', definedSearcher);
this.showDefineSearcher = false; }
/** * Watch the filters for any changes. When the search value is empty, remove it * This is needed due to a bug in PrimeVue where the pagination doesn't work when there is a active filter * * @param newValue * @param oldValue */ @Watch('searchersFilters', { deep: true }) private onFiltersChanged(newValue: any, oldValue: object): void { if (newValue.global === '') { this.searchersFilters = {}; } }
/** * Watch the `showDiffHighlight` property for changes * * @param {boolean} newValue * @param {boolean} oldValue */ @Watch('showDiffHighlight') private onDiffHighlightChanged(newValue: boolean, oldValue: boolean): void { //
}
@Watch('selectedSearchers', { deep: true, }) private onSelectedSearchersChanged(): void { const selectedIds = Object.keys(this.selectedSearchers); const optionsIds = Object.keys(this.searchersOptions);
selectedIds.forEach((selectedId) => { if (optionsIds.includes(selectedId)) { return; }
this.$set(this.searchersOptions, selectedId, { type: this.selectedSearchers[selectedId].tag ? 'displace' : 'replace', value: this.selectedSearchers[selectedId].tag ? this.selectedSearchers[selectedId].tag : '', }); });
optionsIds.forEach((optionId) => { if (selectedIds.includes(optionId)) { return; }
this.$delete(this.selectedSearchers, optionId); }); } }
|