diff --git a/README.md b/README.md index 00b887f..b098ff9 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/angular.json b/angular.json index 023e2a4..5917785 100644 --- a/angular.json +++ b/angular.json @@ -3,7 +3,7 @@ "version": 1, "newProjectRoot": "projects", "projects": { - "UI": { + "definma": { "projectType": "application", "schematics": { "@schematics/angular:component": { @@ -17,7 +17,7 @@ "build": { "builder": "@angular-devkit/build-angular:browser", "options": { - "outputPath": "dist/UI", + "outputPath": "dist/definma", "index": "src/index.html", "main": "src/main.ts", "polyfills": "src/polyfills.ts", @@ -30,7 +30,8 @@ "glob": "**/*", "input": "./node_modules/@inst-iot/bosch-angular-ui-components/assets", "output": "./assets" - } + }, + "src/manifest.webmanifest" ], "styles": [ "src/styles.scss" @@ -64,26 +65,28 @@ "maximumWarning": "6kb", "maximumError": "10kb" } - ] + ], + "serviceWorker": true, + "ngswConfigPath": "ngsw-config.json" } } }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { - "browserTarget": "UI:build", + "browserTarget": "definma:build", "proxyConfig": "src/proxy.conf.json" }, "configurations": { "production": { - "browserTarget": "UI:build:production" + "browserTarget": "definma:build:production" } } }, "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { - "browserTarget": "UI:build" + "browserTarget": "definma:build" } }, "test": { @@ -95,7 +98,8 @@ "karmaConfig": "karma.conf.js", "assets": [ "src/favicon.ico", - "src/assets" + "src/assets", + "src/manifest.webmanifest" ], "styles": [ "src/styles.scss" @@ -120,15 +124,15 @@ "builder": "@angular-devkit/build-angular:protractor", "options": { "protractorConfig": "e2e/protractor.conf.js", - "devServerTarget": "UI:serve" + "devServerTarget": "definma:serve" }, "configurations": { "production": { - "devServerTarget": "UI:serve:production" + "devServerTarget": "definma:serve:production" } } } } }}, - "defaultProject": "UI" + "defaultProject": "definma" } diff --git a/cf_config/Staticfile b/cf_config/Staticfile index 9357ba4..aceedb5 100644 --- a/cf_config/Staticfile +++ b/cf_config/Staticfile @@ -1,4 +1,4 @@ pushstate: enabled force_https: true -root: UI +root: definma location_include: ../../*.conf diff --git a/karma.conf.js b/karma.conf.js index 09aabe9..6df3baf 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -16,7 +16,7 @@ module.exports = function (config) { clearContext: false // leave Jasmine Spec Runner output visible in browser }, coverageIstanbulReporter: { - dir: require('path').join(__dirname, './coverage/UI'), + dir: require('path').join(__dirname, './coverage/definma'), reports: ['html', 'lcovonly', 'text-summary'], fixWebpackSourcePaths: true }, diff --git a/ngsw-config.json b/ngsw-config.json new file mode 100644 index 0000000..4ddf1a7 --- /dev/null +++ b/ngsw-config.json @@ -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)" + ] + } + } + ] +} diff --git a/package-lock.json b/package-lock.json index 352f42d..79fc0f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { - "name": "ui", - "version": "0.0.0", + "name": "definma", + "version": "0.5.5", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -630,6 +630,11 @@ "resolved": "https://registry.npmjs.org/@angular/router/-/router-9.1.7.tgz", "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": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", diff --git a/package.json b/package.json index fe7f7ab..2b41b23 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@angular/platform-browser": "~9.1.7", "@angular/platform-browser-dynamic": "~9.1.7", "@angular/router": "~9.1.7", + "@angular/service-worker": "~9.1.7", "@hapi/joi": "^17.1.1", "@inst-iot/bosch-angular-ui-components": "^0.7.2", "angular-2-local-storage": "^3.0.2", diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 9946350..320f25c 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -2,12 +2,7 @@ import { Component, isDevMode} from '@angular/core'; import {LoginService} from './services/login.service'; 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: validation of samples -// TODO: centralize fetching of materials / templates, etc. -// TODO: PWA // TODO: get rid of chart.js (+moment.js) diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 779a576..44f4513 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -28,6 +28,8 @@ import { UsersComponent } from './users/users.component'; import { ChangelogComponent } from './changelog/changelog.component'; import { DocumentationDatabaseComponent } from './documentation-database/documentation-database.component'; import { PredictionComponent } from './prediction/prediction.component'; +import { ServiceWorkerModule } from '@angular/service-worker'; +import { environment } from '../environments/environment'; @NgModule({ declarations: [ @@ -65,7 +67,8 @@ import { PredictionComponent } from './prediction/prediction.component'; ReactiveFormsModule, FormFieldsModule, CommonModule, - ChartsModule + ChartsModule, + ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }) ], providers: [ ModalService, diff --git a/src/app/documentation-database/documentation-database.component.html b/src/app/documentation-database/documentation-database.component.html index d957144..32296bc 100644 --- a/src/app/documentation-database/documentation-database.component.html +++ b/src/app/documentation-database/documentation-database.component.html @@ -18,7 +18,6 @@ Automatically generated unique id '5f2e63c98d1c020f8cda6e06' - type diff --git a/src/app/sample/sample.component.ts b/src/app/sample/sample.component.ts index 67c2b85..57cf409 100644 --- a/src/app/sample/sample.component.ts +++ b/src/app/sample/sample.component.ts @@ -22,10 +22,6 @@ import {Observable} from 'rxjs'; import {ModalService} from '@inst-iot/bosch-angular-ui-components'; 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({ selector: 'app-sample', diff --git a/src/app/samples/samples.component.ts b/src/app/samples/samples.component.ts index 51f3fbb..cf0aa27 100644 --- a/src/app/samples/samples.component.ts +++ b/src/app/samples/samples.component.ts @@ -8,6 +8,7 @@ import {SampleModel} from '../models/sample.model'; import {LoginService} from '../services/login.service'; import {ModalService} from '@inst-iot/bosch-angular-ui-components'; import {DataService} from '../services/data.service'; +import {LocalStorageService} from 'angular-2-local-storage'; interface LoadSamplesOptions { @@ -28,8 +29,6 @@ interface KeyInterface { styleUrls: ['./samples.component.scss'] }) -// TODO: save last settings - export class SamplesComponent implements OnInit { @ViewChild('pageSizeSelection') pageSizeSelection: ElementRef; @@ -89,29 +88,39 @@ export class SamplesComponent implements OnInit { public autocomplete: AutocompleteService, public login: LoginService, private modalService: ModalService, - public d: DataService + public d: DataService, + private storage: LocalStorageService ) { } ngOnInit(): void { + let loading = 7; + const onLoad = () => { + if ((--loading) <= 0) { + this.loadSamples(); + } + }; + this.calcFieldSelectKeys(); this.d.load('materials', () => { 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.filters.filters.find(e => e.field === 'material.supplier').autocomplete = this.d.arr.materialSuppliers; + onLoad(); }); this.d.load('materialGroups', () => { this.filters.filters.find(e => e.field === 'material.group').autocomplete = this.d.arr.materialGroups; + onLoad(); }); - this.d.load('userKey'); - this.d.load('conditionTemplates'); - this.loadTemplateKeys('material', 'type'); - this.loadTemplateKeys('measurement', 'status'); + this.d.load('userKey', onLoad); + this.d.load('conditionTemplates', onLoad); + this.loadTemplateKeys('material', 'type', onLoad); + this.loadTemplateKeys('measurement', 'status', onLoad); } - loadTemplateKeys(collection, insertBefore) { + loadTemplateKeys(collection, insertBefore, f) { this.d.load(collection + 'Templates', () => { const templateKeys = []; 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 = [...this.keys]; // complete overwrite array to invoke update in rb-multiselect - this.updateActiveKeys(); - this.calcFieldSelectKeys(); + this.loadPreferences(); + f(); }); } @@ -156,6 +165,7 @@ export class SamplesComponent implements OnInit { if (this.loadSamplesQueue.length <= 1) { // nothing queued up this.sampleLoader(this.loadSamplesQueue[0]); } + this.storePreferences(); } 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}); } + 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) { const filter = this.filters.filters.find(e => e.field === field); filter.active = true; @@ -390,4 +432,6 @@ export class SamplesComponent implements OnInit { ucFirst(string) { return string[0].toUpperCase() + string.slice(1); } + + } diff --git a/src/assets/icons/icon-128x128.png b/src/assets/icons/icon-128x128.png new file mode 100644 index 0000000..01e3730 Binary files /dev/null and b/src/assets/icons/icon-128x128.png differ diff --git a/src/assets/icons/icon-144x144.png b/src/assets/icons/icon-144x144.png new file mode 100644 index 0000000..25da7aa Binary files /dev/null and b/src/assets/icons/icon-144x144.png differ diff --git a/src/assets/icons/icon-152x152.png b/src/assets/icons/icon-152x152.png new file mode 100644 index 0000000..65b360e Binary files /dev/null and b/src/assets/icons/icon-152x152.png differ diff --git a/src/assets/icons/icon-192x192.png b/src/assets/icons/icon-192x192.png new file mode 100644 index 0000000..10f939c Binary files /dev/null and b/src/assets/icons/icon-192x192.png differ diff --git a/src/assets/icons/icon-384x384.png b/src/assets/icons/icon-384x384.png new file mode 100644 index 0000000..6f4c7bb Binary files /dev/null and b/src/assets/icons/icon-384x384.png differ diff --git a/src/assets/icons/icon-512x512.png b/src/assets/icons/icon-512x512.png new file mode 100644 index 0000000..45bd462 Binary files /dev/null and b/src/assets/icons/icon-512x512.png differ diff --git a/src/assets/icons/icon-72x72.png b/src/assets/icons/icon-72x72.png new file mode 100644 index 0000000..d504761 Binary files /dev/null and b/src/assets/icons/icon-72x72.png differ diff --git a/src/assets/icons/icon-96x96.png b/src/assets/icons/icon-96x96.png new file mode 100644 index 0000000..28f4640 Binary files /dev/null and b/src/assets/icons/icon-96x96.png differ diff --git a/src/index.html b/src/index.html index bc0c164..173237a 100644 --- a/src/index.html +++ b/src/index.html @@ -6,8 +6,11 @@ + + + diff --git a/src/manifest.webmanifest b/src/manifest.webmanifest new file mode 100644 index 0000000..7b1d634 --- /dev/null +++ b/src/manifest.webmanifest @@ -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" + } + ] +}