diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 22157cd..d5c4f6d 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -13,43 +13,43 @@ import {DocumentationDatabaseComponent} from './documentation/documentation-data import {PredictionComponent} from './prediction/prediction.component'; import {ModelTemplatesComponent} from './model-templates/model-templates.component'; import {DocumentationArchitectureComponent} from - './documentation/documentation-architecture/documentation-architecture.component'; +'./documentation/documentation-architecture/documentation-architecture.component'; import {MaterialsComponent} from './materials/materials.component'; import {MaterialComponent} from './material/material.component'; import {DocumentationModelsComponent} from './documentation/documentation-models/documentation-models.component'; const routes: Routes = [ - {path: '', component: HomeComponent}, - {path: 'home', component: HomeComponent}, - {path: 'prediction', component: PredictionComponent}, - {path: 'models', component: ModelTemplatesComponent}, - {path: 'samples', component: SamplesComponent, canActivate: [LoginService]}, - {path: 'samples/new', component: SampleComponent, canActivate: [LoginService]}, - {path: 'samples/edit/:id', component: SampleComponent, canActivate: [LoginService]}, - {path: 'materials', component: MaterialsComponent, canActivate: [LoginService]}, - {path: 'materials/edit/:id', component: MaterialComponent, canActivate: [LoginService]}, - {path: 'templates', component: TemplatesComponent, canActivate: [LoginService]}, - {path: 'changelog', component: ChangelogComponent, canActivate: [LoginService]}, - {path: 'users', component: UsersComponent, canActivate: [LoginService]}, - {path: 'settings', component: SettingsComponent, canActivate: [LoginService]}, - {path: 'documentation', component: DocumentationComponent}, - {path: 'documentation/architecture', component: DocumentationArchitectureComponent}, - {path: 'documentation/database', component: DocumentationDatabaseComponent}, - {path: 'documentation/models', component: DocumentationModelsComponent}, + {path: '', component: HomeComponent}, + {path: 'home', component: HomeComponent}, + {path: 'prediction', component: PredictionComponent}, + {path: 'models', component: ModelTemplatesComponent}, + {path: 'samples', component: SamplesComponent, canActivate: [LoginService]}, + {path: 'samples/new', component: SampleComponent, canActivate: [LoginService]}, + {path: 'samples/edit/:id', component: SampleComponent, canActivate: [LoginService]}, + {path: 'materials', component: MaterialsComponent, canActivate: [LoginService]}, + {path: 'materials/edit/:id', component: MaterialComponent, canActivate: [LoginService]}, + {path: 'templates', component: TemplatesComponent, canActivate: [LoginService]}, + {path: 'changelog', component: ChangelogComponent, canActivate: [LoginService]}, + {path: 'users', component: UsersComponent, canActivate: [LoginService]}, + {path: 'settings', component: SettingsComponent, canActivate: [LoginService]}, + {path: 'documentation', component: DocumentationComponent}, + {path: 'documentation/architecture', component: DocumentationArchitectureComponent}, + {path: 'documentation/database', component: DocumentationDatabaseComponent}, + {path: 'documentation/models', component: DocumentationModelsComponent}, - // If not authenticated - { path: '**', redirectTo: '' } + // If not authenticated + { path: '**', redirectTo: '' } ]; const routerOptions: ExtraOptions = { - scrollPositionRestoration: 'enabled', - anchorScrolling: 'enabled', - scrollOffset: [0, 64], + scrollPositionRestoration: 'enabled', + anchorScrolling: 'enabled', + scrollOffset: [0, 64], }; @NgModule({ - imports: [RouterModule.forRoot(routes, routerOptions)], - exports: [RouterModule] + imports: [RouterModule.forRoot(routes, routerOptions)], + exports: [RouterModule] }) export class AppRoutingModule { } diff --git a/src/app/app.component.html b/src/app/app.component.html index 15df9aa..57cb40d 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,73 +1,73 @@ - + - - - + + + - + - - - -
-   Settings - -
-
-
+ + + +
+   Settings + +
+
+
-
- - Bug - - -

Report a bug

- - - - Send report - -
- DEVELOPMENT - DeFinMa -
+
+ + Bug + + +

Report a bug

