pwa, save sample view preferences

This commit is contained in:
VLE2FE 2020-08-12 17:38:12 +02:00
parent c681c5d881
commit 5f6411c613
22 changed files with 176 additions and 38 deletions

View File

@ -1,4 +1,4 @@
# Digital fingerprint of plastics - UI # DeFinMa - UI
This is the Angular front end for the digital fingerprint of plastics web page hosted in the bic This is the Angular front end for the digital fingerprint of plastics web page hosted in the bic

View File

@ -3,7 +3,7 @@
"version": 1, "version": 1,
"newProjectRoot": "projects", "newProjectRoot": "projects",
"projects": { "projects": {
"UI": { "definma": {
"projectType": "application", "projectType": "application",
"schematics": { "schematics": {
"@schematics/angular:component": { "@schematics/angular:component": {
@ -17,7 +17,7 @@
"build": { "build": {
"builder": "@angular-devkit/build-angular:browser", "builder": "@angular-devkit/build-angular:browser",
"options": { "options": {
"outputPath": "dist/UI", "outputPath": "dist/definma",
"index": "src/index.html", "index": "src/index.html",
"main": "src/main.ts", "main": "src/main.ts",
"polyfills": "src/polyfills.ts", "polyfills": "src/polyfills.ts",
@ -30,7 +30,8 @@
"glob": "**/*", "glob": "**/*",
"input": "./node_modules/@inst-iot/bosch-angular-ui-components/assets", "input": "./node_modules/@inst-iot/bosch-angular-ui-components/assets",
"output": "./assets" "output": "./assets"
} },
"src/manifest.webmanifest"
], ],
"styles": [ "styles": [
"src/styles.scss" "src/styles.scss"
@ -64,26 +65,28 @@
"maximumWarning": "6kb", "maximumWarning": "6kb",
"maximumError": "10kb" "maximumError": "10kb"
} }
] ],
"serviceWorker": true,
"ngswConfigPath": "ngsw-config.json"
} }
} }
}, },
"serve": { "serve": {
"builder": "@angular-devkit/build-angular:dev-server", "builder": "@angular-devkit/build-angular:dev-server",
"options": { "options": {
"browserTarget": "UI:build", "browserTarget": "definma:build",
"proxyConfig": "src/proxy.conf.json" "proxyConfig": "src/proxy.conf.json"
}, },
"configurations": { "configurations": {
"production": { "production": {
"browserTarget": "UI:build:production" "browserTarget": "definma:build:production"
} }
} }
}, },
"extract-i18n": { "extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n", "builder": "@angular-devkit/build-angular:extract-i18n",
"options": { "options": {
"browserTarget": "UI:build" "browserTarget": "definma:build"
} }
}, },
"test": { "test": {
@ -95,7 +98,8 @@
"karmaConfig": "karma.conf.js", "karmaConfig": "karma.conf.js",
"assets": [ "assets": [
"src/favicon.ico", "src/favicon.ico",
"src/assets" "src/assets",
"src/manifest.webmanifest"
], ],
"styles": [ "styles": [
"src/styles.scss" "src/styles.scss"
@ -120,15 +124,15 @@
"builder": "@angular-devkit/build-angular:protractor", "builder": "@angular-devkit/build-angular:protractor",
"options": { "options": {
"protractorConfig": "e2e/protractor.conf.js", "protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "UI:serve" "devServerTarget": "definma:serve"
}, },
"configurations": { "configurations": {
"production": { "production": {
"devServerTarget": "UI:serve:production" "devServerTarget": "definma:serve:production"
} }
} }
} }
} }
}}, }},
"defaultProject": "UI" "defaultProject": "definma"
} }

View File

