diff --git a/client/src/app/core/services/http.service.ts b/client/src/app/core/services/http.service.ts index cdc619d7..496bbd69 100644 --- a/client/src/app/core/services/http.service.ts +++ b/client/src/app/core/services/http.service.ts @@ -20,7 +20,7 @@ export abstract class HttpService { protected _makeRequest( serverID: string, requestType: string, - body: any, + body: any = {}, makeUrl = ((url: string) => url), ): Observable { const args = {serverID, requestType, body}; diff --git a/client/src/app/gene/components/family-detail.component.ts b/client/src/app/gene/components/family-detail.component.ts index 5b7c0352..ff3db99b 100644 --- a/client/src/app/gene/components/family-detail.component.ts +++ b/client/src/app/gene/components/family-detail.component.ts @@ -4,7 +4,6 @@ import { Observable, Subject } from 'rxjs'; import { take, takeUntil } from 'rxjs/operators'; // App import { AppConfig } from '@gcv/app.config'; -import { DetailsService } from '@gcv/gene/services'; import { Server } from '@gcv/core/models'; import { Track } from '@gcv/gene/models'; @@ -29,21 +28,23 @@ import { Track } from '@gcv/gene/models'; }) export class FamilyDetailComponent implements OnInit, OnDestroy { - private _serverIDs = AppConfig.SERVERS.map(s => s.id); - @Input() family: string; @Input() tracks: Observable; + private _serverIDs = AppConfig.SERVERS.map(s => s.id); private _destroy: Subject = new Subject(); genes: string[] = []; - geneString: string = ""; + geneString: string = ''; familyTreeLinks: any[] = []; - constructor(private detailsService: DetailsService) { } - // Angular hooks + ngOnDestroy() { + this._destroy.next(true); + this._destroy.complete(); + } + ngOnInit() { this.tracks .pipe( @@ -52,11 +53,6 @@ export class FamilyDetailComponent implements OnInit, OnDestroy { .subscribe((tracks) => this._process(tracks)); } - ngOnDestroy() { - this._destroy.next(true); - this._destroy.complete(); - } - // private private _process(tracks) { diff --git a/client/src/app/gene/components/gene-detail.component.ts b/client/src/app/gene/components/gene-detail.component.ts index 23bba5df..f7ce1d0a 100644 --- a/client/src/app/gene/components/gene-detail.component.ts +++ b/client/src/app/gene/components/gene-detail.component.ts @@ -1,128 +1,67 @@ // Angular -import { Component, ComponentFactory, ComponentFactoryResolver, ComponentRef, - Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewContainerRef, ViewChild } from '@angular/core'; +import { Component, + Input, OnDestroy, OnInit } from '@angular/core'; import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; +import { take, takeUntil } from 'rxjs/operators'; // App -import { AlertComponent } from '@gcv/core/components'; import { AppConfig } from '@gcv/app.config'; import { Server } from '@gcv/core/models'; -import { Alert, Gene } from '@gcv/gene/models'; -import { DetailsService } from '@gcv/gene/services'; +import { GeneService } from '@gcv/gene/services'; @Component({ selector: 'gene-detail', - styles: [` - #alerts { - position: absolute; - left: 0; - right: 0; - } - `], + styles: [ '' ], template: ` -
- -
-

{{gene.name}}

-

Family: {{gene.family}}

-

Search for similar contexts

+

{{ gene }}

+

Family: {{ family }}

+

Search for similar contexts

`, }) -export class GeneDetailComponent implements OnChanges, OnDestroy, OnInit { - @Input() gene: Gene; - - @ViewChild('alerts', {read: ViewContainerRef, static: true}) alerts: ViewContainerRef; +export class GeneDetailComponent implements OnDestroy, OnInit { - links: any[]; - familyTreeLink: string; + @Input() gene: string; + @Input() family: string; + @Input() source: string private _serverIDs = AppConfig.SERVERS.map(s => s.id); + private _destroy: Subject = new Subject(); - // emits when the component is destroyed - private destroy: Subject; - - constructor( - private resolver: ComponentFactoryResolver, - private detailsService: DetailsService - ) { - this.destroy = new Subject(); - } + links: any[] = []; + familyTreeLink: string = ''; - ngOnChanges(changes: SimpleChanges): void { - this.links = undefined; - if (this.gene !== undefined) { - this.links = undefined; + constructor(private _geneService: GeneService) { } - this.familyTreeLink = undefined; - const idx = this._serverIDs.indexOf(this.gene.source); - if (idx !== -1) { - const s: Server = AppConfig.SERVERS[idx]; - if (s.hasOwnProperty('familyTreeLink')) { - this.familyTreeLink = s.familyTreeLink.url + this.gene.family; - } - } - - this.detailsService.getGeneDetails(this.gene, (links) => { - this.links = links; - }); - } - } + // Angular hooks ngOnDestroy(): void { - this.destroy.next(true); - this.destroy.complete(); + this._destroy.next(true); + this._destroy.complete(); } ngOnInit(): void { - this.detailsService.requests - .pipe(takeUntil(this.destroy)) - .subscribe(([args, request]) => { - this._requestToAlertComponent(args.serverID, request, 'links', this.alerts); - }); + const idx = this._serverIDs.indexOf(this.source); + if (idx !== -1) { + const server: Server = AppConfig.SERVERS[idx]; + if (server.hasOwnProperty('familyTreeLink')) { + this.familyTreeLink = server.familyTreeLink.url + this.family; + } + } + this._geneService.getGeneDetails(this.gene, this.source) + .pipe( + takeUntil(this._destroy), + take(1)) + .subscribe((links) => this._process(links)); } - private _requestToAlertComponent(serverID, request, what, container) { - const source = AppConfig.getServer(serverID).name; - const factory: ComponentFactory = this.resolver.resolveComponentFactory(AlertComponent); - const componentRef: ComponentRef = container.createComponent(factory); - // EVIL: Angular doesn't have a defined method for hooking dynamic components into - // the Angular lifecycle so we must explicitly call ngOnChanges whenever a change - // occurs. Even worse, there is no hook for the Output directive, so we must - // shoehorn the desired functionality in! - componentRef.instance.close = function(componentRef) { - componentRef.destroy(); - }.bind(this, componentRef); - componentRef.instance.float = true; - componentRef.instance.alert = new Alert( - 'info', - 'Loading ' + what + ' from \'' + source + '\'', - {spinner: true}, - ); - componentRef.instance.ngOnChanges({}); - request - .pipe(takeUntil(componentRef.instance.onClose)) - .subscribe( - (response) => { - componentRef.instance.alert = new Alert( - 'success', - 'Successfully loaded ' + what + ' from \'' + source + '\'', - {closable: true, autoClose: 3}, - ); - componentRef.instance.ngOnChanges({}); - }, - (error) => { - componentRef.instance.alert = new Alert( - 'danger', - 'Failed to load ' + what + ' from \'' + source + '\'', - {closable: true}, - ); - componentRef.instance.ngOnChanges({}); - }); + // private + + private _process(links: any[]) { + this.links = links; } } diff --git a/client/src/app/gene/components/gene.component.ts b/client/src/app/gene/components/gene.component.ts index 78a6f621..d9984d0b 100644 --- a/client/src/app/gene/components/gene.component.ts +++ b/client/src/app/gene/components/gene.component.ts @@ -8,6 +8,7 @@ import { GoldenLayoutDirective } from '@gcv/gene/directives'; import { Track } from '@gcv/gene/models'; import { GeneService, MicroTracksService } from '@gcv/gene/services'; import { FamilyDetailComponent } from './family-detail.component'; +import { GeneDetailComponent } from './gene-detail.component'; import { LegendComponent } from './legend.component'; import { MacroComponent } from './macro.component'; import { MicroComponent } from './micro.component'; @@ -27,6 +28,7 @@ export class GeneComponent implements AfterViewInit, OnDestroy { private _microLegend: Observable<{name: string, id: string}[]>; layoutComponents = [ + {component: GeneDetailComponent, name: 'gene'}, {component: FamilyDetailComponent, name: 'family'}, {component: LegendComponent, name: 'legend'}, {component: MacroComponent, name: 'macro'}, @@ -94,6 +96,17 @@ export class GeneComponent implements AfterViewInit, OnDestroy { }); } + private _geneDetailConfigFactory(gene, family, source) { + const id = `gene:${gene}`; + return { + type: 'component', + componentName: 'gene', + id: id, + title: `Gene ${gene}`, + componentState: {inputs: {gene, family, source}} + }; + } + private _familyDetailConfigFactory(family) { const id = `family:${family}`; return { @@ -160,12 +173,14 @@ export class GeneComponent implements AfterViewInit, OnDestroy { options }, outputs: { - plotClick: (track) => { + plotClick: ({track}) => { const plotConfig = this._plotConfigFactory(clusterID, track); this.goldenLayoutDirective.stackItem(plotConfig, id); }, - geneClick: (name) => { - console.log(name); + geneClick: ({gene, family, source}) => { + const geneConfig = + this._geneDetailConfigFactory(gene, family, source); + this.goldenLayoutDirective.stackItem(geneConfig, id); }, nameClick: (track) => { console.log(track); diff --git a/client/src/app/gene/components/micro.component.ts b/client/src/app/gene/components/micro.component.ts index a8defb63..6fae286f 100644 --- a/client/src/app/gene/components/micro.component.ts +++ b/client/src/app/gene/components/micro.component.ts @@ -55,11 +55,11 @@ export class MicroComponent implements AfterViewInit, OnDestroy { // public emitPlot(track) { - this.plotClick.emit(track); + this.plotClick.emit({track}); } - emitGene(name) { - this.geneClick.emit(name); + emitGene(gene, family, source) { + this.geneClick.emit({gene, family, source}); } emitName(track) { @@ -105,6 +105,7 @@ export class MicroComponent implements AfterViewInit, OnDestroy { return tracks.map((t, j) => { // make track const track = { + source: t.source, genus: t.genus, species: t.species, chromosome_name: t.name, @@ -159,7 +160,7 @@ export class MicroComponent implements AfterViewInit, OnDestroy { this._destroyViewer(); let options = { plotClick: (t, i) => this.emitPlot(tracks[i]), - geneClick: (g, t) => this.emitGene(g.name), + geneClick: (t, g, i) => this.emitGene(g.name, g.family, t.source), nameClick: (t, i) => this.emitName(tracks[i]) }; options = Object.assign(options, this.options); diff --git a/client/src/app/gene/gene.module.ts b/client/src/app/gene/gene.module.ts index c120edcd..242cc565 100644 --- a/client/src/app/gene/gene.module.ts +++ b/client/src/app/gene/gene.module.ts @@ -22,6 +22,7 @@ import { GeneRoutingModule } from '@gcv/gene/gene-routing.module'; @NgModule({ declarations: [...fromComponents.components, ...fromDirectives.directives], entryComponents: [ + fromComponents.GeneDetailComponent, fromComponents.FamilyDetailComponent, fromComponents.LegendComponent, fromComponents.MacroComponent, diff --git a/client/src/app/gene/services/details.service.ts b/client/src/app/gene/services/details.service.ts deleted file mode 100644 index 3cb5834c..00000000 --- a/client/src/app/gene/services/details.service.ts +++ /dev/null @@ -1,29 +0,0 @@ -// Angular -import { HttpClient } from '@angular/common/http'; -import { Injectable } from '@angular/core'; -// app -import { Gene } from '@gcv/gene/models'; -import { HttpService } from '@gcv/core/services/http.service'; - -@Injectable() -export class DetailsService extends HttpService { - - constructor(private _http: HttpClient) { - super(_http); - } - - getGeneDetails(gene: Gene, success: (e) => void): void { - this._makeRequest( - gene.source, - 'geneLinks', - {}, - (url: string) => url + gene.name + '/json', - ) - .subscribe( - success, - (error) => { - console.log(error); - } - ); - } -} diff --git a/client/src/app/gene/services/gene.service.ts b/client/src/app/gene/services/gene.service.ts index 2be7279a..268543f2 100644 --- a/client/src/app/gene/services/gene.service.ts +++ b/client/src/app/gene/services/gene.service.ts @@ -36,4 +36,10 @@ export class GeneService extends HttpService { select(fromGene.getAlignedMicroTrackClusterGenes(id)) ); } + + // fetches source specific details for the given gene + getGeneDetails(gene: string, source: string): Observable { + const makeUrl = (url: string) => url + gene + '/json'; + return this._makeRequest(source, 'geneLinks', {}, makeUrl); + } } diff --git a/client/src/app/gene/services/index.ts b/client/src/app/gene/services/index.ts index 11565d3f..392fceb0 100644 --- a/client/src/app/gene/services/index.ts +++ b/client/src/app/gene/services/index.ts @@ -1,6 +1,5 @@ import { FilterService } from './filter.service'; import { ChromosomeService } from './chromosome.service'; -import { DetailsService } from './details.service'; import { GeneService } from './gene.service'; import { MicroTracksService } from './micro-tracks.service'; import { PairwiseBlocksService} from './pairwise-blocks.service'; @@ -8,7 +7,6 @@ import { PlotsService } from './plots.service'; export const services: any[] = [ ChromosomeService, - DetailsService, FilterService, GeneService, MicroTracksService, @@ -17,7 +15,6 @@ export const services: any[] = [ ]; export * from './chromosome.service'; -export * from './details.service'; export * from './filter.service'; export * from './gene.service'; export * from './micro-tracks.service'; diff --git a/client/src/assets/js/gcv/visualization/micro.ts b/client/src/assets/js/gcv/visualization/micro.ts index 31064ae3..e58f1f11 100644 --- a/client/src/assets/js/gcv/visualization/micro.ts +++ b/client/src/assets/js/gcv/visualization/micro.ts @@ -93,7 +93,7 @@ export class Micro extends Visualizer { this.options.highlight = this.options.highlight || []; this.options.selectiveColoring = this.options.selectiveColoring; this.options.nameClick = this.options.nameClick || ((c) => { /* noop */ }); - this.options.geneClick = this.options.geneClick || ((b) => { /* noop */ }); + this.options.geneClick = this.options.geneClick || ((t, g, i) => { /* noop */ }); this.options.plotClick = this.options.plotClick; this.options.autoResize = this.options.autoResize || false; this.options.hoverDelay = this.options.hoverDelay || 500; @@ -281,7 +281,7 @@ export class Micro extends Visualizer { .style("cursor", "pointer") .on("mouseover", (g) => this.setTimeout(publishGeneEvent("select", g))) .on("mouseout", (g) => this.clearTimeout(publishGeneEvent("deselect", g))) - .on("click", (g) => obj.options.geneClick(g, t)) + .on("click", (g, i) => obj.options.geneClick(t, g, i)) // add optional HTML attributes to gene elelements .addHTMLAttributes(); // add genes to the gene groups