diff --git a/package-lock.json b/package-lock.json index bc55c27..7d7b150 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1887,9 +1887,8 @@ } }, "@inst-iot/bosch-angular-ui-components": { - "version": "0.6.0", - "resolved": "https://rb-artifactory.bosch.com:443/artifactory/api/npm/iot-insights-release-local/@inst-iot/bosch-angular-ui-components/-/@inst-iot/bosch-angular-ui-components-0.6.0.tgz", - "integrity": "sha1-+yjXwe/qCeBHYL+WoG7mOarPXIQ=", + "version": "file:../Bosch-UI-Components/bosch-angular-ui-components/dist-lib/inst-iot-bosch-angular-ui-components-0.6.0.tgz", + "integrity": "sha512-A4nKOvpdKzq+GWSZlL7U511Ii1vdSA905Q0tru7jAzpBzjRaYqCTiCvsAjRDLM+gVPpzgZ8HpMYfNfhMoNlG/w==", "requires": { "tslib": "^1.10.0" } @@ -2002,6 +2001,14 @@ } } }, + "@types/chart.js": { + "version": "2.9.22", + "resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.9.22.tgz", + "integrity": "sha512-CneMxwh2T5fyMpXE5fuprTTmFtlLyZUFq1A3laUrCgOblDzupgiohrFg3jjsTIrqRI5K4qLZdrLN4zT9/MY5Dw==", + "requires": { + "moment": "^2.10.2" + } + }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", @@ -3355,6 +3362,37 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "chart.js": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.3.tgz", + "integrity": "sha512-+2jlOobSk52c1VU6fzkh3UwqHMdSlgH1xFv9FKMqHiNCpXsGPQa/+81AFa+i3jZ253Mq9aAycPwDjnn1XbRNNw==", + "requires": { + "chartjs-color": "^2.1.0", + "moment": "^2.10.2" + } + }, + "chartjs-color": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.4.1.tgz", + "integrity": "sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==", + "requires": { + "chartjs-color-string": "^0.6.0", + "color-convert": "^1.9.3" + } + }, + "chartjs-color-string": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz", + "integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==", + "requires": { + "color-name": "^1.0.0" + } + }, + "chartjs-plugin-datalabels": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chartjs-plugin-datalabels/-/chartjs-plugin-datalabels-0.7.0.tgz", + "integrity": "sha512-PKVUX14nYhH0wcdCpgOoC39Gbzvn6cZ7O9n+bwc02yKD9FTnJ7/TSrBcfebmolFZp1Rcicr9xbT0a5HUbigS7g==" + }, "chokidar": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz", @@ -3588,7 +3626,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "requires": { "color-name": "1.1.3" } @@ -3596,8 +3633,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "color-string": { "version": "1.5.3", @@ -8063,6 +8099,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, + "lodash-es": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.15.tgz", + "integrity": "sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==" + }, "lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", @@ -8658,6 +8699,11 @@ "minimist": "^1.2.5" } }, + "moment": { + "version": "2.27.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz", + "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==" + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -8738,6 +8784,16 @@ "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", "dev": true }, + "ng2-charts": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/ng2-charts/-/ng2-charts-2.3.2.tgz", + "integrity": "sha512-T0rPivwZITKtEtFRVodRCO+kIczWIP6V4YLZvf6Kg1jqc8jYGZ37H5ywT0Q7N0Rt5dJGhC5z1/38nWFBVFx5iw==", + "requires": { + "@types/chart.js": "^2.7.48", + "lodash-es": "^4.17.11", + "tslib": "^1.9.0" + } + }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", diff --git a/package.json b/package.json index b188818..9941ad5 100644 --- a/package.json +++ b/package.json @@ -23,10 +23,13 @@ "@angular/platform-browser-dynamic": "~9.1.7", "@angular/router": "~9.1.7", "@hapi/joi": "^17.1.1", - "@inst-iot/bosch-angular-ui-components": "^0.6.0", + "@inst-iot/bosch-angular-ui-components": "file:../Bosch-UI-Components/bosch-angular-ui-components/dist-lib/inst-iot-bosch-angular-ui-components-0.6.0.tgz", "angular-2-local-storage": "^3.0.2", + "chart.js": "^2.9.3", + "chartjs-plugin-datalabels": "^0.7.0", "flatpickr": "^4.6.3", "lodash": "^4.17.15", + "ng2-charts": "^2.3.2", "quick-score": "0.0.8", "rxjs": "~6.5.5", "tslib": "^1.10.0", diff --git a/src/app/app.component.html b/src/app/app.component.html index 0546de3..405c832 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -4,19 +4,21 @@ Samples - - -
-

- Some user specific information -

+ + + +
+

+ +

