From b0b3f1ab6f7e4607cdc30ac6fd8f17a819a6f81d Mon Sep 17 00:00:00 2001 From: Julian Matschinske <ge93nar@mytum.de> Date: Wed, 15 Apr 2020 17:04:19 +0200 Subject: [PATCH] Add custom proteins --- src/app/analysis.service.ts | 20 ++++- src/app/app.module.ts | 2 + .../custom-proteins.component.html | 44 +++++++++++ .../custom-proteins.component.scss | 10 +++ .../custom-proteins.component.spec.ts | 27 +++++++ .../custom-proteins.component.ts | 75 +++++++++++++++++++ .../explorer-page.component.html | 44 +++++++++-- .../explorer-page.component.scss | 9 +++ .../explorer-page/explorer-page.component.ts | 2 + 9 files changed, 226 insertions(+), 7 deletions(-) create mode 100644 src/app/components/custom-proteins/custom-proteins.component.html create mode 100644 src/app/components/custom-proteins/custom-proteins.component.scss create mode 100644 src/app/components/custom-proteins/custom-proteins.component.spec.ts create mode 100644 src/app/components/custom-proteins/custom-proteins.component.ts diff --git a/src/app/analysis.service.ts b/src/app/analysis.service.ts index 0866109c..bccdf162 100644 --- a/src/app/analysis.service.ts +++ b/src/app/analysis.service.ts @@ -103,7 +103,7 @@ export class AnalysisService { this.selectListSubject.next({items: removedWrappers, selected: false}); } - public addAllHostProteins(nodes, proteins) { + public addVisibleHostProteins(nodes, proteins) { const items: Wrapper[] = []; const visibleIds = new Set<string>(nodes.getIds()); for (const protein of proteins) { @@ -117,7 +117,7 @@ export class AnalysisService { this.selectListSubject.next({items, selected: true}); } - public addAllViralProteins(nodes, viralProteins) { + public addVisibleViralProteins(nodes, viralProteins) { const items: Wrapper[] = []; const visibleIds = new Set<string>(nodes.getIds()); for (const viralProtein of viralProteins) { @@ -131,6 +131,22 @@ export class AnalysisService { this.selectListSubject.next({items, selected: true}); } + public removeAllHostProteins() { + const items: Wrapper[] = Array.from(this.selectedItems.values()).filter(p => p.type === 'host'); + for (const wrapper of items) { + this.selectedItems.delete(wrapper.nodeId); + } + this.selectListSubject.next({items, selected: false}); + } + + public removeAllViralProteins() { + const items: Wrapper[] = Array.from(this.selectedItems.values()).filter(p => p.type === 'virus'); + for (const wrapper of items) { + this.selectedItems.delete(wrapper.nodeId); + } + this.selectListSubject.next({items, selected: false}); + } + resetSelection() { this.selectListSubject.next({items: [], selected: null}); this.selectedItems.clear(); diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 7b184586..37fdc628 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -19,6 +19,7 @@ import {AnalysisWindowComponent} from './components/analysis-window/analysis-win import {TaskListComponent} from './components/task-list/task-list.component'; import {ToggleComponent} from './components/toggle/toggle.component'; import {InfoBoxComponent} from './components/info-box/info-box.component'; +import {CustomProteinsComponent} from './components/custom-proteins/custom-proteins.component'; import {AnalysisService} from './analysis.service'; @@ -36,6 +37,7 @@ import {AnalysisService} from './analysis.service'; TaskListComponent, ToggleComponent, InfoBoxComponent, + CustomProteinsComponent, ], imports: [ BrowserModule, diff --git a/src/app/components/custom-proteins/custom-proteins.component.html b/src/app/components/custom-proteins/custom-proteins.component.html new file mode 100644 index 00000000..90736648 --- /dev/null +++ b/src/app/components/custom-proteins/custom-proteins.component.html @@ -0,0 +1,44 @@ +<div class="modal" [class.is-active]="show"> + <div class="modal-background"></div> + <div class="modal-card"> + <header class="modal-card-head"> + <p class="modal-card-title"> + <span class="icon"><i class="fa fa-dna"></i></span> + Add Custom Proteins + </p> + <button (click)="close()" class="delete" aria-label="close"></button> + </header> + <section class="modal-card-body"> + <div class="notification is-success" *ngIf="itemsAdded.length > 0"> + {{itemsAdded.length}} host proteins have been added to the selection. + </div> + <div class="notification is-warning" *ngIf="notFound.length > 0"> + The following {{notFound.length}} Uniprot IDs could not be found and have been ignored: + <ul><li class="not-found" *ngFor="let nf of notFound">{{nf}}</li></ul> + </div> + <div class="field"> + <label class="label" for="protein-list">List of Uniprot IDs</label> + <div class="control"> + <textarea class="input" [ngModel]="textList" (ngModelChange)="changeTextList($event)" id="protein-list"></textarea> + </div> + </div> + <p *ngIf="proteins"> + Proteins parsed: {{proteins.length}} + </p> + </section> + <footer class="modal-card-foot"> + <button (click)="addProteins();" class="button is-success is-rounded has-tooltip" + data-tooltip="Add list of proteins to the selection." + [disabled]="proteins.length === 0"> + <span class="icon"> + <i class="fa fa-plus"></i> + </span> + <span> + Add + </span> + </button> + <button (click)="close()" class="button is-rounded has-tooltip" data-tooltip="Close the current window.">Close + </button> + </footer> + </div> +</div> diff --git a/src/app/components/custom-proteins/custom-proteins.component.scss b/src/app/components/custom-proteins/custom-proteins.component.scss new file mode 100644 index 00000000..a403b397 --- /dev/null +++ b/src/app/components/custom-proteins/custom-proteins.component.scss @@ -0,0 +1,10 @@ +#protein-list { + height: 150px; +} + +.not-found { + padding: 4px; + margin: 4px; + border-radius: 3px; + background-color: rgba(255, 255, 255, 0.33); +} diff --git a/src/app/components/custom-proteins/custom-proteins.component.spec.ts b/src/app/components/custom-proteins/custom-proteins.component.spec.ts new file mode 100644 index 00000000..2c5e7741 --- /dev/null +++ b/src/app/components/custom-proteins/custom-proteins.component.spec.ts @@ -0,0 +1,27 @@ +import {async, ComponentFixture, TestBed} from '@angular/core/testing'; + +import {CustomProteinsComponent} from './custom-proteins.component'; +import {HttpClientModule} from '@angular/common/http'; + +describe('CustomProteinsComponent', () => { + let component: CustomProteinsComponent; + let fixture: ComponentFixture<CustomProteinsComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [CustomProteinsComponent], + imports: [HttpClientModule], + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CustomProteinsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/custom-proteins/custom-proteins.component.ts b/src/app/components/custom-proteins/custom-proteins.component.ts new file mode 100644 index 00000000..483ce2d9 --- /dev/null +++ b/src/app/components/custom-proteins/custom-proteins.component.ts @@ -0,0 +1,75 @@ +import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; +import {HttpClient} from '@angular/common/http'; +import {environment} from '../../../environments/environment'; +import {getWrapperFromProtein, Wrapper} from '../../interfaces'; +import {AnalysisService} from '../../analysis.service'; + +@Component({ + selector: 'app-custom-proteins', + templateUrl: './custom-proteins.component.html', + styleUrls: ['./custom-proteins.component.scss'] +}) +export class CustomProteinsComponent implements OnInit { + + @Input() + public show = false; + @Output() + public showChange = new EventEmitter<boolean>(); + + public textList = ''; + public proteins: Array<string> = []; + public notFound: Array<string> = []; + public itemsAdded: Array<Wrapper> = []; + + constructor(private http: HttpClient, private analysis: AnalysisService) { } + + ngOnInit(): void { + } + + public close() { + this.show = false; + this.showChange.emit(this.show); + } + + public async addProteins() { + this.notFound = []; + this.itemsAdded = []; + const proteins = this.proteins; + this.changeTextList(''); + const result = await this.http.post<any>(`${environment.backend}query_proteins/`, proteins).toPromise(); + this.notFound = result.notFound; + const details = result.details; + const items = []; + for (const detail of details) { + items.push(getWrapperFromProtein(detail)); + } + this.itemsAdded = items; + this.analysis.addItems(items); + } + + public changeTextList(textList) { + this.textList = textList; + if (!textList) { + this.proteins = []; + return; + } + + const separators = ['\n', ',', ';', ' ']; + + let proteins; + for (const sep of separators) { + if (textList.indexOf(sep) === -1) { + continue; + } + proteins = textList.split(sep).map(ac => ac.trim()).filter(ac => !!ac); + break; + } + + if (!proteins) { + proteins = [textList]; + } + + this.proteins = proteins; + } + +} diff --git a/src/app/pages/explorer-page/explorer-page.component.html b/src/app/pages/explorer-page/explorer-page.component.html index 78343dc7..ad18cfdc 100644 --- a/src/app/pages/explorer-page/explorer-page.component.html +++ b/src/app/pages/explorer-page/explorer-page.component.html @@ -4,6 +4,9 @@ [dataset]="selectedDataset.backendId"> </app-launch-analysis> + <app-custom-proteins [(show)]="showCustomProteinsDialog"> + </app-custom-proteins> + <div class="covex explorer"> <div class="covex left-window"> @@ -387,7 +390,7 @@ </span> </a> </header> - <div *ngIf="collapseSelection"> + <div *ngIf="collapseSelection" class="seed-selection"> <div class="card-content overflow"> <table class="table selection-table" *ngIf="analysis.getCount() > 0"> <thead> @@ -421,25 +424,56 @@ </i> </div> <footer class="card-footer"> - <a (click)="analysis.addAllHostProteins(currentViewNodes, currentViewProteins)" + <a (click)="analysis.addVisibleHostProteins(currentViewNodes, currentViewProteins)" class="card-footer-item has-text-success" data-tooltip="Add all visible host proteins."> <span class="icon"> - <i class="fa fa-plus"></i> + <i class="fa fa-eye"></i> </span> <span> Host Proteins </span> </a> - <a (click)="analysis.addAllViralProteins(currentViewNodes, currentViewEffects)" + <a (click)="analysis.addVisibleViralProteins(currentViewNodes, currentViewEffects)" class="card-footer-item has-text-success" data-tooltip="Add all visible viral proteins."> <span class="icon"> - <i class="fa fa-plus"></i> + <i class="fa fa-eye"></i> + </span> + <span> + Viral Proteins + </span> + </a> + </footer> + <footer class="card-footer"> + <a (click)="analysis.removeAllHostProteins()" + class="card-footer-item has-text-danger" data-tooltip="Remove all host proteins."> + <span class="icon"> + <i class="fa fa-minus"></i> + </span> + <span> + Host Proteins + </span> + </a> + <a (click)="analysis.removeAllViralProteins()" + class="card-footer-item has-text-danger" data-tooltip="Remove all viral proteins."> + <span class="icon"> + <i class="fa fa-minus"></i> </span> <span> Viral Proteins </span> </a> </footer> + <footer class="card-footer"> + <a (click)="showCustomProteinsDialog = true" class="card-footer-item has-text-primary" + data-tooltip="Add a custom list of proteins."> + <span class="icon"> + <i class="fa fa-upload"></i> + </span> + <span> + Custom Proteins + </span> + </a> + </footer> <footer class="card-footer"> <a (click)="analysis.resetSelection()" class="card-footer-item has-text-danger" data-tooltip="Remove all entries of the selection."> diff --git a/src/app/pages/explorer-page/explorer-page.component.scss b/src/app/pages/explorer-page/explorer-page.component.scss index 6042c453..5d4f9d9e 100644 --- a/src/app/pages/explorer-page/explorer-page.component.scss +++ b/src/app/pages/explorer-page/explorer-page.component.scss @@ -41,3 +41,12 @@ padding: 15px; font-weight: bold; } + +.seed-selection { + .card-footer { + font-size: 12px; + a { + padding: 3px; + } + } +} diff --git a/src/app/pages/explorer-page/explorer-page.component.ts b/src/app/pages/explorer-page/explorer-page.component.ts index db0d9275..bca79bba 100644 --- a/src/app/pages/explorer-page/explorer-page.component.ts +++ b/src/app/pages/explorer-page/explorer-page.component.ts @@ -60,6 +60,8 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { public showAnalysisDialog = false; public analysisDialogTarget: 'drug' | 'drug-target'; + public showCustomProteinsDialog = false; + public selectedAnalysisToken: string | null = null; public currentDataset = []; -- GitLab