From bc46ac26f7d34c34b8775f38d85381b9df23e0b1 Mon Sep 17 00:00:00 2001 From: AndiMajore <andi.majore@googlemail.com> Date: Wed, 5 Apr 2023 19:18:00 +0200 Subject: [PATCH] working on create view from selection feature --- src/app/app.module.ts | 2 + .../analysis-panel.component.html | 4 +- .../analysis-panel.component.ts | 463 +++++++++--------- .../view-list/view-list.component.html | 75 +++ .../view-list/view-list.component.scss | 0 .../view-list/view-list.component.spec.ts | 25 + .../view-list/view-list.component.ts | 22 + src/app/config.ts | 2 + .../explorer-page.component.html | 163 ++++-- .../explorer-page/explorer-page.component.ts | 36 +- src/app/services/analysis/analysis.service.ts | 51 ++ 11 files changed, 564 insertions(+), 279 deletions(-) create mode 100644 src/app/components/analysis-panel/view-list/view-list.component.html create mode 100644 src/app/components/analysis-panel/view-list/view-list.component.scss create mode 100644 src/app/components/analysis-panel/view-list/view-list.component.spec.ts create mode 100644 src/app/components/analysis-panel/view-list/view-list.component.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 4715e5a0..b8e8bd93 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -54,6 +54,7 @@ import { NetworkOverviewComponent } from './pages/explorer-page/network-overview import { InfoTileEdgeComponent } from './components/info-tile-edge/info-tile-edge/info-tile-edge.component'; import { NetworkEmptyWarningComponent } from './components/network-empty-warning/network-empty-warning.component'; import { BugReportComponent } from './components/bug-report/bug-report.component'; +import { ViewListComponent } from './components/analysis-panel/view-list/view-list.component'; @NgModule({ @@ -97,6 +98,7 @@ import { BugReportComponent } from './components/bug-report/bug-report.component InfoTileEdgeComponent, NetworkEmptyWarningComponent, BugReportComponent, + ViewListComponent, ], imports: [ BrowserModule, diff --git a/src/app/components/analysis-panel/analysis-panel.component.html b/src/app/components/analysis-panel/analysis-panel.component.html index 35cff928..8677a27f 100644 --- a/src/app/components/analysis-panel/analysis-panel.component.html +++ b/src/app/components/analysis-panel/analysis-panel.component.html @@ -122,7 +122,7 @@ </div> <div class="tab-content meta parameter-tab" - *ngIf="task && task.info.done" + *ngIf="task && task.info && task.info.done" [class.is-visible]="tab === 'meta'" > <div class="columns m-1"> @@ -385,7 +385,7 @@ </div> <div class="content tab-content scrollable table-tab" - *ngIf="task && task.info.done" + *ngIf="task && task.info && task.info.done" [class.is-visible]="tab === 'table'" > <div *ngIf="task.info.target === 'drug'"> diff --git a/src/app/components/analysis-panel/analysis-panel.component.ts b/src/app/components/analysis-panel/analysis-panel.component.ts index b9b152d6..3dd07172 100644 --- a/src/app/components/analysis-panel/analysis-panel.component.ts +++ b/src/app/components/analysis-panel/analysis-panel.component.ts @@ -55,6 +55,7 @@ export class AnalysisPanelComponent implements OnInit, OnChanges, AfterViewInit @ViewChild('networkWithLegend', {static: false}) networkWithLegendEl: ElementRef; @Input() token: string | null = null; + @Input() tokenType: string | null = null; @Output() tokenChange = new EventEmitter<string | null>(); @@ -115,7 +116,7 @@ export class AnalysisPanelComponent implements OnInit, OnChanges, AfterViewInit constructor(public legendService: LegendService, public networkHandler: NetworkHandlerService, public drugstoneConfig: DrugstoneConfigService, private http: HttpClient, public analysis: AnalysisService, public netex: NetexControllerService, public loadingScreen: LoadingScreenService) { try { this.versionString = version; - }catch (e){ + } catch (e) { } } @@ -146,250 +147,258 @@ export class AnalysisPanelComponent implements OnInit, OnChanges, AfterViewInit private async refresh() { if (this.token) { - this.loadingScreen.stateUpdate(true); - this.task = await this.getTask(this.token); - this.analysis.switchSelection(this.token); - - if (this.task.info.algorithm === 'degree') { - this.tableDrugScoreTooltip = - 'Normalized number of direct interactions of the drug with the seeds. ' + - 'The higher the score, the more relevant the drug.'; - this.tableProteinScoreTooltip = - 'Normalized number of direct interactions of the protein with the seeds. ' + - 'The higher the score, the more relevant the protein.'; - } else if (this.task.info.algorithm === 'closeness' || this.task.info.algorithm === 'quick' || this.task.info.algorithm === 'super') { - this.tableDrugScoreTooltip = - 'Normalized inverse mean distance of the drug to the seeds. ' + - 'The higher the score, the more relevant the drug.'; - this.tableProteinScoreTooltip = - 'Normalized inverse mean distance of the protein to the seeds. ' + - 'The higher the score, the more relevant the protein.'; - } else if (this.task.info.algorithm === 'trustrank') { - this.tableDrugScoreTooltip = - 'Amount of ‘trust’ on the drug at termination of the algorithm. ' + - 'The higher the score, the more relevant the drug.'; - this.tableProteinScoreTooltip = - 'Amount of ‘trust’ on the protein at termination of the algorithm. ' + - 'The higher the score, the more relevant the protein.'; - } else if (this.task.info.algorithm === 'proximity') { - this.tableDrugScoreTooltip = - 'Empirical z-score of mean minimum distance between the drug’s targets and the seeds. ' + - 'The lower the score, the more relevant the drug.'; - this.tableProteinScoreTooltip = - 'Empirical z-score of mean minimum distance between the drug’s targets and the seeds. ' + - 'The lower the score, the more relevant the drug.'; - } + if (this.tokenType === 'view') { + this.loadingScreen.stateUpdate(true); + this.task = await this.getView(this.token); + console.log(this.task) + this.analysis.switchSelection(this.token); + this.loadingScreen.stateUpdate(false); + } else { + this.loadingScreen.stateUpdate(true); + this.task = await this.getTask(this.token); + this.analysis.switchSelection(this.token); + + if (this.task.info.algorithm === 'degree') { + this.tableDrugScoreTooltip = + 'Normalized number of direct interactions of the drug with the seeds. ' + + 'The higher the score, the more relevant the drug.'; + this.tableProteinScoreTooltip = + 'Normalized number of direct interactions of the protein with the seeds. ' + + 'The higher the score, the more relevant the protein.'; + } else if (this.task.info.algorithm === 'closeness' || this.task.info.algorithm === 'quick' || this.task.info.algorithm === 'super') { + this.tableDrugScoreTooltip = + 'Normalized inverse mean distance of the drug to the seeds. ' + + 'The higher the score, the more relevant the drug.'; + this.tableProteinScoreTooltip = + 'Normalized inverse mean distance of the protein to the seeds. ' + + 'The higher the score, the more relevant the protein.'; + } else if (this.task.info.algorithm === 'trustrank') { + this.tableDrugScoreTooltip = + 'Amount of ‘trust’ on the drug at termination of the algorithm. ' + + 'The higher the score, the more relevant the drug.'; + this.tableProteinScoreTooltip = + 'Amount of ‘trust’ on the protein at termination of the algorithm. ' + + 'The higher the score, the more relevant the protein.'; + } else if (this.task.info.algorithm === 'proximity') { + this.tableDrugScoreTooltip = + 'Empirical z-score of mean minimum distance between the drug’s targets and the seeds. ' + + 'The lower the score, the more relevant the drug.'; + this.tableProteinScoreTooltip = + 'Empirical z-score of mean minimum distance between the drug’s targets and the seeds. ' + + 'The lower the score, the more relevant the drug.'; + } - if (this.task && this.task.info.done) { - - this.loading = true; - this.netex.getTaskResult(this.token).then(async result => { - this.drugstoneConfig.set_analysisConfig(result.parameters.config); - this.result = result; - if (this.result.parameters.target === 'drug') { - this.legendService.add_to_context('drug'); - } else { - this.legendService.add_to_context('drugTarget'); - } - const nodeAttributes = this.result.nodeAttributes || {}; - - this.networkHandler.activeNetwork.seedMap = nodeAttributes.isSeed || {}; - - // Reset - this.nodeData = {nodes: null, edges: null}; - this.networkHandler.activeNetwork.networkEl.nativeElement.innerHTML = ''; - this.networkHandler.activeNetwork.networkInternal = null; - // Create - await this.createNetwork(this.result).then(nw => { - return new Promise<any>((resolve, reject) => { - - const nodes = nw.nodes; - const edges = nw.edges; - this.networkHandler.activeNetwork.inputNetwork = {nodes: nodes, edges: edges}; - this.nodeData.nodes = new vis.DataSet(nodes); - this.nodeData.edges = new vis.DataSet(edges); - const container = this.networkHandler.activeNetwork.networkEl.nativeElement; - const isBig = nodes.length > 100 || edges.length > 100; - const options = NetworkSettings.getOptions(isBig ? 'analysis-big' : 'analysis', this.drugstoneConfig.currentConfig()); - // @ts-ignore - options.groups = this.drugstoneConfig.currentConfig().nodeGroups; - // @ts-ignore - for (const g of Object.values(options.groups)) { + if (this.task && this.task.info && this.task.info.done) { + + this.loading = true; + this.netex.getTaskResult(this.token).then(async result => { + this.drugstoneConfig.set_analysisConfig(result.parameters.config); + this.result = result; + if (this.result.parameters.target === 'drug') { + this.legendService.add_to_context('drug'); + } else { + this.legendService.add_to_context('drugTarget'); + } + const nodeAttributes = this.result.nodeAttributes || {}; + + this.networkHandler.activeNetwork.seedMap = nodeAttributes.isSeed || {}; + + // Reset + this.nodeData = {nodes: null, edges: null}; + this.networkHandler.activeNetwork.networkEl.nativeElement.innerHTML = ''; + this.networkHandler.activeNetwork.networkInternal = null; + // Create + await this.createNetwork(this.result).then(nw => { + return new Promise<any>((resolve, reject) => { + + const nodes = nw.nodes; + const edges = nw.edges; + this.networkHandler.activeNetwork.inputNetwork = {nodes: nodes, edges: edges}; + this.nodeData.nodes = new vis.DataSet(nodes); + this.nodeData.edges = new vis.DataSet(edges); + const container = this.networkHandler.activeNetwork.networkEl.nativeElement; + const isBig = nodes.length > 100 || edges.length > 100; + const options = NetworkSettings.getOptions(isBig ? 'analysis-big' : 'analysis', this.drugstoneConfig.currentConfig()); // @ts-ignore - delete g.renderer; - } - if (this.drugstoneConfig.config.physicsOn) { - this.drugstoneConfig.config.physicsOn = !isBig; - } - this.networkHandler.activeNetwork.networkInternal = new vis.Network(container, this.nodeData, options); - - if (isBig) { - resolve(nodes); - } - this.networkHandler.activeNetwork.networkInternal.once('stabilizationIterationsDone', async () => { - if (!this.drugstoneConfig.config.physicsOn || this.networkHandler.activeNetwork.isBig()) { - this.networkHandler.activeNetwork.updatePhysicsEnabled(false); + options.groups = this.drugstoneConfig.currentConfig().nodeGroups; + // @ts-ignore + for (const g of Object.values(options.groups)) { + // @ts-ignore + delete g.renderer; + } + if (this.drugstoneConfig.config.physicsOn) { + this.drugstoneConfig.config.physicsOn = !isBig; } - this.networkHandler.updateAdjacentNodes(this.networkHandler.activeNetwork.isBig()).then(() => { + this.networkHandler.activeNetwork.networkInternal = new vis.Network(container, this.nodeData, options); + + if (isBig) { resolve(nodes); - }); - }); - }).then(nodes => { - - this.tableDrugs = nodes.filter(e => e.drugstoneId && e.drugstoneType === 'drug'); - this.tableDrugs.forEach((r) => { - r.rawScore = r.score; - }); - // @ts-ignore - this.tableDrugs.sort((a, b) => b.score - a.score); - this.rankTable(this.tableDrugs); - this.tableProteins = nodes.filter(e => e.drugstoneId && e.drugstoneType === 'protein'); - this.tableSelectedProteins = []; - this.tableProteins.forEach((r) => { - r.rawScore = r.score; - r.isSeed = this.networkHandler.activeNetwork.seedMap[r.id]; - const wrapper = getWrapperFromNode(r); - if (this.analysis.inSelection(wrapper)) { - this.tableSelectedProteins.push(r); } - }); - this.tableProteins.sort((a, b) => b.score - a.score); - this.rankTable(this.tableProteins); - - this.tableHasScores = ['trustrank', 'closeness', 'degree', 'betweenness', 'quick', 'super'] - .indexOf(this.task.info.algorithm) !== -1; - if (this.tableHasScores) { - this.toggleNormalization(true); - } - this.networkHandler.activeNetwork.networkInternal.setData({nodes: undefined, edge: undefined}); - setTimeout(() => { - this.networkHandler.activeNetwork.networkInternal.setData(this.nodeData); - }, 1000); - - this.networkHandler.activeNetwork.networkInternal.on('deselectNode', (properties) => { - this.showDetailsChange.emit(null); - }); - - this.networkHandler.activeNetwork.networkInternal.on('doubleClick', (properties) => { - const nodeIds: Array<string> = properties.nodes; - if (nodeIds.length > 0) { - const nodeId = nodeIds[0]; - const node = this.nodeData.nodes.get(nodeId); - if (node.drugstoneId === undefined || node.nodeType === 'drug' || node.drugstoneType !== 'protein') { - this.analysis.unmappedNodeToast(); - return; + this.networkHandler.activeNetwork.networkInternal.once('stabilizationIterationsDone', async () => { + if (!this.drugstoneConfig.config.physicsOn || this.networkHandler.activeNetwork.isBig()) { + this.networkHandler.activeNetwork.updatePhysicsEnabled(false); } - const wrapper = getWrapperFromNode(node); + this.networkHandler.updateAdjacentNodes(this.networkHandler.activeNetwork.isBig()).then(() => { + resolve(nodes); + }); + }); + }).then(nodes => { + + this.tableDrugs = nodes.filter(e => e.drugstoneId && e.drugstoneType === 'drug'); + this.tableDrugs.forEach((r) => { + r.rawScore = r.score; + }); + // @ts-ignore + this.tableDrugs.sort((a, b) => b.score - a.score); + this.rankTable(this.tableDrugs); + this.tableProteins = nodes.filter(e => e.drugstoneId && e.drugstoneType === 'protein'); + this.tableSelectedProteins = []; + this.tableProteins.forEach((r) => { + r.rawScore = r.score; + r.isSeed = this.networkHandler.activeNetwork.seedMap[r.id]; + const wrapper = getWrapperFromNode(r); if (this.analysis.inSelection(wrapper)) { - this.analysis.removeItems([wrapper]); - this.analysis.getCount(); - } else { - this.analysis.addItems([wrapper]); - this.analysis.getCount(); - } - } - }); - - this.networkHandler.activeNetwork.networkInternal.on('click', (properties) => { - if (properties.nodes.length === 0 && properties.edges.length === 1) { - // clicked on one edge - const edgeId = properties.edges[0]; - this.networkHandler.activeNetwork.openEdgeSummary(edgeId); - } else { - this.networkHandler.activeNetwork.activeEdge = null; - const selectedNodes = this.nodeData.nodes.get(properties.nodes); - if (selectedNodes.length > 0) { - this.showDetailsChange.emit(getWrapperFromNode(selectedNodes[0])); - } else { - this.showDetailsChange.emit(null); + this.tableSelectedProteins.push(r); } - } - }); + }); + this.tableProteins.sort((a, b) => b.score - a.score); + this.rankTable(this.tableProteins); - this.analysis.subscribeList((items, selected) => { - // return if analysis panel is closed or no nodes are loaded - if (!this.token) { - return; + this.tableHasScores = ['trustrank', 'closeness', 'degree', 'betweenness', 'quick', 'super'] + .indexOf(this.task.info.algorithm) !== -1; + if (this.tableHasScores) { + this.toggleNormalization(true); } + this.networkHandler.activeNetwork.networkInternal.setData({nodes: undefined, edge: undefined}); + setTimeout(() => { + this.networkHandler.activeNetwork.networkInternal.setData(this.nodeData); + }, 1000); - if (selected !== null) { - const updatedNodes: Node[] = []; - for (const item of items) { - const node = this.nodeData.nodes.get(item.id); - if (!node) { - continue; - } - const pos = this.networkHandler.activeNetwork.networkInternal.getPositions([item.id]); - node.x = pos[item.id].x; - node.y = pos[item.id].y; - const isSeed = this.networkHandler.activeNetwork.highlightSeeds ? this.networkHandler.activeNetwork.seedMap[node.id] : false; - const nodeStyled = NetworkSettings.getNodeStyle( - node, - this.drugstoneConfig.currentConfig(), - isSeed, - selected, - this.networkHandler.activeNetwork.getGradient(item.id), - this.networkHandler.activeNetwork.nodeRenderer - ); - updatedNodes.push(nodeStyled); - } - this.nodeData.nodes.update(updatedNodes); - - const proteinSelection = this.tableSelectedProteins; - for (const item of items) { - // TODO: Refactor! - const found = proteinSelection.findIndex((i) => getProteinNodeId(i) === item.id); - const tableItem = this.tableProteins.find((i) => getProteinNodeId(i) === item.id); - if (selected && found === -1 && tableItem) { - proteinSelection.push(tableItem); + this.networkHandler.activeNetwork.networkInternal.on('deselectNode', (properties) => { + this.showDetailsChange.emit(null); + }); + + this.networkHandler.activeNetwork.networkInternal.on('doubleClick', (properties) => { + const nodeIds: Array<string> = properties.nodes; + if (nodeIds.length > 0) { + const nodeId = nodeIds[0]; + const node = this.nodeData.nodes.get(nodeId); + if (node.drugstoneId === undefined || node.nodeType === 'drug' || node.drugstoneType !== 'protein') { + this.analysis.unmappedNodeToast(); + return; } - if (!selected && found !== -1 && tableItem) { - proteinSelection.splice(found, 1); + const wrapper = getWrapperFromNode(node); + if (this.analysis.inSelection(wrapper)) { + this.analysis.removeItems([wrapper]); + this.analysis.getCount(); + } else { + this.analysis.addItems([wrapper]); + this.analysis.getCount(); } } - this.tableSelectedProteins = [...proteinSelection]; - } else { - // else: selected is null - const updatedNodes = []; - this.nodeData.nodes.forEach((node) => { - const isSeed = this.networkHandler.activeNetwork.highlightSeeds ? this.networkHandler.activeNetwork.seedMap[node.id] : false; - if (!isSeed) { - return; + }); + + this.networkHandler.activeNetwork.networkInternal.on('click', (properties) => { + if (properties.nodes.length === 0 && properties.edges.length === 1) { + // clicked on one edge + const edgeId = properties.edges[0]; + this.networkHandler.activeNetwork.openEdgeSummary(edgeId); + } else { + this.networkHandler.activeNetwork.activeEdge = null; + const selectedNodes = this.nodeData.nodes.get(properties.nodes); + if (selectedNodes.length > 0) { + this.showDetailsChange.emit(getWrapperFromNode(selectedNodes[0])); + } else { + this.showDetailsChange.emit(null); } - const nodeStyled = NetworkSettings.getNodeStyle( - node, - this.drugstoneConfig.currentConfig(), - isSeed, - selected, - this.networkHandler.activeNetwork.getGradient(node.id), - this.networkHandler.activeNetwork.nodeRenderer - ); - updatedNodes.push(nodeStyled); - }); - this.nodeData.nodes.update(updatedNodes); + } + }); + + this.analysis.subscribeList((items, selected) => { + // return if analysis panel is closed or no nodes are loaded + if (!this.token) { + return; + } - const proteinSelection = []; - for (const item of items) { - const tableItem = this.tableProteins.find((i) => getProteinNodeId(i) === item.id); - if (tableItem) { - proteinSelection.push(tableItem); + if (selected !== null) { + const updatedNodes: Node[] = []; + for (const item of items) { + const node = this.nodeData.nodes.get(item.id); + if (!node) { + continue; + } + const pos = this.networkHandler.activeNetwork.networkInternal.getPositions([item.id]); + node.x = pos[item.id].x; + node.y = pos[item.id].y; + const isSeed = this.networkHandler.activeNetwork.highlightSeeds ? this.networkHandler.activeNetwork.seedMap[node.id] : false; + const nodeStyled = NetworkSettings.getNodeStyle( + node, + this.drugstoneConfig.currentConfig(), + isSeed, + selected, + this.networkHandler.activeNetwork.getGradient(item.id), + this.networkHandler.activeNetwork.nodeRenderer + ); + updatedNodes.push(nodeStyled); + } + this.nodeData.nodes.update(updatedNodes); + + const proteinSelection = this.tableSelectedProteins; + for (const item of items) { + // TODO: Refactor! + const found = proteinSelection.findIndex((i) => getProteinNodeId(i) === item.id); + const tableItem = this.tableProteins.find((i) => getProteinNodeId(i) === item.id); + if (selected && found === -1 && tableItem) { + proteinSelection.push(tableItem); + } + if (!selected && found !== -1 && tableItem) { + proteinSelection.splice(found, 1); + } } + this.tableSelectedProteins = [...proteinSelection]; + } else { + // else: selected is null + const updatedNodes = []; + this.nodeData.nodes.forEach((node) => { + const isSeed = this.networkHandler.activeNetwork.highlightSeeds ? this.networkHandler.activeNetwork.seedMap[node.id] : false; + if (!isSeed) { + return; + } + const nodeStyled = NetworkSettings.getNodeStyle( + node, + this.drugstoneConfig.currentConfig(), + isSeed, + selected, + this.networkHandler.activeNetwork.getGradient(node.id), + this.networkHandler.activeNetwork.nodeRenderer + ); + updatedNodes.push(nodeStyled); + }); + this.nodeData.nodes.update(updatedNodes); + + const proteinSelection = []; + for (const item of items) { + const tableItem = this.tableProteins.find((i) => getProteinNodeId(i) === item.id); + if (tableItem) { + proteinSelection.push(tableItem); + } + } + this.tableSelectedProteins = [...proteinSelection]; } - this.tableSelectedProteins = [...proteinSelection]; + }); + this.emitVisibleItems(true); + }).then(() => { + if (!['quick', 'super', 'connect', 'connectSelected'].includes(this.task.info.algorithm)) { + return; } - }); - this.emitVisibleItems(true); - }).then(() => { - if (!['quick', 'super', 'connect', 'connectSelected'].includes(this.task.info.algorithm)) { - return; - } - this.netex.getAlgorithmDefaults(this.task.info.algorithm).then(response => { - this.algorithmDefault = response - }); - }).catch(console.error); + this.netex.getAlgorithmDefaults(this.task.info.algorithm).then(response => { + this.algorithmDefault = response; + }); + }).catch(console.error); + }); + this.loadingScreen.stateUpdate(false); }); - this.loadingScreen.stateUpdate(false); - }); + } } } @@ -404,6 +413,10 @@ export class AnalysisPanelComponent implements OnInit, OnChanges, AfterViewInit } } + private async getView(token: string): Promise<any> { + return await this.http.get(`${this.netex.getBackend()}view/?token=${token}`).toPromise(); + } + private async getTask(token: string): Promise<any> { return await this.http.get(`${this.netex.getBackend()}task/?token=${token}`).toPromise(); } diff --git a/src/app/components/analysis-panel/view-list/view-list.component.html b/src/app/components/analysis-panel/view-list/view-list.component.html new file mode 100644 index 00000000..f9d5b932 --- /dev/null +++ b/src/app/components/analysis-panel/view-list/view-list.component.html @@ -0,0 +1,75 @@ +<div class="content"> + <div + class="list is-hoverable" + [ngClass]="{ 'text-normal': drugstoneConfig.smallStyle }" + > + <a + *ngFor="let task of analysis.selectionTokens" + class="list-item box small-box" + [class.is-active]="task === token" + > +<!-- <div *ngIf="!task">--> +<!-- <div class="columns mb-0">--> +<!-- <div class="column">--> +<!-- <app-fa-solid-icon--> +<!-- classString="is-pulled-right has-text-warning"--> +<!-- icon="pause"--> +<!-- ></app-fa-solid-icon>--> +<!-- </div>--> +<!-- </div>--> +<!-- <div class="columns mb-0">--> +<!-- <div class="column pt-0 pb-0">--> +<!-- <a--> +<!-- (click)="analysis.removeSelection(task)"--> +<!-- class="has-text-danger is-pulled-right"--> +<!-- >--> +<!-- <app-fa-solid-icon icon="trash"></app-fa-solid-icon>--> +<!-- </a>--> +<!-- </div>--> +<!-- </div>--> +<!-- </div>--> + <div + *ngIf="task" + (click)="open(task)" + pTooltip="Show selection view" + [tooltipStyleClass]="'drgstn drgstn-tooltip drgstn-tooltip-top'" + tooltipPosition="top" + > + <div class="columns mb-0"> + <div class="column is-8"> +<!-- <span class="is-capitalized"--> +<!-- ><app-fa-solid-icon--> +<!-- icon="crosshairs"--> +<!-- *ngIf="task.info.target === 'drug-target'"--> +<!-- ></app-fa-solid-icon>--> +<!-- <app-fa-solid-icon--> +<!-- icon="capsules"--> +<!-- *ngIf="task.info.target === 'drug'"--> +<!-- ></app-fa-solid-icon>--> +<!-- {{ algorithmNames[task.info.algorithm] }}</span--> +<!-- >--> + </div> + <div class="column"> + <app-fa-solid-icon + classString="is-pulled-right has-text-success" + icon="check" + ></app-fa-solid-icon> + </div> + </div> + <div class="columns mb-0"> +<!-- <div class="column is-8 pt-0 pb-0">--> +<!-- <small>Created {{ task.info.finishedAt | date: "short" }}</small>--> +<!-- </div>--> + <div class="column pt-0 pb-0"> + <a + (click)="analysis.removeTask(task)" + class="has-text-danger is-pulled-right" + > + <app-fa-solid-icon icon="trash"></app-fa-solid-icon> + </a> + </div> + </div> + </div> + </a> + </div> +</div> diff --git a/src/app/components/analysis-panel/view-list/view-list.component.scss b/src/app/components/analysis-panel/view-list/view-list.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/components/analysis-panel/view-list/view-list.component.spec.ts b/src/app/components/analysis-panel/view-list/view-list.component.spec.ts new file mode 100644 index 00000000..2b65a6b4 --- /dev/null +++ b/src/app/components/analysis-panel/view-list/view-list.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ViewListComponent } from './view-list.component'; + +describe('ViewListComponent', () => { + let component: ViewListComponent; + let fixture: ComponentFixture<ViewListComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ViewListComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ViewListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/analysis-panel/view-list/view-list.component.ts b/src/app/components/analysis-panel/view-list/view-list.component.ts new file mode 100644 index 00000000..fda85513 --- /dev/null +++ b/src/app/components/analysis-panel/view-list/view-list.component.ts @@ -0,0 +1,22 @@ +import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; +import {DrugstoneConfigService} from '../../../services/drugstone-config/drugstone-config.service'; +import {AnalysisService} from '../../../services/analysis/analysis.service'; + +@Component({ + selector: 'app-view-list', + templateUrl: './view-list.component.html', + styleUrls: ['./view-list.component.scss'] +}) +export class ViewListComponent implements OnInit { + @Input() token: string; + @Output() tokenChange: EventEmitter<string> = new EventEmitter(); + constructor(public drugstoneConfig: DrugstoneConfigService, public analysis: AnalysisService) { } + + ngOnInit(): void { + } + + open(token) { + this.token = token; + this.tokenChange.emit(token); + } +} diff --git a/src/app/config.ts b/src/app/config.ts index e6f08392..73dff93a 100644 --- a/src/app/config.ts +++ b/src/app/config.ts @@ -54,6 +54,7 @@ export interface IConfig { showAdvAnalysis: boolean; showAdvAnalysisContent: Array<AdvAnalysisContentTypes>; showTasks: boolean; + showViews: boolean; showSelection: boolean; showNetworkMenu: false | 'left' | 'right'; expandNetworkMenu: boolean; @@ -153,6 +154,7 @@ export const defaultConfig: IConfig = { showAdvAnalysisContent: ['drug-search', 'drug-target-search', 'enrichment-gprofiler', 'enrichment-digest', 'search-ndex'], showSelection: true, showTasks: true, + showViews: true, showNetworkMenu: 'right', showLegend: true, expandNetworkMenu: true, diff --git a/src/app/pages/explorer-page/explorer-page.component.html b/src/app/pages/explorer-page/explorer-page.component.html index 1adab2e1..9f3269d4 100644 --- a/src/app/pages/explorer-page/explorer-page.component.html +++ b/src/app/pages/explorer-page/explorer-page.component.html @@ -319,55 +319,6 @@ </div> </div> - <div *ngIf="drugstoneConfig.config.showTasks" class="card bar-large"> - <header - class="card-header" - [ngClass]="{ 'b-text-small': drugstoneConfig.smallStyle }" - > - <p class="card-header-title"> - <app-fa-solid-icon icon="tasks"></app-fa-solid-icon> - Tasks ({{ analysis.tasks != null ? analysis.tasks.length : 0 }}) - </p> - <a - (click)="collapseTask = !collapseTask" - data-action="collapse" - class="card-header-icon is-hidden-fullscreen" - aria-label="more options" - > - <app-fa-solid-icon - *ngIf="collapseTask" - icon="angle-down" - ></app-fa-solid-icon> - <app-fa-solid-icon - *ngIf="!collapseTask" - icon="angle-left" - ></app-fa-solid-icon> - </a> - </header> - <div *ngIf="collapseTask"> - <div - class="card-content overflow task-list-container" - *ngIf="analysis.tasks && analysis.tasks.length > 0" - > - <app-task-list [(token)]="selectedAnalysisToken"></app-task-list> - </div> - <footer class="card-footer"> - <a - *ngIf="analysis.tasks && analysis.tasks.length > 0" - (click)=" - analysis.removeAllTasks(); selectedAnalysisToken = null - " - class="card-footer-item has-text-danger" - pTooltip="Delete all tasks." - [tooltipStyleClass]="'drgstn drgstn-tooltip drgstn-tooltip-top'" - tooltipPosition="top" - > - <app-fa-solid-icon icon="trash"></app-fa-solid-icon> - <span> Delete all </span> - </a> - </footer> - </div> - </div> <div *ngIf="drugstoneConfig.config.showSelection" @@ -513,20 +464,130 @@ <app-fa-solid-icon icon="broom"></app-fa-solid-icon> <span> Reset </span> </a> + <a + *ngIf="analysis.getSelection().length" + (click)="analysis.viewFromSelection()" + class="card-footer-item has-text-danger" + tooltipPosition="top" + pTooltip="Create view from the selection." + > + <app-fa-solid-icon icon="check"></app-fa-solid-icon> + <span> Save selection </span> + </a> </footer> <!-- </div> --> <!-- </div> --> </div> </div> + <div *ngIf="drugstoneConfig.config.showViews" class="card bar-large"> + <header + class="card-header" + [ngClass]="{ 'b-text-small': drugstoneConfig.smallStyle }" + > + <p class="card-header-title"> + <app-fa-solid-icon icon="tasks"></app-fa-solid-icon> + Views ({{ analysis.selectionTokens != null ? analysis.selectionTokens.length : 0 }}) + </p> + <a + (click)="collapseViews = !collapseViews" + data-action="collapse" + class="card-header-icon is-hidden-fullscreen" + aria-label="more options" + > + <app-fa-solid-icon + *ngIf="collapseViews" + icon="angle-down" + ></app-fa-solid-icon> + <app-fa-solid-icon + *ngIf="!collapseViews" + icon="angle-left" + ></app-fa-solid-icon> + </a> + </header> + <div *ngIf="collapseViews"> + <div + class="card-content overflow task-list-container" + *ngIf="analysis.selectionTokens && analysis.selectionTokens.length > 0" + > + <app-view-list [(token)]="selectedViewToken"></app-view-list> + </div> + <footer class="card-footer"> + <a + *ngIf="analysis.selectionTokens && analysis.selectionTokens.length > 0" + (click)=" + analysis.removeAllSelections(); selectedViewToken = null + " + class="card-footer-item has-text-danger" + pTooltip="Delete all selection views." + [tooltipStyleClass]="'drgstn drgstn-tooltip drgstn-tooltip-top'" + tooltipPosition="top" + > + <app-fa-solid-icon icon="trash"></app-fa-solid-icon> + <span> Delete all </span> + </a> + </footer> + </div> + </div> + <div *ngIf="drugstoneConfig.config.showTasks" class="card bar-large"> + <header + class="card-header" + [ngClass]="{ 'b-text-small': drugstoneConfig.smallStyle }" + > + <p class="card-header-title"> + <app-fa-solid-icon icon="tasks"></app-fa-solid-icon> + Tasks ({{ analysis.tasks != null ? analysis.tasks.length : 0 }}) + </p> + <a + (click)="collapseTask = !collapseTask" + data-action="collapse" + class="card-header-icon is-hidden-fullscreen" + aria-label="more options" + > + <app-fa-solid-icon + *ngIf="collapseTask" + icon="angle-down" + ></app-fa-solid-icon> + <app-fa-solid-icon + *ngIf="!collapseTask" + icon="angle-left" + ></app-fa-solid-icon> + </a> + </header> + <div *ngIf="collapseTask"> + <div + class="card-content overflow task-list-container" + *ngIf="analysis.tasks && analysis.tasks.length > 0" + > + <app-task-list [(token)]="selectedAnalysisToken"></app-task-list> + </div> + <footer class="card-footer"> + <a + *ngIf="analysis.tasks && analysis.tasks.length > 0" + (click)=" + analysis.removeAllTasks(); selectedAnalysisToken = null + " + class="card-footer-item has-text-danger" + pTooltip="Delete all tasks." + [tooltipStyleClass]="'drgstn drgstn-tooltip drgstn-tooltip-top'" + tooltipPosition="top" + > + <app-fa-solid-icon icon="trash"></app-fa-solid-icon> + <span> Delete all </span> + </a> + </footer> + </div> + </div> + </div> <!-- Start network block --> <div class="drugstone network column" id="main-column"> <!-- analysis panel with analysis network --> - <div *ngIf="selectedAnalysisToken"> + <div *ngIf="selectedToken"> <app-analysis-panel - [(token)]="selectedAnalysisToken" + [(token)]="selectedToken" + [(tokenType)]="selectedAnalysisTokenType" (showDetailsChange)=" networkHandler.activeNetwork.selectedWrapper = $event " diff --git a/src/app/pages/explorer-page/explorer-page.component.ts b/src/app/pages/explorer-page/explorer-page.component.ts index e28cb8eb..c316d55e 100644 --- a/src/app/pages/explorer-page/explorer-page.component.ts +++ b/src/app/pages/explorer-page/explorer-page.component.ts @@ -107,6 +107,7 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { public collapseAnalysis = true; public collapseTask = true; + public collapseViews = true; public collapseSelection = true; public collapseBaitFilter = true; public collapseQuery = true; @@ -127,8 +128,41 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { public showCustomProteinsDialog = false; + public selectedAnalysisTokenType: 'task' | 'view' | null = null; + // public selectedAnalysisToken: string | null = null; + public selectedToken: string | null = null; - public selectedAnalysisToken: string | null = null; + public set selectedViewToken(token: string | null) { + if (token == null || token.length === 0) { + this.selectedToken = null; + } else { + this.selectedToken = token; + this.selectedAnalysisTokenType = 'view'; + } + } + + public set selectedAnalysisToken(token: string | null) { + if (token == null || token.length === 0) { + this.selectedToken = null; + } else { + this.selectedToken = token; + this.selectedAnalysisTokenType = 'task'; + } + } + + public get selectedAnalysisToken() { + if (this.selectedAnalysisTokenType === 'view') { + return null; + } + return this.selectedToken; + } + + public get selectedViewToken() { + if (this.selectedAnalysisTokenType === 'task') { + return null; + } + return this.selectedToken; + } @Input() set taskId(token: string | null) { if (token == null || token.length === 0) { diff --git a/src/app/services/analysis/analysis.service.ts b/src/app/services/analysis/analysis.service.ts index 4afebd0a..81422449 100644 --- a/src/app/services/analysis/analysis.service.ts +++ b/src/app/services/analysis/analysis.service.ts @@ -61,7 +61,9 @@ export class AnalysisService { private selections = new Map<string, Map<string, Wrapper>>(); public tokens: string[] = []; + public selectionTokens: string[] = []; private tokensCookieKey = `drugstone-tokens-${window.location.host}`; + private selectionsCookieKey = `drugstone-selections-${window.location.host}`; private tokensFinishedCookieKey = `drugstone-finishedTokens-${window.location.host}`; public finishedTokens: string[] = []; public tasks: Task[] = []; @@ -81,11 +83,16 @@ export class AnalysisService { public networkHandler: NetworkHandlerService ) { const tokens = localStorage.getItem(this.tokensCookieKey); + const selections = localStorage.getItem(this.selectionsCookieKey); const finishedTokens = localStorage.getItem(this.tokensFinishedCookieKey); if (tokens) { this.tokens = JSON.parse(tokens); } + if (selections) { + this.selectionTokens = JSON.parse(selections); + console.log(this.selectionTokens); + } if (finishedTokens) { this.finishedTokens = JSON.parse(finishedTokens); } @@ -103,6 +110,11 @@ export class AnalysisService { localStorage.setItem(this.tokensCookieKey, JSON.stringify(this.tokens)); } + removeSelection(token) { + this.selectionTokens = this.selectionTokens.filter((item) => item !== token); + localStorage.setItem(this.selectionsCookieKey, JSON.stringify(this.selectionTokens)); + } + removeAllTasks() { this.tasks = []; this.finishedTokens = []; @@ -110,12 +122,23 @@ export class AnalysisService { localStorage.removeItem(this.tokensCookieKey); } + removeAllSelections() { + this.selectionTokens = []; + localStorage.removeItem(this.selectionsCookieKey); + } + async getTasks() { return await this.netex.getTasks(this.finishedTokens.length > 0 && this.tasks.length === 0 ? this.tokens : this.tokens.filter(t => this.finishedTokens.indexOf(t) === -1)).catch((e) => { clearInterval(this.intervalId); }); } + async getViews() { + return await this.netex.getTasks(this.finishedTokens.length > 0 && this.tasks.length === 0 ? this.tokens : this.tokens.filter(t => this.finishedTokens.indexOf(t) === -1)).catch((e) => { + clearInterval(this.intervalId); + }); + } + public getTissues(): Tissue[] { return this.tissues; } @@ -244,6 +267,34 @@ export class AnalysisService { this.selectedItems.clear(); } + async viewFromSelection() { + + const seeds = this.getSelection().map((item) => item.id); + const seedsFiltered = seeds.filter(el => el != null); + const initialNetwork = this.networkHandler.activeNetwork.getResetInputNetwork(); + const filteredNodes = initialNetwork.nodes.filter(node => seedsFiltered.includes(node.id)); + const filteredEdges = initialNetwork.edges.filter(edge => seedsFiltered.includes(edge.from) && seedsFiltered.includes(edge.to)); + this.resetSelection(); + const payload: any = { + config: this.drugstoneConfig.currentConfig(), + network: {nodes: filteredNodes, edges: filteredEdges} + }; + const resp = await this.http.post<any>(`${this.netex.getBackend()}save_selection`, payload).toPromise(); + // @ts-ignore + console.log(resp.token); + // @ts-ignore + this.selectionTokens.push(resp.token); + localStorage.setItem(this.selectionsCookieKey, JSON.stringify(this.selectionTokens)); + + this.toast.setNewToast({ + message: 'Analysis task started. This may take a while. ' + + `Once the computation finished you can view the results in the task list to the ${this.drugstoneConfig.config.showSidebar}.`, + type: 'success' + }); + // @ts-ignore + return resp.token; + } + idInSelection(nodeId: string): boolean { return this.selectedItems.has(nodeId); } -- GitLab