UNPKG

@gmod/jbrowse

Version:

JBrowse - client-side genome browser

467 lines (399 loc) 18.9 kB
define([ 'dojo/_base/declare', 'dijit/Dialog', 'dijit/form/RadioButton', 'dijit/form/Button', 'dojo/dom-construct', 'JBrowse/Model/BinaryTreeNode' ], function(declare, Dialog, RadioButton, Button, dom, TreeNode) { return declare(null, { // Produces a dialog box in which a user may enter settings for how they would like to combine tracks in a Combination Track. constructor: function( args ) { this.newTrackKey = args.trackConfig ? args.trackConfig.key : args.key; this.track = args.track; this.newStore = args.store; this.opTree = this.track.opTree; this.currType = this.track.currType; this.oldType = this.track.oldType; this.supportedBy = this.track.supportedBy; this.displayType = this.track.displayType; this.storeToKey = this.track.config.storeToKey; this.newDisplayType = this.displayType; this.inWords = this.track.inWords; this.trackClasses = this.track.trackClasses; this.dialog = new Dialog( { title: "Combine with " + this.newTrackKey, style: "width: 475px;", className: "combinationDialog" }); var content = this._dialogContent(this.newStore); this.dialog.set('content', content); }, _dialogContent: function(store) { var nodesToAdd = []; var opList = this._allAllowedOperations(store); if(!opList.length) { nodesToAdd.push( dom.create("div", {innerHTML: "No operations are possible for this track."}) ); var actionBar = this._createActionBar(false); nodesToAdd.push(actionBar); return nodesToAdd; } nodesToAdd.push( dom.create( "div", { className: 'intro', innerHTML: "Adding " + this.currType + " track " + this.newTrackKey + " to the combination." }) ); var maskOpListDiv = dom.create("div", {id: this.track.name + "_maskOpList"}); var thisB = this; var maskOps = this._makeUnique(opList.map(function(item) { return item.substring(0, 4); })); nodesToAdd.push(maskOpListDiv); this.changingOpPanel = dom.create("div", {id: this.track.name + "_suffixLists"}); nodesToAdd.push(this.changingOpPanel); nodesToAdd.push(dom.create("h2", {innerHTML: "Combination formula"})); this.formulaPreview = dom.create("div", {innerHTML: "(nothing currently selected)", className: "formulaPreview"}); nodesToAdd.push(this.formulaPreview); this.maskOpButtons = []; for(var i in maskOps) { var opButton = this._renderRadioButton(maskOpListDiv, maskOps[i], this.inWords[maskOps[i]]); this.maskOpButtons.push(opButton); opButton.on("change", function(isSelected) { if(isSelected) { delete this.whichArg; delete this.opValue; thisB.maskOpValue = this.value; var numOpLists = thisB.maskOpValue == "1111" ? 3 : 1; thisB.opListDivs = []; thisB.whichArgDivs = []; thisB.opValue = []; thisB.whichArg = []; thisB.changingOpPanel.innerHTML = ""; for(var i = 0; i < numOpLists; i++) { var opDiv = dom.create("div", {id: thisB.track.name + "_suffix" + i, style: {display: "inline-block", "padding-left": "15px", "vertical-align": "top"}}, thisB.changingOpPanel); if(numOpLists == 3) { var text = ["Main", "Mask", "Display"]; dom.create("h2", {innerHTML: text[i]}, opDiv); } var whichOpSpan = dom.create("h3", {innerHTML: "Combining operation", style: {display: "none"}}, opDiv); thisB.opListDivs[i] = dom.create("div", {id: thisB.track.name + "_OpList" + i}, opDiv); var leftRightSpan = dom.create("h3", {innerHTML: "Left or right?", style: {display: "none"}}, opDiv); thisB.whichArgDivs[i] = dom.create("div", {id: thisB.track.name + "_whichArg" + i}, opDiv); var opButtons = thisB._generateSuffixRadioButtons(this.value, opList, store, thisB.opListDivs[i], i); var leftRightButtons = thisB._maybeRenderWhichArgDiv(this.value, store, thisB.whichArgDivs[i], i); if(leftRightButtons.length && !thisB.whichOpArg) { leftRightButtons[0].set('checked', 'checked'); } if(opButtons.length) { opButtons[0].set('checked', 'checked'); } whichOpSpan.style.display = opButtons.length ? "" : "none"; leftRightSpan.style.display = leftRightButtons.length ? "" : "none"; } } }); } if( maskOps[0] ) this.maskOpButtons[0].set('checked', 'checked'); if( maskOps.length <= 1 ) { if ( !maskOps.length || maskOps[0] == "0000") { maskOpListDiv.style.display = 'none'; } this.maskOpButtons[0].set('disabled', 'disabled'); } var actionBar = this._createActionBar(); nodesToAdd.push(actionBar); return nodesToAdd; }, _createActionBar: function (addingEnabled) { if(addingEnabled === undefined) addingEnabled = true; var actionBar = dom.create("div", { className: "dijitDialogPaneActionBar"}); new Button({ iconClass: 'dijitIconDelete', label: "Cancel", onClick: dojo.hitch(this, function() { this.shouldCombine = false; this.dialog.hide(); }) }).placeAt(actionBar); var btnCombine = new Button({ label: "Combine tracks", onClick: dojo.hitch(this, function() { this.shouldCombine = true; this.dialog.hide(); }) }); btnCombine.placeAt(actionBar); if(!addingEnabled) btnCombine.set("disabled", "disabled"); return actionBar; }, _generateSuffixRadioButtons: function(prefix, stringlist, store, parent, offset) { offset = offset || 0; while(parent.firstChild) { if(dijit.byId(parent.firstChild.id)) dijit.byId(parent.firstChild.id).destroy(); dom.destroy(parent.firstChild); } var buttons = []; var thisB = this; var allowedOps = this._generateSuffixList(prefix, stringlist, offset); for(var i in allowedOps) { var opButton = this._renderRadioButton(parent, allowedOps[i], this.inWords[allowedOps[i]]); buttons.push(opButton); opButton.on("change", function(isSelected) { if(isSelected) { thisB.opValue[offset] = this.value; var operation = thisB._getOperation(); thisB.previewTree = thisB._createPreviewTree(operation, store); thisB.formulaPreview.innerHTML = thisB._generateTreeFormula(thisB.previewTree); } }); } return buttons; }, _getOperation: function() { var retString = this.maskOpValue; for(var i = 0; i < this.opListDivs.length; i++) { retString = retString + this.opValue[i] + this.whichArg[i]; } return retString; }, //Type checking necessary? _generateSuffixList: function(prefix, stringlist, offset) { if(offset === undefined) offset = 0; return this._makeUnique(stringlist.filter(function(value) { return value.indexOf(prefix) != -1; }).map(function(item) { return item.substring(prefix.length + offset, prefix.length + offset + 1); })); }, _maybeRenderWhichArgDiv: function(prefix, store, parent, offset) { offset = offset || 0; while(parent.firstChild) { if(dijit.byId(parent.firstChild.id)) { dijit.byId(parent.firstChild.id).destroy(); } dom.destroy(parent.firstChild); } var leftRightButtons = []; var thisB = this; var whichArgChange = function(isSelected, value) { if(isSelected) { thisB.whichArg[offset] = value === undefined ? this.value : value; var operation = thisB._getOperation(); thisB.previewTree = thisB._createPreviewTree(operation, store); thisB.formulaPreview.innerHTML = thisB._generateTreeFormula(thisB.previewTree); } }; if(prefix == "0020") whichArgChange(true, "L"); else if (prefix == "0002") whichArgChange(true, "R"); else if (prefix == "1111" && offset == 0) whichArgChange(true, "?"); else { var rbLeft = this._renderRadioButton(parent, "L", "left"); var rbRight = this._renderRadioButton(parent, "R", "right"); leftRightButtons.push(rbLeft); leftRightButtons.push(rbRight); rbLeft.on("change", whichArgChange); rbRight.on("change", whichArgChange); } return leftRightButtons; }, _makeUnique: function(stringArray) { var unique = {}; return stringArray.filter(function(value) { if(!unique[value]) { unique[value] = true; return true; } return false; }); }, _createPreviewTree: function (opString, store ) { // Recursive cloning would probably be safer, but this seems to be working okay var newOpTree = store.opTree ? store.opTree.clone() : new TreeNode({Value: store}); if(newOpTree) { newOpTree.recursivelyCall(function(node) { node.highlighted = true; }); } var superior = new TreeNode(this.opTree); var firstChars = opString.substring(0, 2); var inferior = newOpTree; if(firstChars == "01") { superior = newOpTree; inferior = this.opTree; } return this._applyTreeTransform(opString.substring(2), superior, inferior); }, _applyTreeTransform: function (opString, superior, inferior) { var retTree = superior; var firstChars = opString.substring(0, 2); var childToUse; var opTree1 = superior; var opTree2 = inferior; switch(firstChars) { case "10": opTree1 = superior.leftChild; childToUse = "leftChild"; opTree2 = inferior; break; case "01": opTree1 = superior.rightChild; childToUse = "rightChild"; opTree2 = inferior; break; case "11": retTree = new TreeNode({Value: opString.substring(2,3)}); retTree["leftChild"] = this._transformTree(opString.substring(4), superior.leftChild, inferior.leftChild); opString = opString.substring(4); childToUse = "rightChild"; opTree1 = superior.rightChild; opTree2 = inferior.rightChild; break; case "20": this.newDisplayType = this.oldType; break; case "02": this.newDisplayType = this.currType; break; } var opNode= this._transformTree(opString.substring(2), opTree1, opTree2); if(childToUse == undefined) return opNode; retTree[childToUse] = opNode; return retTree; }, _transformTree: function(opString, opTree1, opTree2) { var op = opString.substring (0, 1); var opNode = new TreeNode({Value: op}); if(opString.substring(1,2) == "L") { opNode.add(opTree2); opNode.add(opTree1); } else { opNode.add(opTree1); opNode.add(opTree2); } return opNode; }, // This mess constructs a complete list of all operations that can be performed _allAllowedOperations: function(store) { var allowedList = []; var candidate = ""; var allowedOps; candidate = candidate + (this.oldType == "mask" ? "1" : "0"); candidate = candidate + (this.currType == "mask" ? "1" : "0"); if (candidate == "00") { if(this.oldType == this.currType) { var candidate2 = candidate + "00"; allowedOps = this.trackClasses[this.currType].allowedOps; for(var i in allowedOps) { allowedList.push(candidate2 + allowedOps[i]); } } allowedOps = this.trackClasses["mask"].allowedOps; if(this.currType == "set") { var candidate2 = candidate + "20"; for(var i in allowedOps) allowedList.push(candidate2 + allowedOps[i]); } if(this.oldType == "set") { var candidate2 = candidate + "02"; for(var i in allowedOps) allowedList.push(candidate2 + allowedOps[i]); } } else if (candidate == "10") { if(this.currType == "set") { allowedOps = this.trackClasses[this.currType].allowedOps; var candidate2 = candidate + "10"; for(var i in allowedOps) { allowedList.push(candidate2 + allowedOps[i]); } } if(this.currType == this.displayType) { var candidate2 = candidate + "01"; allowedOps = this.trackClasses[this.currType].allowedOps; for(var i in allowedOps) { allowedList.push(candidate2 + allowedOps[i]); } } } else if (candidate == "01") { if(this.oldType == "set") { allowedOps = this.trackClasses[this.oldType].allowedOps; var candidate2 = candidate + "10"; for(var i in allowedOps) { allowedList.push(candidate2 + allowedOps[i]); } } var displayType = this.supportedBy[store.stores.display.config.type]; if(this.oldType == displayType) { candidate = candidate + "01"; var allowedOps = this.trackClasses[displayType].allowedOps; for(var i in allowedOps) { allowedList.push(candidate + allowedOps[i]); } } } else if (candidate == "11") { // Fix the logic of the tree manipulation to work with out the last L's and R's candidate = candidate + "11"; allowedOps = this.trackClasses["set"].allowedOps; for(var i in allowedOps) { var displayType = this.supportedBy[store.stores.display.config.type]; var oldType = this.displayType; if(displayType == oldType) { var allowedOps2 = this.trackClasses[displayType].allowedOps; for(var j in allowedOps2) { var allowedMaskOps = this.trackClasses["mask"].allowedOps; for(var k in allowedMaskOps) { allowedList.push(candidate + allowedMaskOps[k] + allowedOps[i] + allowedOps2[j]); } } } } } return allowedList; }, _renderRadioButton: function(parent, value, label) { var id = parent.id + "_rb_" + value; if(dijit.byId(id)) { dom.destroy(dijit.byId(id).domNode); dijit.byId(id).destroy(); } label = label || value; var radioButton = new RadioButton({name: parent.id + "_rb", id: id, value: value}); parent.appendChild(radioButton.domNode); var radioButtonLabel = dom.create("label", {"for": radioButton.id, innerHTML: label}, parent); parent.appendChild(dom.create("br")); return radioButton; }, run: function( callback, cancelCallback, errorCallback) { this.dialog.show(); var thisB = this; this.dialog.on("Hide", function() { if(thisB.previewTree) { thisB.previewTree.recursivelyCall(function(node) { if(node.highlighted) delete node.highlighted; }); } if(thisB.shouldCombine) callback(thisB.previewTree, thisB.newStore, thisB.newDisplayType); else cancelCallback(); }); }, _generateTreeFormula: function(tree) { if(!tree || tree === undefined){ return '<span class="null">NULL</span>'; } if(tree.isLeaf()){ return '<span class="leaf' + (tree.highlighted ? ' highlighted': '') + '">' + (tree.get().name ? (this.storeToKey[tree.get().name] ? this.storeToKey[tree.get().name] : tree.get().name) : tree.get()) + '</span>'; } return '<span class="tree">(' + this._generateTreeFormula(tree.left()) +' <span class="op" title="' + this.inWords[tree.get()] + '">'+ tree.get() +"</span> " + this._generateTreeFormula(tree.right()) +")</span>"; }, destroyRecursive: function() { this.dialog.destroyRecursive(); } }); });