Browse Source

Minor UI updates

master
Radu Liviu Carjan 3 years ago
parent
commit
e70a5c8bba
  1. 45
      app/Http/Controllers/FileController.php
  2. 68
      app/Http/Controllers/FilterController.php
  3. 24
      app/Http/Controllers/PagesController.php
  4. 42906
      package-lock.json
  5. 15
      package.json
  6. 572
      public/css/app.css
  7. 30790
      public/js/app.js
  8. 2
      resources/js/SearchDisplace/Cookie.ts
  9. 36
      resources/js/app.js
  10. 64
      resources/js/app.ts
  11. 12
      resources/js/bootstrap.ts
  12. 125
      resources/js/components/Home.vue
  13. 157
      resources/js/components/ProcessFile.vue
  14. 69
      resources/js/components/helpers/Filter.vue
  15. 13
      resources/js/components/layout/Footer.vue
  16. 15
      resources/js/components/layout/Header.vue
  17. 6
      resources/js/interfaces/FileData.ts
  18. 7
      resources/js/interfaces/FilterInterface.ts
  19. 4
      resources/js/interfaces/FilterOptions.ts
  20. 13
      resources/js/shims-tsx.d.ts
  21. 4
      resources/js/shims-vue.d.ts
  22. 4
      resources/views/app.blade.php
  23. 6
      resources/views/pages/home.blade.php
  24. 52
      routes/api.php
  25. 10
      routes/web.php
  26. 3
      run-dev.sh
  27. 38
      tsconfig.json
  28. 13
      webpack.mix.js
  29. 4272
      yarn.lock

45
app/Http/Controllers/FileController.php

@ -0,0 +1,45 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Storage;
class FileController extends Controller
{
/**
*
* @return JsonResponse
*
* @throws BindingResolutionException
*/
public function create(): JsonResponse
{
$file = request()->file('file');
$filename = time() . $file->getClientOriginalName();
$path = Storage::putFileAs('files/', $file, $filename);
return response()->json([
'file' => $file->getClientOriginalName(),
'id' => $filename,
'path' => $path
]);
}
/**
*
* @param string $id
*
* @return JsonResponse
*
* @throws BindingResolutionException
*/
public function get(string $id): JsonResponse
{
return response()->json(['success']);
}
}

68
app/Http/Controllers/FilterController.php

@ -0,0 +1,68 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Http\JsonResponse;
class FilterController extends Controller
{
public static array $filters = [
'phone_number' => [
'display_name' => 'Phone number',
'options' => [
'replace_with' => [
'#', '*', 'x',
]
]
],
'email' => [
'display_name' => 'Email',
'options' => [
'replace_with' => [
'#', '*', 'x',
],
'include_domain' => [
true, false
]
]
]
];
/**
* Create a new filter
*
* @return JsonResponse
*
* @throws BindingResolutionException
*/
public function create(): JsonResponse
{
return response()->json([
'result' => 'success'
]);
}
/**
* Get one or all the filters we have
*
* @param string|null $id The ID of the filter. Null if we want all of them
*
* @return JsonResponse
*
* @throws BindingResolutionException
*/
public function get(string $id = null): JsonResponse
{
if ($id === null) {
return response()->json(static::$filters);
}
if (!empty(static::$filters[$id])) {
return response()-> json(static::$filters[$id]);
}
abort(404);
}
}

24
app/Http/Controllers/PagesController.php

@ -0,0 +1,24 @@
<?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);
}
}

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

15
package.json

@ -10,19 +10,28 @@
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --config=node_modules/laravel-mix/setup/webpack.config.js"
},
"devDependencies": {
"axios": "^0.19",
"@vue/cli-plugin-babel": "^3.8.0",
"@vue/cli-plugin-typescript": "^3.8.0",
"@vue/cli-service": "^3.8.0",
"axios": "^0.21",
"cross-env": "^7.0",
"laravel-mix": "^5.0.1",
"laravel-mix": "^6.0.19",
"lodash": "^4.17.19",
"resolve-url-loader": "^3.1.0",
"sass": "^1.15.2",
"sass-loader": "^8.0.0",
"ts-loader": "^8.1.0",
"typescript": "~3.8.3",
"vue-loader": "^15.9.6",
"vue-template-compiler": "^2.6.12"
},
"dependencies": {
"primeflex": "^2.0.0",
"primeicons": "^4.1.0",
"primevue": "^2.4.1",
"vue": "^2.6.12"
"tsconfig-paths-webpack-plugin": "^3.5.1",
"vue": "^2.6.12",
"vue-class-component": "^7.2.6",
"vue-property-decorator": "^8.1.0"
}
}

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

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