- Logout -
-
+ +
+
+
DEVELOPMENTDigital Fingerprint of Plastics
diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 8b6391c..fc074c0 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,5 +1,6 @@ import { Component, isDevMode} from '@angular/core'; import {LoginService} from './services/login.service'; +import {Router} from '@angular/router'; // TODO: add multiple samples at once // TODO: guess properties from material name @@ -16,11 +17,17 @@ import {LoginService} from './services/login.service'; export class AppComponent { constructor( - public loginService: LoginService + public loginService: LoginService, + private router: Router ) { } get devMode() { return isDevMode(); } + + logout() { + this.loginService.logout(); + this.router.navigate(['/']); + } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 60c0738..663e8e9 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,5 +1,6 @@ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; +import { ChartsModule } from 'ng2-charts'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @@ -16,6 +17,7 @@ import { ValidateDirective } from './validate.directive'; import {CommonModule} from '@angular/common'; import { ErrorComponent } from './error/error.component'; import { ObjectPipe } from './object.pipe'; +import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; @NgModule({ declarations: [ @@ -34,6 +36,7 @@ import { ObjectPipe } from './object.pipe'; storageType: 'localStorage' }), BrowserModule, + BrowserAnimationsModule, AppRoutingModule, RbUiComponentsModule, FormsModule, @@ -41,7 +44,8 @@ import { ObjectPipe } from './object.pipe'; RbTableModule, ReactiveFormsModule, FormFieldsModule, - CommonModule + CommonModule, + ChartsModule ], providers: [ ModalService diff --git a/src/app/login/login.component.ts b/src/app/login/login.component.ts index 765d641..6c28dcc 100644 --- a/src/app/login/login.component.ts +++ b/src/app/login/login.component.ts @@ -29,7 +29,7 @@ export class LoginComponent implements OnInit { login() { this.loginService.login(this.username, this.password).then(ok => { if (ok) { - this.message = 'Login successful'; // TODO: think about following action + this.message = 'Login successful'; this.router.navigate(['/samples']); } else { diff --git a/src/app/sample/sample.component.html b/src/app/sample/sample.component.html index 1274b1b..138e0e9 100644 --- a/src/app/sample/sample.component.html +++ b/src/app/sample/sample.component.html @@ -6,14 +6,14 @@
- + Cannot be empty Unknown material, add properties for new material
-
+

Material properties

{{supplierInput.errors.failure}} @@ -38,7 +38,7 @@
-
+
@@ -69,7 +69,7 @@ {{commentInput.errors.failure}}
Additional properties
-
+
{{keyInput.errors.failure}} @@ -89,7 +89,7 @@ Condition -
+
@@ -105,8 +105,8 @@

Measurements

