File

src/app/components/compare/compare.component.ts

Implements

OnInit

Metadata

Index

Properties
Methods
Inputs
Outputs
Accessors

Constructor

constructor(fb: UntypedFormBuilder, ga: GoogleAnalyticsService)
Parameters :
Name Type Optional
fb UntypedFormBuilder No
ga GoogleAnalyticsService No

Inputs

compareSheets
Type : Observable<CompareData[]>

Outputs

closeCompare
Type : EventEmitter
compareData
Type : EventEmitter

Methods

addCompareSheetRow
addCompareSheetRow()
Returns : void
atLeastOnePhoneRequired
atLeastOnePhoneRequired(group: UntypedFormGroup)
Parameters :
Name Type Optional
group UntypedFormGroup No
Returns : literal type | null
checkLinkFormat
checkLinkFormat(url: string)
Parameters :
Name Type Optional
url string No
Returns : { sheetID: any; gid: any; csvUrl: string; }
compare
compare()
Returns : void
createCompareForm
createCompareForm(link: string, color?: string, title: string, description: string, formData?: FormData, fileName?: string)
Parameters :
Name Type Optional Default value
link string No ''
color string Yes
title string No ''
description string No ''
formData FormData Yes
fileName string Yes
Returns : UntypedFormGroup
doesFormHaveError
doesFormHaveError()
Returns : boolean
getRandomColor
getRandomColor()
Returns : string
markFormGroupTouched
markFormGroupTouched(formGroup: UntypedFormGroup)
Parameters :
Name Type Optional
formGroup UntypedFormGroup No
Returns : void
removeCompareSheetRow
removeCompareSheetRow(i: number)
Parameters :
Name Type Optional
i number No
Returns : void
upload
upload(fileFormDataEvent: FormData, control: AbstractControl)
Parameters :
Name Type Optional
fileFormDataEvent FormData No
control AbstractControl No
Returns : void

Properties

Public fb
Type : UntypedFormBuilder
formGroup
Type : UntypedFormGroup
formSheets
Type : UntypedFormArray
formValid
Default value : true
Public ga
Type : GoogleAnalyticsService

Accessors

