src/app/components/compare/compare.component.ts
OnInit
selector | app-compare |
styleUrls | ./compare.component.scss |
templateUrl | ./compare.component.html |
Properties |
|
Methods |
Inputs |
Outputs |
Accessors |
constructor(fb: UntypedFormBuilder, ga: GoogleAnalyticsService)
|
|||||||||
Parameters :
|
compareSheets | |
Type : Observable<CompareData[]>
|
|
closeCompare | |
Type : EventEmitter
|
|
compareData | |
Type : EventEmitter
|
|
addCompareSheetRow |
addCompareSheetRow()
|
Returns :
void
|
atLeastOnePhoneRequired | ||||||
atLeastOnePhoneRequired(group: UntypedFormGroup)
|
||||||
Parameters :
Returns :
literal type | null
|
checkLinkFormat | ||||||
checkLinkFormat(url: string)
|
||||||
Parameters :
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 :
Returns :
UntypedFormGroup
|
doesFormHaveError |
doesFormHaveError()
|
Returns :
boolean
|
getRandomColor |
getRandomColor()
|
Returns :
string
|
markFormGroupTouched | ||||||
markFormGroupTouched(formGroup: UntypedFormGroup)
|
||||||
Parameters :
Returns :
void
|
removeCompareSheetRow | ||||||
removeCompareSheetRow(i: number)
|
||||||
Parameters :
Returns :
void
|
upload | |||||||||
upload(fileFormDataEvent: FormData, control: AbstractControl)
|
|||||||||
Parameters :
Returns :
void
|
Public fb |
Type : UntypedFormBuilder
|
formGroup |
Type : UntypedFormGroup
|
formSheets |
Type : UntypedFormArray
|
formValid |
Default value : true
|
Public ga |
Type : GoogleAnalyticsService
|
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">* </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;
}