Change the first character of all end-of-line comments to upper case
This commit is contained in:
		@@ -38,7 +38,7 @@ const routes: Routes = [
 | 
				
			|||||||
  {path: 'documentation/database', component: DocumentationDatabaseComponent},
 | 
					  {path: 'documentation/database', component: DocumentationDatabaseComponent},
 | 
				
			||||||
  {path: 'documentation/models', component: DocumentationModelsComponent},
 | 
					  {path: 'documentation/models', component: DocumentationModelsComponent},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // if not authenticated
 | 
					  // If not authenticated
 | 
				
			||||||
  { path: '**', redirectTo: '' }
 | 
					  { path: '**', redirectTo: '' }
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,8 +14,8 @@ import {DataService} from './services/data.service';
 | 
				
			|||||||
})
 | 
					})
 | 
				
			||||||
export class AppComponent implements OnInit{
 | 
					export class AppComponent implements OnInit{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  bugReport = {do: '', work: ''};  // data from bug report inputs
 | 
					  bugReport = {do: '', work: ''};  // Data from bug report inputs
 | 
				
			||||||
  isDocumentation = false;         // true if user is on documentation pages
 | 
					  isDocumentation = false;         // True if user is on documentation pages
 | 
				
			||||||
  devMode = false;
 | 
					  devMode = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
@@ -35,9 +35,9 @@ export class AppComponent implements OnInit{
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ngOnInit() {
 | 
					  ngOnInit() {
 | 
				
			||||||
    // try to log in user
 | 
					    // Try to log in user
 | 
				
			||||||
    this.login.login().then(res => {
 | 
					    this.login.login().then(res => {
 | 
				
			||||||
      // return to home page if log failed, except when on documentation pages
 | 
					      // Return to home page if log failed, except when on documentation pages
 | 
				
			||||||
      if (!res && !(/\/documentation/.test(this.router.url))) {
 | 
					      if (!res && !(/\/documentation/.test(this.router.url))) {
 | 
				
			||||||
        this.router.navigate(['/']);
 | 
					        this.router.navigate(['/']);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,10 +10,10 @@ import {ModalService} from '@inst-iot/bosch-angular-ui-components';
 | 
				
			|||||||
})
 | 
					})
 | 
				
			||||||
export class ChangelogComponent implements OnInit {
 | 
					export class ChangelogComponent implements OnInit {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  timestamp = new Date();  // time from date input
 | 
					  timestamp = new Date();  // Time from date input
 | 
				
			||||||
  pageSize = 25;
 | 
					  pageSize = 25;
 | 
				
			||||||
  changelog: ChangelogModel[] = [];
 | 
					  changelog: ChangelogModel[] = [];
 | 
				
			||||||
  modalDetail = 0;        // index of changelog element to show details of
 | 
					  modalDetail = 0;        // Index of changelog element to show details of
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    private api: ApiService,
 | 
					    private api: ApiService,
 | 
				
			||||||
@@ -24,21 +24,21 @@ export class ChangelogComponent implements OnInit {
 | 
				
			|||||||
    this.loadChangelog();
 | 
					    this.loadChangelog();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  loadChangelog(page = 0) {  // load changelog with page no relative to current page
 | 
					  loadChangelog(page = 0) {  // Load changelog with page no relative to current page
 | 
				
			||||||
    this.api.get<ChangelogModel[]>(`/changelog/${
 | 
					    this.api.get<ChangelogModel[]>(`/changelog/${
 | 
				
			||||||
      page > 0 ? this.changelog[0]._id :  // use id if no new date was given
 | 
					      page > 0 ? this.changelog[0]._id :  // Use id if no new date was given
 | 
				
			||||||
      Math.floor(new Date(
 | 
					      Math.floor(new Date(
 | 
				
			||||||
        new Date(this.timestamp).getTime() - new Date(this.timestamp).getTimezoneOffset() * 60000  // adjust timezone
 | 
					        new Date(this.timestamp).getTime() - new Date(this.timestamp).getTimezoneOffset() * 60000  // Adjust timezone
 | 
				
			||||||
      ).getTime() / 1000).toString(16) + '0000000000000000'  // id from time
 | 
					      ).getTime() / 1000).toString(16) + '0000000000000000'  // Id from time
 | 
				
			||||||
    }/${page}/${this.pageSize}`, data => {
 | 
					    }/${page}/${this.pageSize}`, data => {
 | 
				
			||||||
      this.changelog = data.map(e => new ChangelogModel().deserialize(e));
 | 
					      this.changelog = data.map(e => new ChangelogModel().deserialize(e));
 | 
				
			||||||
      if (page) {  // adjust date picker to new first element when user clicked on next page
 | 
					      if (page) {  // Adjust date picker to new first element when user clicked on next page
 | 
				
			||||||
        this.timestamp = new Date(this.changelog[0].date);
 | 
					        this.timestamp = new Date(this.changelog[0].date);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // show details of a changelog element with reference to needed modal
 | 
					  // Show details of a changelog element with reference to needed modal
 | 
				
			||||||
  showDetails(i: number, modal: TemplateRef<any>) {
 | 
					  showDetails(i: number, modal: TemplateRef<any>) {
 | 
				
			||||||
    this.modalDetail = i;
 | 
					    this.modalDetail = i;
 | 
				
			||||||
    this.modal.open(modal).then(() => {});
 | 
					    this.modal.open(modal).then(() => {});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,8 +7,8 @@ import { Component, OnInit } from '@angular/core';
 | 
				
			|||||||
})
 | 
					})
 | 
				
			||||||
export class ErrorComponent implements OnInit {
 | 
					export class ErrorComponent implements OnInit {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  message = '';            // main error message
 | 
					  message = '';            // Main error message
 | 
				
			||||||
  details: string[] = [];  // array of error detail paragraphs
 | 
					  details: string[] = [];  // Array of error detail paragraphs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor() { }
 | 
					  constructor() { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,8 +12,8 @@ import {LoginService} from '../services/login.service';
 | 
				
			|||||||
})
 | 
					})
 | 
				
			||||||
export class HelpComponent implements OnInit {
 | 
					export class HelpComponent implements OnInit {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  content: HelpModel = new HelpModel().deserialize({text: null, level: 'none'});  // help content
 | 
					  content: HelpModel = new HelpModel().deserialize({text: null, level: 'none'});  // Help content
 | 
				
			||||||
  edit = false;        // set true to change to edit mode
 | 
					  edit = false;        // Set true to change to edit mode
 | 
				
			||||||
  private route = '';  // URIComponent encoded route which serves as a key to fetch the help document
 | 
					  private route = '';  // URIComponent encoded route which serves as a key to fetch the help document
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
@@ -24,10 +24,10 @@ export class HelpComponent implements OnInit {
 | 
				
			|||||||
  ) { }
 | 
					  ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ngOnInit(): void {
 | 
					  ngOnInit(): void {
 | 
				
			||||||
    // remove ids from path
 | 
					    // Remove ids from path
 | 
				
			||||||
    this.route = encodeURIComponent(this.router.url.replace(/\/[0-9a-f]{24}/, ''));
 | 
					    this.route = encodeURIComponent(this.router.url.replace(/\/[0-9a-f]{24}/, ''));
 | 
				
			||||||
    this.api.get<HelpModel>('/help/' + this.route, (data, err) => {
 | 
					    this.api.get<HelpModel>('/help/' + this.route, (data, err) => {
 | 
				
			||||||
      if (!err) {  // content was found
 | 
					      if (!err) {  // Content was found
 | 
				
			||||||
        this.content = new HelpModel().deserialize(data);
 | 
					        this.content = new HelpModel().deserialize(data);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      else {
 | 
					      else {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -45,7 +45,7 @@ export class HomeComponent implements OnInit {
 | 
				
			|||||||
    for (var i = 0; i < data.length; i++) {
 | 
					    for (var i = 0; i < data.length; i++) {
 | 
				
			||||||
      temp.push({ id: data[i], count: 0, active: false });
 | 
					      temp.push({ id: data[i], count: 0, active: false });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this.keys = temp; // invoke update in rb-multiselect
 | 
					    this.keys = temp; // Invoke update in rb-multiselect
 | 
				
			||||||
    this.initChart();
 | 
					    this.initChart();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Only neccesary if keys get preselected
 | 
					    // Only neccesary if keys get preselected
 | 
				
			||||||
@@ -60,7 +60,7 @@ export class HomeComponent implements OnInit {
 | 
				
			|||||||
    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 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 = '';
 | 
					    let temp = '';
 | 
				
			||||||
    this.keys.forEach(key => {
 | 
					    this.keys.forEach(key => {
 | 
				
			||||||
      temp += key.active ? '%22' + key.id.split("%").join("%25") + '%22%2C' : ""; // replace split().join() with replaceAll() 
 | 
					      temp += key.active ? '%22' + key.id.split("%").join("%25") + '%22%2C' : ""; // Replace split().join() with replaceAll() 
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    if (temp === '') {
 | 
					    if (temp === '') {
 | 
				
			||||||
      this.countSamples('');
 | 
					      this.countSamples('');
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,13 +7,13 @@ import {AfterViewInit, Component, ElementRef, Input, OnInit, ViewChild} from '@a
 | 
				
			|||||||
})
 | 
					})
 | 
				
			||||||
export class ImgMagnifierComponent implements OnInit, AfterViewInit {
 | 
					export class ImgMagnifierComponent implements OnInit, AfterViewInit {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Input() src: string;   // image source
 | 
					  @Input() src: string;   // Image source
 | 
				
			||||||
  @Input() zoom: number;  // zoom level
 | 
					  @Input() zoom: number;  // Zoom level
 | 
				
			||||||
  @Input() magnifierSize: {width: number, height: number};  // size of the magnifier
 | 
					  @Input() magnifierSize: {width: number, height: number};  // Size of the magnifier
 | 
				
			||||||
  @ViewChild('mainImg') mainImg: ElementRef;
 | 
					  @ViewChild('mainImg') mainImg: ElementRef;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  backgroundSize;
 | 
					  backgroundSize;
 | 
				
			||||||
  magnifierPos = {x: 0, y: 0};  // position of the magnifier
 | 
					  magnifierPos = {x: 0, y: 0};  // Position of the magnifier
 | 
				
			||||||
  showMagnifier = false;
 | 
					  showMagnifier = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
@@ -29,7 +29,7 @@ export class ImgMagnifierComponent implements OnInit, AfterViewInit {
 | 
				
			|||||||
    }, 1);
 | 
					    }, 1);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  calcPos(event) {  // calculate the current magnifier position
 | 
					  calcPos(event) {  // Calculate the current magnifier position
 | 
				
			||||||
    const img = this.mainImg.nativeElement.getBoundingClientRect();
 | 
					    const img = this.mainImg.nativeElement.getBoundingClientRect();
 | 
				
			||||||
    this.magnifierPos.x = Math.min(
 | 
					    this.magnifierPos.x = Math.min(
 | 
				
			||||||
      img.width - this.magnifierSize.width,
 | 
					      img.width - this.magnifierSize.width,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,11 +12,11 @@ import {ApiService} from '../services/api.service';
 | 
				
			|||||||
})
 | 
					})
 | 
				
			||||||
export class LoginComponent implements OnInit {
 | 
					export class LoginComponent implements OnInit {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  username = '';  // credentials
 | 
					  username = '';  // Credentials
 | 
				
			||||||
  password = '';
 | 
					  password = '';
 | 
				
			||||||
  email = '';
 | 
					  email = '';
 | 
				
			||||||
  message = '';  // message below login fields
 | 
					  message = '';  // Message below login fields
 | 
				
			||||||
  passreset = false;  // to toggle between normal login and password reset form
 | 
					  passreset = false;  // To toggle between normal login and password reset form
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @ViewChild('loginForm') loginForm;
 | 
					  @ViewChild('loginForm') loginForm;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -32,7 +32,7 @@ export class LoginComponent implements OnInit {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  userLogin() {
 | 
					  userLogin() {
 | 
				
			||||||
    if (this.passreset) {  // reset password
 | 
					    if (this.passreset) {  // Reset password
 | 
				
			||||||
      this.api.post('/user/passreset', {name: this.username, email: this.email}, (data, err) => {
 | 
					      this.api.post('/user/passreset', {name: this.username, email: this.email}, (data, err) => {
 | 
				
			||||||
        if (err) {
 | 
					        if (err) {
 | 
				
			||||||
          this.message = 'Could not find a valid user';
 | 
					          this.message = 'Could not find a valid user';
 | 
				
			||||||
@@ -49,7 +49,7 @@ export class LoginComponent implements OnInit {
 | 
				
			|||||||
          if (this.login.isLevel.read) {
 | 
					          if (this.login.isLevel.read) {
 | 
				
			||||||
            this.router.navigate(['/samples']);
 | 
					            this.router.navigate(['/samples']);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          else {  // navigate prediction users to prediction as they cannot access samples
 | 
					          else {  // Navigate prediction users to prediction as they cannot access samples
 | 
				
			||||||
            this.router.navigate(['/prediction']);
 | 
					            this.router.navigate(['/prediction']);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,12 +19,12 @@ export class MaterialComponent implements OnInit, AfterContentChecked {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @ViewChild('materialForm') materialForm: NgForm;
 | 
					  @ViewChild('materialForm') materialForm: NgForm;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  material: MaterialModel;       // material to edit
 | 
					  material: MaterialModel;       // Material to edit
 | 
				
			||||||
  materialNames: string[] = [];  // all other material names for unique validation
 | 
					  materialNames: string[] = [];  // All other material names for unique validation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  modalText = {list: '', suggestion: ''};  // modal for group and supplier correction
 | 
					  modalText = {list: '', suggestion: ''};  // Modal for group and supplier correction
 | 
				
			||||||
  loading = 0;                             // number of loading instances
 | 
					  loading = 0;                             // Number of loading instances
 | 
				
			||||||
  checkFormAfterInit = true;               // revalidate all fields on the next AfterContentChecked
 | 
					  checkFormAfterInit = true;               // Revalidate all fields on the next AfterContentChecked
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    private api: ApiService,
 | 
					    private api: ApiService,
 | 
				
			||||||
@@ -42,7 +42,7 @@ export class MaterialComponent implements OnInit, AfterContentChecked {
 | 
				
			|||||||
      this.material = new MaterialModel().deserialize(data);
 | 
					      this.material = new MaterialModel().deserialize(data);
 | 
				
			||||||
      this.loading--;
 | 
					      this.loading--;
 | 
				
			||||||
      this.d.load('materials', () => {
 | 
					      this.d.load('materials', () => {
 | 
				
			||||||
        // filter out name of the edited material as it can stay the same
 | 
					        // 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.materialNames = this.d.arr.materials.map(e => e.name).filter(e => e !== this.material.name);
 | 
				
			||||||
        this.loading--;
 | 
					        this.loading--;
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
@@ -59,14 +59,14 @@ export class MaterialComponent implements OnInit, AfterContentChecked {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ngAfterContentChecked() {
 | 
					  ngAfterContentChecked() {
 | 
				
			||||||
    // attach validators
 | 
					    // Attach validators
 | 
				
			||||||
    if (this.materialForm && this.material.properties.material_template) {  // material template is set
 | 
					    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.d.id.materialTemplates[this.material.properties.material_template].parameters.forEach((parameter, i) => {
 | 
				
			||||||
        this.attachValidator(this.materialForm, 'materialParameter' + i, parameter.range);
 | 
					        this.attachValidator(this.materialForm, 'materialParameter' + i, parameter.range);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // revalidate
 | 
					    // Revalidate
 | 
				
			||||||
    if (this.checkFormAfterInit && this.materialForm !== undefined && this.materialForm.form.get('propertiesSelect')) {
 | 
					    if (this.checkFormAfterInit && this.materialForm !== undefined && this.materialForm.form.get('propertiesSelect')) {
 | 
				
			||||||
      this.checkFormAfterInit = false;
 | 
					      this.checkFormAfterInit = false;
 | 
				
			||||||
      Object.keys(this.materialForm.form.controls).forEach(field => {
 | 
					      Object.keys(this.materialForm.form.controls).forEach(field => {
 | 
				
			||||||
@@ -75,7 +75,7 @@ export class MaterialComponent implements OnInit, AfterContentChecked {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // attach validators specified in range to input with name
 | 
					  // Attach validators specified in range to input with name
 | 
				
			||||||
  attachValidator(form, name: string, range: {[prop: string]: any}) {
 | 
					  attachValidator(form, name: string, range: {[prop: string]: any}) {
 | 
				
			||||||
    if (form && form.form.get(name)) {
 | 
					    if (form && form.form.get(name)) {
 | 
				
			||||||
      const validators = [];
 | 
					      const validators = [];
 | 
				
			||||||
@@ -100,7 +100,7 @@ export class MaterialComponent implements OnInit, AfterContentChecked {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  materialSave() {
 | 
					  materialSave() {
 | 
				
			||||||
    this.api.put('/material/' + this.material._id, this.material.sendFormat(), () => {
 | 
					    this.api.put('/material/' + this.material._id, this.material.sendFormat(), () => {
 | 
				
			||||||
      delete this.d.arr.materials;  // reload materials
 | 
					      delete this.d.arr.materials;  // Reload materials
 | 
				
			||||||
      this.d.load('materials');
 | 
					      this.d.load('materials');
 | 
				
			||||||
      this.router.navigate(['/materials']);
 | 
					      this.router.navigate(['/materials']);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
@@ -110,7 +110,7 @@ export class MaterialComponent implements OnInit, AfterContentChecked {
 | 
				
			|||||||
    this.modal.open(modal).then(result => {
 | 
					    this.modal.open(modal).then(result => {
 | 
				
			||||||
      if (result) {
 | 
					      if (result) {
 | 
				
			||||||
        this.api.delete('/material/' + this.material._id, (ignore, error) => {
 | 
					        this.api.delete('/material/' + this.material._id, (ignore, error) => {
 | 
				
			||||||
          if (error) {  // material cannot be deleted as it is still referenced by active samples
 | 
					          if (error) {  // Material cannot be deleted as it is still referenced by active samples
 | 
				
			||||||
            const modalRef = this.modal.openComponent(ErrorComponent);
 | 
					            const modalRef = this.modal.openComponent(ErrorComponent);
 | 
				
			||||||
            modalRef.instance.message = 'Cannot delete material as it is still in use!';
 | 
					            modalRef.instance.message = 'Cannot delete material as it is still in use!';
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
@@ -123,16 +123,16 @@ export class MaterialComponent implements OnInit, AfterContentChecked {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  checkTypo(event, list, mKey, modal: TemplateRef<any>) {
 | 
					  checkTypo(event, list, mKey, modal: TemplateRef<any>) {
 | 
				
			||||||
    // user did not click on suggestion and entry is not in list
 | 
					    // User did not click on suggestion and entry is not in list
 | 
				
			||||||
    if (!(event.relatedTarget && (event.relatedTarget.className.indexOf('rb-dropdown-item') >= 0 ||
 | 
					    if (!(event.relatedTarget && (event.relatedTarget.className.indexOf('rb-dropdown-item') >= 0 ||
 | 
				
			||||||
      event.relatedTarget.className.indexOf('close-btn rb-btn rb-passive-link') >= 0)) &&
 | 
					      event.relatedTarget.className.indexOf('close-btn rb-btn rb-passive-link') >= 0)) &&
 | 
				
			||||||
      this.d.arr[list].indexOf(this.material[mKey]) < 0) {
 | 
					      this.d.arr[list].indexOf(this.material[mKey]) < 0) {
 | 
				
			||||||
      this.modalText.list = mKey;
 | 
					      this.modalText.list = mKey;
 | 
				
			||||||
      this.modalText.suggestion = this.d.arr[list]  // find possible entry from list
 | 
					      this.modalText.suggestion = this.d.arr[list]  // Find possible entry from list
 | 
				
			||||||
        .map(e => ({v: e, s: strCompare.sorensenDice(e, this.material[mKey])}))
 | 
					        .map(e => ({v: e, s: strCompare.sorensenDice(e, this.material[mKey])}))
 | 
				
			||||||
        .sort((a, b) => b.s - a.s)[0].v;
 | 
					        .sort((a, b) => b.s - a.s)[0].v;
 | 
				
			||||||
      this.modal.open(modal).then(result => {
 | 
					      this.modal.open(modal).then(result => {
 | 
				
			||||||
        if (result) {  // use suggestion
 | 
					        if (result) {  // Use suggestion
 | 
				
			||||||
          this.material[mKey] = this.modalText.suggestion;
 | 
					          this.material[mKey] = this.modalText.suggestion;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,13 +12,13 @@ import {ModalService} from '@inst-iot/bosch-angular-ui-components';
 | 
				
			|||||||
})
 | 
					})
 | 
				
			||||||
export class MaterialsComponent implements OnInit {
 | 
					export class MaterialsComponent implements OnInit {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  materials: MaterialModel[] = [];                    // all materials
 | 
					  materials: MaterialModel[] = [];                    // All materials
 | 
				
			||||||
  templateKeys: {key: string, label: string}[] = [];  // material template keys
 | 
					  templateKeys: {key: string, label: string}[] = [];  // Material template keys
 | 
				
			||||||
  materialStatus = {validated: true, new: true, deleted: false};  // material statuses to show
 | 
					  materialStatus = {validated: true, new: true, deleted: false};  // Material statuses to show
 | 
				
			||||||
  materialSearch = '';   // material name search string
 | 
					  materialSearch = '';   // Material name search string
 | 
				
			||||||
  sampleSelect = false;  // set to true to show checkboxes for validation
 | 
					  sampleSelect = false;  // Set to true to show checkboxes for validation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  page = 1;  // page settings
 | 
					  page = 1;  // Page settings
 | 
				
			||||||
  pages = 0;
 | 
					  pages = 0;
 | 
				
			||||||
  pageSize = 25;
 | 
					  pageSize = 25;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -36,7 +36,7 @@ export class MaterialsComponent implements OnInit {
 | 
				
			|||||||
          this.templateKeys.push({key: parameter.name, label: `${this.ucFirst(template.name)} ${parameter.name}`});
 | 
					          this.templateKeys.push({key: parameter.name, label: `${this.ucFirst(template.name)} ${parameter.name}`});
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      // filter out duplicates
 | 
					      // Filter out duplicates
 | 
				
			||||||
      this.templateKeys = this.templateKeys.filter((e, i, a) => !a.slice(0, i).find(el => el.key === e.key));
 | 
					      this.templateKeys = this.templateKeys.filter((e, i, a) => !a.slice(0, i).find(el => el.key === e.key));
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -51,7 +51,7 @@ export class MaterialsComponent implements OnInit {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  validate() {
 | 
					  validate() {
 | 
				
			||||||
    if (this.sampleSelect) {  // selection was done do actual validation
 | 
					    if (this.sampleSelect) {  // Selection was done do actual validation
 | 
				
			||||||
      this.materials.forEach(sample => {
 | 
					      this.materials.forEach(sample => {
 | 
				
			||||||
        if (sample.selected) {
 | 
					        if (sample.selected) {
 | 
				
			||||||
          this.api.put('/material/validate/' + sample._id);
 | 
					          this.api.put('/material/validate/' + sample._id);
 | 
				
			||||||
@@ -60,12 +60,12 @@ export class MaterialsComponent implements OnInit {
 | 
				
			|||||||
      this.loadMaterials();
 | 
					      this.loadMaterials();
 | 
				
			||||||
      this.sampleSelect = false;
 | 
					      this.sampleSelect = false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    else {  // activate validation mode
 | 
					    else {  // Activate validation mode
 | 
				
			||||||
      this.sampleSelect = true;
 | 
					      this.sampleSelect = true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  selectAll(event) {  // toggle selection for all items except deleted ones
 | 
					  selectAll(event) {  // Toggle selection for all items except deleted ones
 | 
				
			||||||
    this.materials.forEach(material => {
 | 
					    this.materials.forEach(material => {
 | 
				
			||||||
      if (material.status !== 'deleted') {
 | 
					      if (material.status !== 'deleted') {
 | 
				
			||||||
        material.selected = event.target.checked;
 | 
					        material.selected = event.target.checked;
 | 
				
			||||||
@@ -86,11 +86,11 @@ export class MaterialsComponent implements OnInit {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ucFirst(string) {  // convert first character of string to uppercase
 | 
					  ucFirst(string) {  // Convert first character of string to uppercase
 | 
				
			||||||
    return string[0].toUpperCase() + string.slice(1);
 | 
					    return string[0].toUpperCase() + string.slice(1);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  materialFilter(ms) {  // filter function for material names
 | 
					  materialFilter(ms) {  // Filter function for material names
 | 
				
			||||||
    return e => e.name.indexOf(ms) >= 0;
 | 
					    return e => e.name.indexOf(ms) >= 0;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,12 +13,12 @@ import omit from 'lodash/omit';
 | 
				
			|||||||
})
 | 
					})
 | 
				
			||||||
export class ModelTemplatesComponent implements OnInit {
 | 
					export class ModelTemplatesComponent implements OnInit {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  newModel = false;    // display new model dialog
 | 
					  newModel = false;    // Display new model dialog
 | 
				
			||||||
  modelGroup = '';     // group of the edited model
 | 
					  modelGroup = '';     // Group of the edited model
 | 
				
			||||||
  oldModelGroup = '';  // group of the edited model before editing started
 | 
					  oldModelGroup = '';  // Group of the edited model before editing started
 | 
				
			||||||
  oldModelName = '';   // name of the edited model before editing started
 | 
					  oldModelName = '';   // Name of the edited model before editing started
 | 
				
			||||||
  model = new ModelItemModel().models[0];  // edited model
 | 
					  model = new ModelItemModel().models[0];  // Edited model
 | 
				
			||||||
  groups = [];         // all model group names
 | 
					  groups = [];         // All model group names
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    private api: ApiService,
 | 
					    private api: ApiService,
 | 
				
			||||||
@@ -40,12 +40,12 @@ export class ModelTemplatesComponent implements OnInit {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  saveModel() {
 | 
					  saveModel() {
 | 
				
			||||||
    // group was changed, delete model in old group
 | 
					    // Group was changed, delete model in old group
 | 
				
			||||||
    if (this.oldModelGroup !== '' && this.modelGroup !== this.oldModelGroup) {
 | 
					    if (this.oldModelGroup !== '' && this.modelGroup !== this.oldModelGroup) {
 | 
				
			||||||
      this.delete(null, this.oldModelName, this.oldModelGroup);
 | 
					      this.delete(null, this.oldModelName, this.oldModelGroup);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this.api.post('/model/' + this.modelGroup, omit(this.model, '_id'), () => {
 | 
					    this.api.post('/model/' + this.modelGroup, omit(this.model, '_id'), () => {
 | 
				
			||||||
      this.newModel = false;  // reset model edit parameters
 | 
					      this.newModel = false;  // Reset model edit parameters
 | 
				
			||||||
      this.loadGroups();
 | 
					      this.loadGroups();
 | 
				
			||||||
      this.modelGroup = '';
 | 
					      this.modelGroup = '';
 | 
				
			||||||
      this.oldModelGroup = '';
 | 
					      this.oldModelGroup = '';
 | 
				
			||||||
@@ -56,7 +56,7 @@ export class ModelTemplatesComponent implements OnInit {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  delete(modal, name, group = null) {
 | 
					  delete(modal, name, group = null) {
 | 
				
			||||||
    new Promise(resolve => {
 | 
					    new Promise(resolve => {
 | 
				
			||||||
      if (modal) {  // if modal was given, wait for result
 | 
					      if (modal) {  // If modal was given, wait for result
 | 
				
			||||||
        this.modal.open(modal).then(result => {
 | 
					        this.modal.open(modal).then(result => {
 | 
				
			||||||
          resolve(result);
 | 
					          resolve(result);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
@@ -66,12 +66,12 @@ export class ModelTemplatesComponent implements OnInit {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    }).then(res => {
 | 
					    }).then(res => {
 | 
				
			||||||
      if (res) {
 | 
					      if (res) {
 | 
				
			||||||
        if (group) {  // delete model group if given
 | 
					        if (group) {  // Delete model group if given
 | 
				
			||||||
          this.api.delete(`/model/${group}/${name}`, () => {
 | 
					          this.api.delete(`/model/${group}/${name}`, () => {
 | 
				
			||||||
            this.loadGroups();
 | 
					            this.loadGroups();
 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else {  // delete model file
 | 
					        else {  // Delete model file
 | 
				
			||||||
          this.api.delete(`/model/file/${name}`, () => {
 | 
					          this.api.delete(`/model/file/${name}`, () => {
 | 
				
			||||||
            this.d.arr.modelFiles.splice(this.d.arr.modelFiles.findIndex(e => e.name === name), 1);
 | 
					            this.d.arr.modelFiles.splice(this.d.arr.modelFiles.findIndex(e => e.name === name), 1);
 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,11 +30,11 @@ import * as FileSaver from 'file-saver'
 | 
				
			|||||||
})
 | 
					})
 | 
				
			||||||
export class PredictionComponent implements OnInit {
 | 
					export class PredictionComponent implements OnInit {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  result: { predictions: any[], mean: any[] };  // prediction result from python container
 | 
					  result: { predictions: any[], mean: any[] };  // Prediction result from python container
 | 
				
			||||||
  loading = false;
 | 
					  loading = false;
 | 
				
			||||||
  activeGroup: ModelItemModel = new ModelItemModel();
 | 
					  activeGroup: ModelItemModel = new ModelItemModel();
 | 
				
			||||||
  activeModelIndex = 0;
 | 
					  activeModelIndex = 0;
 | 
				
			||||||
  // if true, spectra belong to different samples, otherwise multiple spectra from the same sample are given
 | 
					  // If true, spectra belong to different samples, otherwise multiple spectra from the same sample are given
 | 
				
			||||||
  multipleSamples = false;
 | 
					  multipleSamples = false;
 | 
				
			||||||
  spectrumNames: string[] = [];
 | 
					  spectrumNames: string[] = [];
 | 
				
			||||||
  spectrum: string[][] = [[]];
 | 
					  spectrum: string[][] = [[]];
 | 
				
			||||||
@@ -84,16 +84,16 @@ export class PredictionComponent implements OnInit {
 | 
				
			|||||||
      if (files.hasOwnProperty(i)) {
 | 
					      if (files.hasOwnProperty(i)) {
 | 
				
			||||||
        const fileReader = new FileReader();
 | 
					        const fileReader = new FileReader();
 | 
				
			||||||
        fileReader.onload = () => {
 | 
					        fileReader.onload = () => {
 | 
				
			||||||
          // parse to database spectrum representation
 | 
					          // Parse to database spectrum representation
 | 
				
			||||||
          this.spectrum = fileReader.result.toString().split('\r\n').map(e => e.split(',').map(el => parseFloat(el)))
 | 
					          this.spectrum = fileReader.result.toString().split('\r\n').map(e => e.split(',').map(el => parseFloat(el)))
 | 
				
			||||||
            .filter(el => el.length === 2) as any;
 | 
					            .filter(el => el.length === 2) as any;
 | 
				
			||||||
          // flatten to format needed for prediction
 | 
					          // Flatten to format needed for prediction
 | 
				
			||||||
          this.flattenedSpectra[i] = {labels: this.spectrum.map(e => e[0]), values: this.spectrum.map(e => e[1])};
 | 
					          this.flattenedSpectra[i] = {labels: this.spectrum.map(e => e[0]), values: this.spectrum.map(e => e[1])};
 | 
				
			||||||
          // add to chart
 | 
					          // Add to chart
 | 
				
			||||||
          this.chart[i] = cloneDeep(this.chartInit);
 | 
					          this.chart[i] = cloneDeep(this.chartInit);
 | 
				
			||||||
          this.chart[i].data = this.spectrum.map(e => ({x: parseFloat(e[0]), y: parseFloat(e[1])}));
 | 
					          this.chart[i].data = this.spectrum.map(e => ({x: parseFloat(e[0]), y: parseFloat(e[1])}));
 | 
				
			||||||
          load --;
 | 
					          load --;
 | 
				
			||||||
          if (load <= 0) {  // all loaded
 | 
					          if (load <= 0) {  // All loaded
 | 
				
			||||||
            this.loadPrediction();
 | 
					            this.loadPrediction();
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
@@ -105,18 +105,18 @@ export class PredictionComponent implements OnInit {
 | 
				
			|||||||
  loadPrediction() {
 | 
					  loadPrediction() {
 | 
				
			||||||
    this.loading = true;
 | 
					    this.loading = true;
 | 
				
			||||||
    this.api.post<any>(this.activeGroup.models[this.activeModelIndex].url, this.flattenedSpectra, data => {
 | 
					    this.api.post<any>(this.activeGroup.models[this.activeModelIndex].url, this.flattenedSpectra, data => {
 | 
				
			||||||
      let tmp = Object.entries(omit(data, ['mean', 'std', 'label']))  // form: [[label, [{value, color}]]]
 | 
					      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}]]
 | 
					          .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 = {
 | 
					      this.result = {
 | 
				
			||||||
        predictions: tmp[0].map((ignore, columnIndex) => tmp.map(row => row[columnIndex])),  // transpose tmp
 | 
					        predictions: tmp[0].map((ignore, columnIndex) => tmp.map(row => row[columnIndex])),  // Transpose tmp
 | 
				
			||||||
        mean: Object.entries(data.mean)
 | 
					        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}]
 | 
					          .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;
 | 
					      this.loading = false;
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  groupChange(index) {  // group was changed
 | 
					  groupChange(index) {  // Group was changed
 | 
				
			||||||
    this.activeGroup = this.d.arr.modelGroups[index];
 | 
					    this.activeGroup = this.d.arr.modelGroups[index];
 | 
				
			||||||
    this.activeModelIndex = 0;
 | 
					    this.activeModelIndex = 0;
 | 
				
			||||||
    this.result = undefined;
 | 
					    this.result = undefined;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,7 +10,7 @@ export class ArrayInputHelperService {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  constructor() { }
 | 
					  constructor() { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  values(id: string) {  // observable which returns new values as they come for subscribed id
 | 
					  values(id: string) {  // Observable which returns new values as they come for subscribed id
 | 
				
			||||||
    return new Observable<{index: number, value: any}>(observer => {
 | 
					    return new Observable<{index: number, value: any}>(observer => {
 | 
				
			||||||
      this.com.subscribe(data => {
 | 
					      this.com.subscribe(data => {
 | 
				
			||||||
        if (data.id === id) {
 | 
					        if (data.id === id) {
 | 
				
			||||||
@@ -20,7 +20,7 @@ export class ArrayInputHelperService {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  newValue(id: string, index: number, value: any) {  // set new value
 | 
					  newValue(id: string, index: number, value: any) {  // Set new value
 | 
				
			||||||
    this.com.next({id, index, value});
 | 
					    this.com.next({id, index, value});
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,8 +14,8 @@ import cloneDeep from 'lodash/cloneDeep';
 | 
				
			|||||||
import {ArrayInputHelperService} from './array-input-helper.service';
 | 
					import {ArrayInputHelperService} from './array-input-helper.service';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Directive({  // directive for template and input values
 | 
					@Directive({  // Directive for template and input values
 | 
				
			||||||
  // tslint:disable-next-line:directive-selector
 | 
					  // Tslint:disable-next-line:directive-selector
 | 
				
			||||||
  selector: '[rbArrayInputItem]'
 | 
					  selector: '[rbArrayInputItem]'
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
export class RbArrayInputItemDirective {
 | 
					export class RbArrayInputItemDirective {
 | 
				
			||||||
@@ -23,8 +23,8 @@ export class RbArrayInputItemDirective {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Directive({  // directive for change detection
 | 
					@Directive({  // Directive for change detection
 | 
				
			||||||
  // tslint:disable-next-line:directive-selector
 | 
					  // Tslint:disable-next-line:directive-selector
 | 
				
			||||||
  selector: '[rbArrayInputListener]'
 | 
					  selector: '[rbArrayInputListener]'
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
export class RbArrayInputListenerDirective {
 | 
					export class RbArrayInputListenerDirective {
 | 
				
			||||||
@@ -37,14 +37,14 @@ export class RbArrayInputListenerDirective {
 | 
				
			|||||||
  ) { }
 | 
					  ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @HostListener('ngModelChange', ['$event'])
 | 
					  @HostListener('ngModelChange', ['$event'])
 | 
				
			||||||
  onChange(event) {  // emit new value
 | 
					  onChange(event) {  // Emit new value
 | 
				
			||||||
    this.helperService.newValue(this.rbArrayInputListener, this.index, event);
 | 
					    this.helperService.newValue(this.rbArrayInputListener, this.index, event);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Component({
 | 
					@Component({
 | 
				
			||||||
  // tslint:disable-next-line:component-selector
 | 
					  // Tslint:disable-next-line:component-selector
 | 
				
			||||||
  selector: 'rb-array-input',
 | 
					  selector: 'rb-array-input',
 | 
				
			||||||
  templateUrl: './rb-array-input.component.html',
 | 
					  templateUrl: './rb-array-input.component.html',
 | 
				
			||||||
  styleUrls: ['./rb-array-input.component.scss'],
 | 
					  styleUrls: ['./rb-array-input.component.scss'],
 | 
				
			||||||
@@ -52,7 +52,7 @@ export class RbArrayInputListenerDirective {
 | 
				
			|||||||
})
 | 
					})
 | 
				
			||||||
export class RbArrayInputComponent implements ControlValueAccessor, OnInit, AfterViewInit {
 | 
					export class RbArrayInputComponent implements ControlValueAccessor, OnInit, AfterViewInit {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  pushTemplate: any = '';  // array element template
 | 
					  pushTemplate: any = '';  // Array element template
 | 
				
			||||||
  @Input('pushTemplate') set _pushTemplate(value) {
 | 
					  @Input('pushTemplate') set _pushTemplate(value) {
 | 
				
			||||||
    this.pushTemplate = value;
 | 
					    this.pushTemplate = value;
 | 
				
			||||||
    if (this.values.length) {
 | 
					    if (this.values.length) {
 | 
				
			||||||
@@ -64,7 +64,7 @@ export class RbArrayInputComponent implements ControlValueAccessor, OnInit, Afte
 | 
				
			|||||||
  @ContentChild(RbArrayInputItemDirective) item: RbArrayInputItemDirective;
 | 
					  @ContentChild(RbArrayInputItemDirective) item: RbArrayInputItemDirective;
 | 
				
			||||||
  @ContentChild(RbArrayInputListenerDirective) item2: RbArrayInputListenerDirective;
 | 
					  @ContentChild(RbArrayInputListenerDirective) item2: RbArrayInputListenerDirective;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  values = [];  // main array to display
 | 
					  values = [];  // Main array to display
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  onChange = (ignore?: any): void => {};
 | 
					  onChange = (ignore?: any): void => {};
 | 
				
			||||||
  onTouched = (ignore?: any): void => {};
 | 
					  onTouched = (ignore?: any): void => {};
 | 
				
			||||||
@@ -78,9 +78,9 @@ export class RbArrayInputComponent implements ControlValueAccessor, OnInit, Afte
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ngAfterViewInit() {
 | 
					  ngAfterViewInit() {
 | 
				
			||||||
    setTimeout(() => {  // needed to find reference
 | 
					    setTimeout(() => {  // Needed to find reference
 | 
				
			||||||
      this.helperService.values(this.item2.rbArrayInputListener).subscribe(data => {  // action on value change
 | 
					      this.helperService.values(this.item2.rbArrayInputListener).subscribe(data => {  // Action on value change
 | 
				
			||||||
        // assign value
 | 
					        // Assign value
 | 
				
			||||||
        if (this.pushPath) {
 | 
					        if (this.pushPath) {
 | 
				
			||||||
          this.values[data.index][this.pushPath] = data.value;
 | 
					          this.values[data.index][this.pushPath] = data.value;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -94,26 +94,26 @@ export class RbArrayInputComponent implements ControlValueAccessor, OnInit, Afte
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  updateArray() {
 | 
					  updateArray() {
 | 
				
			||||||
    let res;
 | 
					    let res;
 | 
				
			||||||
    // adjust fields if pushTemplate is specified
 | 
					    // Adjust fields if pushTemplate is specified
 | 
				
			||||||
    if (this.pushTemplate !== null) {
 | 
					    if (this.pushTemplate !== null) {
 | 
				
			||||||
      if (this.pushPath) {
 | 
					      if (this.pushPath) {
 | 
				
			||||||
        // remove last element if last two are empty
 | 
					        // Remove last element if last two are empty
 | 
				
			||||||
        if (this.values[this.values.length - 1][this.pushPath] === '' &&
 | 
					        if (this.values[this.values.length - 1][this.pushPath] === '' &&
 | 
				
			||||||
          this.values[this.values.length - 2][this.pushPath] === '') {
 | 
					          this.values[this.values.length - 2][this.pushPath] === '') {
 | 
				
			||||||
          this.values.pop();
 | 
					          this.values.pop();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        // add element if last all are filled
 | 
					        // Add element if last all are filled
 | 
				
			||||||
        else if (this.values.filter(e => e[this.pushPath] !== '').length === this.values.length) {
 | 
					        else if (this.values.filter(e => e[this.pushPath] !== '').length === this.values.length) {
 | 
				
			||||||
          this.values.push(cloneDeep(this.pushTemplate));
 | 
					          this.values.push(cloneDeep(this.pushTemplate));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        res = this.values.filter(e => e[this.pushPath] !== '');
 | 
					        res = this.values.filter(e => e[this.pushPath] !== '');
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      else {
 | 
					      else {
 | 
				
			||||||
        // remove last element if last two are empty
 | 
					        // Remove last element if last two are empty
 | 
				
			||||||
        if (this.values[this.values.length - 1] === '' && this.values[this.values.length - 2] === '') {
 | 
					        if (this.values[this.values.length - 1] === '' && this.values[this.values.length - 2] === '') {
 | 
				
			||||||
          this.values.pop();
 | 
					          this.values.pop();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else if (this.values.filter(e => e !== '').length === this.values.length) {  // add element if all are is filled
 | 
					        else if (this.values.filter(e => e !== '').length === this.values.length) {  // Add element if all are is filled
 | 
				
			||||||
          this.values.push(cloneDeep(this.pushTemplate));
 | 
					          this.values.push(cloneDeep(this.pushTemplate));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        res = this.values.filter(e => e !== '');
 | 
					        res = this.values.filter(e => e !== '');
 | 
				
			||||||
@@ -126,13 +126,13 @@ export class RbArrayInputComponent implements ControlValueAccessor, OnInit, Afte
 | 
				
			|||||||
    if (!res.length) {
 | 
					    if (!res.length) {
 | 
				
			||||||
      res = [''];
 | 
					      res = [''];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this.onChange(res);  // trigger ngModel with filled elements
 | 
					    this.onChange(res);  // Trigger ngModel with filled elements
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  writeValue(obj: any) {  // add empty value on init
 | 
					  writeValue(obj: any) {  // Add empty value on init
 | 
				
			||||||
    if (obj) {
 | 
					    if (obj) {
 | 
				
			||||||
      if (this.pushTemplate !== null) {
 | 
					      if (this.pushTemplate !== null) {
 | 
				
			||||||
        // filter out empty values
 | 
					        // Filter out empty values
 | 
				
			||||||
        if (this.pushPath) {
 | 
					        if (this.pushPath) {
 | 
				
			||||||
          this.values = [...obj.filter(e => e[this.pushPath] !== ''), cloneDeep(this.pushTemplate)];
 | 
					          this.values = [...obj.filter(e => e[this.pushPath] !== ''), cloneDeep(this.pushTemplate)];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@ import {Component, Input, OnInit} from '@angular/core';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Component({
 | 
					@Component({
 | 
				
			||||||
  // tslint:disable-next-line:component-selector
 | 
					  // Tslint:disable-next-line:component-selector
 | 
				
			||||||
  selector: 'rb-icon-button',
 | 
					  selector: 'rb-icon-button',
 | 
				
			||||||
  templateUrl: './rb-icon-button.component.html',
 | 
					  templateUrl: './rb-icon-button.component.html',
 | 
				
			||||||
  styleUrls: ['./rb-icon-button.component.scss']
 | 
					  styleUrls: ['./rb-icon-button.component.scss']
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import {Component, Input, OnInit} from '@angular/core';
 | 
					import {Component, Input, OnInit} from '@angular/core';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Component({
 | 
					@Component({
 | 
				
			||||||
  // tslint:disable-next-line:component-selector
 | 
					  // Tslint:disable-next-line:component-selector
 | 
				
			||||||
  selector: 'rb-table',
 | 
					  selector: 'rb-table',
 | 
				
			||||||
  templateUrl: './rb-table.component.html',
 | 
					  templateUrl: './rb-table.component.html',
 | 
				
			||||||
  styleUrls: ['./rb-table.component.scss']
 | 
					  styleUrls: ['./rb-table.component.scss']
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,41 +49,41 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
				
			|||||||
  @ViewChild('sampleForm') sampleForm: NgForm;
 | 
					  @ViewChild('sampleForm') sampleForm: NgForm;
 | 
				
			||||||
  @ViewChild('cmForm') cmForm: NgForm;
 | 
					  @ViewChild('cmForm') cmForm: NgForm;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  baseSample = new SampleModel();            // base sample which is saved
 | 
					  baseSample = new SampleModel();            // Base sample which is saved
 | 
				
			||||||
  sampleCount = 1;                       // number of samples to be generated
 | 
					  sampleCount = 1;                       // Number of samples to be generated
 | 
				
			||||||
  samples: SampleModel[] = [];  // gets filled with response data after saving the sample
 | 
					  samples: SampleModel[] = [];  // Gets filled with response data after saving the sample
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  sampleReferences: [string, string, string][] = [['', '', '']];
 | 
					  sampleReferences: [string, string, string][] = [['', '', '']];
 | 
				
			||||||
  sampleReferenceFinds: {_id: string, number: string}[] = [];    // raw sample reference data from db
 | 
					  sampleReferenceFinds: {_id: string, number: string}[] = [];    // Raw sample reference data from db
 | 
				
			||||||
  currentSRIndex = 0;                    // index of last entered sample reference
 | 
					  currentSRIndex = 0;                    // Index of last entered sample reference
 | 
				
			||||||
  sampleReferenceAutocomplete: string[][] = [[]];
 | 
					  sampleReferenceAutocomplete: string[][] = [[]];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  customFields: [string, string][] = [];
 | 
					  customFields: [string, string][] = [];
 | 
				
			||||||
  availableCustomFields: string[] = [];
 | 
					  availableCustomFields: string[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  newMaterial = false;                   // true if new material should be created
 | 
					  newMaterial = false;                   // True if new material should be created
 | 
				
			||||||
  materials: MaterialModel[] = [];       // all materials
 | 
					  materials: MaterialModel[] = [];       // All materials
 | 
				
			||||||
  materialNames = [];                    // only names for autocomplete
 | 
					  materialNames = [];                    // Only names for autocomplete
 | 
				
			||||||
  material = new MaterialModel();        // object of current selected material
 | 
					  material = new MaterialModel();        // Object of current selected material
 | 
				
			||||||
  defaultDevice = '';                    // default device for spectra
 | 
					  defaultDevice = '';                    // Default device for spectra
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // component mode, either new for generating new samples, editOne or editMulti, editing one or multiple samples
 | 
					  // Component mode, either new for generating new samples, editOne or editMulti, editing one or multiple samples
 | 
				
			||||||
  mode = 'new';
 | 
					  mode = 'new';
 | 
				
			||||||
  view = {                               // active views
 | 
					  view = {                               // Active views
 | 
				
			||||||
    base: false,                         // base sample
 | 
					    base: false,                         // Base sample
 | 
				
			||||||
    baseSum: false,                      // base sample summary
 | 
					    baseSum: false,                      // Base sample summary
 | 
				
			||||||
    cm: false,                           // conditions and measurements
 | 
					    cm: false,                           // Conditions and measurements
 | 
				
			||||||
    cmSum: false                         // conditions and measurements summary
 | 
					    cmSum: false                         // Conditions and measurements summary
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
  loading = 0;                           // number of currently loading instances
 | 
					  loading = 0;                           // Number of currently loading instances
 | 
				
			||||||
  checkFormAfterInit = false;
 | 
					  checkFormAfterInit = false;
 | 
				
			||||||
  modalText = {list: '', suggestion: ''};
 | 
					  modalText = {list: '', suggestion: ''};
 | 
				
			||||||
  cmSampleIndex = '0';
 | 
					  cmSampleIndex = '0';
 | 
				
			||||||
  measurementDeleteList = [];  // buffer with measurements to delete, if the user confirms and saves the cm changes
 | 
					  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
 | 
					  // Deleted measurements if user is allowed and measurements are available
 | 
				
			||||||
  measurementRestoreData: MeasurementModel[] = [];
 | 
					  measurementRestoreData: MeasurementModel[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  charts = [[]];                         // chart data for spectra
 | 
					  charts = [[]];                         // Chart data for spectra
 | 
				
			||||||
  readonly chartInit = [{
 | 
					  readonly chartInit = [{
 | 
				
			||||||
    data: [],
 | 
					    data: [],
 | 
				
			||||||
    label: 'Spectrum',
 | 
					    label: 'Spectrum',
 | 
				
			||||||
@@ -135,7 +135,7 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
				
			|||||||
    this.d.load('measurementTemplates', () => {
 | 
					    this.d.load('measurementTemplates', () => {
 | 
				
			||||||
      this.d.load('user', () => {
 | 
					      this.d.load('user', () => {
 | 
				
			||||||
        this.defaultDevice = this.d.d.user.devices[0];
 | 
					        this.defaultDevice = this.d.d.user.devices[0];
 | 
				
			||||||
        // spectrum device must be from user's devices list
 | 
					        // Spectrum device must be from user's devices list
 | 
				
			||||||
        this.d.arr.measurementTemplates.forEach(template => {
 | 
					        this.d.arr.measurementTemplates.forEach(template => {
 | 
				
			||||||
          const device = template.parameters.find(e => e.name === 'device');
 | 
					          const device = template.parameters.find(e => e.name === 'device');
 | 
				
			||||||
          if (device) {
 | 
					          if (device) {
 | 
				
			||||||
@@ -163,7 +163,7 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
				
			|||||||
        this.mode = 'editOne';
 | 
					        this.mode = 'editOne';
 | 
				
			||||||
        this.view.baseSum = true;
 | 
					        this.view.baseSum = true;
 | 
				
			||||||
        this.view.cm = true;
 | 
					        this.view.cm = true;
 | 
				
			||||||
        if (this.login.isLevel.dev) {  // load measurement restore data
 | 
					        if (this.login.isLevel.dev) {  // Load measurement restore data
 | 
				
			||||||
          this.api.get<MeasurementModel[]>('/measurement/sample/' + sampleIds[0], (data, ignore) => {
 | 
					          this.api.get<MeasurementModel[]>('/measurement/sample/' + sampleIds[0], (data, ignore) => {
 | 
				
			||||||
            if (data) {
 | 
					            if (data) {
 | 
				
			||||||
              this.measurementRestoreData =
 | 
					              this.measurementRestoreData =
 | 
				
			||||||
@@ -177,22 +177,22 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
				
			|||||||
        this.view.base = true;
 | 
					        this.view.base = true;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      this.loading += sampleIds.length;
 | 
					      this.loading += sampleIds.length;
 | 
				
			||||||
      this.api.get<SampleModel>('/sample/' + sampleIds[0], sData => {  // special treatment for first id
 | 
					      this.api.get<SampleModel>('/sample/' + sampleIds[0], sData => {  // Special treatment for first id
 | 
				
			||||||
        this.samples = [new SampleModel().deserialize(sData)];
 | 
					        this.samples = [new SampleModel().deserialize(sData)];
 | 
				
			||||||
        this.baseSample.deserialize(sData);
 | 
					        this.baseSample.deserialize(sData);
 | 
				
			||||||
        this.material = new MaterialModel().deserialize(sData.material);  // read material
 | 
					        this.material = new MaterialModel().deserialize(sData.material);  // Read material
 | 
				
			||||||
        // read custom fields
 | 
					        // Read custom fields
 | 
				
			||||||
        this.customFields = this.baseSample.notes.custom_fields && this.baseSample.notes.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]]) : [];
 | 
					          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
 | 
					        if (this.baseSample.notes.sample_references.length) {  // Read sample references
 | 
				
			||||||
          this.sampleReferences = [];
 | 
					          this.sampleReferences = [];
 | 
				
			||||||
          this.sampleReferenceAutocomplete = [];
 | 
					          this.sampleReferenceAutocomplete = [];
 | 
				
			||||||
          let loadCounter = this.baseSample.notes.sample_references.length;  // count down instances still loading
 | 
					          let loadCounter = this.baseSample.notes.sample_references.length;  // Count down instances still loading
 | 
				
			||||||
          this.baseSample.notes.sample_references.forEach(reference => {
 | 
					          this.baseSample.notes.sample_references.forEach(reference => {
 | 
				
			||||||
            this.api.get<SampleModel>('/sample/' + reference.sample_id, srData => {  // get sample numbers for ids
 | 
					            this.api.get<SampleModel>('/sample/' + reference.sample_id, srData => {  // Get sample numbers for ids
 | 
				
			||||||
              this.sampleReferences.push([srData.number, reference.relation, reference.sample_id]);
 | 
					              this.sampleReferences.push([srData.number, reference.relation, reference.sample_id]);
 | 
				
			||||||
              this.sampleReferenceAutocomplete.push([srData.number]);
 | 
					              this.sampleReferenceAutocomplete.push([srData.number]);
 | 
				
			||||||
              if (!--loadCounter) {  // insert empty template when all instances were loaded
 | 
					              if (!--loadCounter) {  // Insert empty template when all instances were loaded
 | 
				
			||||||
                this.sampleReferences.push(['', '', '']);
 | 
					                this.sampleReferences.push(['', '', '']);
 | 
				
			||||||
                this.sampleReferenceAutocomplete.push([]);
 | 
					                this.sampleReferenceAutocomplete.push([]);
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
@@ -201,20 +201,20 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        if (this.mode === 'editOne') {
 | 
					        if (this.mode === 'editOne') {
 | 
				
			||||||
          this.charts = [[]];
 | 
					          this.charts = [[]];
 | 
				
			||||||
          let spectrumCounter = 0;  // generate charts for spectrum measurements
 | 
					          let spectrumCounter = 0;  // Generate charts for spectrum measurements
 | 
				
			||||||
          this.samples[0].measurements.forEach((measurement, i) => {
 | 
					          this.samples[0].measurements.forEach((measurement, i) => {
 | 
				
			||||||
            this.charts[0].push(cloneDeep(this.chartInit));
 | 
					            this.charts[0].push(cloneDeep(this.chartInit));
 | 
				
			||||||
            if (measurement.values.dpt) {
 | 
					            if (measurement.values.dpt) {
 | 
				
			||||||
              setTimeout(() => {
 | 
					              setTimeout(() => {
 | 
				
			||||||
                this.generateChart(measurement.values.dpt, 0, i);
 | 
					                this.generateChart(measurement.values.dpt, 0, i);
 | 
				
			||||||
              }, spectrumCounter * 20);  // generate charts one after another to avoid freezing the UI
 | 
					              }, spectrumCounter * 20);  // Generate charts one after another to avoid freezing the UI
 | 
				
			||||||
              spectrumCounter ++;
 | 
					              spectrumCounter ++;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        this.checkFormAfterInit = true;
 | 
					        this.checkFormAfterInit = true;
 | 
				
			||||||
        this.loading--;
 | 
					        this.loading--;
 | 
				
			||||||
        sampleIds.slice(1).forEach(sampleId => {  // load further samples for batch edit
 | 
					        sampleIds.slice(1).forEach(sampleId => {  // Load further samples for batch edit
 | 
				
			||||||
          this.api.get<SampleModel>('/sample/' + sampleId, data => {
 | 
					          this.api.get<SampleModel>('/sample/' + sampleId, data => {
 | 
				
			||||||
            this.samples.push(new SampleModel().deserialize(data));
 | 
					            this.samples.push(new SampleModel().deserialize(data));
 | 
				
			||||||
            ['type', 'color', 'batch', 'notes'].forEach((key) => {
 | 
					            ['type', 'color', 'batch', 'notes'].forEach((key) => {
 | 
				
			||||||
@@ -237,8 +237,8 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ngAfterContentChecked() {
 | 
					  ngAfterContentChecked() {
 | 
				
			||||||
    // attach validators
 | 
					    // Attach validators
 | 
				
			||||||
    if (this.samples.length) {  // conditions are displayed
 | 
					    if (this.samples.length) {  // Conditions are displayed
 | 
				
			||||||
      this.samples.forEach((gSample, gIndex) => {
 | 
					      this.samples.forEach((gSample, gIndex) => {
 | 
				
			||||||
        if (this.d.id.conditionTemplates[gSample.condition.condition_template]) {
 | 
					        if (this.d.id.conditionTemplates[gSample.condition.condition_template]) {
 | 
				
			||||||
          this.d.id.conditionTemplates[gSample.condition.condition_template].parameters.forEach((parameter, pIndex) => {
 | 
					          this.d.id.conditionTemplates[gSample.condition.condition_template].parameters.forEach((parameter, pIndex) => {
 | 
				
			||||||
@@ -253,15 +253,15 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
				
			|||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (this.sampleForm && this.material.properties.material_template) {  // material template is set
 | 
					    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.d.id.materialTemplates[this.material.properties.material_template].parameters.forEach((parameter, i) => {
 | 
				
			||||||
        this.attachValidator(this.sampleForm, 'materialParameter' + i, parameter.range);
 | 
					        this.attachValidator(this.sampleForm, 'materialParameter' + i, parameter.range);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // revalidate inputs
 | 
					    // Revalidate inputs
 | 
				
			||||||
    if (this.checkFormAfterInit) {
 | 
					    if (this.checkFormAfterInit) {
 | 
				
			||||||
      if (this.view.base) {  // validate sampleForm
 | 
					      if (this.view.base) {  // Validate sampleForm
 | 
				
			||||||
        if (this.sampleForm !== undefined && this.sampleForm.form.get('cf-key0')) {
 | 
					        if (this.sampleForm !== undefined && this.sampleForm.form.get('cf-key0')) {
 | 
				
			||||||
          this.checkFormAfterInit = false;
 | 
					          this.checkFormAfterInit = false;
 | 
				
			||||||
          Object.keys(this.sampleForm.form.controls).forEach(field => {
 | 
					          Object.keys(this.sampleForm.form.controls).forEach(field => {
 | 
				
			||||||
@@ -269,20 +269,20 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
				
			|||||||
          });
 | 
					          });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      else {  // validate cmForm
 | 
					      else {  // Validate cmForm
 | 
				
			||||||
        // check that all fields are ready for validation
 | 
					        // Check that all fields are ready for validation
 | 
				
			||||||
        let formReady: boolean = this.cmForm !== undefined;  // forms exist
 | 
					        let formReady: boolean = this.cmForm !== undefined;  // Forms exist
 | 
				
			||||||
        if (this.samples[0].condition.condition_template) {  // if condition is set, last condition field exists
 | 
					        if (this.samples[0].condition.condition_template) {  // If condition is set, last condition field exists
 | 
				
			||||||
          formReady = formReady && this.cmForm.form.get('conditionParameter-0-' +
 | 
					          formReady = formReady && this.cmForm.form.get('conditionParameter-0-' +
 | 
				
			||||||
            (this.d.id.conditionTemplates[this.samples[0].condition.condition_template].parameters.length - 1)) as any;
 | 
					            (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
 | 
					        if (this.samples[0].measurements.length) {  // If there are measurements, last measurement field exists
 | 
				
			||||||
          formReady = formReady &&
 | 
					          formReady = formReady &&
 | 
				
			||||||
            this.cmForm.form.get('measurementParameter-0-' + (this.samples[0].measurements.length - 1) +
 | 
					            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]
 | 
					            '-' + (this.d.id.measurementTemplates[this.samples[0].measurements[this.samples[0].measurements.length - 1]
 | 
				
			||||||
              .measurement_template].parameters.length - 1)) as any;
 | 
					              .measurement_template].parameters.length - 1)) as any;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (formReady) {  // fields are ready, do validation
 | 
					        if (formReady) {  // Fields are ready, do validation
 | 
				
			||||||
          this.checkFormAfterInit = false;
 | 
					          this.checkFormAfterInit = false;
 | 
				
			||||||
          Object.keys(this.cmForm.form.controls).forEach(field => {
 | 
					          Object.keys(this.cmForm.form.controls).forEach(field => {
 | 
				
			||||||
            this.cmForm.form.get(field).updateValueAndValidity();
 | 
					            this.cmForm.form.get(field).updateValueAndValidity();
 | 
				
			||||||
@@ -292,7 +292,7 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // attach validators specified in range to input with name
 | 
					  // Attach validators specified in range to input with name
 | 
				
			||||||
  attachValidator(form, name: string, range: {[prop: string]: any}) {
 | 
					  attachValidator(form, name: string, range: {[prop: string]: any}) {
 | 
				
			||||||
    if (form && form.form.get(name)) {
 | 
					    if (form && form.form.get(name)) {
 | 
				
			||||||
      const validators = [];
 | 
					      const validators = [];
 | 
				
			||||||
@@ -319,25 +319,25 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
				
			|||||||
    setTimeout(() => this.checkFormAfterInit = true, 0);
 | 
					    setTimeout(() => this.checkFormAfterInit = true, 0);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // save base sample
 | 
					  // Save base sample
 | 
				
			||||||
  saveSample() {
 | 
					  saveSample() {
 | 
				
			||||||
    if (this.samples.length === 0) {
 | 
					    if (this.samples.length === 0) {
 | 
				
			||||||
      this.loading = this.sampleCount;  // set up loading spinner
 | 
					      this.loading = this.sampleCount;  // Set up loading spinner
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    new Promise<void>(resolve => {
 | 
					    new Promise<void>(resolve => {
 | 
				
			||||||
      if (this.newMaterial) {  // save material first if new one exists
 | 
					      if (this.newMaterial) {  // Save material first if new one exists
 | 
				
			||||||
        this.material.numbers = this.material.numbers.filter(e => e !== '');
 | 
					        this.material.numbers = this.material.numbers.filter(e => e !== '');
 | 
				
			||||||
        this.api.post<MaterialModel>('/material/new', this.material.sendFormat(), data => {
 | 
					        this.api.post<MaterialModel>('/material/new', this.material.sendFormat(), data => {
 | 
				
			||||||
          this.d.arr.materials.push(data);  // add material to data
 | 
					          this.d.arr.materials.push(data);  // Add material to data
 | 
				
			||||||
          this.material = data;
 | 
					          this.material = data;
 | 
				
			||||||
          this.baseSample.material_id = data._id;  // add new material id to sample data
 | 
					          this.baseSample.material_id = data._id;  // Add new material id to sample data
 | 
				
			||||||
          resolve();
 | 
					          resolve();
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      else {
 | 
					      else {
 | 
				
			||||||
        resolve();
 | 
					        resolve();
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }).then(() => {  // save sample
 | 
					    }).then(() => {  // Save sample
 | 
				
			||||||
      if (this.baseSample.notes) {
 | 
					      if (this.baseSample.notes) {
 | 
				
			||||||
        this.baseSample.notes.custom_fields = {};
 | 
					        this.baseSample.notes.custom_fields = {};
 | 
				
			||||||
        this.customFields.forEach(element => {
 | 
					        this.customFields.forEach(element => {
 | 
				
			||||||
@@ -346,10 +346,10 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
				
			|||||||
          }
 | 
					          }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        this.baseSample.notes.sample_references = this.sampleReferences
 | 
					        this.baseSample.notes.sample_references = this.sampleReferences
 | 
				
			||||||
          .filter(e => e[0] && e[1] && e[2])  // filter empty values
 | 
					          .filter(e => e[0] && e[1] && e[2])  // Filter empty values
 | 
				
			||||||
          .map(e => ({sample_id: e[2], relation: e[1]}));
 | 
					          .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
 | 
					      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 ++) {
 | 
					        for (let i = 0; i < this.sampleCount; i ++) {
 | 
				
			||||||
          this.api.post<SampleModel>('/sample/new', this.baseSample.sendFormat(), data => {
 | 
					          this.api.post<SampleModel>('/sample/new', this.baseSample.sendFormat(), data => {
 | 
				
			||||||
            this.samples[i] = new SampleModel().deserialize(data);
 | 
					            this.samples[i] = new SampleModel().deserialize(data);
 | 
				
			||||||
@@ -374,10 +374,10 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // save conditions and measurements
 | 
					  // Save conditions and measurements
 | 
				
			||||||
  cmSave() {  // save measurements and conditions
 | 
					  cmSave() {  // Save measurements and conditions
 | 
				
			||||||
    this.samples.forEach(sample => {
 | 
					    this.samples.forEach(sample => {
 | 
				
			||||||
      if (sample.condition.condition_template) {  // condition was set
 | 
					      if (sample.condition.condition_template) {  // Condition was set
 | 
				
			||||||
        this.api.put('/sample/' + sample._id,
 | 
					        this.api.put('/sample/' + sample._id,
 | 
				
			||||||
          {condition: pick(sample.condition,
 | 
					          {condition: pick(sample.condition,
 | 
				
			||||||
              [
 | 
					              [
 | 
				
			||||||
@@ -387,21 +387,21 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
				
			|||||||
          )}
 | 
					          )}
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      sample.measurements.forEach(measurement => {  // save measurements
 | 
					      sample.measurements.forEach(measurement => {  // Save measurements
 | 
				
			||||||
        if (Object.keys(measurement.values).map(e => measurement.values[e]).join('') !== '') {
 | 
					        if (Object.keys(measurement.values).map(e => measurement.values[e]).join('') !== '') {
 | 
				
			||||||
          Object.keys(measurement.values).forEach(key => {  // map empty values to null
 | 
					          Object.keys(measurement.values).forEach(key => {  // Map empty values to null
 | 
				
			||||||
            measurement.values[key] = measurement.values[key] === '' ? null : measurement.values[key];
 | 
					            measurement.values[key] = measurement.values[key] === '' ? null : measurement.values[key];
 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
          if (measurement._id === null) {  // new measurement
 | 
					          if (measurement._id === null) {  // New measurement
 | 
				
			||||||
            measurement.sample_id = sample._id;
 | 
					            measurement.sample_id = sample._id;
 | 
				
			||||||
            this.api.post<MeasurementModel>('/measurement/new', measurement.sendFormat());
 | 
					            this.api.post<MeasurementModel>('/measurement/new', measurement.sendFormat());
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          else {  // update measurement
 | 
					          else {  // Update measurement
 | 
				
			||||||
            this.api.put<MeasurementModel>('/measurement/' + measurement._id,
 | 
					            this.api.put<MeasurementModel>('/measurement/' + measurement._id,
 | 
				
			||||||
              measurement.sendFormat(['sample_id', 'measurement_template']));
 | 
					              measurement.sendFormat(['sample_id', 'measurement_template']));
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else if (measurement._id !== null) {  // existing measurement was left empty to delete
 | 
					        else if (measurement._id !== null) {  // Existing measurement was left empty to delete
 | 
				
			||||||
          this.api.delete('/measurement/' + measurement._id);
 | 
					          this.api.delete('/measurement/' + measurement._id);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
@@ -414,7 +414,7 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  restoreMeasurements() {
 | 
					  restoreMeasurements() {
 | 
				
			||||||
    let spectrumCounter = 0;  // generate charts for spectrum measurements
 | 
					    let spectrumCounter = 0;  // Generate charts for spectrum measurements
 | 
				
			||||||
    const measurementCount = this.samples[0].measurements.length;
 | 
					    const measurementCount = this.samples[0].measurements.length;
 | 
				
			||||||
    this.measurementRestoreData.forEach((measurement, i) => {
 | 
					    this.measurementRestoreData.forEach((measurement, i) => {
 | 
				
			||||||
      this.api.put('/measurement/restore/' + measurement._id, {}, () => {
 | 
					      this.api.put('/measurement/restore/' + measurement._id, {}, () => {
 | 
				
			||||||
@@ -423,7 +423,7 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
				
			|||||||
        if (measurement.values.dpt) {
 | 
					        if (measurement.values.dpt) {
 | 
				
			||||||
          setTimeout(() => {
 | 
					          setTimeout(() => {
 | 
				
			||||||
            this.generateChart(measurement.values.dpt, 0, measurementCount + i);
 | 
					            this.generateChart(measurement.values.dpt, 0, measurementCount + i);
 | 
				
			||||||
          }, spectrumCounter * 20);  // generate charts one after another to avoid freezing the UI
 | 
					          }, spectrumCounter * 20);  // Generate charts one after another to avoid freezing the UI
 | 
				
			||||||
          spectrumCounter ++;
 | 
					          spectrumCounter ++;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        this.checkFormAfterInit = true;
 | 
					        this.checkFormAfterInit = true;
 | 
				
			||||||
@@ -431,15 +431,15 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // set material based on found material name
 | 
					  // Set material based on found material name
 | 
				
			||||||
  findMaterial(name) {
 | 
					  findMaterial(name) {
 | 
				
			||||||
    const res = this.d.arr.materials.find(e => e.name === name);  // search for match
 | 
					    const res = this.d.arr.materials.find(e => e.name === name);  // Search for match
 | 
				
			||||||
    if (res) {  // material found
 | 
					    if (res) {  // Material found
 | 
				
			||||||
      this.material = cloneDeep(res);
 | 
					      this.material = cloneDeep(res);
 | 
				
			||||||
      this.baseSample.material_id = this.material._id;
 | 
					      this.baseSample.material_id = this.material._id;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    else {  // no matching material found
 | 
					    else {  // No matching material found
 | 
				
			||||||
      if (this.baseSample.material_id !== null) {  // reset previous match
 | 
					      if (this.baseSample.material_id !== null) {  // Reset previous match
 | 
				
			||||||
        this.material = new MaterialModel();
 | 
					        this.material = new MaterialModel();
 | 
				
			||||||
        this.material.properties.material_template =
 | 
					        this.material.properties.material_template =
 | 
				
			||||||
          this.d.latest.materialTemplates.find(e => e.name === 'plastic')._id;
 | 
					          this.d.latest.materialTemplates.find(e => e.name === 'plastic')._id;
 | 
				
			||||||
@@ -449,36 +449,36 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
				
			|||||||
    this.setNewMaterial();
 | 
					    this.setNewMaterial();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // set newMaterial, if value === null -> toggle
 | 
					  // Set newMaterial, if value === null -> toggle
 | 
				
			||||||
  setNewMaterial(value = null) {
 | 
					  setNewMaterial(value = null) {
 | 
				
			||||||
    if (value === null) {  // toggle dialog
 | 
					    if (value === null) {  // Toggle dialog
 | 
				
			||||||
      this.newMaterial = !this.baseSample.material_id;
 | 
					      this.newMaterial = !this.baseSample.material_id;
 | 
				
			||||||
    }  // set to false only if material already exists
 | 
					    }  // Set to false only if material already exists
 | 
				
			||||||
    else if (value || (!value && this.baseSample.material_id !== null )) {
 | 
					    else if (value || (!value && this.baseSample.material_id !== null )) {
 | 
				
			||||||
      this.newMaterial = value;
 | 
					      this.newMaterial = value;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (this.newMaterial) {  // set validators if dialog is open
 | 
					    if (this.newMaterial) {  // Set validators if dialog is open
 | 
				
			||||||
      this.sampleForm.form.get('materialname').setValidators([Validators.required]);
 | 
					      this.sampleForm.form.get('materialname').setValidators([Validators.required]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    else {  // material name must be from list if dialog is closed
 | 
					    else {  // Material name must be from list if dialog is closed
 | 
				
			||||||
      this.sampleForm.form.get('materialname')
 | 
					      this.sampleForm.form.get('materialname')
 | 
				
			||||||
        .setValidators([Validators.required, this.validation.generate('stringOf', [this.materialNames])]);
 | 
					        .setValidators([Validators.required, this.validation.generate('stringOf', [this.materialNames])]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this.sampleForm.form.get('materialname').updateValueAndValidity();
 | 
					    this.sampleForm.form.get('materialname').updateValueAndValidity();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // add a new measurement for generated sample at index
 | 
					  // Add a new measurement for generated sample at index
 | 
				
			||||||
  addMeasurement(gIndex) {
 | 
					  addMeasurement(gIndex) {
 | 
				
			||||||
    this.samples[gIndex].measurements.push(
 | 
					    this.samples[gIndex].measurements.push(
 | 
				
			||||||
      new MeasurementModel(this.d.latest.measurementTemplates.find(e => e.name === 'spectrum')._id)
 | 
					      new MeasurementModel(this.d.latest.measurementTemplates.find(e => e.name === 'spectrum')._id)
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    if (!this.charts[gIndex]) {  // add array if there are no charts yet
 | 
					    if (!this.charts[gIndex]) {  // Add array if there are no charts yet
 | 
				
			||||||
      this.charts[gIndex] = [];
 | 
					      this.charts[gIndex] = [];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this.charts[gIndex].push(cloneDeep(this.chartInit));
 | 
					    this.charts[gIndex].push(cloneDeep(this.chartInit));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // remove the measurement at the specified index
 | 
					  // Remove the measurement at the specified index
 | 
				
			||||||
  removeMeasurement(gIndex, mIndex) {
 | 
					  removeMeasurement(gIndex, mIndex) {
 | 
				
			||||||
    if (this.samples[gIndex].measurements[mIndex]._id !== null) {
 | 
					    if (this.samples[gIndex].measurements[mIndex]._id !== null) {
 | 
				
			||||||
      this.measurementDeleteList.push(this.samples[gIndex].measurements[mIndex]._id);
 | 
					      this.measurementDeleteList.push(this.samples[gIndex].measurements[mIndex]._id);
 | 
				
			||||||
@@ -487,23 +487,23 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
				
			|||||||
    this.charts[gIndex].splice(mIndex, 1);
 | 
					    this.charts[gIndex].splice(mIndex, 1);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // clear entered measurement data at the specified index due to template change
 | 
					  // Clear entered measurement data at the specified index due to template change
 | 
				
			||||||
  clearMeasurement(gIndex, mIndex) {
 | 
					  clearMeasurement(gIndex, mIndex) {
 | 
				
			||||||
    this.charts[gIndex][mIndex][0].data = [];
 | 
					    this.charts[gIndex][mIndex][0].data = [];
 | 
				
			||||||
    this.samples[gIndex].measurements[mIndex].values = {};
 | 
					    this.samples[gIndex].measurements[mIndex].values = {};
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  fileToArray(files, gIndex, mIndex, parameter) {  // process spectrum file input
 | 
					  fileToArray(files, gIndex, mIndex, parameter) {  // Process spectrum file input
 | 
				
			||||||
    for (const i in files) {
 | 
					    for (const i in files) {
 | 
				
			||||||
      if (files.hasOwnProperty(i)) {
 | 
					      if (files.hasOwnProperty(i)) {
 | 
				
			||||||
        const fileReader = new FileReader();
 | 
					        const fileReader = new FileReader();
 | 
				
			||||||
        fileReader.onload = () => {
 | 
					        fileReader.onload = () => {
 | 
				
			||||||
          let index: number = mIndex;
 | 
					          let index: number = mIndex;
 | 
				
			||||||
          if (Number(i) > 0) {  // append further spectra
 | 
					          if (Number(i) > 0) {  // Append further spectra
 | 
				
			||||||
            this.addMeasurement(gIndex);
 | 
					            this.addMeasurement(gIndex);
 | 
				
			||||||
            index = this.samples[gIndex].measurements.length - 1;
 | 
					            index = this.samples[gIndex].measurements.length - 1;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          // autofill further parameters
 | 
					          // Autofill further parameters
 | 
				
			||||||
          this.samples[gIndex].measurements[index].values.device =
 | 
					          this.samples[gIndex].measurements[index].values.device =
 | 
				
			||||||
            this.samples[gIndex].measurements[mIndex].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.filename = files[i].name;
 | 
				
			||||||
@@ -530,16 +530,16 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  checkTypo(event, list, mKey, modal: TemplateRef<any>) {
 | 
					  checkTypo(event, list, mKey, modal: TemplateRef<any>) {
 | 
				
			||||||
    // user did not click on suggestion and entry is not in list
 | 
					    // User did not click on suggestion and entry is not in list
 | 
				
			||||||
    if (!(event.relatedTarget && (event.relatedTarget.className.indexOf('rb-dropdown-item') >= 0 ||
 | 
					    if (!(event.relatedTarget && (event.relatedTarget.className.indexOf('rb-dropdown-item') >= 0 ||
 | 
				
			||||||
        event.relatedTarget.className.indexOf('close-btn rb-btn rb-passive-link') >= 0)) &&
 | 
					        event.relatedTarget.className.indexOf('close-btn rb-btn rb-passive-link') >= 0)) &&
 | 
				
			||||||
      this.d.arr[list].indexOf(this.material[mKey]) < 0) {
 | 
					      this.d.arr[list].indexOf(this.material[mKey]) < 0) {
 | 
				
			||||||
      this.modalText.list = mKey;
 | 
					      this.modalText.list = mKey;
 | 
				
			||||||
      this.modalText.suggestion = this.d.arr[list]  // find possible entry from list
 | 
					      this.modalText.suggestion = this.d.arr[list]  // Find possible entry from list
 | 
				
			||||||
        .map(e => ({v: e, s: strCompare.sorensenDice(e, this.material[mKey])}))
 | 
					        .map(e => ({v: e, s: strCompare.sorensenDice(e, this.material[mKey])}))
 | 
				
			||||||
        .sort((a, b) => b.s - a.s)[0].v;
 | 
					        .sort((a, b) => b.s - a.s)[0].v;
 | 
				
			||||||
      this.modal.open(modal).then(result => {
 | 
					      this.modal.open(modal).then(result => {
 | 
				
			||||||
        if (result) {  // use suggestion
 | 
					        if (result) {  // Use suggestion
 | 
				
			||||||
          this.material[mKey] = this.modalText.suggestion;
 | 
					          this.material[mKey] = this.modalText.suggestion;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
@@ -569,12 +569,12 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
				
			|||||||
        filledFields ++;
 | 
					        filledFields ++;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    // append new field
 | 
					    // Append new field
 | 
				
			||||||
    if (filledFields === fieldNo) {
 | 
					    if (filledFields === fieldNo) {
 | 
				
			||||||
      this.sampleReferences.push(['', '', '']);
 | 
					      this.sampleReferences.push(['', '', '']);
 | 
				
			||||||
      this.sampleReferenceAutocomplete.push([]);
 | 
					      this.sampleReferenceAutocomplete.push([]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    // remove if two end fields are empty
 | 
					    // Remove if two end fields are empty
 | 
				
			||||||
    if (fieldNo > 1 && this.sampleReferences[fieldNo - 1][0] === '' && this.sampleReferences[fieldNo - 2][0] === '') {
 | 
					    if (fieldNo > 1 && this.sampleReferences[fieldNo - 1][0] === '' && this.sampleReferences[fieldNo - 2][0] === '') {
 | 
				
			||||||
      this.sampleReferences.pop();
 | 
					      this.sampleReferences.pop();
 | 
				
			||||||
      this.sampleReferenceAutocomplete.pop();
 | 
					      this.sampleReferenceAutocomplete.pop();
 | 
				
			||||||
@@ -582,7 +582,7 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
				
			|||||||
    this.sampleReferenceIdFind(value);
 | 
					    this.sampleReferenceIdFind(value);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  sampleReferenceList(value) {  // get list of sample reference number suggestions
 | 
					  sampleReferenceList(value) {  // Get list of sample reference number suggestions
 | 
				
			||||||
    return new Observable(observer => {
 | 
					    return new Observable(observer => {
 | 
				
			||||||
      if (value !== '') {
 | 
					      if (value !== '') {
 | 
				
			||||||
        this.api.get<{ _id: string, number: string }[]>(
 | 
					        this.api.get<{ _id: string, number: string }[]>(
 | 
				
			||||||
@@ -603,7 +603,7 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  sampleReferenceIdFind(value) {  // sample reference id from number
 | 
					  sampleReferenceIdFind(value) {  // Sample reference id from number
 | 
				
			||||||
    const idFind = this.sampleReferenceFinds.find(e => e.number === value);
 | 
					    const idFind = this.sampleReferenceFinds.find(e => e.number === value);
 | 
				
			||||||
    if (idFind) {
 | 
					    if (idFind) {
 | 
				
			||||||
      this.sampleReferences[this.currentSRIndex][2] = idFind._id;
 | 
					      this.sampleReferences[this.currentSRIndex][2] = idFind._id;
 | 
				
			||||||
@@ -621,7 +621,7 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
				
			|||||||
    return this.samples.map(e => e.number).join(', ');
 | 
					    return this.samples.map(e => e.number).join(', ');
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  uniqueCfValues(index) {  // returns all names until index for unique check
 | 
					  uniqueCfValues(index) {  // Returns all names until index for unique check
 | 
				
			||||||
    return this.customFields ? this.customFields.slice(0, index).map(e => e[0]) : [];
 | 
					    return this.customFields ? this.customFields.slice(0, index).map(e => e[0]) : [];
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -41,13 +41,13 @@ export class SamplesComponent implements OnInit {
 | 
				
			|||||||
  @ViewChild('pageSizeSelection') pageSizeSelection: ElementRef<HTMLElement>;
 | 
					  @ViewChild('pageSizeSelection') pageSizeSelection: ElementRef<HTMLElement>;
 | 
				
			||||||
  @ViewChild('linkarea') linkarea: ElementRef<HTMLTextAreaElement>;
 | 
					  @ViewChild('linkarea') linkarea: ElementRef<HTMLTextAreaElement>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  downloadSpectra = false;    // download options
 | 
					  downloadSpectra = false;    // Download options
 | 
				
			||||||
  downloadCondition = false;
 | 
					  downloadCondition = false;
 | 
				
			||||||
  downloadFlatten = true;
 | 
					  downloadFlatten = true;
 | 
				
			||||||
  samples: SampleModel[] = [];  // all samples to display
 | 
					  samples: SampleModel[] = [];  // All samples to display
 | 
				
			||||||
  data: Data[] = [];
 | 
					  data: Data[] = [];
 | 
				
			||||||
  totalSamples = 0;  // total number of samples
 | 
					  totalSamples = 0;  // Total number of samples
 | 
				
			||||||
  csvUrl = '';  // store url separate so it only has to be generated when clicking the download button
 | 
					  csvUrl = '';  // Store url separate so it only has to be generated when clicking the download button
 | 
				
			||||||
  filters = {
 | 
					  filters = {
 | 
				
			||||||
    status: { new: true, validated: true, deleted: false },
 | 
					    status: { new: true, validated: true, deleted: false },
 | 
				
			||||||
    pageSize: 25,
 | 
					    pageSize: 25,
 | 
				
			||||||
@@ -69,9 +69,9 @@ export class SamplesComponent implements OnInit {
 | 
				
			|||||||
      { field: 'added', label: 'Added', active: false, autocomplete: [], mode: 'eq', values: [''] }
 | 
					      { field: 'added', label: 'Added', active: false, autocomplete: [], mode: 'eq', values: [''] }
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
  page = 1;   // current page
 | 
					  page = 1;   // Current page
 | 
				
			||||||
  pages = 1;  // total number of pages
 | 
					  pages = 1;  // Total number of pages
 | 
				
			||||||
  loadSamplesQueue = [];  // arguments of queued up loadSamples() calls
 | 
					  loadSamplesQueue = [];  // Arguments of queued up loadSamples() calls
 | 
				
			||||||
  materialKeys: KeyInterface[] = [
 | 
					  materialKeys: KeyInterface[] = [
 | 
				
			||||||
    { id: 'number', label: 'Number', active: true, sortable: true },
 | 
					    { id: 'number', label: 'Number', active: true, sortable: true },
 | 
				
			||||||
    { id: 'material.numbers', label: 'Material numbers', active: false, sortable: false },
 | 
					    { id: 'material.numbers', label: 'Material numbers', active: false, sortable: false },
 | 
				
			||||||
@@ -91,20 +91,20 @@ export class SamplesComponent implements OnInit {
 | 
				
			|||||||
    { id: 'added', label: 'Added', active: true, sortable: true }
 | 
					    { id: 'added', label: 'Added', active: true, sortable: true }
 | 
				
			||||||
  ];
 | 
					  ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // combines the 3 different categories
 | 
					  // Combines the 3 different categories
 | 
				
			||||||
  categories = {
 | 
					  categories = {
 | 
				
			||||||
    material: this.materialKeys,
 | 
					    material: this.materialKeys,
 | 
				
			||||||
    condition: this.conditionKeys,
 | 
					    condition: this.conditionKeys,
 | 
				
			||||||
    measurement: this.measurementKeys
 | 
					    measurement: this.measurementKeys
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  isActiveKey: { [key: string]: boolean } = {};  // object to check if key is currently active
 | 
					  isActiveKey: { [key: string]: boolean } = {};  // Object to check if key is currently active
 | 
				
			||||||
  activeKeys: KeyInterface[] = [];  // list of active keys
 | 
					  activeKeys: KeyInterface[] = [];  // List of active keys
 | 
				
			||||||
  sampleDetailsSample: any = null;  // sample for the sample details dialog
 | 
					  sampleDetailsSample: any = null;  // Sample for the sample details dialog
 | 
				
			||||||
  sampleSelect = 0;  // modes: 0 - no selection, 1 - sample edit selection, 2 - validation selection
 | 
					  sampleSelect = 0;  // Modes: 0 - no selection, 1 - sample edit selection, 2 - validation selection
 | 
				
			||||||
  loading = 0;  // number of loading instances
 | 
					  loading = 0;  // Number of loading instances
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // change the way values are displayed
 | 
					  // Change the way values are displayed
 | 
				
			||||||
  valueConverters = {
 | 
					  valueConverters = {
 | 
				
			||||||
    'added': (v: string, _sample) => new Date(v).toLocaleDateString(),
 | 
					    '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': (v: any, _sample: any) => v.sample_references.length > 0 ? Object.keys(v.sample_references).map(r => v.sample_references[r].relation).join(", ") : "",
 | 
				
			||||||
@@ -156,7 +156,7 @@ export class SamplesComponent implements OnInit {
 | 
				
			|||||||
      this.d.arr[collection + 'Templates'].forEach(item => {
 | 
					      this.d.arr[collection + 'Templates'].forEach(item => {
 | 
				
			||||||
        item.parameters.forEach(parameter => {
 | 
					        item.parameters.forEach(parameter => {
 | 
				
			||||||
          const parameterName = encodeURIComponent(parameter.name);
 | 
					          const parameterName = encodeURIComponent(parameter.name);
 | 
				
			||||||
          // exclude spectrum and duplicates
 | 
					          // Exclude spectrum and duplicates
 | 
				
			||||||
          if (parameter.name !== 'dpt' && !templateKeys.find(e => new RegExp('.' + parameterName + '$').test(e.id))) {
 | 
					          if (parameter.name !== 'dpt' && !templateKeys.find(e => new RegExp('.' + parameterName + '$').test(e.id))) {
 | 
				
			||||||
            const collectionNames = {
 | 
					            const collectionNames = {
 | 
				
			||||||
              material: 'material.properties',
 | 
					              material: 'material.properties',
 | 
				
			||||||
@@ -181,22 +181,22 @@ export class SamplesComponent implements OnInit {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      this.categories[collection].splice(this.categories[collection].findIndex(e => e.id === insertBefore), 0, ...templateKeys);
 | 
					      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.categories[collection] = [...this.categories[collection]];  // Complete overwrite array to invoke update in rb-multiselect
 | 
				
			||||||
      this.loadPreferences();
 | 
					      this.loadPreferences();
 | 
				
			||||||
      f();
 | 
					      f();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // set toPage to null to reload first page, queues calls
 | 
					  // Set toPage to null to reload first page, queues calls
 | 
				
			||||||
  loadSamples(options: LoadSamplesOptions = {}, event = null, category?) {
 | 
					  loadSamples(options: LoadSamplesOptions = {}, event = null, category?) {
 | 
				
			||||||
    if (event) {  // adjust active keys
 | 
					    if (event) {  // Adjust active keys
 | 
				
			||||||
      this.categories[category].forEach(key => {
 | 
					      this.categories[category].forEach(key => {
 | 
				
			||||||
        if (event.hasOwnProperty(key.id)) {
 | 
					        if (event.hasOwnProperty(key.id)) {
 | 
				
			||||||
          key.active = event[key.id];
 | 
					          key.active = event[key.id];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      const sortId = this.filters.sort.replace(/(-asc|-desc)/, '');
 | 
					      const sortId = this.filters.sort.replace(/(-asc|-desc)/, '');
 | 
				
			||||||
      if (event.hasOwnProperty(sortId) && !event[sortId]) {  // reset sort if sort field was unselected
 | 
					      if (event.hasOwnProperty(sortId) && !event[sortId]) {  // Reset sort if sort field was unselected
 | 
				
			||||||
        this.setSort('_id-asc');
 | 
					        this.setSort('_id-asc');
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      this.updateActiveKeys();
 | 
					      this.updateActiveKeys();
 | 
				
			||||||
@@ -205,17 +205,17 @@ export class SamplesComponent implements OnInit {
 | 
				
			|||||||
      this.storage.set('currentPage', 1);
 | 
					      this.storage.set('currentPage', 1);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this.loadSamplesQueue.push(options);
 | 
					    this.loadSamplesQueue.push(options);
 | 
				
			||||||
    if (this.loadSamplesQueue.length <= 1) {  // nothing queued up
 | 
					    if (this.loadSamplesQueue.length <= 1) {  // Nothing queued up
 | 
				
			||||||
      this.sampleLoader(this.loadSamplesQueue[0]);
 | 
					      this.sampleLoader(this.loadSamplesQueue[0]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this.storePreferences();
 | 
					    this.storePreferences();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private sampleLoader(options: LoadSamplesOptions) {  // actual loading of the sample, do not call directly
 | 
					  private sampleLoader(options: LoadSamplesOptions) {  // Actual loading of the sample, do not call directly
 | 
				
			||||||
    this.loading++;
 | 
					    this.loading++;
 | 
				
			||||||
    this.api.get(this.sampleUrl({ paging: true, pagingOptions: options }), (sData, err, headers) => {
 | 
					    this.api.get(this.sampleUrl({ paging: true, pagingOptions: options }), (sData, err, headers) => {
 | 
				
			||||||
      this.loading--;
 | 
					      this.loading--;
 | 
				
			||||||
      if (err) {  // remove stored options on error to avoid loop
 | 
					      if (err) {  // Remove stored options on error to avoid loop
 | 
				
			||||||
        this.storage.remove('samplesPreferences');
 | 
					        this.storage.remove('samplesPreferences');
 | 
				
			||||||
        this.api.requestError(err);
 | 
					        this.api.requestError(err);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -227,7 +227,7 @@ export class SamplesComponent implements OnInit {
 | 
				
			|||||||
        this.samples = sData as any;
 | 
					        this.samples = sData as any;
 | 
				
			||||||
        this.storeData();
 | 
					        this.storeData();
 | 
				
			||||||
        this.loadSamplesQueue.shift();
 | 
					        this.loadSamplesQueue.shift();
 | 
				
			||||||
        if (this.loadSamplesQueue.length > 0) {  // execute next queue item
 | 
					        if (this.loadSamplesQueue.length > 0) {  // Execute next queue item
 | 
				
			||||||
          this.sampleLoader(this.loadSamplesQueue[0]);
 | 
					          this.sampleLoader(this.loadSamplesQueue[0]);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -247,13 +247,13 @@ export class SamplesComponent implements OnInit {
 | 
				
			|||||||
    csv?: boolean,
 | 
					    csv?: boolean,
 | 
				
			||||||
    export?: boolean,
 | 
					    export?: boolean,
 | 
				
			||||||
    host?: boolean
 | 
					    host?: boolean
 | 
				
			||||||
  }) {  // return url to fetch samples
 | 
					  }) {  // Return url to fetch samples
 | 
				
			||||||
    // keys which should always be added if export = false
 | 
					    // Keys which should always be added if export = false
 | 
				
			||||||
    const additionalTableKeys = ['material_id', '_id', 'user_id'];
 | 
					    const additionalTableKeys = ['material_id', '_id', 'user_id'];
 | 
				
			||||||
    const query: string[] = [];
 | 
					    const query: string[] = [];
 | 
				
			||||||
    query.push(...Object.keys(this.filters.status).filter(e => this.filters.status[e]).map(e => 'status[]=' + e));
 | 
					    query.push(...Object.keys(this.filters.status).filter(e => this.filters.status[e]).map(e => 'status[]=' + e));
 | 
				
			||||||
    if (options.paging) {
 | 
					    if (options.paging) {
 | 
				
			||||||
      if (this.samples[0]) {  // do not include from-id when page size was changed
 | 
					      if (this.samples[0]) {  // Do not include from-id when page size was changed
 | 
				
			||||||
        if (!options.pagingOptions.firstPage) {
 | 
					        if (!options.pagingOptions.firstPage) {
 | 
				
			||||||
          query.push('from-id=' + this.samples[0]._id);
 | 
					          query.push('from-id=' + this.samples[0]._id);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -267,7 +267,7 @@ export class SamplesComponent implements OnInit {
 | 
				
			|||||||
      query.push('page-size=' + this.filters.pageSize);
 | 
					      query.push('page-size=' + this.filters.pageSize);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    query.push('sort=' + this.filters.sort);
 | 
					    query.push('sort=' + this.filters.sort);
 | 
				
			||||||
    if (options.export) {  // append API key on export
 | 
					    if (options.export) {  // Append API key on export
 | 
				
			||||||
      query.push('key=' + this.d.d.userKey.key);
 | 
					      query.push('key=' + this.d.d.userKey.key);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -280,13 +280,13 @@ export class SamplesComponent implements OnInit {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    query.push(...cloneDeep(this.filters.filters)
 | 
					    query.push(...cloneDeep(this.filters.filters)
 | 
				
			||||||
      .map(e => {
 | 
					      .map(e => {
 | 
				
			||||||
        e.values = e.values.filter(el => el !== '');  // do not include empty values
 | 
					        e.values = e.values.filter(el => el !== '');  // Do not include empty values
 | 
				
			||||||
        if (e.field === 'added') {  // correct timezone
 | 
					        if (e.field === 'added') {  // Correct timezone
 | 
				
			||||||
          e.values = e.values.map(
 | 
					          e.values = e.values.map(
 | 
				
			||||||
            el => new Date(new Date(el).getTime() - new Date(el).getTimezoneOffset() * 60000).toISOString()
 | 
					            el => new Date(new Date(el).getTime() - new Date(el).getTimezoneOffset() * 60000).toISOString()
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (e.mode === 'null') {  // handle null mode
 | 
					        if (e.mode === 'null') {  // Handle null mode
 | 
				
			||||||
          e.mode = 'in';
 | 
					          e.mode = 'in';
 | 
				
			||||||
          e.values = [null, ''];
 | 
					          e.values = [null, ''];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -304,12 +304,12 @@ export class SamplesComponent implements OnInit {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    if (!options.export) {
 | 
					    if (!options.export) {
 | 
				
			||||||
      additionalTableKeys.forEach(key => {
 | 
					      additionalTableKeys.forEach(key => {
 | 
				
			||||||
        if (query.indexOf('fields[]=' + key) < 0) {  // add key if not already added
 | 
					        if (query.indexOf('fields[]=' + key) < 0) {  // Add key if not already added
 | 
				
			||||||
          query.push('fields[]=' + key);
 | 
					          query.push('fields[]=' + key);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    else {  // export options
 | 
					    else {  // Export options
 | 
				
			||||||
      if (options.csv) {
 | 
					      if (options.csv) {
 | 
				
			||||||
        query.push('output=csv');
 | 
					        query.push('output=csv');
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -329,7 +329,7 @@ export class SamplesComponent implements OnInit {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  loadPage(delta) {
 | 
					  loadPage(delta) {
 | 
				
			||||||
    if (!/[0-9]+/.test(delta) || this.page + delta < 1) {  // invalid delta
 | 
					    if (!/[0-9]+/.test(delta) || this.page + delta < 1) {  // Invalid delta
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this.page += delta;
 | 
					    this.page += delta;
 | 
				
			||||||
@@ -393,11 +393,11 @@ export class SamplesComponent implements OnInit {
 | 
				
			|||||||
    this.loadSamples({ firstPage: true });
 | 
					    this.loadSamples({ firstPage: true });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  updateActiveKeys() {  // array with all activeKeys
 | 
					  updateActiveKeys() {  // Array with all activeKeys
 | 
				
			||||||
    this.activeKeys = [];
 | 
					    this.activeKeys = [];
 | 
				
			||||||
    for (let category in this.categories) {
 | 
					    for (let category in this.categories) {
 | 
				
			||||||
      this.activeKeys.push(...this.categories[category].filter(e => e.active));
 | 
					      this.activeKeys.push(...this.categories[category].filter(e => e.active));
 | 
				
			||||||
      this.filters.filters.forEach(filter => {  // disable filters of fields not displayed
 | 
					      this.filters.filters.forEach(filter => {  // Disable filters of fields not displayed
 | 
				
			||||||
        if (!this.isActiveKey[filter.field]) {
 | 
					        if (!this.isActiveKey[filter.field]) {
 | 
				
			||||||
          filter.active = false;
 | 
					          filter.active = false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -413,18 +413,18 @@ export class SamplesComponent implements OnInit {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  sampleDetails(id: string, modal: TemplateRef<any>) {  // show sample details
 | 
					  sampleDetails(id: string, modal: TemplateRef<any>) {  // Show sample details
 | 
				
			||||||
    this.sampleDetailsSample = null;
 | 
					    this.sampleDetailsSample = null;
 | 
				
			||||||
    this.api.get<SampleModel>('/sample/' + id, data => {
 | 
					    this.api.get<SampleModel>('/sample/' + id, data => {
 | 
				
			||||||
      this.sampleDetailsSample = new SampleModel().deserialize(data);
 | 
					      this.sampleDetailsSample = new SampleModel().deserialize(data);
 | 
				
			||||||
      if (data.notes.custom_fields) {  // convert custom_fields for more optimized display
 | 
					      if (data.notes.custom_fields) {  // Convert custom_fields for more optimized display
 | 
				
			||||||
        this.sampleDetailsSample.notes.custom_fields_entries =
 | 
					        this.sampleDetailsSample.notes.custom_fields_entries =
 | 
				
			||||||
          Object.entries(this.sampleDetailsSample.notes.custom_fields);
 | 
					          Object.entries(this.sampleDetailsSample.notes.custom_fields);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      else {
 | 
					      else {
 | 
				
			||||||
        this.sampleDetailsSample.custom_fields_entries = [];
 | 
					        this.sampleDetailsSample.custom_fields_entries = [];
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (Object.keys(data.condition).length) {  // convert condition
 | 
					      if (Object.keys(data.condition).length) {  // Convert condition
 | 
				
			||||||
        this.sampleDetailsSample.condition_entries =
 | 
					        this.sampleDetailsSample.condition_entries =
 | 
				
			||||||
          Object.entries(omit(this.sampleDetailsSample.condition, ['condition_template']))
 | 
					          Object.entries(omit(this.sampleDetailsSample.condition, ['condition_template']))
 | 
				
			||||||
            .map(e => {
 | 
					            .map(e => {
 | 
				
			||||||
@@ -438,7 +438,7 @@ export class SamplesComponent implements OnInit {
 | 
				
			|||||||
        this.sampleDetailsSample.condition_entries = [];
 | 
					        this.sampleDetailsSample.condition_entries = [];
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      this.sampleDetailsSample.measurement_entries = [];
 | 
					      this.sampleDetailsSample.measurement_entries = [];
 | 
				
			||||||
      // convert measurements for more optimized display without dpt
 | 
					      // Convert measurements for more optimized display without dpt
 | 
				
			||||||
      this.sampleDetailsSample.measurements.forEach(measurement => {
 | 
					      this.sampleDetailsSample.measurements.forEach(measurement => {
 | 
				
			||||||
        const name = this.d.id.measurementTemplates[measurement.measurement_template].name;
 | 
					        const name = this.d.id.measurementTemplates[measurement.measurement_template].name;
 | 
				
			||||||
        this.sampleDetailsSample.measurement_entries
 | 
					        this.sampleDetailsSample.measurement_entries
 | 
				
			||||||
@@ -446,7 +446,7 @@ export class SamplesComponent implements OnInit {
 | 
				
			|||||||
            .map(e => ({ name: this.ucFirst(name) + ' ' + e[0], value: e[1] })));
 | 
					            .map(e => ({ name: this.ucFirst(name) + ' ' + e[0], value: e[1] })));
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      new Promise<void>(resolve => {
 | 
					      new Promise<void>(resolve => {
 | 
				
			||||||
        if (data.notes.sample_references.length) {  // load referenced samples if available
 | 
					        if (data.notes.sample_references.length) {  // Load referenced samples if available
 | 
				
			||||||
          let loadingCounter = data.notes.sample_references.length;
 | 
					          let loadingCounter = data.notes.sample_references.length;
 | 
				
			||||||
          this.sampleDetailsSample.notes.sample_references.forEach(reference => {
 | 
					          this.sampleDetailsSample.notes.sample_references.forEach(reference => {
 | 
				
			||||||
            this.api.get<SampleModel>('/sample/' + reference.sample_id, rData => {
 | 
					            this.api.get<SampleModel>('/sample/' + reference.sample_id, rData => {
 | 
				
			||||||
@@ -468,7 +468,7 @@ export class SamplesComponent implements OnInit {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  validate() {
 | 
					  validate() {
 | 
				
			||||||
    if (this.sampleSelect) {  // do actual validation
 | 
					    if (this.sampleSelect) {  // Do actual validation
 | 
				
			||||||
      this.samples.forEach(sample => {
 | 
					      this.samples.forEach(sample => {
 | 
				
			||||||
        if (sample.selected) {
 | 
					        if (sample.selected) {
 | 
				
			||||||
          this.api.put('/sample/validate/' + sample._id);
 | 
					          this.api.put('/sample/validate/' + sample._id);
 | 
				
			||||||
@@ -477,12 +477,12 @@ export class SamplesComponent implements OnInit {
 | 
				
			|||||||
      this.loadSamples();
 | 
					      this.loadSamples();
 | 
				
			||||||
      this.sampleSelect = 0;
 | 
					      this.sampleSelect = 0;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    else {  // get into validation mode
 | 
					    else {  // Get into validation mode
 | 
				
			||||||
      this.sampleSelect = 2;
 | 
					      this.sampleSelect = 2;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  batchEdit() {  // redirect to batch edit
 | 
					  batchEdit() {  // Redirect to batch edit
 | 
				
			||||||
    if (this.sampleSelect) {
 | 
					    if (this.sampleSelect) {
 | 
				
			||||||
      this.router.navigate(['/samples/edit/' + this.samples.filter(e => e.selected).map(e => e._id).join(',')]);
 | 
					      this.router.navigate(['/samples/edit/' + this.samples.filter(e => e.selected).map(e => e._id).join(',')]);
 | 
				
			||||||
      this.sampleSelect = 0;
 | 
					      this.sampleSelect = 0;
 | 
				
			||||||
@@ -503,7 +503,7 @@ export class SamplesComponent implements OnInit {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  selectAll(event) {  // toggle select all except deleted samples
 | 
					  selectAll(event) {  // Toggle select all except deleted samples
 | 
				
			||||||
    this.samples.forEach(sample => {
 | 
					    this.samples.forEach(sample => {
 | 
				
			||||||
      if (sample.status !== 'deleted') {
 | 
					      if (sample.status !== 'deleted') {
 | 
				
			||||||
        sample.selected = event.target.checked;
 | 
					        sample.selected = event.target.checked;
 | 
				
			||||||
@@ -524,13 +524,13 @@ export class SamplesComponent implements OnInit {
 | 
				
			|||||||
    event.stopPropagation();
 | 
					    event.stopPropagation();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  clipboard() {  // copy contents to clipboard
 | 
					  clipboard() {  // Copy contents to clipboard
 | 
				
			||||||
    this.linkarea.nativeElement.select();
 | 
					    this.linkarea.nativeElement.select();
 | 
				
			||||||
    this.linkarea.nativeElement.setSelectionRange(0, 99999);
 | 
					    this.linkarea.nativeElement.setSelectionRange(0, 99999);
 | 
				
			||||||
    document.execCommand('copy');
 | 
					    document.execCommand('copy');
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ucFirst(string) {  // convert first character of string to uppercase
 | 
					  ucFirst(string) {  // Convert first character of string to uppercase
 | 
				
			||||||
    return string[0].toUpperCase() + string.slice(1);
 | 
					    return string[0].toUpperCase() + string.slice(1);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -540,7 +540,7 @@ export class SamplesComponent implements OnInit {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // stores data in a unified way
 | 
					  // Stores data in a unified way
 | 
				
			||||||
  storeData() {
 | 
					  storeData() {
 | 
				
			||||||
    this.data = [];
 | 
					    this.data = [];
 | 
				
			||||||
    this.samples.forEach(sample => {
 | 
					    this.samples.forEach(sample => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,7 +24,7 @@ export class ApiService {
 | 
				
			|||||||
    return this.host;
 | 
					    return this.host;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // main HTTP methods
 | 
					  // Main HTTP methods
 | 
				
			||||||
  get<T>(url, f: (data?: T, err?, headers?) => void = () => {}) {
 | 
					  get<T>(url, f: (data?: T, err?, headers?) => void = () => {}) {
 | 
				
			||||||
    this.requestErrorHandler<T>(this.http.get(this.url(url), this.options()), f);
 | 
					    this.requestErrorHandler<T>(this.http.get(this.url(url), this.options()), f);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -41,25 +41,25 @@ export class ApiService {
 | 
				
			|||||||
    this.requestErrorHandler<T>(this.http.delete(this.url(url), this.options()), f);
 | 
					    this.requestErrorHandler<T>(this.http.delete(this.url(url), this.options()), f);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // execute request and handle errors
 | 
					  // Execute request and handle errors
 | 
				
			||||||
  private requestErrorHandler<T>(observable: Observable<any>, f: (data?: T, err?, headers?) => void) {
 | 
					  private requestErrorHandler<T>(observable: Observable<any>, f: (data?: T, err?, headers?) => void) {
 | 
				
			||||||
    observable.subscribe(data => {  // successful request
 | 
					    observable.subscribe(data => {  // Successful request
 | 
				
			||||||
      f(
 | 
					      f(
 | 
				
			||||||
        data.body,
 | 
					        data.body,
 | 
				
			||||||
        undefined,
 | 
					        undefined,
 | 
				
			||||||
        data.headers.keys().reduce((s, e) => {s[e.toLowerCase()] = data.headers.get(e); return s; }, {})
 | 
					        data.headers.keys().reduce((s, e) => {s[e.toLowerCase()] = data.headers.get(e); return s; }, {})
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    }, err => {  // error
 | 
					    }, err => {  // Error
 | 
				
			||||||
      if (f.length > 1) {  // pass on error
 | 
					      if (f.length > 1) {  // Pass on error
 | 
				
			||||||
        f(undefined, err, undefined);
 | 
					        f(undefined, err, undefined);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      else {  // handle directly
 | 
					      else {  // Handle directly
 | 
				
			||||||
        this.requestError(err);
 | 
					        this.requestError(err);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  requestError(err) {  // network error dialog
 | 
					  requestError(err) {  // Network error dialog
 | 
				
			||||||
    const modalRef = this.modalService.openComponent(ErrorComponent);
 | 
					    const modalRef = this.modalService.openComponent(ErrorComponent);
 | 
				
			||||||
    modalRef.instance.message = 'Network request failed!';
 | 
					    modalRef.instance.message = 'Network request failed!';
 | 
				
			||||||
    const details = [err.error.status];
 | 
					    const details = [err.error.status];
 | 
				
			||||||
@@ -72,7 +72,7 @@ export class ApiService {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private url(url) {  // detect if host was given, otherwise use default host
 | 
					  private url(url) {  // Detect if host was given, otherwise use default host
 | 
				
			||||||
    if (/http[s]?:\/\//.test(url)) {
 | 
					    if (/http[s]?:\/\//.test(url)) {
 | 
				
			||||||
      return url;
 | 
					      return url;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -81,12 +81,12 @@ export class ApiService {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // generate request options
 | 
					  // Generate request options
 | 
				
			||||||
  private options(): {headers: HttpHeaders, observe: 'body'} {
 | 
					  private options(): {headers: HttpHeaders, observe: 'body'} {
 | 
				
			||||||
    return {headers: this.authOptions(), observe: 'response' as 'body'};
 | 
					    return {headers: this.authOptions(), observe: 'response' as 'body'};
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // generate Basic Auth
 | 
					  // Generate Basic Auth
 | 
				
			||||||
  private authOptions(): HttpHeaders {
 | 
					  private authOptions(): HttpHeaders {
 | 
				
			||||||
    const auth = this.storage.get('basicAuth');
 | 
					    const auth = this.storage.get('basicAuth');
 | 
				
			||||||
    if (auth) {
 | 
					    if (auth) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,7 +16,7 @@ export class DataService {
 | 
				
			|||||||
    private api: ApiService
 | 
					    private api: ApiService
 | 
				
			||||||
  ) { }
 | 
					  ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private collectionMap = {  // list of available collections
 | 
					  private collectionMap = {  // List of available collections
 | 
				
			||||||
    materials: {path: '/materials?status[]=validated&status[]=new', model: MaterialModel, type: 'idArray'},
 | 
					    materials: {path: '/materials?status[]=validated&status[]=new', model: MaterialModel, type: 'idArray'},
 | 
				
			||||||
    materialSuppliers: {path: '/material/suppliers', model: null, type: 'idArray'},
 | 
					    materialSuppliers: {path: '/material/suppliers', model: null, type: 'idArray'},
 | 
				
			||||||
    materialGroups: {path: '/material/groups', model: null, type: 'idArray'},
 | 
					    materialGroups: {path: '/material/groups', model: null, type: 'idArray'},
 | 
				
			||||||
@@ -31,31 +31,31 @@ export class DataService {
 | 
				
			|||||||
    userKey: {path: '/user/key', model: BaseModel, type: 'string'}
 | 
					    userKey: {path: '/user/key', model: BaseModel, type: 'string'}
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  arr: {[key: string]: any[]} = {};  // array of data
 | 
					  arr: {[key: string]: any[]} = {};  // Array of data
 | 
				
			||||||
  latest: {[key: string]: any[]} = {};  // array of latest template versions
 | 
					  latest: {[key: string]: any[]} = {};  // Array of latest template versions
 | 
				
			||||||
  id: {[key: string]: {[id: string]: any}} = {};  // data in format _id: data
 | 
					  id: {[key: string]: {[id: string]: any}} = {};  // Data in format _id: data
 | 
				
			||||||
  d: {[key: string]: any} = {};      // data not in array format
 | 
					  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
 | 
					  load(collection, f = () => {}) {  // Load data
 | 
				
			||||||
    if (this.arr[collection]) { // data already loaded
 | 
					    if (this.arr[collection]) { // Data already loaded
 | 
				
			||||||
      f();
 | 
					      f();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    else {  // load data
 | 
					    else {  // Load data
 | 
				
			||||||
      this.api.get<any>(this.collectionMap[collection].path, data => {
 | 
					      this.api.get<any>(this.collectionMap[collection].path, data => {
 | 
				
			||||||
        if (this.collectionMap[collection].type !== 'string') {  // array data
 | 
					        if (this.collectionMap[collection].type !== 'string') {  // Array data
 | 
				
			||||||
          this.arr[collection] = data
 | 
					          this.arr[collection] = data
 | 
				
			||||||
            .map(
 | 
					            .map(
 | 
				
			||||||
              e => this.collectionMap[collection].model ?
 | 
					              e => this.collectionMap[collection].model ?
 | 
				
			||||||
                new this.collectionMap[collection].model().deserialize(e) : e
 | 
					                new this.collectionMap[collection].model().deserialize(e) : e
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
          // load ids
 | 
					          // Load ids
 | 
				
			||||||
          if (this.collectionMap[collection].type === 'idArray' || this.collectionMap[collection].type === 'template') {
 | 
					          if (this.collectionMap[collection].type === 'idArray' || this.collectionMap[collection].type === 'template') {
 | 
				
			||||||
            this.idReload(collection);
 | 
					            this.idReload(collection);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else {  // not array data
 | 
					        else {  // Not array data
 | 
				
			||||||
          this.d[collection] = new this.collectionMap[collection].model().deserialize(data);
 | 
					          this.d[collection] = new this.collectionMap[collection].model().deserialize(data);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        f();
 | 
					        f();
 | 
				
			||||||
@@ -63,13 +63,13 @@ export class DataService {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // generate id object
 | 
					  // Generate id object
 | 
				
			||||||
  idReload(collection) {
 | 
					  idReload(collection) {
 | 
				
			||||||
    this.id[collection] = this.arr[collection].reduce((s, e) => {s[e._id] = e; return s; }, {});
 | 
					    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
 | 
					    if (this.collectionMap[collection].type === 'template') {  // Generate array with latest templates
 | 
				
			||||||
      const tmpTemplates = {};
 | 
					      const tmpTemplates = {};
 | 
				
			||||||
      this.arr[collection].forEach(template => {
 | 
					      this.arr[collection].forEach(template => {
 | 
				
			||||||
        if (tmpTemplates[template.first_id]) {  // already found another version
 | 
					        if (tmpTemplates[template.first_id]) {  // Already found another version
 | 
				
			||||||
          if (template.version > tmpTemplates[template.first_id].version) {
 | 
					          if (template.version > tmpTemplates[template.first_id].version) {
 | 
				
			||||||
            tmpTemplates[template.first_id] = template;
 | 
					            tmpTemplates[template.first_id] = template;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,22 +10,22 @@ import {DataService} from './data.service';
 | 
				
			|||||||
})
 | 
					})
 | 
				
			||||||
export class LoginService implements CanActivate {
 | 
					export class LoginService implements CanActivate {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private pathPermissions = [  // minimum level needed for the specified paths
 | 
					  private pathPermissions = [  // Minimum level needed for the specified paths
 | 
				
			||||||
    {path: 'materials', permission: 'dev'},
 | 
					    {path: 'materials', permission: 'dev'},
 | 
				
			||||||
    {path: 'templates', permission: 'dev'},
 | 
					    {path: 'templates', permission: 'dev'},
 | 
				
			||||||
    {path: 'changelog', permission: 'dev'},
 | 
					    {path: 'changelog', permission: 'dev'},
 | 
				
			||||||
    {path: 'users', permission: 'admin'}
 | 
					    {path: 'users', permission: 'admin'}
 | 
				
			||||||
  ];
 | 
					  ];
 | 
				
			||||||
  readonly levels = [  // all user levels in ascending permissions order
 | 
					  readonly levels = [  // All user levels in ascending permissions order
 | 
				
			||||||
    'predict',
 | 
					    'predict',
 | 
				
			||||||
    'read',
 | 
					    'read',
 | 
				
			||||||
    'write',
 | 
					    'write',
 | 
				
			||||||
    'dev',
 | 
					    'dev',
 | 
				
			||||||
    'admin'
 | 
					    'admin'
 | 
				
			||||||
  ];
 | 
					  ];
 | 
				
			||||||
  // returns true or false depending on whether the user fulfills the minimum level
 | 
					  // Returns true or false depending on whether the user fulfills the minimum level
 | 
				
			||||||
  isLevel: {[level: string]: boolean} = {};
 | 
					  isLevel: {[level: string]: boolean} = {};
 | 
				
			||||||
  hasPrediction = false;  // true if user has prediction models specified
 | 
					  hasPrediction = false;  // True if user has prediction models specified
 | 
				
			||||||
  userId = '';
 | 
					  userId = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private loggedIn;
 | 
					  private loggedIn;
 | 
				
			||||||
@@ -41,22 +41,22 @@ export class LoginService implements CanActivate {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  login(username = '', password = '') {
 | 
					  login(username = '', password = '') {
 | 
				
			||||||
    return new Promise(resolve => {
 | 
					    return new Promise(resolve => {
 | 
				
			||||||
      if (username !== '' || password !== '') {  // some credentials given
 | 
					      if (username !== '' || password !== '') {  // Some credentials given
 | 
				
			||||||
        let credentials: string[];
 | 
					        let credentials: string[];
 | 
				
			||||||
        const credentialString: string = this.storage.get('basicAuth');
 | 
					        const credentialString: string = this.storage.get('basicAuth');
 | 
				
			||||||
        if (credentialString) {  // found stored credentials
 | 
					        if (credentialString) {  // Found stored credentials
 | 
				
			||||||
          credentials = atob(credentialString).split(':');
 | 
					          credentials = atob(credentialString).split(':');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else {
 | 
					        else {
 | 
				
			||||||
          credentials = ['', ''];
 | 
					          credentials = ['', ''];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (username !== '' && password !== '') {  // all credentials given
 | 
					        if (username !== '' && password !== '') {  // All credentials given
 | 
				
			||||||
          this.storage.set('basicAuth', btoa(username + ':' + password));
 | 
					          this.storage.set('basicAuth', btoa(username + ':' + password));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else if (username !== '') {  // username given
 | 
					        else if (username !== '') {  // Username given
 | 
				
			||||||
          this.storage.set('basicAuth', btoa(username + ':' + credentials[1]));
 | 
					          this.storage.set('basicAuth', btoa(username + ':' + credentials[1]));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else if (password !== '') {  // password given
 | 
					        else if (password !== '') {  // Password given
 | 
				
			||||||
          this.storage.set('basicAuth', btoa(credentials[0] + ':' + password));
 | 
					          this.storage.set('basicAuth', btoa(credentials[0] + ':' + password));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -66,7 +66,7 @@ export class LoginService implements CanActivate {
 | 
				
			|||||||
            this.loggedIn = true;
 | 
					            this.loggedIn = true;
 | 
				
			||||||
            this.levels.forEach(level => {
 | 
					            this.levels.forEach(level => {
 | 
				
			||||||
              this.isLevel[level] = this.levels.indexOf(data.level) >= this.levels.indexOf(level);
 | 
					              this.isLevel[level] = this.levels.indexOf(data.level) >= this.levels.indexOf(level);
 | 
				
			||||||
              if (this.isLevel.dev) {  // set hasPrediction
 | 
					              if (this.isLevel.dev) {  // Set hasPrediction
 | 
				
			||||||
                this.hasPrediction = true;
 | 
					                this.hasPrediction = true;
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
              else {
 | 
					              else {
 | 
				
			||||||
@@ -100,7 +100,7 @@ export class LoginService implements CanActivate {
 | 
				
			|||||||
    this.hasPrediction = false;
 | 
					    this.hasPrediction = false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // canActivate for Angular routing
 | 
					  // CanActivate for Angular routing
 | 
				
			||||||
  canActivate(route: ActivatedRouteSnapshot = null, state: RouterStateSnapshot = null): Observable<boolean> {
 | 
					  canActivate(route: ActivatedRouteSnapshot = null, state: RouterStateSnapshot = null): Observable<boolean> {
 | 
				
			||||||
    return new Observable<boolean>(observer => {
 | 
					    return new Observable<boolean>(observer => {
 | 
				
			||||||
      new Promise(resolve => {
 | 
					      new Promise(resolve => {
 | 
				
			||||||
@@ -114,7 +114,7 @@ export class LoginService implements CanActivate {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }).then(res => {
 | 
					      }).then(res => {
 | 
				
			||||||
        const pathPermission = this.pathPermissions.find(e => e.path.indexOf(route.url[0].path) >= 0);
 | 
					        const pathPermission = this.pathPermissions.find(e => e.path.indexOf(route.url[0].path) >= 0);
 | 
				
			||||||
        // check if level is permitted for path
 | 
					        // Check if level is permitted for path
 | 
				
			||||||
        const ok = res && (!pathPermission || this.isLevel[pathPermission.permission]);
 | 
					        const ok = res && (!pathPermission || this.isLevel[pathPermission.permission]);
 | 
				
			||||||
        observer.next(ok);
 | 
					        observer.next(ok);
 | 
				
			||||||
        observer.complete();
 | 
					        observer.complete();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,7 +19,7 @@ export class ValidationService {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  constructor() { }
 | 
					  constructor() { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  generate(method, args) {  // generate a Validator function
 | 
					  generate(method, args) {  // Generate a Validator function
 | 
				
			||||||
    return (control: AbstractControl): {[key: string]: any} | null => {
 | 
					    return (control: AbstractControl): {[key: string]: any} | null => {
 | 
				
			||||||
      let ok;
 | 
					      let ok;
 | 
				
			||||||
      let error;
 | 
					      let error;
 | 
				
			||||||
@@ -63,7 +63,7 @@ export class ValidationService {
 | 
				
			|||||||
    return {ok: true, error: ''};
 | 
					    return {ok: true, error: ''};
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  stringOf(data, list) {  // string must be in list
 | 
					  stringOf(data, list) {  // String must be in list
 | 
				
			||||||
    const {ignore, error} = Joi.string().allow('').valid(...list.map(e => e.toString())).validate(data);
 | 
					    const {ignore, error} = Joi.string().allow('').valid(...list.map(e => e.toString())).validate(data);
 | 
				
			||||||
    if (error) {
 | 
					    if (error) {
 | 
				
			||||||
      return {ok: false, error: 'must be one of ' + list.join(', ')};
 | 
					      return {ok: false, error: 'must be one of ' + list.join(', ')};
 | 
				
			||||||
@@ -71,7 +71,7 @@ export class ValidationService {
 | 
				
			|||||||
    return {ok: true, error: ''};
 | 
					    return {ok: true, error: ''};
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  stringNin(data, list) {  // string must not be in list
 | 
					  stringNin(data, list) {  // String must not be in list
 | 
				
			||||||
    const {ignore, error} = Joi.string().invalid(...list).validate(data);
 | 
					    const {ignore, error} = Joi.string().invalid(...list).validate(data);
 | 
				
			||||||
    if (error) {
 | 
					    if (error) {
 | 
				
			||||||
      return {ok: false, error: 'value not allowed'};
 | 
					      return {ok: false, error: 'value not allowed'};
 | 
				
			||||||
@@ -79,7 +79,7 @@ export class ValidationService {
 | 
				
			|||||||
    return {ok: true, error: ''};
 | 
					    return {ok: true, error: ''};
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  stringLength(data, length) {  // string with maximum length
 | 
					  stringLength(data, length) {  // String with maximum length
 | 
				
			||||||
    const {ignore, error} = Joi.string().max(length).allow('').validate(data);
 | 
					    const {ignore, error} = Joi.string().max(length).allow('').validate(data);
 | 
				
			||||||
    if (error) {
 | 
					    if (error) {
 | 
				
			||||||
      return {ok: false, error: 'must contain max ' + length + ' characters'};
 | 
					      return {ok: false, error: 'must contain max ' + length + ' characters'};
 | 
				
			||||||
@@ -87,7 +87,7 @@ export class ValidationService {
 | 
				
			|||||||
    return {ok: true, error: ''};
 | 
					    return {ok: true, error: ''};
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  minMax(data, min, max) {  // number between min and max
 | 
					  minMax(data, min, max) {  // Number between min and max
 | 
				
			||||||
    const {ignore, error} = Joi.number().allow('').min(min).max(max).validate(data);
 | 
					    const {ignore, error} = Joi.number().allow('').min(min).max(max).validate(data);
 | 
				
			||||||
    if (error) {
 | 
					    if (error) {
 | 
				
			||||||
      return {ok: false, error: `must be between ${min} and ${max}`};
 | 
					      return {ok: false, error: `must be between ${min} and ${max}`};
 | 
				
			||||||
@@ -95,7 +95,7 @@ export class ValidationService {
 | 
				
			|||||||
    return {ok: true, error: ''};
 | 
					    return {ok: true, error: ''};
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  min(data, min) {  // number above min
 | 
					  min(data, min) {  // Number above min
 | 
				
			||||||
    const {ignore, error} = Joi.number().allow('').min(min).validate(data);
 | 
					    const {ignore, error} = Joi.number().allow('').min(min).validate(data);
 | 
				
			||||||
    if (error) {
 | 
					    if (error) {
 | 
				
			||||||
      return {ok: false, error: `must not be below ${min}`};
 | 
					      return {ok: false, error: `must not be below ${min}`};
 | 
				
			||||||
@@ -103,7 +103,7 @@ export class ValidationService {
 | 
				
			|||||||
    return {ok: true, error: ''};
 | 
					    return {ok: true, error: ''};
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  max(data, max) {  // number below max
 | 
					  max(data, max) {  // Number below max
 | 
				
			||||||
    const {ignore, error} = Joi.number().allow('').max(max).validate(data);
 | 
					    const {ignore, error} = Joi.number().allow('').max(max).validate(data);
 | 
				
			||||||
    if (error) {
 | 
					    if (error) {
 | 
				
			||||||
      return {ok: false, error: `must not be above ${max}`};
 | 
					      return {ok: false, error: `must not be above ${max}`};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,9 +12,9 @@ import {LoginService} from '../services/login.service';
 | 
				
			|||||||
})
 | 
					})
 | 
				
			||||||
export class SettingsComponent implements OnInit {
 | 
					export class SettingsComponent implements OnInit {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  user: UserModel = new UserModel();  // user to edit
 | 
					  user: UserModel = new UserModel();  // User to edit
 | 
				
			||||||
  password = '';                      // new password
 | 
					  password = '';                      // New password
 | 
				
			||||||
  messageUser = '';                   // messages for user and pass part
 | 
					  messageUser = '';                   // Messages for user and pass part
 | 
				
			||||||
  messagePass = '';
 | 
					  messagePass = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
@@ -34,7 +34,7 @@ export class SettingsComponent implements OnInit {
 | 
				
			|||||||
      if (err) {
 | 
					      if (err) {
 | 
				
			||||||
        this.messageUser = err.error.status;
 | 
					        this.messageUser = err.error.status;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      else {  // login with new credentials
 | 
					      else {  // Login with new credentials
 | 
				
			||||||
        this.login.login(data.name).then(res => {
 | 
					        this.login.login(data.name).then(res => {
 | 
				
			||||||
          if (res) {
 | 
					          if (res) {
 | 
				
			||||||
            this.router.navigate(['/samples']);
 | 
					            this.router.navigate(['/samples']);
 | 
				
			||||||
@@ -52,7 +52,7 @@ export class SettingsComponent implements OnInit {
 | 
				
			|||||||
      if (err) {
 | 
					      if (err) {
 | 
				
			||||||
        this.messagePass = err.error.status;
 | 
					        this.messagePass = err.error.status;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      else {  // login with new credentials
 | 
					      else {  // Login with new credentials
 | 
				
			||||||
        this.login.login('', this.password).then(res => {
 | 
					        this.login.login('', this.password).then(res => {
 | 
				
			||||||
          if (res) {
 | 
					          if (res) {
 | 
				
			||||||
            this.router.navigate(['/samples']);
 | 
					            this.router.navigate(['/samples']);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,11 +28,11 @@ import {DataService} from '../services/data.service';
 | 
				
			|||||||
})
 | 
					})
 | 
				
			||||||
export class TemplatesComponent implements OnInit {
 | 
					export class TemplatesComponent implements OnInit {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  collection = 'measurement';       // collection to view
 | 
					  collection = 'measurement';       // Collection to view
 | 
				
			||||||
  templates: TemplateModel[] = [];  // all templates of current collection
 | 
					  templates: TemplateModel[] = [];  // All templates of current collection
 | 
				
			||||||
  templateGroups: {[first_id: string]: TemplateModel[]} = {};  // templates grouped by first_id
 | 
					  templateGroups: {[first_id: string]: TemplateModel[]} = {};  // Templates grouped by first_id
 | 
				
			||||||
  templateEdit: {[first_id: string]: TemplateModel} = {};      // latest template of each first_id for editing
 | 
					  templateEdit: {[first_id: string]: TemplateModel} = {};      // Latest template of each first_id for editing
 | 
				
			||||||
  groupsView: {  // grouped templates
 | 
					  groupsView: {  // Grouped templates
 | 
				
			||||||
    first_id: string,
 | 
					    first_id: string,
 | 
				
			||||||
    name: string,
 | 
					    name: string,
 | 
				
			||||||
    version: number,
 | 
					    version: number,
 | 
				
			||||||
@@ -61,7 +61,7 @@ export class TemplatesComponent implements OnInit {
 | 
				
			|||||||
  templateFormat() {
 | 
					  templateFormat() {
 | 
				
			||||||
    this.templateGroups = {};
 | 
					    this.templateGroups = {};
 | 
				
			||||||
    this.templateEdit = {};
 | 
					    this.templateEdit = {};
 | 
				
			||||||
    this.templates.forEach(template => {  // group templates
 | 
					    this.templates.forEach(template => {  // Group templates
 | 
				
			||||||
      if (this.templateGroups[template.first_id]) {
 | 
					      if (this.templateGroups[template.first_id]) {
 | 
				
			||||||
        this.templateGroups[template.first_id].push(template);
 | 
					        this.templateGroups[template.first_id].push(template);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -100,7 +100,7 @@ export class TemplatesComponent implements OnInit {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
    if (valid) {
 | 
					    if (valid) {
 | 
				
			||||||
      const sendData = {name: template.name, parameters: template.parameters.map(e => omit(e, ['rangeString']))};
 | 
					      const sendData = {name: template.name, parameters: template.parameters.map(e => omit(e, ['rangeString']))};
 | 
				
			||||||
      if (first_id === 'null') {  // new template
 | 
					      if (first_id === 'null') {  // New template
 | 
				
			||||||
        this.api.post<TemplateModel>(`/template/${this.collection}/new`, sendData, () => {
 | 
					        this.api.post<TemplateModel>(`/template/${this.collection}/new`, sendData, () => {
 | 
				
			||||||
          delete this.d.arr[this.collection + 'Templates'];
 | 
					          delete this.d.arr[this.collection + 'Templates'];
 | 
				
			||||||
          this.d.load(this.collection + 'Templates', () => {
 | 
					          this.d.load(this.collection + 'Templates', () => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,12 +13,12 @@ import {DataService} from '../services/data.service';
 | 
				
			|||||||
})
 | 
					})
 | 
				
			||||||
export class UsersComponent implements OnInit {
 | 
					export class UsersComponent implements OnInit {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  users: UserModel[] = [];           // all active users
 | 
					  users: UserModel[] = [];           // All active users
 | 
				
			||||||
  deletedUsers: UserModel[] = [];    // all deleted users
 | 
					  deletedUsers: UserModel[] = [];    // All deleted users
 | 
				
			||||||
  newUser: UserModel | null = null;  // data of new user
 | 
					  newUser: UserModel | null = null;  // Data of new user
 | 
				
			||||||
  newUserPass = '';                  // password of new user
 | 
					  newUserPass = '';                  // Password of new user
 | 
				
			||||||
  modelSelect: {id: string, name: string}[] = [];  // list of all models for selection
 | 
					  modelSelect: {id: string, name: string}[] = [];  // List of all models for selection
 | 
				
			||||||
  modelIds: {[id: string]: string} = {};           // all models by id
 | 
					  modelIds: {[id: string]: string} = {};           // All models by id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    private api: ApiService,
 | 
					    private api: ApiService,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,4 +13,4 @@ export const environment = {
 | 
				
			|||||||
 * This import should be commented out in production mode because it will have a negative impact
 | 
					 * This import should be commented out in production mode because it will have a negative impact
 | 
				
			||||||
 * on performance if an error is thrown.
 | 
					 * on performance if an error is thrown.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
// import 'zone.js/dist/zone-error';  // Included with Angular CLI.
 | 
					// Import 'zone.js/dist/zone-error';  // Included with Angular CLI.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,14 +19,14 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
 | 
					/** IE10 and IE11 requires the following for NgClass support on SVG elements */
 | 
				
			||||||
// import 'classlist.js';  // Run `npm install --save classlist.js`.
 | 
					// Import 'classlist.js';  // Run `npm install --save classlist.js`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Web Animations `@angular/platform-browser/animations`
 | 
					 * Web Animations `@angular/platform-browser/animations`
 | 
				
			||||||
 * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
 | 
					 * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
 | 
				
			||||||
 * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
 | 
					 * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
// import 'web-animations-js';  // Run `npm install --save web-animations-js`.
 | 
					// Import 'web-animations-js';  // Run `npm install --save web-animations-js`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * By default, zone.js will patch all possible macroTask and DomEvents
 | 
					 * By default, zone.js will patch all possible macroTask and DomEvents
 | 
				
			||||||
@@ -41,9 +41,9 @@
 | 
				
			|||||||
 *
 | 
					 *
 | 
				
			||||||
 * The following flags will work for all browsers.
 | 
					 * The following flags will work for all browsers.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
 | 
					 * (window as any).__Zone_disable_requestAnimationFrame = true; // Disable patch requestAnimationFrame
 | 
				
			||||||
 * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
 | 
					 * (window as any).__Zone_disable_on_property = true; // Disable patch onProperty such as onclick
 | 
				
			||||||
 * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
 | 
					 * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // Disable patch specified eventNames
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
 | 
					 *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
 | 
				
			||||||
 *  with the following flag, it will bypass `zone.js` patch for IE/Edge
 | 
					 *  with the following flag, it will bypass `zone.js` patch for IE/Edge
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user