Browse Source

Searchers CRUD. Styles

master
Orzu Ionut 3 years ago
parent
commit
1a8a83bb3c
  1. 5
      .env.example
  2. 1
      .gitignore
  3. 31
      .vscode/launch.json
  4. 3
      README.md
  5. 34
      app/Http/Controllers/FileController.php
  6. 4
      app/Http/Controllers/HomeController.php
  7. 3
      app/Http/Controllers/RegexController.php
  8. 2
      app/Http/Controllers/SearchAndDisplaceController.php
  9. 78
      app/Http/Controllers/SearcherController.php
  10. 24
      app/SearchDisplace/Documents/DocumentFile.php
  11. 2
      app/SearchDisplace/Regex/RegexFactory.php
  12. 24
      app/SearchDisplace/Searchers/Mapper.php
  13. 8
      app/SearchDisplace/Searchers/Searcher.php
  14. 66
      app/SearchDisplace/Searchers/SearcherCreator.php
  15. 2
      app/SearchDisplace/Searchers/SearcherFactory.php
  16. 54
      app/SearchDisplace/Searchers/SearchersCollection.php
  17. 17
      app/SearchDisplace/Searchers/SearchersStorage.php
  18. 9175
      package-lock.json
  19. 5
      package.json
  20. 5116
      public/css/app.css
  21. 11785
      public/js/app.js
  22. 5
      resources/js/app.ts
  23. 21
      resources/js/components/Home/Home.ts
  24. 2
      resources/js/components/Home/Home.vue
  25. 5
      resources/js/components/ProcessFile/ProcessFile.scss
  26. 43
      resources/js/components/ProcessFile/ProcessFile.ts
  27. 86
      resources/js/components/ProcessFile/ProcessFile.vue
  28. 52
      resources/js/components/Regex/Create.vue
  29. 5
      resources/js/components/Regex/PatternBox.vue
  30. 6
      resources/js/components/Regex/TextBox.vue
  31. 12
      resources/js/components/Searchers/AddBox.vue
  32. 149
      resources/js/components/Searchers/Create.vue
  33. 87
      resources/js/components/Searchers/Index.vue
  34. 191
      resources/js/components/Searchers/Show.vue
  35. 19
      resources/js/components/layout/Header.vue
  36. 5
      resources/js/interfaces/Searcher.ts
  37. 5
      resources/js/interfaces/responses/FileUploadResponse.ts
  38. 24
      resources/js/services/ApiService.ts
  39. 21
      resources/sass/app.sass
  40. 7
      resources/sass/components/regex/create/_index.sass
  41. 6
      resources/views/app.blade.php
  42. 10
      resources/views/pages/home.blade.php
  43. 7
      resources/views/pages/searchers/edit.blade.php
  44. 7
      resources/views/pages/searchers/index.blade.php
  45. 3
      routes/web.php
  46. 10261
      yarn.lock

5
.env.example

@ -15,3 +15,8 @@ SESSION_LIFETIME=120
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
SD_DUCKLING_URL=
SD_INGEST_URL=
WEBHOOK_CLIENT_SECRET=

1
.gitignore

@ -11,3 +11,4 @@ Homestead.yaml
npm-debug.log
yarn-error.log
.idea
.vscode

31
.vscode/launch.json

@ -1,31 +0,0 @@
{
// 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",
}
}
]
}

3
README.md

@ -11,3 +11,6 @@ Add in .env the 'WEBHOOK_CLIENT_SECRET' value.
- `$ curl -sSL https://get.haskellstack.org/ | sh`
- `$ stack build && stack exec duckling-example-exe`
- `$ stack test`
### Converting documents from MD to ODT
- `$ apt-get install pandoc`

34
app/Http/Controllers/FileController.php

@ -2,23 +2,19 @@
namespace App\Http\Controllers;
use Illuminate\Contracts\Container\BindingResolutionException;
use App\SearchDisplace\Ingest\SendDocument;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Storage;
use Illuminate\Http\UploadedFile;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\BadResponseException;
use Illuminate\Support\Str;
use Symfony\Component\Process\Process;
class FileController extends Controller
{
/**
*
* @return JsonResponse
*
* @throws BindingResolutionException
*/
public function create(): JsonResponse
{
@ -26,37 +22,25 @@ class FileController extends Controller
/** @var UploadedFile $file */
$file = request()->file('file');
$time = time();
$fileName = $time . '-' . $file->getClientOriginalName();
$fileId = $time . pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
$fileId = $time . '_' . pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
$path = Storage::putFileAs('files/', $file, $fileName);
$sendDocument = new SendDocument();
$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')
]
]
]);
$sendDocument->execute($file->getRealPath(), $fileId);
return response()->json([
'file' => $file->getClientOriginalName(),
'id' => $fileId,
'path' => $path,
'file_name' => $file->getClientOriginalName(),
]);
} catch (BadResponseException $e) {
return response()->json([
'message' => $e->getMessage(),
'response' => $e->getResponse()
], 400);
} catch (\Exception $e) {
return response()->json([
'message' => $e->getMessage(),
], 400);
}
}

4
app/Http/Controllers/HomeController.php

@ -3,6 +3,7 @@
namespace App\Http\Controllers;
use App\SearchDisplace\Searchers\Mapper;
use App\SearchDisplace\Searchers\SearchersCollection;
use Illuminate\View\View;
use Illuminate\Contracts\View\Factory;
use Illuminate\Contracts\Container\BindingResolutionException;
@ -12,12 +13,11 @@ class HomeController extends Controller
/**
*
* @return View|Factory
* @throws BindingResolutionException
*/
public function index(): View
{
return view('pages.home', [
'searchers' => (new Mapper())->get(),
'searchers' => (new SearchersCollection())->all(),
]);
}
}

3
app/Http/Controllers/RegexController.php