-
- +
+ @@ -115,9 +115,16 @@ {{parameterInput.errors.failure}} Cannot be empty - + Cannot be empty + +
@@ -130,7 +137,6 @@
-  
diff --git a/src/app/sample/sample.component.scss b/src/app/sample/sample.component.scss index c65b876..8b4e416 100644 --- a/src/app/sample/sample.component.scss +++ b/src/app/sample/sample.component.scss @@ -15,3 +15,7 @@ td:first-child { grid-template-columns: 1fr 1fr; grid-column-gap: 10px; } + +.dpt-chart { + max-width: 400px; +} diff --git a/src/app/sample/sample.component.ts b/src/app/sample/sample.component.ts index 96d57c3..ba13d26 100644 --- a/src/app/sample/sample.component.ts +++ b/src/app/sample/sample.component.ts @@ -14,20 +14,35 @@ import {NgForm, Validators} from '@angular/forms'; import {ValidationService} from '../services/validation.service'; import {TemplateModel} from '../models/template.model'; import {MeasurementModel} from '../models/measurement.model'; +import { ChartOptions } from 'chart.js'; +import {animate, style, transition, trigger} from '@angular/animations'; // TODO: tests // TODO: confirmation for new group/supplier -// TODO: DPT preview // TODO: work on better recognition for file input // TODO: only show condition (if not set) and measurements in edit sample dialog at first -// TODO: multiple spectra, drag and drop +// TODO: multiple spectra // TODO: multiple samples for base data, extend multiple measurements, conditions @Component({ selector: 'app-sample', templateUrl: './sample.component.html', - styleUrls: ['./sample.component.scss'] + styleUrls: ['./sample.component.scss'], + animations: [ + trigger( + 'inOut', [ + transition(':enter', [ + style({height: 0, opacity: 0}), + animate('0.5s ease-out', style({height: '*', opacity: 1})) + ]), + transition(':leave', [ + style({height: '*', opacity: 1}), + animate('0.5s ease-in', style({height: 0, opacity: 0})) + ]) + ] + ) + ] }) export class SampleComponent implements OnInit, AfterContentChecked { @@ -49,6 +64,27 @@ export class SampleComponent implements OnInit, AfterContentChecked { measurementTemplates: TemplateModel[]; loading = 0; // number of currently loading instances checkFormAfterInit = false; + charts = []; // chart data for spectrums + readonly chartInit = [{ + data: [], + label: 'Spectrum', + showLine: true, + fill: false, + pointRadius: 0, + borderColor: '#00a8b0', + borderWidth: 2 + }]; + readonly chartOptions: ChartOptions = { + scales: { + xAxes: [{ticks: {min: 400, max: 4000, stepSize: 400, reverse: true}}], + yAxes: [{ticks: {min: 0, max: 1}}] + }, + responsive: true, + tooltips: {enabled: false}, + hover: {mode: null}, + maintainAspectRatio: true, + plugins: {datalabels: {display: false}} + }; constructor( private router: Router, @@ -90,6 +126,19 @@ export class SampleComponent implements OnInit, AfterContentChecked { this.loading++; this.api.get('/sample/' + this.route.snapshot.paramMap.get('id'), sData => { this.sample.deserialize(sData); + this.charts = []; + const spectrumTemplate = this.measurementTemplates.find(e => e.name === 'spectrum')._id; + let spectrumCounter = 0; + this.sample.measurements.forEach((measurement, i) => { + this.charts.push(_.cloneDeep(this.chartInit)); + if (measurement.measurement_template === spectrumTemplate) { + setTimeout(() => { + this.generateChart(measurement.values.dpt, i); + console.log(this.charts); + }, spectrumCounter * 20); + spectrumCounter ++; + } + }); this.material = sData.material; this.customFields = this.sample.notes.custom_fields && this.sample.notes.custom_fields !== {} ? Object.keys(this.sample.notes.custom_fields).map(e => [e, this.sample.notes.custom_fields[e]]) : [['', '']]; if ('condition_template' in this.sample.condition) { @@ -225,8 +274,8 @@ export class SampleComponent implements OnInit, AfterContentChecked { this.setNewMaterial(); } - preventSubmit(event) { - if (event.key === 'Enter') { + preventDefault(event) { + if (event.key && event.key === 'Enter' || event.type === 'dragover') { event.preventDefault(); } } @@ -285,6 +334,7 @@ export class SampleComponent implements OnInit, AfterContentChecked { addMeasurement() { this.sample.measurements.push(new MeasurementModel(this.measurementTemplates[0]._id)); + this.charts.push(_.cloneDeep(this.chartInit)); } removeMeasurement(index) { @@ -292,14 +342,24 @@ export class SampleComponent implements OnInit, AfterContentChecked { this.api.delete('/measurement/' + this.sample.measurements[index]._id); } this.sample.measurements.splice(index, 1); + this.charts.splice(index, 1); } - fileToArray(event, mIndex, parameter) { + clearChart(index) { + this.charts[index][0].data = []; + } + + fileToArray(files, mIndex, parameter) { const fileReader = new FileReader(); fileReader.onload = () => { this.sample.measurements[mIndex].values[parameter] = fileReader.result.toString().split('\r\n').map(e => e.split(',')); + this.generateChart(this.sample.measurements[mIndex].values[parameter], mIndex); }; - fileReader.readAsText(event.target.files[0]); + fileReader.readAsText(files[0]); + } + + generateChart(spectrum, index) { + this.charts[index][0].data = spectrum.map(e => ({x: parseFloat(e[0]), y: parseFloat(e[1])})); } toggleCondition() { diff --git a/src/app/samples/samples.component.html b/src/app/samples/samples.component.html index ee76318..9aae131 100644 --- a/src/app/samples/samples.component.html +++ b/src/app/samples/samples.component.html @@ -45,7 +45,7 @@ -
+
@@ -54,11 +54,6 @@
- - - - -
@@ -91,8 +86,6 @@
{{key.label}} - -
diff --git a/src/app/samples/samples.component.scss b/src/app/samples/samples.component.scss index 0641a55..9b4a2f6 100644 --- a/src/app/samples/samples.component.scss +++ b/src/app/samples/samples.component.scss @@ -169,3 +169,8 @@ textarea.linkmodal { min-height: 200px; border: none; } + +.filter-inputs > * { + display: inline-block; + max-width: 250px; +} diff --git a/src/app/services/login.service.ts b/src/app/services/login.service.ts index 5cc336f..088792a 100644 --- a/src/app/services/login.service.ts +++ b/src/app/services/login.service.ts @@ -42,6 +42,11 @@ export class LoginService implements CanActivate { }); } + logout() { + this.storage.remove('basicAuth'); + this.loggedIn = false; + } + canActivate(route: ActivatedRouteSnapshot = null, state: RouterStateSnapshot = null): Observable { return new Observable(observer => { if (this.loggedIn === undefined) { @@ -60,4 +65,8 @@ export class LoginService implements CanActivate { get isLoggedIn() { return this.loggedIn; } + + get username() { + return atob(this.storage.get('basicAuth')).split(':')[0]; + } }