diff --git a/README.md b/README.md index 4beb48917..7ef070b38 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ PHAT is under development in the Zehbe Lab ([http://zehbelab.weebly.com/](http:/ **Project Supervisor/Principal Investigator:** Dr. Ingeborg Zehbe, LU/TBRHRI Research Chair & Senior Scientist **Project Lead/Biologist:** Robert Jackson, PhD Biotech Candidate **Project Lead/Software Developer:** Chris Gibb, HBSc Comp Sci Student -**Team Members:** Mitchell Pynn, Shane Liu, Jeremy Braun +**Team Members:** Mitchell Pynn, Shane Liu, Jeremy Braun, Nick Catanzaro, Zachary Moorman **How to Cite PHAT (pre-print):** -Gibb CM, Jackson R, Mohammed S, Fiaidhi J, Zehbe I. Pathogen-Host Analysis Tool (PHAT): an Integrative Platform to Analyze Pathogen-Host Relationships in Next-Generation Sequencing Data. bioRxiv. https://doi.org/10.1101/178327 +Gibb CM, Jackson R, Mohammed S, Fiaidhi J, Zehbe I. Pathogen-Host Analysis Tool (PHAT): an Integrative Platform to Analyze Pathogen-Host Relationships in Next-Generation Sequencing Data. bioRxiv. https://doi.org/10.1101/178327 \ No newline at end of file diff --git a/src/CheckForUpdateProcess.ts b/src/CheckForUpdateProcess.ts index 0e5338d2b..130cad04d 100644 --- a/src/CheckForUpdateProcess.ts +++ b/src/CheckForUpdateProcess.ts @@ -1,4 +1,5 @@ import {AtomicOperationForkEvent,CompletionFlags} from "./req/atomicOperationsIPC"; +import * as atomic from "./req/operations/atomicOperations"; import * as getUpdate from "./req/getLatestUpdate"; let flags : CompletionFlags = new CompletionFlags(); @@ -28,7 +29,7 @@ process.on data : res } ); - process.exit(0); + atomic.exitFork(0); }).catch((arg : any) => { console.log(arg); flags.done = true; @@ -41,7 +42,7 @@ process.on data : arg } ); - process.exit(1); + atomic.exitFork(1); }); } } @@ -58,7 +59,7 @@ process.on data : err.message } ); - process.exit(1); + atomic.exitFork(1); }); process.on("unhandledRejection",function(err : Error){ console.log(err); @@ -72,5 +73,5 @@ process.on("unhandledRejection",function(err : Error){ data : err.message } ); - process.exit(1); + atomic.exitFork(1); }); \ No newline at end of file diff --git a/src/DownloadAndInstallUpdateProcess.ts b/src/DownloadAndInstallUpdateProcess.ts index 010dc1bf9..fb61199fc 100644 --- a/src/DownloadAndInstallUpdateProcess.ts +++ b/src/DownloadAndInstallUpdateProcess.ts @@ -3,6 +3,7 @@ import * as cp from "child_process"; let GitHubReleases = require("github-releases"); +import * as atomic from "./req/operations/atomicOperations"; import {AtomicOperationForkEvent,CompletionFlags} from "./req/atomicOperationsIPC"; import {getReadable} from "./req/getAppPath"; @@ -71,7 +72,7 @@ process.on ); installer.unref(); } - process.exit(0); + atomic.exitFork(0); }); }); @@ -90,7 +91,7 @@ process.on data : err } ); - process.exit(1); + atomic.exitFork(1); }); process.on("unhandledRejection",function(err : string){ console.log("ERROR "+err); @@ -104,5 +105,5 @@ process.on("unhandledRejection",function(err : string){ data : err } ); - process.exit(1); + atomic.exitFork(1); }); \ No newline at end of file diff --git a/src/InputBamFileProcess.ts b/src/InputBamFileProcess.ts index 00defcd22..ad4d4a989 100644 --- a/src/InputBamFileProcess.ts +++ b/src/InputBamFileProcess.ts @@ -106,7 +106,7 @@ process.on( flags.done = true flags.success = true; update(); - process.exit(0); + atomic.exitFork(0); })(); } } diff --git a/src/LinkRefSeqToAlignmentProcess.ts b/src/LinkRefSeqToAlignmentProcess.ts index c11606eef..3659b1983 100644 --- a/src/LinkRefSeqToAlignmentProcess.ts +++ b/src/LinkRefSeqToAlignmentProcess.ts @@ -98,7 +98,7 @@ process.on( flags.success = true; update(); - process.exit(0); + atomic.exitFork(0); })(); } } diff --git a/src/OpenProjectProcess.ts b/src/OpenProjectProcess.ts index e640c8e94..3e910d37d 100644 --- a/src/OpenProjectProcess.ts +++ b/src/OpenProjectProcess.ts @@ -1,3 +1,4 @@ +import * as atomic from "./req/operations/atomicOperations"; import {AtomicOperationForkEvent,CompletionFlags} from "./req/atomicOperationsIPC"; import {ProjectManifest} from "./req/projectManifest"; import {openProject} from "./req//openProject"; @@ -40,7 +41,7 @@ process.on flags : flags, } ); - process.exit(0); + atomic.exitFork(0); }).catch((err) => { flags.done = true; flags.failure = true; @@ -52,7 +53,7 @@ process.on data : err } ); - process.exit(1); + atomic.exitFork(1); }); } } @@ -69,7 +70,7 @@ process.on data : err } ); - process.exit(1); + atomic.exitFork(1); }); process.on("unhandledRejection",function(err : string){ @@ -84,5 +85,5 @@ process.on("unhandledRejection",function(err : string){ data : err } ); - process.exit(1); + atomic.exitFork(1); }); \ No newline at end of file diff --git a/src/RenderCoverageTrackProcess.ts b/src/RenderCoverageTrackProcess.ts index 134f061d2..ecf2b482e 100644 --- a/src/RenderCoverageTrackProcess.ts +++ b/src/RenderCoverageTrackProcess.ts @@ -1,3 +1,4 @@ +import * as atomic from "./req/operations/atomicOperations"; import {AtomicOperationForkEvent,CompletionFlags} from "./req/atomicOperationsIPC"; import {AlignData} from "./req/alignData" import * as cf from "./req/renderer/circularFigure"; @@ -39,7 +40,7 @@ process.on } } ); - process.exit(0); + atomic.exitFork(0); }); } } @@ -55,5 +56,5 @@ process.on data : err } ); - process.exit(1); + atomic.exitFork(1); }); \ No newline at end of file diff --git a/src/RenderSNPTrackProcess.ts b/src/RenderSNPTrackProcess.ts index a08dd1ae8..f227b4ac0 100644 --- a/src/RenderSNPTrackProcess.ts +++ b/src/RenderSNPTrackProcess.ts @@ -1,3 +1,4 @@ +import * as atomic from "./req/operations/atomicOperations"; import {AtomicOperationForkEvent,CompletionFlags} from "./req/atomicOperationsIPC"; import {AlignData} from "./req/alignData" import * as cf from "./req/renderer/circularFigure"; @@ -39,7 +40,7 @@ process.on } } ); - process.exit(0); + atomic.exitFork(0); }); } } @@ -56,7 +57,7 @@ process.on data : err } ); - process.exit(1); + atomic.exitFork(1); }); process.on("unhandledRejection",function(err : string){ @@ -71,5 +72,5 @@ process.on("unhandledRejection",function(err : string){ data : err } ); - process.exit(1); + atomic.exitFork(1); }); diff --git a/src/RunAlignmentProcess.ts b/src/RunAlignmentProcess.ts index eeaa17a90..6ff0fadd7 100644 --- a/src/RunAlignmentProcess.ts +++ b/src/RunAlignmentProcess.ts @@ -121,7 +121,7 @@ process.on( flags.done = true; flags.success = true; update(); - process.exit(0); + atomic.exitFork(0); }); }); diff --git a/src/SaveCurrentProjectProcess.ts b/src/SaveCurrentProjectProcess.ts index 9ecd83ca4..71127d7cf 100644 --- a/src/SaveCurrentProjectProcess.ts +++ b/src/SaveCurrentProjectProcess.ts @@ -1,11 +1,11 @@ -import {handleForkFailures} from "./req/operations/atomicOperations"; +import * as atomic from "./req/operations/atomicOperations"; import {AtomicOperationForkEvent,CompletionFlags} from "./req/atomicOperationsIPC"; import {ProjectManifest} from "./req/projectManifest"; import {saveCurrentProject} from "./req//saveCurrentProject"; let proj : ProjectManifest; let flags : CompletionFlags = new CompletionFlags(); -handleForkFailures(); +atomic.handleForkFailures(); process.on ( "message",function(ev : AtomicOperationForkEvent) @@ -29,7 +29,7 @@ process.on flags : flags, } ); - process.exit(0); + atomic.exitFork(0); }).catch((err) => { flags.done = true; flags.failure = true; @@ -41,7 +41,7 @@ process.on data : err } ); - process.exit(1); + atomic.exitFork(1); }); } } diff --git a/src/circularGenomeBuilder.html b/src/circularGenomeBuilder.html index 97b3d23bb..cc8b849fe 100644 --- a/src/circularGenomeBuilder.html +++ b/src/circularGenomeBuilder.html @@ -69,6 +69,11 @@
  • Delete Figure
  • +
  • diff --git a/src/circularGenomeBuilderRenderer.ts b/src/circularGenomeBuilderRenderer.ts index 5b2ff79a1..bbf150e41 100644 --- a/src/circularGenomeBuilderRenderer.ts +++ b/src/circularGenomeBuilderRenderer.ts @@ -156,25 +156,33 @@ $ totalTracks++; if(ops[i].flags.done && ops[i].flags.success) { - console.log("compiled "+ops[i].uuid); if(ops[i].uuid) { + console.log("compiled "+ops[i].uuid); tc.removeTrack(ops[i].uuid); genomeView.firstRender = true; } + else if(ops[i].compileBase) + { + console.log("compiled base figure"); + tc.resetBaseFigureSVG(); + genomeView.firstRender = true; + } } } } } if(totalTracks > 0) document.getElementById("navBarLoadingText").innerHTML = `Recalculating ${totalTracks} tracks`; - if(totalTracks == 1) + if(totalTracks == 0) { - setTimeout(function(){ - document.getElementById("navBarLoadingText").innerHTML = ``; - },1000); + document.getElementById("navBarLoadingText").innerHTML = ``; } } + else if(arg.val === undefined) + { + document.getElementById("navBarLoadingText").innerHTML = ``; + } } } viewMgr.render(); diff --git a/src/compileTemplatesProcess.ts b/src/compileTemplatesProcess.ts index e0b58bd60..3abe4b857 100644 --- a/src/compileTemplatesProcess.ts +++ b/src/compileTemplatesProcess.ts @@ -81,8 +81,9 @@ function compileAndSend() data : { figure : figure } + },function(){ + process.exit(0); }); - process.exit(0); } process.on( diff --git a/src/guiTests/req/closeToolBar.ts b/src/guiTests/req/closeToolBar.ts index b1f8bc01a..ebd89d8af 100644 --- a/src/guiTests/req/closeToolBar.ts +++ b/src/guiTests/req/closeToolBar.ts @@ -11,7 +11,6 @@ export async function closeToolBar() : Promise { console.log("Failed to open tool bar!"); console.log(toolBar); - //process.exit(1); } toolBar[0].close(); resolve(); diff --git a/src/req/operations/CheckForUpdate.ts b/src/req/operations/CheckForUpdate.ts index 805588187..a63d070b9 100644 --- a/src/req/operations/CheckForUpdate.ts +++ b/src/req/operations/CheckForUpdate.ts @@ -23,38 +23,26 @@ export class CheckForUpdate extends atomic.AtomicOperation this.closeLogOnSuccess = true; this.logRecord = atomic.openLog(this.name,"Check for Update"); let self = this; - this.checkForUpdateProcess = cp.fork(getReadable("CheckForUpdate.js")); - this.addPID(this.checkForUpdateProcess.pid); - this.checkForUpdateProcess.on( - "message",function(ev : AtomicOperationForkEvent) + this.checkForUpdateProcess = atomic.makeFork("CheckForUpdate.js",{ + setData : true, + data : {} + },function(ev : AtomicOperationForkEvent){ + self.logObject(ev); + if(ev.finishedSettingData == true) { - self.logObject(ev); - if(ev.finishedSettingData == true) - { - self.checkForUpdateProcess.send( - { - run : true - } - ); - } - if(ev.update == true) - { - self.extraData = ev.data; - self.flags = ev.flags; - self.update(); - } - } - ); - setTimeout( - function(){ self.checkForUpdateProcess.send( { - setData : true, - data : { - } + run : true } ); - },500 - ); + } + if(ev.update == true) + { + self.extraData = ev.data; + self.flags = ev.flags; + self.update(); + } + }); + this.addPID(this.checkForUpdateProcess.pid); } } \ No newline at end of file diff --git a/src/req/operations/CompileTemplates.ts b/src/req/operations/CompileTemplates.ts index f78986288..937b94815 100644 --- a/src/req/operations/CompileTemplates.ts +++ b/src/req/operations/CompileTemplates.ts @@ -30,42 +30,31 @@ export class CompileTemplates extends atomic.AtomicOperation public run() : void { let self = this; - this.compileTemplatesProcess = cp.fork(getReadable("compileTemplates.js")); - this.addPID(this.compileTemplatesProcess.pid); - self.compileTemplatesProcess.on( - "message",function(ev : AtomicOperationForkEvent){ - if(ev.update == true) + this.compileTemplatesProcess = atomic.makeFork("compileTemplates.js",{ + setData : true, + data : { + figure : self.figure, + uuid : self.uuid, + compileBase : self.compileBase + }, + name : self.name, + description : "Compile Templates For Figure" + },function(ev : AtomicOperationForkEvent){ + if(ev.update == true) + { + self.flags = ev.flags; + if(ev.flags.done) + { + self.logRecord = ev.logRecord; + atomic.recordLogRecord(ev.logRecord); + } + if(ev.flags.success) { - self.flags = ev.flags; - if(ev.flags.done) - { - self.logRecord = ev.logRecord; - atomic.recordLogRecord(ev.logRecord); - } - if(ev.flags.success) - { - self.figure = ev.data.figure; - } - self.update(); + self.figure = ev.data.figure; } + self.update(); } - ); - - setTimeout( - function(){ - self.compileTemplatesProcess.send( - { - setData : true, - data : { - figure : self.figure, - uuid : self.uuid, - compileBase : self.compileBase - }, - name : self.name, - description : "Compile Templates For Figure" - } - ); - },10 - ); + }); + this.addPID(this.compileTemplatesProcess.pid); } } \ No newline at end of file diff --git a/src/req/operations/DownloadAndInstallUpdate.ts b/src/req/operations/DownloadAndInstallUpdate.ts index 55fd63b22..0e25bd30f 100644 --- a/src/req/operations/DownloadAndInstallUpdate.ts +++ b/src/req/operations/DownloadAndInstallUpdate.ts @@ -19,38 +19,27 @@ export class DownloadAndInstallUpdate extends atomic.AtomicOperation { this.logRecord = atomic.openLog(this.name,"Download and Install Update"); let self = this; - this.downloadAndInstallUpdateProcess = cp.fork(getReadable("DownloadAndInstallUpdate.js")); - this.addPID(this.downloadAndInstallUpdateProcess.pid); - this.downloadAndInstallUpdateProcess.on( - "message",function(ev : AtomicOperationForkEvent) - { - if(ev.finishedSettingData == true) - { - self.downloadAndInstallUpdateProcess.send( - { - run : true - } - ); - } - if(ev.update == true) - { - self.extraData = ev.data; - self.flags = ev.flags; - self.update(); - } + this.downloadAndInstallUpdateProcess = atomic.makeFork("DownloadAndInstallUpdate.js",{ + setData : true, + data : { + asset : self.asset } - ); - setTimeout( - function(){ + },function(ev : AtomicOperationForkEvent){ + if(ev.finishedSettingData == true) + { self.downloadAndInstallUpdateProcess.send( { - setData : true, - data : { - asset : self.asset - } + run : true } ); - },500 - ); + } + if(ev.update == true) + { + self.extraData = ev.data; + self.flags = ev.flags; + self.update(); + } + }); + this.addPID(this.downloadAndInstallUpdateProcess.pid); } } \ No newline at end of file diff --git a/src/req/operations/InputBamFile.ts b/src/req/operations/InputBamFile.ts index f30197963..dcfd0aa41 100644 --- a/src/req/operations/InputBamFile.ts +++ b/src/req/operations/InputBamFile.ts @@ -22,50 +22,40 @@ export class InputBamFile extends atomic.AtomicOperation this.closeLogOnFailure = false; this.closeLogOnSuccess = false; let self = this; - this.inputBamFileProcess = cp.fork(getReadable("InputBamFile.js")); - this.addPID(this.inputBamFileProcess.pid); - self.inputBamFileProcess.on( - "message",function(ev : AtomicOperationForkEvent){ - if(ev.finishedSettingData == true) - { - self.inputBamFileProcess.send( - { - run : true - } - ); - } - if(ev.update == true) - { - self.flags = ev.flags; - if(ev.flags.done) - { - self.alignData = ev.data.alignData; - if(ev.flags.failure == true && self.alignData) - { - //make sure output dir is deleted on failure - self.destinationArtifactsDirectories.push(getArtifactDir(self.alignData)); - } - self.logRecord = ev.logRecord; - atomic.recordLogRecord(ev.logRecord); - } - self.progressMessage = ev.progressMessage; - self.update(); - } - } - ); - setTimeout( - function(){ + this.inputBamFileProcess = atomic.makeFork("InputBamFile.js",{ + setData : true, + data : { + bamPath : self.bamPath + }, + name : self.name, + description : "Input Bam File" + },function(ev : AtomicOperationForkEvent){ + if(ev.finishedSettingData == true) + { self.inputBamFileProcess.send( { - setData : true, - data : { - bamPath : self.bamPath - }, - name : self.name, - description : "Input Bam File" + run : true } ); } - ); + if(ev.update == true) + { + self.flags = ev.flags; + if(ev.flags.done) + { + self.alignData = ev.data.alignData; + if(ev.flags.failure == true && self.alignData) + { + //make sure output dir is deleted on failure + self.destinationArtifactsDirectories.push(getArtifactDir(self.alignData)); + } + self.logRecord = ev.logRecord; + atomic.recordLogRecord(ev.logRecord); + } + self.progressMessage = ev.progressMessage; + self.update(); + } + }); + this.addPID(this.inputBamFileProcess.pid); } } \ No newline at end of file diff --git a/src/req/operations/InstallUpdate.ts b/src/req/operations/InstallUpdate.ts index 66dd250a5..08ca92630 100644 --- a/src/req/operations/InstallUpdate.ts +++ b/src/req/operations/InstallUpdate.ts @@ -20,20 +20,15 @@ export class InstallUpdate extends atomic.AtomicOperation let self = this; try { - this.installUpdateJob = cp.fork(getReadable("installUpdate.js")); - this.addPID(this.installUpdateJob.pid); - this.installUpdateJob.on - ( - "message",function(data : any) + this.installUpdateJob = atomic.makeFork("installUpdate.js",{},function(data : any){ + if(data.totalFiles) { - if(data.totalFiles) - { - self.filesInUpdate = data.totalFiles; - self.update(); - self.setSuccess(self.flags); - } + self.filesInUpdate = data.totalFiles; + self.update(); + self.setSuccess(self.flags); } - ); + }); + this.addPID(this.installUpdateJob.pid); } catch(err) diff --git a/src/req/operations/LinkRefSeqToAlignment.ts b/src/req/operations/LinkRefSeqToAlignment.ts index 35221f58d..d2207f5c9 100644 --- a/src/req/operations/LinkRefSeqToAlignment.ts +++ b/src/req/operations/LinkRefSeqToAlignment.ts @@ -26,51 +26,41 @@ export class LinkRefSeqToAlignment extends atomic.AtomicOperation this.closeLogOnFailure = false; this.closeLogOnSuccess = false; let self = this; - this.linkRefSeqToAlignmentProcess = cp.fork(getReadable("LinkRefSeqToAlignment.js")); - this.addPID(this.linkRefSeqToAlignmentProcess.pid); - self.linkRefSeqToAlignmentProcess.on( - "message",function(ev : AtomicOperationForkEvent){ - if(ev.finishedSettingData == true) + this.linkRefSeqToAlignmentProcess = atomic.makeFork("LinkRefSeqToAlignment.js",{ + setData : true, + data : { + align : self.alignData, + fasta : self.fasta + }, + name : self.name, + description : "Link Ref Seq To Alignment" + },function(ev : AtomicOperationForkEvent){ + if(ev.finishedSettingData == true) + { + self.linkRefSeqToAlignmentProcess.send( + { + run : true + } + ); + } + if(ev.update == true) + { + self.flags = ev.flags; + if(ev.flags.success == true) { - self.linkRefSeqToAlignmentProcess.send( - { - run : true - } - ); + self.alignData = ev.data.alignData; } - if(ev.update == true) + if(ev.flags.done) { - self.flags = ev.flags; - if(ev.flags.success == true) - { - self.alignData = ev.data.alignData; - } - if(ev.flags.done) - { - self.logRecord = ev.logRecord; - atomic.recordLogRecord(ev.logRecord); - } - self.step = ev.step; - self.progressMessage = ev.progressMessage; - console.log(self.step+" "+self.progressMessage); - self.update(); + self.logRecord = ev.logRecord; + atomic.recordLogRecord(ev.logRecord); } + self.step = ev.step; + self.progressMessage = ev.progressMessage; + console.log(self.step+" "+self.progressMessage); + self.update(); } - ); - setTimeout( - function(){ - self.linkRefSeqToAlignmentProcess.send( - { - setData : true, - data : { - align : self.alignData, - fasta : self.fasta - }, - name : self.name, - description : "Link Ref Seq To Alignment" - } - ); - },500 - ); + }); + this.addPID(this.linkRefSeqToAlignmentProcess.pid); } } \ No newline at end of file diff --git a/src/req/operations/OpenProject.ts b/src/req/operations/OpenProject.ts index fbb3c783e..15f629071 100644 --- a/src/req/operations/OpenProject.ts +++ b/src/req/operations/OpenProject.ts @@ -27,49 +27,37 @@ export class OpenProject extends atomic.AtomicOperation { this.logRecord = atomic.openLog(this.name,"Open Project"); let self = this; - this.openProjectProcess = cp.fork(getReadable("OpenProject.js")); - this.addPID(this.openProjectProcess.pid); - self.openProjectProcess.on( - "message",function(ev : AtomicOperationForkEvent) + this.openProjectProcess = atomic.makeFork("OpenProject.js",{ + setData : true, + data : { + proj : self.proj, + externalProjectPath : self.externalProjectPath + }, + readableBasePath : getReadable(""), + writableBasePath : getWritable(""), + readableAndWritableBasePath : getReadableAndWritable("") + + },function(ev : AtomicOperationForkEvent){ + self.logObject(ev); + if(ev.finishedSettingData == true) { - self.logObject(ev); - if(ev.finishedSettingData == true) - { - self.openProjectProcess.send( - { - run : true - } - ); - } - if(ev.update == true) - { - self.extraData = ev.data; - self.flags = ev.flags; - if(ev.flags.success == true) - { - self.setSuccess(self.flags); - } - self.update(); - } - } - ); - setTimeout( - function(){ self.openProjectProcess.send( { - setData : true, - data : { - proj : self.proj, - externalProjectPath : self.externalProjectPath - }, - readableBasePath : getReadable(""), - writableBasePath : getWritable(""), - readableAndWritableBasePath : getReadableAndWritable("") - + run : true } ); - },10 - ); - + } + if(ev.update == true) + { + self.extraData = ev.data; + self.flags = ev.flags; + if(ev.flags.success == true) + { + self.setSuccess(self.flags); + } + self.update(); + } + }); + this.addPID(this.openProjectProcess.pid); } } \ No newline at end of file diff --git a/src/req/operations/RenderCoverageTrack.ts b/src/req/operations/RenderCoverageTrack.ts index cead8cdff..f2129b422 100644 --- a/src/req/operations/RenderCoverageTrack.ts +++ b/src/req/operations/RenderCoverageTrack.ts @@ -34,9 +34,40 @@ export class RenderCoverageTrackForContig extends atomic.AtomicOperation { this.logRecord = atomic.openLog(this.name,"Render Coverage Track"); let self = this; - this.renderCoverageTrackProcess = cp.fork(getReadable("RenderCoverageTrack.js")); + this.renderCoverageTrackProcess = atomic.makeFork("RenderCoverageTrack.js",{ + setData : true, + data : { + alignData : self.alignData, + contiguuid : self.contiguuid, + circularFigure : self.circularFigure, + colour : self.colour + } + },function(ev : AtomicOperationForkEvent){ + if(ev.finishedSettingData == true) + { + self.renderCoverageTrackProcess.send( + { + run : true + } + ); + } + + if(ev.update == true) + { + self.extraData = ev.data; + self.flags = ev.flags; + if(ev.flags.success == true) + { + self.circularFigure = ev.data.circularFigure; + self.contiguuid = ev.data.contiguuid; + self.alignData = ev.data.alignData; + self.colour = ev.data.colour; + } + self.update(); + } + }); this.addPID(this.renderCoverageTrackProcess.pid); - self.renderCoverageTrackProcess.on( + /*self.renderCoverageTrackProcess.on( "message",function(ev : AtomicOperationForkEvent) { if(ev.finishedSettingData == true) @@ -77,6 +108,6 @@ export class RenderCoverageTrackForContig extends atomic.AtomicOperation } ); },500 - ); + );*/ } } \ No newline at end of file diff --git a/src/req/operations/RenderSNPTrack.ts b/src/req/operations/RenderSNPTrack.ts index 37770660b..7de0fb258 100644 --- a/src/req/operations/RenderSNPTrack.ts +++ b/src/req/operations/RenderSNPTrack.ts @@ -34,48 +34,37 @@ export class RenderSNPTrackForContig extends atomic.AtomicOperation { this.logRecord = atomic.openLog(this.name,"Render SNP Track"); let self = this; - this.renderSNPTrackProcess = cp.fork(getReadable("RenderSNPTrack.js")); - this.addPID(this.renderSNPTrackProcess.pid); - self.renderSNPTrackProcess.on( - "message",function(ev : AtomicOperationForkEvent) - { - if(ev.finishedSettingData == true) - { - self.renderSNPTrackProcess.send( - { - run : true - } - ); - } - if(ev.update == true) - { - self.extraData = ev.data; - self.flags = ev.flags; - if(ev.flags.success == true) - { - self.circularFigure = ev.data.circularFigure; - self.contiguuid = ev.data.contiguuid; - self.alignData = ev.data.alignData; - self.colour = ev.data.colour; - } - self.update(); - } + this.renderSNPTrackProcess = atomic.makeFork("RenderSNPTrack.js",{ + setData : true, + data : { + alignData : self.alignData, + contiguuid : self.contiguuid, + circularFigure : self.circularFigure, + colour : self.colour } - ); - setTimeout( - function(){ + },function(ev : AtomicOperationForkEvent){ + if(ev.finishedSettingData == true) + { self.renderSNPTrackProcess.send( { - setData : true, - data : { - alignData : self.alignData, - contiguuid : self.contiguuid, - circularFigure : self.circularFigure, - colour : self.colour - } + run : true } ); - },500 - ); + } + if(ev.update == true) + { + self.extraData = ev.data; + self.flags = ev.flags; + if(ev.flags.success == true) + { + self.circularFigure = ev.data.circularFigure; + self.contiguuid = ev.data.contiguuid; + self.alignData = ev.data.alignData; + self.colour = ev.data.colour; + } + self.update(); + } + }); + this.addPID(this.renderSNPTrackProcess.pid); } } \ No newline at end of file diff --git a/src/req/operations/RunAlignment.ts b/src/req/operations/RunAlignment.ts index 9f3061cd5..f421e3b3d 100644 --- a/src/req/operations/RunAlignment.ts +++ b/src/req/operations/RunAlignment.ts @@ -43,55 +43,45 @@ export class RunAlignment extends atomic.AtomicOperation this.closeLogOnFailure = false; this.closeLogOnSuccess = false; let self = this; - this.runAlignmentProcess = cp.fork(getReadable("RunAlignment.js")); - this.addPID(this.runAlignmentProcess.pid); - self.runAlignmentProcess.on( - "message",function(ev : AtomicOperationForkEvent){ - if(ev.finishedSettingData == true) - { - self.runAlignmentProcess.send( - { - run : true - } - ); - } - if(ev.pid) + this.runAlignmentProcess = atomic.makeFork("RunAlignment.js",{ + setData : true, + data : { + alignData : self.alignData + }, + name : self.name, + description : "Run Alignment" + },function(ev : AtomicOperationForkEvent){ + if(ev.finishedSettingData == true) + { + self.runAlignmentProcess.send( + { + run : true + } + ); + } + if(ev.pid) + { + self.addPID(ev.pid); + console.log(ev.pid); + } + if(ev.update == true) + { + self.flags = ev.flags; + if(ev.flags.success == true) { - self.addPID(ev.pid); - console.log(ev.pid); + self.alignData = ev.data.alignData; } - if(ev.update == true) + if(ev.flags.done) { - self.flags = ev.flags; - if(ev.flags.success == true) - { - self.alignData = ev.data.alignData; - } - if(ev.flags.done) - { - self.logRecord = ev.logRecord; - atomic.recordLogRecord(ev.logRecord); - } - self.step = ev.step; - self.progressMessage = ev.progressMessage; - console.log(self.step+" "+self.progressMessage); - self.update(); + self.logRecord = ev.logRecord; + atomic.recordLogRecord(ev.logRecord); } + self.step = ev.step; + self.progressMessage = ev.progressMessage; + console.log(self.step+" "+self.progressMessage); + self.update(); } - ); - setTimeout( - function(){ - self.runAlignmentProcess.send( - { - setData : true, - data : { - alignData : self.alignData - }, - name : self.name, - description : "Run Alignment" - } - ); - },500 - ); + }); + this.addPID(this.runAlignmentProcess.pid); } } \ No newline at end of file diff --git a/src/req/operations/SaveCurrentProject.ts b/src/req/operations/SaveCurrentProject.ts index 261a671c1..f7f2ca32c 100644 --- a/src/req/operations/SaveCurrentProject.ts +++ b/src/req/operations/SaveCurrentProject.ts @@ -21,42 +21,30 @@ export class SaveCurrentProject extends atomic.AtomicOperation { this.logRecord = atomic.openLog(this.name,"Save Current Project"); let self = this; - this.saveCurrentProjectProcess = cp.fork(getReadable("SaveCurrentProject.js")); - this.addPID(this.saveCurrentProjectProcess.pid); - self.saveCurrentProjectProcess.on( - "message",function(ev : AtomicOperationForkEvent) + this.saveCurrentProjectProcess = atomic.makeFork("SaveCurrentProject.js",{ + setData : true, + data : self.proj + },function(ev : AtomicOperationForkEvent){ + self.logObject(ev); + if(ev.finishedSettingData == true) { - self.logObject(ev); - if(ev.finishedSettingData == true) - { - self.saveCurrentProjectProcess.send( - { - run : true - } - ); - } - if(ev.update == true) - { - self.extraData = ev.data; - self.flags = ev.flags; - if(ev.flags.success == true) - { - self.setSuccess(self.flags); - } - self.update(); - } - } - ); - setTimeout( - function(){ self.saveCurrentProjectProcess.send( { - setData : true, - data : self.proj + run : true } ); - },500 - ); - + } + if(ev.update == true) + { + self.extraData = ev.data; + self.flags = ev.flags; + if(ev.flags.success == true) + { + self.setSuccess(self.flags); + } + self.update(); + } + }); + this.addPID(this.saveCurrentProjectProcess.pid); } } \ No newline at end of file diff --git a/src/req/operations/atomicOperations.ts b/src/req/operations/atomicOperations.ts index 1d3641ad8..a9742b64d 100644 --- a/src/req/operations/atomicOperations.ts +++ b/src/req/operations/atomicOperations.ts @@ -1,6 +1,7 @@ import {EventEmitter} from "events"; import * as fs from "fs"; import * as readline from "readline"; +import * as cp from "child_process"; const uuidv4 : () => string = require("uuid/v4"); import * as rimraf from "rimraf"; @@ -8,7 +9,7 @@ import * as mkdirp from "mkdirp"; import {AtomicOperationForkEvent} from "./../atomicOperationsIPC"; import {SpawnRequestParams} from "./../JobIPC"; -import {getReadableAndWritable} from "./../getAppPath"; +import {getReadableAndWritable,getReadable} from "./../getAppPath"; /** * Class representing some operation which should be run atomically @@ -294,7 +295,7 @@ export abstract class AtomicOperation */ public logObject(obj : any) : void { - logString(this.logRecord,JSON.stringify(obj)); + logString(this.logRecord,JSON.stringify(obj,undefined,4)); } } @@ -315,6 +316,62 @@ export class ForkLogger extends AtomicOperation public run(){} } + +/** + * Forks target, passing data and piping stdout/stderr to console. + * Calls cb on messages from the forked ChildProcess + * + * @export + * @param {string} target + * @param {*} data + * @param {(ev : any) => void} cb + * @returns {cp.ChildProcess} + */ +export function makeFork(target : string,data : any,cb : (ev : any) => void) : cp.ChildProcess +{ + let res = cp.fork( + getReadable(target),[],{ + silent : true + } + ); + + res.stdout.on("data",function(data : Buffer){ + console.log(data.toString()); + }); + + res.stderr.on("data",function(data : Buffer){ + console.error(data.toString()); + }); + + res.on("message",function(ev : any){ + cb(ev); + }); + + res.on("exit",function(code : number){ + console.log(`${target} exited with ${code}`); + }); + + setTimeout( + function(){ + res.send(data); + },10 + ); + + return res; +} + +/** + * Disconnects IPC channel and triggers a process exit of retCode + * + * @export + * @param {number} retCode + */ +export function exitFork(retCode : number) : void +{ + process.disconnect(); + process.exitCode = retCode; +} + /** * Registers traps for unhandled errors in the current process. Logs exception details and stack traces * using the provided logger @@ -345,7 +402,7 @@ export function handleForkFailures(logger? : ForkLogger,progressMessage? : strin } process.send(failureObj); - process.exit(1); + exitFork(1); }; (process as NodeJS.EventEmitter).on("uncaughtException",function(err : Error){ diff --git a/src/req/renderer/circularFigure.ts b/src/req/renderer/circularFigure.ts index 7ab4ba320..332595e40 100644 --- a/src/req/renderer/circularFigure.ts +++ b/src/req/renderer/circularFigure.ts @@ -174,6 +174,8 @@ export class CircularFigure public radius : number; public height : number; public width : number; + public isInteractive : boolean; + public showContigNames : boolean; public circularFigureBPTrackOptions : CircularFigureBPTrackOptions; public renderedCoverageTracks : Array; public renderedSNPTracks : Array; @@ -203,6 +205,18 @@ export class CircularFigure this.contigs[1].loaded = true; } this.customContigs = new Array(); + this.isInteractive = true; + this.showContigNames = true; + let totalBP = 0; + for(let i = 0; i != this.contigs.length; ++i) + { + totalBP += this.contigs[i].bp; + } + if(this.contigs.length >= 50 || totalBP >= 1000000) + { + this.isInteractive = false; + this.showContigNames = false; + } cacheBaseFigure(this); } } @@ -232,7 +246,7 @@ export abstract class FigureCanvas * @param {number} [end=-1] * @returns {string} */ -export function renderContig(contig : Contig,start : number = -1,end : number = -1) : string +export function renderContig(figure : CircularFigure,contig : Contig,start : number = -1,end : number = -1) : string { if(start == -1) start = contig.start; @@ -247,12 +261,13 @@ export function renderContig(contig : Contig,start : number = -1,end : number = vAdjust : contig.vAdjust, markerStyle : `fill:${contig.color};opacity:${contig.opacity};`, uuid : contig.uuid, - onClick : "markerOnClick" + onClick : "markerOnClick", + isInteractive : figure.isInteractive })} ${markerLabel.add( { type : "path", - text : contig.alias, + text : figure.showContigNames ? contig.alias : "", labelStyle : `fill:${contig.fontFill};opacity:${contig.opacity};` })} ${markerLabel.end()} @@ -280,7 +295,8 @@ export function renderBaseFigure(figure : CircularFigure) : string { text : figure.name, labelStyle : "font-size:20px;font-weight:400", - onClick : "figureNameOnClick" + onClick : "figureNameOnClick", + isInteractive : figure.isInteractive })} ${trackLabel.end()} ${(()=> @@ -289,12 +305,12 @@ export function renderBaseFigure(figure : CircularFigure) : string let lastLocation = 0; for(let i = 0; i != figure.contigs.length; ++i) { - res += renderContig(figure.contigs[i],lastLocation,lastLocation+figure.contigs[i].bp); + res += renderContig(figure,figure.contigs[i],lastLocation,lastLocation+figure.contigs[i].bp); lastLocation = lastLocation + figure.contigs[i].bp; } for(let i = 0; i != figure.customContigs.length; ++i) { - res += renderContig(figure.customContigs[i],figure.customContigs[i].start,figure.customContigs[i].end); + res += renderContig(figure,figure.customContigs[i],figure.customContigs[i].start,figure.customContigs[i].end); } return res; })()} @@ -363,6 +379,20 @@ export function getBaseFigureSVGFromCache(figure : CircularFigure) : string return (fs.readFileSync(getReadableAndWritable(`rt/circularFigures/${figure.uuid}/baseFigure.svg`))); } +/** + * Deletes the contents of the current disk SVG cache for the base figure of figure + * + * @export + * @param {CircularFigure} figure + */ +export function deleteBaseFigureSVGFromCache(figure : CircularFigure) : void +{ + try + { + fs.unlinkSync(getReadableAndWritable(`rt/circularFigures/${figure.uuid}/baseFigure.svg`)); + } + catch(err){} +} /** * Returns the distance from the begginning of the figure to the begginning of the contig specified by contiguuid diff --git a/src/req/renderer/circularGenome/trackLabel.ts b/src/req/renderer/circularGenome/trackLabel.ts index 7fc835a0e..26ca38c17 100644 --- a/src/req/renderer/circularGenome/trackLabel.ts +++ b/src/req/renderer/circularGenome/trackLabel.ts @@ -1,15 +1,16 @@ export function add( - options? : { + options : { text? : string, labelStyle? : string, vAdjust? : string, wAdjust? : string, - onClick? : string + onClick? : string, + isInteractive : boolean } ) : string { let res = ` - {return options.onClick ? `ng-click="${options.onClick}()"` : "";})()} ${ + {return options.onClick ? `ng-click="${options.onClick}()"` : "";})()}` : ""} ${ ( ()=> { diff --git a/src/req/renderer/circularGenome/trackMarker.ts b/src/req/renderer/circularGenome/trackMarker.ts index c17d0fa68..5e80f0a45 100644 --- a/src/req/renderer/circularGenome/trackMarker.ts +++ b/src/req/renderer/circularGenome/trackMarker.ts @@ -1,5 +1,5 @@ export function add( - options? : { + options : { start? : string, end? : string, markerStyle? : string, @@ -9,13 +9,14 @@ export function add( vAdjust? : number, wAdjust? : string, uuid? : string, - onClick? : string + onClick? : string, + isInteractive : boolean } ) : string { let res = ` { diff --git a/src/req/renderer/circularGenomeBuilderRenderer/centreFigure.ts b/src/req/renderer/circularGenomeBuilderRenderer/centreFigure.ts index 970389c81..6dcfd994d 100644 --- a/src/req/renderer/circularGenomeBuilderRenderer/centreFigure.ts +++ b/src/req/renderer/circularGenomeBuilderRenderer/centreFigure.ts @@ -1,4 +1,11 @@ import * as cf from "./../circularFigure"; +/** + * Center div based on the dimensions of figure and the window + * + * @export + * @param {HTMLElement} div + * @param {cf.CircularFigure} figure + */ export function centreFigure(div : HTMLElement,figure : cf.CircularFigure) : void { if(div) diff --git a/src/req/renderer/circularGenomeBuilderRenderer/displayFigure.ts b/src/req/renderer/circularGenomeBuilderRenderer/displayFigure.ts index 445903e3e..a78452163 100644 --- a/src/req/renderer/circularGenomeBuilderRenderer/displayFigure.ts +++ b/src/req/renderer/circularGenomeBuilderRenderer/displayFigure.ts @@ -7,7 +7,54 @@ import * as viewMgr from "./../viewMgr"; import * as masterView from "./masterView"; import {GenomeView} from "./genomeView"; import {centreFigure} from "./centreFigure"; +/** + * Displays the currently set figure + * + * @export + * @param {GenomeView} self + * @returns {Promise} + */ export async function displayFigure(self : GenomeView) : Promise +{ + if(!self.genome.isInteractive) + return await displayNonInteractiveFigure(self); + else + return await displayInteractiveFigure(self); + +} +/** + * Renders a figure as a non-interactive SVG using the specified canvas + * + * @export + * @param {GenomeView} self + * @returns {Promise} + */ +export async function displayNonInteractiveFigure(self : GenomeView) : Promise +{ + return new Promise((resolve,reject) => { + tc.refreshCache(self.genome); + removeDiv(self); + + let $div : any = $(` +
    + ${getSelectedDataTrackSVGsFromCache(self)} + ${tc.baseFigureSVG ? tc.baseFigureSVG : ""} +
    + `); + + $(document.body).append($div); + centreFigure(document.getElementById(self.div),self.genome); + resolve(); + }); +} +/** + * Renders a figure as interactive using Angular bindings using the specified canvas + * + * @export + * @param {GenomeView} self + * @returns {Promise} + */ +export async function displayInteractiveFigure(self : GenomeView) : Promise { //This is an unholy mess adapted from the example given inline in the //angular source code https://github.com/angular/angular.js/blob/master/src/auto/injector.js @@ -25,19 +72,8 @@ export async function displayFigure(self : GenomeView) : Promise return new Promise((resolve,reject) => { setImmediate(function(){ setImmediate(function(){ - try - { - document.body.removeChild(document.getElementById("controls")); - } - catch(err){} - try - { - //Remove the div this view is bound to - document.body.removeChild(document.getElementById(self.div)); - } - catch(err){} - $("#"+self.div).remove(); - + + removeDiv(self); for(let i = 0; i != self.genome.contigs.length; ++i) { @@ -68,39 +104,11 @@ export async function displayFigure(self : GenomeView) : Promise $div = $(`
    - ${(()=>{ - let res = ""; - for(let i = 0; i != self.genome.renderedCoverageTracks.length; ++i) - { - if(self.genome.renderedCoverageTracks[i].checked) - { - try - { - res += `
    `; - res += tc.getCachedCoverageTrack(self.genome.renderedCoverageTracks[i]); - res += `
    `; - } - catch(err){} - } - } - for(let i = 0; i != self.genome.renderedSNPTracks.length; ++i) - { - if(self.genome.renderedSNPTracks[i].checked) - { - try - { - res += `
    `; - res += tc.getCachedSNPTrack(self.genome.renderedSNPTracks[i]); - res += `
    `; - } - catch(err){} - } - } - return res; - })()} -
    - ${templates} -
    + ${getSelectedDataTrackSVGsFromCache(self)} + +
    + ${templates} +
    `); $(document.body).append($div); @@ -151,4 +159,65 @@ export async function displayFigure(self : GenomeView) : Promise }); }); }); +} + +/** + * Delete the div used by self for rendering + * + * @export + * @param {GenomeView} self + */ +export function removeDiv(self : GenomeView) : void +{ + try + { + document.body.removeChild(document.getElementById("controls")); + } + catch(err){} + try + { + //Remove the div this view is bound to + document.body.removeChild(document.getElementById(self.div)); + } + catch(err){} + $("#"+self.div).remove(); +} + +/** + * Returns SVGs for all selected tracks, wrapped in divs ready for rendering + * + * @export + * @param {GenomeView} self + * @returns {string} + */ +export function getSelectedDataTrackSVGsFromCache(self : GenomeView) : string +{ + let res = ""; + for(let i = 0; i != self.genome.renderedCoverageTracks.length; ++i) + { + if(self.genome.renderedCoverageTracks[i].checked) + { + try + { + res += `
    `; + res += tc.getCachedCoverageTrack(self.genome.renderedCoverageTracks[i]); + res += `
    `; + } + catch(err){} + } + } + for(let i = 0; i != self.genome.renderedSNPTracks.length; ++i) + { + if(self.genome.renderedSNPTracks[i].checked) + { + try + { + res += `
    `; + res += tc.getCachedSNPTrack(self.genome.renderedSNPTracks[i]); + res += `
    `; + } + catch(err){} + } + } + return res; } \ No newline at end of file diff --git a/src/req/renderer/circularGenomeBuilderRenderer/exportToSVG.ts b/src/req/renderer/circularGenomeBuilderRenderer/exportToSVG.ts index 9ff6ac433..452de661f 100644 --- a/src/req/renderer/circularGenomeBuilderRenderer/exportToSVG.ts +++ b/src/req/renderer/circularGenomeBuilderRenderer/exportToSVG.ts @@ -2,6 +2,15 @@ import * as fs from "fs"; import {GenomeView} from "./genomeView"; import * as cf from "./../circularFigure"; +/** + * Writes the SVG to fileName. Allows DOM updates to interleave execution + * + * @export + * @param {GenomeView} self + * @param {string} fileName + * @param {string} svg + * @returns {Promise} + */ export async function writeSVG(self : GenomeView,fileName : string,svg : string) : Promise { return new Promise((resolve,reject) => { @@ -20,6 +29,13 @@ export async function writeSVG(self : GenomeView,fileName : string,svg : string) }); } +/** + * Serializes the canvas div. Allows DOM updates to interleave execution + * + * @export + * @param {GenomeView} self + * @returns {Promise} + */ export async function serializeFigure(self : GenomeView) : Promise { return new Promise((resolve,reject) => { @@ -47,6 +63,13 @@ export async function serializeFigure(self : GenomeView) : Promise valid SVG. Here, we pass all of the raw templates needed for the current figure to angular to compile into one single SVG that is ready for serialization. */ +/** + * JIT compiles the entire figure into one SVG, ignoring all precompiled SVGS. Allows DOM updates to interleave execution + * + * @export + * @param {GenomeView} self + * @returns {Promise} + */ export async function renderSVG(self : GenomeView) : Promise { return new Promise((resolve,reject) => { diff --git a/src/req/renderer/circularGenomeBuilderRenderer/genomeView.ts b/src/req/renderer/circularGenomeBuilderRenderer/genomeView.ts index 25e125502..70b736063 100644 --- a/src/req/renderer/circularGenomeBuilderRenderer/genomeView.ts +++ b/src/req/renderer/circularGenomeBuilderRenderer/genomeView.ts @@ -16,17 +16,51 @@ import {displayFigure} from "./displayFigure"; import {centreFigure} from "./centreFigure"; import {writeLoadingModal} from "./writeLoadingModal"; import {setSelectedContigByUUID} from "./writeContigEditorModal"; +import {reCacheBaseFigure} from "./reCacheBaseFigure"; +import * as tc from "./templateCache"; import {writeSVG,serializeFigure,renderSVG} from "./exportToSVG"; require("angular"); require("@chgibb/angularplasmid"); let app : any = angular.module('myApp',['angularplasmid']); +/** + * Manages the display and behaviour of the figure being edited + * + * @export + * @class GenomeView + * @extends {viewMgr.View} + * @implements {cf.FigureCanvas} + */ export class GenomeView extends viewMgr.View implements cf.FigureCanvas { + /** + * The current figure being displayed + * + * @type {cf.CircularFigure} + * @memberof GenomeView + */ public genome : cf.CircularFigure; + /** + * Reconstruct the figure using cached data + * + * @type {boolean} + * @memberof GenomeView + */ public firstRender : boolean; + /** + * Aligns for this figure + * + * @type {Array} + * @memberof GenomeView + */ public alignData : Array; + /** + * Bound Angular scope for genome + * + * @type {*} + * @memberof GenomeView + */ public scope : any; public constructor(name : string,div : string) { @@ -35,8 +69,17 @@ export class GenomeView extends viewMgr.View implements cf.FigureCanvas } public onMount() : void{} public onUnMount() : void{} + /** + * Update the Angular scope for genome + * + * @param {cf.FigureCanvas} [scope] + * @returns {void} + * @memberof GenomeView + */ public updateScope(scope? : cf.FigureCanvas) : void { + if(!this.genome.isInteractive) + return; if(scope) this.scope = scope; this.scope.genome = this.genome; @@ -51,6 +94,11 @@ export class GenomeView extends viewMgr.View implements cf.FigureCanvas this.scope.div = this.div; } + /** + * Export genome to SVG + * + * @memberof GenomeView + */ public exportSVG() { let self = this; @@ -96,6 +144,14 @@ export class GenomeView extends viewMgr.View implements cf.FigureCanvas } ); } + /** + * Called when a trackMarker is clicked by the user + * + * @param {*} $event + * @param {*} $marker + * @param {string} uuid + * @memberof GenomeView + */ public markerOnClick($event : any,$marker : any,uuid : string) : void { let masterView = viewMgr.getViewByName("masterView"); @@ -104,6 +160,11 @@ export class GenomeView extends viewMgr.View implements cf.FigureCanvas masterView.showModal(); viewMgr.render(); } + /** + * Called when the name of the figure is clicked by the user + * + * @memberof GenomeView + */ public figureNameOnClick() : void { let self = this; @@ -116,14 +177,20 @@ export class GenomeView extends viewMgr.View implements cf.FigureCanvas let masterView = viewMgr.getViewByName("masterView"); let genomeView = viewMgr.getViewByName("genomeView",masterView.views); - genomeView.firstRender = true; + //Save changes - masterView.dataChanged(); + masterView.saveFigureChanges(); //Re render + genomeView.firstRender = true; viewMgr.render(); } }); } + /** + * Should be called when genome.radius changes + * + * @memberof GenomeView + */ public inputRadiusOnChange() { this.genome.height = this.genome.radius*10; @@ -131,6 +198,11 @@ export class GenomeView extends viewMgr.View implements cf.FigureCanvas //Re center figure this.postRender(); } + /** + * Should be called when the track interval changes + * + * @memberof GenomeView + */ public showBPTrackOnChange() { let masterView = viewMgr.getViewByName("masterView"); @@ -172,6 +244,11 @@ export class GenomeView extends viewMgr.View implements cf.FigureCanvas return " "; return undefined; } + /** + * Recenter figure and clean artifacts + * + * @memberof GenomeView + */ public postRender() : void { if(this.genome !== undefined) diff --git a/src/req/renderer/circularGenomeBuilderRenderer/masterView.ts b/src/req/renderer/circularGenomeBuilderRenderer/masterView.ts index 9813db1fd..5b40d6ca9 100644 --- a/src/req/renderer/circularGenomeBuilderRenderer/masterView.ts +++ b/src/req/renderer/circularGenomeBuilderRenderer/masterView.ts @@ -9,7 +9,8 @@ const dialogs = Dialogs(); import {SaveKeyEvent} from "./../../ipcEvents"; import {AtomicOperationIPC} from "./../../atomicOperationsIPC"; import * as viewMgr from "./../viewMgr"; -import {CircularFigure,} from "./../circularFigure"; +import {CircularFigure} from "./../circularFigure"; +import {reCacheBaseFigure} from "./reCacheBaseFigure"; import {Fasta} from "./../../fasta"; import {AlignData} from "./../../alignData"; @@ -20,6 +21,7 @@ import {writeAlignsModal} from "./writeAlignsModal"; import {writeAvailableTracksModal} from "./writeAvailableTracksModal"; import {writeContigEditorModal} from "./writeContigEditorModal"; import {writeContigCreatorModal} from "./writeContigCreatorModal"; +import {writeEditContigsModal} from "./writeEditContigsModal"; import {writeLoadingModal} from "./writeLoadingModal"; @@ -32,6 +34,13 @@ export function addView(arr : Array,div : string) { arr.push(new View(div)); } +/** + * Manages the display and the behaviour of the figure editor + * + * @export + * @class View + * @extends {viewMgr.View} + */ export class View extends viewMgr.View { public views : Array; @@ -42,6 +51,7 @@ export class View extends viewMgr.View public availableTracksModalOpen : boolean; public contigEditorModalOpen : boolean; public contigCreatorModalOpen : boolean; + public editContigsModalOpen : boolean; public loadingModal : boolean; public constructor(div : string) { @@ -53,8 +63,15 @@ export class View extends viewMgr.View this.availableTracksModalOpen = false; this.contigEditorModalOpen = false; this.contigCreatorModalOpen = false; + this.editContigsModalOpen = false; this.loadingModal = false; } + /** + * Retrieve all the alignments run for the currently open figure + * + * @returns {(Array | undefined)} + * @memberof View + */ public getAlignsForOpenGenome() : Array | undefined { let res : Array = new Array(); @@ -72,6 +89,11 @@ export class View extends viewMgr.View return undefined; return res; } + /** + * Show the modal, with whatever happens to be on it. Bootstrap only allows a single modal + * + * @memberof View + */ public showModal() : void { try @@ -81,6 +103,11 @@ export class View extends viewMgr.View } catch(err){} } + /** + * Dismiss the modal + * + * @memberof View + */ public dismissModal() : void { ($(".modal")).modal("hide"); @@ -91,7 +118,14 @@ export class View extends viewMgr.View this.availableTracksModalOpen = false; this.contigCreatorModalOpen = false; this.contigEditorModalOpen = false; + this.editContigsModalOpen = false; } + /** + * Highlight the currently open figure in the "Figures" dropdown + * + * @returns {void} + * @memberof View + */ public setSelectedFigureInDropDown() : void { let genomeView = viewMgr.getViewByName("genomeView",this.views); @@ -109,6 +143,11 @@ export class View extends viewMgr.View } } } + /** + * Update the textbox in the navbar with the radius of the open figure + * + * @memberof View + */ public setFigureRadiusInInput() : void { let genomeView = viewMgr.getViewByName("genomeView",this.views); @@ -118,6 +157,11 @@ export class View extends viewMgr.View else el.value = genomeView.genome.radius.toString(); } + /** + * Update the textbox in the navbar with the track interval of the open figure + * + * @memberof View + */ public setFigureBPIntervalInput() : void { let genomeView = viewMgr.getViewByName("genomeView",this.views); @@ -127,6 +171,12 @@ export class View extends viewMgr.View else el.value = genomeView.genome.circularFigureBPTrackOptions.interval.toString(); } + /** + * Update the checkbox in the navbar with the interval status of the open figure + * + * @returns {void} + * @memberof View + */ public setShowBPIntervalCheckBox() : void { let genomeView = viewMgr.getViewByName("genomeView",this.views); @@ -138,6 +188,11 @@ export class View extends viewMgr.View else if(genomeView.genome.circularFigureBPTrackOptions.showLabels == 1) checkbox.checked = true; } + /** + * On startup. Apply behaviour to static dropdowns and controls + * + * @memberof View + */ public onMount() : void { GenomeView.addView(this.views,"genomeView"); @@ -148,7 +203,7 @@ export class View extends viewMgr.View let genomeView = viewMgr.getViewByName("genomeView",this.views); let self = this; window.onbeforeunload = function(e){ - self.dataChanged(); + self.saveFigureChanges(); } document.getElementById("figures").onclick = function(this : HTMLElement,ev : MouseEvent){ for(let i = 0; i != self.fastaInputs.length; ++i) @@ -160,7 +215,7 @@ export class View extends viewMgr.View self.fastaInputs[i].uuid, self.fastaInputs[i].contigs )); - self.dataChanged(); + self.saveFigureChanges(); genomeView.genome = self.circularFigures[self.circularFigures.length - 1]; genomeView.firstRender = true; viewMgr.render(); @@ -180,6 +235,34 @@ export class View extends viewMgr.View } } } + document.getElementById("figureOptions").onclick = function(this : HTMLElement,ev : MouseEvent){ + if((event.target).id == `${genomeView.genome.uuid}ToggleInteractivity`) + { + genomeView.genome.isInteractive = !genomeView.genome.isInteractive; + self.saveFigureChanges(); + genomeView.firstRender = true; + viewMgr.render(); + } + if((event.target).id == `${genomeView.genome.uuid}ToggleContigNames`) + { + genomeView.genome.showContigNames = !genomeView.genome.showContigNames; + self.saveFigureChanges(); + genomeView.firstRender = true; + viewMgr.render(); + } + if((event.target).id == `EditFigureName`) + { + genomeView.figureNameOnClick(); + } + if((event.target).id ==`EditContigs`) + { + self.editContigsModalOpen = true; + writeEditContigsModal(); + self.showModal(); + + } + } + document.getElementById("showBPIntervalCheckBox").onclick = function(this : HTMLElement,ev : MouseEvent){ document.getElementById("updateNavBarButton").click(); } @@ -237,6 +320,8 @@ export class View extends viewMgr.View document.getElementById("updateNavBarButton").onclick = function(this : HTMLElement,ev : MouseEvent){ let radiusHasChanged = false; + let trackIntervalChanged = false; + let showIntervalChanged = false; if(!genomeView.genome) return; let radius = parseInt((document.getElementById("figureRadiusInput")).value); @@ -249,22 +334,29 @@ export class View extends viewMgr.View let trackInterval = parseInt((document.getElementById("figureBPIntervalInput")).value); if(trackInterval) + { + if(trackInterval != genomeView.genome.circularFigureBPTrackOptions.interval) + trackIntervalChanged = true; genomeView.genome.circularFigureBPTrackOptions.interval = trackInterval; + } let showInterval = ((document.getElementById("showBPIntervalCheckBox")).checked); if(showInterval !== undefined) { + if((showInterval === true && genomeView.genome.circularFigureBPTrackOptions.showLabels == 0) || (showInterval === false && genomeView.genome.circularFigureBPTrackOptions.showLabels == 1)) + showIntervalChanged = true; if(showInterval === true) genomeView.genome.circularFigureBPTrackOptions.showLabels = 1; else genomeView.genome.circularFigureBPTrackOptions.showLabels = 0; } - if(radiusHasChanged) + if(radiusHasChanged || trackIntervalChanged || showIntervalChanged) { - tc.triggerReCompileForAllTracks(genomeView.genome); + genomeView.firstRender = true; + tc.triggerReCompileForWholeFigure(genomeView.genome); + self.saveFigureChanges(); } genomeView.updateScope(); - self.dataChanged(); viewMgr.render(); } @@ -280,8 +372,15 @@ export class View extends viewMgr.View this.views[i].onUnMount(); } } + /** + * Update dynamic dropdowns and controls. Call render on GenomeView + * + * @returns {string} + * @memberof View + */ public renderView() : string { + let genomeView = viewMgr.getViewByName("genomeView",this.views); let res = ""; for(let i = 0; i != this.fastaInputs.length; ++i) { @@ -306,6 +405,17 @@ export class View extends viewMgr.View } } document.getElementById("figures").innerHTML = res; + + res = "" + if(genomeView.genome) + { + res += `
  • ${genomeView.genome.isInteractive ? "Disable Interactivity" : "Enable Interactivity"}
  • `; + res += `
  • ${genomeView.genome.showContigNames ? "Don't Show Contig Names" : "Show Contig Names"}
  • `; + res += `
  • Edit Figure Name
  • `; + res += `
  • Edit Contigs
  • `; + } + document.getElementById("figureOptions").innerHTML = res; + for(let i = 0; i != this.views.length; ++i) { this.views[i].render(); @@ -336,6 +446,11 @@ export class View extends viewMgr.View this.setFigureBPIntervalInput(); this.setShowBPIntervalCheckBox(); } + /** + * Save circular figures for the open project + * + * @memberof View + */ public dataChanged() : void { ipc.send( @@ -348,6 +463,21 @@ export class View extends viewMgr.View } ); } + /** + * Save changes to the open figure. Resets SVG caches to force figure updating + * + * @memberof View + */ + public saveFigureChanges() : void + { + let genomeView = viewMgr.getViewByName("genomeView",this.views); + this.dataChanged(); + if(genomeView.genome) + { + tc.resetCaches(); + reCacheBaseFigure(genomeView.genome); + } + } public divClickEvents(event : JQueryEventObject) : void { let genomeView = viewMgr.getViewByName("genomeView",this.views); diff --git a/src/req/renderer/circularGenomeBuilderRenderer/reCacheBaseFigure.ts b/src/req/renderer/circularGenomeBuilderRenderer/reCacheBaseFigure.ts index bec62b53d..a0936fd00 100644 --- a/src/req/renderer/circularGenomeBuilderRenderer/reCacheBaseFigure.ts +++ b/src/req/renderer/circularGenomeBuilderRenderer/reCacheBaseFigure.ts @@ -1,18 +1,12 @@ import * as cf from "./../circularFigure"; -export async function reCacheBaseFigure(figure : cf.CircularFigure) : Promise +/** + * Rerenders figure's template cache, resets SVG cache + * + * @export + * @param {cf.CircularFigure} figure + */ +export function reCacheBaseFigure(figure : cf.CircularFigure) : void { - return new Promise((resolve,reject) => { - (async function() : Promise { - return new Promise((resolve,reject) => { - setImmediate(function(){ - setImmediate(function(){ - cf.cacheBaseFigure(figure); - resolve(); - }); - }); - }); - })().then(() => { - resolve(); - }); - }); + cf.cacheBaseFigure(figure); + cf.deleteBaseFigureSVGFromCache(figure); } \ No newline at end of file diff --git a/src/req/renderer/circularGenomeBuilderRenderer/templateCache.ts b/src/req/renderer/circularGenomeBuilderRenderer/templateCache.ts index 516d45162..c1a86fa1e 100644 --- a/src/req/renderer/circularGenomeBuilderRenderer/templateCache.ts +++ b/src/req/renderer/circularGenomeBuilderRenderer/templateCache.ts @@ -37,17 +37,41 @@ class CachedSNPTrackSVG } } -let baseFigureTemplateString = ""; +export let baseFigureSVG : string = undefined; let coverageTrackCache = new Array();; let SNPTrackCache = new Array(); +/** + * Clear the in-memory cache of the SVG for the base figure + * + * @export + */ +export function resetBaseFigureSVG() : void +{ + baseFigureSVG = undefined; +} + +/** + * Clear all in-memory caches + * + * @export + */ export function resetCaches() : void { coverageTrackCache = new Array(); SNPTrackCache = new Array(); + baseFigureSVG = undefined } +/** + * Update caches for newFigure with new data (if changes are detected). + * Will reset and rebuild caches if this function is called with a figure + * different from the one used the last time it was called + * + * @export + * @param {cf.CircularFigure} newFigure + */ export function refreshCache(newFigure : cf.CircularFigure) : void { if(!figure || newFigure.uuid != figure.uuid) @@ -55,6 +79,28 @@ export function refreshCache(newFigure : cf.CircularFigure) : void resetCaches(); figure = newFigure; } + console.log(newFigure); + if(!newFigure.isInteractive) + { + if(!baseFigureSVG) + { + try + { + baseFigureSVG = cf.getBaseFigureSVGFromCache(figure); + } + catch(err) + { + ipc.send( + "runOperation", + { + opName : "compileTemplates", + figure : newFigure, + compileBase : true + } + ); + } + } + } //load tracks which had not been loaded previously let found = false; @@ -123,24 +169,16 @@ export function refreshCache(newFigure : cf.CircularFigure) : void } } } - - /*for(let i = 0; i != newFigure.renderedSNPTracks.length; ++i) - { - found = false; - for(let k = 0; k != SNPTrackCache.length; ++k) - { - if(newFigure.renderedSNPTracks[i].uuid == SNPTrackCache[k].trackRecord.uuid) - { - found = true; - break; - } - } - if(!found) - SNPTrackCache.push(new CachedSNPTrackSVG(newFigure.renderedSNPTracks[i])); - }*/ } -//retrieve loaded tracks + +/** + * Retrieve an (already loaded) SVG for the specified coverage track + * + * @export + * @param {cf.RenderedCoverageTrackRecord} trackRecord + * @returns {string} + */ export function getCachedCoverageTrack(trackRecord : cf.RenderedCoverageTrackRecord) : string { for(let i = 0; i != coverageTrackCache.length; ++i) @@ -151,6 +189,13 @@ export function getCachedCoverageTrack(trackRecord : cf.RenderedCoverageTrackRec throw new Error(`Could not fetch ${trackRecord.uuid} from cache`); } +/** + * Retrieve an (already loaded) SVG for the specified SNP track + * + * @export + * @param {cf.RenderedSNPTrackRecord} trackRecord + * @returns {string} + */ export function getCachedSNPTrack(trackRecord : cf.RenderedSNPTrackRecord) : string { for(let i = 0; i != SNPTrackCache.length; ++i) @@ -161,6 +206,13 @@ export function getCachedSNPTrack(trackRecord : cf.RenderedSNPTrackRecord) : str throw new Error(`Could not fetch ${trackRecord.uuid} from cache`); } +/** + * Deletes the track specified by uuid from the in-memory cache + * + * @export + * @param {string} uuid + * @returns {void} + */ export function removeTrack(uuid : string) : void { for(let i = 0; i != coverageTrackCache.length; ++i) @@ -183,10 +235,28 @@ export function removeTrack(uuid : string) : void } } -export function triggerReCompileForAllTracks(newFigure : cf.CircularFigure) : void +/** + * Triggers a compile for each compononent of newFigure (including non-visible data tracks) regardless of cache status. + * Will only trigger a compile for the base figure if the figure is non-interactive + * @export + * @param {cf.CircularFigure} newFigure + * @returns {void} + */ +export function triggerReCompileForWholeFigure(newFigure : cf.CircularFigure) : void { if(!figure) return; + if(!figure.isInteractive) + { + ipc.send( + "runOperation", + { + opName : "compileTemplates", + figure : newFigure, + compileBase : true + } + ); + } for(let i = 0; i != coverageTrackCache.length; ++i) { ipc.send( diff --git a/src/req/renderer/circularGenomeBuilderRenderer/writeAlignsModal.ts b/src/req/renderer/circularGenomeBuilderRenderer/writeAlignsModal.ts index de493ad78..c750cac2e 100644 --- a/src/req/renderer/circularGenomeBuilderRenderer/writeAlignsModal.ts +++ b/src/req/renderer/circularGenomeBuilderRenderer/writeAlignsModal.ts @@ -3,6 +3,11 @@ import * as masterView from "./masterView"; import * as genomeView from "./genomeView"; import {writeAvailableTracksModal,setSelectedAlign} from "./writeAvailableTracksModal"; import {getReadable} from "./../../getAppPath"; +/** + * Writes the alignment selection menu into the modal + * + * @export + */ export function writeAlignsModal() : void { let masterView = viewMgr.getViewByName("masterView"); diff --git a/src/req/renderer/circularGenomeBuilderRenderer/writeAvailableTracksModal.ts b/src/req/renderer/circularGenomeBuilderRenderer/writeAvailableTracksModal.ts index 557bb8884..fc4e15b3a 100644 --- a/src/req/renderer/circularGenomeBuilderRenderer/writeAvailableTracksModal.ts +++ b/src/req/renderer/circularGenomeBuilderRenderer/writeAvailableTracksModal.ts @@ -10,10 +10,21 @@ import {getReadable} from "./../../getAppPath"; require("@claviska/jquery-minicolors"); let selectedAlign : AlignData; +/** + * Set the selected alignment to view track options for + * + * @export + * @param {AlignData} align + */ export function setSelectedAlign(align : AlignData) : void { selectedAlign = align; } +/** + * Writes the data track selection menu into the modal + * + * @export + */ export function writeAvailableTracksModal() : void { let masterView = viewMgr.getViewByName("masterView"); diff --git a/src/req/renderer/circularGenomeBuilderRenderer/writeContigCreatorModal.ts b/src/req/renderer/circularGenomeBuilderRenderer/writeContigCreatorModal.ts index cd7435bf6..c6990d243 100644 --- a/src/req/renderer/circularGenomeBuilderRenderer/writeContigCreatorModal.ts +++ b/src/req/renderer/circularGenomeBuilderRenderer/writeContigCreatorModal.ts @@ -4,6 +4,11 @@ import * as genomeView from "./genomeView"; import * as cf from "./../circularFigure"; import {reCacheBaseFigure} from "./reCacheBaseFigure"; import {writeLoadingModal} from "./writeLoadingModal"; +/** + * Writes the contig creation menu into the modal + * + * @export + */ export function writeContigCreatorModal() : void { let masterView = viewMgr.getViewByName("masterView"); @@ -61,12 +66,11 @@ export function writeContigCreatorModal() : void masterView.loadingModal = true; writeLoadingModal(); setTimeout(function(){ - reCacheBaseFigure(genomeView.genome).then(() => { - masterView.loadingModal = false; - masterView.dismissModal(); - genomeView.firstRender = true; - viewMgr.render(); - }); + reCacheBaseFigure(genomeView.genome); + masterView.loadingModal = false; + masterView.dismissModal(); + genomeView.firstRender = true; + viewMgr.render(); },10); viewMgr.render(); diff --git a/src/req/renderer/circularGenomeBuilderRenderer/writeContigEditorModal.ts b/src/req/renderer/circularGenomeBuilderRenderer/writeContigEditorModal.ts index 9d81a9f0a..3f9f6a6b0 100644 --- a/src/req/renderer/circularGenomeBuilderRenderer/writeContigEditorModal.ts +++ b/src/req/renderer/circularGenomeBuilderRenderer/writeContigEditorModal.ts @@ -9,6 +9,13 @@ import {reCacheBaseFigure} from "./reCacheBaseFigure"; import {writeLoadingModal} from "./writeLoadingModal"; let contig : cf.Contig; let editedAlias = ""; +/** + * Set the contig to edit by uuid + * + * @export + * @param {string} uuid + * @returns {void} + */ export function setSelectedContigByUUID(uuid : string) : void { let masterView = viewMgr.getViewByName("masterView"); @@ -35,6 +42,11 @@ export function setSelectedContigByUUID(uuid : string) : void } } +/** + * Writes the contig editor menu into the modal + * + * @export + */ export function writeContigEditorModal() : void { if(!contig) @@ -105,21 +117,8 @@ export function writeContigEditorModal() : void } masterView.contigEditorModalOpen = false; masterView.dismissModal(); - - masterView.dataChanged(); - - masterView.loadingModal = true; - writeLoadingModal(); - - setTimeout(function(){ - reCacheBaseFigure(genomeView.genome).then(() => { - masterView.loadingModal = false; - masterView.dismissModal(); - genomeView.firstRender = true; - viewMgr.render(); - }); - },10); - + genomeView.firstRender = true; + masterView.saveFigureChanges(); viewMgr.render(); } diff --git a/src/req/renderer/circularGenomeBuilderRenderer/writeEditContigsModal.ts b/src/req/renderer/circularGenomeBuilderRenderer/writeEditContigsModal.ts new file mode 100644 index 000000000..b29fb6dc6 --- /dev/null +++ b/src/req/renderer/circularGenomeBuilderRenderer/writeEditContigsModal.ts @@ -0,0 +1,69 @@ +import * as viewMgr from "./../viewMgr"; +import * as masterView from "./masterView"; +import * as genomeView from "./genomeView"; + +/** + * Writes the list of contigs to edit into the modal + * + * @export + */ +export function writeEditContigsModal() : void +{ + let masterView = viewMgr.getViewByName("masterView"); + let genomeView = viewMgr.getViewByName("genomeView",masterView.views); + + let title = `Select Contig to Edit`; + + let body = ``; + if(!genomeView.genome) + { + body = ` +

    You must select a figure to edit before you can view it's contigs to edit.

    + `; + } + else if(genomeView.genome) + { + body += `
    Custom Contigs
    `; + if(genomeView.genome.customContigs.length > 0) + { + for(let i = 0; i != genomeView.genome.customContigs.length; ++i) + { + body += `

    ${genomeView.genome.customContigs[i].name}

    `; + } + } + else + { + body += `

    No custom contigs

    `; + } + + body += `
    Reference Contigs
    `; + + for(let i = 0; i != genomeView.genome.contigs.length; ++i) + { + body += `

    ${genomeView.genome.contigs[i].name}

    `; + } + } + + let footer = ``; + + document.getElementById("modalTitle").innerHTML = title; + document.getElementById("modalBody").innerHTML = body; + document.getElementById("modalFooter").innerHTML = footer; + + if(genomeView.genome.customContigs.length > 0) + { + for(let i = 0; i != genomeView.genome.customContigs.length; ++i) + { + document.getElementById(`${genomeView.genome.customContigs[i].uuid}Edit`).onclick = function(this : HTMLElement,ev : MouseEvent){ + genomeView.markerOnClick(undefined,undefined,genomeView.genome.customContigs[i].uuid); + } + } + } + + for(let i = 0; i != genomeView.genome.contigs.length; ++i) + { + document.getElementById(`${genomeView.genome.contigs[i].uuid}Edit`).onclick = function(this : HTMLElement,ev : MouseEvent){ + genomeView.markerOnClick(undefined,undefined,genomeView.genome.contigs[i].uuid); + } + } +} \ No newline at end of file diff --git a/src/req/renderer/circularGenomeBuilderRenderer/writeLoadingModal.ts b/src/req/renderer/circularGenomeBuilderRenderer/writeLoadingModal.ts index dae01ae8a..d92772632 100644 --- a/src/req/renderer/circularGenomeBuilderRenderer/writeLoadingModal.ts +++ b/src/req/renderer/circularGenomeBuilderRenderer/writeLoadingModal.ts @@ -1,3 +1,8 @@ +/** + * Writes a generic loading message into the modal. May be modified by #loadingText after being written + * + * @export + */ export function writeLoadingModal() : void { let title = `Loading...`;