Orzu Ionut
3 years ago
38 changed files with 29978 additions and 8139 deletions
-
31.vscode/launch.json
-
83app/Http/Controllers/FileController.php
-
21app/Http/Controllers/FilterController.php
-
10app/Http/Controllers/HomeController.php
-
24app/Http/Controllers/PagesController.php
-
4app/Http/Controllers/SearchAndDisplaceController.php
-
84package-lock.json
-
2package.json
-
BINpublic/fonts/vendor/primevue/resources/themes/mdc-dark-indigo/roboto-v20-latin-ext_latin-500.woff
-
BINpublic/fonts/vendor/primevue/resources/themes/mdc-dark-indigo/roboto-v20-latin-ext_latin-500.woff2
-
BINpublic/fonts/vendor/primevue/resources/themes/mdc-dark-indigo/roboto-v20-latin-ext_latin-700.woff
-
BINpublic/fonts/vendor/primevue/resources/themes/mdc-dark-indigo/roboto-v20-latin-ext_latin-700.woff2
-
BINpublic/fonts/vendor/primevue/resources/themes/mdc-dark-indigo/roboto-v20-latin-ext_latin-regular.woff
-
BINpublic/fonts/vendor/primevue/resources/themes/mdc-dark-indigo/roboto-v20-latin-ext_latin-regular.woff2
-
26600public/js/app.js
-
32resources/js/app.ts
-
100resources/js/components/Home.vue
-
56resources/js/components/Home/Home.ts
-
35resources/js/components/Home/Home.vue
-
82resources/js/components/ProcessFile.vue
-
54resources/js/components/ProcessFile/ProcessFile.scss
-
160resources/js/components/ProcessFile/ProcessFile.ts
-
188resources/js/components/ProcessFile/ProcessFile.vue
-
69resources/js/components/helpers/Filter.vue
-
2resources/js/components/layout/Header.vue
-
7resources/js/interfaces/FilterInterface.ts
-
4resources/js/interfaces/FilterOptions.ts
-
4resources/js/interfaces/responses/ErrorResponse.ts
-
5resources/js/interfaces/responses/FileStatusResponse.ts
-
6resources/js/interfaces/responses/FileUploadResponse.ts
-
18resources/js/plugins/ApiPlugin.ts
-
7resources/js/plugins/plugins.d.ts
-
110resources/js/services/ApiService.ts
-
10resources/views/pages/home.blade.php
-
10routes/api.php
-
15routes/web.php
-
7webpack.mix.js
-
10261yarn.lock
@ -0,0 +1,31 @@ |
|||
{ |
|||
// Use IntelliSense to learn about possible attributes. |
|||
// Hover to view descriptions of existing attributes. |
|||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 |
|||
"version": "0.2.0", |
|||
"configurations": [ |
|||
|
|||
{ |
|||
"name": "Listen for Xdebug", |
|||
"type": "php", |
|||
"request": "launch", |
|||
"port": 9003, |
|||
"stopOnEntry": false, |
|||
"log": true, |
|||
"pathMappings": { |
|||
"/home/vagrant/sandd-core": "/mnt/Multimedia/Projects/Web/sandd/searchanddisplace-core", |
|||
} |
|||
}, |
|||
{ |
|||
"name": "Launch currently open script", |
|||
"type": "php", |
|||
"request": "launch", |
|||
"program": "${file}", |
|||
"cwd": "${fileDirname}", |
|||
"port": 9003, |
|||
"pathMappings": { |
|||
"/home/vagrant/sandd-core": "/mnt/Multimedia/Projects/Web/sandd/searchanddisplace-core", |
|||
} |
|||
} |
|||
] |
|||
} |
@ -1,24 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers; |
|||
|
|||
use Illuminate\View\View; |
|||
use Illuminate\Contracts\View\Factory; |
|||
use Illuminate\Contracts\Container\BindingResolutionException; |
|||
|
|||
class PagesController extends Controller |
|||
{ |
|||
|
|||
/** |
|||
* Return the home page view |
|||
* |
|||
* @return View|Factory |
|||
* |
|||
* @throws BindingResolutionException |
|||
*/ |
|||
public function home(): View |
|||
{ |
|||
$filters = FilterController::$filters; |
|||
return view('pages.home')->with('filters', $filters); |
|||
} |
|||
} |
26600
public/js/app.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -1,100 +0,0 @@ |
|||
<template> |
|||
<div class="wrap" v-if="!fileUploaded && !uploading"> |
|||
<Toast position="top-right" /> |
|||
|
|||
<Panel header="Please upload a file"> |
|||
<FileUpload |
|||
name="demo[]" |
|||
:customUpload="true" |
|||
:auto="true" |
|||
@uploader="uploadFile" |
|||
> |
|||
<template #empty> |
|||
<p>Drag and drop files to here to upload.</p> |
|||
</template> |
|||
</FileUpload> |
|||
</Panel> |
|||
|
|||
<BlockUI :blocked="uiBlocked" :fullScreen="true"></BlockUI> |
|||
</div> |
|||
<div class="wrap" v-else-if="!fileUploaded && uploading"> |
|||
<Skeleton /> |
|||
<Skeleton /> |
|||
<Skeleton /> |
|||
<Skeleton /> |
|||
<Skeleton /> |
|||
<Skeleton /> |
|||
<Skeleton /> |
|||
</div> |
|||
<div class="wrap" v-else> |
|||
<process-file :file="uploadResult" :filters="filters"></process-file> |
|||
</div> |
|||
</template> |
|||
|
|||
<script lang="ts"> |
|||
import axios from 'axios'; |
|||
// import Vue from 'vue'; |
|||
// import Component from 'vue-class-component'; |
|||
import { Vue, Component, Prop } from 'vue-property-decorator'; |
|||
|
|||
@Component |
|||
export default class Home extends Vue { |
|||
|
|||
@Prop({ default: [] }) |
|||
public readonly filters!: Array<object> |
|||
|
|||
public uiBlocked = false; |
|||
public uploading = false; |
|||
public fileUploaded: boolean = false; |
|||
public uploadResult = null; |
|||
|
|||
/** |
|||
* |
|||
*/ |
|||
public created() |
|||
{ |
|||
console.log(this.filters); |
|||
} |
|||
|
|||
/** |
|||
* A method which uploads the files to the server for processing |
|||
* |
|||
* @param event The event containing the uploaded files information |
|||
*/ |
|||
public uploadFile(event: any): void { |
|||
this.uploading = true; |
|||
this.fileUploaded = false; |
|||
|
|||
|
|||
this['$toast'].add({severity:'success', summary: 'Success Message', detail:'Order submitted', life: 3000}); |
|||
let file = event.files[0]; |
|||
|
|||
let formData = new FormData(); |
|||
formData.append('file', file); |
|||
|
|||
setTimeout( |
|||
() => { |
|||
axios.post( |
|||
'http://core.sandd/api/file', |
|||
formData, |
|||
{ |
|||
headers: { |
|||
'Content-Type': 'multipart/form-data' |
|||
} |
|||
} |
|||
).then( |
|||
(response) => { |
|||
this.fileUploaded = true; |
|||
this.uploadResult = response.data; |
|||
// console.log('Success: ', response); |
|||
} |
|||
).catch( |
|||
(err) => { |
|||
console.log('Error: ', err); |
|||
} |
|||
); |
|||
}, 500 |
|||
) |
|||
} |
|||
} |
|||
</script> |
@ -0,0 +1,56 @@ |
|||
import { Vue, Component, Prop } from 'vue-property-decorator'; |
|||
import FileUploadResponse from '@interfaces/responses/FileUploadResponse'; |
|||
|
|||
@Component |
|||
export default class Home extends Vue { |
|||
|
|||
@Prop({ default: [] }) |
|||
public readonly searchers!: { [key: string]: string; } |
|||
|
|||
public uiBlocked = false; |
|||
public uploading = false; |
|||
public fileUploaded: boolean = false; |
|||
public uploadResult: FileUploadResponse = { |
|||
id: '', |
|||
file: '', |
|||
path: '' |
|||
}; |
|||
|
|||
/** |
|||
* |
|||
*/ |
|||
public created() |
|||
{} |
|||
|
|||
/** |
|||
* 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> { |
|||
this.uploading = true; |
|||
this.fileUploaded = false; |
|||
|
|||
|
|||
this.$toast.add({severity:'info', summary: 'Uploading...', detail:'Uploading your file...', life: 3000}); |
|||
|
|||
try { |
|||
let file = event.files[0]; |
|||
|
|||
let response = await this.$api.uploadFile(file); |
|||
this.fileUploaded = true; |
|||
this.uploadResult = response; |
|||
} catch (err) { |
|||
console.log('Error uploading file: ', err); |
|||
this.$toast.add({ |
|||
severity: 'error', |
|||
summary: 'Error!', |
|||
detail: 'There was an error uploading your file. Please try again later', |
|||
life: 3000 |
|||
}); |
|||
|
|||
this.uploading = false; |
|||
this.fileUploaded = false; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,35 @@ |
|||
<template> |
|||
<div class="wrap" v-if="!fileUploaded && !uploading"> |
|||
<Toast /> |
|||
|
|||
<Panel header="Please upload a file"> |
|||
<FileUpload |
|||
name="demo[]" |
|||
:customUpload="true" |
|||
:auto="true" |
|||
@uploader="uploadFile" |
|||
> |
|||
<template #empty> |
|||
<p>Drag and drop files to here to upload.</p> |
|||
</template> |
|||
</FileUpload> |
|||
</Panel> |
|||
|
|||
<BlockUI :blocked="uiBlocked" :fullScreen="true"></BlockUI> |
|||
</div> |
|||
<div class="wrap" v-else-if="!fileUploaded && uploading"> |
|||
<Skeleton /><br /> |
|||
<Skeleton /><br /> |
|||
<Skeleton /><br /> |
|||
<Skeleton /><br /> |
|||
<Skeleton /><br /> |
|||
<Skeleton /><br /> |
|||
<Skeleton /><br /> |
|||
</div> |
|||
<div class="wrap" v-else> |
|||
<process-file :file="uploadResult" :searchers="searchers"></process-file> |
|||
</div> |
|||
</template> |
|||
|
|||
<script lang="ts" src="./Home.ts"> |
|||
</script> |
@ -1,82 +0,0 @@ |
|||
<template> |
|||
<div class="p-d-flex p-flex-row p-jc-between p-ai-stretch"> |
|||
<Panel |
|||
class="p-mr-2 p-as-stretch file-card" |
|||
> |
|||
<template #header> |
|||
File preview |
|||
</template> |
|||
|
|||
<Skeleton /><br /> |
|||
<Skeleton /><br /> |
|||
<Skeleton /><br /> |
|||
<Skeleton /><br /> |
|||
<Skeleton /><br /> |
|||
<Skeleton /><br /> |
|||
<Skeleton /><br /> |
|||
</Panel> |
|||
|
|||
<Card class="p-mr-2 p-as-stretch filters-card"> |
|||
<template #header> |
|||
<Toolbar> |
|||
<template #left> |
|||
<h3>Available filters</h3> |
|||
</template> |
|||
<template #right> |
|||
<Button |
|||
icon="pi pi-plus" |
|||
class="p-button-success" |
|||
/> |
|||
</template> |
|||
</Toolbar> |
|||
</template> |
|||
|
|||
<template #content> |
|||
<filter-view |
|||
v-for="(filter, id, index) in filters" |
|||
:key="index" |
|||
:id="id" |
|||
:display-name="filter.display_name" |
|||
:options="filter.options" |
|||
></filter-view> |
|||
</template> |
|||
</Card> |
|||
</div> |
|||
</template> |
|||
|
|||
<script lang="ts"> |
|||
import { Vue, Component, Prop } from 'vue-property-decorator'; |
|||
import { FileData } from '../interfaces/FileData'; |
|||
import { FilterInterface } from '../interfaces/FilterInterface'; |
|||
|
|||
@Component |
|||
export default class ProcessFile extends Vue { |
|||
|
|||
@Prop({ default: null }) |
|||
public readonly file!: FileData|null; |
|||
|
|||
@Prop({ default: [] }) |
|||
public readonly filters!: { [keys:string]: FilterInterface } |
|||
|
|||
private selectedFile: File|null = null; |
|||
private selectedFilters = []; |
|||
|
|||
/** |
|||
* |
|||
*/ |
|||
created() { |
|||
console.log('FILE: ', this.file); |
|||
console.log('FILTERS: ', this.filters); |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.file-card { |
|||
flex: 0 1 66%; |
|||
} |
|||
|
|||
.filters-card { |
|||
flex: 0 1 32% |
|||
} |
|||
</style> |
@ -0,0 +1,54 @@ |
|||
.file-card { |
|||
flex: 0 1 49%; |
|||
} |
|||
|
|||
// .filters-card { |
|||
// flex: 0 1 24% |
|||
// } |
|||
.p-overlaypanel { |
|||
// width: 450px; |
|||
min-width: 450px; |
|||
} |
|||
|
|||
.p-toolbar-group-right { |
|||
button { |
|||
margin-left: 10px; |
|||
} |
|||
} |
|||
|
|||
.p-sidebar-content { |
|||
.p-toolbar { |
|||
margin-top: 32px; |
|||
} |
|||
} |
|||
|
|||
.p-card-header { |
|||
.p-toolbar { |
|||
border: unset; |
|||
border-radius: unset; |
|||
border-bottom: 1px solid #dee2e6; |
|||
padding: .5rem; |
|||
} |
|||
} |
|||
|
|||
button.add-searchers { |
|||
height: 100%; |
|||
} |
|||
|
|||
.p-col.sidebar-title { |
|||
display: flex; |
|||
align-content: flex-start; |
|||
justify-content: flex-start; |
|||
} |
|||
|
|||
.p-grid.sidebar-title-container { |
|||
padding-top: 50px; |
|||
} |
|||
|
|||
.md-viewer { |
|||
text-align: start; |
|||
|
|||
h1, h2, h3, h4, h5 { |
|||
font-size: initial; |
|||
} |
|||
} |
@ -0,0 +1,160 @@ |
|||
import axios from 'axios'; |
|||
// import OverlayPanel from 'primevue/overlaypanel/OverlayPanel';
|
|||
import { Vue, Component, Prop, Watch } from 'vue-property-decorator'; |
|||
import { FileData } from '@/interfaces/FileData'; |
|||
|
|||
@Component |
|||
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!: { [keys: string]: string; } |
|||
|
|||
|
|||
/** |
|||
* 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 = ''; |
|||
|
|||
// 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; |
|||
|
|||
// The list of filters/searchers in a format usable by the datatable
|
|||
private searchersData: Array<{ id: string; name: string; }> = []; |
|||
|
|||
// The list of filters applied to the selected searchers
|
|||
private searchersFilters: any = []; |
|||
|
|||
// The list of selected filters/searchers
|
|||
private selectedSearchers: Array<{ id: string; name: string; }> = []; |
|||
|
|||
//The list of expanded rows in the selected filters/searchers table
|
|||
private expandedRows: Array<{id: string; name: string; }> = []; |
|||
|
|||
// The list of options applied to the searchers (for the moment, only replace_with)
|
|||
private searchersOptions: { [key: string]: string } = {}; |
|||
|
|||
/** |
|||
* |
|||
*/ |
|||
created() { |
|||
for(let index in this.searchers) { |
|||
let searcherData = { |
|||
id: index, |
|||
name: this.searchers[index] |
|||
}; |
|||
|
|||
this.searchersData.push(searcherData); |
|||
} |
|||
|
|||
|
|||
this.intervalId = setInterval(this.waitForFile, 3000); |
|||
} |
|||
|
|||
/** |
|||
* 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; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Wait for the file to be processed in ingest |
|||
*/ |
|||
private async waitForFile() { |
|||
|
|||
const response = await this.$api.getFileData(this.file.id); |
|||
|
|||
if (response.content !== null) { |
|||
|
|||
if (response.ingest_status === 'fail') { |
|||
this.$toast.add({ |
|||
severity: 'error', |
|||
summary: 'File error', |
|||
detail: 'THere was an error processing the file in ingest', |
|||
life: 3000 |
|||
}); |
|||
} else { |
|||
this.fileContent = response.content; |
|||
|
|||
this.$toast.add({ |
|||
severity:'success', |
|||
summary: 'File loaded', |
|||
detail: 'The file has been processed by ingest.', |
|||
life: 3000 |
|||
}); |
|||
|
|||
clearInterval(this.intervalId); |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
private onSelectedSearchersReorder($event: any) |
|||
{ |
|||
this.selectedSearchers = $event.value; |
|||
} |
|||
|
|||
/** |
|||
* Run the searchers |
|||
*/ |
|||
private async runSearchers() { |
|||
this.processing = true; |
|||
this.processedFileContent = ''; |
|||
|
|||
let searchers: Array<{ key: string; replace_with: string; }> = []; |
|||
this.selectedSearchers.forEach( (searcher) => { |
|||
searchers.push({ |
|||
'key': searcher.id, |
|||
'replace_with': this.searchersOptions[searcher.id] || '' |
|||
}); |
|||
}); |
|||
|
|||
const response = await this.$api.filterDocument(this.fileContent, searchers); |
|||
|
|||
console.log(response); |
|||
|
|||
this.processedFileContent = response.content; |
|||
this.processing = false; |
|||
} |
|||
|
|||
private async downloadOdt() |
|||
{ |
|||
let response = await this.$api.convertFile(this.processedFileContent); |
|||
window.open('http://core.sandd/file/download/' + response.path); |
|||
} |
|||
} |
@ -0,0 +1,188 @@ |
|||
<template> |
|||
|
|||
<div class="p-d-flex p-flex-row p-jc-between p-ai-stretch"> |
|||
<!-- <Toast /> --> |
|||
<Card |
|||
class="p-mr-2 p-as-stretch file-card" |
|||
> |
|||
<template #header> |
|||
<Toolbar> |
|||
<template #left> |
|||
<h3>Original document content</h3> |
|||
</template> |
|||
</Toolbar> |
|||
</template> |
|||
|
|||
<template #content> |
|||
<div class="md-viewer"> |
|||
<template v-if="fileContent === ''"> |
|||
<Skeleton /><br /> |
|||
<Skeleton /><br /> |
|||
<Skeleton /><br /> |
|||
<Skeleton /><br /> |
|||
<Skeleton /><br /> |
|||
<Skeleton /><br /> |
|||
<Skeleton /><br /> |
|||
</template> |
|||
<template v-else> |
|||
<vue-markdown :source="fileContent" /> |
|||
</template> |
|||
</div> |
|||
</template> |
|||
|
|||
</Card> |
|||
|
|||
<Card |
|||
class="p-mr-2 p-as-stretch file-card" |
|||
> |
|||
|
|||
<template #header> |
|||
<Toolbar> |
|||
<template #left> |
|||
<h3>Processed document content</h3> |
|||
</template> |
|||
|
|||
<template #right> |
|||
<Button |
|||
label="Download document" |
|||
icon="pi pi-download" |
|||
class="p-button-secondary p-button-outlined p-button-sm" |
|||
@click="downloadOdt()"/> |
|||
|
|||
<Button |
|||
label="Run filters" |
|||
icon="pi pi-play" |
|||
class="p-button-success p-button-outlined p-button-sm" |
|||
:disabled="fileContent == ''" |
|||
@click="runSearchers()"/> |
|||
|
|||
<Button |
|||
label="Toggle filters list" |
|||
icon="pi pi-list" |
|||
class="p-button-info p-button-outlined p-button-sm" |
|||
@click="toggleSearchersSidebar()"/> |
|||
</template> |
|||
</Toolbar> |
|||
</template> |
|||
|
|||
<template #content> |
|||
<div class="md-viewer"> |
|||
<template v-if="processing === true"> |
|||
<Skeleton /><br /> |
|||
<Skeleton /><br /> |
|||
<Skeleton /><br /> |
|||
<Skeleton /><br /> |
|||
<Skeleton /><br /> |
|||
<Skeleton /><br /> |
|||
<Skeleton /><br /> |
|||
</template> |
|||
<template v-else-if="processedFileContent !== ''"> |
|||
<vue-markdown :source="processedFileContent" /> |
|||
</template> |
|||
<template v-else> |
|||
<Message severity="info" :closable="false"> |
|||
Not processed yet. Please select and run some filters to see the result. |
|||
</Message> |
|||
</template> |
|||
</div> |
|||
</template> |
|||
|
|||
</Card> |
|||
|
|||
|
|||
<Sidebar |
|||
:visible.sync="searchersSidebarVisible" |
|||
:showCloseIcon="true" |
|||
class="p-sidebar-md" |
|||
position="right"> |
|||
|
|||
<div class="p-grid p-jc-between sidebar-title-container"> |
|||
<div class="p-col sidebar-title"> |
|||
<h3>Document searchers</h3> |
|||
</div> |
|||
|
|||
<div class="p-col-2"> |
|||
<Button |
|||
icon="pi pi-plus" |
|||
class="p-button-success p-button-sm p-button-text add-searchers" |
|||
@click="toggleSearchersDialog(true)" |
|||
aria:haspopup="true" |
|||
aria-controls="searcers_dialog" /> |
|||
</div> |
|||
</div> |
|||
|
|||
<Dialog |
|||
header="Available document searchers" |
|||
:visible.sync="searchersDialogVisible" |
|||
:maximizable="true" |
|||
:style="{width: '50vw'}" |
|||
:contentStyle="{overflow: 'visible'}" |
|||
id="searcers_dialog" |
|||
ref="searcers-dialog"> |
|||
|
|||
<DataTable |
|||
:value.sync="searchersData" |
|||
:selection.sync="selectedSearchers" |
|||
dataKey="id" |
|||
selectionMode="multiple" |
|||
class="p-datatable-sm" |
|||
:metaKeySelection="false" |
|||
:paginator="true" :rows="10" |
|||
paginatorTemplate="CurrentPageReport FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown" |
|||
currentPageReportTemplate="Showing {first} to {last} of {totalRecords}" |
|||
:rowsPerPageOptions="[10,20,50]"> |
|||
|
|||
<Column selectionMode="multiple" headerStyle="width: 3em"></Column> |
|||
<Column field="name" header="Name" sortable></Column> |
|||
|
|||
</DataTable> |
|||
|
|||
<template #footer> |
|||
<Button |
|||
label="Done" |
|||
icon="pi pi-check" |
|||
class="p-button-info p-button-outlined p-button-sm" |
|||
@click="toggleSearchersDialog(false)"/> |
|||
</template> |
|||
|
|||
</Dialog> |
|||
|
|||
<DataTable |
|||
:value.sync="selectedSearchers" |
|||
dataKey="id" |
|||
class="p-datatable-sm" |
|||
:expandedRows.sync="expandedRows" |
|||
@row-reorder="onSelectedSearchersReorder"> |
|||
|
|||
<Column :rowReorder="true" headerStyle="width: 3rem" /> |
|||
<Column field="name" header="Name"> |
|||
</Column> |
|||
<Column :expander="true" headerStyle="width: 3rem" /> |
|||
|
|||
<template #expansion="slotProps"> |
|||
<div class="options-subtable"> |
|||
|
|||
<div class="p-fluid"> |
|||
<div class="p-field"> |
|||
<label :for="`replace_with__${slotProps.data.id}`"> |
|||
Replace values with: |
|||
</label> |
|||
|
|||
<InputText |
|||
:id="`replace_with__${slotProps.data.id}`" |
|||
type="text" |
|||
class="p-inputtext-sm" |
|||
v-model="searchersOptions[slotProps.data.id]"/> |
|||
</div> |
|||
</div> |
|||
|
|||
</div> |
|||
</template> |
|||
|
|||
</DataTable> |
|||
</Sidebar> |
|||
</div> |
|||
</template> |
|||
|
|||
<script lang="ts" src="./ProcessFile.ts"></script> |
|||
<style lang="scss" src="./ProcessFile.scss"></style> |
@ -1,69 +0,0 @@ |
|||
<template> |
|||
<Fieldset :legend="displayName" :toggleable="true" class="filter-container"> |
|||
|
|||
<div v-for="option of optionsList" :key="option.name" class="filter-option"> |
|||
<h5>{{ option.name }}</h5> |
|||
|
|||
<SelectButton |
|||
:key="option.name" |
|||
v-model="selectedOption" |
|||
:options="option.list" |
|||
optionLabel="name" /> |
|||
</div> |
|||
</Fieldset> |
|||
</template> |
|||
|
|||
<script lang="ts"> |
|||
|
|||
import { Vue, Component, Prop } from 'vue-property-decorator'; |
|||
import { FilterOptions } from '../../interfaces/FilterOptions'; |
|||
|
|||
@Component |
|||
export default class Filter extends Vue { |
|||
|
|||
@Prop(String) |
|||
public readonly id!: string; |
|||
|
|||
@Prop(String) |
|||
public readonly displayName!: string; |
|||
|
|||
@Prop({ default: [] }) |
|||
public readonly options!: FilterOptions; |
|||
|
|||
private optionsList = new Array; |
|||
|
|||
public selectedOption = null; |
|||
|
|||
public created() |
|||
{ |
|||
for (let index in this.options) { |
|||
let words = index.split('_'); |
|||
for (let i = 0; i < words.length; i++) { |
|||
words[i] = words[i].charAt(0).toUpperCase() + words[i].substr(1); |
|||
} |
|||
|
|||
|
|||
|
|||
let option = { |
|||
name: words.join(' '), |
|||
list: new Array |
|||
}; |
|||
this.options[index].forEach( opt => { |
|||
option.list.push({ |
|||
'name': opt, |
|||
'value': opt |
|||
}); |
|||
}); |
|||
|
|||
this.optionsList.push(option); |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.filter-container, |
|||
.filter-option { |
|||
margin-bottom: 10px; |
|||
} |
|||
</style> |
@ -1,7 +0,0 @@ |
|||
import { FilterOptions } from "./FilterOptions"; |
|||
|
|||
export interface FilterInterface |
|||
{ |
|||
display_name: string; |
|||
options: FilterOptions; |
|||
} |
@ -1,4 +0,0 @@ |
|||
export interface FilterOptions |
|||
{ |
|||
[keys: string]: string[] |
|||
} |
@ -0,0 +1,4 @@ |
|||
export default interface ErrorResponse |
|||
{ |
|||
message: string; |
|||
} |
@ -0,0 +1,5 @@ |
|||
export default interface FileStatusResponse |
|||
{ |
|||
content: string; |
|||
ingest_status: string; |
|||
} |
@ -0,0 +1,6 @@ |
|||
export default interface FileUploadResponse |
|||
{ |
|||
id: string; |
|||
file: string; |
|||
path: string; |
|||
} |
@ -0,0 +1,18 @@ |
|||
import _Vue, { PluginFunction } from 'vue'; |
|||
import ApiService from '@/services/ApiService'; |
|||
|
|||
const ApiPlugin = { |
|||
|
|||
install: (Vue: typeof _Vue, options?: any) => { |
|||
|
|||
let apiService = new ApiService(); |
|||
|
|||
Vue.mixin({ |
|||
created() { |
|||
Vue.prototype.$api = apiService; |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
|
|||
export default ApiPlugin; |
@ -0,0 +1,7 @@ |
|||
import ApiService from '@/services/ApiService'; |
|||
|
|||
declare module 'vue/types/vue' { |
|||
interface Vue { |
|||
$api: ApiService; |
|||
} |
|||
} |
@ -0,0 +1,110 @@ |
|||
import axios from 'axios'; |
|||
import FileUploadResponse from '@interfaces/responses/FileUploadResponse'; |
|||
import FileStatusResponse from '@interfaces/responses/FileStatusResponse'; |
|||
|
|||
|
|||
export default class ApiService { |
|||
/** @type {string} */ |
|||
private readonly baseUrl: string = 'http://core.sandd'; |
|||
|
|||
/** @type { [key:string] : string; } */ |
|||
private readonly apiRoutes = { |
|||
file: this.baseUrl + '/api/file', |
|||
fileDownload: this.baseUrl + '/api/file/convert', |
|||
searchAndDisplace: this.baseUrl + '/search-and-displace' |
|||
}; |
|||
|
|||
constructor() {} |
|||
|
|||
/** |
|||
* Upload a file to the server and return its response. |
|||
* Throws an error if the response wasn't successful |
|||
* |
|||
* TODO: Annotate the return type correctly |
|||
* |
|||
* @param {File} file The file we want to upload |
|||
* |
|||
* @returns {Promise<FileUploadResponse>} The response from the server |
|||
*/ |
|||
public async uploadFile(file: File): Promise<FileUploadResponse> |
|||
{ |
|||
let formData = new FormData(); |
|||
formData.append('file', file); |
|||
|
|||
try { |
|||
let response = await axios.post<FileUploadResponse>( |
|||
this.apiRoutes.file, |
|||
formData, |
|||
{ |
|||
headers: { |
|||
'Content-Type': 'multipart/form-data' |
|||
} |
|||
} |
|||
) |
|||
return response.data; |
|||
} catch (err) { |
|||
throw err; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Get data for a file from the server. |
|||
* Throws an error if the response wasn't successful |
|||
* |
|||
* @param {string} fileId The id of the file we want to query |
|||
* |
|||
* @returns {Promise<FileStatusResponse>} The response from the server |
|||
* |
|||
* @throws |
|||
*/ |
|||
public async getFileData(fileId: string): Promise<FileStatusResponse> |
|||
{ |
|||
try { |
|||
let response = await axios.get<FileStatusResponse>(this.apiRoutes.searchAndDisplace + `/${fileId}`); |
|||
|
|||
return response.data; |
|||
} catch (err) { |
|||
throw err; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Perform a search and displace operation on a document |
|||
* |
|||
* @param {string} content The content of the document |
|||
* @param {Array} searchers The list of searchers and their replace values |
|||
*/ |
|||
public async filterDocument(content: string, searchers: Array<{ key: string; replace_with: string; }>) |
|||
{ |
|||
try { |
|||
let response = await axios.post( |
|||
this.apiRoutes.searchAndDisplace, |
|||
{ |
|||
'content': content, |
|||
'searchers': searchers |
|||
} |
|||
); |
|||
|
|||
return response.data; |
|||
} catch (err) { |
|||
throw err; |
|||
} |
|||
} |
|||
|
|||
|
|||
public async convertFile(content:string) |
|||
{ |
|||
try { |
|||
let response = await axios.post( |
|||
this.apiRoutes.fileDownload, |
|||
{ |
|||
'content': content |
|||
} |
|||
); |
|||
|
|||
return response.data; |
|||
} catch (err) { |
|||
throw err; |
|||
} |
|||
} |
|||
} |
@ -1,5 +1,13 @@ |
|||
@extends('app') |
|||
|
|||
@section('content') |
|||
<home :filters="{{ json_encode($filters) }}"></home> |
|||
<div class="page-wrapper"> |
|||
<app-header></app-header> |
|||
|
|||
<div class="content"> |
|||
<home :searchers="{{ json_encode($searchers) }}"></home> |
|||
</div> |
|||
</div> |
|||
|
|||
<app-footer></app-footer> |
|||
@endsection |
10261
yarn.lock
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
Write
Preview
Loading…
Cancel
Save
Reference in new issue