2
resources/js/SearchDisplace/Cookie.js → resources/js/SearchDisplace/Cookie.ts

@ -2,7 +2,7 @@ class Cookie {
/**
* alternative: get cookie by name with using a regular expression
*/
static getByName(name) {
static getByName(name: string) {
const pair = document.cookie.match(new RegExp(name + '=([^;]+)'));
return !!pair ? pair[1] : null;

36
resources/js/app.js

@ -1,36 +0,0 @@
require('./bootstrap');
window.Vue = require('vue');
/**
* Import vendor classes
*/
import PrimeVue from 'primevue/config';
import Button from 'primevue/button';
import Card from 'primevue/card';
import Sidebar from 'primevue/sidebar';
import OrderList from 'primevue/orderlist';
// Own components
import Header from './layout/Header';
import Footer from './layout/Footer';
import Home from './components/Home';
window.Event = new Vue();
Vue.use(PrimeVue, {
ripple: true,
});
Vue.component('Button', Button)
.component('Card', Card)
.component('Sidebar', Sidebar)
.component('OrderList', OrderList);
Vue.component('home', Home)
.component('Header', Header)
.component('Footer', Footer);
new Vue({
el: '#app',
});

64
resources/js/app.ts

@ -0,0 +1,64 @@
import './bootstrap';
import Vue from 'vue';
/**
* Import vendor classes
*/
import PrimeVue from 'primevue/config';
import Button from 'primevue/button';
import Panel from 'primevue/panel';
import Card from 'primevue/card';
import FileUpload from 'primevue/fileupload';
import BlockUI from 'primevue/blockui';
import Sidebar from 'primevue/sidebar';
import OrderList from 'primevue/orderlist';
import Chip from 'primevue/chip';
import Divider from 'primevue/divider';
import Listbox from 'primevue/listbox';
import Toolbar from 'primevue/toolbar';
import Skeleton from 'primevue/skeleton';
import ToastService from 'primevue/toastservice';
import Toast from 'primevue/toast';
import Fieldset from 'primevue/fieldset';
// Own components
import AppHeader from './components/layout/Header.vue';
import AppFooter from './components/layout/Footer.vue';
import Home from './components/Home.vue';
import ProcessFile from './components/ProcessFile.vue';
import Filter from './components/helpers/Filter.vue';
import SelectButton from 'primevue/selectbutton';
Vue.use(PrimeVue, {
ripple: true,
});
Vue.use(ToastService);
Vue.component('Button', Button);
Vue.component('Panel', Panel);
Vue.component('Card', Card);
Vue.component('FileUpload', FileUpload);
Vue.component('BlockUI', BlockUI);
Vue.component('Sidebar', Sidebar);
Vue.component('OrderList', OrderList);
Vue.component('Chip', Chip);
Vue.component('Divider', Divider);
Vue.component('Listbox', Listbox);
Vue.component('Toolbar', Toolbar);
Vue.component('Skeleton', Skeleton);
Vue.component('Toast', Toast);
Vue.component('SelectButton', SelectButton);
Vue.component('Fieldset', Fieldset);
Vue.component('home', Home);
Vue.component('process-file', ProcessFile);
Vue.component('filter-view', Filter);
Vue.component('app-header', AppHeader);
Vue.component('app-footer', AppFooter);
new Vue({
el: '#app',
});

12
resources/js/bootstrap.js → resources/js/bootstrap.ts

@ -1,6 +1,6 @@
import Cookie from './SearchDisplace/Cookie';
window._ = require('lodash');
// window._ = require('lodash');
/**
* We'll load the axios HTTP library which allows us to easily issue requests
@ -8,14 +8,14 @@ window._ = require('lodash');
* CSRF token as a header based on the value of the "XSRF" token cookie.
*/
window.axios = require('axios');
import axios from "axios";
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
let token = document.head.querySelector('meta[name="csrf-token"]');
let token: HTMLMetaElement | null = document.head.querySelector('meta[name="csrf-token"]');
if (token) {
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
} else {
console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
}
@ -30,4 +30,4 @@ if (token) {
// return request;
// }, (error) => {
// return Promise.reject(error)
// });
// });

125
resources/js/components/Home.vue

@ -1,11 +1,13 @@
<template>
<div class="wrap">
<Panel header="Header">
<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="myUploader"
@uploader="uploadFile"
>
<template #empty>
<p>Drag and drop files to here to upload.</p>
@ -13,47 +15,86 @@
</FileUpload>
</Panel>
<!-- <process-file :files="files"></process-file>-->
<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>
import FileUpload from 'primevue/fileupload';
import BlockUI from 'primevue/blockui';
import Panel from 'primevue/panel';
export default {
data() {
return {
uiBlocked: false,
files: [],
};
},
components: {
FileUpload,
BlockUI,
Panel,
},
methods: {
myUploader(event) {
this.uiBlocked = true;
event.files.forEach((file) => {
// Handle the file
this.files.push(file);
});
setTimeout(() => {
this.uiBlocked = false;
// this.$router.push('process-file');
}, 500);
},
},
};
<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>

157
resources/js/components/ProcessFile.vue

@ -1,107 +1,84 @@
<template>
<div class="wrap">
<Sidebar v-model:visible="filesSidebarVisible" class="p-sidebar-lg">
<Listbox v-model="selectedFile" :options="files" optionLabel="file">
<template #option="slotProps">
<div>
<span>{{ slotProps.option.file.name }}</span>
<Chip :label="slotProps.option.status" />
</div>
</template>
</Listbox>
</Sidebar>
<div class="p-d-flex p-flex-row p-jc-between p-ai-stretch">
<Panel
v-if="selectedFile !== null"
class="p-mr-2 p-as-stretch file-card"
>
<template #header>
{{ selectedFile.file.name }}
</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>
<OrderList
v-model="selectedFilters"
listStyle="height:auto"
dataKey="file"
>
<template #header> Selected filters </template>
<template #item="slotProps">
<div class="p-item" @click="selectItem(item)">
<div>
<span class="p-caritem-vin">{{
slotProps.item.file.name
}}</span>
<Chip :label="slotProps.item.status" />
</div>
</div>
</template>
</OrderList>
</Panel>
<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>
<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> CONTENT </template>
</Card>
</div>
<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>
export default {
data() {
return {
uiBlocked: false,
filesSidebarVisible: false,
selectedFile: null,
selectedFilters: [],
};
},
<script lang="ts">
import axios from 'axios';
import { Vue, Component, Prop } from 'vue-property-decorator';
import { FileData } from '../interfaces/FileData';
import { FilterInterface } from '../interfaces/FilterInterface';
props: {
files: {
required: true,
type: Array,
},
},
@Component
export default class ProcessFile extends Vue {
methods: {
selectItem(item) {
console.log('SELECTING ITEM: ', item);
this.selectedFile = item;
this.filesSidebarVisible = false;
}
},
@Prop({ default: null })
public readonly file!: FileData|null;
created() {
this.files = [];
@Prop({ default: [] })
public readonly filters!: { [keys:string]: FilterInterface }
setTimeout(
() => {
this.filesSidebarVisible = true;
},
500,
);
},
};
private selectedFile: File|null = null;
private selectedFilters = [];
/**
*
*/
created() {
console.log('FILE: ', this.file);
console.log('FILTERS: ', this.filters);
}
}
</script>
<style>
.file-card,
<style lang="scss">
.file-card {
flex: 0 1 66%;
}
.filters-card {
flex: 0 1 48%;
flex: 0 1 32%
}
</style>

69
resources/js/components/helpers/Filter.vue

@ -0,0 +1,69 @@
<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>

13
resources/js/layout/Footer.vue → resources/js/components/layout/Footer.vue

@ -7,3 +7,16 @@
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import Component from 'vue-class-component';
@Component
export default class AppFooter extends Vue {
created() {}
}
</script>

15
resources/js/layout/Header.vue → resources/js/components/layout/Header.vue

@ -8,4 +8,17 @@
<!-- Right side of header -->
<div class="right"></div>
</div>
</template>a
</template>
<script lang="ts">
import Vue from 'vue';
import Component from 'vue-class-component';
@Component
export default class AppHeader extends Vue {
created() {}
}
</script>

6
resources/js/interfaces/FileData.ts

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

7
resources/js/interfaces/FilterInterface.ts

@ -0,0 +1,7 @@
import { FilterOptions } from "./FilterOptions";
export interface FilterInterface
{
display_name: string;
options: FilterOptions;
}

4
resources/js/interfaces/FilterOptions.ts

@ -0,0 +1,4 @@
export interface FilterOptions
{
[keys: string]: string[]
}

13
resources/js/shims-tsx.d.ts

@ -0,0 +1,13 @@
import Vue, { VNode } from 'vue';
declare global {
namespace JSX {
// tslint:disable no-empty-interface
interface Element extends VNode {}
// tslint:disable no-empty-interface
interface ElementClass extends Vue {}
interface IntrinsicElements {
[elem: string]: any;
}
}
}

4
resources/js/shims-vue.d.ts

@ -0,0 +1,4 @@
declare module '*.vue' {
import Vue from 'vue'
export default Vue
}

4
resources/views/app.blade.php

@ -15,9 +15,7 @@
<div id="app">
{{-- <navbar></navbar>--}}
<div id="content">
@yield('content')
</div>
@yield('content')
</div>
<script src="{{ mix('js/app.js') }}" defer></script>

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

@ -3,12 +3,12 @@
@section('content')
<div class="page-wrapper">
<Header></Header>
<app-header></app-header>
<div class="content">
<home></home>
<home :filters="{{ json_encode($filters) }}"></home>
</div>
</div>
<Footer></Footer>
<app-footer></app-footer>
@endsection

52
routes/api.php

@ -1,6 +1,8 @@
<?php
use Illuminate\Http\Request;
use App\Http\Controllers\FileController;
use App\Http\Controllers\FilterController;
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
@ -13,6 +15,48 @@ use Illuminate\Http\Request;
|
*/
Route::middleware('auth:api')->get('/user', function (Request $request) {
return $request->user();
});
# File routes
// Route::name('file.')->prefix('file')->middleware('auth:api')->group(
Route::name('file.')->prefix('file')->group(
function() {
# POST "/file" (used to create/upload a file)
Route::post('/', [
FileController::class,
'create'
])->name('create');
# GET "/file/{id}" (to retrieve data about a file)
Route::get('/{id}', [
FileController::class,
'get'
])->name('get');
}
);
# File routes
// Route::name('filter.')->prefix('filter')->middleware('auth:api')->group(
Route::name('filter.')->prefix('filter')->group(
function() {
# POST "/file" (used to create/upload a file)
Route::post('/', [
FilterController::class,
'create'
])->name('create');
# GET "/filter/all" (to retrieve data about a filter)
Route::get('/all', [
FilterController::class,
'get'
])->name('get.all');
# GET "/filter/{id}" (to retrieve data about a filter)
Route::get('/{id}', [
FilterController::class,
'get'
])->name('get');
}
);

10
routes/web.php

@ -1,5 +1,8 @@
<?php
use App\Http\Controllers\PagesController;
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| Web Routes
@ -11,8 +14,9 @@
|
*/
Route::get('/', function () {
return view('pages.home');
});
Route::get('/', [
PagesController::class,
'home'
]);
Route::webhooks('/webhooks', 'default');

3
run-dev.sh

@ -0,0 +1,3 @@
#!/bin/zsh
clear && npm run watch

38
tsconfig.json

@ -0,0 +1,38 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",
"experimentalDecorators": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"baseUrl": ".",
"types": [
"webpack-env"
],
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
],
"paths": {
"@/*": [
"resources/js/*"
],
"@components/*": [
"resources/js/components/*"
],
},
},
"include": [
"resources/js/**/*"
],
"exclude": [
"node_modules"
]
}

13
webpack.mix.js

@ -1,4 +1,5 @@
const mix = require('laravel-mix');
const path = require('path');
/*
|--------------------------------------------------------------------------
@ -11,5 +12,15 @@ const mix = require('laravel-mix');
|
*/
mix.js('resources/js/app.js', 'public/js')
mix.webpackConfig({
resolve: {
alias: {
"@/*": path.resolve(__dirname, "resources/js/"),
"@components/*": path.resolve(__dirname, "resources/js/components/"),
}
}
});
mix.ts('resources/js/app.ts', 'public/js')
.vue()
.sass('resources/sass/app.scss', 'public/css');

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

Loading…
Cancel
Save