/static/scripts/galaxy.workflow_editor.canvas.js
JavaScript | 1991 lines | 1720 code | 130 blank | 141 comment | 241 complexity | c6f8619e1882812047aa9b91508c5d54 MD5 | raw file
Possible License(s): CC-BY-3.0
Large files files are truncated, but you can click here to view the full file
- function CollectionTypeDescription( collectionType ) {
- this.collectionType = collectionType;
- this.isCollection = true;
- this.rank = collectionType.split(":").length;
- }
- $.extend( CollectionTypeDescription.prototype, {
- append: function( otherCollectionTypeDescription ) {
- if( otherCollectionTypeDescription === NULL_COLLECTION_TYPE_DESCRIPTION ) {
- return this;
- }
- if( otherCollectionTypeDescription === ANY_COLLECTION_TYPE_DESCRIPTION ) {
- return otherCollectionType;
- }
- return new CollectionTypeDescription( this.collectionType + ":" + otherCollectionTypeDescription.collectionType );
- },
- canMatch: function( otherCollectionTypeDescription ) {
- if( otherCollectionTypeDescription === NULL_COLLECTION_TYPE_DESCRIPTION ) {
- return false;
- }
- if( otherCollectionTypeDescription === ANY_COLLECTION_TYPE_DESCRIPTION ) {
- return true;
- }
- return otherCollectionTypeDescription.collectionType == this.collectionType;
- },
- canMapOver: function( otherCollectionTypeDescription ) {
- if( otherCollectionTypeDescription === NULL_COLLECTION_TYPE_DESCRIPTION ) {
- return false;
- }
- if( otherCollectionTypeDescription === ANY_COLLECTION_TYPE_DESCRIPTION ) {
- return false;
- }
- if( this.rank <= otherCollectionTypeDescription.rank ) {
- // Cannot map over self...
- return false;
- }
- var requiredSuffix = otherCollectionTypeDescription.collectionType
- return this._endsWith( this.collectionType, requiredSuffix );
- },
- effectiveMapOver: function( otherCollectionTypeDescription ) {
- var otherCollectionType = otherCollectionTypeDescription.collectionType;
- var effectiveCollectionType = this.collectionType.substring( 0, this.collectionType.length - otherCollectionType.length - 1 );
- return new CollectionTypeDescription( effectiveCollectionType );
- },
- equal: function( otherCollectionTypeDescription ) {
- return otherCollectionTypeDescription.collectionType == this.collectionType;
- },
- toString: function() {
- return "CollectionType[" + this.collectionType + "]";
- },
- _endsWith: function( str, suffix ) {
- return str.indexOf(suffix, str.length - suffix.length) !== -1;
- }
- } );
- NULL_COLLECTION_TYPE_DESCRIPTION = {
- isCollection: false,
- canMatch: function( other ) { return false; },
- canMapOver: function( other ) {
- return false;
- },
- toString: function() {
- return "NullCollectionType[]";
- },
- append: function( otherCollectionType ) {
- return otherCollectionType;
- },
- equal: function( other ) {
- return other === this;
- }
- };
- ANY_COLLECTION_TYPE_DESCRIPTION = {
- isCollection: true,
- canMatch: function( other ) { return NULL_COLLECTION_TYPE_DESCRIPTION !== other; },
- canMapOver: function( other ) {
- return false;
- },
- toString: function() {
- return "AnyCollectionType[]";
- },
- append: function( otherCollectionType ) {
- throw "Cannot append to ANY_COLLECTION_TYPE_DESCRIPTION";
- },
- equal: function( other ) {
- return other === this;
- }
- };
- var TerminalMapping = Backbone.Model.extend( {
- initialize: function( attr ) {
- this.mapOver = attr.mapOver || NULL_COLLECTION_TYPE_DESCRIPTION;
- this.terminal = attr.terminal;
- this.terminal.terminalMapping = this;
- },
- disableMapOver: function() {
- this.setMapOver( NULL_COLLECTION_TYPE_DESCRIPTION );
- },
- setMapOver: function( collectionTypeDescription ) {
- // TODO: Can I use "attributes" or something to auto trigger "change"
- // event?
- this.mapOver = collectionTypeDescription;
- this.trigger("change");
- }
- } );
- var TerminalMappingView = Backbone.View.extend( {
- tagName: "div",
- className: "fa-icon-button fa fa-folder-o",
- initialize: function( options ) {
- var mapText = "Run tool in parallel over collection";
- this.$el.tooltip( {delay: 500, title: mapText } );
- this.model.bind( "change", _.bind( this.render, this ) );
- },
- render: function() {
- if( this.model.mapOver.isCollection ) {
- this.$el.show();
- } else {
- this.$el.hide();
- }
- },
- } );
- var InputTerminalMappingView = TerminalMappingView.extend( {
- events: {
- "click": "onClick",
- "mouseenter": "onMouseEnter",
- "mouseleave": "onMouseLeave",
- },
- onMouseEnter: function( e ) {
- var model = this.model;
- if( ! model.terminal.connected() && model.mapOver.isCollection ) {
- this.$el.css( "color", "red" );
- }
- },
- onMouseLeave: function( e ) {
- this.$el.css( "color", "black" );
- },
- onClick: function( e ) {
- var model = this.model;
- if( ! model.terminal.connected() && model.mapOver.isCollection ) {
- // TODO: Consider prompting...
- model.terminal.resetMapping();
- }
- },
- } );
- var InputTerminalMapping = TerminalMapping;
- var InputCollectionTerminalMapping = TerminalMapping;
- var OutputTerminalMapping = TerminalMapping;
- var OutputTerminalMappingView = TerminalMappingView;
- var InputCollectionTerminalMappingView = InputTerminalMappingView;
- var OutputCollectionTerminalMapping = TerminalMapping;
- var OutputCollectionTerminalMappingView = TerminalMappingView;
- var Terminal = Backbone.Model.extend( {
- initialize: function( attr ) {
- this.element = attr.element;
- this.connectors = [];
- },
- connect: function ( connector ) {
- this.connectors.push( connector );
- if ( this.node ) {
- this.node.markChanged();
- }
- },
- disconnect: function ( connector ) {
- this.connectors.splice( $.inArray( connector, this.connectors ), 1 );
- if ( this.node ) {
- this.node.markChanged();
- this.resetMappingIfNeeded();
- }
- },
- redraw: function () {
- $.each( this.connectors, function( _, c ) {
- c.redraw();
- });
- },
- destroy: function () {
- $.each( this.connectors.slice(), function( _, c ) {
- c.destroy();
- });
- },
- destroyInvalidConnections: function( ) {
- _.each( this.connectors, function( connector ) {
- connector.destroyIfInvalid();
- } );
- },
- setMapOver : function( val ) {
- if( this.multiple ) {
- return; // Cannot set this to be multirun...
- }
- if( ! this.mapOver().equal( val ) ) {
- this.terminalMapping.setMapOver( val );
- _.each( this.node.output_terminals, function( outputTerminal ) {
- outputTerminal.setMapOver( val );
- } );
- }
- },
- mapOver: function( ) {
- if ( ! this.terminalMapping ) {
- return NULL_COLLECTION_TYPE_DESCRIPTION;
- } else {
- return this.terminalMapping.mapOver;
- }
- },
- isMappedOver: function( ) {
- return this.terminalMapping && this.terminalMapping.mapOver.isCollection;
- },
- resetMapping: function() {
- this.terminalMapping.disableMapOver();
- },
- resetMappingIfNeeded: function( ) {}, // Subclasses should override this...
- } );
- var OutputTerminal = Terminal.extend( {
- initialize: function( attr ) {
- Terminal.prototype.initialize.call( this, attr );
- this.datatypes = attr.datatypes;
- },
- resetMappingIfNeeded: function( ) {
- // If inputs were only mapped over to preserve
- // an output just disconnected reset these...
- if( ! this.node.hasConnectedOutputTerminals() && ! this.node.hasConnectedMappedInputTerminals()){
- _.each( this.node.mappedInputTerminals(), function( mappedInput ) {
- mappedInput.resetMappingIfNeeded();
- } );
- }
- var noMappedInputs = ! this.node.hasMappedOverInputTerminals();
- if( noMappedInputs ) {
- this.resetMapping();
- }
- },
- resetMapping: function() {
- this.terminalMapping.disableMapOver();
- _.each( this.connectors, function( connector ) {
- var connectedInput = connector.handle2;
- if( connectedInput ) {
- // Not exactly right because this is still connected.
- // Either rewrite resetMappingIfNeeded or disconnect
- // and reconnect if valid.
- connectedInput.resetMappingIfNeeded();
- connector.destroyIfInvalid();
- }
- } );
- }
- } );
- var BaseInputTerminal = Terminal.extend( {
- initialize: function( attr ) {
- Terminal.prototype.initialize.call( this, attr );
- this.update( attr.input ); // subclasses should implement this...
- },
- canAccept: function ( other ) {
- if( this._inputFilled() ) {
- return false;
- } else {
- return this.attachable( other );
- }
- },
- resetMappingIfNeeded: function( ) {
- var mapOver = this.mapOver();
- if( ! mapOver.isCollection ) {
- return;
- }
- // No output terminals are counting on this being mapped
- // over if connected inputs are still mapped over or if none
- // of the outputs are connected...
- var reset = this.node.hasConnectedMappedInputTerminals() ||
- ( ! this.node.hasConnectedOutputTerminals() );
- if( reset ) {
- this.resetMapping();
- }
- },
- resetMapping: function() {
- this.terminalMapping.disableMapOver();
- if( ! this.node.hasMappedOverInputTerminals() ) {
- _.each( this.node.output_terminals, function( terminal) {
- // This shouldn't be called if there are mapped over
- // outputs.
- terminal.resetMapping();
- } );
- }
- },
- connected: function() {
- return this.connectors.length !== 0;
- },
- _inputFilled: function() {
- var inputFilled;
- if( ! this.connected() ) {
- inputFilled = false;
- } else {
- if( this.multiple ) {
- if(this._collectionAttached()) {
- // Can only attach one collection to multiple input
- // data parameter.
- inputsFilled = true;
- } else {
- inputFilled = false;
- }
- } else {
- inputFilled = true;
- }
- }
- return inputFilled;
- },
- _collectionAttached: function( ) {
- if( ! this.connected() ) {
- return false;
- } else {
- var firstOutput = this.connectors[ 0 ].handle1;
- if( ! firstOutput ){
- return false;
- } else {
- if( firstOutput.isDataCollectionInput || firstOutput.isMappedOver() || firstOutput.datatypes.indexOf( "input_collection" ) > 0 ) {
- return true;
- } else {
- return false;
- }
- }
- }
- },
- _mappingConstraints: function( ) {
- // If this is a connected terminal, return list of collection types
- // other terminals connected to node are constraining mapping to.
- if( ! this.node ) {
- return []; // No node - completely unconstrained
- }
- var mapOver = this.mapOver();
- if( mapOver.isCollection ) {
- return [ mapOver ];
- }
- var constraints = [];
- if( ! this.node.hasConnectedOutputTerminals() ) {
- _.each( this.node.connectedMappedInputTerminals(), function( inputTerminal ) {
- constraints.push( inputTerminal.mapOver() );
- } );
- } else {
- // All outputs should have same mapOver status - least specific.
- constraints.push( _.first( _.values( this.node.output_terminals ) ).mapOver() );
- }
- return constraints;
- },
- _producesAcceptableDatatype: function( other ) {
- // other is a non-collection output...
- for ( var t in this.datatypes ) {
- var cat_outputs = new Array();
- cat_outputs = cat_outputs.concat(other.datatypes);
- if (other.node.post_job_actions){
- for (var pja_i in other.node.post_job_actions){
- var pja = other.node.post_job_actions[pja_i];
- if (pja.action_type == "ChangeDatatypeAction" && (pja.output_name == '' || pja.output_name == other.name) && pja.action_arguments){
- cat_outputs.push(pja.action_arguments['newtype']);
- }
- }
- }
- // FIXME: No idea what to do about case when datatype is 'input'
- for ( var other_datatype_i in cat_outputs ) {
- var other_datatype = cat_outputs[other_datatype_i];
- if ( other_datatype == "input" || other_datatype == "input_collection" || issubtype( cat_outputs[other_datatype_i], this.datatypes[t] ) ) {
- return true;
- }
- }
- }
- return false;
- },
- _otherCollectionType: function( other ) {
- var otherCollectionType = NULL_COLLECTION_TYPE_DESCRIPTION;
- if( other.isDataCollectionInput ) {
- otherCollectionType = other.collectionType;
- } else {
- var otherMapOver = other.mapOver();
- if( otherMapOver.isCollection ) {
- otherCollectionType = otherMapOver;
- }
- }
- return otherCollectionType;
- },
- } );
- var InputTerminal = BaseInputTerminal.extend( {
- update: function( input ) {
- this.datatypes = input.extensions;
- this.multiple = input.multiple;
- this.collection = false;
- },
- connect: function( connector ) {
- BaseInputTerminal.prototype.connect.call( this, connector );
- var other_output = connector.handle1;
- if( ! other_output ) {
- return;
- }
- var otherCollectionType = this._otherCollectionType( other_output );
- if( otherCollectionType.isCollection ) {
- this.setMapOver( otherCollectionType );
- }
- },
- attachable: function( other ) {
- var otherCollectionType = this._otherCollectionType( other );
- var thisMapOver = this.mapOver();
- if( otherCollectionType.isCollection ) {
- if( this.multiple ) {
- if( this.connected() && ! this._collectionAttached() ) {
- // if single inputs attached, cannot also attach a
- // collection (yet...)
- return false;
- }
- if( otherCollectionType.rank == 1 ) {
- return this._producesAcceptableDatatype( other );
- } else {
- // TODO: Allow subcollection mapping over this as if it were
- // a list collection input.
- return false;
- }
- }
- if( thisMapOver.isCollection && thisMapOver.canMatch( otherCollectionType ) ) {
- return this._producesAcceptableDatatype( other );
- } else {
- // Need to check if this would break constraints...
- var mappingConstraints = this._mappingConstraints();
- if( mappingConstraints.every( _.bind( otherCollectionType.canMatch, otherCollectionType ) ) ) {
- return this._producesAcceptableDatatype( other );
- } else {
- return false;
- }
- }
- } else if( thisMapOver.isCollection ) {
- // Attempting to match a non-collection output to an
- // explicitly collection input.
- return false;
- }
- return this._producesAcceptableDatatype( other );
- }
- });
- var InputCollectionTerminal = BaseInputTerminal.extend( {
- update: function( input ) {
- this.multiple = false;
- this.collection = true;
- this.datatypes = input.extensions;
- if( input.collection_type ) {
- this.collectionType = new CollectionTypeDescription( input.collection_type );
- } else {
- this.collectionType = ANY_COLLECTION_TYPE_DESCRIPTION;
- }
- },
- connect: function( connector ) {
- BaseInputTerminal.prototype.connect.call( this, connector );
- var other = connector.handle1;
- if( ! other ) {
- return;
- }
- var effectiveMapOver = this._effectiveMapOver( other );
- this.setMapOver( effectiveMapOver );
- },
- _effectiveMapOver: function( other ) {
- var collectionType = this.collectionType;
- var otherCollectionType = this._otherCollectionType( other );
- if( ! collectionType.canMatch( otherCollectionType ) ) {
- return otherCollectionType.effectiveMapOver( collectionType );
- } else {
- return NULL_COLLECTION_TYPE_DESCRIPTION;
- }
- },
- _effectiveCollectionType: function( ) {
- var collectionType = this.collectionType;
- var thisMapOver = this.mapOver();
- return thisMapOver.append( collectionType );
- },
- attachable: function ( other ) {
- var otherCollectionType = this._otherCollectionType( other );
- if( otherCollectionType.isCollection ) {
- var effectiveCollectionType = this._effectiveCollectionType( );
- var thisMapOver = this.mapOver();
- if( effectiveCollectionType.canMatch( otherCollectionType ) ) {
- // Only way a direct match...
- return this._producesAcceptableDatatype( other );
- // Otherwise we need to mapOver
- } else if( thisMapOver.isCollection ) {
- // In this case, mapOver already set and we didn't match skipping...
- return false;
- } else if( otherCollectionType.canMapOver( this.collectionType ) ) {
- var effectiveMapOver = this._effectiveMapOver( other );
- if( ! effectiveMapOver.isCollection ) {
- return false;
- }
- // Need to check if this would break constraints...
- var mappingConstraints = this._mappingConstraints();
- if( mappingConstraints.every( effectiveMapOver.canMatch ) ) {
- return this._producesAcceptableDatatype( other );
- }
- }
- }
- return false;
- }
- });
- var OutputCollectionTerminal = Terminal.extend( {
- initialize: function( attr ) {
- Terminal.prototype.initialize.call( this, attr );
- this.datatypes = attr.datatypes;
- this.collectionType = new CollectionTypeDescription( attr.collection_type );
- this.isDataCollectionInput = true;
- },
- update: function( output ) {
- var newCollectionType = new CollectionTypeDescription( output.collection_type );
- if( newCollectionType.collectionType != this.collectionType.collectionType ) {
- _.each( this.connectors, function( connector ) {
- // TODO: consider checking if connection valid before removing...
- connector.destroy();
- } );
- }
- this.collectionType = newCollectionType;
- }
- } );
- function Connector( handle1, handle2 ) {
- this.canvas = null;
- this.dragging = false;
- this.inner_color = "#FFFFFF";
- this.outer_color = "#D8B365";
- if ( handle1 && handle2 ) {
- this.connect( handle1, handle2 );
- }
- }
- $.extend( Connector.prototype, {
- connect: function ( t1, t2 ) {
- this.handle1 = t1;
- if ( this.handle1 ) {
- this.handle1.connect( this );
- }
- this.handle2 = t2;
- if ( this.handle2 ) {
- this.handle2.connect( this );
- }
- },
- destroy : function () {
- if ( this.handle1 ) {
- this.handle1.disconnect( this );
- }
- if ( this.handle2 ) {
- this.handle2.disconnect( this );
- }
- $(this.canvas).remove();
- },
- destroyIfInvalid: function() {
- if( this.handle1 && this.handle2 && ! this.handle2.attachable( this.handle1 ) ) {
- this.destroy();
- }
- },
- redraw : function () {
- var canvas_container = $("#canvas-container");
- if ( ! this.canvas ) {
- this.canvas = document.createElement( "canvas" );
- // excanvas specific hack
- if ( window.G_vmlCanvasManager ) {
- G_vmlCanvasManager.initElement( this.canvas );
- }
- canvas_container.append( $(this.canvas) );
- if ( this.dragging ) {
- this.canvas.style.zIndex = "300";
- }
- }
- var relativeLeft = function( e ) {
- return $(e).offset().left - canvas_container.offset().left;
- };
- var relativeTop = function( e ) {
- return $(e).offset().top - canvas_container.offset().top;
- };
- if (!this.handle1 || !this.handle2) {
- return;
- }
- // Find the position of each handle
- var start_x = relativeLeft( this.handle1.element ) + 5;
- var start_y = relativeTop( this.handle1.element ) + 5;
- var end_x = relativeLeft( this.handle2.element ) + 5;
- var end_y = relativeTop( this.handle2.element ) + 5;
- // Calculate canvas area
- var canvas_extra = 100;
- var canvas_min_x = Math.min( start_x, end_x );
- var canvas_max_x = Math.max( start_x, end_x );
- var canvas_min_y = Math.min( start_y, end_y );
- var canvas_max_y = Math.max( start_y, end_y );
- var cp_shift = Math.min( Math.max( Math.abs( canvas_max_y - canvas_min_y ) / 2, 100 ), 300 );
- var canvas_left = canvas_min_x - canvas_extra;
- var canvas_top = canvas_min_y - canvas_extra;
- var canvas_width = canvas_max_x - canvas_min_x + 2 * canvas_extra;
- var canvas_height = canvas_max_y - canvas_min_y + 2 * canvas_extra;
- // Place the canvas
- this.canvas.style.left = canvas_left + "px";
- this.canvas.style.top = canvas_top + "px";
- this.canvas.setAttribute( "width", canvas_width );
- this.canvas.setAttribute( "height", canvas_height );
- // Adjust points to be relative to the canvas
- start_x -= canvas_left;
- start_y -= canvas_top;
- end_x -= canvas_left;
- end_y -= canvas_top;
- // Draw the line
- var c = this.canvas.getContext("2d"),
- start_offsets = null,
- end_offsets = null;
- var num_offsets = 1;
- if ( this.handle1 && this.handle1.isMappedOver() ) {
- var start_offsets = [ -6, -3, 0, 3, 6 ];
- num_offsets = 5;
- } else {
- var start_offsets = [ 0 ];
- }
- if ( this.handle2 && this.handle2.isMappedOver() ) {
- var end_offsets = [ -6, -3, 0, 3, 6 ];
- num_offsets = 5;
- } else {
- var end_offsets = [ 0 ];
- }
- var connector = this;
- for( var i = 0; i < num_offsets; i++ ) {
- var inner_width = 5,
- outer_width = 7;
- if( start_offsets.length > 1 || end_offsets.length > 1 ) {
- // We have a multi-run, using many lines, make them small.
- inner_width = 1;
- outer_width = 3;
- }
- connector.draw_outlined_curve( start_x, start_y, end_x, end_y, cp_shift, inner_width, outer_width, start_offsets[ i % start_offsets.length ], end_offsets[ i % end_offsets.length ] );
- }
- },
- draw_outlined_curve : function( start_x, start_y, end_x, end_y, cp_shift, inner_width, outer_width, offset_start, offset_end ) {
- var offset_start = offset_start || 0;
- var offset_end = offset_end || 0;
- var c = this.canvas.getContext("2d");
- c.lineCap = "round";
- c.strokeStyle = this.outer_color;
- c.lineWidth = outer_width;
- c.beginPath();
- c.moveTo( start_x, start_y + offset_start );
- c.bezierCurveTo( start_x + cp_shift, start_y + offset_start, end_x - cp_shift, end_y + offset_end, end_x, end_y + offset_end);
- c.stroke();
- // Inner line
- c.strokeStyle = this.inner_color;
- c.lineWidth = inner_width;
- c.beginPath();
- c.moveTo( start_x, start_y + offset_start );
- c.bezierCurveTo( start_x + cp_shift, start_y + offset_start, end_x - cp_shift, end_y + offset_end, end_x, end_y + offset_end );
- c.stroke();
- }
- } );
- var Node = Backbone.Model.extend({
- initialize: function( attr ) {
- this.element = attr.element;
- this.input_terminals = {};
- this.output_terminals = {};
- this.tool_errors = {};
- },
- connectedOutputTerminals: function() {
- return this._connectedTerminals( this.output_terminals );
- },
- _connectedTerminals: function( terminals ) {
- var connectedTerminals = [];
- $.each( terminals, function( _, t ) {
- if( t.connectors.length > 0 ) {
- connectedTerminals.push( t );
- }
- } );
- return connectedTerminals;
- },
- hasConnectedOutputTerminals: function() {
- // return this.connectedOutputTerminals().length > 0; <- optimized this
- var outputTerminals = this.output_terminals;
- for( var outputName in outputTerminals ) {
- if( outputTerminals[ outputName ].connectors.length > 0 ) {
- return true;
- }
- }
- return false;
- },
- connectedMappedInputTerminals: function() {
- return this._connectedMappedTerminals( this.input_terminals );
- },
- hasConnectedMappedInputTerminals: function() {
- // return this.connectedMappedInputTerminals().length > 0; <- optimized this
- var inputTerminals = this.input_terminals;
- for( var inputName in inputTerminals ) {
- var inputTerminal = inputTerminals[ inputName ];
- if( inputTerminal.connectors.length > 0 && inputTerminal.isMappedOver() ) {
- return true;
- }
- }
- return false;
- },
- _connectedMappedTerminals: function( terminals ) {
- var mapped_outputs = [];
- $.each( terminals, function( _, t ) {
- var mapOver = t.mapOver();
- if( mapOver.isCollection ) {
- if( t.connectors.length > 0 ) {
- mapped_outputs.push( t );
- }
- }
- });
- return mapped_outputs;
- },
- mappedInputTerminals: function() {
- return this._mappedTerminals( this.input_terminals );
- },
- _mappedTerminals: function( terminals ) {
- var mappedTerminals = [];
- $.each( terminals, function( _, t ) {
- var mapOver = t.mapOver();
- if( mapOver.isCollection ) {
- mappedTerminals.push( t );
- }
- } );
- return mappedTerminals;
- },
- hasMappedOverInputTerminals: function() {
- var found = false;
- _.each( this.input_terminals, function( t ) {
- var mapOver = t.mapOver();
- if( mapOver.isCollection ) {
- found = true;
- }
- } );
- return found;
- },
- redraw : function () {
- $.each( this.input_terminals, function( _, t ) {
- t.redraw();
- });
- $.each( this.output_terminals, function( _, t ) {
- t.redraw();
- });
- },
- destroy : function () {
- $.each( this.input_terminals, function( k, t ) {
- t.destroy();
- });
- $.each( this.output_terminals, function( k, t ) {
- t.destroy();
- });
- workflow.remove_node( this );
- $(this.element).remove();
- },
- make_active : function () {
- $(this.element).addClass( "toolForm-active" );
- },
- make_inactive : function () {
- // Keep inactive nodes stacked from most to least recently active
- // by moving element to the end of parent's node list
- var element = this.element.get(0);
- (function(p) { p.removeChild( element ); p.appendChild( element ); })(element.parentNode);
- // Remove active class
- $(element).removeClass( "toolForm-active" );
- },
- init_field_data : function ( data ) {
- if ( data.type ) {
- this.type = data.type;
- }
- this.name = data.name;
- this.form_html = data.form_html;
- this.tool_state = data.tool_state;
- this.tool_errors = data.tool_errors;
- this.tooltip = data.tooltip ? data.tooltip : "";
- this.annotation = data.annotation;
- this.post_job_actions = data.post_job_actions ? data.post_job_actions : {};
- this.workflow_outputs = data.workflow_outputs ? data.workflow_outputs : [];
- var node = this;
- var nodeView = new NodeView({
- el: this.element[ 0 ],
- node: node,
- });
- node.nodeView = nodeView;
- $.each( data.data_inputs, function( i, input ) {
- nodeView.addDataInput( input );
- });
- if ( ( data.data_inputs.length > 0 ) && ( data.data_outputs.length > 0 ) ) {
- nodeView.addRule();
- }
- $.each( data.data_outputs, function( i, output ) {
- nodeView.addDataOutput( output );
- } );
- nodeView.render();
- workflow.node_changed( this );
- },
- update_field_data : function( data ) {
- var node = this;
- nodeView = node.nodeView;
- this.tool_state = data.tool_state;
- this.form_html = data.form_html;
- this.tool_errors = data.tool_errors;
- this.annotation = data['annotation'];
- if( "post_job_actions" in data ) {
- // Won't be present in response for data inputs
- var pja_in = $.parseJSON(data.post_job_actions);
- this.post_job_actions = pja_in ? pja_in : {};
- }
- node.nodeView.renderToolErrors();
- // Update input rows
- var old_body = nodeView.$( "div.inputs" );
- var new_body = nodeView.newInputsDiv();
- var newTerminalViews = {};
- _.each( data.data_inputs, function( input ) {
- var terminalView = node.nodeView.addDataInput( input, new_body );
- newTerminalViews[ input.name ] = terminalView;
- });
- // Cleanup any leftover terminals
- _.each( _.difference( _.values( nodeView.terminalViews ), _.values( newTerminalViews ) ), function( unusedView ) {
- unusedView.el.terminal.destroy();
- } );
- nodeView.terminalViews = newTerminalViews;
- // In general workflow editor assumes tool outputs don't change in # or
- // type (not really valid right?) but adding special logic here for
- // data collection input parameters that can have their collection
- // change.
- if( data.data_outputs.length == 1 && "collection_type" in data.data_outputs[ 0 ] ) {
- nodeView.updateDataOutput( data.data_outputs[ 0 ] );
- }
- old_body.replaceWith( new_body );
- // If active, reactivate with new form_html
- this.markChanged();
- this.redraw();
- },
- error : function ( text ) {
- var b = $(this.element).find( ".toolFormBody" );
- b.find( "div" ).remove();
- var tmp = "<div style='color: red; text-style: italic;'>" + text + "</div>";
- this.form_html = tmp;
- b.html( tmp );
- workflow.node_changed( this );
- },
- markChanged: function() {
- workflow.node_changed( this );
- }
- } );
- function Workflow( canvas_container ) {
- this.canvas_container = canvas_container;
- this.id_counter = 0;
- this.nodes = {};
- this.name = null;
- this.has_changes = false;
- this.active_form_has_changes = false;
- }
- $.extend( Workflow.prototype, {
- add_node : function( node ) {
- node.id = this.id_counter;
- node.element.attr( 'id', 'wf-node-step-' + node.id );
- this.id_counter++;
- this.nodes[ node.id ] = node;
- this.has_changes = true;
- node.workflow = this;
- },
- remove_node : function( node ) {
- if ( this.active_node == node ) {
- this.clear_active_node();
- }
- delete this.nodes[ node.id ] ;
- this.has_changes = true;
- },
- remove_all : function() {
- wf = this;
- $.each( this.nodes, function ( k, v ) {
- v.destroy();
- wf.remove_node( v );
- });
- },
- rectify_workflow_outputs : function() {
- // Find out if we're using workflow_outputs or not.
- var using_workflow_outputs = false;
- var has_existing_pjas = false;
- $.each( this.nodes, function ( k, node ) {
- if (node.workflow_outputs && node.workflow_outputs.length > 0){
- using_workflow_outputs = true;
- }
- $.each(node.post_job_actions, function(pja_id, pja){
- if (pja.action_type === "HideDatasetAction"){
- has_existing_pjas = true;
- }
- });
- });
- if (using_workflow_outputs !== false || has_existing_pjas !== false){
- // Using workflow outputs, or has existing pjas. Remove all PJAs and recreate based on outputs.
- $.each(this.nodes, function (k, node ){
- if (node.type === 'tool'){
- var node_changed = false;
- if (node.post_job_actions == null){
- node.post_job_actions = {};
- node_changed = true;
- }
- var pjas_to_rem = [];
- $.each(node.post_job_actions, function(pja_id, pja){
- if (pja.action_type == "HideDatasetAction"){
- pjas_to_rem.push(pja_id);
- }
- });
- if (pjas_to_rem.length > 0 ) {
- $.each(pjas_to_rem, function(i, pja_name){
- node_changed = true;
- delete node.post_job_actions[pja_name];
- });
- }
- if (using_workflow_outputs){
- $.each(node.output_terminals, function(ot_id, ot){
- var create_pja = true;
- $.each(node.workflow_outputs, function(i, wo_name){
- if (ot.name === wo_name){
- create_pja = false;
- }
- });
- if (create_pja === true){
- node_changed = true;
- var pja = {
- action_type : "HideDatasetAction",
- output_name : ot.name,
- action_arguments : {}
- }
- node.post_job_actions['HideDatasetAction'+ot.name] = null;
- node.post_job_actions['HideDatasetAction'+ot.name] = pja;
- }
- });
- }
- // lastly, if this is the active node, and we made changes, reload the display at right.
- if (workflow.active_node == node && node_changed === true) {
- workflow.reload_active_node();
- }
- }
- });
- }
- },
- to_simple : function () {
- var nodes = {};
- $.each( this.nodes, function ( i, node ) {
- var input_connections = {};
- $.each( node.input_terminals, function ( k, t ) {
- input_connections[ t.name ] = null;
- // There should only be 0 or 1 connectors, so this is
- // really a sneaky if statement
- var cons = []
- $.each( t.connectors, function ( i, c ) {
- cons[i] = { id: c.handle1.node.id, output_name: c.handle1.name };
- input_connections[ t.name ] = cons;
- });
- });
- var post_job_actions = {};
- if (node.post_job_actions){
- $.each( node.post_job_actions, function ( i, act ) {
- var pja = {
- action_type : act.action_type,
- output_name : act.output_name,
- action_arguments : act.action_arguments
- }
- post_job_actions[ act.action_type + act.output_name ] = null;
- post_job_actions[ act.action_type + act.output_name ] = pja;
- });
- }
- if (!node.workflow_outputs){
- node.workflow_outputs = [];
- // Just in case.
- }
- var node_data = {
- id : node.id,
- type : node.type,
- tool_id : node.tool_id,
- tool_state : node.tool_state,
- tool_errors : node.tool_errors,
- input_connections : input_connections,
- position : $(node.element).position(),
- annotation: node.annotation,
- post_job_actions: node.post_job_actions,
- workflow_outputs: node.workflow_outputs
- };
- nodes[ node.id ] = node_data;
- });
- return { steps: nodes };
- },
- from_simple : function ( data ) {
- wf = this;
- var max_id = 0;
- wf.name = data.name;
- // First pass, nodes
- var using_workflow_outputs = false;
- $.each( data.steps, function( id, step ) {
- var node = prebuild_node( step.type, step.name, step.tool_id );
- node.init_field_data( step );
- if ( step.position ) {
- node.element.css( { top: step.position.top, left: step.position.left } );
- }
- node.id = step.id;
- wf.nodes[ node.id ] = node;
- max_id = Math.max( max_id, parseInt( id ) );
- // For older workflows, it's possible to have HideDataset PJAs, but not WorkflowOutputs.
- // Check for either, and then add outputs in the next pass.
- if (!using_workflow_outputs && node.type === 'tool'){
- if (node.workflow_outputs.length > 0){
- using_workflow_outputs = true;
- }
- else{
- $.each(node.post_job_actions, function(pja_id, pja){
- if (pja.action_type === "HideDatasetAction"){
- using_workflow_outputs = true;
- }
- });
- }
- }
- });
- wf.id_counter = max_id + 1;
- // Second pass, connections
- $.each( data.steps, function( id, step ) {
- var node = wf.nodes[id];
- $.each( step.input_connections, function( k, v ) {
- if ( v ) {
- if ( ! $.isArray( v ) ) {
- v = [ v ];
- }
- $.each( v, function( l, x ) {
- var other_node = wf.nodes[ x.id ];
- var c = new Connector();
- c.connect( other_node.output_terminals[ x.output_name ],
- node.input_terminals[ k ] );
- c.redraw();
- });
- }
- });
- if(using_workflow_outputs && node.type === 'tool'){
- // Ensure that every output terminal has a WorkflowOutput or HideDatasetAction.
- $.each(node.output_terminals, function(ot_id, ot){
- if(node.post_job_actions['HideDatasetAction'+ot.name] === undefined){
- node.workflow_outputs.push(ot.name);
- callout = $(node.element).find('.callout.'+ot.name);
- callout.find('img').attr('src', galaxy_config.root + 'static/images/fugue/asterisk-small.png');
- workflow.has_changes = true;
- }
- });
- }
- });
- },
- check_changes_in_active_form : function() {
- // If active form has changed, save it
- if (this.active_form_has_changes) {
- this.has_changes = true;
- // Submit form.
- $("#right-content").find("form").submit();
- this.active_form_has_changes = false;
- }
- },
- reload_active_node : function() {
- if (this.active_node){
- var node = this.active_node;
- this.clear_active_node();
- this.activate_node(node);
- }
- },
- clear_active_node : function() {
- if ( this.active_node ) {
- this.active_node.make_inactive();
- this.active_node = null;
- }
- parent.show_form_for_tool( "<div>No node selected</div>" );
- },
- activate_node : function( node ) {
- if ( this.active_node != node ) {
- this.check_changes_in_active_form();
- this.clear_active_node();
- parent.show_form_for_tool( node.form_html + node.tooltip, node );
- node.make_active();
- this.active_node = node;
- }
- },
- node_changed : function ( node ) {
- this.has_changes = true;
- if ( this.active_node == node ) {
- // Reactive with new form_html
- this.check_changes_in_active_form(); //Force changes to be saved even on new connection (previously dumped)
- parent.show_form_for_tool( node.form_html + node.tooltip, node );
- }
- },
- layout : function () {
- this.check_changes_in_active_form();
- this.has_changes = true;
- // Prepare predecessor / successor tracking
- var n_pred = {};
- var successors = {};
- // First pass to initialize arrays even for nodes with no connections
- $.each( this.nodes, function( id, node ) {
- if ( n_pred[id] === undefined ) { n_pred[id] = 0; }
- if ( successors[id] === undefined ) { successors[id] = []; }
- });
- // Second pass to count predecessors and successors
- $.each( this.nodes, function( id, node ) {
- $.each( node.input_terminals, function ( j, t ) {
- $.each( t.connectors, function ( k, c ) {
- // A connection exists from `other` to `node`
- var other = c.handle1.node;
- // node gains a predecessor
- n_pred[node.id] += 1;
- // other gains a successor
- successors[other.id].push( node.id );
- });
- });
- });
- // Assemble order, tracking levels
- node_ids_by_level = [];
- while ( true ) {
- // Everything without a predecessor
- level_parents = [];
- for ( var pred_k in n_pred ) {
- if ( n_pred[ pred_k ] == 0 ) {
- level_parents.push( pred_k );
- }
- }
- if ( level_parents.length == 0 ) {
- break;
- }
- node_ids_by_level.push( level_parents );
- // Remove the parents from this level, and decrement the number
- // of predecessors for each successor
- for ( var k in level_parents ) {
- var v = level_parents[k];
- delete n_pred[v];
- for ( var sk in successors[v] ) {
- n_pred[ successors[v][sk] ] -= 1;
- }
- }
- }
- if ( n_pred.length ) {
- // ERROR: CYCLE! Currently we do nothing
- return;
- }
- // Layout each level
- var all_nodes = this.nodes;
- var h_pad = 80; v_pad = 30;
- var left = h_pad;
- $.each( node_ids_by_level, function( i, ids ) {
- // We keep nodes in the same order in a level to give the user
- // some control over ordering
- ids.sort( function( a, b ) {
- return $(all_nodes[a].element).position().top - $(all_nodes[b].element).position().top;
- });
- // Position each node
- var max_width = 0;
- var top = v_pad;
- $.each( ids, function( j, id ) {
- var node = all_nodes[id];
- var element = $(node.element);
- $(element).css( { top: top, left: left } );
- max_width = Math.max( max_width, $(element).width() );
- top += $(element).height() + v_pad;
- });
- left += max_width + h_pad;
- });
- // Need to redraw all connectors
- $.each( all_nodes, function( _, node ) { node.redraw(); } );
- },
- bounds_for_all_nodes: function() {
- var xmin = Infinity, xmax = -Infinity,
- ymin = Infinity, ymax = -Infinity,
- p;
- $.each( this.nodes, function( id, node ) {
- e = $(node.element);
- p = e.position();
- xmin = Math.min( xmin, p.left );
- xmax = Math.max( xmax, p.left + e.width() );
- ymin = Math.min( ymin, p.top );
- ymax = Math.max( ymax, p.top + e.width() );
- });
- return { xmin: xmin, xmax: xmax, ymin: ymin, ymax: ymax };
- },
- fit_canvas_to_nodes: function() {
- // Span of all elements
- var bounds = this.bounds_for_all_nodes();
- var position = this.canvas_container.position();
- var parent = this.canvas_container.parent();
- // Determine amount we need to expand on top/left
- var xmin_delta = fix_delta( bounds.xmin, 100 );
- var ymin_delta = fix_delta( bounds.ymin, 100 );
- // May need to expand farther to fill viewport
- xmin_delta = Math.max( xmin_delta, position.left );
- ymin_delta = Math.max( ymin_delta, position.top );
- var left = position.left - xmin_delta;
- var top = position.top - ymin_delta;
- // Same for width/height
- var width = round_up( bounds.xmax + 100, 100 ) + xmin_delta;
- var height = round_up( bounds.ymax + 100, 100 ) + ymin_delta;
- width = Math.max( width, - left + parent.width() );
- height = Math.max( height, - top + parent.height() );
- // Grow the canvas container
- this.canvas_container.css( {
- left: left,
- top: top,
- width: width,
- height: height
- });
- // Move elements back if needed
- this.canvas_container.children().each( function() {
- var p = $(this).position();
- $(this).css( "left", p.left + xmin_delta );
- $(this).css( "top", p.top + ymin_delta );
- });
- }
- });
- function fix_delta( x, n ) {
- if ( x < n|| x > 3*n ) {
- new_pos = ( Math.ceil( ( ( x % n ) ) / n ) + 1 ) * n;
- return ( - ( x - new_pos ) );
- }
- return 0;
- }
-
- function round_up( x, n ) {
- return Math.ceil( x / n ) * n;
- }
-
- function prebuild_node( type, title_text, tool_id ) {
- var f = $("<div class='toolForm toolFormInCanvas'></div>");
- var node = new Node( { element: f } );
- node.type = type;
- if ( type == 'tool' ) {
- node.tool_id = tool_id;
- }
- var title = $("<div class='toolFormTitle unselectable'>" + title_text + "</div>" );
- f.append( title );
- f.css( "left", $(window).scrollLeft() + 20 ); f.css( "top", $(window).scrollTop() + 20 );
- var b = $("<div class='toolFormBody'></div>");
- var tmp = "<div><img height='16' align='middle' src='" + galaxy_config.root + "static/images/loading_small_white_bg.gif'/> loading tool info...</div>";
- b.append( tmp );
- node.form_html = tmp;
- f.append( b );
- // Fix width to computed width
- // Now add floats
- var buttons = $("<div class='buttons' style='float: right;'></div>");
- buttons.append( $("<div>").addClass("fa-icon-button fa fa-times").click( function( e ) {
- node.destroy();
- }));
- // Place inside container
- f.appendTo( "#canvas-container" );
- // Position in container
- var o = $("#canvas-container").position();
- var p = $("#canvas-container").parent();
- var width = f.width();
- var height = f.height();
- f.css( { left: ( - o.left ) + ( p.width() / 2 ) - ( width / 2 ), top: ( - o.top ) + ( p.height() / 2 ) - ( height / 2 ) } );
- buttons.prependTo( title );
- width += ( buttons.width() + 10 );
- f.css( "width", width );
- $(f).bind( "dragstart", function() {
- workflow.activate_node( node );
- }).bind( "dragend", function() {
- workflow.node_changed( this );
- workflow.fit_canvas_to_nodes();
- canvas_manager.draw_overview();
- }).bind( "dragclickonly", function() {
- workflow.activate_node( node );
- }).bind( "drag", function( e, d ) {
- // Move
- var po = $(this).offsetParent().offset(),
- x = d.offsetX - po.left,
- y = d.offsetY - po.top;
- $(this).css( { left: x, top: y } );
- // Redraw
- $(this).find( ".terminal" ).each( function() {
- this.terminal.redraw();
- });
- });
- return node;
- }
- function add_node( type, title_text, tool_id ) {
- // Abstraction for use by galaxy.workflow.js to hide
- // some editor details from workflow code and reduce duplication
- // between add_node_for_tool and add_node_for_module.
- var node = prebuild_node( type, title_text, tool_id );
- workflow.add_node( node );
- workflow.fit_canvas_to_nodes();
- canvas_manager.draw_overview();
- workflow.activate_node( node );
- return node;
- }
- var ext_to_type = null;
- var type_to_type = null;
- function issubtype( child, parent ) {
- child = ext_to_type[child];
- parent = ext_to_type[parent];
- return ( type_to_type[child] ) && ( parent in type_to_…
Large files files are truncated, but you can click here to view the full file