Browse Source

Minor updates

master
Radu Liviu Carjan 3 years ago
parent
commit
54b460b6f0
  1. 31
      .vscode/launch.json
  2. 152
      app/Http/Controllers/FileController.php
  3. 4
      app/Http/Controllers/SearchAndDisplaceController.php
  4. 4184
      public/css/app.css
  5. 325
      public/js/app.js
  6. 38
      resources/js/components/Home/Home.ts
  7. 16
      resources/js/components/Home/Home.vue
  8. 2
      resources/js/components/ProcessFile/ProcessFile.scss
  9. 43
      resources/js/components/ProcessFile/ProcessFile.ts
  10. 38
      resources/js/components/ProcessFile/ProcessFile.vue
  11. 4
      resources/js/interfaces/responses/ErrorResponse.ts
  12. 5
      resources/js/interfaces/responses/FileStatusResponse.ts
  13. 6
      resources/js/interfaces/responses/FileUploadResponse.ts
  14. 60
      resources/js/services/ApiService.ts
  15. 3
      resources/sass/app.scss
  16. 7
      routes/api.php
  17. 71
      yarn.lock

31
.vscode/launch.json

@ -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",
}
}
]
}

152
app/Http/Controllers/FileController.php

@ -7,6 +7,7 @@ use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Storage;
use Illuminate\Http\UploadedFile;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\BadResponseException;
class FileController extends Controller
{
@ -19,130 +20,41 @@ class FileController extends Controller
*/
public function create(): JsonResponse
{
/** @var UploadedFile $file */
$file = request()->file('file');
$time = time();
$fileName = $time . '-' . $file->getClientOriginalName();
$fileId = $time . pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
$path = Storage::putFileAs('files/', $file, $fileName);
$guzzle = new Client();
/** @var \Psr\Http\Message\ResponseInterface $response */
$response = $guzzle->request('POST', 'http://ingest.sandd/ingest', [
'multipart' => [
[
'name' => 'body',
'contents' => json_encode([ 'id' => $fileId ]),
'headers' => [ 'Content-Type' => 'application/json' ]
],
[
'name' => 'document',
'contents' => fopen($file->getPathname(), 'r')
try {
/** @var UploadedFile $file */
$file = request()->file('file');
$time = time();
$fileName = $time . '-' . $file->getClientOriginalName();
$fileId = $time . pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
$path = Storage::putFileAs('files/', $file, $fileName);
$guzzle = new Client();
/** @var \Psr\Http\Message\ResponseInterface $response */
$response = $guzzle->request('POST', 'http://ingest.sandd/ingest', [
'multipart' => [
[
'name' => 'id',
'contents' => $fileId,
'headers' => [ 'Content-Type' => 'application/json' ]
],
[
'name' => 'document',
'contents' => fopen($file->getPathname(), 'r')
]
]
]
]);
return response()->json([
'file' => $file->getClientOriginalName(),
'id' => $fileId,
'path' => $path,
]);
}
/**
*
* @param string $id
*
* @return JsonResponse
*
* @throws BindingResolutionException
*/
public function get(string $id): JsonResponse
{
$success = rand(0, 1);
if ($success == 1) {
$text = <<<EOT
## About Laravel
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
- [Simple, fast routing engine](https://laravel.com/docs/routing).
- [Powerful dependency injection container](https://laravel.com/docs/container).
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
- [Robust background job processing](https://laravel.com/docs/queues).
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
Laravel is accessible, powerful, and provides tools required for large, robust applications.
## Learning Laravel
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework.
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains over 1500 video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
## Laravel Sponsors
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the Laravel [Patreon page](https://patreon.com/taylorotwell).
- **[Vehikl](https://vehikl.com/)**
- **[Tighten Co.](https://tighten.co)**
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
- **[64 Robots](https://64robots.com)**
- **[Cubet Techno Labs](https://cubettech.com)**
- **[Cyber-Duck](https://cyber-duck.co.uk)**
- **[British Software Development](https://www.britishsoftware.co)**
- **[Webdock, Fast VPS Hosting](https://www.webdock.io/en)**
- **[DevSquad](https://devsquad.com)**
- [UserInsights](https://userinsights.com)
- [Fragrantica](https://www.fragrantica.com)
- [SOFTonSOFA](https://softonsofa.com/)
- [User10](https://user10.com)
- [Soumettre.fr](https://soumettre.fr/)
- [CodeBrisk](https://codebrisk.com)
- [1Forge](https://1forge.com)
- [TECPRESSO](https://tecpresso.co.jp/)
- [Runtime Converter](http://runtimeconverter.com/)
- [WebL'Agence](https://weblagence.com/)
- [Invoice Ninja](https://www.invoiceninja.com)
- [iMi digital](https://www.imi-digital.de/)
- [Earthlink](https://www.earthlink.ro/)
- [Steadfast Collective](https://steadfastcollective.com/)
- [We Are The Robots Inc.](https://watr.mx/)
- [Understand.io](https://www.understand.io/)
- [Abdel Elrafa](https://abdelelrafa.com)
- [Hyper Host](https://hyper.host)
- [Appoly](https://www.appoly.co.uk)
- [OP.GG](https://op.gg)
## Contributing
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
## Code of Conduct
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
## Security Vulnerabilities
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
## License
]);
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
EOT;
return response()->json([
'text' => $text,
'ready' => true
'file' => $file->getClientOriginalName(),
'id' => $fileId,
'path' => $path,
]);
} catch (BadResponseException $e) {
return response()->json([
'message' => $e->getMessage(),
'response' => $e->getResponse()
], 400);
}
return response()->json([
'ready' => false
]);
}
}

4
app/Http/Controllers/SearchAndDisplaceController.php

@ -32,8 +32,8 @@ class SearchAndDisplaceController extends Controller
'searchers' => 'required|array', // Check if matches all rules, must have 'key' and 'replace_with'.
]);
$searchAndDisplace = new SearchAndDisplace(request()->get('content'), [
'searchers' => request()->get('searchers'),
$searchAndDisplace = new SearchAndDisplace(request()->input('content'), [
'searchers' => request()->input('searchers'),
]);
try {

4184
public/css/app.css
File diff suppressed because it is too large
View File

325
public/js/app.js

@ -2187,7 +2187,11 @@ var Home = /*#__PURE__*/function (_Vue) {
_this.uiBlocked = false;
_this.uploading = false;
_this.fileUploaded = false;
_this.uploadResult = null;
_this.uploadResult = {
id: '',
file: '',
path: ''
};
return _this;
}
/**
@ -2206,40 +2210,60 @@ var Home = /*#__PURE__*/function (_Vue) {
}, {
key: "uploadFile",
value: function uploadFile(event) {
var _this2 = this;
this.uploading = true;
this.fileUploaded = false;
this.$toast.add({
severity: 'success',
summary: 'Success Message',
detail: 'Order submitted',
life: 3000
});
var file = event.files[0];
setTimeout( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_0___default().mark(function _callee() {
var response;
value: function () {
var _uploadFile = _asyncToGenerator( /*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_0___default().mark(function _callee(event) {
var file, response;
return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_0___default().wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return _this2.$api.uploadFile(file);
this.uploading = true;
this.fileUploaded = false;
this.$toast.add({
severity: 'info',
summary: 'Uploading...',
detail: 'Uploading your file...',
life: 3000
});
_context.prev = 3;
file = event.files[0];
_context.next = 7;
return this.$api.uploadFile(file);
case 2:
case 7:
response = _context.sent;
_this2.fileUploaded = true;
_this2.uploadResult = response;
this.fileUploaded = true;
this.uploadResult = response;
_context.next = 18;
break;
case 5:
case 12:
_context.prev = 12;
_context.t0 = _context["catch"](3);
console.log('Error uploading file: ', _context.t0);
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;
case 18:
case "end":
return _context.stop();
}
}
}, _callee);
})), 500);
}
}, _callee, this, [[3, 12]]);
}));
function uploadFile(_x) {
return _uploadFile.apply(this, arguments);
}
return uploadFile;
}()
}]);
return Home;
@ -2320,6 +2344,7 @@ var ProcessFile = /*#__PURE__*/function (_Vue) {
_this.selectedSearchers = []; //The list of expanded rows in the selected filters/searchers table
_this.expandedRows = [];
_this.searchersOptions = {};
return _this;
}
/**
@ -2341,7 +2366,7 @@ var ProcessFile = /*#__PURE__*/function (_Vue) {
this.intervalId = setInterval(this.waitForFile, 3000);
}
/**
*
* Wait for the file to be processed in ingest
*/
}, {
@ -2359,7 +2384,7 @@ var ProcessFile = /*#__PURE__*/function (_Vue) {
case 2:
response = _context.sent;
if (response.text !== null && response.ready === true) {
if (response.content !== null) {
if (response.ingest_status === 'fail') {
this.$toast.add({
severity: 'error',
@ -2368,7 +2393,7 @@ var ProcessFile = /*#__PURE__*/function (_Vue) {
life: 3000
});
} else {
this.fileContent = response.documentContent;
this.fileContent = response.content;
this.$toast.add({
severity: 'success',
summary: 'File loaded',
@ -2377,8 +2402,6 @@ var ProcessFile = /*#__PURE__*/function (_Vue) {
});
clearInterval(this.intervalId);
}
} else {
console.log('FILE NOT READY YET!');
}
case 4:
@ -2395,34 +2418,66 @@ var ProcessFile = /*#__PURE__*/function (_Vue) {
return waitForFile;
}()
/**
* Toggle the menu containing the list of available searchers
*
* @param $event
*/
}, {
key: "toggleSearchersMenu",
value: function toggleSearchersMenu($event) {
this.$refs['searchers-overlay'].toggle($event);
}
}, {
key: "onRowSelect",
value: function onRowSelect($event) {
console.log('SELECT: ', $event);
console.log(this.selectedSearchers);
}
}, {
key: "onRowUnselect",
value: function onRowUnselect($event) {
console.log('UNSELECT: ', $event);
console.log(this.selectedSearchers);
}
}, {
key: "onSelectedSearchersReorder",
value: function onSelectedSearchersReorder($event) {
this.selectedSearchers = $event.value;
}
/**
* Run the filters
*/
}, {
key: "onSelectedSearcherExpand",
value: function onSelectedSearcherExpand($event) {}
}, {
key: "onSelectedSearcherCollapse",
value: function onSelectedSearcherCollapse($event) {}
key: "runFilters",
value: function () {
var _runFilters = _asyncToGenerator( /*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_0___default().mark(function _callee2() {
var _this2 = this;
var searchers, response;
return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_0___default().wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
searchers = [];
this.selectedSearchers.forEach(function (searcher) {
searchers.push({
'key': searcher.id,
'replace_with': _this2.searchersOptions[searcher.id] || ''
});
});
_context2.next = 4;
return this.$api.filterDocument(this.fileContent, searchers);
case 4:
response = _context2.sent;
console.log(response);
this.fileContent = response.content;
case 7:
case "end":
return _context2.stop();
}
}
}, _callee2, this);
}));
function runFilters() {
return _runFilters.apply(this, arguments);
}
return runFilters;
}()
}]);
return ProcessFile;
@ -2505,18 +2560,24 @@ var ApiService = /*#__PURE__*/function () {
function ApiService() {
_classCallCheck(this, ApiService);
/** @type {string} */
this.baseUrl = 'http://core.sandd';
/** @type { [key:string] : string; } */
this.apiRoutes = {
file: '/api/file',
searchAndDisplace: '/search-and-displace'
file: this.baseUrl + '/api/file',
searchAndDisplace: this.baseUrl + '/search-and-displace'
};
}
/**
* Upload a file to the server and return its response.
* Throws an error if the response wasn't successful
*
* @param file
* @returns
* TODO: Annotate the return type correctly
*
* @param {File} file The file we want to upload
*
* @returns {Promise<FileUploadResponse>} The response from the server
*/
@ -2533,7 +2594,7 @@ var ApiService = /*#__PURE__*/function () {
formData.append('file', file);
_context.prev = 2;
_context.next = 5;
return axios__WEBPACK_IMPORTED_MODULE_1___default().post(this.baseUrl + this.apiRoutes.file, formData, {
return axios__WEBPACK_IMPORTED_MODULE_1___default().post(this.apiRoutes.file, formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
@ -2566,8 +2627,11 @@ var ApiService = /*#__PURE__*/function () {
* Get data for a file from the server.
* Throws an error if the response wasn't successful
*
* @param fileId
* @returns
* @param {string} fileId The id of the file we want to query
*
* @returns {Promise<FileStatusResponse>} The response from the server
*
* @throws
*/
}, {
@ -2581,7 +2645,7 @@ var ApiService = /*#__PURE__*/function () {
case 0:
_context2.prev = 0;
_context2.next = 3;
return axios__WEBPACK_IMPORTED_MODULE_1___default().get(this.baseUrl + this.apiRoutes.searchAndDisplace + "/".concat(fileId));
return axios__WEBPACK_IMPORTED_MODULE_1___default().get(this.apiRoutes.searchAndDisplace + "/".concat(fileId));
case 3:
response = _context2.sent;
@ -2606,6 +2670,52 @@ var ApiService = /*#__PURE__*/function () {
return getFileData;
}()
/**
* 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
*/
}, {
key: "filterDocument",
value: function () {
var _filterDocument = _asyncToGenerator( /*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_0___default().mark(function _callee3(content, searchers) {
var response;
return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_0___default().wrap(function _callee3$(_context3) {
while (1) {
switch (_context3.prev = _context3.next) {
case 0:
_context3.prev = 0;
_context3.next = 3;
return axios__WEBPACK_IMPORTED_MODULE_1___default().post(this.apiRoutes.searchAndDisplace, {
'content': content,
'searchers': searchers
});
case 3:
response = _context3.sent;
return _context3.abrupt("return", response.data);
case 7:
_context3.prev = 7;
_context3.t0 = _context3["catch"](0);
throw _context3.t0;
case 10:
case "end":
return _context3.stop();
}
}
}, _callee3, this, [[0, 7]]);
}));
function filterDocument(_x3, _x4) {
return _filterDocument.apply(this, arguments);
}
return filterDocument;
}()
}]);
return ApiService;
@ -3136,7 +3246,7 @@ __webpack_require__.r(__webpack_exports__);
var ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_0___default()(function(i){return i[1]});
// Module
___CSS_LOADER_EXPORT___.push([module.id, ".file-card {\n flex: 0 1 74%;\n}\n.filters-card {\n flex: 0 1 24%;\n}\n.p-overlaypanel {\n min-width: 300px;\n}", ""]);
___CSS_LOADER_EXPORT___.push([module.id, ".file-card {\n flex: 0 1 74%;\n}\n.filters-card {\n flex: 0 1 24%;\n}\n.p-overlaypanel {\n min-width: 450px;\n}", ""]);
// Exports
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);
@ -30540,7 +30650,7 @@ var render = function() {
"div",
{ staticClass: "wrap" },
[
_c("Toast", { attrs: { position: "top-right" } }),
_c("Toast"),
_vm._v(" "),
_c(
"Panel",
@ -30582,18 +30692,25 @@ var render = function() {
{ staticClass: "wrap" },
[
_c("Skeleton"),
_c("br"),
_vm._v(" "),
_c("Skeleton"),
_c("br"),
_vm._v(" "),
_c("Skeleton"),
_c("br"),
_vm._v(" "),
_c("Skeleton"),
_c("br"),
_vm._v(" "),
_c("Skeleton"),
_c("br"),
_vm._v(" "),
_c("Skeleton"),
_c("br"),
_vm._v(" "),
_c("Skeleton")
_c("Skeleton"),
_c("br")
],
1
)
@ -30635,8 +30752,6 @@ var render = function() {
"div",
{ staticClass: "p-d-flex p-flex-row p-jc-between p-ai-stretch" },
[
_c("Toast"),
_vm._v(" "),
_c("Card", {
staticClass: "p-mr-2 p-as-stretch file-card",
scopedSlots: _vm._u([
@ -30652,6 +30767,28 @@ var render = function() {
return [_c("h3", [_vm._v("File preview")])]
},
proxy: true
},
{
key: "right",
fn: function() {
return [
_c("Button", {
staticClass:
"p-button-success p-button-outlined p-button-sm",
attrs: {
label: "Run filters",
icon: "pi pi-play",
disabled: _vm.fileContent == ""
},
on: {
click: function($event) {
return _vm.runFilters()
}
}
})
]
},
proxy: true
}
])
})
@ -30741,6 +30878,7 @@ var render = function() {
_c(
"DataTable",
{
staticClass: "p-datatable-sm",
attrs: {
value: _vm.selectedSearchers,
dataKey: "id",
@ -30756,9 +30894,7 @@ var render = function() {
"update:expanded-rows": function($event) {
_vm.expandedRows = $event
},
"row-reorder": _vm.onSelectedSearchersReorder,
"row-expand": _vm.onSelectedSearcherExpand,
"row-collapse": _vm.onSelectedSearcherCollapse
"row-reorder": _vm.onSelectedSearchersReorder
},
scopedSlots: _vm._u([
{
@ -30766,12 +30902,6 @@ var render = function() {
fn: function(slotProps) {
return [
_c("div", { staticClass: "options-subtable" }, [
_c("h5", [
_vm._v(
"Options for " + _vm._s(slotProps.data.name)
)
]),
_vm._v(" "),
_c("div", { staticClass: "p-fluid" }, [
_c(
"div",
@ -30779,31 +30909,41 @@ var render = function() {
[
_c(
"label",
{ attrs: { for: "firstname" } },
[_vm._v("Option 1")]
),
_vm._v(" "),
_c("InputText", {
staticClass: "p-inputtext-sm",
attrs: { id: "firstname", type: "text" }
})
],
1
),
_vm._v(" "),
_c(
"div",
{ staticClass: "p-field" },
[
_c(
"label",
{ attrs: { for: "lastname" } },
[_vm._v("Option 2")]
{
attrs: {
for:
"replace_with__" + slotProps.data.id
}
},
[
_vm._v(
"\n Replace values with:\n "
)
]
),
_vm._v(" "),
_c("InputText", {
staticClass: "p-inputtext-sm",
attrs: { id: "lastname", type: "text" }
attrs: {
id:
"replace_with__" + slotProps.data.id,
type: "text"
},
model: {
value:
_vm.searchersOptions[
slotProps.data.id
],
callback: function($$v) {
_vm.$set(
_vm.searchersOptions,
slotProps.data.id,
$$v
)
},
expression:
"searchersOptions[slotProps.data.id]"
}
})
],
1
@ -30854,7 +30994,12 @@ var render = function() {
selection: _vm.selectedSearchers,
dataKey: "id",
selectionMode: "multiple",
metaKeySelection: false
metaKeySelection: false,
paginator: true,
rows: 10,
paginatorTemplate:
"FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown",
rowsPerPageOptions: [10, 20, 50]
},
on: {
"update:value": function($event) {

38
resources/js/components/Home/Home.ts

@ -1,5 +1,5 @@
import axios from 'axios';
import { Vue, Component, Prop } from 'vue-property-decorator';
import FileUploadResponse from '@interfaces/responses/FileUploadResponse';
@Component
export default class Home extends Vue {
@ -10,7 +10,11 @@ export default class Home extends Vue {
public uiBlocked = false;
public uploading = false;
public fileUploaded: boolean = false;
public uploadResult = null;
public uploadResult: FileUploadResponse = {
id: '',
file: '',
path: ''
};
/**
*
@ -23,20 +27,30 @@ export default class Home extends Vue {
*
* @param event The event containing the uploaded files information
*/
public uploadFile(event: any): void {
public async uploadFile(event: any): Promise<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];
this.$toast.add({severity:'info', summary: 'Uploading...', detail:'Uploading your file...', life: 3000});
setTimeout(
async () => {
let response = await this.$api.uploadFile(file);
this.fileUploaded = true;
this.uploadResult = response;
}, 500
)
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;
}
}
}

16
resources/js/components/Home/Home.vue

@ -1,6 +1,6 @@
<template>
<div class="wrap" v-if="!fileUploaded && !uploading">
<Toast position="top-right" />
<Toast />
<Panel header="Please upload a file">
<FileUpload
@ -18,13 +18,13 @@
<BlockUI :blocked="uiBlocked" :fullScreen="true"></BlockUI>
</div>
<div class="wrap" v-else-if="!fileUploaded && uploading">
<Skeleton />
<Skeleton />
<Skeleton />
<Skeleton />
<Skeleton />
<Skeleton />
<Skeleton />
<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>

2
resources/js/components/ProcessFile/ProcessFile.scss

@ -7,5 +7,5 @@
}
.p-overlaypanel {
// width: 450px;
min-width: 300px;
min-width: 450px;
}

43
resources/js/components/ProcessFile/ProcessFile.ts

@ -40,6 +40,8 @@ export default class ProcessFile extends Vue {
//The list of expanded rows in the selected filters/searchers table
private expandedRows: Array<{id: string; name: string; }> = [];
private searchersOptions: { [key: string]: string } = {};
/**
*
*/
@ -58,13 +60,13 @@ export default class ProcessFile extends Vue {
}
/**
*
* Wait for the file to be processed in ingest
*/
private async waitForFile() {
const response = await this.$api.getFileData(this.file.id);
if (response.text !== null && response.ready === true) {
if (response.content !== null) {
if (response.ingest_status === 'fail') {
this.$toast.add({
@ -74,7 +76,7 @@ export default class ProcessFile extends Vue {
life: 3000
});
} else {
this.fileContent = response.documentContent;
this.fileContent = response.content;
this.$toast.add({
severity:'success',
@ -85,37 +87,40 @@ export default class ProcessFile extends Vue {
clearInterval(this.intervalId);
}
} else {
console.log('FILE NOT READY YET!');
}
}
/**
* Toggle the menu containing the list of available searchers
*
* @param $event
*/
private toggleSearchersMenu($event: any) {
this.$refs['searchers-overlay'].toggle($event);
}
private onRowSelect($event: any) {
console.log('SELECT: ', $event);
console.log(this.selectedSearchers);
}
private onRowUnselect($event: any) {
console.log('UNSELECT: ', $event);
console.log(this.selectedSearchers);
}
private onSelectedSearchersReorder($event: any)
{
this.selectedSearchers = $event.value;
}
private onSelectedSearcherExpand($event:any)
{
/**
* Run the filters
*/
private async runFilters() {
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);
private onSelectedSearcherCollapse($event:any)
{
console.log(response);
this.fileContent = response.content;
}
}

38
resources/js/components/ProcessFile/ProcessFile.vue

@ -1,7 +1,7 @@
<template>
<div class="p-d-flex p-flex-row p-jc-between p-ai-stretch">
<Toast />
<!-- <Toast /> -->
<Card
class="p-mr-2 p-as-stretch file-card"
>
@ -10,6 +10,15 @@
<template #left>
<h3>File preview</h3>
</template>
<template #right>
<Button
label="Run filters"
icon="pi pi-play"
class="p-button-success p-button-outlined p-button-sm"
:disabled="fileContent == ''"
@click="runFilters()"/>
</template>
</Toolbar>
</template>
@ -51,10 +60,9 @@
<DataTable
:value.sync="selectedSearchers"
dataKey="id"
class="p-datatable-sm"
:expandedRows.sync="expandedRows"
@row-reorder="onSelectedSearchersReorder"
@row-expand="onSelectedSearcherExpand"
@row-collapse="onSelectedSearcherCollapse">
@row-reorder="onSelectedSearchersReorder">
<Column :rowReorder="true" headerStyle="width: 3rem" />
<Column field="name" header="Name" sortable></Column>
@ -63,18 +71,21 @@
<template #expansion="slotProps">
<div class="options-subtable">
<!-- TODO: Add real options here -->
<h5>Options for {{slotProps.data.name}}</h5>
<!-- <h5>Options for {{slotProps.data.name}}</h5> -->
<!-- <ProgressSpinner /> -->
<div class="p-fluid">
<div class="p-field">
<label for="firstname">Option 1</label>
<InputText id="firstname" type="text" class="p-inputtext-sm" />
</div>
<div class="p-field">
<label for="lastname">Option 2</label>
<InputText id="lastname" type="text" class="p-inputtext-sm" />
<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>
@ -97,7 +108,10 @@
dataKey="id"
selectionMode="multiple"
class="p-datatable-sm"
:metaKeySelection="false">
:metaKeySelection="false"
:paginator="true" :rows="10"
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown"
:rowsPerPageOptions="[10,20,50]">
<Column selectionMode="multiple" headerStyle="width: 3em"></Column>
<Column field="name" header="Name" sortable></Column>

4
resources/js/interfaces/responses/ErrorResponse.ts

@ -0,0 +1,4 @@
export default interface ErrorResponse
{
message: string;
}

5
resources/js/interfaces/responses/FileStatusResponse.ts

@ -0,0 +1,5 @@
export default interface FileStatusResponse
{
content: string;
ingest_status: string;
}

6
resources/js/interfaces/responses/FileUploadResponse.ts

@ -0,0 +1,6 @@
export default interface FileUploadResponse
{
id: string;
file: string;
path: string;
}

60
resources/js/services/ApiService.ts

@ -1,31 +1,38 @@
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: '/api/file',
searchAndDisplace: '/search-and-displace'
file: this.baseUrl + '/api/file',
searchAndDisplace: this.baseUrl + '/search-and-displace'
};
constructor()
{}
constructor() {}
/**
* Upload a file to the server and return its response.
* Throws an error if the response wasn't successful
*
* @param file
* @returns
* 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)
public async uploadFile(file: File): Promise<FileUploadResponse>
{
let formData = new FormData();
formData.append('file', file);
try {
let response = await axios.post(
this.baseUrl + this.apiRoutes.file,
let response = await axios.post<FileUploadResponse>(
this.apiRoutes.file,
formData,
{
headers: {
@ -33,7 +40,6 @@ export default class ApiService {
}
}
)
return response.data;
} catch (err) {
throw err;
@ -44,13 +50,39 @@ export default class ApiService {
* Get data for a file from the server.
* Throws an error if the response wasn't successful
*
* @param fileId
* @returns
* @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 getFileData(fileId: string)
public async filterDocument(content: string, searchers: Array<{ key: string; replace_with: string; }>)
{
try {
let response = await axios.get(this.baseUrl + this.apiRoutes.searchAndDisplace + `/${fileId}`);
let response = await axios.post(
this.apiRoutes.searchAndDisplace,
{
'content': content,
'searchers': searchers
}
);
return response.data;
} catch (err) {

3
resources/sass/app.scss

@ -10,8 +10,9 @@
// @import '~primevue/resources/themes/fluent-light/theme.css';
// @import '~primevue/resources/themes/vela-green/theme.css';
@import '~primevue/resources/themes/mdc-light-indigo/theme.css';
// @import '~primevue/resources/themes/mdc-light-indigo/theme.css';
// @import '~primevue/resources/themes/mdc-dark-indigo/theme.css';
@import '~primevue/resources/themes/saga-blue/theme.css';
@import '~primevue/resources/primevue.min.css';
@import '~primeicons/primeicons.css';

7
routes/api.php

@ -25,13 +25,6 @@ Route::name('file.')->prefix('file')->group(
FileController::class,
'create'
])->name('create');
# GET "/file/{id}" (to retrieve data about a file)
Route::get('/{id}', [
FileController::class,
'get'
])->name('get');
}
);

71
yarn.lock

@ -1136,6 +1136,11 @@
"@types/minimatch" "*"
"@types/node" "*"
"@types/highlight.js@^9.7.0":
"integrity" "sha512-t2szdkwmg2JJyuCM20e8kR2X59WCE5Zkl4bzm1u1Oukjm79zpbiAv+QjnwLnuuV0WHEcX2NgUItu0pAMKuOPww=="
"resolved" "https://registry.npmjs.org/@types/highlight.js/-/highlight.js-9.12.4.tgz"
"version" "9.12.4"
"@types/http-proxy@^1.17.5":
"integrity" "sha512-GNkDE7bTv6Sf8JbV2GksknKOsk7OznNYHSdrtvPJXO0qJ9odZig6IZKUi5RFGi6d1bf6dgIAe4uXi3DBc7069Q=="
"resolved" "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.5.tgz"
@ -1189,6 +1194,25 @@
"resolved" "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz"
"version" "0.0.29"
"@types/linkify-it@*":
"integrity" "sha512-pQv3Sygwxxh6jYQzXaiyWDAHevJqWtqDUv6t11Sa9CPGiXny66II7Pl6PR8QO5OVysD6HYOkHMeBgIjLnk9SkQ=="
"resolved" "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.1.tgz"
"version" "3.0.1"
"@types/markdown-it@^12.0.1":
"integrity" "sha512-mHfT8j/XkPb1uLEfs0/C3se6nd+webC2kcqcy8tgcVr0GDEONv/xaQzAN+aQvkxQXk/jC0Q6mPS+0xhFwRF35g=="
"resolved" "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.0.1.tgz"
"version" "12.0.1"
dependencies:
"@types/highlight.js" "^9.7.0"
"@types/linkify-it" "*"
"@types/mdurl" "*"
"@types/mdurl@*":
"integrity" "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA=="
"resolved" "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz"
"version" "1.0.2"
"@types/micromatch@^2":
"integrity" "sha512-6rW4NsUHaDudxJSuRlm1PdNu61CDXkgix7LBOBg7b3yWQ43XANYSPwkvX1cGiZvBVZW8c5rsCEfrfzbPkch8ag=="
"resolved" "https://registry.npmjs.org/@types/micromatch/-/micromatch-2.3.30.tgz"
@ -1955,6 +1979,11 @@
dependencies:
"sprintf-js" "~1.0.2"
"argparse@^2.0.1":
"integrity" "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
"resolved" "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz"
"version" "2.0.1"
"arity-n@^1.0.4":
"integrity" "sha1-2edrEXM+CFacCEeuezmyhgswt0U="
"resolved" "https://registry.npmjs.org/arity-n/-/arity-n-1.0.4.tgz"
@ -3969,6 +3998,11 @@
"resolved" "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz"
"version" "2.2.0"
"entities@~2.1.0":
"integrity" "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w=="
"resolved" "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz"
"version" "2.1.0"
"envinfo@^7.7.3":
"integrity" "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw=="
"resolved" "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz"
@ -5995,6 +6029,13 @@
"resolved" "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz"
"version" "1.1.6"
"linkify-it@^3.0.1":
"integrity" "sha512-gDBO4aHNZS6coiZCKVhSNh43F9ioIL4JwRjLZPkoLIY4yZFwg264Y5lu2x6rb1Js42Gh6Yqm2f6L2AJcnkzinQ=="
"resolved" "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.2.tgz"
"version" "3.0.2"
dependencies:
"uc.micro" "^1.0.1"
"loader-runner@^2.3.1", "loader-runner@^2.4.0":
"integrity" "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw=="
"resolved" "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz"
@ -6215,6 +6256,17 @@
dependencies:
"object-visit" "^1.0.0"
"markdown-it@^12.0.4":
"integrity" "sha512-qv3sVLl4lMT96LLtR7xeRJX11OUFjsaD5oVat2/SNBIb21bJXwal2+SklcRbTwGwqWpWH/HRtYavOoJE+seL8w=="
"resolved" "https://registry.npmjs.org/markdown-it/-/markdown-it-12.0.6.tgz"
"version" "12.0.6"
dependencies:
"argparse" "^2.0.1"
"entities" "~2.1.0"
"linkify-it" "^3.0.1"
"mdurl" "^1.0.1"
"uc.micro" "^1.0.5"
"md5.js@^1.3.4":
"integrity" "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg=="
"resolved" "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz"
@ -6243,6 +6295,11 @@
"resolved" "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz"
"version" "2.0.4"
"mdurl@^1.0.1":
"integrity" "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4="
"resolved" "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz"
"version" "1.0.1"
"media-typer@0.3.0":
"integrity" "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
"resolved" "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz"
@ -9396,6 +9453,11 @@
"resolved" "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz"
"version" "3.8.3"
"uc.micro@^1.0.1", "uc.micro@^1.0.5":
"integrity" "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="
"resolved" "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz"
"version" "1.0.6"
"uglify-js@3.4.x":
"integrity" "sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw=="
"resolved" "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz"
@ -9666,6 +9728,15 @@
"vue-hot-reload-api" "^2.3.0"
"vue-style-loader" "^4.1.0"
"vue-markdown-render@^1.1.1":
"integrity" "sha512-NkkGOC4ip6C3Cp0dyFBCEPZ9dDF2yI2wrePSwoU2xamyhvcs0eJk0t9VZcUiKCz1bm5SvVeTelIU0qzVgi6jVQ=="
"resolved" "https://registry.npmjs.org/vue-markdown-render/-/vue-markdown-render-1.1.1.tgz"
"version" "1.1.1"
dependencies:
"markdown-it" "^12.0.4"
"punycode" "^2.1.1"
"vue" "^2.6.12"
"vue-property-decorator@^8.1.0":
"integrity" "sha512-O6OUN2OMsYTGPvgFtXeBU3jPnX5ffQ9V4I1WfxFQ6dqz6cOUbR3Usou7kgFpfiXDvV7dJQSFcJ5yUPgOtPPm1Q=="
"resolved" "https://registry.npmjs.org/vue-property-decorator/-/vue-property-decorator-8.5.1.tgz"

Loading…
Cancel
Save