@ -1,4 +1,4 @@
pushstate: enabled pushstate: enabled
force_https: true force_https: true
root: UI root: definma
location_include: ../../*.conf location_include: ../../*.conf

View File

@ -16,7 +16,7 @@ module.exports = function (config) {
clearContext: false // leave Jasmine Spec Runner output visible in browser clearContext: false // leave Jasmine Spec Runner output visible in browser
}, },
coverageIstanbulReporter: { coverageIstanbulReporter: {
dir: require('path').join(__dirname, './coverage/UI'), dir: require('path').join(__dirname, './coverage/definma'),
reports: ['html', 'lcovonly', 'text-summary'], reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true fixWebpackSourcePaths: true
}, },

29
ngsw-config.json Normal file
View File

@ -0,0 +1,29 @@
{
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
"index": "/index.html",
"assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html",
"/manifest.webmanifest",
"/*.css",
"/*.js"
]
}
}, {
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**",
"/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
]
}
}
]
}

9
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "ui", "name": "definma",
"version": "0.0.0", "version": "0.5.5",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -630,6 +630,11 @@
"resolved": "https://registry.npmjs.org/@angular/router/-/router-9.1.7.tgz", "resolved": "https://registry.npmjs.org/@angular/router/-/router-9.1.7.tgz",
"integrity": "sha512-ycrkhkCbfOMCe9PngFjnyk8nH5jt0Kyb2NPtjmaGOtSCuZBZ0kOU0rQGmQnj3d2PiT0Yir59S8eEAf3Fh0iDuw==" "integrity": "sha512-ycrkhkCbfOMCe9PngFjnyk8nH5jt0Kyb2NPtjmaGOtSCuZBZ0kOU0rQGmQnj3d2PiT0Yir59S8eEAf3Fh0iDuw=="
}, },
"@angular/service-worker": {
"version": "9.1.12",
"resolved": "https://registry.npmjs.org/@angular/service-worker/-/service-worker-9.1.12.tgz",
"integrity": "sha512-UsmhPfhIYq9LanuFT6V7Kmkr5Vjl2CMjKkL1gqhChkywNW4vavAYsBkuVji8P76ZKliq41TCG01z6xIAWji8QA=="
},
"@babel/code-frame": { "@babel/code-frame": {
"version": "7.8.3", "version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz",

View File

@ -23,6 +23,7 @@
"@angular/platform-browser": "~9.1.7", "@angular/platform-browser": "~9.1.7",
"@angular/platform-browser-dynamic": "~9.1.7", "@angular/platform-browser-dynamic": "~9.1.7",
"@angular/router": "~9.1.7", "@angular/router": "~9.1.7",
"@angular/service-worker": "~9.1.7",
"@hapi/joi": "^17.1.1", "@hapi/joi": "^17.1.1",
"@inst-iot/bosch-angular-ui-components": "^0.7.2", "@inst-iot/bosch-angular-ui-components": "^0.7.2",
"angular-2-local-storage": "^3.0.2", "angular-2-local-storage": "^3.0.2",

View File

@ -2,12 +2,7 @@ import { Component, isDevMode} from '@angular/core';
import {LoginService} from './services/login.service'; import {LoginService} from './services/login.service';
import {NavigationStart, Router} from '@angular/router'; import {NavigationStart, Router} from '@angular/router';
// TODO: add multiple samples at once
// TODO: validation: DPT: filename
// TODO: filter by not completely filled/no measurements // TODO: filter by not completely filled/no measurements
// TODO: validation of samples
// TODO: centralize fetching of materials / templates, etc.
// TODO: PWA
// TODO: get rid of chart.js (+moment.js) // TODO: get rid of chart.js (+moment.js)

View File

@ -28,6 +28,8 @@ import { UsersComponent } from './users/users.component';
import { ChangelogComponent } from './changelog/changelog.component'; import { ChangelogComponent } from './changelog/changelog.component';
import { DocumentationDatabaseComponent } from './documentation-database/documentation-database.component'; import { DocumentationDatabaseComponent } from './documentation-database/documentation-database.component';
import { PredictionComponent } from './prediction/prediction.component'; import { PredictionComponent } from './prediction/prediction.component';
import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -65,7 +67,8 @@ import { PredictionComponent } from './prediction/prediction.component';
ReactiveFormsModule, ReactiveFormsModule,
FormFieldsModule, FormFieldsModule,
CommonModule, CommonModule,
ChartsModule ChartsModule,
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production })
], ],
providers: [ providers: [
ModalService, ModalService,

View File

@ -18,7 +18,6 @@
<td>Automatically generated unique id</td> <td>Automatically generated unique id</td>
<td>'5f2e63c98d1c020f8cda6e06'</td> <td>'5f2e63c98d1c020f8cda6e06'</td>
</tr> </tr>
<!-- TODO: new names-->
<tr> <tr>
<td>type</td> <td>type</td>
<td> <td>

View File

@ -22,10 +22,6 @@ import {Observable} from 'rxjs';
import {ModalService} from '@inst-iot/bosch-angular-ui-components'; import {ModalService} from '@inst-iot/bosch-angular-ui-components';
import {DataService} from '../services/data.service'; import {DataService} from '../services/data.service';
// TODO: clean up this mess !!!
// TODO: only show condition (if not set) and measurements in edit sample dialog at first
// TODO: multiple samples for base data, extend multiple measurements, conditions
@Component({ @Component({
selector: 'app-sample', selector: 'app-sample',

View File

@ -8,6 +8,7 @@ import {SampleModel} from '../models/sample.model';
import {LoginService} from '../services/login.service'; import {LoginService} from '../services/login.service';
import {ModalService} from '@inst-iot/bosch-angular-ui-components'; import {ModalService} from '@inst-iot/bosch-angular-ui-components';
import {DataService} from '../services/data.service'; import {DataService} from '../services/data.service';
import {LocalStorageService} from 'angular-2-local-storage';
interface LoadSamplesOptions { interface LoadSamplesOptions {
@ -28,8 +29,6 @@ interface KeyInterface {
styleUrls: ['./samples.component.scss'] styleUrls: ['./samples.component.scss']
}) })
// TODO: save last settings
export class SamplesComponent implements OnInit { export class SamplesComponent implements OnInit {
@ViewChild('pageSizeSelection') pageSizeSelection: ElementRef<HTMLElement>; @ViewChild('pageSizeSelection') pageSizeSelection: ElementRef<HTMLElement>;
@ -89,29 +88,39 @@ export class SamplesComponent implements OnInit {
public autocomplete: AutocompleteService, public autocomplete: AutocompleteService,
public login: LoginService, public login: LoginService,
private modalService: ModalService, private modalService: ModalService,
public d: DataService public d: DataService,
private storage: LocalStorageService
) { ) {
} }
ngOnInit(): void { ngOnInit(): void {
let loading = 7;
const onLoad = () => {
if ((--loading) <= 0) {
this.loadSamples();
}
};
this.calcFieldSelectKeys(); this.calcFieldSelectKeys();
this.d.load('materials', () => { this.d.load('materials', () => {
this.filters.filters.find(e => e.field === 'material.name').autocomplete = this.d.arr.materials.map(e => e.name); this.filters.filters.find(e => e.field === 'material.name').autocomplete = this.d.arr.materials.map(e => e.name);
this.loadSamples(); onLoad();
}); });
this.d.load('materialSuppliers', () => { this.d.load('materialSuppliers', () => {
this.filters.filters.find(e => e.field === 'material.supplier').autocomplete = this.d.arr.materialSuppliers; this.filters.filters.find(e => e.field === 'material.supplier').autocomplete = this.d.arr.materialSuppliers;
onLoad();
}); });
this.d.load('materialGroups', () => { this.d.load('materialGroups', () => {
this.filters.filters.find(e => e.field === 'material.group').autocomplete = this.d.arr.materialGroups; this.filters.filters.find(e => e.field === 'material.group').autocomplete = this.d.arr.materialGroups;
onLoad();
}); });
this.d.load('userKey'); this.d.load('userKey', onLoad);
this.d.load('conditionTemplates'); this.d.load('conditionTemplates', onLoad);
this.loadTemplateKeys('material', 'type'); this.loadTemplateKeys('material', 'type', onLoad);
this.loadTemplateKeys('measurement', 'status'); this.loadTemplateKeys('measurement', 'status', onLoad);
} }
loadTemplateKeys(collection, insertBefore) { loadTemplateKeys(collection, insertBefore, f) {
this.d.load(collection + 'Templates', () => { this.d.load(collection + 'Templates', () => {
const templateKeys = []; const templateKeys = [];
this.d.arr[collection + 'Templates'].forEach(item => { this.d.arr[collection + 'Templates'].forEach(item => {
@ -138,8 +147,8 @@ export class SamplesComponent implements OnInit {
}); });
this.keys.splice(this.keys.findIndex(e => e.id === insertBefore), 0, ...templateKeys); this.keys.splice(this.keys.findIndex(e => e.id === insertBefore), 0, ...templateKeys);
this.keys = [...this.keys]; // complete overwrite array to invoke update in rb-multiselect this.keys = [...this.keys]; // complete overwrite array to invoke update in rb-multiselect
this.updateActiveKeys(); this.loadPreferences();
this.calcFieldSelectKeys(); f();
}); });
} }
@ -156,6 +165,7 @@ export class SamplesComponent implements OnInit {
if (this.loadSamplesQueue.length <= 1) { // nothing queued up if (this.loadSamplesQueue.length <= 1) { // nothing queued up
this.sampleLoader(this.loadSamplesQueue[0]); this.sampleLoader(this.loadSamplesQueue[0]);
} }
this.storePreferences();
} }
private sampleLoader(options: LoadSamplesOptions) { // actual loading of the sample, do not call directly private sampleLoader(options: LoadSamplesOptions) { // actual loading of the sample, do not call directly
@ -256,6 +266,38 @@ export class SamplesComponent implements OnInit {
this.loadSamples({toPage: delta}); this.loadSamples({toPage: delta});
} }
storePreferences() {
const store = {
filters: {
...pick(this.filters, ['status', 'pageSize', 'toPage', 'sort']),
filters: this.filters.filters.map(e => pick(e, ['field', 'active', 'mode', 'values']))
},
keys: this.keys.map(e => pick(e, ['id', 'active']))
};
this.storage.set('samplesPreferences', store);
}
loadPreferences() {
const store: any = this.storage.get('samplesPreferences');
if (store) {
this.filters = {...this.filters, ...pick(store.filters, ['status', 'pageSize', 'toPage', 'sort'])};
store.filters.filters.forEach(filter => {
const filterIndex = this.filters.filters.findIndex(e => e.field === filter.field);
if (filterIndex >= 0) {
this.filters.filters[filterIndex] = {...this.filters.filters[filterIndex], ...filter};
}
});
store.keys.forEach(key => {
const keyIndex = this.keys.findIndex(e => e.id === key.id);
if (keyIndex >= 0) {
this.keys[keyIndex].active = key.active;
}
});
this.calcFieldSelectKeys();
this.updateActiveKeys();
}
}
updateFilterFields(field) { updateFilterFields(field) {
const filter = this.filters.filters.find(e => e.field === field); const filter = this.filters.filters.find(e => e.field === field);
filter.active = true; filter.active = true;
@ -390,4 +432,6 @@ export class SamplesComponent implements OnInit {
ucFirst(string) { ucFirst(string) {
return string[0].toUpperCase() + string.slice(1); return string[0].toUpperCase() + string.slice(1);
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -6,8 +6,11 @@
<base href="/"> <base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico"> <link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="manifest" href="manifest.webmanifest">
<meta name="theme-color" content="#1976d2">
</head> </head>
<body> <body>
<app-root></app-root> <app-root></app-root>
<noscript>Please enable JavaScript to continue using this application.</noscript>
</body> </body>
</html> </html>

59
src/manifest.webmanifest Normal file
View File

@ -0,0 +1,59 @@
{
"name": "definma",
"short_name": "definma",
"theme_color": "#1976d2",
"background_color": "#fafafa",
"display": "standalone",
"scope": "./",
"start_url": "./",
"icons": [
{
"src": "assets/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-384x384.png",
"sizes": "384x384",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable any"
}
]
}