CSControls
getCSControls()
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { AbstractControl, UntypedFormArray, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { GoogleAnalyticsService } from 'ngx-google-analytics';
import { Observable } from 'rxjs';
import { GaAction, GaCategory, GaCompareInfo } from '../../models/ga.model';
import { CompareData } from '../../models/sheet.model';

@Component({
  selector: 'app-compare',
  templateUrl: './compare.component.html',
  styleUrls: ['./compare.component.scss'],
})
export class CompareComponent implements OnInit {
  @Output() closeCompare = new EventEmitter<boolean>();
  @Output() compareData = new EventEmitter<CompareData[]>();

  @Input() compareSheets!: Observable<CompareData[]>;

  formGroup!: UntypedFormGroup;
  formSheets!: UntypedFormArray;
  formValid = true;

  constructor(
    public fb: UntypedFormBuilder,
    public ga: GoogleAnalyticsService,
  ) {}

  ngOnInit(): void {
    this.formGroup = this.fb.group({
      sheets: this.fb.array([]),
    });
    this.formSheets = this.formGroup.get('sheets') as UntypedFormArray;

    this.compareSheets.subscribe((sheets) => {
      if (sheets.length) {
        for (const source of sheets) {
          this.formSheets.push(
            this.createCompareForm(
              source.link,
              source.color,
              source.title,
              source.description,
              source.formData,
              source.fileName,
            ),
          );
        }
      } else {
        this.formSheets.push(this.createCompareForm());
      }
    });

    this.formGroup.valueChanges.subscribe(() => {
      const formArray = this.formGroup.controls['sheets'] as UntypedFormArray;
      formArray.controls.forEach((control) => {
        const sheet = control as UntypedFormGroup;
        const file = sheet.controls['formData'];
        const link = sheet.controls['link'];
        if (file.value != null) {
          link.clearValidators();
          link.updateValueAndValidity({ emitEvent: false });
        }
      });
    });
  }

  upload(fileFormDataEvent: FormData, control: AbstractControl) {
    const sheet = control as UntypedFormGroup;
    sheet.controls['formData'].setValue(fileFormDataEvent);
  }

  markFormGroupTouched(formGroup: UntypedFormGroup) {
    Object.values(formGroup.controls).forEach((control) => {
      const form = control as UntypedFormGroup;
      form.markAsTouched();

      if (form.controls) {
        this.markFormGroupTouched(form);
      }
    });
  }

  compare() {
    this.markFormGroupTouched(this.formGroup);
    this.formValid = this.formGroup.status === 'VALID';
    if (this.formGroup.status !== 'VALID') {
      return;
    }
    const data: CompareData[] = [];
    for (const [idx, sheet] of this.formGroup.value.sheets.entries()) {
      if (sheet.title === '') {
        sheet.title = `Sheet ${idx + 1}`;
      }

      data.push({
        ...sheet,
        sheetId: this.checkLinkFormat(sheet.link)?.sheetID,
        gid: this.checkLinkFormat(sheet.link)?.gid,
        csvUrl: this.checkLinkFormat(sheet.link)?.csvUrl,
      });

      const sheetInfo: GaCompareInfo = {
        title: sheet.title,
        desc: sheet.description,
        link: sheet.link,
        color: sheet.color,
      };
      this.ga.event(GaAction.CLICK, GaCategory.COMPARE, `Add new sheet to compare: ${JSON.stringify(sheetInfo)}`);
    }

    this.compareData.emit(data);
  }

  checkLinkFormat(url: string) {
    if (url.startsWith('https://docs.google.com/spreadsheets/d/')) {
      const splitUrl = url.split('/');
      if (splitUrl.length === 7) {
        return {
          sheetID: splitUrl[5],
          gid: splitUrl[6].split('=')[1],
          csvUrl: '',
        };
      }
    }
    return {
      sheetID: '0',
      gid: '0',
      csvUrl: url,
    };
  }

  createCompareForm(
    link = '',
    color?: string,
    title = '',
    description = '',
    formData?: FormData,
    fileName?: string,
  ): UntypedFormGroup {
    if (!color) {
      color = this.getRandomColor();
    }

    return this.fb.group(
      {
        title: [title],
        description: [description],
        link: [
          link,
          Validators.compose([Validators.required, Validators.pattern(/\/([\w-_]{15,})\/(.*?gid=(\d+))?|\w*csv$/)]),
        ],
        color: [color],
        formData: [formData],
        fileName: [fileName],
      },
      { validators: [this.atLeastOnePhoneRequired] },
    );
  }

  atLeastOnePhoneRequired(group: UntypedFormGroup): { [s: string]: boolean } | null {
    if (group) {
      if (group.controls['link'].value || group.controls['fileName'].value) {
        return null;
      }
    }
    return { error: true };
  }

  get CSControls() {
    return this.formGroup.get('sheets') as UntypedFormArray;
  }

  getRandomColor() {
    const letters = '3456789BC'.split('');
    let color = '#';
    for (let i = 0; i < 6; i++) {
      color += letters[Math.floor(Math.random() * letters.length)];
    }
    return color;
  }

  doesFormHaveError() {
    (this.formGroup.controls['sheets'].value as UntypedFormGroup[]).forEach((sheet) => {
      // mark as touched for all controls
      sheet.controls['link'].markAsTouched();
    });
    return this.formGroup.status !== 'VALID';
  }

  addCompareSheetRow() {
    const sheet = this.createCompareForm();
    this.formSheets.push(sheet);
    this.ga.event(GaAction.CLICK, GaCategory.COMPARE, 'Add new compare row', undefined);
  }

  removeCompareSheetRow(i: number) {
    this.formSheets.removeAt(i);
    this.ga.event(GaAction.CLICK, GaCategory.COMPARE, 'Delete compare row', i);
  }
}
<app-sidenav>
  <div header>
    <app-sidenav-header
      [title]="'Compare Data'"
      (closeSideNav)="closeCompare.emit()"
      [tooltipString]="'Link your own data to compare it with the Master Data Tables'"
    ></app-sidenav-header>
  </div>

  <div body>
    <div class="px-3">
      <mat-expansion-panel [expanded]="true" class="mepNoPadding">
        <mat-expansion-panel-header>
          <mat-panel-title>
            <div class="instruction-title">Instructions</div>
          </mat-panel-title>
        </mat-expansion-panel-header>

        <div class="mt-1 text-muted">
          <ul class="pl-3">
            <li class="text-justify">
              Through this feature, you can upload your own data (ASCT+B to ASCT+B, ASCT+B to experimental data, or
              ASCT+B to OMAP) by pasting the Browser URL address for the Google sheet of your data in the fields
              mentioned below.
            </li>
            <li class="text-justify">
              In order to successfully link your sheet, make sure the sheet has public access by selecting Share --> Get
              link --> selecting "Anyone with the link" option and "Viewer" privileges, select "Done" to save.
            </li>

            <li class="text-justify">
              Please make sure your data follows the appropriate data format:
              <ul class="pl-3">
                <li class="text-justify">
                  <a
                    href="https://docs.google.com/spreadsheets/d/1smQ8_3F-KSRlY7bmozsoM1EonQL4ahNaXP7zeQFf3Ko/edit#gid=0"
                    target="_blank"
                    >ASCT+B Table template</a
                  >
                </li>
                <li class="text-justify">
                  <a
                    href="https://docs.google.com/spreadsheets/d/1DE4Bh2PI7mgaMciMOky2OTduNwYNRU-DyYQPT8szJ-Y/edit#gid=0"
                    target="_blank"
                    >OMAP compare template</a
                  >
                </li>
              </ul>
            </li>
          </ul>
        </div>
        <hr />
        <div class="optional-info">
          <div class="rounded-blob">New</div>
          <div>Organ Mapping Antibody Panel files (CSV) are now supported!</div>
        </div>
      </mat-expansion-panel>
    </div>

    <div class="content px-3">
      <div class="mt-3">
        <span class="required-field-disclaimer">* required field</span>
      </div>

      <form [formGroup]="formGroup" class="mt-4 px-2">
        <div formArrayName="sheets">
          <div *ngFor="let sheet of CSControls.controls; let i = index" class="cc py-3">
            <div [formGroupName]="i">
              <div class="w-100 title-sheet-container">
                <div class="w-75 mr-1 flex-container">
                  <p>
                    <mat-form-field appearance="fill">
                      <mat-label>Title</mat-label>
                      <input matInput placeholder="Sheet {{ i + 1 }}" formControlName="title" />
                      <mat-hint>A title for your sheet</mat-hint>
                    </mat-form-field>
                  </p>
                </div>
                <div class="title-sheet-sub-container">
                  <div class="w-75">
                    <p>
                      <mat-form-field appearance="fill">
                        <mat-label>Description</mat-label>
                        <input
                          matInput
                          placeholder="This data maps amazing structures!"
                          formControlName="description"
                        />
                        <mat-hint>A suitable description</mat-hint>
                      </mat-form-field>
                    </p>
                  </div>
                  <div class="ml-2">
                    <button
                      mat-icon-button
                      [disabled]="formGroup.value.sheets.length === 1"
                      (click)="removeCompareSheetRow(i)"
                    >
                      <mat-icon color="red">delete</mat-icon>
                    </button>
                  </div>
                </div>
              </div>

              <div class="pick-color-container">
                <mat-label class="pick-color-title">Pick Color</mat-label>
                <input
                  type="color"
                  class="w-100 form-control pick-color-textbox"
                  formControlName="color"
                  [style.backgroundColor]="sheet.get('color')?.value"
                />
              </div>

              <div class="data-upload-title">
                Data
                <p class="red">* &nbsp;</p>
                <span class="data-disclaimer-text"> ( Upload a csv file or link to the data)</span>
              </div>

              <div class="w-100 mt-4 sheet-link-container">
                <div class="w-100 flex-container">
                  <p>
                    <mat-form-field appearance="fill" class="w-75">
                      <mat-label>Google Sheet (or CSV) Link</mat-label>
                      <input class="w-100" matInput placeholder="Enter link..." formControlName="link" />
                      <mat-hint>Enter Browser URL address for your public Google Sheet (or CSV)</mat-hint>
                    </mat-form-field>
                  </p>
                </div>

                <div class="title-sheet-sub-container">
                  <div class="w-100">
                    <p>
                      <app-file-upload
                        formControlName="fileName"
                        (fileFormDataEvent)="upload($event, sheet)"
                      ></app-file-upload>
                    </p>
                  </div>
                </div>
              </div>
            </div>
            <hr />
          </div>
        </div>
      </form>
    </div>
  </div>

  <div footer>
    <div class="mt-2" class="button-container">
      <button
        mat-flat-button
        color="primary"
        (click)="compare()"
        class="compare-button"
        [ngClass]="{ 'compare-button-color': !formValid }"
      >
        Compare
      </button>
      <button mat-flat-button class="add-button" (click)="addCompareSheetRow()">
        <mat-icon>add</mat-icon>
        Add
      </button>
    </div>
  </div>
</app-sidenav>

./compare.component.scss

// .cc {
//   border-radius: 8px;
//   box-shadow: 3px 3px 40px 30px #ececec;
// }

.link-input-field {
  width: 100%;
}

input[type='color'] {
  -webkit-appearance: none;
  border: none;
  outline: none;
}

::ng-deep input[type='color']::-webkit-color-swatch-wrapper {
  padding: 0;
}

input[type='color']::-webkit-color-swatch {
  border: none;
}

.content {
  flex: 1 1;
  overflow: auto;
}

.mat-expansion-panel:not([class*='mat-elevation-z']) {
  box-shadow: none;
}

.mat-expansion-panel-header:not(.compare) {
  background: rgb(228, 228, 228) !important;
}

.mat-expansion-panel-header:hover {
  opacity: 0.85 !important;
}

.instruction-title {
  font-weight: 600;
  font-size: 10pt;
}

.required-field-disclaimer {
  font-size: 0.625rem;
  color: #f44336;
}

.title-sheet-container {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.title-sheet-sub-container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex: 0.5;
}

.flex-container {
  flex: 0.5;
}

.file-upload {
  color: #757575;
}

.sheet-link-container {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.pick-color-container {
  width: 4rem;
  margin-top: 1.125rem !important;
  margin-bottom: 1.125rem !important;

  .pick-color-title {
    font-size: 0.625rem;
    color: grey;
  }

  .pick-color-textbox {
    height: 1.875rem;
    margin-top: 0.25rem;
  }
}

.button-container {
  width: 100%;
  justify-content: space-between;
  display: flex;
}

.compare-button {
  border-radius: 0.5rem !important;
}

.compare-button-color {
  background-color: #f44336 !important;
}

.add-button {
  border: 0.063rem solid rgb(196, 196, 196);
  color: grey;
  border-radius: 0.5rem !important;
}

.data-upload-title {
  margin-top: 1.5rem !important;
  color: #444a65;

  .data-disclaimer-text {
    font-size: 0.625rem;
    color: #757575;
  }
}

.red {
  color: red;
  display: inline;
}

.optional-info {
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  grid-gap: 10px;
}

.rounded-blob {
  align-items: center;
  height: 1.5rem;
  width: 2.5rem;
  background-color: #c2cae5;
  border-radius: 100px;
  text-align: center;
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""