@ -21,10 +21,11 @@ class RegexController extends Controller
try {
$factory = new RegexFactory(request()->get('name'), request()->get('expression'));
$factory->create();
$searcher = $factory->create();
return response()->json([
'status' => 'success',
'searcher' => $searcher,
], 200);
} catch (\Exception $exception) {
return response()->json([

2
app/Http/Controllers/SearchAndDisplaceController.php

@ -14,6 +14,8 @@ class SearchAndDisplaceController extends Controller
try {
$documentContent = $handler->getAfterIngest($id);
$handler->destroy($id);
return response()->json([
'content' => $documentContent,
'ingest_status' => !! $documentContent ? 'success' : 'fail',

78
app/Http/Controllers/SearcherController.php

@ -3,6 +3,7 @@
namespace App\Http\Controllers;
use App\SearchDisplace\Searchers\SearcherFactory;
use App\SearchDisplace\Searchers\SearchersCollection;
use App\SearchDisplace\Searchers\SearchersStorage;
class SearcherController extends Controller
@ -13,7 +14,7 @@ class SearcherController extends Controller
if (request()->has('q')) {
$searchers = [];
} else {
$searchers = (new SearchersStorage())->all();
$searchers = (new SearchersCollection())->all();
}
if (request()->wantsJson()) {
@ -43,14 +44,16 @@ class SearcherController extends Controller
try {
$factory = new SearcherFactory(request()->get('name'), request()->get('rows'));
$factory->create();
$searcher = $factory->create();
return response()->json([
'status' => 'success',
'searcher' => $searcher,
], 200);
} catch (\Exception $exception) {
return response()->json([
'status' => 'fail',
'trace' => $exception->getTrace(),
'message' => $exception->getMessage(),
], 400);
}
@ -58,15 +61,42 @@ class SearcherController extends Controller
public function show($id)
{
$searchersStorage = new SearchersStorage();
$searchersCollection = new SearchersCollection();
try {
if ( ! $searchersStorage->has($id)) {
$searcher = $searchersCollection->get($id);
if ( ! $searcher) {
abort(404);
}
if (request()->wantsJson()) {
return response()->json([
'searcher' => $searcher,
], 200);
}
return view('pages.searchers.show', [
'searcher' => $searchersStorage->get($id),
'searcher' => $searcher,
]);
} catch (\Exception $exception) {
abort(400);
}
}
public function edit($id)
{
$searchersCollection = new SearchersCollection();
try {
$searcher = $searchersCollection->get($id);
if ( ! $searcher) {
abort(404);
}
return view('pages.searchers.edit', [
'searcher' => $searcher,
]);
} catch (\Exception $exception) {
abort(400);
@ -75,6 +105,44 @@ class SearcherController extends Controller
public function update($id)
{
request()->validate([
'name' => 'required',
'rows' => 'required|array',
'rows.*' => 'array',
]);
try {
$factory = new SearcherFactory(request()->get('name'), request()->get('rows'));
$searcher = $factory->save($id);
return response()->json([
'status' => 'success',
'searcher' => $searcher,
], 200);
} catch (\Exception $exception) {
return response()->json([
'status' => 'fail',
'trace' => $exception->getTrace(),
'message' => $exception->getMessage(),
], 400);
}
}
public function destroy($id)
{
$searcherStorage = new SearchersStorage();
if ( ! $searcherStorage->has($id)) {
abort(404);
}
$deleted = $searcherStorage->destroy($id);
if ( ! $deleted) {
return response()->json([], 400);
}
return response()->json([], 200);
}
}

24
app/SearchDisplace/Documents/DocumentFile.php

@ -15,7 +15,7 @@ class DocumentFile
public function getAfterIngest($id)
{
$path = "contracts/$id";
$path = $this->getPath($id);
// Ingest success.
if ($this->storage->exists("$path.md")) {
@ -29,4 +29,26 @@ class DocumentFile
throw new \Exception('Document has not been processed yet.');
}
public function destroy($id)
{
$path = $this->getPath($id);
// Ingest success.
if ($this->storage->exists("$path.md")) {
return $this->storage->delete("$path.md");
}
// Ingest fail.
if ($this->storage->exists($path)) {
return $this->storage->delete($path);
}
throw new \Exception('Document has not been processed yet.');
}
protected function getPath($id)
{
return "contracts/$id";
}
}

2
app/SearchDisplace/Regex/RegexFactory.php

@ -23,6 +23,6 @@ class RegexFactory extends SearcherCreator
],
];
$this->store();
return $this->store();
}
}

24
app/SearchDisplace/Searchers/Mapper.php

@ -68,14 +68,34 @@ class Mapper
return array_keys($this->mapper);
}
public function get()
public function all()
{
$items = [];
foreach ($this->mapper as $key => $value) {
$items[$key] = $value['name'];
$items[$key] = [
'id' => $key,
'name' => $value['name'],
];
}
return $items;
}
public function has($id)
{
return in_array($id, $this->getSearchers());
}
public function get($id)
{
if ( ! $this->has($id)) {
return null;
}
return array_merge($this->mapper[$id], [
'id' => $id,
'description' => '',
]);
}
}

8
app/SearchDisplace/Searchers/Searcher.php

@ -6,7 +6,7 @@ class Searcher
{
protected $searchers;
protected $content;
protected $ducklingSearchers;
protected $ducklingMapper;
protected $searchersStorage;
public function __construct($searchers, $content)
@ -16,7 +16,7 @@ class Searcher
ksort($this->searchers);
$this->ducklingSearchers = (new Mapper())->getSearchers();
$this->ducklingMapper = new Mapper();
$this->searchersStorage = new SearchersStorage();
}
@ -35,7 +35,7 @@ class Searcher
// Apply searchers in a half serial, half parallel manner, so apply each searcher on the same initial content
// but with the modifications made by the previous searchers, this way the 'order' matters.
foreach ($this->searchers as $searcher) {
$results[$searcher['key']] = in_array($searcher['key'], $this->ducklingSearchers)
$results[$searcher['key']] = $this->ducklingMapper->has($searcher['key'])
? $this->applyDucklingSearcher($content, $searcher['key'])
: $this->applyCustomSearcher($content, $searcher);
}
@ -88,7 +88,7 @@ class Searcher
}
if (array_key_exists('key', $searcher)) {
if (in_array($searcher['key'], $this->ducklingSearchers)) {
if ($this->ducklingMapper->has($searcher['key'])) {
return $this->applyDucklingSearcher($content, $searcher['key']);
} else {
throw new \Exception('Invalid searcher: ' . $searcher['key']);

66
app/SearchDisplace/Searchers/SearcherCreator.php

@ -9,28 +9,86 @@ abstract class SearcherCreator
protected $name;
protected $id;
protected $description;
protected $rows;
protected $rows = [];
protected $storage;
protected $searchersCollection;
public function __construct($name, $description)
{
$this->name = $name;
$this->description = $description;
$this->id = md5(uniqid(rand(), true));
$this->id = $this->generateID();
$this->storage = Storage::disk('local');
$this->searchersCollection = new SearchersCollection();
}
abstract public function create();
protected function store()
{
$id = "{$this->id}_{$this->name}";
return $this->save($id);
}
public function save($id)
{
$this->rows = $this->processRows($this->rows);
$contents = [
'id' => $this->id,
'id' => $id,
'name' => $this->name,
'description' => $this->description,
'rows' => $this->rows,
];
$this->storage->put("searchers/{$this->id}_{$this->name}.json", json_encode($contents));
$this->storage->put("searchers/$id.json", json_encode($contents));
return array_merge($contents, [
'id' => $id,
]);
}
protected function processRows($rows)
{
foreach ($rows as $rowIndex => $row) {
foreach ($row as $columnIndex => $column) {
if (array_key_exists('id', $column)) {
$rows[$rowIndex][$columnIndex]['id'] = $this->generateNewIDFromID($column);
// if ( ! array_key_exists('rows', $column)) {
// $searcher = $this->searchersCollection->get($column['id']);
//
// $rows[$rowIndex][$columnIndex]['rows'] = $searcher['rows'];
// }
}
if (array_key_exists('rows', $rows[$rowIndex][$columnIndex])) {
$rows[$rowIndex][$columnIndex]['rows'] = $this->processRows($rows[$rowIndex][$columnIndex]['rows']);
}
}
}
return $rows;
}
protected function generateNewIDFromID($searcher)
{
if (array_key_exists('type', $searcher) && $searcher['type'] === 'duckling') {
return $searcher['id'];
}
$result = explode('_', $searcher['id']);
if (count($result) === 1) {
return $result[0];
}
return $this->generateID() . '_' . $result[1];
}
protected function generateID()
{
return md5(uniqid(rand(), true));
}
}

2
app/SearchDisplace/Searchers/SearcherFactory.php

@ -13,6 +13,6 @@ class SearcherFactory extends SearcherCreator
public function create()
{
$this->store();
return $this->store();
}
}

54
app/SearchDisplace/Searchers/SearchersCollection.php

@ -0,0 +1,54 @@
<?php
namespace App\SearchDisplace\Searchers;
class SearchersCollection
{
public function all()
{
$collection = [];
foreach ((new SearchersStorage())->all() as $item) {
$collection[] = array_merge($item, [
'type' => 'custom',
]);
}
foreach ((new Mapper)->all() as $item) {
$collection[] = array_merge($item, [
'type' => 'duckling',
]);
}
return $collection;
}
public function get($id)
{
$searcherStorage = new SearchersStorage();
if ($searcherStorage->has($id)) {
return array_merge($searcherStorage->get($id), [
'type' => 'custom',
]);
}
$ducklingMapper = new Mapper();
if ($ducklingMapper->has($id)) {
return array_merge($ducklingMapper->get($id), [
'type' => 'duckling',
]);
}
return null;
}
public function has($id)
{
$searcherStorage = new SearchersStorage();
$ducklingMapper = new Mapper();
return $searcherStorage->has($id) || $ducklingMapper->has($id);
}
}

17
app/SearchDisplace/Searchers/SearchersStorage.php

@ -21,12 +21,12 @@ class SearchersStorage
foreach ($files as $file) {
if (pathinfo($file, PATHINFO_EXTENSION) === 'json') {
$name = pathinfo($file, PATHINFO_FILENAME);
$result = explode('_', $name);
$fileName = pathinfo($file, PATHINFO_FILENAME);
$result = explode('_', $fileName);
if (count($result) === 2) {
$searchers[$name] = [
'id' => $result[0],
$searchers[$fileName] = [
'id' => $fileName,
'name' => $result[1],
];
}
@ -47,4 +47,13 @@ class SearchersStorage
return json_decode($contents, true);
}
public function destroy($id)
{
if ( ! $this->has($id)) {
return false;
}
return $this->storage->delete("searchers/$id.json");
}
}

9175
package-lock.json
File diff suppressed because it is too large
View File

5
package.json

@ -20,12 +20,11 @@
"lodash": "^4.17.19",
"resolve-url-loader": "^3.1.0",
"sass": "^1.15.2",
"sass-loader": "^8.0.0",
"sass-loader": "^11.1.1",
"ts-loader": "^8.1.0",
"typescript": "~3.8.3",
"vue-loader": "^15.9.6",
"vue-template-compiler": "^2.6.12",
"webpack": "^5.9.0"
"vue-template-compiler": "^2.6.12"
},
"dependencies": {
"primeflex": "^2.0.0",

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

11785
public/js/app.js
File diff suppressed because it is too large
View File

5
resources/js/app.ts

@ -4,6 +4,7 @@ import Vue from 'vue';
/**
* Import vendor classes
*/
import VueMarkdown from "vue-markdown-render";
import PrimeVue from 'primevue/config';
import Button from 'primevue/button';
import Panel from 'primevue/panel';
@ -36,6 +37,7 @@ import Message from 'primevue/message';
import AppHeader from './components/layout/Header.vue';
import AppFooter from './components/layout/Footer.vue';
import RegexCreate from './components/Regex/Create.vue';
import SearchersIndex from './components/Searchers/Index.vue';
import SearchersCreate from './components/Searchers/Create.vue';
import SearchersShow from './components/Searchers/Show.vue';
import ApiPlugin from './plugins/ApiPlugin';
@ -49,7 +51,7 @@ Vue.use(PrimeVue, {
Vue.use(ToastService);
Vue.use(ApiPlugin);
// Vue.component('vue-markdown', VueMarkdown);
Vue.component('vue-markdown', VueMarkdown);
Vue.component('Button', Button);
Vue.component('Panel', Panel);
Vue.component('Card', Card);
@ -84,6 +86,7 @@ Vue.component('home', Home);
Vue.component('regex-create', RegexCreate);
Vue.component('searchers-index', SearchersIndex);
Vue.component('searchers-create', SearchersCreate);
Vue.component('searchers-show', SearchersShow);

21
resources/js/components/Home/Home.ts

@ -1,10 +1,9 @@
import { Vue, Component, Prop } from 'vue-property-decorator';
import FileUploadResponse from '@interfaces/responses/FileUploadResponse';
import {Vue, Component, Prop} from 'vue-property-decorator';
import FileUploadResponse from '@/interfaces/responses/FileUploadResponse';
@Component
export default class Home extends Vue {
@Prop({ default: [] })
@Prop({default: []})
public readonly searchers!: { [key: string]: string; }
public uiBlocked = false;
@ -12,16 +11,9 @@ export default class Home extends Vue {
public fileUploaded: boolean = false;
public uploadResult: FileUploadResponse = {
id: '',
file: '',
path: ''
file_name: '',
};
/**
*
*/
public created()
{}
/**
* A method which uploads the files to the server for processing
*
@ -32,12 +24,13 @@ export default class Home extends Vue {
this.fileUploaded = false;
this.$toast.add({severity:'info', summary: 'Uploading...', detail:'Uploading your file...', life: 3000});
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) {
@ -53,4 +46,4 @@ export default class Home extends Vue {
this.fileUploaded = false;
}
}
}
}

2
resources/js/components/Home/Home.vue

@ -1,7 +1,5 @@
<template>
<div class="wrap" v-if="!fileUploaded && !uploading">
<Toast />
<Panel header="Please upload a file">
<FileUpload
name="demo[]"

5
resources/js/components/ProcessFile/ProcessFile.scss

@ -45,10 +45,11 @@ button.add-searchers {
padding-top: 50px;
}
// @TODO Does not work, fix this.
.md-viewer {
text-align: start;
text-align: start !important;
h1, h2, h3, h4, h5 {
font-size: initial;
}
}
}

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

@ -1,7 +1,7 @@
import axios from 'axios';
// import OverlayPanel from 'primevue/overlaypanel/OverlayPanel';
import { Vue, Component, Prop, Watch } from 'vue-property-decorator';
import { FileData } from '@/interfaces/FileData';
import {Vue, Component, Prop, Watch} from 'vue-property-decorator';
import {FileData} from '@/interfaces/FileData';
@Component
export default class ProcessFile extends Vue {
@ -9,19 +9,19 @@ export default class ProcessFile extends Vue {
/**
* Props
*/
// The data for the file we are processing
@Prop({ default: { id: -1, file: '', path: '' } })
// 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; }
@Prop({default: []})
public readonly searchers!: [];
/**
* Class members
*/
// The id of the interval used to query the file status
// The id of the interval used to query the file status
private intervalId!: any;
// The content of the file we are processing
@ -40,7 +40,7 @@ export default class ProcessFile extends Vue {
private searchersDialogVisible: boolean = false;
// The list of filters/searchers in a format usable by the datatable
private searchersData: Array<{ id: string; name: string; }> = [];
// private searchersData: Array<{ id: string; name: string; type: string; }> = [];
// The list of filters applied to the selected searchers
private searchersFilters: any = [];
@ -49,7 +49,7 @@ export default class ProcessFile extends Vue {
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; }> = [];
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 } = {};
@ -58,16 +58,6 @@ export default class ProcessFile extends Vue {
*
*/
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);
}
@ -83,7 +73,7 @@ export default class ProcessFile extends Vue {
*
* @param {string} newValue The new value for the dialog visibility
*/
private toggleSearchersDialog(newValue?: boolean) {
private toggleSearchersDialog(newValue?: boolean) {
if (typeof newValue !== 'undefined') {
this.searchersDialogVisible = newValue;
@ -112,7 +102,7 @@ export default class ProcessFile extends Vue {
this.fileContent = response.content;
this.$toast.add({
severity:'success',
severity: 'success',
summary: 'File loaded',
detail: 'The file has been processed by ingest.',
life: 3000
@ -124,8 +114,7 @@ export default class ProcessFile extends Vue {
}
private onSelectedSearchersReorder($event: any)
{
private onSelectedSearchersReorder($event: any) {
this.selectedSearchers = $event.value;
}
@ -137,7 +126,7 @@ export default class ProcessFile extends Vue {
this.processedFileContent = '';
let searchers: Array<{ key: string; replace_with: string; }> = [];
this.selectedSearchers.forEach( (searcher) => {
this.selectedSearchers.forEach((searcher) => {
searchers.push({
'key': searcher.id,
'replace_with': this.searchersOptions[searcher.id] || ''
@ -152,9 +141,9 @@ export default class ProcessFile extends Vue {
this.processing = false;
}
private async downloadOdt()
{
private async downloadOdt() {
let response = await this.$api.convertFile(this.processedFileContent);
window.open('http://core.sandd/file/download/' + response.path);
window.open(`${window.location.origin}/file/download/` + response.path);
}
}

86
resources/js/components/ProcessFile/ProcessFile.vue

@ -1,10 +1,6 @@
<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"
>
<Card class="p-mr-2 p-as-stretch file-card">
<template #header>
<Toolbar>
<template #left>
@ -14,28 +10,31 @@
</template>
<template #content>
<div class="md-viewer">
<div class="md-viewer" style="text-align: start; font-size: 0.7em;">
<template v-if="fileContent === ''">
<Skeleton /><br />
<Skeleton /><br />
<Skeleton /><br />
<Skeleton /><br />
<Skeleton /><br />
<Skeleton /><br />
<Skeleton /><br />
<Skeleton/>
<br/>
<Skeleton/>
<br/>
<Skeleton/>
<br/>
<Skeleton/>
<br/>
<Skeleton/>
<br/>
<Skeleton/>
<br/>
<Skeleton/>
<br/>
</template>
<template v-else>
<vue-markdown :source="fileContent" />
<vue-markdown :source="fileContent"/>
</template>
</div>
</template>
</Card>
<Card
class="p-mr-2 p-as-stretch file-card"
>
<Card class="p-mr-2 p-as-stretch file-card">
<template #header>
<Toolbar>
<template #left>
@ -66,18 +65,25 @@
</template>
<template #content>
<div class="md-viewer">
<div class="md-viewer" style="text-align: start; font-size: 0.7em;">
<template v-if="processing === true">
<Skeleton /><br />
<Skeleton /><br />
<Skeleton /><br />
<Skeleton /><br />
<Skeleton /><br />
<Skeleton /><br />
<Skeleton /><br />
<Skeleton/>
<br/>
<Skeleton/>
<br/>
<Skeleton/>
<br/>
<Skeleton/>
<br/>
<Skeleton/>
<br/>
<Skeleton/>
<br/>
<Skeleton/>
<br/>
</template>
<template v-else-if="processedFileContent !== ''">
<vue-markdown :source="processedFileContent" />
<vue-markdown :source="processedFileContent"/>
</template>
<template v-else>
<Message severity="info" :closable="false">
@ -86,16 +92,13 @@
</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>
@ -107,7 +110,7 @@
class="p-button-success p-button-sm p-button-text add-searchers"
@click="toggleSearchersDialog(true)"
aria:haspopup="true"
aria-controls="searcers_dialog" />
aria-controls="searcers_dialog"/>
</div>
</div>
@ -117,17 +120,18 @@
:maximizable="true"
:style="{width: '50vw'}"
:contentStyle="{overflow: 'visible'}"
id="searcers_dialog"
ref="searcers-dialog">
id="searchers_dialog"
ref="searchers-dialog">
<DataTable
:value.sync="searchersData"
:value.sync="searchers"
:selection.sync="selectedSearchers"
dataKey="id"
selectionMode="multiple"
class="p-datatable-sm"
:metaKeySelection="false"
:paginator="true" :rows="10"
:paginator="true"
:rows="10"
paginatorTemplate="CurrentPageReport FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown"
currentPageReportTemplate="Showing {first} to {last} of {totalRecords}"
:rowsPerPageOptions="[10,20,50]">
@ -154,10 +158,11 @@
:expandedRows.sync="expandedRows"
@row-reorder="onSelectedSearchersReorder">
<Column :rowReorder="true" headerStyle="width: 3rem" />
<Column field="name" header="Name">
</Column>
<Column :expander="true" headerStyle="width: 3rem" />
<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">
@ -178,7 +183,6 @@
</div>
</template>
</DataTable>
</Sidebar>
</div>

52
resources/js/components/Regex/Create.vue

@ -1,16 +1,19 @@
<template>
<div id="regex-create">
<div>
<div>
<input v-model="name"
<div class="regex-header">
<template v-if=" ! regex">
<h1> New regex searcher </h1>
<InputText v-model="name"
type="text"
placeholder="Enter searcher name"
class="input">
</div>
</InputText>
</template>
<button @click="onSave" :disabled=" ! name || ! pattern">
<Button @click="onSave" :disabled="( ! name && ! regex) || ! pattern">
Save
</button>
</Button>
</div>
<div class="regex-box">
@ -32,13 +35,15 @@
</template>
<script lang="ts">
import {Component, Vue} from "vue-property-decorator";
import {Component, Prop, Vue} from "vue-property-decorator";
import TextBox from './TextBox.vue';
import PatternBox from './PatternBox.vue';
import Flags from './Flags.vue';
import SideBar from './SideBar.vue';
@Component({
name: 'RegexCreate',
components: {
TextBox,
PatternBox,
@ -52,18 +57,47 @@ export default class Create extends Vue {
private pattern: string = '';
private flags: Array<string> = ['g', 'i'];
async onSave() {
@Prop({
default: '',
})
public readonly regex!: string;
onSave() {
if ( ! this.regex) {
this.save();
return;
}
this.$emit('updated', this.pattern);
}
async save() {
try {
const { data } = await (window as any).axios.post('/regex', {
name: this.name,
expression: this.pattern,
});
alert('Saved.');
this.$toast.add({
severity: 'success',
summary: 'Searcher created.',
life: 1000,
});
setTimeout(() => {
window.location.href = `/searchers/${data.searcher.id}`;
}, 1000);
} catch (e) {
console.log(e);
console.log('Something went wrong.');
}
}
created() {
if (this.regex) {
this.pattern = this.regex;
}
}
};
</script>

5
resources/js/components/Regex/PatternBox.vue

@ -1,10 +1,11 @@
<template>
<div id="pattern-box" class="pattern-box-wrapper">
<input autofocus
<InputText autofocus
v-model="pattern"
placeholder="Enter pattern"
class="pattern-box"
type="text">
</InputText>
<p v-show="error" class="error-text">{{ error }}</p>
</div>
@ -18,7 +19,7 @@
private pattern: string = '';
private error: string = '';
@Prop({default: ''}) public readonly value: string = '';
@Prop({default: ''}) public readonly value!: string;
@Watch('pattern')
patternChanged(value: string) {

6
resources/js/components/Regex/TextBox.vue

@ -19,8 +19,10 @@ import {Component, Prop, Vue} from "vue-property-decorator";
export default class TextBox extends Vue {
private text: string = '';
@Prop({default: ''}) public readonly pattern: string = '';
@Prop() public readonly flags!: Array<string>;
@Prop({default: ''})
public readonly pattern!: string;
@Prop()
public readonly flags!: Array<string>;
getMatches() {
return this.text

12
resources/js/components/Searchers/AddBox.vue

@ -2,7 +2,7 @@
<div>
<div @click="adding = true"
class="box"
style="font-size: 6rem;">
style="font-size: 6rem; max-width: 100px;">
+
</div>
@ -12,7 +12,10 @@
:closable="false"
:style="{width: '50vw'}"
:modal="true">
<searchers @selected="onSearcherSelected"></searchers>
<searchers @selected="onSearcherSelected"
:ignore-searcher-ids="ignoreSearcherIds"
:allow-select="true">
</searchers>
<template #footer>
<Button label="Close"
@ -31,7 +34,7 @@
</template>
<script lang="ts">
import {Component, Vue} from "vue-property-decorator";
import {Component, Prop, Vue} from "vue-property-decorator";
import Dialog from 'primevue/dialog';
import Searchers from './Index.vue';
@ -47,6 +50,9 @@ export default class Create extends Vue {
private adding: boolean = false;
private selectedSearcher: Object = {};
@Prop({default: () => {return[];}})
public readonly ignoreSearcherIds!: Array<string>;
onSearcherSelected(searcher: Object) {
this.selectedSearcher = searcher;
}

149
resources/js/components/Searchers/Create.vue

@ -1,44 +1,86 @@
<template>
<div id="searchers-create">
<div>
<input v-model="name"
<h1> {{ searcher.id ? 'Edit' : 'New' }} searcher </h1>
<InputText v-model="name"
type="text"
placeholder="Enter searcher name"
class="input">
</InputText>
<button @click="onSave" :disabled=" ! name || rows.length === 0">
<Button @click="onSave" :disabled=" ! name || rows.length === 0">
Save
</button>
</Button>
</div>
<div v-for="(row, rowIndex) in rows" :key="`row-${rowIndex}`" class="flex-row">
<div v-for="(searcher, columnIndex) in row" :key="`column-${columnIndex}`">
<div class="box">
{{ searcher.name }}
</div>
<div v-if="standalone">
<p>
A searcher may contain multiple compounded searchers on multiple rows and columns.
</p>
<p>
Each searcher in a row is extending the searching criteria on the content resulted from the
previous row searchers.
</p>
</div>
<div v-for="(row, rowIndex) in rows"
:key="`row-${rowIndex}`"
class="searchers-row flex-row">
<div v-for="(searcher, columnIndex) in row"
:key="`column-${columnIndex}`"
class="searcher box">
<searcher-show :searcher="searcher"
:editable="true"
:standalone="false"
@deleted="onRemoveItem(rowIndex, columnIndex)">
</searcher-show>
</div>
<add-box @added="(searcher) => { onSearcherAdded(searcher, rowIndex); }">
<add-box :ignore-searcher-ids="searcher.id ? [searcher.id] : []"
@added="(searcher) => { onSearcherAdded(searcher, rowIndex); }">
</add-box>
</div>
<add-box @added="onNewRowSearcherAdded">
</add-box>
<div class="searchers-row flex-row">
<add-box @added="onNewRowSearcherAdded">
</add-box>
</div>
</div>
</template>
<script lang="ts">
import {Component, Vue} from "vue-property-decorator";
import {Component, Prop, Vue} from "vue-property-decorator";
import AddBox from './AddBox.vue';
import SearcherShow from './Show.vue';
@Component({
name: 'SearcherCreate',
components: {
AddBox,
SearcherShow,
},
})
export default class Create extends Vue {
private id: String = '';
private name: String = '';
private rows: Array<Array<Object>> = [];
private rows: Array<Array<any>> = [];
@Prop({
default: () => {
return {
id: '',
name: '',
rows: [],
};
}
})
public readonly searcher!: any;
@Prop({default: true})
public readonly standalone!: boolean;
onNewRowSearcherAdded(searcher: Object) {
const length = this.rows.push([]);
@ -46,20 +88,87 @@
this.onSearcherAdded(searcher, length - 1);
}
onSearcherAdded(searcher: Object, rowIndex: number) {
this.rows[rowIndex].push(searcher);
async onSearcherAdded(searcher: any, rowIndex: number) {
try {
const { data } = await (window as any).axios.get(`/searchers/${searcher.id}`);
this.rows[rowIndex].push(data.searcher);
} catch (e) {
}
}
async onSave() {
onSave() {
if (this.standalone) {
this.save();
return;
}
const updatedSearcher = Object.assign(this.searcher, {
name: this.name,
rows: this.rows,
});
this.$emit('updated', updatedSearcher);
}
async save() {
try {
const { data } = await (window as any).axios.post('/searchers', {
name: this.name,
rows: this.rows,
});
const searcher = this.id ? await this.update() : await this.create();
window.location.href = `/searchers/${searcher.id}`;
} catch (e) {
console.log(e);
console.log('Something went wrong.');
}
}
async update() {
const { data } = await (window as any).axios.put(`/searchers/${this.id}`, {
name: this.name,
rows: this.rows,
});
return data.searcher;
}
async create() {
const { data } = await (window as any).axios.post('/searchers', {
name: this.name,
rows: this.rows,
});
return data.searcher;
}
onRemoveItem(rowIndex: number, columnIndex: number) {
if (
this.rows[rowIndex][columnIndex].hasOwnProperty('rows') &&
this.rows[rowIndex][columnIndex].rows.length === 0
) {
this.$toast.add({
severity: 'info',
summary: `${this.searcher.name} searcher deleted`,
detail: 'The searcher has been deleted as well because it does not have any searching data..',
life: 4000,
});
}
this.rows[rowIndex].splice(columnIndex, 1);
if (this.rows[rowIndex].length === 0) {
this.rows.splice(rowIndex, 1);
}
}
created() {
// Editing.
if (this.searcher.id) {
this.id = this.searcher.id;
this.rows = this.searcher.rows;
this.name = this.searcher.name;
}
}
};
</script>

87
resources/js/components/Searchers/Index.vue

@ -1,55 +1,70 @@
<template>
<div style="display: flex; flex-direction: row;">
<div v-for="(searcher, id) in searchers"
:key="id"
@click="onSelect(id)"
class="box flex-column"
:class="{selected: id === selectedSearcherId}"
style="margin-left: 1rem;">
<div>
<span> {{ searcher.name }} </span>
</div>
<div style="margin-top: 5px; color: dodgerblue;">
<a @click.stop="onOpen(id)"> View </a>
</div>
<div>
<h1> Searchers </h1>
<div v-if=" ! allowSelect" style="margin-bottom: 1rem;">
<a href="/searchers/create">
<Button class="fc-button p-button-success">
Add searcher
</Button>
</a>
</div>
<div class="content">
<DataTable
:value.sync="searchers"
:selection.sync="selectedSearcher"
dataKey="id"
:selectionMode="allowSelect ? 'single' : ''"
class="p-datatable-md"
:paginator="true"
:rows="10"
paginatorTemplate="CurrentPageReport FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown"
currentPageReportTemplate="Showing {first} to {last} of {totalRecords}"
:rowsPerPageOptions="[10,20,50]">
<Column v-if="allowSelect" selectionMode="single" headerStyle="width: 3em"></Column>
<Column header="Name" sortable>
<template #body="slotProps">
<a @click.stop="onOpen(slotProps.data.id)" class="searcher-link">
{{ slotProps.data.name }}
</a>
</template>
</Column>
</DataTable>
</div>
</div>
</template>
<script lang="ts">
import {Component, Vue} from "vue-property-decorator";
import {Component, Prop, Vue, Watch} from "vue-property-decorator";
import {Searcher} from "@/interfaces/Searcher";
@Component({
})
export default class Create extends Vue {
private searchers: any = {};
private selectedSearcherId: string = '';
export default class Index extends Vue {
private searchers: Array<Searcher> = [];
private selectedSearcher: any = {};
@Prop({default: false})
public readonly allowSelect!: boolean;
@Prop({default: () => []})
public readonly ignoreSearcherIds!: Array<string>;
async boot() {
try {
const { data } = await (window as any).axios.get('/searchers');
this.searchers = data.searchers;
this.searchers = data.searchers.filter((searcher: Searcher) => {
return ! this.ignoreSearcherIds.includes(searcher.id);
});
} catch (e) {
}
}
onSelect(id: string) {
if (this.selectedSearcherId === id) {
this.selectedSearcherId = '';
this.$emit('selected', {});
return;
}
this.selectedSearcherId = id;
this.$emit('selected', this.searchers[id]);
}
onOpen(id: string) {
window.open(this.getURL(id), '_blank');
}
@ -58,8 +73,14 @@ export default class Create extends Vue {
return `/searchers/${id}`;
}
@Watch('selectedSearcher')
selectedSearcherIdChanged(value: any) {
this.$emit('selected', value);
}
created() {
this.boot();
}
};
</script>

191
resources/js/components/Searchers/Show.vue

@ -1,36 +1,102 @@
<template>
<div id="searchers-show">
<div>
<h3>{{ searcher.name }}</h3>
<h1 v-if="editable">
{{ searcher.hasOwnProperty('name') ? searcher.name : searcher.expression }}
</h1>
<h3 v-if=" ! editable">
{{ searcher.hasOwnProperty('name') ? searcher.name : searcher.expression }}
</h3>
<h5>{{ searcher.description }}</h5>
<a v-if="editable"
:href="`/searchers/${searcher.id}/edit`">
Edit
</a>
<div v-if="editable && ! ducklingSearcher" style="margin-bottom: 1rem;">
<a @click="onEdit">
<Button class="fc-button">
Edit
</Button>
</a>
<a @click="onDelete">
<Button class="fc-button p-button-danger">
Delete
</Button>
</a>
</div>
</div>
<div v-for="(row, rowIndex) in searcher.rows" :key="`row-${rowIndex}`" class="flex-row">
<div v-for="(searcherItem, columnIndex) in row" :key="`column-${columnIndex}`">
<div class="box is-plain">
<template v-if="searcherItem.hasOwnProperty('name')">
<searcher-show :editable="false"
:searcher="searcherItem">
</searcher-show>
</template>
<template v-else>
<b>{{ searcherItem.expression }}</b>
<p>
<!-- // Show example here, so for example the user has to input the example in order to save-->
<!-- // the regex, show highlight, so apply regex on example text-->
</p>
</template>
</div>
<div v-if=" ! editing"
v-for="(row, rowIndex) in searcher.rows"
:key="`row-${rowIndex}`"
class="searchers-row flex-row">
<div v-for="(searcherItem, columnIndex) in row"
:key="`column-${columnIndex}`"
class="searcher box"
:class="{'is-highlighted': searcher.hasOwnProperty('highlight')}">
<template v-if="searcherItem.hasOwnProperty('name')">
<searcher-show :editable=" ! standalone && editable"
:standalone="standalone"
:searcher="searcherItem"
@deleted="onDeleted(rowIndex, columnIndex)">
</searcher-show>
</template>
<template v-else>
<b>{{ searcherItem.expression }}</b>
<p>
<!-- // Show example here, so for example the user has to input the example in order to save-->
<!-- // the regex, show highlight, so apply regex on example text-->
</p>
</template>
</div>
</div>
<Dialog v-if="editing"
header="Edit searcher"
position="center"
:visible.sync="editing"
:closable="true"
:style="{width: '100vw', height: '100vh',}"
:modal="true">
<searchers-editor v-if="searcher.hasOwnProperty('name')"
:searcher="searcher"
:standalone="true"
@updated="onSearcherUpdated">
</searchers-editor>
<regex-create v-if="searcher.hasOwnProperty('expression')"
:regex="searcher.expression"
@updated="onRegexUpdated">
</regex-create>
</Dialog>
<Dialog
header="Delete searcher"
:visible.sync="deleting"
:style="{width: '50vw'}"
:contentStyle="{overflow: 'visible'}"
id="remove-searcher-dialog"
ref="remove-searcher-dialog">
<p>
Are you sure you want to delete this searcher?
</p>
<template #footer>
<Button label="Close"
icon="pi pi-times"
@click="deleting = false"
class="p-button-text"/>
<Button
label="Delete searcher"
icon="pi pi-check"
class="p-button-danger p-button-outlined p-button-sm"
@click="confirmDelete"/>
</template>
</Dialog>
</div>
</template>
@ -39,12 +105,87 @@ import {Component, Vue, Prop} from "vue-property-decorator";
@Component({
name: 'SearcherShow',
components: {
'searchers-editor': () => import('./Create.vue'),
},
})
export default class Create extends Vue {
export default class Show extends Vue {
private editing: boolean = false;
private deleting: boolean = false;
@Prop({default: {}})
public readonly searcher!: Object;
public readonly searcher!: any;
@Prop({default: true})
public readonly editable!: boolean;
@Prop({default: true})
public readonly standalone!: boolean;
onEdit() {
if (this.standalone) {
window.location.href = `/searchers/${this.searcher.id}/edit`;
return;
}
this.editing = true;
}
onDelete() {
if (this.standalone) {
this.deleting = true;
return;
}
this.$emit('deleted');
}
async confirmDelete() {
try {
await (window as any).axios.delete(`/searchers/${this.searcher.id}`);
this.$toast.add({
severity: 'success',
summary: 'Searcher deleted.',
life: 3000,
});
window.location.href = '/searchers';
} catch (e) {
console.log(e);
}
}
onDeleted(rowIndex: number, columnIndex: number) {
this.searcher.rows[rowIndex].splice(columnIndex, 1);
if (this.searcher.rows[rowIndex].length === 0) {
this.searcher.rows.splice(rowIndex, 1);
}
if (this.searcher.rows.length === 0) {
this.$emit('deleted');
}
}
onRegexUpdated(regex: string) {
this.$set(this.searcher, 'expression', regex);
this.editing = false;
}
onSearcherUpdated(searcher: any) {
this.$set(this.searcher, 'name', searcher.name);
this.$set(this.searcher, 'rows', searcher.rows);
this.editing = false;
}
get ducklingSearcher(): boolean {
return this.searcher.type !== 'custom' && ! this.searcher.hasOwnProperty('expression');
}
};
</script>

19
resources/js/components/layout/Header.vue

@ -2,23 +2,30 @@
<div class="header">
<!-- Left side of header -->
<div class="left">
<Button class="p-button-primary" label="Search and Displace" />
<Button @click="onHomeButtonClick" class="p-button-primary" label="Search and Displace" />
</div>
<!-- Right side of header -->
<div class="right"></div>
<div class="right">
<a href="/regex/create" style="margin-right: 1rem;">
<Button class="fc-button">Add regex</Button>
</a>
<a href="/searchers">
<Button class="fc-button">Searchers</Button>
</a>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import Component from 'vue-class-component';
@Component
export default class AppHeader extends Vue {
created() {}
onHomeButtonClick() {
window.location.href = '/';
}
}
</script>

5
resources/js/interfaces/Searcher.ts

@ -0,0 +1,5 @@
export interface Searcher {
id: string;
name: string,
description: string,
}

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

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

24
resources/js/services/ApiService.ts

@ -1,11 +1,10 @@
import axios from 'axios';
import FileUploadResponse from '@interfaces/responses/FileUploadResponse';
import FileStatusResponse from '@interfaces/responses/FileStatusResponse';
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';
private readonly baseUrl: string = window.location.origin;
/** @type { [key:string] : string; } */
private readonly apiRoutes = {
@ -14,7 +13,8 @@ export default class ApiService {
searchAndDisplace: this.baseUrl + '/search-and-displace'
};
constructor() {}
constructor() {
}
/**
* Upload a file to the server and return its response.
@ -26,8 +26,7 @@ export default class ApiService {
*
* @returns {Promise<FileUploadResponse>} The response from the server
*/
public async uploadFile(file: File): Promise<FileUploadResponse>
{
public async uploadFile(file: File): Promise<FileUploadResponse> {
let formData = new FormData();
formData.append('file', file);
@ -57,8 +56,7 @@ export default class ApiService {
*
* @throws
*/
public async getFileData(fileId: string): Promise<FileStatusResponse>
{
public async getFileData(fileId: string): Promise<FileStatusResponse> {
try {
let response = await axios.get<FileStatusResponse>(this.apiRoutes.searchAndDisplace + `/${fileId}`);
@ -74,8 +72,7 @@ export default class ApiService {
* @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; }>)
{
public async filterDocument(content: string, searchers: Array<{ key: string; replace_with: string; }>) {
try {
let response = await axios.post(
this.apiRoutes.searchAndDisplace,
@ -92,8 +89,7 @@ export default class ApiService {
}
public async convertFile(content:string)
{
public async convertFile(content: string) {
try {
let response = await axios.post(
this.apiRoutes.fileDownload,
@ -107,4 +103,4 @@ export default class ApiService {
throw err;
}
}
}
}

21
resources/sass/app.sass

@ -7,9 +7,9 @@
@import '~primeflex/src/_spacing'
@import '~primeflex/src/_elevation'
// @import '~primevue/resources/themes/fluent-light/theme.css'
@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/primevue.min.css'
@import '~primeicons/primeicons.css'
@ -61,10 +61,16 @@ body
&.is-plain
cursor: default
&.is-highlighted
border-color: #50b0ff
&.auto
width: auto
min-width: 100px
.searcher.box
align-items: flex-start
.flex-row
display: flex
flex-direction: row
@ -77,3 +83,14 @@ body
display: flex
align-items: center
justify-content: center
.searcher-link
&:hover
color: dodgerblue
cursor: pointer
.searchers-row
background: #f3f3f3
padding: 1rem
border-radius: 5px
margin-bottom: 1rem

7
resources/sass/components/regex/create/_index.sass

@ -4,12 +4,17 @@
@import 'references'
#regex-create
.regex-header
margin-bottom: 5rem
.regex-box
background-color: #001221
display: flex
width: 850px
height: 500px
margin: 5rem auto
margin-left: auto
margin-right: auto
margin-bottom: 5rem
position: relative
border-radius: 5px
transition: all 0.25s linear

6
resources/views/app.blade.php

@ -15,10 +15,10 @@
<div id="app">
<app-header></app-header>
<Toast position="top-right"></Toast>
<div class="page-wrapper">
<div class="content">
@yield('content')
</div>
@yield('content')
</div>
<app-footer></app-footer>

10
resources/views/pages/home.blade.php

@ -1,13 +1,5 @@
@extends('app')
@section('content')
<div class="page-wrapper">
<app-header></app-header>
<div class="content">
<home :searchers="{{ json_encode($searchers) }}"></home>
</div>
</div>
<app-footer></app-footer>
<home :searchers="{{ json_encode($searchers) }}"></home>
@endsection

7
resources/views/pages/searchers/edit.blade.php

@ -0,0 +1,7 @@
@extends('app')
@section('content')
<searchers-create :searcher="{{ json_encode($searcher) }}"></searchers-create>
@endsection

7
resources/views/pages/searchers/index.blade.php

@ -0,0 +1,7 @@
@extends('app')
@section('content')
<searchers-index></searchers-index>
@endsection

3
routes/web.php

@ -16,9 +16,6 @@ use Illuminate\Support\Facades\Route;
Route::get('/', 'HomeController@index');
Route::get('/search-and-displace/{id}', 'SearchAndDisplaceController@show');
Route::post('/search-and-displace', 'SearchAndDisplaceController@store');
Route::get('/file/download/{path}', [
FileController::class,
'download'

10261
yarn.lock
File diff suppressed because it is too large
View File

Loading…
Cancel
Save