+ + + + Send report + +
+ DEVELOPMENT + DeFinMa +
- +
- - CR/APS1 and CR/ANA1 2020 - - + + CR/APS1 and CR/ANA1 2020 + + diff --git a/src/app/app.component.scss b/src/app/app.component.scss index c482ab2..6f2fa5c 100644 --- a/src/app/app.component.scss +++ b/src/app/app.component.scss @@ -1,26 +1,26 @@ .dev-label { - color: #F00; - font-size: 32px; - margin-right: 40px; + color: #F00; + font-size: 32px; + margin-right: 40px; } .spacing { - display: grid; - grid-template-columns: 1fr; - grid-row-gap: 10px; + display: grid; + grid-template-columns: 1fr; + grid-row-gap: 10px; } .bug-textarea { - width: 800px; + width: 800px; } .container { - position: relative; - overflow: hidden; + position: relative; + overflow: hidden; } .to-the-top { - position: fixed; - right: 1rem; - bottom: 20px; + position: fixed; + right: 1rem; + bottom: 20px; } diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 2845a87..386b83c 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -8,76 +8,76 @@ import {DataService} from './services/data.service'; @Component({ - selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'] + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.scss'] }) export class AppComponent implements OnInit{ - bugReport = {do: '', work: ''}; // Data from bug report inputs - isDocumentation = false; // True if user is on documentation pages - devMode = false; + bugReport = {do: '', work: ''}; // Data from bug report inputs + isDocumentation = false; // True if user is on documentation pages + devMode = false; - constructor( - public login: LoginService, - public router: Router, - private route: ActivatedRoute, - public window: Window, - private modal: ModalService, - public d: DataService - ) { - this.devMode = isDevMode(); - this.router.events.subscribe(event => { - if (event instanceof NavigationStart) { - this.isDocumentation = /\/documentation/.test(event.url); - } - }); - } + constructor( + public login: LoginService, + public router: Router, + private route: ActivatedRoute, + public window: Window, + private modal: ModalService, + public d: DataService + ) { + this.devMode = isDevMode(); + this.router.events.subscribe(event => { + if (event instanceof NavigationStart) { + this.isDocumentation = /\/documentation/.test(event.url); + } + }); + } - ngOnInit() { - // Try to log in user - this.login.login().then(res => { - // Return to home page if log failed, except when on documentation pages - if (!res && !(/\/documentation/.test(this.router.url))) { - this.router.navigate(['/']); - } - }); - } + ngOnInit() { + // Try to log in user + this.login.login().then(res => { + // Return to home page if log failed, except when on documentation pages + if (!res && !(/\/documentation/.test(this.router.url))) { + this.router.navigate(['/']); + } + }); + } - logout() { - this.login.logout(); - this.router.navigate(['/']); - } + logout() { + this.login.logout(); + this.router.navigate(['/']); + } - help() { - this.modal.openComponent(HelpComponent); - } + help() { + this.modal.openComponent(HelpComponent); + } - bugReportContent() { - return `mailto:${this.d.contact.mail}?subject=Bug report&body=Thanks for sending the report! Your bug will be -(hopefully) fixed soon. -%0D%0A%0D%0A--- REPORT DATA --- -%0D%0A%0D%0ATime: ${new Date().toString()}%0D%0A -URL: ${this.window.location}%0D%0A%0D%0AWhat did you do?%0D%0A${encodeURIComponent(this.bugReport.do)} -%0D%0A%0D%0AWhat did not work?%0D%0A${encodeURIComponent(this.bugReport.work)}%0D%0A%0D%0ABrowser:%0D%0A -%0D%0AappCodeName: ${navigator.appCodeName} -%0D%0AappVersion: ${navigator.appVersion} -%0D%0Alanguage: ${navigator.language} -%0D%0AonLine: ${navigator.onLine} -%0D%0Aoscpu: ${navigator.oscpu} -%0D%0Aplatform: ${navigator.platform} -%0D%0AuserAgent: ${navigator.userAgent} -%0D%0AinnerWidth: ${this.window.innerWidth} -%0D%0AinnerHeight: ${this.window.innerHeight}`; - } + bugReportContent() { + return `mailto:${this.d.contact.mail}?subject=Bug report&body=Thanks for sending the report! Your bug will be + (hopefully) fixed soon. + %0D%0A%0D%0A--- REPORT DATA --- + %0D%0A%0D%0ATime: ${new Date().toString()}%0D%0A + URL: ${this.window.location}%0D%0A%0D%0AWhat did you do?%0D%0A${encodeURIComponent(this.bugReport.do)} + %0D%0A%0D%0AWhat did not work?%0D%0A${encodeURIComponent(this.bugReport.work)}%0D%0A%0D%0ABrowser:%0D%0A + %0D%0AappCodeName: ${navigator.appCodeName} + %0D%0AappVersion: ${navigator.appVersion} + %0D%0Alanguage: ${navigator.language} + %0D%0AonLine: ${navigator.onLine} + %0D%0Aoscpu: ${navigator.oscpu} + %0D%0Aplatform: ${navigator.platform} + %0D%0AuserAgent: ${navigator.userAgent} + %0D%0AinnerWidth: ${this.window.innerWidth} + %0D%0AinnerHeight: ${this.window.innerHeight}`; + } - closeBugReport(close) { - setTimeout(() => close(), 1); - } + closeBugReport(close) { + setTimeout(() => close(), 1); + } - toTheTop() { - this.window.scroll(0, 0); - } + toTheTop() { + this.window.scroll(0, 0); + } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index e048d84..3dab028 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -27,66 +27,66 @@ import { SettingsComponent } from './settings/settings.component'; import { UsersComponent } from './users/users.component'; import { ChangelogComponent } from './changelog/changelog.component'; import { DocumentationDatabaseComponent } from - './documentation/documentation-database/documentation-database.component'; +'./documentation/documentation-database/documentation-database.component'; import { PredictionComponent } from './prediction/prediction.component'; import { HelpComponent } from './help/help.component'; import { ModelTemplatesComponent } from './model-templates/model-templates.component'; import { SizePipe } from './size.pipe'; import { DocumentationArchitectureComponent } from - './documentation/documentation-architecture/documentation-architecture.component'; +'./documentation/documentation-architecture/documentation-architecture.component'; import { MaterialsComponent } from './materials/materials.component'; import { MaterialComponent } from './material/material.component'; import { DocumentationModelsComponent } from './documentation/documentation-models/documentation-models.component'; @NgModule({ - declarations: [ - AppComponent, - LoginComponent, - HomeComponent, - SamplesComponent, - SampleComponent, - ValidateDirective, - ErrorComponent, - ObjectPipe, - DocumentationComponent, - ImgMagnifierComponent, - ExistsPipe, - TemplatesComponent, - ParametersPipe, - SettingsComponent, - UsersComponent, - ChangelogComponent, - DocumentationDatabaseComponent, - PredictionComponent, - HelpComponent, - ModelTemplatesComponent, - SizePipe, - DocumentationArchitectureComponent, - MaterialsComponent, - MaterialComponent, - DocumentationModelsComponent - ], - imports: [ - LocalStorageModule.forRoot({ - prefix: 'definma', - storageType: 'localStorage' - }), - BrowserModule, - BrowserAnimationsModule, - AppRoutingModule, - RbUiComponentsModule, - FormsModule, - HttpClientModule, - RbCustomInputsModule, - ReactiveFormsModule, - FormFieldsModule, - CommonModule, - ChartsModule - ], - providers: [ - ModalService, - { provide: Window, useValue: window } - ], - bootstrap: [AppComponent] + declarations: [ + AppComponent, + LoginComponent, + HomeComponent, + SamplesComponent, + SampleComponent, + ValidateDirective, + ErrorComponent, + ObjectPipe, + DocumentationComponent, + ImgMagnifierComponent, + ExistsPipe, + TemplatesComponent, + ParametersPipe, + SettingsComponent, + UsersComponent, + ChangelogComponent, + DocumentationDatabaseComponent, + PredictionComponent, + HelpComponent, + ModelTemplatesComponent, + SizePipe, + DocumentationArchitectureComponent, + MaterialsComponent, + MaterialComponent, + DocumentationModelsComponent + ], + imports: [ + LocalStorageModule.forRoot({ + prefix: 'definma', + storageType: 'localStorage' + }), + BrowserModule, + BrowserAnimationsModule, + AppRoutingModule, + RbUiComponentsModule, + FormsModule, + HttpClientModule, + RbCustomInputsModule, + ReactiveFormsModule, + FormFieldsModule, + CommonModule, + ChartsModule + ], + providers: [ + ModalService, + { provide: Window, useValue: window } + ], + bootstrap: [AppComponent] }) export class AppModule { } diff --git a/src/app/changelog/changelog.component.html b/src/app/changelog/changelog.component.html index 8f47562..861b46c 100644 --- a/src/app/changelog/changelog.component.html +++ b/src/app/changelog/changelog.component.html @@ -1,46 +1,46 @@
- - + + - - - - - - - - - + + + + + + + + + - +
- - Date - Action - Data - - - {{entry.date}} - {{entry.action}} - {{entry.data | json}} - + +Date +Action +Data + + +{{entry.date}} +{{entry.action}} +{{entry.data | json}} + -

Date

-

{{changelog[modalDetail].date}}

-

Action

-

{{changelog[modalDetail].action}}

-

Collection

-

{{changelog[modalDetail].collection}}

-

Conditions

-

{{changelog[modalDetail].conditions | json}}

-

Data

-

{{changelog[modalDetail].data | json}}

+

Date

+

{{changelog[modalDetail].date}}

+

Action

+

{{changelog[modalDetail].action}}

+

Collection

+

{{changelog[modalDetail].collection}}

+

Conditions

+

{{changelog[modalDetail].conditions | json}}

+

Data

+

{{changelog[modalDetail].data | json}}

diff --git a/src/app/changelog/changelog.component.scss b/src/app/changelog/changelog.component.scss index aec193a..d8dce28 100644 --- a/src/app/changelog/changelog.component.scss +++ b/src/app/changelog/changelog.component.scss @@ -2,20 +2,20 @@ .header { - & > * { - float: left; - } + & > * { + float: left; + } - button { - float: right; - } + button { + float: right; + } } tr.clickable { - background: none; - transition: background-color 0.5s; + background: none; + transition: background-color 0.5s; - &:hover { - background: $color-gray-mercury; - } + &:hover { + background: $color-gray-mercury; + } } diff --git a/src/app/changelog/changelog.component.ts b/src/app/changelog/changelog.component.ts index 2fe8313..5588921 100644 --- a/src/app/changelog/changelog.component.ts +++ b/src/app/changelog/changelog.component.ts @@ -4,44 +4,44 @@ import {ApiService} from '../services/api.service'; import {ModalService} from '@inst-iot/bosch-angular-ui-components'; @Component({ - selector: 'app-changelog', - templateUrl: './changelog.component.html', - styleUrls: ['./changelog.component.scss'] + selector: 'app-changelog', + templateUrl: './changelog.component.html', + styleUrls: ['./changelog.component.scss'] }) export class ChangelogComponent implements OnInit { - timestamp = new Date(); // Time from date input - pageSize = 25; - changelog: ChangelogModel[] = []; - modalDetail = 0; // Index of changelog element to show details of + timestamp = new Date(); // Time from date input + pageSize = 25; + changelog: ChangelogModel[] = []; + modalDetail = 0; // Index of changelog element to show details of - constructor( - private api: ApiService, - private modal: ModalService - ) { } + constructor( + private api: ApiService, + private modal: ModalService + ) { } - ngOnInit(): void { - this.loadChangelog(); - } + ngOnInit(): void { + this.loadChangelog(); + } - loadChangelog(page = 0) { // Load changelog with page no relative to current page - this.api.get(`/changelog/${ - page > 0 ? this.changelog[0]._id : // Use id if no new date was given - Math.floor(new Date( - new Date(this.timestamp).getTime() - new Date(this.timestamp).getTimezoneOffset() * 60000 // Adjust timezone - ).getTime() / 1000).toString(16) + '0000000000000000' // Id from time - }/${page}/${this.pageSize}`, data => { - this.changelog = data.map(e => new ChangelogModel().deserialize(e)); - if (page) { // Adjust date picker to new first element when user clicked on next page - this.timestamp = new Date(this.changelog[0].date); - } - }); - } + loadChangelog(page = 0) { // Load changelog with page no relative to current page + this.api.get(`/changelog/${ + page > 0 ? this.changelog[0]._id : // Use id if no new date was given + Math.floor(new Date( + new Date(this.timestamp).getTime() - new Date(this.timestamp).getTimezoneOffset() * 60000 // Adjust timezone + ).getTime() / 1000).toString(16) + '0000000000000000' // Id from time + }/${page}/${this.pageSize}`, data => { + this.changelog = data.map(e => new ChangelogModel().deserialize(e)); + if (page) { // Adjust date picker to new first element when user clicked on next page + this.timestamp = new Date(this.changelog[0].date); + } + }); + } - // Show details of a changelog element with reference to needed modal - showDetails(i: number, modal: TemplateRef) { - this.modalDetail = i; - this.modal.open(modal).then(() => {}); - } + // Show details of a changelog element with reference to needed modal + showDetails(i: number, modal: TemplateRef) { + this.modalDetail = i; + this.modal.open(modal).then(() => {}); + } } diff --git a/src/app/documentation/documentation-architecture/documentation-architecture.component.html b/src/app/documentation/documentation-architecture/documentation-architecture.component.html index 6191715..5a5c425 100644 --- a/src/app/documentation/documentation-architecture/documentation-architecture.component.html +++ b/src/app/documentation/documentation-architecture/documentation-architecture.component.html @@ -1,50 +1,50 @@

- Bosch IoT Cloud space where all applications are hosted: - - https://apps.sys.de1.bosch-iot-cloud.com/organizations/b28baba5-f95f-4ce5-bc9c-3f45acd1dfb2 -
- Find the API documentation here: - - https://definma-api.apps.de1.bosch-iot-cloud.com/api-doc/ -
- Admin database management page: - - https://definma-db.apps.de1.bosch-iot-cloud.com/ -
- Code repository UI - - https://sourcecode.socialcoding.bosch.com/users/vle2fe/repos/definma-ui -
- Code repository API - - https://sourcecode.socialcoding.bosch.com/users/vle2fe/repos/definma-api -
- Code repository Models - - https://sourcecode.socialcoding.bosch.com/users/poe2rng/repos/definma-models -
+Bosch IoT Cloud space where all applications are hosted: + + https://apps.sys.de1.bosch-iot-cloud.com/organizations/b28baba5-f95f-4ce5-bc9c-3f45acd1dfb2 +
+Find the API documentation here: + + https://definma-api.apps.de1.bosch-iot-cloud.com/api-doc/ +
+Admin database management page: + + https://definma-db.apps.de1.bosch-iot-cloud.com/ +
+Code repository UI + + https://sourcecode.socialcoding.bosch.com/users/vle2fe/repos/definma-ui +
+Code repository API + + https://sourcecode.socialcoding.bosch.com/users/vle2fe/repos/definma-api +
+Code repository Models + + https://sourcecode.socialcoding.bosch.com/users/poe2rng/repos/definma-models +

- All applications are hosted in the bosch IoT Cloud. The API is hosted in a Node.js container, with a bound MongoDB - instance.
- The Angular UI is hosted in a staticfile container, serving the built Angular bundle.
- Finally any machine learning models are hosted in Python containers. +All applications are hosted in the bosch IoT Cloud. The API is hosted in a Node.js container, with a bound MongoDB +instance.
+The Angular UI is hosted in a staticfile container, serving the built Angular bundle.
+Finally any machine learning models are hosted in Python containers.

Development setup

- In any case, it is good to have PxProxy working, following the - - Bosch installation guide - .
- For pushing to the BIC, you need the CloudFoundry CLI as described in the - BIC documentation. - If the the downloaded version of the CLI should not work, there is an old installer with a definitely working version - on the file share in the bin folder. - Please consider that you need to have a login to the BIC and be marked as space developer for the DeFinMa Org.
- For front and back end development, you need a - local MongoDB server as well as - Node.js. +In any case, it is good to have PxProxy working, following the + + Bosch installation guide +.
+For pushing to the BIC, you need the CloudFoundry CLI as described in the +BIC documentation. +If the the downloaded version of the CLI should not work, there is an old installer with a definitely working version +on the file share in the bin folder. +Please consider that you need to have a login to the BIC and be marked as space developer for the DeFinMa Org.
+For front and back end development, you need a +local MongoDB server as well as +Node.js.

diff --git a/src/app/documentation/documentation-architecture/documentation-architecture.component.ts b/src/app/documentation/documentation-architecture/documentation-architecture.component.ts index 3dd81d7..6e5fb29 100644 --- a/src/app/documentation/documentation-architecture/documentation-architecture.component.ts +++ b/src/app/documentation/documentation-architecture/documentation-architecture.component.ts @@ -1,15 +1,15 @@ import { Component, OnInit } from '@angular/core'; @Component({ - selector: 'app-documentation-architecture', - templateUrl: './documentation-architecture.component.html', - styleUrls: ['./documentation-architecture.component.scss'] + selector: 'app-documentation-architecture', + templateUrl: './documentation-architecture.component.html', + styleUrls: ['./documentation-architecture.component.scss'] }) export class DocumentationArchitectureComponent implements OnInit { - constructor() { } + constructor() { } - ngOnInit(): void { - } + ngOnInit(): void { + } } diff --git a/src/app/documentation/documentation-database/documentation-database.component.html b/src/app/documentation/documentation-database/documentation-database.component.html index 4a38b1c..dabee34 100644 --- a/src/app/documentation/documentation-database/documentation-database.component.html +++ b/src/app/documentation/documentation-database/documentation-database.component.html @@ -1,38 +1,38 @@

- The used database instance is a MongoDB instance running on the BIC, storing all application data. - The admin database management page can be accessed here. - However, it is recommended to use a dedicated MongoDB application for anything apart from a quick look. - The two tested applications are MongoDB Compass and - Robo 3T (recommended for trying queries). +The used database instance is a MongoDB instance running on the BIC, storing all application data. +The admin database management page can be accessed here. +However, it is recommended to use a dedicated MongoDB application for anything apart from a quick look. +The two tested applications are MongoDB Compass and +Robo 3T (recommended for trying queries).

- To connect to the BIC instance from your local application follow the - BIC guide. -
- TL;DR: +To connect to the BIC instance from your local application follow the +BIC guide. +
+TL;DR:

For the first time:

    -
  • cf login
  • -
  • cf enable-ssh definma-api
  • -
  • cf create-service-key definmadb <key name, can be whatever>
  • +
  • cf login
  • +
  • cf enable-ssh definma-api
  • +
  • cf create-service-key definmadb <key name, can be whatever>

Every time:

    -
  • cf ssh -L 1120:st0cvm200118.internal-mongodb.de1.bosch-iot-cloud.com:30000 definma-api
  • -
  • - Login into your application with localhost:1120 and use credentials and authentication database from the BIC - (Data can be found at Home > DeFinMa > production > definma-api > Services > dots on the right of definmadb > View Credentials) -
  • +
  • cf ssh -L 1120:st0cvm200118.internal-mongodb.de1.bosch-iot-cloud.com:30000 definma-api
  • +
  • + Login into your application with localhost:1120 and use credentials and authentication database from the BIC + (Data can be found at Home > DeFinMa > production > definma-api > Services > dots on the right of definmadb > View Credentials) +

Backup Instructions

- For creating a database backup, you must follow the same steps from above (except the last one). - Additionally you need the MongoDB Tools installed. - Extract the ZIP file to a location on your hard drive and launch a Git Bash in that directory. +For creating a database backup, you must follow the same steps from above (except the last one). +Additionally you need the MongoDB Tools installed. +Extract the ZIP file to a location on your hard drive and launch a Git Bash in that directory.

To back up the database from the BIC:

@@ -51,377 +51,377 @@

- More information can be found inside the - documentation.
- The BIC service also creates an internal backup, which can be requested to restore, see - MongoDB FAQ +More information can be found inside the +documentation.
+The BIC service also creates an internal backup, which can be requested to restore, see +MongoDB FAQ

Database Model

+ id="db-structure">

Field Reference

- samplesExample - - _id - Automatically generated unique id - '5f2e63c98d1c020f8cda6e06' - - - type - - The material status of the sample, can be either as-delivered/raw or - processed. - - 'processed' - - - number - The sample number, generated by the server for new samples - 'An31' - - - color - Sample color - 'black' - - - batch - Batch number the sample was from - '2264486614' - - - status - Status of the document, can be either new, validated or - deleted. - 'new' - - - condition - sample condition with condition_template reference and all fields defined by the - template - {{'{'}}condition_template: '5f3151b5b8a886007d2de9ed', time in minutes: 30{{'}'}} - - - material_id - Reference to the sample material - '5f2e63118d1c020f8cda6a0a' - - - note_id - Reference to the sample note - '5f2e63c98d1c020f8cda6e08' - - - user_id - Reference to the user who created the sample - '5f294dd4aa9ea5085c7d7315' - + samplesExample + + _id + Automatically generated unique id + '5f2e63c98d1c020f8cda6e06' + + + type + + The material status of the sample, can be either as-delivered/raw or + processed. + + 'processed' + + + number + The sample number, generated by the server for new samples + 'An31' + + + color + Sample color + 'black' + + + batch + Batch number the sample was from + '2264486614' + + + status + Status of the document, can be either new, validated or + deleted. + 'new' + + + condition + sample condition with condition_template reference and all fields defined by the + template + {{'{'}}condition_template: '5f3151b5b8a886007d2de9ed', time in minutes: 30{{'}'}} + + + material_id + Reference to the sample material + '5f2e63118d1c020f8cda6a0a' + + + note_id + Reference to the sample note + '5f2e63c98d1c020f8cda6e08' + + + user_id + Reference to the user who created the sample + '5f294dd4aa9ea5085c7d7315' + - notes - - _id - Automatically generated unique id - '5f2e63e98d1c020f8cda70cc' - - - comment - General remarks - 'stabilized' - - - sample_references - Array of references to other samples, each reference containing the referenced sample_id - as well as a relation field describing the relationship - {{'{'}}sample_id: '5f2e63d68d1c020f8cda701c', relation: 'belongs to'{{'}'}} - - - custom_fields - Additional information as key value pairs for the sample, making it easier to process this information - {{'{'}}vwz: '0 min'{{'}'}} - + notes + + _id + Automatically generated unique id + '5f2e63e98d1c020f8cda70cc' + + + comment + General remarks + 'stabilized' + + + sample_references + Array of references to other samples, each reference containing the referenced sample_id + as well as a relation field describing the relationship + {{'{'}}sample_id: '5f2e63d68d1c020f8cda701c', relation: 'belongs to'{{'}'}} + + + custom_fields + Additional information as key value pairs for the sample, making it easier to process this information + {{'{'}}vwz: '0 min'{{'}'}} + - note_fields - - _id - Automatically generated unique id - '5f2e63e98d1c020f8cda70ce' - - - name - name of the custom_fields key - 'test series' - - - qty - number of notes with this key - 24 - + note_fields + + _id + Automatically generated unique id + '5f2e63e98d1c020f8cda70ce' + + + name + name of the custom_fields key + 'test series' + + + qty + number of notes with this key + 24 + - materials - - _id - Automatically generated unique id - '5f2e63e98d1c020f8cda70d0' - - - name - Trade name of the material - 'Ultradur B4300 G6' - - - numbers - Bosch material part numbers - ['5515753021, '5515753404'] - - - properties - material class specific properties with material_template reference and all fields - defined by the template - {{'{'}}material_template: '5f2e89874ac96c007fb06e83', mineral: 0, glass_fiber: 30, carbon_fiber: 0{{'}'}} - - - group_id - Reference to the material group - '5f2e631191c5d68f8a0708c4' - - - supplier_id - Reference to the material supplier - '5f2e631191c5d68f8a0708c7' - - - status - Status of the document, can be either new, validated or - deleted. - 'new' - + materials + + _id + Automatically generated unique id + '5f2e63e98d1c020f8cda70d0' + + + name + Trade name of the material + 'Ultradur B4300 G6' + + + numbers + Bosch material part numbers + ['5515753021, '5515753404'] + + + properties + material class specific properties with material_template reference and all fields + defined by the template + {{'{'}}material_template: '5f2e89874ac96c007fb06e83', mineral: 0, glass_fiber: 30, carbon_fiber: 0{{'}'}} + + + group_id + Reference to the material group + '5f2e631191c5d68f8a0708c4' + + + supplier_id + Reference to the material supplier + '5f2e631191c5d68f8a0708c7' + + + status + Status of the document, can be either new, validated or + deleted. + 'new' + - material_groups - - _id - Automatically generated unique id - '5f2e631291c5d68f8a0708f9' - - - name - The chemical material type - 'PA66' - + material_groups + + _id + Automatically generated unique id + '5f2e631291c5d68f8a0708f9' + + + name + The chemical material type + 'PA66' + - material_supplier - - _id - Automatically generated unique id - '5f2e631991c5d68f8a0709c3' - - - name - The material supplier - 'BASF' - + material_supplier + + _id + Automatically generated unique id + '5f2e631991c5d68f8a0709c3' + + + name + The material supplier + 'BASF' + - measurements - - _id - Automatically generated unique id - '5f294d25aa9ea5085c7d7305' - - - sample_id - Reference to the sample this measurement belongs to - '5f2e63c98d1c020f8cda6e06' - - - measurement_template - Reference to the Template defining the structure of the measurement values - '5f294d25aa9ea5085c7d7305' - - - values - Measurement values in defined format - {{'{'}}vn: 100.4{{'}'}} - - - status - Status of the document, can be either new, validated or - deleted. - 'new' - + measurements + + _id + Automatically generated unique id + '5f294d25aa9ea5085c7d7305' + + + sample_id + Reference to the sample this measurement belongs to + '5f2e63c98d1c020f8cda6e06' + + + measurement_template + Reference to the Template defining the structure of the measurement values + '5f294d25aa9ea5085c7d7305' + + + values + Measurement values in defined format + {{'{'}}vn: 100.4{{'}'}} + + + status + Status of the document, can be either new, validated or + deleted. + 'new' + - <collection>_templates - - _id - Automatically generated unique id - '5f2e63ee8d1c020f8cda7128' - - - name - Display name of the template - 'spectrum' - - - version - Version number of the template - 2 - - - first_id - Reference to the first instance of this template with version number 1 - '5f2e89bb4ac96c007fb06e86' - - - parameters - Specified parameters of this template. The name property is used as the key in the - document using this template, the range can have the following properties: min specifies - the minimum numeric value, max specifies the maximum numeric value, - values specifies an array of allowed values of this parameter and type: 'array' - specifies that this parameter must be an array - - + <collection>_templates + + _id + Automatically generated unique id + '5f2e63ee8d1c020f8cda7128' + + + name + Display name of the template + 'spectrum' + + + version + Version number of the template + 2 + + + first_id + Reference to the first instance of this template with version number 1 + '5f2e89bb4ac96c007fb06e86' + + + parameters + Specified parameters of this template. The name property is used as the key in the + document using this template, the range can have the following properties: min specifies + the minimum numeric value, max specifies the maximum numeric value, + values specifies an array of allowed values of this parameter and type: 'array' + specifies that this parameter must be an array + + - users - - _id - Automatically generated unique id - '5f2e63cc8d1c020f8cda6e6a' - - - name - The username - 'admin' - - - email - The user's email address used for password reset - 'test@bosch.com' - - - location - The abbreviation of the Bosch site of the user - 'Rng' - - - level - The permission level, can be either read, write, - dev or admin. The exact level permissions can be found at the - general documentation - - - - devices - Array of all spectrum measurement devices the user has access to - ['Rng01', 'Rng02'] - - - pass - The user's password in hashed form using bcrypt - '$2a$10$m8DqvZR3plZEv8EPwPo7Luvyrm/ZQDiPzfBh6bpU/1XFWOGONkJyG' - - - key - The API key, generated when the user is created - '5f294dd4aa9ea5085c7d7314' - - - models - - Reference to the prediction models the user should have access to. Ignored for dev and - admin as they automatically have access to all models. - - ['5f466fb1e566810dd8b3e919', '5f294d8aaa9ea5085c7d730b'] - - - status - Status of the document, can be either new, validated or - deleted. - 'new' - + users + + _id + Automatically generated unique id + '5f2e63cc8d1c020f8cda6e6a' + + + name + The username + 'admin' + + + email + The user's email address used for password reset + 'test@bosch.com' + + + location + The abbreviation of the Bosch site of the user + 'Rng' + + + level + The permission level, can be either read, write, + dev or admin. The exact level permissions can be found at the + general documentation + + + + devices + Array of all spectrum measurement devices the user has access to + ['Rng01', 'Rng02'] + + + pass + The user's password in hashed form using bcrypt + '$2a$10$m8DqvZR3plZEv8EPwPo7Luvyrm/ZQDiPzfBh6bpU/1XFWOGONkJyG' + + + key + The API key, generated when the user is created + '5f294dd4aa9ea5085c7d7314' + + + models + + Reference to the prediction models the user should have access to. Ignored for dev and + admin as they automatically have access to all models. + + ['5f466fb1e566810dd8b3e919', '5f294d8aaa9ea5085c7d730b'] + + + status + Status of the document, can be either new, validated or + deleted. + 'new' + - models - - group - group the model belongs to - 'VN' - - - models - models of the group with _id, name and url to the Python endpoint - {{'{'}}_id: '5f466fb1e566810dd8b3e919', name: POM, url: 'http://localhost:9099/test'{{'}'}} - + models + + group + group the model belongs to + 'VN' + + + models + models of the group with _id, name and url to the Python endpoint + {{'{'}}_id: '5f466fb1e566810dd8b3e919', name: POM, url: 'http://localhost:9099/test'{{'}'}} + - model_files - - _id - Automatically generated unique id - '5f294d47aa9ea5085c7d7308' - - - name - The name of the model - 'humidity-1' - - - data - The Python model data in binary format - <binary data> - + model_files + + _id + Automatically generated unique id + '5f294d47aa9ea5085c7d7308' + + + name + The name of the model + 'humidity-1' + + + data + The Python model data in binary format + <binary data> + - changelogs - - _id - Automatically generated unique id - '5f2e63cc8d1c020f8cda6e6e' - - - action - The URL which invoked the database write access - 'POST /material/new' - - - collection_name - Collection that was written to - 'material_groups' - - - conditions - Condition arguments used when accessing the database - {{'{'}}id: '5f2e63118d1c020f8cda6a0a'{{'}'}} - - - data - data which was written to the database - {{'{'}}name: 'PBT'{{'}'}} - - - user_id - The user that executed this command - '5f2e63118d1c020f8cda6a09' - + changelogs + + _id + Automatically generated unique id + '5f2e63cc8d1c020f8cda6e6e' + + + action + The URL which invoked the database write access + 'POST /material/new' + + + collection_name + Collection that was written to + 'material_groups' + + + conditions + Condition arguments used when accessing the database + {{'{'}}id: '5f2e63118d1c020f8cda6a0a'{{'}'}} + + + data + data which was written to the database + {{'{'}}name: 'PBT'{{'}'}} + + + user_id + The user that executed this command + '5f2e63118d1c020f8cda6a09' + - help - - _id - Automatically generated unique id - '5f2e63d28d1c020f8cda6f86' - - - key - The key used to find the required help text - '/documentation/database' - - - level - The minimum level required to read this help - 'write' - - - text - The actual help text - 'This page documents the database.' - + help + + _id + Automatically generated unique id + '5f2e63d28d1c020f8cda6f86' + + + key + The key used to find the required help text + '/documentation/database' + + + level + The minimum level required to read this help + 'write' + + + text + The actual help text + 'This page documents the database.' + diff --git a/src/app/documentation/documentation-database/documentation-database.component.scss b/src/app/documentation/documentation-database/documentation-database.component.scss index 2c80228..4eca6a5 100644 --- a/src/app/documentation/documentation-database/documentation-database.component.scss +++ b/src/app/documentation/documentation-database/documentation-database.component.scss @@ -1,14 +1,14 @@ .field-reference td:nth-child(3) { - font-family: boschmono, monospace; + font-family: boschmono, monospace; } span.name { - font-style: italic; + font-style: italic; } pre { - padding: 1rem; - color: #212529; - background: #e6e6e6; - white-space: pre-wrap; + padding: 1rem; + color: #212529; + background: #e6e6e6; + white-space: pre-wrap; } diff --git a/src/app/documentation/documentation-database/documentation-database.component.ts b/src/app/documentation/documentation-database/documentation-database.component.ts index b04797f..78c73ab 100644 --- a/src/app/documentation/documentation-database/documentation-database.component.ts +++ b/src/app/documentation/documentation-database/documentation-database.component.ts @@ -1,15 +1,15 @@ import { Component, OnInit } from '@angular/core'; @Component({ - selector: 'app-documentation-database', - templateUrl: './documentation-database.component.html', - styleUrls: ['./documentation-database.component.scss'] + selector: 'app-documentation-database', + templateUrl: './documentation-database.component.html', + styleUrls: ['./documentation-database.component.scss'] }) export class DocumentationDatabaseComponent implements OnInit { - constructor() { } + constructor() { } - ngOnInit(): void { - } + ngOnInit(): void { + } } diff --git a/src/app/documentation/documentation-models/documentation-models.component.html b/src/app/documentation/documentation-models/documentation-models.component.html index 45f211d..3ec7430 100644 --- a/src/app/documentation/documentation-models/documentation-models.component.html +++ b/src/app/documentation/documentation-models/documentation-models.component.html @@ -1,38 +1,38 @@

Model file upload

- Model files have to be saved as a .pkl file in 80_Modelle_BIC.
- For upload the upload script has to be opened (can be in every environment). The name of the model file has to be - entered wih the .pkl ending in the first command and without .pkl in the second. +Model files have to be saved as a .pkl file in 80_Modelle_BIC.
+For upload the upload script has to be opened (can be in every environment). The name of the model file has to be +entered wih the .pkl ending in the first command and without .pkl in the second.

Adding new model scripts

- Open Spyder in the base environment and open the model.py within. +Open Spyder in the base environment and open the model.py within.

    -
  • Enter model file name without .pkl in fetchData
  • -
  • Add new prediction function below others
  • -
  • duplicate another @app.route and rename according to desired route name and prediction function name
  • +
  • Enter model file name without .pkl in fetchData
  • +
  • Add new prediction function below others
  • +
  • duplicate another @app.route and rename according to desired route name and prediction function name

Testing locally

- To test the model Python script locally, you need to execute it in the base environment, in which all necessary - packages have to be installed. Note that Spyder also has to be opened in the base environment. +To test the model Python script locally, you need to execute it in the base environment, in which all necessary +packages have to be installed. Note that Spyder also has to be opened in the base environment.

    -
  • Open a separate Anaconda Prompt and cd into the definma-UI folder
  • -
  • Adapt the model-mock.json at definma-UI/assets/ and enter the new model URL and name
  • -
  • Execute "python -m http.server" in the Anaconda Prompt
  • -
  • Execute the model.py script in Spyder
  • -
  • Navigate to http://localhost:8000 in the browser
  • -
  • If there are problems with cached data, open the Browser developer console (Ctrl+Shift+I) and tick - "Disable cache" in the Network tab. The console then has to stay open.
  • +
  • Open a separate Anaconda Prompt and cd into the definma-UI folder
  • +
  • Adapt the model-mock.json at definma-UI/assets/ and enter the new model URL and name
  • +
  • Execute "python -m http.server" in the Anaconda Prompt
  • +
  • Execute the model.py script in Spyder
  • +
  • Navigate to http://localhost:8000 in the browser
  • +
  • If there are problems with cached data, open the Browser developer console (Ctrl+Shift+I) and tick + "Disable cache" in the Network tab. The console then has to stay open.

Model script upload

@@ -40,17 +40,17 @@ In the Windows Powershell:
    -
  • cf login (only at the first time)
  • -
  • API endpoint: https://api.sys.de1.bosch-iot-cloud.com
  • -
  • Enter email with .de.bosch.com
  • -
  • Enter BIC password
  • -
  • Select space to upload to (currently 3 - development)
  • -
  • cd into the folder containing the manifest.yaml (here is also model.py)
  • -
  • cf push
  • +
  • cf login (only at the first time)
  • +
  • API endpoint: https://api.sys.de1.bosch-iot-cloud.com
  • +
  • Enter email with .de.bosch.com
  • +
  • Enter BIC password
  • +
  • Select space to upload to (currently 3 - development)
  • +
  • cd into the folder containing the manifest.yaml (here is also model.py)
  • +
  • cf push

- After upload the new model details have to be entered in the UI. +After upload the new model details have to be entered in the UI.

diff --git a/src/app/documentation/documentation-models/documentation-models.component.ts b/src/app/documentation/documentation-models/documentation-models.component.ts index b7f7b30..d0c2131 100644 --- a/src/app/documentation/documentation-models/documentation-models.component.ts +++ b/src/app/documentation/documentation-models/documentation-models.component.ts @@ -1,15 +1,15 @@ import { Component, OnInit } from '@angular/core'; @Component({ - selector: 'app-documentation-models', - templateUrl: './documentation-models.component.html', - styleUrls: ['./documentation-models.component.scss'] + selector: 'app-documentation-models', + templateUrl: './documentation-models.component.html', + styleUrls: ['./documentation-models.component.scss'] }) export class DocumentationModelsComponent implements OnInit { - constructor() { } + constructor() { } - ngOnInit(): void { - } + ngOnInit(): void { + } } diff --git a/src/app/documentation/documentation.component.html b/src/app/documentation/documentation.component.html index 994c74e..9749472 100644 --- a/src/app/documentation/documentation.component.html +++ b/src/app/documentation/documentation.component.html @@ -1,78 +1,78 @@

- - View the presentation explaining the main functions -
- View the T3000 report and - Bachelor Thesis for an in-depth application description. + + View the presentation explaining the main functions +
+View the T3000 report and +Bachelor Thesis for an in-depth application description.

User levels

- - - prediction - read - write - dev - admin - - - use prediction models - specified ones - specified ones - specified ones - all - all - - - read sample data - - - - - - - - add samples/edit own - - - - - - - - read spectral data - - - - - - - - edit other's data - - - - - - - - maintain templates - - - - - - - - edit users - - - - - - + + + prediction + read + write + dev + admin + + + use prediction models + specified ones + specified ones + specified ones + all + all + + + read sample data + + + + + + + + add samples/edit own + + + + + + + + read spectral data + + + + + + + + edit other's data + + + + + + + + maintain templates + + + + + + + + edit users + + + + + + diff --git a/src/app/documentation/documentation.component.scss b/src/app/documentation/documentation.component.scss index 49f650f..f71cbcd 100644 --- a/src/app/documentation/documentation.component.scss +++ b/src/app/documentation/documentation.component.scss @@ -1,17 +1,17 @@ @import "~@inst-iot/bosch-angular-ui-components/styles/variables/colors"; p { - margin-bottom: 20px; + margin-bottom: 20px; } img#db-structure { - width: 100%; + width: 100%; } .rb-ic-checkmark-frame { - color: $brand-success; + color: $brand-success; } .rb-ic-abort-frame { - color: $brand-danger; + color: $brand-danger; } diff --git a/src/app/documentation/documentation.component.ts b/src/app/documentation/documentation.component.ts index 29668b5..14fc59f 100644 --- a/src/app/documentation/documentation.component.ts +++ b/src/app/documentation/documentation.component.ts @@ -3,17 +3,17 @@ import {ApiService} from '../services/api.service'; @Component({ - selector: 'app-documentation', - templateUrl: './documentation.component.html', - styleUrls: ['./documentation.component.scss'] + selector: 'app-documentation', + templateUrl: './documentation.component.html', + styleUrls: ['./documentation.component.scss'] }) export class DocumentationComponent implements OnInit { - constructor( - public api: ApiService - ) { } + constructor( + public api: ApiService + ) { } - ngOnInit(): void { - } + ngOnInit(): void { + } } diff --git a/src/app/error/error.component.html b/src/app/error/error.component.html index 4612d39..2adb9c9 100644 --- a/src/app/error/error.component.html +++ b/src/app/error/error.component.html @@ -1,11 +1,11 @@ -
- {{message}} -
-
- Details -
-

{{detail}}

-
-
+
+ {{message}} +
+
+ Details +
+

{{detail}}

+
+
diff --git a/src/app/error/error.component.ts b/src/app/error/error.component.ts index d8c8f0c..8d49653 100644 --- a/src/app/error/error.component.ts +++ b/src/app/error/error.component.ts @@ -1,18 +1,18 @@ import { Component, OnInit } from '@angular/core'; @Component({ - selector: 'app-error', - templateUrl: './error.component.html', - styleUrls: ['./error.component.scss'] + selector: 'app-error', + templateUrl: './error.component.html', + styleUrls: ['./error.component.scss'] }) export class ErrorComponent implements OnInit { - message = ''; // Main error message - details: string[] = []; // Array of error detail paragraphs + message = ''; // Main error message + details: string[] = []; // Array of error detail paragraphs - constructor() { } + constructor() { } - ngOnInit(): void { - } + ngOnInit(): void { + } } diff --git a/src/app/exists.pipe.spec.ts b/src/app/exists.pipe.spec.ts index a485fa5..586e80b 100644 --- a/src/app/exists.pipe.spec.ts +++ b/src/app/exists.pipe.spec.ts @@ -2,8 +2,8 @@ import { ExistsPipe } from './exists.pipe'; describe('ExistsPipe', () => { - it('create an instance', () => { - const pipe = new ExistsPipe(); - expect(pipe).toBeTruthy(); - }); + it('create an instance', () => { + const pipe = new ExistsPipe(); + expect(pipe).toBeTruthy(); + }); }); diff --git a/src/app/exists.pipe.ts b/src/app/exists.pipe.ts index 18fb1f3..7e6f2c2 100644 --- a/src/app/exists.pipe.ts +++ b/src/app/exists.pipe.ts @@ -1,13 +1,13 @@ import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ - name: 'exists', - pure: true + name: 'exists', + pure: true }) export class ExistsPipe implements PipeTransform { - transform(value: unknown, key?): unknown { - return value || value === 0 ? (key ? value[key] : value) : ''; - } + transform(value: unknown, key?): unknown { + return value || value === 0 ? (key ? value[key] : value) : ''; + } } diff --git a/src/app/help/help.component.html b/src/app/help/help.component.html index c4c96a5..6b145c2 100644 --- a/src/app/help/help.component.html +++ b/src/app/help/help.component.html @@ -1,25 +1,25 @@

Help - +

- - - - - - Save - Delete + + + + + + Save + Delete
-

- {{content.text}} -

- - - Sadly, currently there is no help available for this page. Please contact - {{d.contact.name}} for further questions. - - +

+ {{content.text}} +

+ + + Sadly, currently there is no help available for this page. Please contact + {{d.contact.name}} for further questions. + +
diff --git a/src/app/help/help.component.scss b/src/app/help/help.component.scss index bd39691..5d8abd0 100644 --- a/src/app/help/help.component.scss +++ b/src/app/help/help.component.scss @@ -1,7 +1,7 @@ .delete-btn { - float: right; + float: right; } .content-text { - white-space: pre-line; + white-space: pre-line; } diff --git a/src/app/help/help.component.ts b/src/app/help/help.component.ts index 5681b18..3a933ad 100644 --- a/src/app/help/help.component.ts +++ b/src/app/help/help.component.ts @@ -6,48 +6,48 @@ import {HelpModel} from '../models/help.model'; import {LoginService} from '../services/login.service'; @Component({ - selector: 'app-help', - templateUrl: './help.component.html', - styleUrls: ['./help.component.scss'] + selector: 'app-help', + templateUrl: './help.component.html', + styleUrls: ['./help.component.scss'] }) export class HelpComponent implements OnInit { - content: HelpModel = new HelpModel().deserialize({text: null, level: 'none'}); // Help content - edit = false; // Set true to change to edit mode - private route = ''; // URIComponent encoded route which serves as a key to fetch the help document + content: HelpModel = new HelpModel().deserialize({text: null, level: 'none'}); // Help content + edit = false; // Set true to change to edit mode + private route = ''; // URIComponent encoded route which serves as a key to fetch the help document - constructor( - private router: Router, - public d: DataService, - private api: ApiService, - public login: LoginService - ) { } + constructor( + private router: Router, + public d: DataService, + private api: ApiService, + public login: LoginService + ) { } - ngOnInit(): void { - // Remove ids from path - this.route = encodeURIComponent(this.router.url.replace(/\/[0-9a-f]{24}/, '')); - this.api.get('/help/' + this.route, (data, err) => { - if (!err) { // Content was found - this.content = new HelpModel().deserialize(data); - } - else { - this.content.text = ''; - } - }); - } + ngOnInit(): void { + // Remove ids from path + this.route = encodeURIComponent(this.router.url.replace(/\/[0-9a-f]{24}/, '')); + this.api.get('/help/' + this.route, (data, err) => { + if (!err) { // Content was found + this.content = new HelpModel().deserialize(data); + } + else { + this.content.text = ''; + } + }); + } - saveHelp() { - this.api.post('/help/' + this.route, this.content.sendFormat(), () => { - this.edit = false; - }); - } + saveHelp() { + this.api.post('/help/' + this.route, this.content.sendFormat(), () => { + this.edit = false; + }); + } - deleteHelp() { - this.api.delete('/help/' + this.route, (ignore, err) => { - if (!err) { - this.content = new HelpModel().deserialize({text: null, level: 'none'}); - this.edit = false; - } - }); - } + deleteHelp() { + this.api.delete('/help/' + this.route, (ignore, err) => { + if (!err) { + this.content = new HelpModel().deserialize({text: null, level: 'none'}); + this.edit = false; + } + }); + } } diff --git a/src/app/home/home.component.html b/src/app/home/home.component.html index 837a6dd..23a30fa 100644 --- a/src/app/home/home.component.html +++ b/src/app/home/home.component.html @@ -1,20 +1,20 @@
- - + +
- - {{key.id}} + + {{key.id}} - - - Apply groups - + + + Apply groups + -
- - -
+
+ + +
diff --git a/src/app/home/home.component.scss b/src/app/home/home.component.scss index 4457c15..15aa735 100644 --- a/src/app/home/home.component.scss +++ b/src/app/home/home.component.scss @@ -1,17 +1,17 @@ app-login { - float: left; + float: left; } .key-visual { - width: 70%; - float: right; + width: 70%; + float: right; } .selection{ - float: left; - width: 20%; + float: left; + width: 20%; } rb-form-multi-select { - width: 20%; + width: 20%; } diff --git a/src/app/home/home.component.ts b/src/app/home/home.component.ts index 3134e14..16ee885 100644 --- a/src/app/home/home.component.ts +++ b/src/app/home/home.component.ts @@ -5,138 +5,138 @@ import { Chart } from 'chart.js'; interface KeyInterface { - id: string; - count: number; - active: boolean; + id: string; + count: number; + active: boolean; } @Component({ - selector: 'app-home', - templateUrl: './home.component.html', - styleUrls: ['./home.component.scss'] + selector: 'app-home', + templateUrl: './home.component.html', + styleUrls: ['./home.component.scss'] }) export class HomeComponent implements OnInit { - keys: KeyInterface[] = []; - isActiveKey: { [key: string]: boolean } = {}; // Object to check if key is currently active - myChart: Chart; + keys: KeyInterface[] = []; + isActiveKey: { [key: string]: boolean } = {}; // Object to check if key is currently active + myChart: Chart; - constructor( - public login: LoginService, - public api: ApiService - ) { } + constructor( + public login: LoginService, + public api: ApiService + ) { } - ngOnInit() { - // Fetch all available groups - this.fetchData('/material/groups', data => this.createGroup(data)); - } + ngOnInit() { + // Fetch all available groups + this.fetchData('/material/groups', data => this.createGroup(data)); + } - // Api access with callback - async fetchData(URL: string, processor: any) { - this.api.get(URL, (sData, err, headers) => { - processor(sData); - }); - } + // Api access with callback + async fetchData(URL: string, processor: any) { + this.api.get(URL, (sData, err, headers) => { + processor(sData); + }); + } - // Fill interface with data - createGroup(data: any) { - let temp: KeyInterface[] = []; + // Fill interface with data + createGroup(data: any) { + let temp: KeyInterface[] = []; - for (var i = 0; i < data.length; i++) { - temp.push({ id: data[i], count: 0, active: false }); - } - this.keys = temp; // Invoke update in rb-multiselect - this.initChart(); + for (var i = 0; i < data.length; i++) { + temp.push({ id: data[i], count: 0, active: false }); + } + this.keys = temp; // Invoke update in rb-multiselect + this.initChart(); - // Only neccesary if keys get preselected - //this.calcFieldSelectKeys(); + // Only neccesary if keys get preselected + //this.calcFieldSelectKeys(); - // Fetch all samples populated with according group - this.getSamples(); - } + // Fetch all samples populated with according group + this.getSamples(); + } - // Iterate through active keys to assemble an api request for the required data - getSamples() { - let query = '/samples?status%5B%5D=validated&status=new&filters%5B%5D=%7B%22mode%22%3A%22in%22%2C%22field%22%3A%22material.group%22%2C%22values%22%3A%5B'; - let temp = ''; - this.keys.forEach(key => { - temp += key.active ? '%22' + key.id.split("%").join("%25") + '%22%2C' : ""; // Replace split().join() with replaceAll() - }); - if (temp === '') { - this.countSamples(''); - } else { - query = query + temp.substr(0, temp.length - 3) + '%5D%7D&fields%5B%5D=material.group'; - this.fetchData(query, data => this.countSamples(data)); - } - } + // Iterate through active keys to assemble an api request for the required data + getSamples() { + let query = '/samples?status%5B%5D=validated&status=new&filters%5B%5D=%7B%22mode%22%3A%22in%22%2C%22field%22%3A%22material.group%22%2C%22values%22%3A%5B'; + let temp = ''; + this.keys.forEach(key => { + temp += key.active ? '%22' + key.id.split("%").join("%25") + '%22%2C' : ""; // Replace split().join() with replaceAll() + }); + if (temp === '') { + this.countSamples(''); + } else { + query = query + temp.substr(0, temp.length - 3) + '%5D%7D&fields%5B%5D=material.group'; + this.fetchData(query, data => this.countSamples(data)); + } + } - // Loop through samples and count - countSamples(data: any) { - this.keys.map(key => key.count = 0); - for (var i = 0; i < data.length; i++) { - this.keys.forEach(key => { - if (key.id === data[i].material.group) { - key.count += 1; - } - }); - } - this.updateGraph(); - } + // Loop through samples and count + countSamples(data: any) { + this.keys.map(key => key.count = 0); + for (var i = 0; i < data.length; i++) { + this.keys.forEach(key => { + if (key.id === data[i].material.group) { + key.count += 1; + } + }); + } + this.updateGraph(); + } - // Preset select - calcFieldSelectKeys() { - this.keys.forEach(key => { - this.isActiveKey[key.id] = key.active; - }); - } + // Preset select + calcFieldSelectKeys() { + this.keys.forEach(key => { + this.isActiveKey[key.id] = key.active; + }); + } - // Update keys based on select - updateGroups(activeKeys: any) { - this.keys.forEach(key => { - if (activeKeys.hasOwnProperty(key.id)) { - key.active = activeKeys[key.id]; - } - }); - this.getSamples(); - } + // Update keys based on select + updateGroups(activeKeys: any) { + this.keys.forEach(key => { + if (activeKeys.hasOwnProperty(key.id)) { + key.active = activeKeys[key.id]; + } + }); + this.getSamples(); + } - // Get data for graph based on active keys - updateGraph() { - let nameList: string[] = []; - let dataList: number[] = []; + // Get data for graph based on active keys + updateGraph() { + let nameList: string[] = []; + let dataList: number[] = []; - this.keys.forEach(key => { - if (key.active) { - nameList.push(key.id); - dataList.push(key.count); - } - }) - this.myChart.data.labels = nameList; - this.myChart.data.datasets[0].data = dataList; - this.myChart.update(); - } + this.keys.forEach(key => { + if (key.active) { + nameList.push(key.id); + dataList.push(key.count); + } + }) + this.myChart.data.labels = nameList; + this.myChart.data.datasets[0].data = dataList; + this.myChart.update(); + } - // Initialize graph - async initChart() { - this.myChart = new Chart("myChart", { - type: 'bar', - data: { - labels: [], - datasets: [{ - label: 'Number of samples per group', - data: [], - backgroundColor: 'rgba(0, 86, 145, 1)' - }] - }, - options: { - scales: { - yAxes: [{ - ticks: { - beginAtZero: true - } - }] - } - } - }); - } + // Initialize graph + async initChart() { + this.myChart = new Chart("myChart", { + type: 'bar', + data: { + labels: [], + datasets: [{ + label: 'Number of samples per group', + data: [], + backgroundColor: 'rgba(0, 86, 145, 1)' + }] + }, + options: { + scales: { + yAxes: [{ + ticks: { + beginAtZero: true + } + }] + } + } + }); + } } diff --git a/src/app/img-magnifier/img-magnifier.component.html b/src/app/img-magnifier/img-magnifier.component.html index a547720..b970d44 100644 --- a/src/app/img-magnifier/img-magnifier.component.html +++ b/src/app/img-magnifier/img-magnifier.component.html @@ -1,17 +1,17 @@
-
-
- +
+
+
diff --git a/src/app/img-magnifier/img-magnifier.component.scss b/src/app/img-magnifier/img-magnifier.component.scss index e895365..08d30fd 100644 --- a/src/app/img-magnifier/img-magnifier.component.scss +++ b/src/app/img-magnifier/img-magnifier.component.scss @@ -1,18 +1,18 @@ @import "~@inst-iot/bosch-angular-ui-components/styles/variables/colors"; .img-container { - position:relative; - overflow: hidden; + position:relative; + overflow: hidden; - & > img { - width: 100%; - } + & > img { + width: 100%; + } } .magnifier { - position: absolute; - background: #FFF no-repeat -500px -500px; - z-index: 99; - border: 1px solid #FFF; - box-shadow: 10px 10px 25px $color-bosch-light-gray-b25; + position: absolute; + background: #FFF no-repeat -500px -500px; + z-index: 99; + border: 1px solid #FFF; + box-shadow: 10px 10px 25px $color-bosch-light-gray-b25; } diff --git a/src/app/img-magnifier/img-magnifier.component.ts b/src/app/img-magnifier/img-magnifier.component.ts index 8c55baf..5edbace 100644 --- a/src/app/img-magnifier/img-magnifier.component.ts +++ b/src/app/img-magnifier/img-magnifier.component.ts @@ -1,48 +1,48 @@ import {AfterViewInit, Component, ElementRef, Input, OnInit, ViewChild} from '@angular/core'; @Component({ - selector: 'app-img-magnifier', - templateUrl: './img-magnifier.component.html', - styleUrls: ['./img-magnifier.component.scss'] + selector: 'app-img-magnifier', + templateUrl: './img-magnifier.component.html', + styleUrls: ['./img-magnifier.component.scss'] }) export class ImgMagnifierComponent implements OnInit, AfterViewInit { - @Input() src: string; // Image source - @Input() zoom: number; // Zoom level - @Input() magnifierSize: {width: number, height: number}; // Size of the magnifier - @ViewChild('mainImg') mainImg: ElementRef; + @Input() src: string; // Image source + @Input() zoom: number; // Zoom level + @Input() magnifierSize: {width: number, height: number}; // Size of the magnifier + @ViewChild('mainImg') mainImg: ElementRef; - backgroundSize; - magnifierPos = {x: 0, y: 0}; // Position of the magnifier - showMagnifier = false; + backgroundSize; + magnifierPos = {x: 0, y: 0}; // Position of the magnifier + showMagnifier = false; - constructor( - private window: Window - ) { } + constructor( + private window: Window + ) { } - ngOnInit(): void { - } + ngOnInit(): void { + } - ngAfterViewInit() { - setTimeout(() => { - this.calcBackgroundSize(); - }, 1); - } + ngAfterViewInit() { + setTimeout(() => { + this.calcBackgroundSize(); + }, 1); + } - calcPos(event) { // Calculate the current magnifier position - const img = this.mainImg.nativeElement.getBoundingClientRect(); - this.magnifierPos.x = Math.min( - img.width - this.magnifierSize.width, - Math.max(0, event.pageX - img.left - this.window.pageXOffset - this.magnifierSize.width / 2) - ); - this.magnifierPos.y = Math.min( - img.height - this.magnifierSize.height + 7, - Math.max(0, event.pageY - img.top - this.window.pageYOffset - this.magnifierSize.height / 2) - ); - } + calcPos(event) { // Calculate the current magnifier position + const img = this.mainImg.nativeElement.getBoundingClientRect(); + this.magnifierPos.x = Math.min( + img.width - this.magnifierSize.width, + Math.max(0, event.pageX - img.left - this.window.pageXOffset - this.magnifierSize.width / 2) + ); + this.magnifierPos.y = Math.min( + img.height - this.magnifierSize.height + 7, + Math.max(0, event.pageY - img.top - this.window.pageYOffset - this.magnifierSize.height / 2) + ); + } - calcBackgroundSize() { - this.backgroundSize = this.mainImg ? (this.mainImg.nativeElement.width * this.zoom - this.magnifierSize.width) + - 'px ' + (this.mainImg.nativeElement.height * this.zoom - this.magnifierSize.height) + 'px ' : '0 0'; - } + calcBackgroundSize() { + this.backgroundSize = this.mainImg ? (this.mainImg.nativeElement.width * this.zoom - this.magnifierSize.width) + + 'px ' + (this.mainImg.nativeElement.height * this.zoom - this.magnifierSize.height) + 'px ' : '0 0'; + } } diff --git a/src/app/login/login.component.html b/src/app/login/login.component.html index e3dfca1..3fcbf5a 100644 --- a/src/app/login/login.component.html +++ b/src/app/login/login.component.html @@ -1,25 +1,25 @@ diff --git a/src/app/login/login.component.scss b/src/app/login/login.component.scss index 9f3ea33..ddd110e 100644 --- a/src/app/login/login.component.scss +++ b/src/app/login/login.component.scss @@ -1,17 +1,17 @@ .login-wrapper { - max-width: 250px; + max-width: 250px; } .message { - font-size: 13px; - margin-top: 10px; + font-size: 13px; + margin-top: 10px; } .login-button { - margin-right: 10px; + margin-right: 10px; } .forgot-pass { - display: block; - margin-bottom: 1rem; + display: block; + margin-bottom: 1rem; } diff --git a/src/app/login/login.component.ts b/src/app/login/login.component.ts index fdad337..4d432ef 100644 --- a/src/app/login/login.component.ts +++ b/src/app/login/login.component.ts @@ -6,57 +6,57 @@ import {ApiService} from '../services/api.service'; @Component({ - selector: 'app-login', - templateUrl: './login.component.html', - styleUrls: ['./login.component.scss'] + selector: 'app-login', + templateUrl: './login.component.html', + styleUrls: ['./login.component.scss'] }) export class LoginComponent implements OnInit { - username = ''; // Credentials - password = ''; - email = ''; - message = ''; // Message below login fields - passreset = false; // To toggle between normal login and password reset form + username = ''; // Credentials + password = ''; + email = ''; + message = ''; // Message below login fields + passreset = false; // To toggle between normal login and password reset form - @ViewChild('loginForm') loginForm; + @ViewChild('loginForm') loginForm; - constructor( - private validate: ValidationService, - private login: LoginService, - private api: ApiService, - private router: Router - ) { } + constructor( + private validate: ValidationService, + private login: LoginService, + private api: ApiService, + private router: Router + ) { } - ngOnInit() { - } + ngOnInit() { + } - userLogin() { - if (this.passreset) { // Reset password - this.api.post('/user/passreset', {name: this.username, email: this.email}, (data, err) => { - if (err) { - this.message = 'Could not find a valid user'; - } - else { - this.message = 'Password reset, check your inbox'; - } - }); - } - else { - this.login.login(this.username, this.password).then(ok => { - if (ok) { - this.message = 'Login successful'; - if (this.login.isLevel.read) { - this.router.navigate(['/samples']); - } - else { // Navigate prediction users to prediction as they cannot access samples - this.router.navigate(['/prediction']); - } - } - else { - this.message = 'Wrong credentials!'; - } - }); - } - } + userLogin() { + if (this.passreset) { // Reset password + this.api.post('/user/passreset', {name: this.username, email: this.email}, (data, err) => { + if (err) { + this.message = 'Could not find a valid user'; + } + else { + this.message = 'Password reset, check your inbox'; + } + }); + } + else { + this.login.login(this.username, this.password).then(ok => { + if (ok) { + this.message = 'Login successful'; + if (this.login.isLevel.read) { + this.router.navigate(['/samples']); + } + else { // Navigate prediction users to prediction as they cannot access samples + this.router.navigate(['/prediction']); + } + } + else { + this.message = 'Wrong credentials!'; + } + }); + } + } } diff --git a/src/app/material/material.component.html b/src/app/material/material.component.html index 6338780..e292be6 100644 --- a/src/app/material/material.component.html +++ b/src/app/material/material.component.html @@ -1,65 +1,65 @@

{{material.name | exists}}

- - {{materialnameInput.errors.failure}} - - - {{supplierInput.errors.failure}} - - - {{groupInput.errors.failure}} - + + {{materialnameInput.errors.failure}} + + + {{supplierInput.errors.failure}} + + + {{groupInput.errors.failure}} + - - - The specified {{modalText.list}} could not be found in the list.
- Did you mean {{modalText.suggestion}}? -
-
- - - - - - - - {{parameterInput.errors.failure}} - Cannot be empty - + + + The specified {{modalText.list}} could not be found in the list.
+ Did you mean {{modalText.suggestion}}? +
+
+ + + + + + + + {{parameterInput.errors.failure}} + Cannot be empty + - - Save material - - - Delete sample - + + Save material + + + Delete sample +
- - Do you really want to delete {{material.name}}? - + + Do you really want to delete {{material.name}}? + diff --git a/src/app/material/material.component.scss b/src/app/material/material.component.scss index 314fe09..0ee2c8a 100644 --- a/src/app/material/material.component.scss +++ b/src/app/material/material.component.scss @@ -1,3 +1,3 @@ .delete-material { - float: right; + float: right; } diff --git a/src/app/material/material.component.ts b/src/app/material/material.component.ts index 4da0724..f82da2a 100644 --- a/src/app/material/material.component.ts +++ b/src/app/material/material.component.ts @@ -11,132 +11,132 @@ import {ValidationService} from '../services/validation.service'; import {ErrorComponent} from '../error/error.component'; @Component({ - selector: 'app-material', - templateUrl: './material.component.html', - styleUrls: ['./material.component.scss'] + selector: 'app-material', + templateUrl: './material.component.html', + styleUrls: ['./material.component.scss'] }) export class MaterialComponent implements OnInit, AfterContentChecked { - @ViewChild('materialForm') materialForm: NgForm; + @ViewChild('materialForm') materialForm: NgForm; - material: MaterialModel; // Material to edit - materialNames: string[] = []; // All other material names for unique validation + material: MaterialModel; // Material to edit + materialNames: string[] = []; // All other material names for unique validation - modalText = {list: '', suggestion: ''}; // Modal for group and supplier correction - loading = 0; // Number of loading instances - checkFormAfterInit = true; // Revalidate all fields on the next AfterContentChecked + modalText = {list: '', suggestion: ''}; // Modal for group and supplier correction + loading = 0; // Number of loading instances + checkFormAfterInit = true; // Revalidate all fields on the next AfterContentChecked - constructor( - private api: ApiService, - private route: ActivatedRoute, - public d: DataService, - private modal: ModalService, - public autocomplete: AutocompleteService, - private router: Router, - private validation: ValidationService - ) { } + constructor( + private api: ApiService, + private route: ActivatedRoute, + public d: DataService, + private modal: ModalService, + public autocomplete: AutocompleteService, + private router: Router, + private validation: ValidationService + ) { } - ngOnInit(): void { - this.loading = 5; - this.api.get('/material/' + this.route.snapshot.paramMap.get('id'), data => { - this.material = new MaterialModel().deserialize(data); - this.loading--; - this.d.load('materials', () => { - // Filter out name of the edited material as it can stay the same - this.materialNames = this.d.arr.materials.map(e => e.name).filter(e => e !== this.material.name); - this.loading--; - }); - }); - this.d.load('materialSuppliers', () => { - this.loading--; - }); - this.d.load('materialGroups', () => { - this.loading--; - }); - this.d.load('materialTemplates', () => { - this.loading--; - }); - } + ngOnInit(): void { + this.loading = 5; + this.api.get('/material/' + this.route.snapshot.paramMap.get('id'), data => { + this.material = new MaterialModel().deserialize(data); + this.loading--; + this.d.load('materials', () => { + // Filter out name of the edited material as it can stay the same + this.materialNames = this.d.arr.materials.map(e => e.name).filter(e => e !== this.material.name); + this.loading--; + }); + }); + this.d.load('materialSuppliers', () => { + this.loading--; + }); + this.d.load('materialGroups', () => { + this.loading--; + }); + this.d.load('materialTemplates', () => { + this.loading--; + }); + } - ngAfterContentChecked() { - // Attach validators - if (this.materialForm && this.material.properties.material_template) { // Material template is set - this.d.id.materialTemplates[this.material.properties.material_template].parameters.forEach((parameter, i) => { - this.attachValidator(this.materialForm, 'materialParameter' + i, parameter.range); - }); - } + ngAfterContentChecked() { + // Attach validators + if (this.materialForm && this.material.properties.material_template) { // Material template is set + this.d.id.materialTemplates[this.material.properties.material_template].parameters.forEach((parameter, i) => { + this.attachValidator(this.materialForm, 'materialParameter' + i, parameter.range); + }); + } - // Revalidate - if (this.checkFormAfterInit && this.materialForm !== undefined && this.materialForm.form.get('propertiesSelect')) { - this.checkFormAfterInit = false; - Object.keys(this.materialForm.form.controls).forEach(field => { - this.materialForm.form.get(field).updateValueAndValidity(); - }); - } - } + // Revalidate + if (this.checkFormAfterInit && this.materialForm !== undefined && this.materialForm.form.get('propertiesSelect')) { + this.checkFormAfterInit = false; + Object.keys(this.materialForm.form.controls).forEach(field => { + this.materialForm.form.get(field).updateValueAndValidity(); + }); + } + } - // Attach validators specified in range to input with name - attachValidator(form, name: string, range: {[prop: string]: any}) { - if (form && form.form.get(name)) { - const validators = []; - if (range.hasOwnProperty('required')) { - validators.push(Validators.required); - } - if (range.hasOwnProperty('values')) { - validators.push(this.validation.generate('stringOf', [range.values])); - } - else if (range.hasOwnProperty('min') && range.hasOwnProperty('max')) { - validators.push(this.validation.generate('minMax', [range.min, range.max])); - } - else if (range.hasOwnProperty('min')) { - validators.push(this.validation.generate('min', [range.min])); - } - else if (range.hasOwnProperty('max')) { - validators.push(this.validation.generate('max', [range.max])); - } - form.form.get(name).setValidators(validators); - } - } + // Attach validators specified in range to input with name + attachValidator(form, name: string, range: {[prop: string]: any}) { + if (form && form.form.get(name)) { + const validators = []; + if (range.hasOwnProperty('required')) { + validators.push(Validators.required); + } + if (range.hasOwnProperty('values')) { + validators.push(this.validation.generate('stringOf', [range.values])); + } + else if (range.hasOwnProperty('min') && range.hasOwnProperty('max')) { + validators.push(this.validation.generate('minMax', [range.min, range.max])); + } + else if (range.hasOwnProperty('min')) { + validators.push(this.validation.generate('min', [range.min])); + } + else if (range.hasOwnProperty('max')) { + validators.push(this.validation.generate('max', [range.max])); + } + form.form.get(name).setValidators(validators); + } + } - materialSave() { - this.api.put('/material/' + this.material._id, this.material.sendFormat(), () => { - delete this.d.arr.materials; // Reload materials - this.d.load('materials'); - this.router.navigate(['/materials']); - }); - } + materialSave() { + this.api.put('/material/' + this.material._id, this.material.sendFormat(), () => { + delete this.d.arr.materials; // Reload materials + this.d.load('materials'); + this.router.navigate(['/materials']); + }); + } - deleteConfirm(modal) { - this.modal.open(modal).then(result => { - if (result) { - this.api.delete('/material/' + this.material._id, (ignore, error) => { - if (error) { // Material cannot be deleted as it is still referenced by active samples - const modalRef = this.modal.openComponent(ErrorComponent); - modalRef.instance.message = 'Cannot delete material as it is still in use!'; - } - else { - this.router.navigate(['/materials']); - } - }); - } - }); - } + deleteConfirm(modal) { + this.modal.open(modal).then(result => { + if (result) { + this.api.delete('/material/' + this.material._id, (ignore, error) => { + if (error) { // Material cannot be deleted as it is still referenced by active samples + const modalRef = this.modal.openComponent(ErrorComponent); + modalRef.instance.message = 'Cannot delete material as it is still in use!'; + } + else { + this.router.navigate(['/materials']); + } + }); + } + }); + } - checkTypo(event, list, mKey, modal: TemplateRef) { - // User did not click on suggestion and entry is not in list - if (!(event.relatedTarget && (event.relatedTarget.className.indexOf('rb-dropdown-item') >= 0 || - event.relatedTarget.className.indexOf('close-btn rb-btn rb-passive-link') >= 0)) && - this.d.arr[list].indexOf(this.material[mKey]) < 0) { - this.modalText.list = mKey; - this.modalText.suggestion = this.d.arr[list] // Find possible entry from list - .map(e => ({v: e, s: strCompare.sorensenDice(e, this.material[mKey])})) - .sort((a, b) => b.s - a.s)[0].v; - this.modal.open(modal).then(result => { - if (result) { // Use suggestion - this.material[mKey] = this.modalText.suggestion; - } - }); - } - } + checkTypo(event, list, mKey, modal: TemplateRef) { + // User did not click on suggestion and entry is not in list + if (!(event.relatedTarget && (event.relatedTarget.className.indexOf('rb-dropdown-item') >= 0 || + event.relatedTarget.className.indexOf('close-btn rb-btn rb-passive-link') >= 0)) && + this.d.arr[list].indexOf(this.material[mKey]) < 0) { + this.modalText.list = mKey; + this.modalText.suggestion = this.d.arr[list] // Find possible entry from list + .map(e => ({v: e, s: strCompare.sorensenDice(e, this.material[mKey])})) + .sort((a, b) => b.s - a.s)[0].v; + this.modal.open(modal).then(result => { + if (result) { // Use suggestion + this.material[mKey] = this.modalText.suggestion; + } + }); + } + } } diff --git a/src/app/materials/materials.component.html b/src/app/materials/materials.component.html index 0aa854f..f8ef66f 100644 --- a/src/app/materials/materials.component.html +++ b/src/app/materials/materials.component.html @@ -1,91 +1,91 @@
- - - {{sampleSelect ? 'Validate' : 'Validation'}} - + + + {{sampleSelect ? 'Validate' : 'Validation'}} +
- - - validated - - - new - - - deleted - + + + validated + + + new + + + deleted +
- - - all - - Name - Supplier - Group - {{key.label}} - Numbers - - - - - - - - {{material.name}} - {{material.supplier}} - {{material.group}} - {{material.properties[key.key] | exists}} - {{material.numbers}} - - - - - - - + + + all + + Name + Supplier + Group + {{key.label}} + Numbers + + + + + + + + {{material.name}} + {{material.supplier}} + {{material.group}} + {{material.properties[key.key] | exists}} + {{material.numbers}} + + + + + + + -
- - - - of {{pages}} - - -
+
+ + + + of {{pages}} + + +
- - Do you really want to restore this sample? - + + Do you really want to restore this sample? + diff --git a/src/app/materials/materials.component.scss b/src/app/materials/materials.component.scss index ceec451..20af7db 100644 --- a/src/app/materials/materials.component.scss +++ b/src/app/materials/materials.component.scss @@ -1,63 +1,63 @@ .paging { - height: 50px; - float: left; + height: 50px; + float: left; - rb-form-input { - max-width: 65px; - } + rb-form-input { + max-width: 65px; + } - > * { - float: left; - } + > * { + float: left; + } - > button { - margin-top: 18px; - } + > button { + margin-top: 18px; + } - > span { - margin-top: 20px; - margin-left: 5px; - } + > span { + margin-top: 20px; + margin-left: 5px; + } } .status-selection { - overflow: hidden; - margin-bottom: 10px; - float: left; - margin-right: 15px; + overflow: hidden; + margin-bottom: 10px; + float: left; + margin-right: 15px; - label { - display: block; - font-weight: 700; - font-size: 10px; - } + label { + display: block; + font-weight: 700; + font-size: 10px; + } - rb-form-checkbox { - float: left; - margin-right: 10px; - margin-top: -10px; - } + rb-form-checkbox { + float: left; + margin-right: 10px; + margin-top: -10px; + } } .header-addnew { - & > * { - display: inline; - margin-bottom: 10px; - } + & > * { + display: inline; + margin-bottom: 10px; + } - rb-icon-button { - float: right; - } + rb-icon-button { + float: right; + } } .material-search { - width: 300px; - float: left; - position: relative; + width: 300px; + float: left; + position: relative; - span { - position: absolute; - right: 5px; - top: 24px; - } + span { + position: absolute; + right: 5px; + top: 24px; + } } diff --git a/src/app/materials/materials.component.ts b/src/app/materials/materials.component.ts index eb392a0..458a541 100644 --- a/src/app/materials/materials.component.ts +++ b/src/app/materials/materials.component.ts @@ -6,92 +6,92 @@ import {ModalService} from '@inst-iot/bosch-angular-ui-components'; @Component({ - selector: 'app-materials', - templateUrl: './materials.component.html', - styleUrls: ['./materials.component.scss'] + selector: 'app-materials', + templateUrl: './materials.component.html', + styleUrls: ['./materials.component.scss'] }) export class MaterialsComponent implements OnInit { - materials: MaterialModel[] = []; // All materials - templateKeys: {key: string, label: string}[] = []; // Material template keys - materialStatus = {validated: true, new: true, deleted: false}; // Material statuses to show - materialSearch = ''; // Material name search string - sampleSelect = false; // Set to true to show checkboxes for validation + materials: MaterialModel[] = []; // All materials + templateKeys: {key: string, label: string}[] = []; // Material template keys + materialStatus = {validated: true, new: true, deleted: false}; // Material statuses to show + materialSearch = ''; // Material name search string + sampleSelect = false; // Set to true to show checkboxes for validation - page = 1; // Page settings - pages = 0; - pageSize = 25; + page = 1; // Page settings + pages = 0; + pageSize = 25; - constructor( - private api: ApiService, - public d: DataService, - private modal: ModalService - ) { } + constructor( + private api: ApiService, + public d: DataService, + private modal: ModalService + ) { } - ngOnInit(): void { - this.loadMaterials(); - this.d.load('materialTemplates', () => { - this.d.arr.materialTemplates.forEach(template => { - template.parameters.forEach(parameter => { - this.templateKeys.push({key: parameter.name, label: `${this.ucFirst(template.name)} ${parameter.name}`}); - }); - }); - // Filter out duplicates - this.templateKeys = this.templateKeys.filter((e, i, a) => !a.slice(0, i).find(el => el.key === e.key)); - }); - } + ngOnInit(): void { + this.loadMaterials(); + this.d.load('materialTemplates', () => { + this.d.arr.materialTemplates.forEach(template => { + template.parameters.forEach(parameter => { + this.templateKeys.push({key: parameter.name, label: `${this.ucFirst(template.name)} ${parameter.name}`}); + }); + }); + // Filter out duplicates + this.templateKeys = this.templateKeys.filter((e, i, a) => !a.slice(0, i).find(el => el.key === e.key)); + }); + } - loadMaterials() { - this.api.get('/materials?' + - Object.entries(this.materialStatus).filter(e => e[1]).map(e => 'status[]=' + e[0]).join('&'), data => { - this.materials = data.map(e => new MaterialModel().deserialize(e)); - this.pages = Math.ceil(this.materials.length / this.pageSize); - this.page = 1; - }); - } + loadMaterials() { + this.api.get('/materials?' + + Object.entries(this.materialStatus).filter(e => e[1]).map(e => 'status[]=' + e[0]).join('&'), data => { + this.materials = data.map(e => new MaterialModel().deserialize(e)); + this.pages = Math.ceil(this.materials.length / this.pageSize); + this.page = 1; + }); + } - validate() { - if (this.sampleSelect) { // Selection was done do actual validation - this.materials.forEach(sample => { - if (sample.selected) { - this.api.put('/material/validate/' + sample._id); - } - }); - this.loadMaterials(); - this.sampleSelect = false; - } - else { // Activate validation mode - this.sampleSelect = true; - } - } + validate() { + if (this.sampleSelect) { // Selection was done do actual validation + this.materials.forEach(sample => { + if (sample.selected) { + this.api.put('/material/validate/' + sample._id); + } + }); + this.loadMaterials(); + this.sampleSelect = false; + } + else { // Activate validation mode + this.sampleSelect = true; + } + } - selectAll(event) { // Toggle selection for all items except deleted ones - this.materials.forEach(material => { - if (material.status !== 'deleted') { - material.selected = event.target.checked; - } - else { - material.selected = false; - } - }); - } + selectAll(event) { // Toggle selection for all items except deleted ones + this.materials.forEach(material => { + if (material.status !== 'deleted') { + material.selected = event.target.checked; + } + else { + material.selected = false; + } + }); + } - restoreMaterial(id, modal) { - this.modal.open(modal).then(res => { - if (res) { - this.api.put('/sample/restore/' + id, {}, ignore => { - this.materials.find(e => e._id === id).status = 'new'; - }); - } - }); - } + restoreMaterial(id, modal) { + this.modal.open(modal).then(res => { + if (res) { + this.api.put('/sample/restore/' + id, {}, ignore => { + this.materials.find(e => e._id === id).status = 'new'; + }); + } + }); + } - ucFirst(string) { // Convert first character of string to uppercase - return string[0].toUpperCase() + string.slice(1); - } + ucFirst(string) { // Convert first character of string to uppercase + return string[0].toUpperCase() + string.slice(1); + } - materialFilter(ms) { // Filter function for material names - return e => e.name.indexOf(ms) >= 0; - } + materialFilter(ms) { // Filter function for material names + return e => e.name.indexOf(ms) >= 0; + } } diff --git a/src/app/model-templates/model-templates.component.html b/src/app/model-templates/model-templates.component.html index e361d20..7b5a92b 100644 --- a/src/app/model-templates/model-templates.component.html +++ b/src/app/model-templates/model-templates.component.html @@ -1,73 +1,73 @@ - New model + New model
- - {{groupInput.errors.failure}} - Cannot be empty - - - {{nameInput.errors.failure}} - Cannot be empty - - - {{urlInput.errors.failure}} - Cannot be empty - + + {{groupInput.errors.failure}} + Cannot be empty + + + {{nameInput.errors.failure}} + Cannot be empty + + + {{urlInput.errors.failure}} + Cannot be empty + - - Save model - + + Save model +
- - Name - URL - - - + + Name + URL + + + - - {{group.group}} - - {{modelItem.name}} - {{modelItem.url}} - - - - - - - - - + + {{group.group}} + + {{modelItem.name}} + {{modelItem.url}} + + + + + + + + + - - Model files - - - - - {{file.name}} - {{file.size | size:'M'}} - - + + Model files + + + + + {{file.name}} + {{file.size | size:'M'}} + + - - Do you really want to delete? - + + Do you really want to delete? + diff --git a/src/app/model-templates/model-templates.component.ts b/src/app/model-templates/model-templates.component.ts index e76be79..48f2c5e 100644 --- a/src/app/model-templates/model-templates.component.ts +++ b/src/app/model-templates/model-templates.component.ts @@ -7,77 +7,77 @@ import {ModalService} from '@inst-iot/bosch-angular-ui-components'; import omit from 'lodash/omit'; @Component({ - selector: 'app-model-templates', - templateUrl: './model-templates.component.html', - styleUrls: ['./model-templates.component.scss'] + selector: 'app-model-templates', + templateUrl: './model-templates.component.html', + styleUrls: ['./model-templates.component.scss'] }) export class ModelTemplatesComponent implements OnInit { - newModel = false; // Display new model dialog - modelGroup = ''; // Group of the edited model - oldModelGroup = ''; // Group of the edited model before editing started - oldModelName = ''; // Name of the edited model before editing started - model = new ModelItemModel().models[0]; // Edited model - groups = []; // All model group names + newModel = false; // Display new model dialog + modelGroup = ''; // Group of the edited model + oldModelGroup = ''; // Group of the edited model before editing started + oldModelName = ''; // Name of the edited model before editing started + model = new ModelItemModel().models[0]; // Edited model + groups = []; // All model group names - constructor( - private api: ApiService, - public autocomplete: AutocompleteService, - public d: DataService, - private modal: ModalService - ) { } + constructor( + private api: ApiService, + public autocomplete: AutocompleteService, + public d: DataService, + private modal: ModalService + ) { } - ngOnInit(): void { - this.loadGroups(); - } + ngOnInit(): void { + this.loadGroups(); + } - loadGroups() { - delete this.d.arr.modelGroups; - this.d.load('modelGroups', () => { - this.groups = this.d.arr.modelGroups.map(e => e.group); - }); - this.d.load('modelFiles'); - } + loadGroups() { + delete this.d.arr.modelGroups; + this.d.load('modelGroups', () => { + this.groups = this.d.arr.modelGroups.map(e => e.group); + }); + this.d.load('modelFiles'); + } - saveModel() { - // Group was changed, delete model in old group - if (this.oldModelGroup !== '' && this.modelGroup !== this.oldModelGroup) { - this.delete(null, this.oldModelName, this.oldModelGroup); - } - this.api.post('/model/' + this.modelGroup, omit(this.model, '_id'), () => { - this.newModel = false; // Reset model edit parameters - this.loadGroups(); - this.modelGroup = ''; - this.oldModelGroup = ''; - this.oldModelName = ''; - this.model = new ModelItemModel().models[0]; - }); - } + saveModel() { + // Group was changed, delete model in old group + if (this.oldModelGroup !== '' && this.modelGroup !== this.oldModelGroup) { + this.delete(null, this.oldModelName, this.oldModelGroup); + } + this.api.post('/model/' + this.modelGroup, omit(this.model, '_id'), () => { + this.newModel = false; // Reset model edit parameters + this.loadGroups(); + this.modelGroup = ''; + this.oldModelGroup = ''; + this.oldModelName = ''; + this.model = new ModelItemModel().models[0]; + }); + } - delete(modal, name, group = null) { - new Promise(resolve => { - if (modal) { // If modal was given, wait for result - this.modal.open(modal).then(result => { - resolve(result); - }); - } - else { - resolve(true); - } - }).then(res => { - if (res) { - if (group) { // Delete model group if given - this.api.delete(`/model/${group}/${name}`, () => { - this.loadGroups(); - }); - } - else { // Delete model file - this.api.delete(`/model/file/${name}`, () => { - this.d.arr.modelFiles.splice(this.d.arr.modelFiles.findIndex(e => e.name === name), 1); - }); - } - } - }); - } + delete(modal, name, group = null) { + new Promise(resolve => { + if (modal) { // If modal was given, wait for result + this.modal.open(modal).then(result => { + resolve(result); + }); + } + else { + resolve(true); + } + }).then(res => { + if (res) { + if (group) { // Delete model group if given + this.api.delete(`/model/${group}/${name}`, () => { + this.loadGroups(); + }); + } + else { // Delete model file + this.api.delete(`/model/file/${name}`, () => { + this.d.arr.modelFiles.splice(this.d.arr.modelFiles.findIndex(e => e.name === name), 1); + }); + } + } + }); + } } diff --git a/src/app/models/base.model.spec.ts b/src/app/models/base.model.spec.ts index 4bbedf0..3241bbf 100644 --- a/src/app/models/base.model.spec.ts +++ b/src/app/models/base.model.spec.ts @@ -1,7 +1,7 @@ import { BaseModel } from './base.model'; describe('BaseModel', () => { - it('should create an instance', () => { - expect(new BaseModel()).toBeTruthy(); - }); + it('should create an instance', () => { + expect(new BaseModel()).toBeTruthy(); + }); }); diff --git a/src/app/models/base.model.ts b/src/app/models/base.model.ts index 587913d..835bcbc 100644 --- a/src/app/models/base.model.ts +++ b/src/app/models/base.model.ts @@ -1,10 +1,10 @@ export class BaseModel { - deserialize(input: any): this { - Object.assign(this, input); - return this; - } + deserialize(input: any): this { + Object.assign(this, input); + return this; + } - sendFormat(): this { - return this; - } + sendFormat(): this { + return this; + } } diff --git a/src/app/models/changelog.model.spec.ts b/src/app/models/changelog.model.spec.ts index 49eea17..9b81dbf 100644 --- a/src/app/models/changelog.model.spec.ts +++ b/src/app/models/changelog.model.spec.ts @@ -1,7 +1,7 @@ import { ChangelogModel } from './changelog.model'; describe('ChangelogModel', () => { - it('should create an instance', () => { - expect(new ChangelogModel()).toBeTruthy(); - }); + it('should create an instance', () => { + expect(new ChangelogModel()).toBeTruthy(); + }); }); diff --git a/src/app/models/changelog.model.ts b/src/app/models/changelog.model.ts index df17c13..6aeeed7 100644 --- a/src/app/models/changelog.model.ts +++ b/src/app/models/changelog.model.ts @@ -2,10 +2,10 @@ import {BaseModel} from './base.model'; import {IdModel} from './id.model'; export class ChangelogModel extends BaseModel { - _id: IdModel = null; - date: Date; - action: string; - collection: string; - conditions: {[key: string]: any}; - data: {[key: string]: any}; + _id: IdModel = null; + date: Date; + action: string; + collection: string; + conditions: {[key: string]: any}; + data: {[key: string]: any}; } diff --git a/src/app/models/custom-fields.model.spec.ts b/src/app/models/custom-fields.model.spec.ts index 4b2d5d2..6c4d315 100644 --- a/src/app/models/custom-fields.model.spec.ts +++ b/src/app/models/custom-fields.model.spec.ts @@ -1,7 +1,7 @@ import { CustomFieldsModel } from './custom-fields.model'; describe('CustomFieldsModel', () => { - it('should create an instance', () => { - expect(new CustomFieldsModel()).toBeTruthy(); - }); + it('should create an instance', () => { + expect(new CustomFieldsModel()).toBeTruthy(); + }); }); diff --git a/src/app/models/custom-fields.model.ts b/src/app/models/custom-fields.model.ts index 7f20193..e73e4fb 100644 --- a/src/app/models/custom-fields.model.ts +++ b/src/app/models/custom-fields.model.ts @@ -2,6 +2,6 @@ import {BaseModel} from './base.model'; export class CustomFieldsModel extends BaseModel { - name = ''; - qty = 0; + name = ''; + qty = 0; } diff --git a/src/app/models/help.model.spec.ts b/src/app/models/help.model.spec.ts index 899436f..275bb5f 100644 --- a/src/app/models/help.model.spec.ts +++ b/src/app/models/help.model.spec.ts @@ -1,7 +1,7 @@ import { HelpModel } from './help.model'; describe('Help.Model', () => { - it('should create an instance', () => { - expect(new HelpModel()).toBeTruthy(); - }); + it('should create an instance', () => { + expect(new HelpModel()).toBeTruthy(); + }); }); diff --git a/src/app/models/help.model.ts b/src/app/models/help.model.ts index cc4e63b..04dbbaa 100644 --- a/src/app/models/help.model.ts +++ b/src/app/models/help.model.ts @@ -1,6 +1,6 @@ import {BaseModel} from './base.model'; export class HelpModel extends BaseModel { - text = ''; - level = 'none'; + text = ''; + level = 'none'; } diff --git a/src/app/models/material.model.spec.ts b/src/app/models/material.model.spec.ts index 6be3590..40ce02d 100644 --- a/src/app/models/material.model.spec.ts +++ b/src/app/models/material.model.spec.ts @@ -1,7 +1,7 @@ import { MaterialModel } from './material.model'; describe('MaterialModel', () => { - it('should create an instance', () => { - expect(new MaterialModel()).toBeTruthy(); - }); + it('should create an instance', () => { + expect(new MaterialModel()).toBeTruthy(); + }); }); diff --git a/src/app/models/material.model.ts b/src/app/models/material.model.ts index 38c6479..5a70b3f 100644 --- a/src/app/models/material.model.ts +++ b/src/app/models/material.model.ts @@ -3,16 +3,16 @@ import {IdModel} from './id.model'; import {BaseModel} from './base.model'; export class MaterialModel extends BaseModel { - _id: IdModel = null; - name = ''; - supplier = ''; - group = ''; - properties: {material_template: string, [prop: string]: string} = {material_template: null}; - numbers: string[] = ['']; - selected = false; - status = ''; + _id: IdModel = null; + name = ''; + supplier = ''; + group = ''; + properties: {material_template: string, [prop: string]: string} = {material_template: null}; + numbers: string[] = ['']; + selected = false; + status = ''; - sendFormat() { - return pick(this, ['name', 'supplier', 'group', 'numbers', 'properties']); - } + sendFormat() { + return pick(this, ['name', 'supplier', 'group', 'numbers', 'properties']); + } } diff --git a/src/app/models/measurement.model.spec.ts b/src/app/models/measurement.model.spec.ts index 2a96f8f..e8ee3e6 100644 --- a/src/app/models/measurement.model.spec.ts +++ b/src/app/models/measurement.model.spec.ts @@ -1,7 +1,7 @@ import { MeasurementModel } from './measurement.model'; describe('MeasurementModel', () => { - it('should create an instance', () => { - expect(new MeasurementModel()).toBeTruthy(); - }); + it('should create an instance', () => { + expect(new MeasurementModel()).toBeTruthy(); + }); }); diff --git a/src/app/models/measurement.model.ts b/src/app/models/measurement.model.ts index 0b8ed3d..c0a8bdf 100644 --- a/src/app/models/measurement.model.ts +++ b/src/app/models/measurement.model.ts @@ -4,28 +4,28 @@ import {IdModel} from './id.model'; import {BaseModel} from './base.model'; export class MeasurementModel extends BaseModel { - _id: IdModel = null; - sample_id: IdModel = null; - measurement_template: IdModel; - values: {[prop: string]: any} = {}; - status = ''; + _id: IdModel = null; + sample_id: IdModel = null; + measurement_template: IdModel; + values: {[prop: string]: any} = {}; + status = ''; - constructor(measurementTemplate: IdModel = null) { - super(); - this.measurement_template = measurementTemplate; - } + constructor(measurementTemplate: IdModel = null) { + super(); + this.measurement_template = measurementTemplate; + } - deserialize(input: any): this { - Object.assign(this, input); - Object.keys(this.values).forEach(key => { - if (this.values[key] === null) { - this.values[key] = ''; - } - }); - return this; - } + deserialize(input: any): this { + Object.assign(this, input); + Object.keys(this.values).forEach(key => { + if (this.values[key] === null) { + this.values[key] = ''; + } + }); + return this; + } - sendFormat(omitValues = []) { - return omit(pick(this, ['sample_id', 'measurement_template', 'values']), omitValues); - } + sendFormat(omitValues = []) { + return omit(pick(this, ['sample_id', 'measurement_template', 'values']), omitValues); + } } diff --git a/src/app/models/model-file.model.spec.ts b/src/app/models/model-file.model.spec.ts index 6b1f9f3..1ac7171 100644 --- a/src/app/models/model-file.model.spec.ts +++ b/src/app/models/model-file.model.spec.ts @@ -1,7 +1,7 @@ import { ModelFileModel } from './model-file.model'; describe('ModelFile.Model', () => { - it('should create an instance', () => { - expect(new ModelFileModel()).toBeTruthy(); - }); + it('should create an instance', () => { + expect(new ModelFileModel()).toBeTruthy(); + }); }); diff --git a/src/app/models/model-file.model.ts b/src/app/models/model-file.model.ts index 4235376..29514e7 100644 --- a/src/app/models/model-file.model.ts +++ b/src/app/models/model-file.model.ts @@ -1,6 +1,6 @@ import {BaseModel} from './base.model'; export class ModelFileModel extends BaseModel { - name = ''; - size = 0; + name = ''; + size = 0; } diff --git a/src/app/models/model-item.model.spec.ts b/src/app/models/model-item.model.spec.ts index d57897f..2802696 100644 --- a/src/app/models/model-item.model.spec.ts +++ b/src/app/models/model-item.model.spec.ts @@ -1,7 +1,7 @@ import { ModelItemModel } from './model-item.model'; describe('ModelItemModel', () => { - it('should create an instance', () => { - expect(new ModelItemModel()).toBeTruthy(); - }); + it('should create an instance', () => { + expect(new ModelItemModel()).toBeTruthy(); + }); }); diff --git a/src/app/models/model-item.model.ts b/src/app/models/model-item.model.ts index 86f8302..371c40d 100644 --- a/src/app/models/model-item.model.ts +++ b/src/app/models/model-item.model.ts @@ -1,9 +1,9 @@ import {BaseModel} from './base.model'; export class ModelItemModel extends BaseModel { - group = ''; - models = [{ - name: '', - url: '' - }]; + group = ''; + models = [{ + name: '', + url: '' + }]; } diff --git a/src/app/models/sample.model.spec.ts b/src/app/models/sample.model.spec.ts index 2959c9a..ea0379d 100644 --- a/src/app/models/sample.model.spec.ts +++ b/src/app/models/sample.model.spec.ts @@ -1,7 +1,7 @@ import { SampleModel } from './sample.model'; describe('SampleModel', () => { - it('should create an instance', () => { - expect(new SampleModel()).toBeTruthy(); - }); + it('should create an instance', () => { + expect(new SampleModel()).toBeTruthy(); + }); }); diff --git a/src/app/models/sample.model.ts b/src/app/models/sample.model.ts index 966d2b7..732e26b 100644 --- a/src/app/models/sample.model.ts +++ b/src/app/models/sample.model.ts @@ -6,63 +6,63 @@ import {MeasurementModel} from './measurement.model'; import {BaseModel} from './base.model'; export class SampleModel extends BaseModel { - _id: IdModel = null; - color = ''; - number = ''; - type = ''; - batch = ''; - condition: {condition_template: string, [prop: string]: string} = {condition_template: null}; - material_id: IdModel = null; - material: MaterialModel; - measurements: MeasurementModel[] = []; - note_id: IdModel = null; - user_id: IdModel = null; - selected = false; - notes: { - comment: string, - sample_references: {sample_id: IdModel, relation: string}[], - custom_fields: {[prop: string]: string} - } = {comment: '', sample_references: [], custom_fields: {}}; - status = ''; - added: Date = null; + _id: IdModel = null; + color = ''; + number = ''; + type = ''; + batch = ''; + condition: {condition_template: string, [prop: string]: string} = {condition_template: null}; + material_id: IdModel = null; + material: MaterialModel; + measurements: MeasurementModel[] = []; + note_id: IdModel = null; + user_id: IdModel = null; + selected = false; + notes: { + comment: string, + sample_references: {sample_id: IdModel, relation: string}[], + custom_fields: {[prop: string]: string} + } = {comment: '', sample_references: [], custom_fields: {}}; + status = ''; + added: Date = null; - deserialize(input: any): this { - Object.assign(this, input); - if (input.hasOwnProperty('material')) { - this.material = new MaterialModel().deserialize(input.material); - this.material_id = input.material._id; - } - if (input.hasOwnProperty('measurements')) { - this.measurements = input.measurements.map(e => new MeasurementModel().deserialize(e)); - } - if (input.hasOwnProperty('added')) { - this.added = new Date(input.added); - } - return this; - } + deserialize(input: any): this { + Object.assign(this, input); + if (input.hasOwnProperty('material')) { + this.material = new MaterialModel().deserialize(input.material); + this.material_id = input.material._id; + } + if (input.hasOwnProperty('measurements')) { + this.measurements = input.measurements.map(e => new MeasurementModel().deserialize(e)); + } + if (input.hasOwnProperty('added')) { + this.added = new Date(input.added); + } + return this; + } - sendFormat(pickCondition = true) { - const pickFields = ['color', 'type', 'batch', 'material_id', 'notes']; - if (pickCondition) { - pickFields.push('condition'); - } - const tmp = pick(this.conditionTemplateCheck(), pickFields); - Object.keys(tmp).forEach(key => { - if (tmp[key] === undefined) { - delete tmp[key]; - } - }); - if (this.material && this.material.name === undefined) { - delete tmp.material_id; - } - return tmp; - } + sendFormat(pickCondition = true) { + const pickFields = ['color', 'type', 'batch', 'material_id', 'notes']; + if (pickCondition) { + pickFields.push('condition'); + } + const tmp = pick(this.conditionTemplateCheck(), pickFields); + Object.keys(tmp).forEach(key => { + if (tmp[key] === undefined) { + delete tmp[key]; + } + }); + if (this.material && this.material.name === undefined) { + delete tmp.material_id; + } + return tmp; + } - private conditionTemplateCheck() { - const res = cloneDeep(this); - if (res.condition.condition_template === null) { - res.condition = {}; - } - return res; - } + private conditionTemplateCheck() { + const res = cloneDeep(this); + if (res.condition.condition_template === null) { + res.condition = {}; + } + return res; + } } diff --git a/src/app/models/template.model.spec.ts b/src/app/models/template.model.spec.ts index 39913ae..6ae3559 100644 --- a/src/app/models/template.model.spec.ts +++ b/src/app/models/template.model.spec.ts @@ -1,7 +1,7 @@ import { TemplateModel } from './template.model'; describe('TemplateModel', () => { - it('should create an instance', () => { - expect(new TemplateModel()).toBeTruthy(); - }); + it('should create an instance', () => { + expect(new TemplateModel()).toBeTruthy(); + }); }); diff --git a/src/app/models/template.model.ts b/src/app/models/template.model.ts index 23cbbe6..0750397 100644 --- a/src/app/models/template.model.ts +++ b/src/app/models/template.model.ts @@ -2,9 +2,9 @@ import {IdModel} from './id.model'; import {BaseModel} from './base.model'; export class TemplateModel extends BaseModel { - _id: IdModel = null; - name = ''; - version = 0; - first_id: IdModel = null; - parameters: {name: string, range: {[prop: string]: any}, rangeString?: string}[] = []; + _id: IdModel = null; + name = ''; + version = 0; + first_id: IdModel = null; + parameters: {name: string, range: {[prop: string]: any}, rangeString?: string}[] = []; } diff --git a/src/app/models/user.model.spec.ts b/src/app/models/user.model.spec.ts index c5ec2e7..c6b439e 100644 --- a/src/app/models/user.model.spec.ts +++ b/src/app/models/user.model.spec.ts @@ -1,7 +1,7 @@ import { UserModel } from './user.model'; describe('UserModel', () => { - it('should create an instance', () => { - expect(new UserModel()).toBeTruthy(); - }); + it('should create an instance', () => { + expect(new UserModel()).toBeTruthy(); + }); }); diff --git a/src/app/models/user.model.ts b/src/app/models/user.model.ts index 172008d..1bc46a0 100644 --- a/src/app/models/user.model.ts +++ b/src/app/models/user.model.ts @@ -3,31 +3,31 @@ import {BaseModel} from './base.model'; import {IdModel} from './id.model'; export class UserModel extends BaseModel{ - _id: IdModel = null; - name = ''; - origName = ''; - email = ''; - level = ''; - location = ''; - devices = ['']; - models = ['']; - status = 'new'; - edit = false; + _id: IdModel = null; + name = ''; + origName = ''; + email = ''; + level = ''; + location = ''; + devices = ['']; + models = ['']; + status = 'new'; + edit = false; - deserialize(input: any): this { - Object.assign(this, input); - this.origName = this.name; - return this; - } + deserialize(input: any): this { + Object.assign(this, input); + this.origName = this.name; + return this; + } - sendFormat(mode = 'user') { - const keys = ['name', 'email', 'location', 'devices']; - if (mode === 'admin') { - keys.push('level'); - keys.push('models'); - this.devices = this.devices.filter(e => e); - this.models = this.models.filter(e => e); - } - return pick(this, keys); - } + sendFormat(mode = 'user') { + const keys = ['name', 'email', 'location', 'devices']; + if (mode === 'admin') { + keys.push('level'); + keys.push('models'); + this.devices = this.devices.filter(e => e); + this.models = this.models.filter(e => e); + } + return pick(this, keys); + } } diff --git a/src/app/object.pipe.spec.ts b/src/app/object.pipe.spec.ts index dd4963c..ab9b728 100644 --- a/src/app/object.pipe.spec.ts +++ b/src/app/object.pipe.spec.ts @@ -2,8 +2,8 @@ import { ObjectPipe } from './object.pipe'; describe('ObjectPipe', () => { - it('create an instance', () => { - const pipe = new ObjectPipe(); - expect(pipe).toBeTruthy(); - }); + it('create an instance', () => { + const pipe = new ObjectPipe(); + expect(pipe).toBeTruthy(); + }); }); diff --git a/src/app/object.pipe.ts b/src/app/object.pipe.ts index eed5e53..3030bcd 100644 --- a/src/app/object.pipe.ts +++ b/src/app/object.pipe.ts @@ -2,14 +2,14 @@ import { Pipe, PipeTransform } from '@angular/core'; import omit from 'lodash/omit'; @Pipe({ - name: 'object', - pure: true + name: 'object', + pure: true }) export class ObjectPipe implements PipeTransform { - transform(value: object, omitValue: string[] = []): string { - const res = omit(value, omitValue); - return res && Object.keys(res).length ? JSON.stringify(res) : ''; - } + transform(value: object, omitValue: string[] = []): string { + const res = omit(value, omitValue); + return res && Object.keys(res).length ? JSON.stringify(res) : ''; + } } diff --git a/src/app/parameters.pipe.spec.ts b/src/app/parameters.pipe.spec.ts index 28ba71e..1f756ea 100644 --- a/src/app/parameters.pipe.spec.ts +++ b/src/app/parameters.pipe.spec.ts @@ -2,8 +2,8 @@ import { ParametersPipe } from './parameters.pipe'; describe('ParametersPipe', () => { - it('create an instance', () => { - const pipe = new ParametersPipe(); - expect(pipe).toBeTruthy(); - }); + it('create an instance', () => { + const pipe = new ParametersPipe(); + expect(pipe).toBeTruthy(); + }); }); diff --git a/src/app/parameters.pipe.ts b/src/app/parameters.pipe.ts index e8eecb4..6347c69 100644 --- a/src/app/parameters.pipe.ts +++ b/src/app/parameters.pipe.ts @@ -1,13 +1,13 @@ import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ - name: 'parameters' + name: 'parameters' }) export class ParametersPipe implements PipeTransform { - transform(value: {name: string, range: object}[]): string { - return `{${value.map(e => `${e.name}: <${JSON.stringify(e.range).replace('{}', 'any') - .replace(/["{}]/g, '')}>`).join(', ')}}`; - } + transform(value: {name: string, range: object}[]): string { + return `{${value.map(e => `${e.name}: <${JSON.stringify(e.range).replace('{}', 'any') + .replace(/["{}]/g, '')}>`).join(', ')}}`; + } } diff --git a/src/app/prediction/prediction.component.html b/src/app/prediction/prediction.component.html index ff3e00b..aca8d42 100644 --- a/src/app/prediction/prediction.component.html +++ b/src/app/prediction/prediction.component.html @@ -1,93 +1,93 @@ - -
-
+ +
+
- +
- -

- {{spectrumNames[i]}}: - - {{predictionEntry.category}} {{predictionEntry.value}} {{predictionEntry.label}} - - # -

-
- -

- Average result: - - {{predictionEntry.category}} {{predictionEntry.value}} {{predictionEntry.label}}  {{( predictionEntry.std !== '' ? ' (standard deviation: '+ predictionEntry.std+')' : '') }} - - # -

- Details -
-

- {{spectrumNames[i]}}: - - {{predictionEntry.category}} {{predictionEntry.value}} {{predictionEntry.label}} - - # -

-
-
+ +

+ {{spectrumNames[i]}}: + + {{predictionEntry.category}} {{predictionEntry.value}} {{predictionEntry.label}} + + # +

+
+ +

+ Average result: + + {{predictionEntry.category}} {{predictionEntry.value}} {{predictionEntry.label}}  {{( predictionEntry.std !== '' ? ' (standard deviation: '+ predictionEntry.std+')' : '') }} + + # +

+ Details +
+

+ {{spectrumNames[i]}}: + + {{predictionEntry.category}} {{predictionEntry.value}} {{predictionEntry.label}} + + # +

+
+
- - + + - - - - Predict - -
-
+ + + + Predict + +
+
-
- Prediction of: - - - - -
+
+ Prediction of: + + + + +
- Export to CSV + Export to CSV - Export to PDF + Export to PDF
- - + +
-

#Disclaimer: This tool is still under development

-

- The prediction and classification of material parameters are validated only for certain conditions. - These results may therefore under no circumstances be used to evaluate quality-relevant issues.
- For more details please contact {{d.contact.name}}. -

+

#Disclaimer: This tool is still under development

+

+ The prediction and classification of material parameters are validated only for certain conditions. + These results may therefore under no circumstances be used to evaluate quality-relevant issues.
+ For more details please contact {{d.contact.name}}. +

diff --git a/src/app/prediction/prediction.component.scss b/src/app/prediction/prediction.component.scss index c930087..e344070 100644 --- a/src/app/prediction/prediction.component.scss +++ b/src/app/prediction/prediction.component.scss @@ -1,20 +1,20 @@ .dpt-chart { - max-width: 800px; - margin-left: auto; - margin-right: auto; - margin-top: 0.5rem; + max-width: 800px; + margin-left: auto; + margin-right: auto; + margin-top: 0.5rem; } .file-input { - display: grid; - grid-template-columns: 1fr auto; - grid-column-gap: 1rem; + display: grid; + grid-template-columns: 1fr auto; + grid-column-gap: 1rem; } .result { - margin: 30px 0; + margin: 30px 0; - h4 { - margin-bottom: 1rem; - } + h4 { + margin-bottom: 1rem; + } } diff --git a/src/app/prediction/prediction.component.ts b/src/app/prediction/prediction.component.ts index e809c05..42a9602 100644 --- a/src/app/prediction/prediction.component.ts +++ b/src/app/prediction/prediction.component.ts @@ -15,267 +15,266 @@ import * as pdfFonts from 'pdfmake/build/vfs_fonts'; @Component({ - selector: 'app-prediction', - templateUrl: './prediction.component.html', - styleUrls: ['./prediction.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})) - ]) - ] - ) - ] + selector: 'app-prediction', + templateUrl: './prediction.component.html', + styleUrls: ['./prediction.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 PredictionComponent implements OnInit { - result: { predictions: any[], mean: any[] }; // Prediction result from python container - loading = false; - activeGroup: ModelItemModel = new ModelItemModel(); - activeModelIndex = 0; - // If true, spectra belong to different samples, otherwise multiple spectra from the same sample are given - multipleSamples = false; - spectrumNames: string[] = []; - spectrum: string[][] = [[]]; - flattenedSpectra = []; - chart = []; - 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: {}}] - }, - responsive: true, - tooltips: {enabled: false}, - hover: {mode: null}, - maintainAspectRatio: true, - plugins: {datalabels: {display: false}} - }; + result: { predictions: any[], mean: any[] }; // Prediction result from python container + loading = false; + activeGroup: ModelItemModel = new ModelItemModel(); + activeModelIndex = 0; + // If true, spectra belong to different samples, otherwise multiple spectra from the same sample are given + multipleSamples = false; + spectrumNames: string[] = []; + spectrum: string[][] = [[]]; + flattenedSpectra = []; + chart = []; + 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: {}}] + }, + responsive: true, + tooltips: {enabled: false}, + hover: {mode: null}, + maintainAspectRatio: true, + plugins: {datalabels: {display: false}} + }; - constructor( - private api: ApiService, - public d: DataService, - public login: LoginService - ) { - this.chart[0] = cloneDeep(this.chartInit); - } + constructor( + private api: ApiService, + public d: DataService, + public login: LoginService + ) { + this.chart[0] = cloneDeep(this.chartInit); + } - ngOnInit(): void { - this.d.load('modelGroups', () => { - this.activeGroup = this.d.arr.modelGroups[0]; - }); - } + ngOnInit(): void { + this.d.load('modelGroups', () => { + this.activeGroup = this.d.arr.modelGroups[0]; + }); + } - fileToArray(files) { - this.loading = true; - this.flattenedSpectra = []; - this.chart = []; - let load = files.length; - this.spectrumNames = files.map(e => e.name); - for (const i in files) { - if (files.hasOwnProperty(i)) { - const fileReader = new FileReader(); - fileReader.onload = () => { - // Parse to database spectrum representation - this.spectrum = fileReader.result.toString().split('\r\n').map(e => e.split(',').map(el => parseFloat(el))) - .filter(el => el.length === 2) as any; - // Flatten to format needed for prediction - this.flattenedSpectra[i] = {labels: this.spectrum.map(e => e[0]), values: this.spectrum.map(e => e[1])}; - // Add to chart - this.chart[i] = cloneDeep(this.chartInit); - this.chart[i].data = this.spectrum.map(e => ({x: parseFloat(e[0]), y: parseFloat(e[1])})); - load --; - if (load <= 0) { // All loaded - this.loadPrediction(); - } - }; - fileReader.readAsText(files[i]); - } - } - } + fileToArray(files) { + this.loading = true; + this.flattenedSpectra = []; + this.chart = []; + let load = files.length; + this.spectrumNames = files.map(e => e.name); + for (const i in files) { + if (files.hasOwnProperty(i)) { + const fileReader = new FileReader(); + fileReader.onload = () => { + // Parse to database spectrum representation + this.spectrum = fileReader.result.toString().split('\r\n').map(e => e.split(',').map(el => parseFloat(el))) + .filter(el => el.length === 2) as any; + // Flatten to format needed for prediction + this.flattenedSpectra[i] = {labels: this.spectrum.map(e => e[0]), values: this.spectrum.map(e => e[1])}; + // Add to chart + this.chart[i] = cloneDeep(this.chartInit); + this.chart[i].data = this.spectrum.map(e => ({x: parseFloat(e[0]), y: parseFloat(e[1])})); + load --; + if (load <= 0) { // All loaded + this.loadPrediction(); + } + }; + fileReader.readAsText(files[i]); + } + } + } - loadPrediction() { - this.loading = true; - this.api.post(this.activeGroup.models[this.activeModelIndex].url, this.flattenedSpectra, data => { - let tmp = Object.entries(omit(data, ['mean', 'std', 'label'])) // Form: [[label, [{value, color}]]] - .map((entry: any) => entry[1].map(e => ({category: entry[0], label: data.label[entry[0]], value: e.value, color: e.color}))); // Form: [[{category, label, value, color}]] - this.result = { - predictions: tmp[0].map((ignore, columnIndex) => tmp.map(row => row[columnIndex])), // Transpose tmp - mean: Object.entries(data.mean) - .map((entry:any) => ({category: entry[0], label: data.label[entry[0]], value: entry[1].value, color: entry[1].color, std: data.std[entry[0]]})) // Form: [{category, label, value, color}] - }; - this.loading = false; - }); - } + loadPrediction() { + this.loading = true; + this.api.post(this.activeGroup.models[this.activeModelIndex].url, this.flattenedSpectra, data => { + let tmp = Object.entries(omit(data, ['mean', 'std', 'label'])) // Form: [[label, [{value, color}]]] + .map((entry: any) => entry[1].map(e => ({category: entry[0], label: data.label[entry[0]], value: e.value, color: e.color}))); // Form: [[{category, label, value, color}]] + this.result = { + predictions: tmp[0].map((ignore, columnIndex) => tmp.map(row => row[columnIndex])), // Transpose tmp + mean: Object.entries(data.mean) + .map((entry:any) => ({category: entry[0], label: data.label[entry[0]], value: entry[1].value, color: entry[1].color, std: data.std[entry[0]]})) // Form: [{category, label, value, color}] + }; + this.loading = false; + }); + } - groupChange(index) { // Group was changed - this.activeGroup = this.d.arr.modelGroups[index]; - this.activeModelIndex = 0; - this.result = undefined; - } + groupChange(index) { // Group was changed + this.activeGroup = this.d.arr.modelGroups[index]; + this.activeModelIndex = 0; + this.result = undefined; + } - // Aggregates spectrum names and prediction values into an associative array - prepareExport() { - const zip = (a, b) => a.map((k, i) => [k, b[i]]); - const values = this.result.predictions - .map(prediction => prediction - .filter(field => field.category === 'Prediction') - .map(field => field.value)); - return zip(this.spectrumNames, values); - } + // Aggregates spectrum names and prediction values into an associative array + prepareExport() { + const zip = (a, b) => a.map((k, i) => [k, b[i]]); + const values = this.result.predictions + .map(prediction => prediction + .filter(field => field.category === 'Prediction') + .map(field => field.value)); + return zip(this.spectrumNames, values); + } - // Generates a timestamp suitable for file naming - generateTimestamp() { - let d = new Date(); - return d.getFullYear() + '-' + (d.getMonth() + 1) + '-' + d.getDate() + '--' + d.getHours() + "-" + d.getMinutes(); - } + // Generates a timestamp suitable for file naming + generateTimestamp() { + let d = new Date(); + return d.getFullYear() + '-' + (d.getMonth() + 1) + '-' + d.getDate() + '--' + d.getHours() + "-" + d.getMinutes(); + } - // Converts the prediction results to a CSV file - exportCSV() { - const predictions = this.prepareExport(); - const csv = predictions.map(line => line.join(";")).join("\n"); - FileSaver.saveAs(new Blob([csv], { type: 'text/csv;charset=utf-8' }), 'predictions-' + this.generateTimestamp() + '.csv'); - } + // Converts the prediction results to a CSV file + exportCSV() { + const predictions = this.prepareExport(); + const csv = predictions.map(line => line.join(";")).join("\n"); + FileSaver.saveAs(new Blob([csv], { type: 'text/csv;charset=utf-8' }), 'predictions-' + this.generateTimestamp() + '.csv'); + } - // Converts the prediction results to a PDF file - exportPDF() { - const doc = { - content: [ - { - text: 'DeFinMa - Decoding the Fingerprint of Materials by AI', - style: 'header' - }, - { - table: { - widths: ['auto', '*', 'auto', 'auto', 'auto'], - body: [ - [ - {text: new Date().toLocaleDateString(), style: 'tableHeader'}, - {text: 'Customer', style: 'tableHeader'}, - {text: 'Security class', style: 'tableHeader'}, - {text: 'Person in charge', style: 'tableHeader'}, - {text: 'Phone', style: 'tableHeader'}], - [ - this.activeGroup.group, - this.login.username, - 'Intern', - { - stack: [ - 'CR/APS1-Lotter', - 'CR/APS1-Lingenfelser' - ] - }, - { - stack: [ - '0711/811-49017', - '0711/811-6897' - ] - } - ] - ] - } - }, - { - text: 'Prediction of ' + this.activeGroup.group + ' (' + this.activeGroup.models[this.activeModelIndex].name + ')*', - style: 'subheader' - }, - { - text: this.result.mean.map(e => e.category + ' ' + e.value + ' ' + e.label + ' ' + (e.std !== '' ? (' (standard deviation: ' + e.std + ')') : '')) - }, - { - table: { - widths: ['*', 'auto'], - body: [ - [{text: 'Input Data / Sample Name', style: 'tableHeader'}, {text: 'Prediction*', style: 'tableHeader'}] - ].concat(this.prepareExport()) - } - }, - { - text: 'Reference Data', - style: 'subheader' - }, - { - image: document.getElementsByTagName('canvas')[0].toDataURL('image/png'), - width: 500 - }, - { - table: { - body: [[{ - stack: [ - { - text: '*Disclaimer: This tool is still under development and Testing', - style: 'subsubheader' - }, - { - text: [ - 'The prediction and classification of material parameters are validated only for certain conditions.', - 'These results may therefore under no circumstances be used to evaluate quality-relevant issues.', - 'For more details please contact ', - { - text: 'CR/APS1-Lingenfelser', - link: 'mailto:dominic.lingenfelser@bosch.com' - }, - '.' - ] - }, - ] - }]], - }, - margin: [25, 20] - }, - { - table: { - widths: ['*', '*', 'auto'], - body: [ - [{text: 'Pr\u00fcfung', style: 'tableHeader'}, {text: 'Freigabe', style: 'tableHeader'}, {text: 'Datum', style: 'tableHeader'}], - ['CR/APS1-Lotter', 'CR/APS1-Lingenfelser', new Date().toLocaleDateString()] - ], - } - } - ], - footer: { - text: '\u00a9 Alle Rechte bei Robert Bosch GmbH, auch f\u00fcr den Fall von Schutzreichtsanmeldungen. Jede Verf\u00fcgungsbefugnis, wie Kopier- und Weitergaberecht, bei uns.', - fontSize: 8, - alignment: 'center' - }, - styles: { - header: { - fontSize: 18, - bold: true, - margin: [0, 10] - }, - subheader: { - fontSize: 15, - bold: true, - margin: [0, 8] - }, - subsubheader: { - bold: true, - margin: [0, 5] - }, - tableHeader: { - bold: true, - fontSize: 13, - color: 'black' - } - }, - pageMargins: [50, 50, 50, 15] - }; + // Converts the prediction results to a PDF file + exportPDF() { + const doc = { + content: [ + { + text: 'DeFinMa - Decoding the Fingerprint of Materials by AI', + style: 'header' + }, + { + table: { + widths: ['auto', '*', 'auto', 'auto', 'auto'], + body: [[ + {text: new Date().toLocaleDateString(), style: 'tableHeader'}, + {text: 'Customer', style: 'tableHeader'}, + {text: 'Security class', style: 'tableHeader'}, + {text: 'Person in charge', style: 'tableHeader'}, + {text: 'Phone', style: 'tableHeader'}], + [ + this.activeGroup.group, + this.login.username, + 'Intern', + { + stack: [ + 'CR/APS1-Lotter', + 'CR/APS1-Lingenfelser' + ] + }, + { + stack: [ + '0711/811-49017', + '0711/811-6897' + ] + } + ] + ] + } + }, + { + text: 'Prediction of ' + this.activeGroup.group + ' (' + this.activeGroup.models[this.activeModelIndex].name + ')*', + style: 'subheader' + }, + { + text: this.result.mean.map(e => e.category + ' ' + e.value + ' ' + e.label + ' ' + (e.std !== '' ? (' (standard deviation: ' + e.std + ')') : '')) + }, + { + table: { + widths: ['*', 'auto'], + body: [ + [{text: 'Input Data / Sample Name', style: 'tableHeader'}, {text: 'Prediction*', style: 'tableHeader'}] + ].concat(this.prepareExport()) + } + }, + { + text: 'Reference Data', + style: 'subheader' + }, + { + image: document.getElementsByTagName('canvas')[0].toDataURL('image/png'), + width: 500 + }, + { + table: { + body: [[{ + stack: [ + { + text: '*Disclaimer: This tool is still under development and Testing', + style: 'subsubheader' + }, + { + text: [ + 'The prediction and classification of material parameters are validated only for certain conditions.', + 'These results may therefore under no circumstances be used to evaluate quality-relevant issues.', + 'For more details please contact ', + { + text: 'CR/APS1-Lingenfelser', + link: 'mailto:dominic.lingenfelser@bosch.com' + }, + '.' + ] + }, + ] + }]], + }, + margin: [25, 20] + }, + { + table: { + widths: ['*', '*', 'auto'], + body: [ + [{text: 'Pr\u00fcfung', style: 'tableHeader'}, {text: 'Freigabe', style: 'tableHeader'}, {text: 'Datum', style: 'tableHeader'}], + ['CR/APS1-Lotter', 'CR/APS1-Lingenfelser', new Date().toLocaleDateString()] + ], + } + } + ], + footer: { + text: '\u00a9 Alle Rechte bei Robert Bosch GmbH, auch f\u00fcr den Fall von Schutzreichtsanmeldungen. Jede Verf\u00fcgungsbefugnis, wie Kopier- und Weitergaberecht, bei uns.', + fontSize: 8, + alignment: 'center' + }, + styles: { + header: { + fontSize: 18, + bold: true, + margin: [0, 10] + }, + subheader: { + fontSize: 15, + bold: true, + margin: [0, 8] + }, + subsubheader: { + bold: true, + margin: [0, 5] + }, + tableHeader: { + bold: true, + fontSize: 13, + color: 'black' + } + }, + pageMargins: [50, 50, 50, 15] + }; - pdfMake.createPdf(doc).download('predictions-' + this.generateTimestamp() + '.pdf'); - } + pdfMake.createPdf(doc).download('predictions-' + this.generateTimestamp() + '.pdf'); + } } diff --git a/src/app/rb-custom-inputs/rb-array-input/array-input-helper.service.ts b/src/app/rb-custom-inputs/rb-array-input/array-input-helper.service.ts index 110bc9b..791aafb 100644 --- a/src/app/rb-custom-inputs/rb-array-input/array-input-helper.service.ts +++ b/src/app/rb-custom-inputs/rb-array-input/array-input-helper.service.ts @@ -2,25 +2,25 @@ import { Injectable } from '@angular/core'; import {Observable, Subject} from 'rxjs'; @Injectable({ - providedIn: 'root' + providedIn: 'root' }) export class ArrayInputHelperService { - com: Subject<{ id: string, index: number, value: any }> = new Subject(); + com: Subject<{ id: string, index: number, value: any }> = new Subject(); - constructor() { } + constructor() { } - values(id: string) { // Observable which returns new values as they come for subscribed id - return new Observable<{index: number, value: any}>(observer => { - this.com.subscribe(data => { - if (data.id === id) { - observer.next({index: data.index, value: data.value}); - } - }); - }); - } + values(id: string) { // Observable which returns new values as they come for subscribed id + return new Observable<{index: number, value: any}>(observer => { + this.com.subscribe(data => { + if (data.id === id) { + observer.next({index: data.index, value: data.value}); + } + }); + }); + } - newValue(id: string, index: number, value: any) { // Set new value - this.com.next({id, index, value}); - } + newValue(id: string, index: number, value: any) { // Set new value + this.com.next({id, index, value}); + } } diff --git a/src/app/rb-custom-inputs/rb-array-input/rb-array-input.component.html b/src/app/rb-custom-inputs/rb-array-input/rb-array-input.component.html index d68f8c8..4053d5e 100644 --- a/src/app/rb-custom-inputs/rb-array-input/rb-array-input.component.html +++ b/src/app/rb-custom-inputs/rb-array-input/rb-array-input.component.html @@ -1,3 +1,3 @@ - + diff --git a/src/app/rb-custom-inputs/rb-array-input/rb-array-input.component.ts b/src/app/rb-custom-inputs/rb-array-input/rb-array-input.component.ts index bf67848..b141f8f 100644 --- a/src/app/rb-custom-inputs/rb-array-input/rb-array-input.component.ts +++ b/src/app/rb-custom-inputs/rb-array-input/rb-array-input.component.ts @@ -1,13 +1,13 @@ import { - AfterViewInit, - Component, - ContentChild, - Directive, - forwardRef, - HostListener, - Input, - OnInit, - TemplateRef + AfterViewInit, + Component, + ContentChild, + Directive, + forwardRef, + HostListener, + Input, + OnInit, + TemplateRef } from '@angular/core'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; import cloneDeep from 'lodash/cloneDeep'; @@ -15,150 +15,150 @@ import {ArrayInputHelperService} from './array-input-helper.service'; @Directive({ // Directive for template and input values - // tslint:disable-next-line:directive-selector - selector: '[rbArrayInputItem]' + // tslint:disable-next-line:directive-selector + selector: '[rbArrayInputItem]' }) export class RbArrayInputItemDirective { - constructor(public templateRef: TemplateRef) { - } + constructor(public templateRef: TemplateRef) { + } } @Directive({ // Directive for change detection - // tslint:disable-next-line:directive-selector - selector: '[rbArrayInputListener]' + // tslint:disable-next-line:directive-selector + selector: '[rbArrayInputListener]' }) export class RbArrayInputListenerDirective { - @Input() rbArrayInputListener: string; - @Input() index; + @Input() rbArrayInputListener: string; + @Input() index; - constructor( - private helperService: ArrayInputHelperService - ) { } + constructor( + private helperService: ArrayInputHelperService + ) { } - @HostListener('ngModelChange', ['$event']) - onChange(event) { // Emit new value - this.helperService.newValue(this.rbArrayInputListener, this.index, event); - } + @HostListener('ngModelChange', ['$event']) + onChange(event) { // Emit new value + this.helperService.newValue(this.rbArrayInputListener, this.index, event); + } } @Component({ - // tslint:disable-next-line:component-selector - selector: 'rb-array-input', - templateUrl: './rb-array-input.component.html', - styleUrls: ['./rb-array-input.component.scss'], - providers: [{provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => RbArrayInputComponent), multi: true}] + // tslint:disable-next-line:component-selector + selector: 'rb-array-input', + templateUrl: './rb-array-input.component.html', + styleUrls: ['./rb-array-input.component.scss'], + providers: [{provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => RbArrayInputComponent), multi: true}] }) export class RbArrayInputComponent implements ControlValueAccessor, OnInit, AfterViewInit { - pushTemplate: any = ''; // Array element template - @Input('pushTemplate') set _pushTemplate(value) { - this.pushTemplate = value; - if (this.values.length) { - this.updateArray(); - } - } - @Input() pushPath: string = null; + pushTemplate: any = ''; // Array element template + @Input('pushTemplate') set _pushTemplate(value) { + this.pushTemplate = value; + if (this.values.length) { + this.updateArray(); + } + } + @Input() pushPath: string = null; - @ContentChild(RbArrayInputItemDirective) item: RbArrayInputItemDirective; - @ContentChild(RbArrayInputListenerDirective) item2: RbArrayInputListenerDirective; + @ContentChild(RbArrayInputItemDirective) item: RbArrayInputItemDirective; + @ContentChild(RbArrayInputListenerDirective) item2: RbArrayInputListenerDirective; - values = []; // Main array to display + values = []; // Main array to display - onChange = (ignore?: any): void => {}; - onTouched = (ignore?: any): void => {}; + onChange = (ignore?: any): void => {}; + onTouched = (ignore?: any): void => {}; - constructor( - private helperService: ArrayInputHelperService - ) { } + constructor( + private helperService: ArrayInputHelperService + ) { } - ngOnInit(): void { - } + ngOnInit(): void { + } - ngAfterViewInit() { - setTimeout(() => { // Needed to find reference - this.helperService.values(this.item2.rbArrayInputListener).subscribe(data => { // Action on value change - // Assign value - if (this.pushPath) { - this.values[data.index][this.pushPath] = data.value; - } - else { - this.values[data.index] = data.value; - } - this.updateArray(); - }); - }, 0); - } + ngAfterViewInit() { + setTimeout(() => { // Needed to find reference + this.helperService.values(this.item2.rbArrayInputListener).subscribe(data => { // Action on value change + // Assign value + if (this.pushPath) { + this.values[data.index][this.pushPath] = data.value; + } + else { + this.values[data.index] = data.value; + } + this.updateArray(); + }); + }, 0); + } - updateArray() { - let res; - // Adjust fields if pushTemplate is specified - if (this.pushTemplate !== null) { - if (this.pushPath) { - // Remove last element if last two are empty - if (this.values[this.values.length - 1][this.pushPath] === '' && - this.values[this.values.length - 2][this.pushPath] === '') { - this.values.pop(); - } - // Add element if last all are filled - else if (this.values.filter(e => e[this.pushPath] !== '').length === this.values.length) { - this.values.push(cloneDeep(this.pushTemplate)); - } - res = this.values.filter(e => e[this.pushPath] !== ''); - } - else { - // Remove last element if last two are empty - if (this.values[this.values.length - 1] === '' && this.values[this.values.length - 2] === '') { - this.values.pop(); - } - else if (this.values.filter(e => e !== '').length === this.values.length) { // Add element if all are is filled - this.values.push(cloneDeep(this.pushTemplate)); - } - res = this.values.filter(e => e !== ''); - } - } - else { - this.values = [this.values[0]]; - res = this.values; - } - if (!res.length) { - res = ['']; - } - this.onChange(res); // Trigger ngModel with filled elements - } + updateArray() { + let res; + // Adjust fields if pushTemplate is specified + if (this.pushTemplate !== null) { + if (this.pushPath) { + // Remove last element if last two are empty + if (this.values[this.values.length - 1][this.pushPath] === '' && + this.values[this.values.length - 2][this.pushPath] === '') { + this.values.pop(); + } + // Add element if last all are filled + else if (this.values.filter(e => e[this.pushPath] !== '').length === this.values.length) { + this.values.push(cloneDeep(this.pushTemplate)); + } + res = this.values.filter(e => e[this.pushPath] !== ''); + } + else { + // Remove last element if last two are empty + if (this.values[this.values.length - 1] === '' && this.values[this.values.length - 2] === '') { + this.values.pop(); + } + else if (this.values.filter(e => e !== '').length === this.values.length) { // Add element if all are is filled + this.values.push(cloneDeep(this.pushTemplate)); + } + res = this.values.filter(e => e !== ''); + } + } + else { + this.values = [this.values[0]]; + res = this.values; + } + if (!res.length) { + res = ['']; + } + this.onChange(res); // Trigger ngModel with filled elements + } - writeValue(obj: any) { // Add empty value on init - if (obj) { - if (this.pushTemplate !== null) { - // Filter out empty values - if (this.pushPath) { - this.values = [...obj.filter(e => e[this.pushPath] !== ''), cloneDeep(this.pushTemplate)]; - } - else { - this.values = [...obj.filter(e => e !== ''), cloneDeep(this.pushTemplate)]; - } - } - else { - this.values = obj; - } - } - else { - if (this.pushTemplate !== null) { - this.values = [cloneDeep(this.pushTemplate)]; - } - else { - this.values = ['']; - } - } - } + writeValue(obj: any) { // Add empty value on init + if (obj) { + if (this.pushTemplate !== null) { + // Filter out empty values + if (this.pushPath) { + this.values = [...obj.filter(e => e[this.pushPath] !== ''), cloneDeep(this.pushTemplate)]; + } + else { + this.values = [...obj.filter(e => e !== ''), cloneDeep(this.pushTemplate)]; + } + } + else { + this.values = obj; + } + } + else { + if (this.pushTemplate !== null) { + this.values = [cloneDeep(this.pushTemplate)]; + } + else { + this.values = ['']; + } + } + } - registerOnChange(fn: any) { - this.onChange = fn; - } + registerOnChange(fn: any) { + this.onChange = fn; + } - registerOnTouched(fn: any) { - this.onTouched = fn; - } + registerOnTouched(fn: any) { + this.onTouched = fn; + } } diff --git a/src/app/rb-custom-inputs/rb-custom-inputs.module.ts b/src/app/rb-custom-inputs/rb-custom-inputs.module.ts index d286532..963de30 100644 --- a/src/app/rb-custom-inputs/rb-custom-inputs.module.ts +++ b/src/app/rb-custom-inputs/rb-custom-inputs.module.ts @@ -2,7 +2,7 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RbTableComponent } from './rb-table/rb-table.component'; import {RbArrayInputComponent, RbArrayInputListenerDirective, RbArrayInputItemDirective} from - './rb-array-input/rb-array-input.component'; +'./rb-array-input/rb-array-input.component'; import {RbUiComponentsModule} from '@inst-iot/bosch-angular-ui-components'; import {FormsModule} from '@angular/forms'; import { RbIconButtonComponent } from './rb-icon-button/rb-icon-button.component'; @@ -10,24 +10,24 @@ import { RbIconButtonComponent } from './rb-icon-button/rb-icon-button.component @NgModule({ - declarations: [ - RbTableComponent, - RbArrayInputComponent, - RbArrayInputListenerDirective, - RbArrayInputItemDirective, - RbIconButtonComponent - ], - imports: [ - CommonModule, - FormsModule, - RbUiComponentsModule - ], - exports: [ - RbTableComponent, - RbArrayInputComponent, - RbArrayInputListenerDirective, - RbArrayInputItemDirective, - RbIconButtonComponent - ] + declarations: [ + RbTableComponent, + RbArrayInputComponent, + RbArrayInputListenerDirective, + RbArrayInputItemDirective, + RbIconButtonComponent + ], + imports: [ + CommonModule, + FormsModule, + RbUiComponentsModule + ], + exports: [ + RbTableComponent, + RbArrayInputComponent, + RbArrayInputListenerDirective, + RbArrayInputItemDirective, + RbIconButtonComponent + ] }) export class RbCustomInputsModule { } diff --git a/src/app/rb-custom-inputs/rb-icon-button/rb-icon-button.component.html b/src/app/rb-custom-inputs/rb-icon-button/rb-icon-button.component.html index fdfd5f0..8ba6f9d 100644 --- a/src/app/rb-custom-inputs/rb-icon-button/rb-icon-button.component.html +++ b/src/app/rb-custom-inputs/rb-icon-button/rb-icon-button.component.html @@ -1,4 +1,4 @@ diff --git a/src/app/rb-custom-inputs/rb-icon-button/rb-icon-button.component.scss b/src/app/rb-custom-inputs/rb-icon-button/rb-icon-button.component.scss index 8cb83b5..eab1020 100644 --- a/src/app/rb-custom-inputs/rb-icon-button/rb-icon-button.component.scss +++ b/src/app/rb-custom-inputs/rb-icon-button/rb-icon-button.component.scss @@ -1,8 +1,8 @@ .icon-space { - margin-right: 0.1em !important; + margin-right: 0.1em !important; } button.rb-btn > span:not(.icon-space) { - margin-right: -10px; - margin-left: -10px; + margin-right: -10px; + margin-left: -10px; } diff --git a/src/app/rb-custom-inputs/rb-icon-button/rb-icon-button.component.ts b/src/app/rb-custom-inputs/rb-icon-button/rb-icon-button.component.ts index 413e430..49e6fb4 100644 --- a/src/app/rb-custom-inputs/rb-icon-button/rb-icon-button.component.ts +++ b/src/app/rb-custom-inputs/rb-icon-button/rb-icon-button.component.ts @@ -2,22 +2,22 @@ import {Component, Input, OnInit} from '@angular/core'; @Component({ - // tslint:disable-next-line:component-selector - selector: 'rb-icon-button', - templateUrl: './rb-icon-button.component.html', - styleUrls: ['./rb-icon-button.component.scss'] + // tslint:disable-next-line:component-selector + selector: 'rb-icon-button', + templateUrl: './rb-icon-button.component.html', + styleUrls: ['./rb-icon-button.component.scss'] }) export class RbIconButtonComponent implements OnInit { - @Input() icon: string; - @Input() mode: string; - @Input() iconOnly; - @Input() disabled; - @Input() type = 'button'; + @Input() icon: string; + @Input() mode: string; + @Input() iconOnly; + @Input() disabled; + @Input() type = 'button'; - constructor() { } + constructor() { } - ngOnInit(): void { - } + ngOnInit(): void { + } } diff --git a/src/app/rb-custom-inputs/rb-table/rb-table.component.html b/src/app/rb-custom-inputs/rb-table/rb-table.component.html index ff94c8a..18e97a1 100644 --- a/src/app/rb-custom-inputs/rb-table/rb-table.component.html +++ b/src/app/rb-custom-inputs/rb-table/rb-table.component.html @@ -1,5 +1,5 @@
- - -
+ + +
diff --git a/src/app/rb-custom-inputs/rb-table/rb-table.component.scss b/src/app/rb-custom-inputs/rb-table/rb-table.component.scss index 57f52f6..c5f8029 100644 --- a/src/app/rb-custom-inputs/rb-table/rb-table.component.scss +++ b/src/app/rb-custom-inputs/rb-table/rb-table.component.scss @@ -1,38 +1,38 @@ @import "~@inst-iot/bosch-angular-ui-components/styles/variables/colors"; .table-wrapper.scroll-top { - overflow-x: auto; - width: 100%; + overflow-x: auto; + width: 100%; - &, & > table { // scrollbar at the top - transform:rotateX(180deg); - -ms-transform:rotateX(180deg); /* IE 9 */ - -webkit-transform:rotateX(180deg); /* Safari and Chrome */ - } + &, & > table { // scrollbar at the top + transform:rotateX(180deg); + -ms-transform:rotateX(180deg); /* IE 9 */ + -webkit-transform:rotateX(180deg); /* Safari and Chrome */ + } } table { - width: 100%; - border-collapse: collapse; + width: 100%; + border-collapse: collapse; - ::ng-deep tr { - border-bottom: 1px solid $color-gray-mercury; + ::ng-deep tr { + border-bottom: 1px solid $color-gray-mercury; - ::ng-deep td, ::ng-deep th { - padding: 8px 5px; - } + ::ng-deep td, ::ng-deep th { + padding: 8px 5px; + } - ::ng-deep th { - text-align: left; - } - } + ::ng-deep th { + text-align: left; + } + } } table.ellipsis { - ::ng-deep td, ::ng-deep th { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - max-width: 200px; - } + ::ng-deep td, ::ng-deep th { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + max-width: 200px; + } } diff --git a/src/app/rb-custom-inputs/rb-table/rb-table.component.ts b/src/app/rb-custom-inputs/rb-table/rb-table.component.ts index d9fc915..630e32d 100644 --- a/src/app/rb-custom-inputs/rb-table/rb-table.component.ts +++ b/src/app/rb-custom-inputs/rb-table/rb-table.component.ts @@ -1,19 +1,19 @@ import {Component, Input, OnInit} from '@angular/core'; @Component({ - // tslint:disable-next-line:component-selector - selector: 'rb-table', - templateUrl: './rb-table.component.html', - styleUrls: ['./rb-table.component.scss'] + // tslint:disable-next-line:component-selector + selector: 'rb-table', + templateUrl: './rb-table.component.html', + styleUrls: ['./rb-table.component.scss'] }) export class RbTableComponent implements OnInit { - @Input() scrollTop; - @Input() ellipsis; + @Input() scrollTop; + @Input() ellipsis; - constructor() { } + constructor() { } - ngOnInit(): void { - } + ngOnInit(): void { + } } diff --git a/src/app/sample/sample.component.html b/src/app/sample/sample.component.html index 59d43ee..9a2b4f0 100644 --- a/src/app/sample/sample.component.html +++ b/src/app/sample/sample.component.html @@ -4,335 +4,335 @@ - + -
-
-
- - Cannot be empty - Unknown material, add properties for new material - - - New material - -
+ +
+
+ + Cannot be empty + Unknown material, add properties for new material + + + New material + +
-
-

Material properties

- - {{supplierInput.errors.failure}} - - - {{groupInput.errors.failure}} - - - - The specified {{modalText.list}} could not be found in the list.
- Did you mean {{modalText.suggestion}}? -
-
- - - - - - - - {{parameterInput.errors.failure}} - Cannot be empty - -
+
+

Material properties

+ + {{supplierInput.errors.failure}} + + + {{groupInput.errors.failure}} + + + + The specified {{modalText.list}} could not be found in the list.
+ Did you mean {{modalText.suggestion}}? +
+
+ + + + + + + + {{parameterInput.errors.failure}} + Cannot be empty + +
-
- - - - Cannot be empty - - - {{colorInput.errors.failure}} - Cannot be empty - - - {{batchInput.errors.failure}} - -
-
+
+ + + + Cannot be empty + + + {{colorInput.errors.failure}} + Cannot be empty + + + {{batchInput.errors.failure}} + +
+
-
- - {{commentInput.errors.failure}} - -
Sample references
-
-
- - Unknown sample number - -
- - Cannot be empty - -
-
Additional properties
- - -
- - {{keyInput.errors.failure}} - -
- - Cannot be empty - -
-
-
+
+ + {{commentInput.errors.failure}} + +
Sample references
+
+
+ + Unknown sample number + +
+ + Cannot be empty + +
+
Additional properties
+ + +
+ + {{keyInput.errors.failure}} + +
+ + Cannot be empty + +
+
+
-
- - Save sample - - - Delete samples - -
- - - Cannot be empty - Must be at least 1 - - - -
+
+ + Save sample + + + Delete samples + +
+ + + Cannot be empty + Must be at least 1 + + + + - + -
-

Successfully added samples:

- - - - Sample Number{{baseSample.number}} - Material{{material.name}} - Type{{baseSample.type}} - Color{{baseSample.color}} - Batch{{baseSample.batch}} - Comment{{baseSample.notes | exists:'comment'}} - - Sample reference{{reference[0]}} - {{reference[1]}} - - {{field[0]}}{{field[1]}} - -
+
+

Successfully added samples:

+ + + + Sample Number{{baseSample.number}} + Material{{material.name}} + Type{{baseSample.type}} + Color{{baseSample.color}} + Batch{{baseSample.batch}} + Comment{{baseSample.notes | exists:'comment'}} + + Sample reference{{reference[0]}} - {{reference[1]}} + + {{field[0]}}{{field[1]}} + +
- + -
-
- - -
-
-
-
-

{{sample.number}}

-
-
- Condition - -
-
- - - - +
+ + + +
+
+
+
+

{{sample.number}}

+
+
+ Condition + +
+
+ + + + - - - - Cannot be empty - - - {{parameterInput.errors.failure}} - Cannot be empty - - -
-
+ + + + Cannot be empty + + + {{parameterInput.errors.failure}} + Cannot be empty + + +
+
-
-
- Measurements - - Restore measurements - -
-
- - - - -
- - - Cannot be empty - - - - Cannot be empty - - - {{parameterInput.errors.failure}} - Cannot be empty - - - - -
- - Delete measurement - -
-
- - New measurement - -
-
-
-
- - Summary - - - Delete sample - -
- -
+
+
+ Measurements + + Restore measurements + +
+
+ + + + +
+ + + Cannot be empty + + + + Cannot be empty + + + {{parameterInput.errors.failure}} + Cannot be empty + + + + +
+ + Delete measurement + +
+
+ + New measurement + +
+
+
+
+ + Summary + + + Delete sample + +
+ +
- + -
- - - - - {{sample.number}} - - {{parameter.name}}{{sample.condition[parameter.name]}} - - - - {{parameter.name}}{{measurement.values[parameter.name]}} - - - - - - Save sample{{samples.length > 1 ? 's' : ''}} - -
+
+ + + + + {{sample.number}} + + {{parameter.name}}{{sample.condition[parameter.name]}} + + + + {{parameter.name}}{{measurement.values[parameter.name]}} + + + + + + Save sample{{samples.length > 1 ? 's' : ''}} + +
- - Do you really want to delete {{(samples.length > 1 ? 'samples ' : 'sample ') + sampleNames()}}? - + + Do you really want to delete {{(samples.length > 1 ? 'samples ' : 'sample ') + sampleNames()}}? + diff --git a/src/app/sample/sample.component.scss b/src/app/sample/sample.component.scss index 5ec00c6..912d0e0 100644 --- a/src/app/sample/sample.component.scss +++ b/src/app/sample/sample.component.scss @@ -1,49 +1,49 @@ ::ng-deep rb-table#response-data > table { - width: auto !important; + width: auto !important; } td:first-child { - font-weight: bold; + font-weight: bold; } .condition-set { - float: right; + float: right; } .two-col { - display: grid; - grid-template-columns: 1fr 1fr; - grid-column-gap: 10px; + display: grid; + grid-template-columns: 1fr 1fr; + grid-column-gap: 10px; } .dpt-chart { - max-width: 400px; + max-width: 400px; } .sample-count { - max-width: 150px; - margin-right: 20px; - display: inline-block; + max-width: 150px; + margin-right: 20px; + display: inline-block; } .set-new-material { - display: block; + display: block; } .delete-sample { - float: right; + float: right; } .cm-form { - display: grid; - grid-template-columns: 1fr 1fr; - grid-column-gap: 1rem; + display: grid; + grid-template-columns: 1fr 1fr; + grid-column-gap: 1rem; - rb-tab-panel, div.buttons { - grid-column: span 2; - } + rb-tab-panel, div.buttons { + grid-column: span 2; + } } .restore-measurements { - float: right; + float: right; } diff --git a/src/app/sample/sample.component.ts b/src/app/sample/sample.component.ts index 9b6f027..b273f61 100644 --- a/src/app/sample/sample.component.ts +++ b/src/app/sample/sample.component.ts @@ -5,10 +5,10 @@ import pick from 'lodash/pick'; import isEqual from 'lodash/isEqual'; import strCompare from 'str-compare'; import { - AfterContentChecked, - Component, - OnInit, TemplateRef, - ViewChild + AfterContentChecked, + Component, + OnInit, TemplateRef, + ViewChild } from '@angular/core'; import {ActivatedRoute, Router} from '@angular/router'; import {AutocompleteService} from '../services/autocomplete.service'; @@ -26,608 +26,608 @@ import {DataService} from '../services/data.service'; import {LoginService} from '../services/login.service'; @Component({ - selector: 'app-sample', - templateUrl: './sample.component.html', - 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})) - ]) - ] - ) - ] + selector: 'app-sample', + templateUrl: './sample.component.html', + 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 { - @ViewChild('sampleForm') sampleForm: NgForm; - @ViewChild('cmForm') cmForm: NgForm; + @ViewChild('sampleForm') sampleForm: NgForm; + @ViewChild('cmForm') cmForm: NgForm; - baseSample = new SampleModel(); // Base sample which is saved - sampleCount = 1; // Number of samples to be generated - samples: SampleModel[] = []; // Gets filled with response data after saving the sample + baseSample = new SampleModel(); // Base sample which is saved + sampleCount = 1; // Number of samples to be generated + samples: SampleModel[] = []; // Gets filled with response data after saving the sample - sampleReferences: [string, string, string][] = [['', '', '']]; - sampleReferenceFinds: {_id: string, number: string}[] = []; // Raw sample reference data from db - currentSRIndex = 0; // Index of last entered sample reference - sampleReferenceAutocomplete: string[][] = [[]]; + sampleReferences: [string, string, string][] = [['', '', '']]; + sampleReferenceFinds: {_id: string, number: string}[] = []; // Raw sample reference data from db + currentSRIndex = 0; // Index of last entered sample reference + sampleReferenceAutocomplete: string[][] = [[]]; - customFields: [string, string][] = []; - availableCustomFields: string[] = []; + customFields: [string, string][] = []; + availableCustomFields: string[] = []; - newMaterial = false; // True if new material should be created - materials: MaterialModel[] = []; // All materials - materialNames = []; // Only names for autocomplete - material = new MaterialModel(); // Object of current selected material - defaultDevice = ''; // Default device for spectra + newMaterial = false; // True if new material should be created + materials: MaterialModel[] = []; // All materials + materialNames = []; // Only names for autocomplete + material = new MaterialModel(); // Object of current selected material + defaultDevice = ''; // Default device for spectra - // Component mode, either new for generating new samples, editOne or editMulti, editing one or multiple samples - mode = 'new'; - view = { // Active views - base: false, // Base sample - baseSum: false, // Base sample summary - cm: false, // Conditions and measurements - cmSum: false // Conditions and measurements summary - }; - loading = 0; // Number of currently loading instances - checkFormAfterInit = false; - modalText = {list: '', suggestion: ''}; - cmSampleIndex = '0'; - measurementDeleteList = []; // Buffer with measurements to delete, if the user confirms and saves the cm changes - // Deleted measurements if user is allowed and measurements are available - measurementRestoreData: MeasurementModel[] = []; + // Component mode, either new for generating new samples, editOne or editMulti, editing one or multiple samples + mode = 'new'; + view = { // Active views + base: false, // Base sample + baseSum: false, // Base sample summary + cm: false, // Conditions and measurements + cmSum: false // Conditions and measurements summary + }; + loading = 0; // Number of currently loading instances + checkFormAfterInit = false; + modalText = {list: '', suggestion: ''}; + cmSampleIndex = '0'; + measurementDeleteList = []; // Buffer with measurements to delete, if the user confirms and saves the cm changes + // Deleted measurements if user is allowed and measurements are available + measurementRestoreData: MeasurementModel[] = []; - charts = [[]]; // Chart data for spectra - 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}} - }; +charts = [[]]; // Chart data for spectra + 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, - private route: ActivatedRoute, - private api: ApiService, - private validation: ValidationService, - public autocomplete: AutocompleteService, - private modal: ModalService, - public d: DataService, - private login: LoginService - ) { } + constructor( + private router: Router, + private route: ActivatedRoute, + private api: ApiService, + private validation: ValidationService, + public autocomplete: AutocompleteService, + private modal: ModalService, + public d: DataService, + private login: LoginService + ) { } - ngOnInit(): void { - this.mode = this.router.url === '/samples/new' ? 'new' : ''; - this.loading = 7; - this.d.load('materials', () => { - this.materialNames = this.d.arr.materials.map(e => e.name); - this.loading--; - }); - this.d.load('materialSuppliers', () => { - this.loading--; - }); - this.d.load('materialGroups', () => { - this.loading--; - }); - this.d.load('conditionTemplates', () => { - this.loading--; - }); - this.d.load('measurementTemplates', () => { - this.d.load('user', () => { - this.defaultDevice = this.d.d.user.devices[0]; - // Spectrum device must be from user's devices list - this.d.arr.measurementTemplates.forEach(template => { - const device = template.parameters.find(e => e.name === 'device'); - if (device) { - device.range.values = this.d.d.user.devices; - } - }); - this.d.idReload('measurementTemplates'); - }); - this.loading--; - }); - this.d.load('materialTemplates', () => { - if (!this.material.properties.material_template) { - this.material.properties.material_template = - this.d.latest.materialTemplates.find(e => e.name === 'plastic')._id; - } - this.loading--; - }); - this.d.load('sampleNotesFields', () => { - this.availableCustomFields = this.d.arr.sampleNotesFields.map(e => e.name); - this.loading--; - }); - if (this.mode !== 'new') { - const sampleIds = this.route.snapshot.paramMap.get('id').split(','); - if (sampleIds.length === 1) { - this.mode = 'editOne'; - this.view.baseSum = true; - this.view.cm = true; - if (this.login.isLevel.dev) { // Load measurement restore data - this.api.get('/measurement/sample/' + sampleIds[0], (data, ignore) => { - if (data) { - this.measurementRestoreData = - data.filter(e => e.status === 'deleted').map(e => new MeasurementModel().deserialize(e)); - } - }); - } - } - else { - this.mode = 'editMulti'; - this.view.base = true; - } - this.loading += sampleIds.length; - this.api.get('/sample/' + sampleIds[0], sData => { // Special treatment for first id - this.samples = [new SampleModel().deserialize(sData)]; - this.baseSample.deserialize(sData); - this.material = new MaterialModel().deserialize(sData.material); // Read material - // Read custom fields - this.customFields = this.baseSample.notes.custom_fields && this.baseSample.notes.custom_fields !== {} ? - Object.keys(this.baseSample.notes.custom_fields).map(e => [e, this.baseSample.notes.custom_fields[e]]) : []; - if (this.baseSample.notes.sample_references.length) { // Read sample references - this.sampleReferences = []; - this.sampleReferenceAutocomplete = []; - let loadCounter = this.baseSample.notes.sample_references.length; // Count down instances still loading - this.baseSample.notes.sample_references.forEach(reference => { - this.api.get('/sample/' + reference.sample_id, srData => { // Get sample numbers for ids - this.sampleReferences.push([srData.number, reference.relation, reference.sample_id]); - this.sampleReferenceAutocomplete.push([srData.number]); - if (!--loadCounter) { // Insert empty template when all instances were loaded - this.sampleReferences.push(['', '', '']); - this.sampleReferenceAutocomplete.push([]); - } - }); - }); - } - if (this.mode === 'editOne') { - this.charts = [[]]; - let spectrumCounter = 0; // Generate charts for spectrum measurements - this.samples[0].measurements.forEach((measurement, i) => { - this.charts[0].push(cloneDeep(this.chartInit)); - if (measurement.values.dpt) { - setTimeout(() => { - this.generateChart(measurement.values.dpt, 0, i); - }, spectrumCounter * 20); // Generate charts one after another to avoid freezing the UI - spectrumCounter ++; - } - }); - } - this.checkFormAfterInit = true; - this.loading--; - sampleIds.slice(1).forEach(sampleId => { // Load further samples for batch edit - this.api.get('/sample/' + sampleId, data => { - this.samples.push(new SampleModel().deserialize(data)); - ['type', 'color', 'batch', 'notes'].forEach((key) => { - if (!isEqual(data[key], this.baseSample[key])) { - this.baseSample[key] = undefined; - } - }); - if (!isEqual(data.material.name, this.baseSample.material.name)) { - this.baseSample.material.name = undefined; - } - this.loading--; - this.checkFormAfterInit = true; - }); - }); - }); - } - else { - this.view.base = true; - } - } + ngOnInit(): void { + this.mode = this.router.url === '/samples/new' ? 'new' : ''; + this.loading = 7; + this.d.load('materials', () => { + this.materialNames = this.d.arr.materials.map(e => e.name); + this.loading--; + }); + this.d.load('materialSuppliers', () => { + this.loading--; + }); + this.d.load('materialGroups', () => { + this.loading--; + }); + this.d.load('conditionTemplates', () => { + this.loading--; + }); + this.d.load('measurementTemplates', () => { + this.d.load('user', () => { + this.defaultDevice = this.d.d.user.devices[0]; + // Spectrum device must be from user's devices list + this.d.arr.measurementTemplates.forEach(template => { + const device = template.parameters.find(e => e.name === 'device'); + if (device) { + device.range.values = this.d.d.user.devices; + } + }); + this.d.idReload('measurementTemplates'); + }); + this.loading--; + }); + this.d.load('materialTemplates', () => { + if (!this.material.properties.material_template) { + this.material.properties.material_template = + this.d.latest.materialTemplates.find(e => e.name === 'plastic')._id; + } + this.loading--; + }); + this.d.load('sampleNotesFields', () => { + this.availableCustomFields = this.d.arr.sampleNotesFields.map(e => e.name); + this.loading--; + }); + if (this.mode !== 'new') { + const sampleIds = this.route.snapshot.paramMap.get('id').split(','); + if (sampleIds.length === 1) { + this.mode = 'editOne'; + this.view.baseSum = true; + this.view.cm = true; + if (this.login.isLevel.dev) { // Load measurement restore data + this.api.get('/measurement/sample/' + sampleIds[0], (data, ignore) => { + if (data) { + this.measurementRestoreData = + data.filter(e => e.status === 'deleted').map(e => new MeasurementModel().deserialize(e)); + } + }); + } + } + else { + this.mode = 'editMulti'; + this.view.base = true; + } + this.loading += sampleIds.length; + this.api.get('/sample/' + sampleIds[0], sData => { // Special treatment for first id + this.samples = [new SampleModel().deserialize(sData)]; + this.baseSample.deserialize(sData); + this.material = new MaterialModel().deserialize(sData.material); // Read material + // Read custom fields + this.customFields = this.baseSample.notes.custom_fields && this.baseSample.notes.custom_fields !== {} ? + Object.keys(this.baseSample.notes.custom_fields).map(e => [e, this.baseSample.notes.custom_fields[e]]) : []; + if (this.baseSample.notes.sample_references.length) { // Read sample references + this.sampleReferences = []; + this.sampleReferenceAutocomplete = []; + let loadCounter = this.baseSample.notes.sample_references.length; // Count down instances still loading + this.baseSample.notes.sample_references.forEach(reference => { + this.api.get('/sample/' + reference.sample_id, srData => { // Get sample numbers for ids + this.sampleReferences.push([srData.number, reference.relation, reference.sample_id]); + this.sampleReferenceAutocomplete.push([srData.number]); + if (!--loadCounter) { // Insert empty template when all instances were loaded + this.sampleReferences.push(['', '', '']); + this.sampleReferenceAutocomplete.push([]); + } + }); + }); + } + if (this.mode === 'editOne') { + this.charts = [[]]; + let spectrumCounter = 0; // Generate charts for spectrum measurements + this.samples[0].measurements.forEach((measurement, i) => { + this.charts[0].push(cloneDeep(this.chartInit)); + if (measurement.values.dpt) { + setTimeout(() => { + this.generateChart(measurement.values.dpt, 0, i); + }, spectrumCounter * 20); // Generate charts one after another to avoid freezing the UI + spectrumCounter ++; + } + }); + } + this.checkFormAfterInit = true; + this.loading--; + sampleIds.slice(1).forEach(sampleId => { // Load further samples for batch edit + this.api.get('/sample/' + sampleId, data => { + this.samples.push(new SampleModel().deserialize(data)); + ['type', 'color', 'batch', 'notes'].forEach((key) => { + if (!isEqual(data[key], this.baseSample[key])) { + this.baseSample[key] = undefined; + } + }); + if (!isEqual(data.material.name, this.baseSample.material.name)) { + this.baseSample.material.name = undefined; + } + this.loading--; + this.checkFormAfterInit = true; + }); + }); + }); + } + else { + this.view.base = true; + } + } - ngAfterContentChecked() { - // Attach validators - if (this.samples.length) { // Conditions are displayed - this.samples.forEach((gSample, gIndex) => { - if (this.d.id.conditionTemplates[gSample.condition.condition_template]) { - this.d.id.conditionTemplates[gSample.condition.condition_template].parameters.forEach((parameter, pIndex) => { - this.attachValidator(this.cmForm, `conditionParameter-${gIndex}-${pIndex}`, parameter.range); - }); - } - gSample.measurements.forEach((measurement, mIndex) => { - this.d.id.measurementTemplates[measurement.measurement_template].parameters.forEach((parameter, pIndex) => { - this.attachValidator(this.cmForm, `measurementParameter-${gIndex}-${mIndex}-${pIndex}`, parameter.range); - }); - }); - }); - } + ngAfterContentChecked() { + // Attach validators + if (this.samples.length) { // Conditions are displayed + this.samples.forEach((gSample, gIndex) => { + if (this.d.id.conditionTemplates[gSample.condition.condition_template]) { + this.d.id.conditionTemplates[gSample.condition.condition_template].parameters.forEach((parameter, pIndex) => { + this.attachValidator(this.cmForm, `conditionParameter-${gIndex}-${pIndex}`, parameter.range); + }); + } + gSample.measurements.forEach((measurement, mIndex) => { + this.d.id.measurementTemplates[measurement.measurement_template].parameters.forEach((parameter, pIndex) => { + this.attachValidator(this.cmForm, `measurementParameter-${gIndex}-${mIndex}-${pIndex}`, parameter.range); + }); + }); + }); + } - if (this.sampleForm && this.material.properties.material_template) { // Material template is set - this.d.id.materialTemplates[this.material.properties.material_template].parameters.forEach((parameter, i) => { - this.attachValidator(this.sampleForm, 'materialParameter' + i, parameter.range); - }); - } + if (this.sampleForm && this.material.properties.material_template) { // Material template is set + this.d.id.materialTemplates[this.material.properties.material_template].parameters.forEach((parameter, i) => { + this.attachValidator(this.sampleForm, 'materialParameter' + i, parameter.range); + }); + } - // Revalidate inputs - if (this.checkFormAfterInit) { - if (this.view.base) { // Validate sampleForm - if (this.sampleForm !== undefined && this.sampleForm.form.get('cf-key0')) { - this.checkFormAfterInit = false; - Object.keys(this.sampleForm.form.controls).forEach(field => { - this.sampleForm.form.get(field).updateValueAndValidity(); - }); - } - } - else { // Validate cmForm - // Check that all fields are ready for validation - let formReady: boolean = this.cmForm !== undefined; // Forms exist - if (this.samples[0].condition.condition_template) { // If condition is set, last condition field exists - formReady = formReady && this.cmForm.form.get('conditionParameter-0-' + - (this.d.id.conditionTemplates[this.samples[0].condition.condition_template].parameters.length - 1)) as any; - } - if (this.samples[0].measurements.length) { // If there are measurements, last measurement field exists - formReady = formReady && - this.cmForm.form.get('measurementParameter-0-' + (this.samples[0].measurements.length - 1) + - '-' + (this.d.id.measurementTemplates[this.samples[0].measurements[this.samples[0].measurements.length - 1] - .measurement_template].parameters.length - 1)) as any; - } - if (formReady) { // Fields are ready, do validation - this.checkFormAfterInit = false; - Object.keys(this.cmForm.form.controls).forEach(field => { - this.cmForm.form.get(field).updateValueAndValidity(); - }); - } - } - } - } + // Revalidate inputs + if (this.checkFormAfterInit) { + if (this.view.base) { // Validate sampleForm + if (this.sampleForm !== undefined && this.sampleForm.form.get('cf-key0')) { + this.checkFormAfterInit = false; + Object.keys(this.sampleForm.form.controls).forEach(field => { + this.sampleForm.form.get(field).updateValueAndValidity(); + }); + } + } + else { // Validate cmForm + // Check that all fields are ready for validation + let formReady: boolean = this.cmForm !== undefined; // Forms exist + if (this.samples[0].condition.condition_template) { // If condition is set, last condition field exists + formReady = formReady && this.cmForm.form.get('conditionParameter-0-' + + (this.d.id.conditionTemplates[this.samples[0].condition.condition_template].parameters.length - 1)) as any; + } + if (this.samples[0].measurements.length) { // If there are measurements, last measurement field exists + formReady = formReady && + this.cmForm.form.get('measurementParameter-0-' + (this.samples[0].measurements.length - 1) + + '-' + (this.d.id.measurementTemplates[this.samples[0].measurements[this.samples[0].measurements.length - 1] + .measurement_template].parameters.length - 1)) as any; + } + if (formReady) { // Fields are ready, do validation + this.checkFormAfterInit = false; + Object.keys(this.cmForm.form.controls).forEach(field => { + this.cmForm.form.get(field).updateValueAndValidity(); + }); + } + } + } + } - // Attach validators specified in range to input with name - attachValidator(form, name: string, range: {[prop: string]: any}) { - if (form && form.form.get(name)) { - const validators = []; - if (range.hasOwnProperty('required')) { - validators.push(Validators.required); - } - if (range.hasOwnProperty('values')) { - validators.push(this.validation.generate('stringOf', [range.values])); - } - else if (range.hasOwnProperty('min') && range.hasOwnProperty('max')) { - validators.push(this.validation.generate('minMax', [range.min, range.max])); - } - else if (range.hasOwnProperty('min')) { - validators.push(this.validation.generate('min', [range.min])); - } - else if (range.hasOwnProperty('max')) { - validators.push(this.validation.generate('max', [range.max])); - } - form.form.get(name).setValidators(validators); - } - } + // Attach validators specified in range to input with name + attachValidator(form, name: string, range: {[prop: string]: any}) { + if (form && form.form.get(name)) { + const validators = []; + if (range.hasOwnProperty('required')) { + validators.push(Validators.required); + } + if (range.hasOwnProperty('values')) { + validators.push(this.validation.generate('stringOf', [range.values])); + } + else if (range.hasOwnProperty('min') && range.hasOwnProperty('max')) { + validators.push(this.validation.generate('minMax', [range.min, range.max])); + } + else if (range.hasOwnProperty('min')) { + validators.push(this.validation.generate('min', [range.min])); + } + else if (range.hasOwnProperty('max')) { + validators.push(this.validation.generate('max', [range.max])); + } + form.form.get(name).setValidators(validators); + } + } - reValidate() { - setTimeout(() => this.checkFormAfterInit = true, 0); - } + reValidate() { + setTimeout(() => this.checkFormAfterInit = true, 0); + } - // Save base sample - saveSample() { - if (this.samples.length === 0) { - this.loading = this.sampleCount; // Set up loading spinner - } - new Promise(resolve => { - if (this.newMaterial) { // Save material first if new one exists - this.material.numbers = this.material.numbers.filter(e => e !== ''); - this.api.post('/material/new', this.material.sendFormat(), data => { - this.d.arr.materials.push(data); // Add material to data - this.material = data; - this.baseSample.material_id = data._id; // Add new material id to sample data - resolve(); - }); - } - else { - resolve(); - } - }).then(() => { // Save sample - if (this.baseSample.notes) { - this.baseSample.notes.custom_fields = {}; - this.customFields.forEach(element => { - if (element[0] !== '') { - this.baseSample.notes.custom_fields[element[0]] = element[1]; - } - }); - this.baseSample.notes.sample_references = this.sampleReferences - .filter(e => e[0] && e[1] && e[2]) // Filter empty values - .map(e => ({sample_id: e[2], relation: e[1]})); - } - if (this.samples.length === 0) { // Only save new sample for the first time in mode new, otherwise save changes - for (let i = 0; i < this.sampleCount; i ++) { - this.api.post('/sample/new', this.baseSample.sendFormat(), data => { - this.samples[i] = new SampleModel().deserialize(data); - this.samples[i].material = this.d.arr.materials.find(e => e._id === this.samples[i].material_id); - this.loading --; - }); - } - this.view.base = false; - this.view.baseSum = true; - this.view.cm = true; - } - else { - this.samples.forEach((sample, i) => { - this.api.put('/sample/' + sample._id, this.baseSample.sendFormat(false), data => { - merge(this.samples[i], omit(data, ['condition'])); - this.samples[i].material = this.d.arr.materials.find(e => e._id === this.samples[0].material_id); - this.view.base = false; - this.view.baseSum = true; - }); - }); - } - }); - } + // Save base sample + saveSample() { + if (this.samples.length === 0) { + this.loading = this.sampleCount; // Set up loading spinner + } + new Promise(resolve => { + if (this.newMaterial) { // Save material first if new one exists + this.material.numbers = this.material.numbers.filter(e => e !== ''); + this.api.post('/material/new', this.material.sendFormat(), data => { + this.d.arr.materials.push(data); // Add material to data + this.material = data; + this.baseSample.material_id = data._id; // Add new material id to sample data + resolve(); + }); + } + else { + resolve(); + } + }).then(() => { // Save sample + if (this.baseSample.notes) { + this.baseSample.notes.custom_fields = {}; + this.customFields.forEach(element => { + if (element[0] !== '') { + this.baseSample.notes.custom_fields[element[0]] = element[1]; + } + }); + this.baseSample.notes.sample_references = this.sampleReferences + .filter(e => e[0] && e[1] && e[2]) // Filter empty values + .map(e => ({sample_id: e[2], relation: e[1]})); + } + if (this.samples.length === 0) { // Only save new sample for the first time in mode new, otherwise save changes + for (let i = 0; i < this.sampleCount; i ++) { + this.api.post('/sample/new', this.baseSample.sendFormat(), data => { + this.samples[i] = new SampleModel().deserialize(data); + this.samples[i].material = this.d.arr.materials.find(e => e._id === this.samples[i].material_id); + this.loading --; + }); + } + this.view.base = false; + this.view.baseSum = true; + this.view.cm = true; + } + else { + this.samples.forEach((sample, i) => { + this.api.put('/sample/' + sample._id, this.baseSample.sendFormat(false), data => { + merge(this.samples[i], omit(data, ['condition'])); + this.samples[i].material = this.d.arr.materials.find(e => e._id === this.samples[0].material_id); + this.view.base = false; + this.view.baseSum = true; + }); + }); + } + }); + } - // Save conditions and measurements - cmSave() { // Save measurements and conditions - this.samples.forEach(sample => { - if (sample.condition.condition_template) { // Condition was set - this.api.put('/sample/' + sample._id, - {condition: pick(sample.condition, - [ - 'condition_template', - ...this.d.id.conditionTemplates[sample.condition.condition_template].parameters.map(e => e.name) - ] - )} - ); - } - sample.measurements.forEach(measurement => { // Save measurements - if (Object.keys(measurement.values).map(e => measurement.values[e]).join('') !== '') { - Object.keys(measurement.values).forEach(key => { // Map empty values to null - measurement.values[key] = measurement.values[key] === '' ? null : measurement.values[key]; - }); - if (measurement._id === null) { // New measurement - measurement.sample_id = sample._id; - this.api.post('/measurement/new', measurement.sendFormat()); - } - else { // Update measurement - this.api.put('/measurement/' + measurement._id, - measurement.sendFormat(['sample_id', 'measurement_template'])); - } - } - else if (measurement._id !== null) { // Existing measurement was left empty to delete - this.api.delete('/measurement/' + measurement._id); - } - }); - this.measurementDeleteList.forEach(measurement => { - this.api.delete('/measurement/' + measurement); - }); - }); + // Save conditions and measurements + cmSave() { // Save measurements and conditions + this.samples.forEach(sample => { + if (sample.condition.condition_template) { // Condition was set + this.api.put('/sample/' + sample._id, + {condition: pick(sample.condition, + [ + 'condition_template', + ...this.d.id.conditionTemplates[sample.condition.condition_template].parameters.map(e => e.name) + ] + )} + ); + } + sample.measurements.forEach(measurement => { // Save measurements + if (Object.keys(measurement.values).map(e => measurement.values[e]).join('') !== '') { + Object.keys(measurement.values).forEach(key => { // Map empty values to null + measurement.values[key] = measurement.values[key] === '' ? null : measurement.values[key]; + }); + if (measurement._id === null) { // New measurement + measurement.sample_id = sample._id; + this.api.post('/measurement/new', measurement.sendFormat()); + } + else { // Update measurement + this.api.put('/measurement/' + measurement._id, + measurement.sendFormat(['sample_id', 'measurement_template'])); + } + } + else if (measurement._id !== null) { // Existing measurement was left empty to delete + this.api.delete('/measurement/' + measurement._id); + } + }); + this.measurementDeleteList.forEach(measurement => { + this.api.delete('/measurement/' + measurement); + }); + }); - this.router.navigate(['/samples']); - } + this.router.navigate(['/samples']); + } - restoreMeasurements() { - let spectrumCounter = 0; // Generate charts for spectrum measurements - const measurementCount = this.samples[0].measurements.length; - this.measurementRestoreData.forEach((measurement, i) => { - this.api.put('/measurement/restore/' + measurement._id, {}, () => { - this.samples[0].measurements.push(measurement); - this.charts[0].push(cloneDeep(this.chartInit)); - if (measurement.values.dpt) { - setTimeout(() => { - this.generateChart(measurement.values.dpt, 0, measurementCount + i); - }, spectrumCounter * 20); // Generate charts one after another to avoid freezing the UI - spectrumCounter ++; - } - this.checkFormAfterInit = true; - }); - }); - } + restoreMeasurements() { + let spectrumCounter = 0; // Generate charts for spectrum measurements + const measurementCount = this.samples[0].measurements.length; + this.measurementRestoreData.forEach((measurement, i) => { + this.api.put('/measurement/restore/' + measurement._id, {}, () => { + this.samples[0].measurements.push(measurement); + this.charts[0].push(cloneDeep(this.chartInit)); + if (measurement.values.dpt) { + setTimeout(() => { + this.generateChart(measurement.values.dpt, 0, measurementCount + i); + }, spectrumCounter * 20); // Generate charts one after another to avoid freezing the UI + spectrumCounter ++; + } + this.checkFormAfterInit = true; + }); + }); + } - // Set material based on found material name - findMaterial(name) { - const res = this.d.arr.materials.find(e => e.name === name); // Search for match - if (res) { // Material found - this.material = cloneDeep(res); - this.baseSample.material_id = this.material._id; - } - else { // No matching material found - if (this.baseSample.material_id !== null) { // Reset previous match - this.material = new MaterialModel(); - this.material.properties.material_template = - this.d.latest.materialTemplates.find(e => e.name === 'plastic')._id; - } - this.baseSample.material_id = null; - } - this.setNewMaterial(); - } + // Set material based on found material name + findMaterial(name) { + const res = this.d.arr.materials.find(e => e.name === name); // Search for match + if (res) { // Material found + this.material = cloneDeep(res); + this.baseSample.material_id = this.material._id; + } + else { // No matching material found + if (this.baseSample.material_id !== null) { // Reset previous match + this.material = new MaterialModel(); + this.material.properties.material_template = + this.d.latest.materialTemplates.find(e => e.name === 'plastic')._id; + } + this.baseSample.material_id = null; + } + this.setNewMaterial(); + } - // Set newMaterial, if value === null -> toggle - setNewMaterial(value = null) { - if (value === null) { // Toggle dialog - this.newMaterial = !this.baseSample.material_id; - } // Set to false only if material already exists - else if (value || (!value && this.baseSample.material_id !== null )) { - this.newMaterial = value; - } - if (this.newMaterial) { // Set validators if dialog is open - this.sampleForm.form.get('materialname').setValidators([Validators.required]); - } - else { // Material name must be from list if dialog is closed - this.sampleForm.form.get('materialname') - .setValidators([Validators.required, this.validation.generate('stringOf', [this.materialNames])]); - } - this.sampleForm.form.get('materialname').updateValueAndValidity(); - } + // Set newMaterial, if value === null -> toggle + setNewMaterial(value = null) { + if (value === null) { // Toggle dialog + this.newMaterial = !this.baseSample.material_id; + } // Set to false only if material already exists + else if (value || (!value && this.baseSample.material_id !== null )) { + this.newMaterial = value; + } + if (this.newMaterial) { // Set validators if dialog is open + this.sampleForm.form.get('materialname').setValidators([Validators.required]); + } + else { // Material name must be from list if dialog is closed + this.sampleForm.form.get('materialname') + .setValidators([Validators.required, this.validation.generate('stringOf', [this.materialNames])]); + } + this.sampleForm.form.get('materialname').updateValueAndValidity(); + } - // Add a new measurement for generated sample at index - addMeasurement(gIndex) { - this.samples[gIndex].measurements.push( - new MeasurementModel(this.d.latest.measurementTemplates.find(e => e.name === 'spectrum')._id) - ); - if (!this.charts[gIndex]) { // Add array if there are no charts yet - this.charts[gIndex] = []; - } - this.charts[gIndex].push(cloneDeep(this.chartInit)); - } + // Add a new measurement for generated sample at index + addMeasurement(gIndex) { + this.samples[gIndex].measurements.push( + new MeasurementModel(this.d.latest.measurementTemplates.find(e => e.name === 'spectrum')._id) + ); + if (!this.charts[gIndex]) { // Add array if there are no charts yet + this.charts[gIndex] = []; + } + this.charts[gIndex].push(cloneDeep(this.chartInit)); + } - // Remove the measurement at the specified index - removeMeasurement(gIndex, mIndex) { - if (this.samples[gIndex].measurements[mIndex]._id !== null) { - this.measurementDeleteList.push(this.samples[gIndex].measurements[mIndex]._id); - } - this.samples[gIndex].measurements.splice(mIndex, 1); - this.charts[gIndex].splice(mIndex, 1); - } + // Remove the measurement at the specified index + removeMeasurement(gIndex, mIndex) { + if (this.samples[gIndex].measurements[mIndex]._id !== null) { + this.measurementDeleteList.push(this.samples[gIndex].measurements[mIndex]._id); + } + this.samples[gIndex].measurements.splice(mIndex, 1); + this.charts[gIndex].splice(mIndex, 1); + } - // Clear entered measurement data at the specified index due to template change - clearMeasurement(gIndex, mIndex) { - this.charts[gIndex][mIndex][0].data = []; - this.samples[gIndex].measurements[mIndex].values = {}; - } + // Clear entered measurement data at the specified index due to template change + clearMeasurement(gIndex, mIndex) { + this.charts[gIndex][mIndex][0].data = []; + this.samples[gIndex].measurements[mIndex].values = {}; + } - fileToArray(files, gIndex, mIndex, parameter) { // Process spectrum file input - for (const i in files) { - if (files.hasOwnProperty(i)) { - const fileReader = new FileReader(); - fileReader.onload = () => { - let index: number = mIndex; - if (Number(i) > 0) { // Append further spectra - this.addMeasurement(gIndex); - index = this.samples[gIndex].measurements.length - 1; - } - // Autofill further parameters - this.samples[gIndex].measurements[index].values.device = - this.samples[gIndex].measurements[mIndex].values.device; - this.samples[gIndex].measurements[index].values.filename = files[i].name; - this.samples[gIndex].measurements[index].values[parameter] = - fileReader.result.toString().split('\r\n').map(e => e.split(',')).filter(el => el.length === 2); - this.generateChart(this.samples[gIndex].measurements[index].values[parameter], gIndex, index); - }; - fileReader.readAsText(files[i]); - } - } - } + fileToArray(files, gIndex, mIndex, parameter) { // Process spectrum file input + for (const i in files) { + if (files.hasOwnProperty(i)) { + const fileReader = new FileReader(); + fileReader.onload = () => { + let index: number = mIndex; + if (Number(i) > 0) { // Append further spectra + this.addMeasurement(gIndex); + index = this.samples[gIndex].measurements.length - 1; + } + // Autofill further parameters + this.samples[gIndex].measurements[index].values.device = + this.samples[gIndex].measurements[mIndex].values.device; + this.samples[gIndex].measurements[index].values.filename = files[i].name; + this.samples[gIndex].measurements[index].values[parameter] = + fileReader.result.toString().split('\r\n').map(e => e.split(',')).filter(el => el.length === 2); + this.generateChart(this.samples[gIndex].measurements[index].values[parameter], gIndex, index); + }; + fileReader.readAsText(files[i]); + } + } + } - generateChart(spectrum, gIndex, mIndex) { - this.charts[gIndex][mIndex][0].data = spectrum.map(e => ({x: parseFloat(e[0]), y: parseFloat(e[1])})); - } + generateChart(spectrum, gIndex, mIndex) { + this.charts[gIndex][mIndex][0].data = spectrum.map(e => ({x: parseFloat(e[0]), y: parseFloat(e[1])})); + } - toggleCondition(sample) { - if (sample.condition.condition_template) { - sample.condition.condition_template = null; - } - else { - sample.condition.condition_template = this.d.latest.conditionTemplates[0]._id; - } - } + toggleCondition(sample) { + if (sample.condition.condition_template) { + sample.condition.condition_template = null; + } + else { + sample.condition.condition_template = this.d.latest.conditionTemplates[0]._id; + } + } - checkTypo(event, list, mKey, modal: TemplateRef) { - // User did not click on suggestion and entry is not in list - if (!(event.relatedTarget && (event.relatedTarget.className.indexOf('rb-dropdown-item') >= 0 || - event.relatedTarget.className.indexOf('close-btn rb-btn rb-passive-link') >= 0)) && - this.d.arr[list].indexOf(this.material[mKey]) < 0) { - this.modalText.list = mKey; - this.modalText.suggestion = this.d.arr[list] // Find possible entry from list - .map(e => ({v: e, s: strCompare.sorensenDice(e, this.material[mKey])})) - .sort((a, b) => b.s - a.s)[0].v; - this.modal.open(modal).then(result => { - if (result) { // Use suggestion - this.material[mKey] = this.modalText.suggestion; - } - }); - } - } + checkTypo(event, list, mKey, modal: TemplateRef) { + // User did not click on suggestion and entry is not in list + if (!(event.relatedTarget && (event.relatedTarget.className.indexOf('rb-dropdown-item') >= 0 || + event.relatedTarget.className.indexOf('close-btn rb-btn rb-passive-link') >= 0)) && + this.d.arr[list].indexOf(this.material[mKey]) < 0) { + this.modalText.list = mKey; + this.modalText.suggestion = this.d.arr[list] // Find possible entry from list + .map(e => ({v: e, s: strCompare.sorensenDice(e, this.material[mKey])})) + .sort((a, b) => b.s - a.s)[0].v; + this.modal.open(modal).then(result => { + if (result) { // Use suggestion + this.material[mKey] = this.modalText.suggestion; + } + }); + } + } - deleteConfirm(modal) { - this.modal.open(modal).then(result => { - if (result) { - this.samples.forEach(sample => { - this.api.delete('/sample/' + sample._id); - }); - this.router.navigate(['/samples']); - } - }); - } + deleteConfirm(modal) { + this.modal.open(modal).then(result => { + if (result) { + this.samples.forEach(sample => { + this.api.delete('/sample/' + sample._id); + }); + this.router.navigate(['/samples']); + } + }); + } - checkSampleReference(value, index) { - if (value) { - this.sampleReferences[index][0] = value; - } - this.currentSRIndex = index; - const fieldNo = this.sampleReferences.length; - let filledFields = 0; - this.sampleReferences.forEach(field => { - if (field[0] !== '') { - filledFields ++; - } - }); - // Append new field - if (filledFields === fieldNo) { - this.sampleReferences.push(['', '', '']); - this.sampleReferenceAutocomplete.push([]); - } - // Remove if two end fields are empty - if (fieldNo > 1 && this.sampleReferences[fieldNo - 1][0] === '' && this.sampleReferences[fieldNo - 2][0] === '') { - this.sampleReferences.pop(); - this.sampleReferenceAutocomplete.pop(); - } - this.sampleReferenceIdFind(value); - } + checkSampleReference(value, index) { + if (value) { + this.sampleReferences[index][0] = value; + } + this.currentSRIndex = index; + const fieldNo = this.sampleReferences.length; + let filledFields = 0; + this.sampleReferences.forEach(field => { + if (field[0] !== '') { + filledFields ++; + } + }); + // Append new field + if (filledFields === fieldNo) { + this.sampleReferences.push(['', '', '']); + this.sampleReferenceAutocomplete.push([]); + } + // Remove if two end fields are empty + if (fieldNo > 1 && this.sampleReferences[fieldNo - 1][0] === '' && this.sampleReferences[fieldNo - 2][0] === '') { + this.sampleReferences.pop(); + this.sampleReferenceAutocomplete.pop(); + } + this.sampleReferenceIdFind(value); + } - sampleReferenceList(value) { // Get list of sample reference number suggestions - return new Observable(observer => { - if (value !== '') { - this.api.get<{ _id: string, number: string }[]>( - '/samples?status[]=validated&status[]=new&page-size=25&sort=number-asc&fields[]=number&fields[]=_id&' + - 'filters[]=%7B%22mode%22%3A%22stringin%22%2C%22field%22%3A%22number%22%2C%22values%22%3A%5B%22' + value + - '%22%5D%7D', data => { - this.sampleReferenceAutocomplete[this.currentSRIndex] = data.map(e => e.number); - this.sampleReferenceFinds = data; - observer.next(data.map(e => e.number)); - observer.complete(); - this.sampleReferenceIdFind(value); - }); - } - else { - observer.next([]); - observer.complete(); - } - }); - } + sampleReferenceList(value) { // Get list of sample reference number suggestions + return new Observable(observer => { + if (value !== '') { + this.api.get<{ _id: string, number: string }[]>( + '/samples?status[]=validated&status[]=new&page-size=25&sort=number-asc&fields[]=number&fields[]=_id&' + + 'filters[]=%7B%22mode%22%3A%22stringin%22%2C%22field%22%3A%22number%22%2C%22values%22%3A%5B%22' + value + + '%22%5D%7D', data => { + this.sampleReferenceAutocomplete[this.currentSRIndex] = data.map(e => e.number); + this.sampleReferenceFinds = data; + observer.next(data.map(e => e.number)); + observer.complete(); + this.sampleReferenceIdFind(value); + }); + } + else { + observer.next([]); + observer.complete(); + } + }); + } - sampleReferenceIdFind(value) { // Sample reference id from number - const idFind = this.sampleReferenceFinds.find(e => e.number === value); - if (idFind) { - this.sampleReferences[this.currentSRIndex][2] = idFind._id; - } - else { - this.sampleReferences[this.currentSRIndex][2] = ''; - } - } + sampleReferenceIdFind(value) { // Sample reference id from number + const idFind = this.sampleReferenceFinds.find(e => e.number === value); + if (idFind) { + this.sampleReferences[this.currentSRIndex][2] = idFind._id; + } + else { + this.sampleReferences[this.currentSRIndex][2] = ''; + } + } - sampleReferenceListBind() { - return this.sampleReferenceList.bind(this); - } + sampleReferenceListBind() { + return this.sampleReferenceList.bind(this); + } - sampleNames() { - return this.samples.map(e => e.number).join(', '); - } + sampleNames() { + return this.samples.map(e => e.number).join(', '); + } - uniqueCfValues(index) { // Returns all names until index for unique check - return this.customFields ? this.customFields.slice(0, index).map(e => e[0]) : []; - } + uniqueCfValues(index) { // Returns all names until index for unique check + return this.customFields ? this.customFields.slice(0, index).map(e => e[0]) : []; + } - preventDefault(event) { - if (event.key && event.key === 'Enter' || event.type === 'dragover') { - event.preventDefault(); - } - } + preventDefault(event) { + if (event.key && event.key === 'Enter' || event.type === 'dragover') { + event.preventDefault(); + } + } } diff --git a/src/app/samples/samples.component.html b/src/app/samples/samples.component.html index f1cfeb4..59bce40 100644 --- a/src/app/samples/samples.component.html +++ b/src/app/samples/samples.component.html @@ -1,292 +1,292 @@
- - New sample - - - - {{sampleSelect === 2 ? 'Validate' : 'Validation'}} - + + New sample + + + + {{sampleSelect === 2 ? 'Validate' : 'Validation'}} +
-   Filter - -
-
- - - validated - - - new - - - deleted - -
- - - - - - - - - +   Filter + + +
+ + + validated + + + new + + + deleted + +
+ + + + + + + + + -
- - {{item.label}} - -
+
+ + {{item.label}} + +
- - Reset to default - + + Reset to default + -
-
- - - - - - - - - - - - - - -
- - - - - - - - - - - - -
-
-
- - has no condition - - - has no measurements - -
- +
+
+ + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+
+
+ + has no condition + + + has no measurements + +
+ - - Apply filters - -
+ + Apply filters + +
- -
Loading...
+ +
Loading...
- - JSON download link - - - - - - - Download result as CSV - - + + JSON download link + + + + + + + Download result as CSV + +
- - - - - - - all - - -
- {{key.label}} -
- - - - - - -
-
- - + + + + + + + all + + +
+ {{key.label}} +
+ + + + + + +
+
+ + - - - - - - - - - - - - - {{data[i].data[j]}} - - + + + + + + + + + + + + + {{data[i].data[j]}} + +
-
- - - - - of {{pages}} ({{totalSamples}} samples) - - -
+
+ + + + + of {{pages}} ({{totalSamples}} samples) + + +
- - -

{{sampleDetailsSample.number}}

- - - Material - {{sampleDetailsSample.material.name}} - - - Supplier - {{sampleDetailsSample.material.supplier}} - - - Group - {{sampleDetailsSample.material.group}} - - - Type - {{sampleDetailsSample.type}} - - - color - {{sampleDetailsSample.color}} - - - Batch - {{sampleDetailsSample.batch}} - - - Comment - {{sampleDetailsSample.notes.comment | exists}} - - - {{customField[0]}} - {{customField[1]}} - - - {{reference.relation}} - - - - - - {{conditionField[0]}} - {{conditionField[1]}} - - - {{measurement.name}} - {{measurement.value}} - - - User - {{sampleDetailsSample.user}} - - - Status - {{sampleDetailsSample.status}} - - -
+ + +

{{sampleDetailsSample.number}}

+ + + Material + {{sampleDetailsSample.material.name}} + + + Supplier + {{sampleDetailsSample.material.supplier}} + + + Group + {{sampleDetailsSample.material.group}} + + + Type + {{sampleDetailsSample.type}} + + + color + {{sampleDetailsSample.color}} + + + Batch + {{sampleDetailsSample.batch}} + + + Comment + {{sampleDetailsSample.notes.comment | exists}} + + + {{customField[0]}} + {{customField[1]}} + + + {{reference.relation}} + + + + + + {{conditionField[0]}} + {{conditionField[1]}} + + + {{measurement.name}} + {{measurement.value}} + + + User + {{sampleDetailsSample.user}} + + + Status + {{sampleDetailsSample.status}} + + +
- - Do you really want to restore this sample? - - \ No newline at end of file + + Do you really want to restore this sample? + + diff --git a/src/app/samples/samples.component.scss b/src/app/samples/samples.component.scss index a710bcf..6e5121d 100644 --- a/src/app/samples/samples.component.scss +++ b/src/app/samples/samples.component.scss @@ -1,249 +1,249 @@ @import "~@inst-iot/bosch-angular-ui-components/styles/variables/colors"; .header-addnew { - margin-bottom: 40px; - height: 42px; + margin-bottom: 40px; + height: 42px; - & > * { - display: inline; - margin-bottom: 10px; - } + & > * { + display: inline; + margin-bottom: 10px; + } - rb-icon-button { - float: right; - } + rb-icon-button { + float: right; + } } rb-table { - width: 100%; + width: 100%; } - td .clickable.rb-ic { - font-size: 1.1rem; - color: $color-gray-silver-sand; - cursor: pointer; +td .clickable.rb-ic { + font-size: 1.1rem; + color: $color-gray-silver-sand; + cursor: pointer; - &:hover { - color: #000; - } + &:hover { + color: #000; + } } rb-form-multi-select{ - min-width: 9rem; + min-width: 9rem; } .status-selection { - overflow: hidden; - margin-bottom: 10px; - float: left; - margin-right: 15px; + overflow: hidden; + margin-bottom: 10px; + float: left; + margin-right: 15px; - label { - display: block; - font-weight: 700; - font-size: 10px; - } + label { + display: block; + font-weight: 700; + font-size: 10px; + } - rb-form-checkbox { - float: left; - margin-right: 10px; - margin-top: -10px; - } + rb-form-checkbox { + float: left; + margin-right: 10px; + margin-top: -10px; + } } .selection { - max-width: 230px; - float: left; + max-width: 230px; + float: left; } .paging { - height: 50px; - float: left; + height: 50px; + float: left; - rb-form-input { - max-width: 65px; - } + rb-form-input { + max-width: 65px; + } - > * { - float: left; - } + > * { + float: left; + } - > button { - margin-top: 18px; - } + > button { + margin-top: 18px; + } - > span { - margin-top: 20px; - margin-left: 5px; - } + > span { + margin-top: 20px; + margin-left: 5px; + } } .sort-header { - width: 100%; - position: relative; + width: 100%; + position: relative; - & > span:first-child { - max-width: 180px; - overflow: hidden; - display: block; - text-overflow: ellipsis; - margin-right: 20px; - } + & > span:first-child { + max-width: 180px; + overflow: hidden; + display: block; + text-overflow: ellipsis; + margin-right: 20px; + } - div { - display: grid; - grid-template-columns: 1fr; - position: absolute; - right: 0; - top: 0; - background: #FFF; + div { + display: grid; + grid-template-columns: 1fr; + position: absolute; + right: 0; + top: 0; + background: #FFF; - :nth-child(1) { - margin-bottom: -3px; - cursor: pointer; - } + :nth-child(1) { + margin-bottom: -3px; + cursor: pointer; + } - :nth-child(2) { - margin-top: -3px; - cursor: pointer; - } - } + :nth-child(2) { + margin-top: -3px; + cursor: pointer; + } + } } .filters:after { - content:""; - clear:both; - display:block; + content:""; + clear:both; + display:block; } .download { - margin-top: 5px; - float: right; + margin-top: 5px; + float: right; - & > rb-form-checkbox { - display: inline-block; - } + & > rb-form-checkbox { + display: inline-block; + } - button { - margin-right: 10px; - } + button { + margin-right: 10px; + } } .sort-arr-up { - position: relative; + position: relative; - & > span { - width: 0; - height: 0; - border-left: 6.3px solid transparent; - border-right: 6.3px solid transparent; - border-bottom: 6.3px solid #000; - position: absolute; - top: 5px; - display: block; - left: 2px; - } + & > span { + width: 0; + height: 0; + border-left: 6.3px solid transparent; + border-right: 6.3px solid transparent; + border-bottom: 6.3px solid #000; + position: absolute; + top: 5px; + display: block; + left: 2px; + } } .sort-arr-down { - position: relative; + position: relative; - & > span { - width: 0; - height: 0; - border-left: 6.3px solid transparent; - border-right: 6.3px solid transparent; - border-top: 6.3px solid #000; - position: absolute; - top: 5px; - display: block; - left: 2px; - } + & > span { + width: 0; + height: 0; + border-left: 6.3px solid transparent; + border-right: 6.3px solid transparent; + border-top: 6.3px solid #000; + position: absolute; + top: 5px; + display: block; + left: 2px; + } } .fieldfilters { - clear: both; + clear: both; - & > div { - display: grid; - grid-template-columns: auto auto 1fr; - float: left; - margin-right: 30px; - } + & > div { + display: grid; + grid-template-columns: auto auto 1fr; + float: left; + margin-right: 30px; + } - & > rb-form-checkbox { - float: left; - } + & > rb-form-checkbox { + float: left; + } } .filtermode { - max-width: 82px; + max-width: 82px; } textarea.linkmodal { - display: block; - min-width: 600px; - min-height: 200px; - border: none; + display: block; + min-width: 600px; + min-height: 200px; + border: none; } .filter-inputs > * { - display: inline-block; - width: 220px; + display: inline-block; + width: 220px; } .sample-details-table { - td { - max-width: none; - } + td { + max-width: none; + } } .validation-close { - margin-left: -1px; + margin-left: -1px; } .samples-table tr.clickable { - background: none; - transition: background-color 0.5s; + background: none; + transition: background-color 0.5s; - &:hover { - background: $color-gray-mercury; - } + &:hover { + background: $color-gray-mercury; + } } ::ng-deep .samples-table rb-form-checkbox .input-wrapper { - padding-top: 0; - margin-top: -4.5px; + padding-top: 0; + margin-top: -4.5px; } .link-dialog { - rb-form-checkbox { - display: inline-block; - } + rb-form-checkbox { + display: inline-block; + } - rb-icon-button { - float: right; - } + rb-icon-button { + float: right; + } } .samples-loading { - float: left; - display: grid; - grid-template-columns: auto auto; - grid-template-rows: 1fr auto 1fr; + float: left; + display: grid; + grid-template-columns: auto auto; + grid-template-rows: 1fr auto 1fr; - rb-loading-spinner { - grid-row: span 3; - transform: scale(0.5); - } + rb-loading-spinner { + grid-row: span 3; + transform: scale(0.5); + } - & > div { - grid-column: 2 / 3; - grid-row: 2 / 3; - } + & > div { + grid-column: 2 / 3; + grid-row: 2 / 3; + } } .reset-preferences { - float: right; + float: right; } diff --git a/src/app/samples/samples.component.ts b/src/app/samples/samples.component.ts index af205cb..be24bcb 100644 --- a/src/app/samples/samples.component.ts +++ b/src/app/samples/samples.component.ts @@ -13,548 +13,548 @@ import { Router } from '@angular/router'; import { KeyValue } from '@angular/common'; interface LoadSamplesOptions { - toPage?: number; - event?: Event; - firstPage?: boolean; + toPage?: number; + event?: Event; + firstPage?: boolean; } interface KeyInterface { - id: string; - label: string; - active: boolean; - sortable: boolean; + id: string; + label: string; + active: boolean; + sortable: boolean; } interface Data { - id: string; - data: any[]; + id: string; + data: any[]; } @Component({ - selector: 'app-samples', - templateUrl: './samples.component.html', - styleUrls: ['./samples.component.scss'] + selector: 'app-samples', + templateUrl: './samples.component.html', + styleUrls: ['./samples.component.scss'] }) export class SamplesComponent implements OnInit { - @ViewChild('pageSizeSelection') pageSizeSelection: ElementRef; - @ViewChild('linkarea') linkarea: ElementRef; + @ViewChild('pageSizeSelection') pageSizeSelection: ElementRef; + @ViewChild('linkarea') linkarea: ElementRef; - downloadSpectra = false; // Download options - downloadCondition = false; - downloadFlatten = true; - samples: SampleModel[] = []; // All samples to display - data: Data[] = []; - totalSamples = 0; // Total number of samples - csvUrl = ''; // Store url separate so it only has to be generated when clicking the download button - filters = { - status: { new: true, validated: true, deleted: false }, - pageSize: 25, - toPage: 0, - sort: 'added-asc', - no: { condition: false, measurements: false }, - filters: [ - { field: 'number', label: 'Number', active: false, autocomplete: [], mode: 'eq', values: [''] }, - { field: 'material.name', label: 'Product name', active: false, autocomplete: [], mode: 'eq', values: [''] }, - { field: 'material.supplier', label: 'Supplier', active: false, autocomplete: [], mode: 'eq', values: [''] }, - { field: 'material.group', label: 'Material', active: false, autocomplete: [], mode: 'eq', values: [''] }, - { field: 'material.glass_fiber', label: 'GF', active: false, autocomplete: [], mode: 'eq', values: [''] }, - { field: 'material.carbon_fiber', label: 'CF', active: false, autocomplete: [], mode: 'eq', values: [''] }, - { field: 'material.mineral', label: 'M', active: false, autocomplete: [], mode: 'eq', values: [''] }, - { field: 'type', label: 'Type', active: false, autocomplete: [], mode: 'eq', values: [''] }, - { field: 'color', label: 'Color', active: false, autocomplete: [], mode: 'eq', values: [''] }, - { field: 'batch', label: 'Batch', active: false, autocomplete: [], mode: 'eq', values: [''] }, - { field: 'notes.comment', label: 'Comment', active: false, autocomplete: [], mode: 'eq', values: [''] }, - { field: 'added', label: 'Added', active: false, autocomplete: [], mode: 'eq', values: [''] } - ] - }; - page = 1; // Current page - pages = 1; // Total number of pages - loadSamplesQueue = []; // Arguments of queued up loadSamples() calls - materialKeys: KeyInterface[] = [ - { id: 'number', label: 'Number', active: true, sortable: true }, - { id: 'material.numbers', label: 'Material numbers', active: false, sortable: false }, - { id: 'material.supplier', label: 'Supplier', active: false, sortable: true }, - { id: 'material.group', label: 'Material', active: true, sortable: true }, - { id: 'type', label: 'Type', active: true, sortable: true }, - { id: 'color', label: 'Color', active: false, sortable: true }, - { id: 'batch', label: 'Batch', active: true, sortable: true }, - { id: 'notes.comment', label: 'Comment', active: false, sortable: false } - ]; - conditionKeys: KeyInterface[] = [ - { id: 'notes', label: 'Notes', active: false, sortable: false }, - ]; - measurementKeys: KeyInterface[] = [ - { id: 'material.name', label: 'Product name', active: true, sortable: true }, - { id: 'status', label: 'Status', active: false, sortable: true }, - { id: 'added', label: 'Added', active: true, sortable: true } - ]; + downloadSpectra = false; // Download options + downloadCondition = false; + downloadFlatten = true; + samples: SampleModel[] = []; // All samples to display + data: Data[] = []; + totalSamples = 0; // Total number of samples + csvUrl = ''; // Store url separate so it only has to be generated when clicking the download button + filters = { + status: { new: true, validated: true, deleted: false }, + pageSize: 25, + toPage: 0, + sort: 'added-asc', + no: { condition: false, measurements: false }, + filters: [ + { field: 'number', label: 'Number', active: false, autocomplete: [], mode: 'eq', values: [''] }, + { field: 'material.name', label: 'Product name', active: false, autocomplete: [], mode: 'eq', values: [''] }, + { field: 'material.supplier', label: 'Supplier', active: false, autocomplete: [], mode: 'eq', values: [''] }, + { field: 'material.group', label: 'Material', active: false, autocomplete: [], mode: 'eq', values: [''] }, + { field: 'material.glass_fiber', label: 'GF', active: false, autocomplete: [], mode: 'eq', values: [''] }, + { field: 'material.carbon_fiber', label: 'CF', active: false, autocomplete: [], mode: 'eq', values: [''] }, + { field: 'material.mineral', label: 'M', active: false, autocomplete: [], mode: 'eq', values: [''] }, + { field: 'type', label: 'Type', active: false, autocomplete: [], mode: 'eq', values: [''] }, + { field: 'color', label: 'Color', active: false, autocomplete: [], mode: 'eq', values: [''] }, + { field: 'batch', label: 'Batch', active: false, autocomplete: [], mode: 'eq', values: [''] }, + { field: 'notes.comment', label: 'Comment', active: false, autocomplete: [], mode: 'eq', values: [''] }, + { field: 'added', label: 'Added', active: false, autocomplete: [], mode: 'eq', values: [''] } + ] + }; + page = 1; // Current page + pages = 1; // Total number of pages + loadSamplesQueue = []; // Arguments of queued up loadSamples() calls + materialKeys: KeyInterface[] = [ + { id: 'number', label: 'Number', active: true, sortable: true }, + { id: 'material.numbers', label: 'Material numbers', active: false, sortable: false }, + { id: 'material.supplier', label: 'Supplier', active: false, sortable: true }, + { id: 'material.group', label: 'Material', active: true, sortable: true }, + { id: 'type', label: 'Type', active: true, sortable: true }, + { id: 'color', label: 'Color', active: false, sortable: true }, + { id: 'batch', label: 'Batch', active: true, sortable: true }, + { id: 'notes.comment', label: 'Comment', active: false, sortable: false } + ]; + conditionKeys: KeyInterface[] = [ + { id: 'notes', label: 'Notes', active: false, sortable: false }, + ]; + measurementKeys: KeyInterface[] = [ + { id: 'material.name', label: 'Product name', active: true, sortable: true }, + { id: 'status', label: 'Status', active: false, sortable: true }, + { id: 'added', label: 'Added', active: true, sortable: true } + ]; - // Combines the 3 different categories - categories = { - material: this.materialKeys, - condition: this.conditionKeys, - measurement: this.measurementKeys - }; + // Combines the 3 different categories + categories = { + material: this.materialKeys, + condition: this.conditionKeys, + measurement: this.measurementKeys + }; - isActiveKey: { [key: string]: boolean } = {}; // Object to check if key is currently active - activeKeys: KeyInterface[] = []; // List of active keys - sampleDetailsSample: any = null; // Sample for the sample details dialog - sampleSelect = 0; // Modes: 0 - no selection, 1 - sample edit selection, 2 - validation selection - loading = 0; // Number of loading instances + isActiveKey: { [key: string]: boolean } = {}; // Object to check if key is currently active + activeKeys: KeyInterface[] = []; // List of active keys + sampleDetailsSample: any = null; // Sample for the sample details dialog + sampleSelect = 0; // Modes: 0 - no selection, 1 - sample edit selection, 2 - validation selection + loading = 0; // Number of loading instances - // Change the way values are displayed - valueConverters = { - 'added': (v: string, _sample) => new Date(v).toLocaleDateString(), - 'notes': (v: any, _sample: any) => v.sample_references.length > 0 ? Object.keys(v.sample_references).map(r => v.sample_references[r].relation).join(", ") : "", - 'notes.comment': (v: any, sample: any) => sample['notes']['comment'] - } + // Change the way values are displayed + valueConverters = { + 'added': (v: string, _sample) => new Date(v).toLocaleDateString(), + 'notes': (v: any, _sample: any) => v.sample_references.length > 0 ? Object.keys(v.sample_references).map(r => v.sample_references[r].relation).join(", ") : "", + 'notes.comment': (v: any, sample: any) => sample['notes']['comment'] + } - constructor( - private api: ApiService, - public autocomplete: AutocompleteService, - public login: LoginService, - private modalService: ModalService, - public d: DataService, - private storage: LocalStorageService, - private window: Window, - private router: Router - ) {} + constructor( + private api: ApiService, + public autocomplete: AutocompleteService, + public login: LoginService, + private modalService: ModalService, + public d: DataService, + private storage: LocalStorageService, + private window: Window, + private router: Router + ) {} - ngOnInit(): void { - this.loading = 8; - const onLoad = () => { - if ((--this.loading) <= 0) { - this.loadSamples(); - } - }; + ngOnInit(): void { + this.loading = 8; + const onLoad = () => { + if ((--this.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); - 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', onLoad); - this.d.load('conditionTemplates', onLoad); - this.loadTemplateKeys('material', 'type', onLoad); - this.loadTemplateKeys('condition', 'notes', onLoad); - this.loadTemplateKeys('measurement', 'status', onLoad); - } + this.calcFieldSelectKeys(); + this.d.load('materials', () => { + this.filters.filters.find(e => e.field === 'material.name').autocomplete = this.d.arr.materials.map(e => e.name); + 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', onLoad); + this.d.load('conditionTemplates', onLoad); + this.loadTemplateKeys('material', 'type', onLoad); + this.loadTemplateKeys('condition', 'notes', onLoad); + this.loadTemplateKeys('measurement', 'status', onLoad); + } - loadTemplateKeys(collection, insertBefore, f) { - this.d.load(collection + 'Templates', () => { - const templateKeys = []; - this.d.arr[collection + 'Templates'].forEach(item => { - item.parameters.forEach(parameter => { - const parameterName = encodeURIComponent(parameter.name); - // Exclude spectrum and duplicates - if (parameter.name !== 'dpt' && !templateKeys.find(e => new RegExp('.' + parameterName + '$').test(e.id))) { - const collectionNames = { - material: 'material.properties', - condition: 'condition', - measurement: 'measurements.' + encodeURIComponent(item.name) - }; - templateKeys.push({ - id: `${collectionNames[collection]}.${parameterName}`, - label: `${this.ucFirst(item.name)} ${parameter.name}`, - active: false, - sortable: true - }); - this.filters.filters.push({ - field: `${collectionNames[collection]}.${parameterName}`, - label: `${this.ucFirst(item.name)} ${parameter.name}`, - active: false, - autocomplete: [], - mode: 'eq', - values: [''] - }); - } - }); - }); - this.categories[collection].splice(this.categories[collection].findIndex(e => e.id === insertBefore), 0, ...templateKeys); - this.categories[collection] = [...this.categories[collection]]; // Complete overwrite array to invoke update in rb-multiselect - this.loadPreferences(); - f(); - }); - } + loadTemplateKeys(collection, insertBefore, f) { + this.d.load(collection + 'Templates', () => { + const templateKeys = []; + this.d.arr[collection + 'Templates'].forEach(item => { + item.parameters.forEach(parameter => { + const parameterName = encodeURIComponent(parameter.name); + // Exclude spectrum and duplicates + if (parameter.name !== 'dpt' && !templateKeys.find(e => new RegExp('.' + parameterName + '$').test(e.id))) { + const collectionNames = { + material: 'material.properties', + condition: 'condition', + measurement: 'measurements.' + encodeURIComponent(item.name) + }; + templateKeys.push({ + id: `${collectionNames[collection]}.${parameterName}`, + label: `${this.ucFirst(item.name)} ${parameter.name}`, + active: false, + sortable: true + }); + this.filters.filters.push({ + field: `${collectionNames[collection]}.${parameterName}`, + label: `${this.ucFirst(item.name)} ${parameter.name}`, + active: false, + autocomplete: [], + mode: 'eq', + values: [''] + }); + } + }); + }); + this.categories[collection].splice(this.categories[collection].findIndex(e => e.id === insertBefore), 0, ...templateKeys); + this.categories[collection] = [...this.categories[collection]]; // Complete overwrite array to invoke update in rb-multiselect + this.loadPreferences(); + f(); + }); + } - // Set toPage to null to reload first page, queues calls - loadSamples(options: LoadSamplesOptions = {}, event = null, category?) { - if (event) { // Adjust active keys - this.categories[category].forEach(key => { - if (event.hasOwnProperty(key.id)) { - key.active = event[key.id]; - } - }); - const sortId = this.filters.sort.replace(/(-asc|-desc)/, ''); - if (event.hasOwnProperty(sortId) && !event[sortId]) { // Reset sort if sort field was unselected - this.setSort('_id-asc'); - } - this.updateActiveKeys(); - } - if (options.firstPage) { - this.storage.set('currentPage', 1); - } - this.loadSamplesQueue.push(options); - if (this.loadSamplesQueue.length <= 1) { // Nothing queued up - this.sampleLoader(this.loadSamplesQueue[0]); - } - this.storePreferences(); - } + // Set toPage to null to reload first page, queues calls + loadSamples(options: LoadSamplesOptions = {}, event = null, category?) { + if (event) { // Adjust active keys + this.categories[category].forEach(key => { + if (event.hasOwnProperty(key.id)) { + key.active = event[key.id]; + } + }); + const sortId = this.filters.sort.replace(/(-asc|-desc)/, ''); + if (event.hasOwnProperty(sortId) && !event[sortId]) { // Reset sort if sort field was unselected + this.setSort('_id-asc'); + } + this.updateActiveKeys(); + } + if (options.firstPage) { + this.storage.set('currentPage', 1); + } + this.loadSamplesQueue.push(options); + 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 - this.loading++; - this.api.get(this.sampleUrl({ paging: true, pagingOptions: options }), (sData, err, headers) => { - this.loading--; - if (err) { // Remove stored options on error to avoid loop - this.storage.remove('samplesPreferences'); - this.api.requestError(err); - } - else { - if (!options.toPage && headers['x-total-items']) { - this.totalSamples = headers['x-total-items']; - } - this.pages = Math.ceil(this.totalSamples / this.filters.pageSize); - this.samples = sData as any; - this.storeData(); - this.loadSamplesQueue.shift(); - if (this.loadSamplesQueue.length > 0) { // Execute next queue item - this.sampleLoader(this.loadSamplesQueue[0]); - } - } - }); - if (this.storage.get('currentPage') !== this.page) { - this.loadPage(Number(this.storage.get('currentPage')) - this.page); - } - } + private sampleLoader(options: LoadSamplesOptions) { // Actual loading of the sample, do not call directly + this.loading++; + this.api.get(this.sampleUrl({ paging: true, pagingOptions: options }), (sData, err, headers) => { + this.loading--; + if (err) { // Remove stored options on error to avoid loop + this.storage.remove('samplesPreferences'); + this.api.requestError(err); + } + else { + if (!options.toPage && headers['x-total-items']) { + this.totalSamples = headers['x-total-items']; + } + this.pages = Math.ceil(this.totalSamples / this.filters.pageSize); + this.samples = sData as any; + this.storeData(); + this.loadSamplesQueue.shift(); + if (this.loadSamplesQueue.length > 0) { // Execute next queue item + this.sampleLoader(this.loadSamplesQueue[0]); + } + } + }); + if (this.storage.get('currentPage') !== this.page) { + this.loadPage(Number(this.storage.get('currentPage')) - this.page); + } + } - sampleUrl(options: { - paging?: boolean, - pagingOptions?: { - firstPage?: boolean, - toPage?: number, - event?: Event - }, - csv?: boolean, - export?: boolean, - host?: boolean - }) { // Return url to fetch samples - // Keys which should always be added if export = false - const additionalTableKeys = ['material_id', '_id', 'user_id']; - const query: string[] = []; - query.push(...Object.keys(this.filters.status).filter(e => this.filters.status[e]).map(e => 'status[]=' + e)); - if (options.paging) { - if (this.samples[0]) { // Do not include from-id when page size was changed - if (!options.pagingOptions.firstPage) { - query.push('from-id=' + this.samples[0]._id); - } - else { - this.page = 1; - } - } - if (options.pagingOptions.toPage) { - query.push('to-page=' + options.pagingOptions.toPage); - } - query.push('page-size=' + this.filters.pageSize); - } - query.push('sort=' + this.filters.sort); - if (options.export) { // Append API key on export - query.push('key=' + this.d.d.userKey.key); - } + sampleUrl(options: { + paging?: boolean, + pagingOptions?: { + firstPage?: boolean, + toPage?: number, + event?: Event + }, + csv?: boolean, + export?: boolean, + host?: boolean + }) { // Return url to fetch samples + // Keys which should always be added if export = false + const additionalTableKeys = ['material_id', '_id', 'user_id']; + const query: string[] = []; + query.push(...Object.keys(this.filters.status).filter(e => this.filters.status[e]).map(e => 'status[]=' + e)); + if (options.paging) { + if (this.samples[0]) { // Do not include from-id when page size was changed + if (!options.pagingOptions.firstPage) { + query.push('from-id=' + this.samples[0]._id); + } + else { + this.page = 1; + } + } + if (options.pagingOptions.toPage) { + query.push('to-page=' + options.pagingOptions.toPage); + } + query.push('page-size=' + this.filters.pageSize); + } + query.push('sort=' + this.filters.sort); + if (options.export) { // Append API key on export + query.push('key=' + this.d.d.userKey.key); + } - for (let category in this.categories) { - this.categories[category].forEach(key => { - if (key.active && (options.export || (!options.export && key.id.indexOf('material.') < 0))) { - query.push('fields[]=' + key.id); - } - }); - } - query.push(...cloneDeep(this.filters.filters) - .map(e => { - e.values = e.values.filter(el => el !== ''); // Do not include empty values - if (e.field === 'added') { // Correct timezone - e.values = e.values.map( - el => new Date(new Date(el).getTime() - new Date(el).getTimezoneOffset() * 60000).toISOString() - ); - } - if (e.mode === 'null') { // Handle null mode - e.mode = 'in'; - e.values = [null, '']; - } - return e; - }) - .filter(e => e.active && e.values.length > 0) - .map(e => 'filters[]=' + encodeURIComponent(JSON.stringify(pick(e, ['mode', 'field', 'values'])))) - ); - if (this.filters.no.condition) { - query.push('filters[]=' + encodeURIComponent(JSON.stringify({ mode: 'eq', field: 'condition', values: [{}] }))); - } - if (this.filters.no.measurements) { - query.push('filters[]=' + - encodeURIComponent(JSON.stringify({ mode: 'eq', field: 'measurements', values: [null] }))); - } - if (!options.export) { - additionalTableKeys.forEach(key => { - if (query.indexOf('fields[]=' + key) < 0) { // Add key if not already added - query.push('fields[]=' + key); - } - }); - } - else { // Export options - if (options.csv) { - query.push('output=csv'); - } - else if (this.downloadFlatten) { - query.push('output=flatten'); - } - if (this.downloadSpectra) { - query.push('fields[]=measurements.spectrum.dpt'); - } - if (this.downloadCondition) { - query.push('fields[]=condition'); - } - } - return (options.host && isDevMode() ? this.window.location.host : '') + - (options.export ? this.api.hostName : '') + - '/samples?' + query.join('&'); - } + for (let category in this.categories) { + this.categories[category].forEach(key => { + if (key.active && (options.export || (!options.export && key.id.indexOf('material.') < 0))) { + query.push('fields[]=' + key.id); + } + }); + } + query.push(...cloneDeep(this.filters.filters) + .map(e => { + e.values = e.values.filter(el => el !== ''); // Do not include empty values + if (e.field === 'added') { // Correct timezone + e.values = e.values.map( + el => new Date(new Date(el).getTime() - new Date(el).getTimezoneOffset() * 60000).toISOString() + ); + } + if (e.mode === 'null') { // Handle null mode + e.mode = 'in'; + e.values = [null, '']; + } + return e; + }) + .filter(e => e.active && e.values.length > 0) + .map(e => 'filters[]=' + encodeURIComponent(JSON.stringify(pick(e, ['mode', 'field', 'values'])))) + ); + if (this.filters.no.condition) { + query.push('filters[]=' + encodeURIComponent(JSON.stringify({ mode: 'eq', field: 'condition', values: [{}] }))); + } + if (this.filters.no.measurements) { + query.push('filters[]=' + + encodeURIComponent(JSON.stringify({ mode: 'eq', field: 'measurements', values: [null] }))); + } + if (!options.export) { + additionalTableKeys.forEach(key => { + if (query.indexOf('fields[]=' + key) < 0) { // Add key if not already added + query.push('fields[]=' + key); + } + }); + } + else { // Export options + if (options.csv) { + query.push('output=csv'); + } + else if (this.downloadFlatten) { + query.push('output=flatten'); + } + if (this.downloadSpectra) { + query.push('fields[]=measurements.spectrum.dpt'); + } + if (this.downloadCondition) { + query.push('fields[]=condition'); + } + } + return (options.host && isDevMode() ? this.window.location.host : '') + + (options.export ? this.api.hostName : '') + + '/samples?' + query.join('&'); + } - loadPage(delta) { - if (!/[0-9]+/.test(delta) || this.page + delta < 1) { // Invalid delta - return; - } - this.page += delta; - this.storage.set('currentPage', this.page); - this.loadSamples({ toPage: delta }); - } + loadPage(delta) { + if (!/[0-9]+/.test(delta) || this.page + delta < 1) { // Invalid delta + return; + } + this.page += delta; + this.storage.set('currentPage', this.page); + this.loadSamples({ toPage: delta }); + } - storePreferences() { - let keys = []; - for (let category in this.categories) { - keys.push(...this.categories[category].map(e => pick(e, ['id', 'active']))); - } + storePreferences() { + let keys = []; + for (let category in this.categories) { + keys.push(...this.categories[category].map(e => pick(e, ['id', 'active']))); + } - const store = { - filters: { - ...pick(this.filters, ['status', 'pageSize', 'toPage', 'sort']), - filters: this.filters.filters.map(e => pick(e, ['field', 'active', 'mode', 'values'])) - }, - keys: keys - }; - this.storage.set('samplesPreferences', store); - this.calcFieldSelectKeys(); - } + const store = { + filters: { + ...pick(this.filters, ['status', 'pageSize', 'toPage', 'sort']), + filters: this.filters.filters.map(e => pick(e, ['field', 'active', 'mode', 'values'])) + }, + keys: keys + }; + this.storage.set('samplesPreferences', store); + this.calcFieldSelectKeys(); + } - 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 }; - } - }); + 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 => { - for (let category in this.categories) { - const keyIndex = this.categories[category].findIndex(e => e.id === key.id); - if (keyIndex >= 0) { - this.categories[category][keyIndex].active = key.active; - } - } - }); - } - this.calcFieldSelectKeys(); - this.updateActiveKeys(); - } + store.keys.forEach(key => { + for (let category in this.categories) { + const keyIndex = this.categories[category].findIndex(e => e.id === key.id); + if (keyIndex >= 0) { + this.categories[category][keyIndex].active = key.active; + } + } + }); + } + this.calcFieldSelectKeys(); + this.updateActiveKeys(); + } - resetPreferences() { - this.storage.remove('samplesPreferences'); - this.window.location.reload(); - } + resetPreferences() { + this.storage.remove('samplesPreferences'); + this.window.location.reload(); + } - updateFilterFields(field) { - const filter = this.filters.filters.find(e => e.field === field); - filter.active = !(filter.values.length === 1 && filter.values[0] === ''); - } + updateFilterFields(field) { + const filter = this.filters.filters.find(e => e.field === field); + filter.active = !(filter.values.length === 1 && filter.values[0] === ''); + } - setSort(string) { - this.filters.sort = string; - this.loadSamples({ firstPage: true }); - } + setSort(string) { + this.filters.sort = string; + this.loadSamples({ firstPage: true }); + } - updateActiveKeys() { // Array with all activeKeys - this.activeKeys = []; - for (let category in this.categories) { - this.activeKeys.push(...this.categories[category].filter(e => e.active)); - this.filters.filters.forEach(filter => { // Disable filters of fields not displayed - if (!this.isActiveKey[filter.field]) { - filter.active = false; - } - }); - } - } + updateActiveKeys() { // Array with all activeKeys + this.activeKeys = []; + for (let category in this.categories) { + this.activeKeys.push(...this.categories[category].filter(e => e.active)); + this.filters.filters.forEach(filter => { // Disable filters of fields not displayed + if (!this.isActiveKey[filter.field]) { + filter.active = false; + } + }); + } + } - calcFieldSelectKeys() { - for (let category in this.categories) { - this.categories[category].forEach(key => { - this.isActiveKey[key.id] = key.active; - }); - } - } + calcFieldSelectKeys() { + for (let category in this.categories) { + this.categories[category].forEach(key => { + this.isActiveKey[key.id] = key.active; + }); + } + } - sampleDetails(id: string, modal: TemplateRef) { // Show sample details - this.sampleDetailsSample = null; - this.api.get('/sample/' + id, data => { - this.sampleDetailsSample = new SampleModel().deserialize(data); - if (data.notes.custom_fields) { // Convert custom_fields for more optimized display - this.sampleDetailsSample.notes.custom_fields_entries = - Object.entries(this.sampleDetailsSample.notes.custom_fields); - } - else { - this.sampleDetailsSample.custom_fields_entries = []; - } - if (Object.keys(data.condition).length) { // Convert condition - this.sampleDetailsSample.condition_entries = - Object.entries(omit(this.sampleDetailsSample.condition, ['condition_template'])) - .map(e => { - e[0] = `${this.ucFirst( - this.d.id.conditionTemplates[this.sampleDetailsSample.condition.condition_template].name - )} ${e[0]}`; - return e; - }); - } - else { - this.sampleDetailsSample.condition_entries = []; - } - this.sampleDetailsSample.measurement_entries = []; - // Convert measurements for more optimized display without dpt - this.sampleDetailsSample.measurements.forEach(measurement => { - const name = this.d.id.measurementTemplates[measurement.measurement_template].name; - this.sampleDetailsSample.measurement_entries - .push(...Object.entries(measurement.values).filter(e => e[0] !== 'dpt') - .map(e => ({ name: this.ucFirst(name) + ' ' + e[0], value: e[1] }))); - }); - new Promise(resolve => { - if (data.notes.sample_references.length) { // Load referenced samples if available - let loadingCounter = data.notes.sample_references.length; - this.sampleDetailsSample.notes.sample_references.forEach(reference => { - this.api.get('/sample/' + reference.sample_id, rData => { - reference.number = rData.number; - loadingCounter--; - if (!loadingCounter) { - resolve(); - } - }); - }); - } - else { - resolve(); - } - }).then(() => { - this.modalService.open(modal).then(() => { }); - }); - }); - } + sampleDetails(id: string, modal: TemplateRef) { // Show sample details + this.sampleDetailsSample = null; + this.api.get('/sample/' + id, data => { + this.sampleDetailsSample = new SampleModel().deserialize(data); + if (data.notes.custom_fields) { // Convert custom_fields for more optimized display + this.sampleDetailsSample.notes.custom_fields_entries = + Object.entries(this.sampleDetailsSample.notes.custom_fields); + } + else { + this.sampleDetailsSample.custom_fields_entries = []; + } + if (Object.keys(data.condition).length) { // Convert condition + this.sampleDetailsSample.condition_entries = + Object.entries(omit(this.sampleDetailsSample.condition, ['condition_template'])) + .map(e => { + e[0] = `${this.ucFirst( + this.d.id.conditionTemplates[this.sampleDetailsSample.condition.condition_template].name + )} ${e[0]}`; + return e; + }); + } + else { + this.sampleDetailsSample.condition_entries = []; + } + this.sampleDetailsSample.measurement_entries = []; + // Convert measurements for more optimized display without dpt + this.sampleDetailsSample.measurements.forEach(measurement => { + const name = this.d.id.measurementTemplates[measurement.measurement_template].name; + this.sampleDetailsSample.measurement_entries + .push(...Object.entries(measurement.values).filter(e => e[0] !== 'dpt') + .map(e => ({ name: this.ucFirst(name) + ' ' + e[0], value: e[1] }))); + }); + new Promise(resolve => { + if (data.notes.sample_references.length) { // Load referenced samples if available + let loadingCounter = data.notes.sample_references.length; + this.sampleDetailsSample.notes.sample_references.forEach(reference => { + this.api.get('/sample/' + reference.sample_id, rData => { + reference.number = rData.number; + loadingCounter--; + if (!loadingCounter) { + resolve(); + } + }); + }); + } + else { + resolve(); + } + }).then(() => { + this.modalService.open(modal).then(() => { }); + }); + }); + } - validate() { - if (this.sampleSelect) { // Do actual validation - this.samples.forEach(sample => { - if (sample.selected) { - this.api.put('/sample/validate/' + sample._id); - } - }); - this.loadSamples(); - this.sampleSelect = 0; - } - else { // Get into validation mode - this.sampleSelect = 2; - } - } + validate() { + if (this.sampleSelect) { // Do actual validation + this.samples.forEach(sample => { + if (sample.selected) { + this.api.put('/sample/validate/' + sample._id); + } + }); + this.loadSamples(); + this.sampleSelect = 0; + } + else { // Get into validation mode + this.sampleSelect = 2; + } + } - batchEdit() { // Redirect to batch edit - if (this.sampleSelect) { - this.router.navigate(['/samples/edit/' + this.samples.filter(e => e.selected).map(e => e._id).join(',')]); - this.sampleSelect = 0; - } - else { - this.sampleSelect = 1; - } - } + batchEdit() { // Redirect to batch edit + if (this.sampleSelect) { + this.router.navigate(['/samples/edit/' + this.samples.filter(e => e.selected).map(e => e._id).join(',')]); + this.sampleSelect = 0; + } + else { + this.sampleSelect = 1; + } + } - restoreSample(id, modal, event) { - this.stopPropagation(event); - this.modalService.open(modal).then(res => { - if (res) { - this.api.put('/sample/restore/' + id, {}, ignore => { - this.samples.find(e => e._id === id).status = 'new'; - }); - } - }); - } + restoreSample(id, modal, event) { + this.stopPropagation(event); + this.modalService.open(modal).then(res => { + if (res) { + this.api.put('/sample/restore/' + id, {}, ignore => { + this.samples.find(e => e._id === id).status = 'new'; + }); + } + }); + } - selectAll(event) { // Toggle select all except deleted samples - this.samples.forEach(sample => { - if (sample.status !== 'deleted') { - sample.selected = event.target.checked; - } - else { - sample.selected = false; - } - }); - } + selectAll(event) { // Toggle select all except deleted samples + this.samples.forEach(sample => { + if (sample.status !== 'deleted') { + sample.selected = event.target.checked; + } + else { + sample.selected = false; + } + }); + } - preventDefault(event, key = 'all') { - if (key === 'all' || event.key === key) { - event.preventDefault(); - } - } + preventDefault(event, key = 'all') { + if (key === 'all' || event.key === key) { + event.preventDefault(); + } + } - stopPropagation(event) { - event.stopPropagation(); - } + stopPropagation(event) { + event.stopPropagation(); + } - clipboard() { // Copy contents to clipboard - this.linkarea.nativeElement.select(); - this.linkarea.nativeElement.setSelectionRange(0, 99999); - document.execCommand('copy'); - } + clipboard() { // Copy contents to clipboard + this.linkarea.nativeElement.select(); + this.linkarea.nativeElement.setSelectionRange(0, 99999); + document.execCommand('copy'); + } - ucFirst(string) { // Convert first character of string to uppercase - return string[0].toUpperCase() + string.slice(1); - } + ucFirst(string) { // Convert first character of string to uppercase + return string[0].toUpperCase() + string.slice(1); + } - refreshMultiSelect() { - for (let collection in this.categories) { - this.categories[collection] = [...this.categories[collection]]; - } - } + refreshMultiSelect() { + for (let collection in this.categories) { + this.categories[collection] = [...this.categories[collection]]; + } + } - // Stores data in a unified way - storeData() { - this.data = []; - this.samples.forEach(sample => { - let value = []; - this.activeKeys.forEach(key => { - let id = key.id.split('material.')[1]; - let tmpValue = id ? this.d.id.materials[sample.material_id][id] : sample[key.id]; - value.push(this.valueConverters[key.id] ? this.valueConverters[key.id](tmpValue, sample) : tmpValue); - }); - this.data.push({ id: sample._id, data: value }); - }); - } + // Stores data in a unified way + storeData() { + this.data = []; + this.samples.forEach(sample => { + let value = []; + this.activeKeys.forEach(key => { + let id = key.id.split('material.')[1]; + let tmpValue = id ? this.d.id.materials[sample.material_id][id] : sample[key.id]; + value.push(this.valueConverters[key.id] ? this.valueConverters[key.id](tmpValue, sample) : tmpValue); + }); + this.data.push({ id: sample._id, data: value }); + }); + } - originalOrder = (a: KeyValue, b: KeyValue): number => { - return 0; - } + originalOrder = (a: KeyValue, b: KeyValue): number => { + return 0; + } } diff --git a/src/app/services/api.service.ts b/src/app/services/api.service.ts index 0ba4565..aa79880 100644 --- a/src/app/services/api.service.ts +++ b/src/app/services/api.service.ts @@ -7,93 +7,93 @@ import {ModalService} from '@inst-iot/bosch-angular-ui-components'; @Injectable({ - providedIn: 'root' + providedIn: 'root' }) export class ApiService { - private host = isDevMode() ? '/api' : 'https://definma-api.apps.de1.bosch-iot-cloud.com'; + private host = isDevMode() ? '/api' : 'https://definma-api.apps.de1.bosch-iot-cloud.com'; - constructor( - private http: HttpClient, - private storage: LocalStorageService, - private modalService: ModalService, - private window: Window - ) { } + constructor( + private http: HttpClient, + private storage: LocalStorageService, + private modalService: ModalService, + private window: Window + ) { } - get hostName() { - return this.host; - } + get hostName() { + return this.host; + } - // Main HTTP methods - get(url, f: (data?: T, err?, headers?) => void = () => {}) { - this.requestErrorHandler(this.http.get(this.url(url), this.options()), f); - } + // Main HTTP methods + get(url, f: (data?: T, err?, headers?) => void = () => {}) { + this.requestErrorHandler(this.http.get(this.url(url), this.options()), f); + } - post(url, data = null, f: (data?: T, err?, headers?) => void = () => {}) { - this.requestErrorHandler(this.http.post(this.url(url), data, this.options()), f); - } + post(url, data = null, f: (data?: T, err?, headers?) => void = () => {}) { + this.requestErrorHandler(this.http.post(this.url(url), data, this.options()), f); + } - put(url, data = null, f: (data?: T, err?, headers?) => void = () => {}) { - this.requestErrorHandler(this.http.put(this.url(url), data, this.options()), f); - } + put(url, data = null, f: (data?: T, err?, headers?) => void = () => {}) { + this.requestErrorHandler(this.http.put(this.url(url), data, this.options()), f); + } - delete(url, f: (data?: T, err?, headers?) => void = () => {}) { - this.requestErrorHandler(this.http.delete(this.url(url), this.options()), f); - } + delete(url, f: (data?: T, err?, headers?) => void = () => {}) { + this.requestErrorHandler(this.http.delete(this.url(url), this.options()), f); + } - // Execute request and handle errors - private requestErrorHandler(observable: Observable, f: (data?: T, err?, headers?) => void) { - observable.subscribe(data => { // Successful request - f( - data.body, - undefined, - data.headers.keys().reduce((s, e) => {s[e.toLowerCase()] = data.headers.get(e); return s; }, {}) - ); - }, err => { // Error - if (f.length > 1) { // Pass on error - f(undefined, err, undefined); - } - else { // Handle directly - this.requestError(err); - } - }); - } + // Execute request and handle errors + private requestErrorHandler(observable: Observable, f: (data?: T, err?, headers?) => void) { + observable.subscribe(data => { // Successful request + f( + data.body, + undefined, + data.headers.keys().reduce((s, e) => {s[e.toLowerCase()] = data.headers.get(e); return s; }, {}) + ); + }, err => { // Error + if (f.length > 1) { // Pass on error + f(undefined, err, undefined); + } + else { // Handle directly + this.requestError(err); + } + }); + } - requestError(err) { // Network error dialog - const modalRef = this.modalService.openComponent(ErrorComponent); - modalRef.instance.message = 'Network request failed!'; - const details = [err.error.status]; - if (err.error.details) { - details.push(err.error.details); - } - modalRef.instance.details = details; - modalRef.result.then(() => { - this.window.location.reload(); - }); - } + requestError(err) { // Network error dialog + const modalRef = this.modalService.openComponent(ErrorComponent); + modalRef.instance.message = 'Network request failed!'; + const details = [err.error.status]; + if (err.error.details) { + details.push(err.error.details); + } + modalRef.instance.details = details; + modalRef.result.then(() => { + this.window.location.reload(); + }); + } - private url(url) { // Detect if host was given, otherwise use default host - if (/http[s]?:\/\//.test(url)) { - return url; - } - else { - return this.host + url; - } - } - - // Generate request options - private options(): {headers: HttpHeaders, observe: 'body'} { - return {headers: this.authOptions(), observe: 'response' as 'body'}; - } - - // Generate Basic Auth - private authOptions(): HttpHeaders { - const auth = this.storage.get('basicAuth'); - if (auth) { - return new HttpHeaders({Authorization: 'Basic ' + auth}); - } - else { - return new HttpHeaders(); - } - } + private url(url) { // Detect if host was given, otherwise use default host + if (/http[s]?:\/\//.test(url)) { + return url; + } +else { + return this.host + url; +} + } + + // Generate request options + private options(): {headers: HttpHeaders, observe: 'body'} { + return {headers: this.authOptions(), observe: 'response' as 'body'}; + } + + // Generate Basic Auth + private authOptions(): HttpHeaders { + const auth = this.storage.get('basicAuth'); + if (auth) { + return new HttpHeaders({Authorization: 'Basic ' + auth}); + } + else { + return new HttpHeaders(); + } + } } diff --git a/src/app/services/autocomplete.service.ts b/src/app/services/autocomplete.service.ts index 4000b67..9df0cba 100644 --- a/src/app/services/autocomplete.service.ts +++ b/src/app/services/autocomplete.service.ts @@ -3,18 +3,18 @@ import {QuickScore} from 'quick-score'; import {of} from 'rxjs'; @Injectable({ - providedIn: 'root' + providedIn: 'root' }) export class AutocompleteService { - constructor() { } + constructor() { } - bind(ref, list: string[]) { - return this.search.bind(ref, list); - } + bind(ref, list: string[]) { + return this.search.bind(ref, list); + } - search(arr: string[], str: string) { - const qs = new QuickScore(arr); - return of(str === '' ? [] : qs.search(str).map(e => e.item)); - } + search(arr: string[], str: string) { + const qs = new QuickScore(arr); + return of(str === '' ? [] : qs.search(str).map(e => e.item)); + } } diff --git a/src/app/services/data.service.ts b/src/app/services/data.service.ts index 18716c8..b18f7af 100644 --- a/src/app/services/data.service.ts +++ b/src/app/services/data.service.ts @@ -8,77 +8,77 @@ import {ModelItemModel} from '../models/model-item.model'; import {ModelFileModel} from '../models/model-file.model'; @Injectable({ - providedIn: 'root' + providedIn: 'root' }) export class DataService { - constructor( - private api: ApiService - ) { } + constructor( + private api: ApiService + ) { } - private collectionMap = { // List of available collections - materials: {path: '/materials?status[]=validated&status[]=new', model: MaterialModel, type: 'idArray'}, - materialSuppliers: {path: '/material/suppliers', model: null, type: 'idArray'}, - materialGroups: {path: '/material/groups', model: null, type: 'idArray'}, - materialTemplates: {path: '/template/materials', model: TemplateModel, type: 'template'}, - measurementTemplates: {path: '/template/measurements', model: TemplateModel, type: 'template'}, - conditionTemplates: {path: '/template/conditions', model: TemplateModel, type: 'template'}, - sampleNotesFields: {path: '/sample/notes/fields', model: TemplateModel, type: 'idArray'}, - users: {path: '/users', model: UserModel, type: 'idArray'}, - modelGroups: {path: '/model/groups', model: ModelItemModel, type: 'array'}, - modelFiles: {path: '/model/files', model: ModelFileModel, type: 'array'}, - user: {path: '/user', model: UserModel, type: 'string'}, - userKey: {path: '/user/key', model: BaseModel, type: 'string'} - }; + private collectionMap = { // List of available collections + materials: {path: '/materials?status[]=validated&status[]=new', model: MaterialModel, type: 'idArray'}, + materialSuppliers: {path: '/material/suppliers', model: null, type: 'idArray'}, + materialGroups: {path: '/material/groups', model: null, type: 'idArray'}, + materialTemplates: {path: '/template/materials', model: TemplateModel, type: 'template'}, + measurementTemplates: {path: '/template/measurements', model: TemplateModel, type: 'template'}, + conditionTemplates: {path: '/template/conditions', model: TemplateModel, type: 'template'}, + sampleNotesFields: {path: '/sample/notes/fields', model: TemplateModel, type: 'idArray'}, + users: {path: '/users', model: UserModel, type: 'idArray'}, + modelGroups: {path: '/model/groups', model: ModelItemModel, type: 'array'}, + modelFiles: {path: '/model/files', model: ModelFileModel, type: 'array'}, + user: {path: '/user', model: UserModel, type: 'string'}, + userKey: {path: '/user/key', model: BaseModel, type: 'string'} + }; - arr: {[key: string]: any[]} = {}; // Array of data - latest: {[key: string]: any[]} = {}; // Array of latest template versions - id: {[key: string]: {[id: string]: any}} = {}; // Data in format _id: data - d: {[key: string]: any} = {}; // Data not in array format + arr: {[key: string]: any[]} = {}; // Array of data + latest: {[key: string]: any[]} = {}; // Array of latest template versions + id: {[key: string]: {[id: string]: any}} = {}; // Data in format _id: data + d: {[key: string]: any} = {}; // Data not in array format - contact = {name: 'CR/APS1-Lingenfelser', mail: 'dominic.lingenfelser@bosch.com'}; // Global contact data + contact = {name: 'CR/APS1-Lingenfelser', mail: 'dominic.lingenfelser@bosch.com'}; // Global contact data - load(collection, f = () => {}) { // Load data - if (this.arr[collection]) { // Data already loaded - f(); - } - else { // Load data - this.api.get(this.collectionMap[collection].path, data => { - if (this.collectionMap[collection].type !== 'string') { // Array data - this.arr[collection] = data - .map( - e => this.collectionMap[collection].model ? - new this.collectionMap[collection].model().deserialize(e) : e - ); - // Load ids - if (this.collectionMap[collection].type === 'idArray' || this.collectionMap[collection].type === 'template') { - this.idReload(collection); - } - } - else { // Not array data - this.d[collection] = new this.collectionMap[collection].model().deserialize(data); - } - f(); - }); - } - } + load(collection, f = () => {}) { // Load data + if (this.arr[collection]) { // Data already loaded + f(); + } + else { // Load data + this.api.get(this.collectionMap[collection].path, data => { + if (this.collectionMap[collection].type !== 'string') { // Array data + this.arr[collection] = data + .map( + e => this.collectionMap[collection].model ? + new this.collectionMap[collection].model().deserialize(e) : e + ); + // Load ids + if (this.collectionMap[collection].type === 'idArray' || this.collectionMap[collection].type === 'template') { + this.idReload(collection); + } + } + else { // Not array data + this.d[collection] = new this.collectionMap[collection].model().deserialize(data); + } + f(); + }); + } + } - // Generate id object - idReload(collection) { - this.id[collection] = this.arr[collection].reduce((s, e) => {s[e._id] = e; return s; }, {}); - if (this.collectionMap[collection].type === 'template') { // Generate array with latest templates - const tmpTemplates = {}; - this.arr[collection].forEach(template => { - if (tmpTemplates[template.first_id]) { // Already found another version - if (template.version > tmpTemplates[template.first_id].version) { - tmpTemplates[template.first_id] = template; - } - } - else { - tmpTemplates[template.first_id] = template; - } - }); - this.latest[collection] = Object.values(tmpTemplates); - } - } + // Generate id object + idReload(collection) { + this.id[collection] = this.arr[collection].reduce((s, e) => {s[e._id] = e; return s; }, {}); + if (this.collectionMap[collection].type === 'template') { // Generate array with latest templates + const tmpTemplates = {}; + this.arr[collection].forEach(template => { + if (tmpTemplates[template.first_id]) { // Already found another version + if (template.version > tmpTemplates[template.first_id].version) { + tmpTemplates[template.first_id] = template; + } + } + else { + tmpTemplates[template.first_id] = template; + } + }); + this.latest[collection] = Object.values(tmpTemplates); + } + } } diff --git a/src/app/services/login.service.ts b/src/app/services/login.service.ts index 720b8b3..d056ed1 100644 --- a/src/app/services/login.service.ts +++ b/src/app/services/login.service.ts @@ -6,130 +6,130 @@ import {Observable} from 'rxjs'; import {DataService} from './data.service'; @Injectable({ - providedIn: 'root' + providedIn: 'root' }) export class LoginService implements CanActivate { - private pathPermissions = [ // Minimum level needed for the specified paths - {path: 'materials', permission: 'dev'}, - {path: 'templates', permission: 'dev'}, - {path: 'changelog', permission: 'dev'}, - {path: 'users', permission: 'admin'} - ]; - readonly levels = [ // All user levels in ascending permissions order - 'predict', - 'read', - 'write', - 'dev', - 'admin' - ]; - // Returns true or false depending on whether the user fulfills the minimum level - isLevel: {[level: string]: boolean} = {}; - hasPrediction = false; // True if user has prediction models specified - userId = ''; + private pathPermissions = [ // Minimum level needed for the specified paths + {path: 'materials', permission: 'dev'}, + {path: 'templates', permission: 'dev'}, + {path: 'changelog', permission: 'dev'}, + {path: 'users', permission: 'admin'} + ]; + readonly levels = [ // All user levels in ascending permissions order + 'predict', + 'read', + 'write', + 'dev', + 'admin' + ]; + // Returns true or false depending on whether the user fulfills the minimum level + isLevel: {[level: string]: boolean} = {}; + hasPrediction = false; // True if user has prediction models specified + userId = ''; - private loggedIn; + private loggedIn; - constructor( - private api: ApiService, - private storage: LocalStorageService, - private router: Router, - private d: DataService - ) { + constructor( + private api: ApiService, + private storage: LocalStorageService, + private router: Router, + private d: DataService + ) { - } + } - login(username = '', password = '') { - return new Promise(resolve => { - if (username !== '' || password !== '') { // Some credentials given - let credentials: string[]; - const credentialString: string = this.storage.get('basicAuth'); - if (credentialString) { // Found stored credentials - credentials = atob(credentialString).split(':'); - } - else { - credentials = ['', '']; - } - if (username !== '' && password !== '') { // All credentials given - this.storage.set('basicAuth', btoa(username + ':' + password)); - } - else if (username !== '') { // Username given - this.storage.set('basicAuth', btoa(username + ':' + credentials[1])); - } - else if (password !== '') { // Password given - this.storage.set('basicAuth', btoa(credentials[0] + ':' + password)); - } - } - this.api.get('/authorized', (data: any, error) => { - if (!error) { - if (data.status === 'Authorization successful') { - this.loggedIn = true; - this.levels.forEach(level => { - this.isLevel[level] = this.levels.indexOf(data.level) >= this.levels.indexOf(level); - if (this.isLevel.dev) { // Set hasPrediction - this.hasPrediction = true; - } - else { - this.d.load('modelGroups', () => { - this.hasPrediction = this.d.arr.modelGroups.length > 0; - }); - } - }); - this.userId = data.user_id; - resolve(true); - } else { - this.loggedIn = false; - this.storage.remove('basicAuth'); - resolve(false); - } - } else { - this.loggedIn = false; - this.storage.remove('basicAuth'); - resolve(false); - } - }); - }); - } + login(username = '', password = '') { + return new Promise(resolve => { + if (username !== '' || password !== '') { // Some credentials given + let credentials: string[]; + const credentialString: string = this.storage.get('basicAuth'); + if (credentialString) { // Found stored credentials + credentials = atob(credentialString).split(':'); + } + else { + credentials = ['', '']; + } + if (username !== '' && password !== '') { // All credentials given + this.storage.set('basicAuth', btoa(username + ':' + password)); + } + else if (username !== '') { // Username given + this.storage.set('basicAuth', btoa(username + ':' + credentials[1])); + } + else if (password !== '') { // Password given + this.storage.set('basicAuth', btoa(credentials[0] + ':' + password)); + } + } + this.api.get('/authorized', (data: any, error) => { + if (!error) { + if (data.status === 'Authorization successful') { + this.loggedIn = true; + this.levels.forEach(level => { + this.isLevel[level] = this.levels.indexOf(data.level) >= this.levels.indexOf(level); + if (this.isLevel.dev) { // Set hasPrediction + this.hasPrediction = true; + } + else { + this.d.load('modelGroups', () => { + this.hasPrediction = this.d.arr.modelGroups.length > 0; + }); + } + }); + this.userId = data.user_id; + resolve(true); + } else { + this.loggedIn = false; + this.storage.remove('basicAuth'); + resolve(false); + } + } else { + this.loggedIn = false; + this.storage.remove('basicAuth'); + resolve(false); + } + }); + }); + } - logout() { - this.storage.remove('basicAuth'); - this.loggedIn = false; - this.levels.forEach(level => { - this.isLevel[level] = false; - }); - this.hasPrediction = false; - } + logout() { + this.storage.remove('basicAuth'); + this.loggedIn = false; + this.levels.forEach(level => { + this.isLevel[level] = false; + }); + this.hasPrediction = false; + } - // CanActivate for Angular routing - canActivate(route: ActivatedRouteSnapshot = null, state: RouterStateSnapshot = null): Observable { - return new Observable(observer => { - new Promise(resolve => { - if (this.loggedIn === undefined) { - this.login().then(res => { - resolve(res); - }); - } - else { - resolve(this.loggedIn); - } - }).then(res => { - const pathPermission = this.pathPermissions.find(e => e.path.indexOf(route.url[0].path) >= 0); - // Check if level is permitted for path - const ok = res && (!pathPermission || this.isLevel[pathPermission.permission]); - observer.next(ok); - observer.complete(); - if (!ok) { - this.router.navigate(['/']); - } - }); - }); - } + // CanActivate for Angular routing + canActivate(route: ActivatedRouteSnapshot = null, state: RouterStateSnapshot = null): Observable { + return new Observable(observer => { + new Promise(resolve => { + if (this.loggedIn === undefined) { + this.login().then(res => { + resolve(res); + }); + } + else { + resolve(this.loggedIn); + } + }).then(res => { + const pathPermission = this.pathPermissions.find(e => e.path.indexOf(route.url[0].path) >= 0); + // Check if level is permitted for path + const ok = res && (!pathPermission || this.isLevel[pathPermission.permission]); + observer.next(ok); + observer.complete(); + if (!ok) { + this.router.navigate(['/']); + } + }); + }); + } - get isLoggedIn() { - return this.loggedIn; - } + get isLoggedIn() { + return this.loggedIn; + } - get username() { - return atob(this.storage.get('basicAuth')).split(':')[0]; - } + get username() { + return atob(this.storage.get('basicAuth')).split(':')[0]; + } } diff --git a/src/app/services/validation.service.ts b/src/app/services/validation.service.ts index 352cead..141c1b1 100644 --- a/src/app/services/validation.service.ts +++ b/src/app/services/validation.service.ts @@ -3,183 +3,183 @@ import Joi from '@hapi/joi'; import {AbstractControl} from '@angular/forms'; @Injectable({ - providedIn: 'root' + providedIn: 'root' }) export class ValidationService { - private vUsername = Joi.string() - .lowercase() - .pattern(new RegExp('^[a-z0-9-_.]+$')) - .min(1) - .max(128); + private vUsername = Joi.string() + .lowercase() + .pattern(new RegExp('^[a-z0-9-_.]+$')) + .min(1) + .max(128); - private vPassword = Joi.string() - .min(8) - .max(128); + private vPassword = Joi.string() + .min(8) + .max(128); - constructor() { } + constructor() { } - generate(method, args) { // Generate a Validator function - return (control: AbstractControl): {[key: string]: any} | null => { - let ok; - let error; - if (args) { - ({ok, error} = this[method](control.value, ...args)); - } - else { - ({ok, error} = this[method](control.value)); - } - return ok ? null : { failure: error }; - }; - } + generate(method, args) { // Generate a Validator function + return (control: AbstractControl): {[key: string]: any} | null => { + let ok; + let error; + if (args) { + ({ok, error} = this[method](control.value, ...args)); + } + else { + ({ok, error} = this[method](control.value)); + } + return ok ? null : { failure: error }; + }; + } - username(data) { - const {ignore, error} = this.vUsername.validate(data); - if (error) { - return {ok: false, error: 'username must only contain a-z0-9-_.'}; - } - return {ok: true, error: ''}; - } + username(data) { + const {ignore, error} = this.vUsername.validate(data); + if (error) { + return {ok: false, error: 'username must only contain a-z0-9-_.'}; + } + return {ok: true, error: ''}; + } - password(data) { - const {ignore, error} = this.vPassword.validate(data); - if (error) { - return {ok: false, error: 'password must have at least 8 characters'}; - } - return {ok: true, error: ''}; - } + password(data) { + const {ignore, error} = this.vPassword.validate(data); + if (error) { + return {ok: false, error: 'password must have at least 8 characters'}; + } + return {ok: true, error: ''}; + } - string(data, option = null) { - let validator = Joi.string().max(128).allow('', true, false); - let errorMsg = 'must contain max 128 characters'; - if (option === 'alphanum') { - validator = validator.alphanum(); - errorMsg = 'must contain max 128 alphanumerical characters'; - } - const {ignore, error} = validator.validate(data); - if (error) { - return {ok: false, error: errorMsg}; - } - return {ok: true, error: ''}; - } + string(data, option = null) { + let validator = Joi.string().max(128).allow('', true, false); + let errorMsg = 'must contain max 128 characters'; + if (option === 'alphanum') { + validator = validator.alphanum(); + errorMsg = 'must contain max 128 alphanumerical characters'; + } + const {ignore, error} = validator.validate(data); + if (error) { + return {ok: false, error: errorMsg}; + } + return {ok: true, error: ''}; + } - stringOf(data, list) { // String must be in list - const {ignore, error} = Joi.string().allow('').valid(...list.map(e => e.toString())).validate(data); - if (error) { - return {ok: false, error: 'must be one of ' + list.join(', ')}; - } - return {ok: true, error: ''}; - } + stringOf(data, list) { // String must be in list + const {ignore, error} = Joi.string().allow('').valid(...list.map(e => e.toString())).validate(data); + if (error) { + return {ok: false, error: 'must be one of ' + list.join(', ')}; + } + return {ok: true, error: ''}; + } - stringNin(data, list) { // String must not be in list - const {ignore, error} = Joi.string().invalid(...list).validate(data); - if (error) { - return {ok: false, error: 'value not allowed'}; - } - return {ok: true, error: ''}; - } + stringNin(data, list) { // String must not be in list + const {ignore, error} = Joi.string().invalid(...list).validate(data); + if (error) { + return {ok: false, error: 'value not allowed'}; + } + return {ok: true, error: ''}; + } - stringLength(data, length) { // String with maximum length - const {ignore, error} = Joi.string().max(length).allow('').validate(data); - if (error) { - return {ok: false, error: 'must contain max ' + length + ' characters'}; - } - return {ok: true, error: ''}; - } + stringLength(data, length) { // String with maximum length + const {ignore, error} = Joi.string().max(length).allow('').validate(data); + if (error) { + return {ok: false, error: 'must contain max ' + length + ' characters'}; + } + return {ok: true, error: ''}; + } - minMax(data, min, max) { // Number between min and max - const {ignore, error} = Joi.number().allow('').min(min).max(max).validate(data); - if (error) { - return {ok: false, error: `must be between ${min} and ${max}`}; - } - return {ok: true, error: ''}; - } + minMax(data, min, max) { // Number between min and max + const {ignore, error} = Joi.number().allow('').min(min).max(max).validate(data); + if (error) { + return {ok: false, error: `must be between ${min} and ${max}`}; + } + return {ok: true, error: ''}; + } - min(data, min) { // Number above min - const {ignore, error} = Joi.number().allow('').min(min).validate(data); - if (error) { - return {ok: false, error: `must not be below ${min}`}; - } - return {ok: true, error: ''}; - } + min(data, min) { // Number above min + const {ignore, error} = Joi.number().allow('').min(min).validate(data); + if (error) { + return {ok: false, error: `must not be below ${min}`}; + } + return {ok: true, error: ''}; + } - max(data, max) { // Number below max - const {ignore, error} = Joi.number().allow('').max(max).validate(data); - if (error) { - return {ok: false, error: `must not be above ${max}`}; - } - return {ok: true, error: ''}; - } + max(data, max) { // Number below max + const {ignore, error} = Joi.number().allow('').max(max).validate(data); + if (error) { + return {ok: false, error: `must not be above ${max}`}; + } + return {ok: true, error: ''}; + } - url(data) { - const {ignore, error} = Joi.string().uri().validate(data); - if (error) { - return {ok: false, error: `must be a valid URL`}; - } - return {ok: true, error: ''}; - } + url(data) { + const {ignore, error} = Joi.string().uri().validate(data); + if (error) { + return {ok: false, error: `must be a valid URL`}; + } + return {ok: true, error: ''}; + } - unique(data, list) { - const {ignore, error} = Joi.string().allow('').invalid(...list.map(e => e.toString())).validate(data); - if (error) { - return {ok: false, error: `values must be unique`}; - } - return {ok: true, error: ''}; - } + unique(data, list) { + const {ignore, error} = Joi.string().allow('').invalid(...list.map(e => e.toString())).validate(data); + if (error) { + return {ok: false, error: `values must be unique`}; + } + return {ok: true, error: ''}; + } - equal(data, compare) { - if (data !== compare) { - return {ok: false, error: `must be equal`}; - } - return {ok: true, error: ''}; - } + equal(data, compare) { + if (data !== compare) { + return {ok: false, error: `must be equal`}; + } + return {ok: true, error: ''}; + } - parameterName(data) { - const {ignore, error} = Joi.string() - .max(128) - .invalid('condition_template', 'material_template') - .allow('') - .pattern(/^[^.]+$/) - .required() - .messages({'string.pattern.base': 'name must not contain a dot'}) - .validate(data); - if (error) { - return {ok: false, error: error.details[0].message}; - } - return {ok: true, error: ''}; - } + parameterName(data) { + const {ignore, error} = Joi.string() + .max(128) + .invalid('condition_template', 'material_template') + .allow('') + .pattern(/^[^.]+$/) + .required() + .messages({'string.pattern.base': 'name must not contain a dot'}) + .validate(data); + if (error) { + return {ok: false, error: error.details[0].message}; + } + return {ok: true, error: ''}; + } - parameterRange(data) { - if (data) { - try { - const {ignore, error} = Joi.object({ - values: Joi.array() - .min(1), + parameterRange(data) { + if (data) { + try { + const {ignore, error} = Joi.object({ + values: Joi.array() + .min(1), - min: Joi.number(), + min: Joi.number(), - max: Joi.number(), + max: Joi.number(), - type: Joi.string() - .valid('string', 'number', 'boolean', 'array'), + type: Joi.string() + .valid('string', 'number', 'boolean', 'array'), - required: Joi.boolean() - }) - .oxor('values', 'min') - .oxor('values', 'max') - .required() - .validate(JSON.parse(data)); - if (error) { - return {ok: false, error: error.details[0].message}; - } - } - catch (e) { - return {ok: false, error: `no valid JSON`}; - } - return {ok: true, error: ''}; - } - else { - return {ok: false, error: `no valid value`}; - } - } + required: Joi.boolean() + }) + .oxor('values', 'min') + .oxor('values', 'max') + .required() + .validate(JSON.parse(data)); + if (error) { + return {ok: false, error: error.details[0].message}; + } + } + catch (e) { + return {ok: false, error: `no valid JSON`}; + } + return {ok: true, error: ''}; + } + else { + return {ok: false, error: `no valid value`}; + } + } } diff --git a/src/app/settings/settings.component.html b/src/app/settings/settings.component.html index 18b2cd3..db361c2 100644 --- a/src/app/settings/settings.component.html +++ b/src/app/settings/settings.component.html @@ -1,46 +1,46 @@
- - {{nameInput.errors.failure}} - Cannot be empty - - - Invalid email - Cannot be empty - - - {{locationInput.errors.failure}} - Cannot be empty - - - - {{deviceInput.errors.failure}} - - - - Save change - - {{messageUser}} + + {{nameInput.errors.failure}} + Cannot be empty + + + Invalid email + Cannot be empty + + + {{locationInput.errors.failure}} + Cannot be empty + + + + {{deviceInput.errors.failure}} + + + + Save change + + {{messageUser}}

Change password

- - {{passAInput.errors.failure}} - - - {{passBInput.errors.failure}} - - - {{messagePass}} + + {{passAInput.errors.failure}} + + + {{passBInput.errors.failure}} + + + {{messagePass}}
diff --git a/src/app/settings/settings.component.scss b/src/app/settings/settings.component.scss index 3651625..8ade1cc 100644 --- a/src/app/settings/settings.component.scss +++ b/src/app/settings/settings.component.scss @@ -1,7 +1,7 @@ .pass-heading { - margin-top: 40px; + margin-top: 40px; } .message { - margin-left: 20px; + margin-left: 20px; } diff --git a/src/app/settings/settings.component.ts b/src/app/settings/settings.component.ts index e3cd49e..cb838b2 100644 --- a/src/app/settings/settings.component.ts +++ b/src/app/settings/settings.component.ts @@ -6,63 +6,63 @@ import {LoginService} from '../services/login.service'; @Component({ - selector: 'app-settings', - templateUrl: './settings.component.html', - styleUrls: ['./settings.component.scss'] + selector: 'app-settings', + templateUrl: './settings.component.html', + styleUrls: ['./settings.component.scss'] }) export class SettingsComponent implements OnInit { - user: UserModel = new UserModel(); // User to edit - password = ''; // New password - messageUser = ''; // Messages for user and pass part - messagePass = ''; + user: UserModel = new UserModel(); // User to edit + password = ''; // New password + messageUser = ''; // Messages for user and pass part + messagePass = ''; - constructor( - private api: ApiService, - private login: LoginService, - private router: Router - ) { } + constructor( + private api: ApiService, + private login: LoginService, + private router: Router + ) { } - ngOnInit(): void { - this.api.get('/user', data => { - this.user.deserialize(data); - }); - } + ngOnInit(): void { + this.api.get('/user', data => { + this.user.deserialize(data); + }); + } - saveUser() { - this.api.put('/user', this.user.sendFormat(), (data, err) => { - if (err) { - this.messageUser = err.error.status; - } - else { // Login with new credentials - this.login.login(data.name).then(res => { - if (res) { - this.router.navigate(['/samples']); - } - else { - this.messageUser = 'request not successful, try again'; - } - }); - } - }); - } + saveUser() { + this.api.put('/user', this.user.sendFormat(), (data, err) => { + if (err) { + this.messageUser = err.error.status; + } + else { // Login with new credentials + this.login.login(data.name).then(res => { + if (res) { + this.router.navigate(['/samples']); + } + else { + this.messageUser = 'request not successful, try again'; + } + }); + } + }); + } - savePass() { - this.api.put('/user', {pass: this.password}, (ignore, err) => { - if (err) { - this.messagePass = err.error.status; - } - else { // Login with new credentials - this.login.login('', this.password).then(res => { - if (res) { - this.router.navigate(['/samples']); - } - else { - this.messagePass = 'request not successful, try again'; - } - }); - } - }); - } + savePass() { + this.api.put('/user', {pass: this.password}, (ignore, err) => { + if (err) { + this.messagePass = err.error.status; + } + else { // Login with new credentials + this.login.login('', this.password).then(res => { + if (res) { + this.router.navigate(['/samples']); + } + else { + this.messagePass = 'request not successful, try again'; + } + }); + } + }); + } } diff --git a/src/app/size.pipe.spec.ts b/src/app/size.pipe.spec.ts index 5a0238e..326eea7 100644 --- a/src/app/size.pipe.spec.ts +++ b/src/app/size.pipe.spec.ts @@ -1,8 +1,8 @@ import { SizePipe } from './size.pipe'; describe('SizePipe', () => { - it('create an instance', () => { - const pipe = new SizePipe(); - expect(pipe).toBeTruthy(); - }); + it('create an instance', () => { + const pipe = new SizePipe(); + expect(pipe).toBeTruthy(); + }); }); diff --git a/src/app/size.pipe.ts b/src/app/size.pipe.ts index ea3cbe8..a79eaf8 100644 --- a/src/app/size.pipe.ts +++ b/src/app/size.pipe.ts @@ -1,16 +1,16 @@ import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ - name: 'size' + name: 'size' }) export class SizePipe implements PipeTransform { - transform(value: number, exp: string): string { - const divide = ['', 'k', 'M', 'G', 'T'].indexOf(exp); - for (let i = 0; i < divide; i ++) { - value = value / 1024; - } - return `${value.toFixed(2)} ${exp}B`; - } + transform(value: number, exp: string): string { + const divide = ['', 'k', 'M', 'G', 'T'].indexOf(exp); + for (let i = 0; i < divide; i ++) { + value = value / 1024; + } + return `${value.toFixed(2)} ${exp}B`; + } } diff --git a/src/app/templates/templates.component.html b/src/app/templates/templates.component.html index f7b286c..295c92e 100644 --- a/src/app/templates/templates.component.html +++ b/src/app/templates/templates.component.html @@ -1,66 +1,66 @@ - - - + [(ngModel)]="collection" (ngModelChange)="loadTemplates()"> + + + New template
-
-
Name
-
Version
-
+
+
Name
+
Version
+
- -
-
{{group.name}}
-
{{group.version}}
-
-
-
- -
{{template.name}}
-
{{template.version}}
-
{{template.parameters | parameters}}
-
-
-
-
- - {{supplierInput.errors.failure}} - - - - - {{parameterName.errors.failure}} - - - {{parameterRange.errors.failure}} - - - -
- - Edit template - - - Save template - -
-
-
-
-
+ +
+
{{group.name}}
+
{{group.version}}
+
+
+
+ +
{{template.name}}
+
{{template.version}}
+
{{template.parameters | parameters}}
+
+
+
+
+ + {{supplierInput.errors.failure}} + + + + + {{parameterName.errors.failure}} + + + {{parameterRange.errors.failure}} + + + +
+ + Edit template + + + Save template + +
+
+
+
+
diff --git a/src/app/templates/templates.component.scss b/src/app/templates/templates.component.scss index ede71e8..b85387c 100644 --- a/src/app/templates/templates.component.scss +++ b/src/app/templates/templates.component.scss @@ -2,39 +2,39 @@ .list { - .row { - display: grid; - grid-template-columns: 1fr 4fr; - border-bottom: 1px solid $color-gray-mercury; - overflow: hidden; + .row { + display: grid; + grid-template-columns: 1fr 4fr; + border-bottom: 1px solid $color-gray-mercury; + overflow: hidden; - & > div { - padding: 8px 5px; + & > div { + padding: 8px 5px; - &.header { - font-weight: bold; - } + &.header { + font-weight: bold; + } - &.details { - grid-column: span 2; - display: grid; - grid-template-columns: 1fr 1fr 3fr; - background: $color-gray-alabaster; + &.details { + grid-column: span 2; + display: grid; + grid-template-columns: 1fr 1fr 3fr; + background: $color-gray-alabaster; - .template-actions { - grid-column: span 3; - margin-top: 10px; + .template-actions { + grid-column: span 3; + margin-top: 10px; - .parameters { - display: grid; - grid-template-columns: 1fr 2fr; - } + .parameters { + display: grid; + grid-template-columns: 1fr 2fr; + } - rb-icon-button[icon="save"] { - float: right; - } - } - } - } - } + rb-icon-button[icon="save"] { + float: right; + } + } + } + } + } } diff --git a/src/app/templates/templates.component.ts b/src/app/templates/templates.component.ts index d435e5b..c4aa069 100644 --- a/src/app/templates/templates.component.ts +++ b/src/app/templates/templates.component.ts @@ -8,132 +8,132 @@ import omit from 'lodash/omit'; import {DataService} from '../services/data.service'; @Component({ - selector: 'app-templates', - templateUrl: './templates.component.html', - styleUrls: ['./templates.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})) - ]) - ] - ) - ] + selector: 'app-templates', + templateUrl: './templates.component.html', + styleUrls: ['./templates.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 TemplatesComponent implements OnInit { - collection = 'measurement'; // Collection to view - templates: TemplateModel[] = []; // All templates of current collection - templateGroups: {[first_id: string]: TemplateModel[]} = {}; // Templates grouped by first_id - templateEdit: {[first_id: string]: TemplateModel} = {}; // Latest template of each first_id for editing - groupsView: { // Grouped templates - first_id: string, - name: string, - version: number, - expanded: boolean, - edit: boolean, - entries: TemplateModel[] - }[] = []; + collection = 'measurement'; // Collection to view + templates: TemplateModel[] = []; // All templates of current collection + templateGroups: {[first_id: string]: TemplateModel[]} = {}; // Templates grouped by first_id + templateEdit: {[first_id: string]: TemplateModel} = {}; // Latest template of each first_id for editing + groupsView: { // Grouped templates + first_id: string, + name: string, + version: number, + expanded: boolean, + edit: boolean, + entries: TemplateModel[] + }[] = []; - constructor( - private api: ApiService, - private validate: ValidationService, - public d: DataService - ) { } + constructor( + private api: ApiService, + private validate: ValidationService, + public d: DataService + ) { } - ngOnInit(): void { - this.loadTemplates(); - } + ngOnInit(): void { + this.loadTemplates(); + } - loadTemplates() { - this.d.load(this.collection + 'Templates', () => { - this.templates = this.d.arr[this.collection + 'Templates']; - this.templateFormat(); - }); - } + loadTemplates() { + this.d.load(this.collection + 'Templates', () => { + this.templates = this.d.arr[this.collection + 'Templates']; + this.templateFormat(); + }); + } - templateFormat() { - this.templateGroups = {}; - this.templateEdit = {}; - this.templates.forEach(template => { // Group templates - if (this.templateGroups[template.first_id]) { - this.templateGroups[template.first_id].push(template); - } - else { - this.templateGroups[template.first_id] = [template]; - } - }); - Object.keys(this.templateGroups).forEach(id => { - this.templateGroups[id] = this.templateGroups[id].sort((a, b) => a.version - b.version); - this.templateEdit[id] = cloneDeep(this.templateGroups[id][this.templateGroups[id].length - 1]); - this.templateEdit[id].parameters = this.templateEdit[id].parameters - .map(e => {e.rangeString = JSON.stringify(e.range, null, 2); return e; }); - }); - this.groupsView = Object.values(this.templateGroups) - .map(e => ({ - first_id: e[e.length - 1].first_id, - name: e[e.length - 1].name, - version: e[e.length - 1].version, - expanded: false, - edit: false, - entries: e - })); - } + templateFormat() { + this.templateGroups = {}; + this.templateEdit = {}; + this.templates.forEach(template => { // Group templates + if (this.templateGroups[template.first_id]) { + this.templateGroups[template.first_id].push(template); + } + else { + this.templateGroups[template.first_id] = [template]; + } + }); + Object.keys(this.templateGroups).forEach(id => { + this.templateGroups[id] = this.templateGroups[id].sort((a, b) => a.version - b.version); + this.templateEdit[id] = cloneDeep(this.templateGroups[id][this.templateGroups[id].length - 1]); + this.templateEdit[id].parameters = this.templateEdit[id].parameters + .map(e => {e.rangeString = JSON.stringify(e.range, null, 2); return e; }); + }); + this.groupsView = Object.values(this.templateGroups) + .map(e => ({ + first_id: e[e.length - 1].first_id, + name: e[e.length - 1].name, + version: e[e.length - 1].version, + expanded: false, + edit: false, + entries: e + })); + } - saveTemplate(first_id) { - const template = cloneDeep(this.templateEdit[first_id]); - template.parameters = template.parameters.filter(e => e.name !== ''); - let valid = true; - valid = valid && this.validate.string(template.name).ok; - template.parameters.forEach(parameter => { - valid = valid && this.validate.parameterName(parameter.name).ok; - valid = valid && this.validate.parameterRange(parameter.rangeString).ok; - if (valid) { - parameter.range = JSON.parse(parameter.rangeString); - } - }); - if (valid) { - const sendData = {name: template.name, parameters: template.parameters.map(e => omit(e, ['rangeString']))}; - if (first_id === 'null') { // New template - this.api.post(`/template/${this.collection}/new`, sendData, () => { - delete this.d.arr[this.collection + 'Templates']; - this.d.load(this.collection + 'Templates', () => { - this.templates = this.d.arr[this.collection + 'Templates']; - this.templateFormat(); - }); - }); - } - else { - this.api.put(`/template/${this.collection}/${template.first_id}`, sendData, () => { - delete this.d.arr[this.collection + 'Templates']; - this.d.load(this.collection + 'Templates', () => { - this.templates = this.d.arr[this.collection + 'Templates']; - this.templateFormat(); - }); - }); - } - } - } + saveTemplate(first_id) { + const template = cloneDeep(this.templateEdit[first_id]); + template.parameters = template.parameters.filter(e => e.name !== ''); + let valid = true; + valid = valid && this.validate.string(template.name).ok; + template.parameters.forEach(parameter => { + valid = valid && this.validate.parameterName(parameter.name).ok; + valid = valid && this.validate.parameterRange(parameter.rangeString).ok; + if (valid) { + parameter.range = JSON.parse(parameter.rangeString); + } + }); + if (valid) { + const sendData = {name: template.name, parameters: template.parameters.map(e => omit(e, ['rangeString']))}; + if (first_id === 'null') { // New template + this.api.post(`/template/${this.collection}/new`, sendData, () => { + delete this.d.arr[this.collection + 'Templates']; + this.d.load(this.collection + 'Templates', () => { + this.templates = this.d.arr[this.collection + 'Templates']; + this.templateFormat(); + }); + }); + } + else { + this.api.put(`/template/${this.collection}/${template.first_id}`, sendData, () => { + delete this.d.arr[this.collection + 'Templates']; + this.d.load(this.collection + 'Templates', () => { + this.templates = this.d.arr[this.collection + 'Templates']; + this.templateFormat(); + }); + }); + } + } + } - newTemplate() { - if (!this.templateEdit.null) { - const template = new TemplateModel(); - template.name = 'new template'; - this.groupsView.push({ - first_id: 'null', - name: 'new template', - version: 0, - expanded: true, - edit: true, - entries: [template] - }); - this.templateEdit.null = new TemplateModel(); - } - } + newTemplate() { + if (!this.templateEdit.null) { + const template = new TemplateModel(); + template.name = 'new template'; + this.groupsView.push({ + first_id: 'null', + name: 'new template', + version: 0, + expanded: true, + edit: true, + entries: [template] + }); + this.templateEdit.null = new TemplateModel(); + } + } } diff --git a/src/app/users/users.component.html b/src/app/users/users.component.html index 50ce9ff..6bb6e32 100644 --- a/src/app/users/users.component.html +++ b/src/app/users/users.component.html @@ -1,170 +1,170 @@ New user
- - {{nameInput.errors.failure}} - Cannot be empty - - - Invalid email - Cannot be empty - - - - Cannot be empty - - - {{locationInput.errors.failure}} - Cannot be empty - - - - {{deviceInput.errors.failure}} - - - - - - - - - {{passAInput.errors.failure}} - - - {{passBInput.errors.failure}} - - - Save user - + + {{nameInput.errors.failure}} + Cannot be empty + + + Invalid email + Cannot be empty + + + + Cannot be empty + + + {{locationInput.errors.failure}} + Cannot be empty + + + + {{deviceInput.errors.failure}} + + + + + + + + + {{passAInput.errors.failure}} + + + {{passBInput.errors.failure}} + + + Save user +
- - Name - Email - Level - Location - Device - Models - - + + Name + Email + Level + Location + Device + Models + + - - - {{user.name}} - {{user.email}} - {{user.level}} - {{user.location}} - {{user.devices}} - - - {{(i > 0 ? ', ' : '') + modelIds[model]}} - - - - - - - - {{nameInput.errors.failure}} - Cannot be empty - - - - - Invalid email - Cannot be empty - - - - - - - - - - {{locationInput.errors.failure}} - Cannot be empty - - - - - - - - - - - - - - - - - - Delete - - - - Do you really want to delete this user? - - - - Save - - - - + + + {{user.name}} + {{user.email}} + {{user.level}} + {{user.location}} + {{user.devices}} + + + {{(i > 0 ? ', ' : '') + modelIds[model]}} + + + + + + + + {{nameInput.errors.failure}} + Cannot be empty + + + + + Invalid email + Cannot be empty + + + + + + + + + + {{locationInput.errors.failure}} + Cannot be empty + + + + + + + + + + + + + + + + + + Delete + + + + Do you really want to delete this user? + + + + Save + + + + -   Deleted users - - - - Name - Email - Level - Location - Device - Models - - +   Deleted users + + + + Name + Email + Level + Location + Device + Models + + - - {{user.name}} - {{user.email}} - {{user.level}} - {{user.location}} - {{user.devices}} - - - {{(i > 0 ? ', ' : '') + modelIds[model]}} - - - - - - + + {{user.name}} + {{user.email}} + {{user.level}} + {{user.location}} + {{user.devices}} + + + {{(i > 0 ? ', ' : '') + modelIds[model]}} + + + + + + - - - - + + + + diff --git a/src/app/users/users.component.scss b/src/app/users/users.component.scss index 6a6e5b8..d11b9f1 100644 --- a/src/app/users/users.component.scss +++ b/src/app/users/users.component.scss @@ -1,16 +1,16 @@ ::ng-deep td .error-messages { - position: absolute; + position: absolute; } td:last-child rb-icon-button { - width: 100px; - float: left; + width: 100px; + float: left; - ::ng-deep button { - width: 100%; - } + ::ng-deep button { + width: 100%; + } } rb-form-select { - min-width: 150px; + min-width: 150px; } diff --git a/src/app/users/users.component.ts b/src/app/users/users.component.ts index 8fad021..e3556c9 100644 --- a/src/app/users/users.component.ts +++ b/src/app/users/users.component.ts @@ -7,78 +7,78 @@ import {DataService} from '../services/data.service'; @Component({ - selector: 'app-users', - templateUrl: './users.component.html', - styleUrls: ['./users.component.scss'] + selector: 'app-users', + templateUrl: './users.component.html', + styleUrls: ['./users.component.scss'] }) export class UsersComponent implements OnInit { - users: UserModel[] = []; // All active users - deletedUsers: UserModel[] = []; // All deleted users - newUser: UserModel | null = null; // Data of new user - newUserPass = ''; // Password of new user - modelSelect: {id: string, name: string}[] = []; // List of all models for selection - modelIds: {[id: string]: string} = {}; // All models by id + users: UserModel[] = []; // All active users + deletedUsers: UserModel[] = []; // All deleted users + newUser: UserModel | null = null; // Data of new user + newUserPass = ''; // Password of new user + modelSelect: {id: string, name: string}[] = []; // List of all models for selection + modelIds: {[id: string]: string} = {}; // All models by id - constructor( - private api: ApiService, - public login: LoginService, - private modal: ModalService, - public d: DataService - ) { } + constructor( + private api: ApiService, + public login: LoginService, + private modal: ModalService, + public d: DataService + ) { } - ngOnInit(): void { - this.api.get('/users', data => { - this.users = data.map(e => new UserModel().deserialize(e)).filter(e => e.status !== 'deleted'); - this.deletedUsers = data.map(e => new UserModel().deserialize(e)).filter(e => e.status === 'deleted'); - }); - this.d.load('modelGroups', () => { - this.d.arr.modelGroups.forEach(group => { - this.modelSelect.push(...group.models.map(e => ({id: e._id, name: `${group.group} - ${e.name}`}))); - }); - this.modelIds = this.modelSelect.reduce((s, e) => {s[e.id] = e.name; return s; }, {}); - }); - } + ngOnInit(): void { + this.api.get('/users', data => { + this.users = data.map(e => new UserModel().deserialize(e)).filter(e => e.status !== 'deleted'); + this.deletedUsers = data.map(e => new UserModel().deserialize(e)).filter(e => e.status === 'deleted'); + }); + this.d.load('modelGroups', () => { + this.d.arr.modelGroups.forEach(group => { + this.modelSelect.push(...group.models.map(e => ({id: e._id, name: `${group.group} - ${e.name}`}))); + }); + this.modelIds = this.modelSelect.reduce((s, e) => {s[e.id] = e.name; return s; }, {}); + }); + } - saveUser(user: UserModel) { - user.models = user.models.filter(e => e !== ''); - this.api.put('/user/' + user.origName, user.sendFormat('admin'), data => { - user.deserialize(data); - user.edit = false; - }); - } + saveUser(user: UserModel) { + user.models = user.models.filter(e => e !== ''); + this.api.put('/user/' + user.origName, user.sendFormat('admin'), data => { + user.deserialize(data); + user.edit = false; + }); + } - saveNewUser() { - this.newUser.models = this.newUser.models.filter(e => e !== ''); - this.api.post('/user/new', {...this.newUser.sendFormat('admin'), pass: this.newUserPass}, data => { - this.newUser = null; - this.users.push(new UserModel().deserialize(data)); - this.newUserPass = ''; - }); - } + saveNewUser() { + this.newUser.models = this.newUser.models.filter(e => e !== ''); + this.api.post('/user/new', {...this.newUser.sendFormat('admin'), pass: this.newUserPass}, data => { + this.newUser = null; + this.users.push(new UserModel().deserialize(data)); + this.newUserPass = ''; + }); + } - addNewUser() { - this.newUser = this.newUser ? null : new UserModel(); - } + addNewUser() { + this.newUser = this.newUser ? null : new UserModel(); + } - deleteConfirm(modal, user) { - this.modal.open(modal).then(result => { - if (result) { - this.api.delete('/user/' + user.name, () => { - user.status = 'deleted'; - user.edit = false; - this.deletedUsers.push(user); - this.users.splice(this.users.findIndex(e => e.name === user.name), 1); - }); - } - }); - } + deleteConfirm(modal, user) { + this.modal.open(modal).then(result => { + if (result) { + this.api.delete('/user/' + user.name, () => { + user.status = 'deleted'; + user.edit = false; + this.deletedUsers.push(user); + this.users.splice(this.users.findIndex(e => e.name === user.name), 1); + }); + } + }); + } - restoreUser(user) { - this.api.put('/user/restore/' + user.name, {}, () => { - user.status = 'new'; - this.users.push(user); - this.deletedUsers.splice(this.deletedUsers.findIndex(e => e.name === user.name), 1); - }); - } + restoreUser(user) { + this.api.put('/user/restore/' + user.name, {}, () => { + user.status = 'new'; + this.users.push(user); + this.deletedUsers.splice(this.deletedUsers.findIndex(e => e.name === user.name), 1); + }); + } } diff --git a/src/app/validate.directive.ts b/src/app/validate.directive.ts index 7b8a2ec..3e3ff7a 100644 --- a/src/app/validate.directive.ts +++ b/src/app/validate.directive.ts @@ -3,26 +3,26 @@ import {AbstractControl, NG_VALIDATORS} from '@angular/forms'; import {ValidationService} from './services/validation.service'; @Directive({ - selector: '[appValidate]', - providers: [{provide: NG_VALIDATORS, useExisting: ValidateDirective, multi: true}] + selector: '[appValidate]', + providers: [{provide: NG_VALIDATORS, useExisting: ValidateDirective, multi: true}] }) export class ValidateDirective { - @Input('appValidate') method: string; - @Input('appValidateArgs') args: Array; + @Input('appValidate') method: string; + @Input('appValidateArgs') args: Array; - constructor( - private validation: ValidationService - ) { } + constructor( + private validation: ValidationService + ) { } - validate(control: AbstractControl): {[key: string]: any} | null { - let ok; - let error; - if (this.args) { - ({ok, error} = this.validation[this.method](control.value, ...this.args)); - } - else { - ({ok, error} = this.validation[this.method](control.value)); - } - return ok ? null : { failure: error }; - } + validate(control: AbstractControl): {[key: string]: any} | null { + let ok; + let error; + if (this.args) { + ({ok, error} = this.validation[this.method](control.value, ...this.args)); + } + else { + ({ok, error} = this.validation[this.method](control.value)); + } + return ok ? null : { failure: error }; + } } diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index 3612073..8b7239d 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -1,3 +1,3 @@ export const environment = { - production: true + production: true }; diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 7b4f817..c5a517a 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -3,7 +3,7 @@ // The list of file replacements can be found in `angular.json`. export const environment = { - production: false + production: false }; /*