mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
1113 lines
45 KiB
Mako
1113 lines
45 KiB
Mako
<%inherit file="/webapps/galaxy/base_panels.mako"/>
|
|
|
|
<%def name="init()">
|
|
<%
|
|
self.active_view="workflow"
|
|
self.overlay_visible=True
|
|
%>
|
|
</%def>
|
|
|
|
<%def name="late_javascripts()">
|
|
<script type='text/javascript' src="${h.url_for('/static/scripts/galaxy.panels.js')}"> </script>
|
|
<script type="text/javascript">
|
|
ensure_dd_helper();
|
|
make_left_panel( $("#left"), $("#center"), $("#left-border" ) );
|
|
make_right_panel( $("#right"), $("#center"), $("#right-border" ) );
|
|
## handle_minwidth_hint = rp.handle_minwidth_hint;
|
|
</script>
|
|
</%def>
|
|
|
|
<%def name="javascripts()">
|
|
|
|
${parent.javascripts()}
|
|
|
|
<!--[if lt IE 9]>
|
|
<script type='text/javascript' src="${h.url_for('/static/scripts/excanvas.js')}"></script>
|
|
<![endif]-->
|
|
|
|
${h.js( "jquery",
|
|
"jquery.tipsy",
|
|
"jquery.event.drag",
|
|
"jquery.event.drop",
|
|
"jquery.event.hover",
|
|
"jquery.form",
|
|
"json2",
|
|
"jstorage",
|
|
"galaxy.base",
|
|
"galaxy.workflow_editor.canvas",
|
|
"jquery.autocomplete",
|
|
"autocomplete_tagging")}
|
|
|
|
<!--[if lt IE 7]>
|
|
<script type='text/javascript'>
|
|
window.lt_ie_7 = true;
|
|
</script>
|
|
<![endif]-->
|
|
|
|
<script type='text/javascript'>
|
|
// Globals
|
|
workflow = null;
|
|
canvas_manager = null;
|
|
active_ajax_call = false;
|
|
var galaxy_async = new GalaxyAsync();
|
|
galaxy_async.set_func_url(galaxy_async.set_user_pref, "${h.url_for( controller='user', action='set_user_pref_async' )}");
|
|
|
|
// jQuery onReady
|
|
$( function() {
|
|
|
|
if ( window.lt_ie_7 ) {
|
|
show_modal(
|
|
"Browser not supported",
|
|
"Sorry, the workflow editor is not supported for IE6 and below."
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Init tool options.
|
|
%if trans.app.toolbox_search.enabled:
|
|
make_popupmenu( $("#tools-options-button"), {
|
|
## Search tools menu item.
|
|
<%
|
|
show_tool_search = False
|
|
if trans.user:
|
|
show_tool_search = trans.user.preferences.get( "workflow.show_tool_search", "True" )
|
|
|
|
if show_tool_search == "True":
|
|
initial_text = "Hide Search"
|
|
else:
|
|
initial_text = "Search Tools"
|
|
%>
|
|
"${initial_text}": function() {
|
|
// Show/hide menu and update vars, user preferences.
|
|
var menu = $('#tool-search');
|
|
if (menu.is(":visible")) {
|
|
// Hide menu.
|
|
pref_value = "False";
|
|
menu_option_text = "Search Tools";
|
|
menu.toggle();
|
|
|
|
// Reset search.
|
|
reset_tool_search(true);
|
|
} else {
|
|
// Show menu.
|
|
pref_value = "True";
|
|
menu_option_text = "Hide Search";
|
|
menu.toggle();
|
|
}
|
|
|
|
// Update menu option.
|
|
$("#tools-options-button-menu").find("li").eq(0).text(menu_option_text);
|
|
|
|
galaxy_async.set_user_pref("workflow.show_tool_search", pref_value);
|
|
}
|
|
});
|
|
|
|
// Init searching.
|
|
$("#tool-search-query").click( function (){
|
|
$(this).focus();
|
|
$(this).select();
|
|
})
|
|
.keyup( function () {
|
|
// Remove italics.
|
|
$(this).css("font-style", "normal");
|
|
|
|
// Don't update if same value as last time
|
|
if ( this.value.length < 3 ) {
|
|
reset_tool_search(false);
|
|
} else if ( this.value != this.lastValue ) {
|
|
// Add class to denote that searching is active.
|
|
$(this).addClass("search_active");
|
|
// input.addClass(config.loadingClass);
|
|
// Add '*' to facilitate partial matching.
|
|
var q = this.value + '*';
|
|
// Stop previous ajax-request
|
|
if (this.timer) {
|
|
clearTimeout(this.timer);
|
|
}
|
|
// Start a new ajax-request in X ms
|
|
$("#search-spinner").show();
|
|
this.timer = setTimeout(function () {
|
|
|
|
$.get("${h.url_for( controller='root', action='tool_search' )}", { query: q }, function (data) {
|
|
// input.removeClass(config.loadingClass);
|
|
// Show live-search if results and search-term aren't empty
|
|
$("#search-no-results").hide();
|
|
// Hide all tool sections.
|
|
$(".toolSectionWrapper").hide();
|
|
// This hides all tools but not workflows link (which is in a .toolTitle div).
|
|
$(".toolSectionWrapper").find(".toolTitle").hide();
|
|
if ( data.length != 0 ) {
|
|
// Map tool ids to element ids and join them.
|
|
var s = $.map( data, function( n, i ) { return "#link-" + n; } ).join( ", " );
|
|
|
|
// First pass to show matching tools and their parents.
|
|
$(s).each( function() {
|
|
// Add class to denote match.
|
|
$(this).parent().addClass("search_match");
|
|
$(this).parent().show().parent().parent().show().parent().show();
|
|
});
|
|
|
|
// Hide labels that have no visible children.
|
|
$(".toolPanelLabel").each( function() {
|
|
var this_label = $(this);
|
|
var next = this_label.next();
|
|
var no_visible_tools = true;
|
|
// Look through tools following label and, if none are visible, hide label.
|
|
while (next.length !== 0 && next.hasClass("toolTitle")) {
|
|
if (next.is(":visible")) {
|
|
no_visible_tools = false;
|
|
break;
|
|
} else {
|
|
next = next.next();
|
|
}
|
|
}
|
|
if (no_visible_tools) {
|
|
this_label.hide();
|
|
}
|
|
});
|
|
} else {
|
|
$("#search-no-results").show();
|
|
}
|
|
$("#search-spinner").hide();
|
|
}, "json" );
|
|
}, 200 );
|
|
}
|
|
this.lastValue = this.value;
|
|
});
|
|
%endif
|
|
|
|
// Canvas overview management
|
|
canvas_manager = new CanvasManager( $("#canvas-viewport"), $("#overview") );
|
|
|
|
// Initialize workflow state
|
|
reset();
|
|
// Load the datatype info
|
|
$.ajax( {
|
|
url: "${h.url_for( action='get_datatypes' )}",
|
|
dataType: "json",
|
|
cache: false,
|
|
success: function( data ) {
|
|
populate_datatype_info( data );
|
|
// Load workflow definition
|
|
$.ajax( {
|
|
url: "${h.url_for( action='load_workflow' )}",
|
|
data: { id: "${trans.security.encode_id( stored.id )}", "_": "true" },
|
|
dataType: 'json',
|
|
cache: false,
|
|
success: function( data ) {
|
|
reset();
|
|
workflow.from_simple( data );
|
|
workflow.has_changes = false;
|
|
workflow.fit_canvas_to_nodes();
|
|
scroll_to_nodes();
|
|
canvas_manager.draw_overview();
|
|
// Determine if any parameters were 'upgraded' and provide message
|
|
upgrade_message = "";
|
|
$.each( data.upgrade_messages, function( k, v ) {
|
|
upgrade_message += ( "<li>Step " + ( parseInt(k, 10) + 1 ) + ": " + workflow.nodes[k].name + "<ul>");
|
|
$.each( v, function( i, vv ) {
|
|
upgrade_message += "<li>" + vv +"</li>";
|
|
});
|
|
upgrade_message += "</ul></li>";
|
|
});
|
|
if ( upgrade_message ) {
|
|
show_modal( "Workflow loaded with changes",
|
|
"Problems were encountered loading this workflow (possibly a result of tool upgrades). Please review the following parameters and then save.<ul>" + upgrade_message + "</ul>",
|
|
{ "Continue" : hide_modal } );
|
|
} else {
|
|
hide_modal();
|
|
}
|
|
show_workflow_parameters();
|
|
},
|
|
beforeSubmit: function( data ) {
|
|
show_modal( "Loading workflow", "progress" );
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// For autosave purposes
|
|
$(document).ajaxStart( function() {
|
|
active_ajax_call = true;
|
|
$(document).bind( "ajaxStop.global", function() {
|
|
active_ajax_call = false;
|
|
});
|
|
});
|
|
|
|
$(document).ajaxError( function ( e, x ) {
|
|
// console.log( e, x );
|
|
var message = x.responseText || x.statusText || "Could not connect to server";
|
|
show_modal( "Server error", message, { "Ignore error" : hide_modal } );
|
|
return false;
|
|
});
|
|
|
|
make_popupmenu( $("#workflow-options-button"), {
|
|
##"Create New" : create_new_workflow_dialog,
|
|
"Edit Attributes" : edit_workflow_attributes,
|
|
##"Edit Workflow Outputs": edit_workflow_outputs,
|
|
"Layout": layout_editor,
|
|
"Save" : save_current_workflow,
|
|
##"Load a Workflow" : load_workflow,
|
|
"Close": close_editor
|
|
});
|
|
|
|
function edit_workflow_outputs(){
|
|
workflow.clear_active_node();
|
|
$('.right-content').hide();
|
|
var new_content = "";
|
|
for (var node_key in workflow.nodes){
|
|
var node = workflow.nodes[node_key];
|
|
if(node.type == 'tool'){
|
|
new_content += "<div class='toolForm' style='margin-bottom:5px;'><div class='toolFormTitle'>Step " + node.id + " - " + node.name + "</div>";
|
|
for (var ot_key in node.output_terminals){
|
|
var output = node.output_terminals[ot_key];
|
|
// if (node.workflow_outputs[node.id + "|" + output.name]){
|
|
if ($.inArray(output.name, node.workflow_outputs) != -1){
|
|
new_content += "<p>"+output.name +"<input type='checkbox' name='"+ node.id + "|" + output.name +"' checked /></p>";
|
|
}
|
|
else{
|
|
new_content += "<p>"+output.name +"<input type='checkbox' name='"+ node.id + "|" + output.name +"' /></p>";
|
|
}
|
|
}
|
|
new_content += "</div>";
|
|
}
|
|
}
|
|
$("#output-fill-area").html(new_content);
|
|
$("#output-fill-area input").bind('click', function(){
|
|
var node_id = this.name.split('|')[0];
|
|
var output_name = this.name.split('|')[1];
|
|
if (this.checked){
|
|
if($.inArray(output_name, workflow.nodes[node_id].workflow_outputs) == -1){
|
|
workflow.nodes[node_id].workflow_outputs.push(output_name);
|
|
}//else it's already in the array. Shouldn't happen, but forget it.
|
|
}else{
|
|
while ($.inArray(output_name, workflow.nodes[node_id].workflow_outputs) != -1){
|
|
var ia = $.inArray(output_name, workflow.nodes[node_id].workflow_outputs);
|
|
workflow.nodes[node_id].workflow_outputs = workflow.nodes[node_id].workflow_outputs.slice(0,ia).concat( workflow.nodes[node_id].workflow_outputs.slice(ia+1) );
|
|
}
|
|
}
|
|
workflow.has_changes = true;
|
|
});
|
|
$('#workflow-output-area').show();
|
|
}
|
|
|
|
function layout_editor() {
|
|
workflow.layout();
|
|
workflow.fit_canvas_to_nodes();
|
|
scroll_to_nodes();
|
|
canvas_manager.draw_overview();
|
|
}
|
|
|
|
function edit_workflow_attributes() {
|
|
workflow.clear_active_node();
|
|
$('.right-content').hide();
|
|
$('#edit-attributes').show();
|
|
|
|
}
|
|
|
|
// On load, set the size to the pref stored in local storage if it exists
|
|
overview_size = $.jStorage.get("overview-size");
|
|
if (overview_size !== undefined) {
|
|
$("#overview-border").css( {
|
|
width: overview_size,
|
|
height: overview_size
|
|
});
|
|
}
|
|
|
|
// Show viewport on load unless pref says it's off
|
|
if ($.jStorage.get("overview-off")) {
|
|
hide_overview();
|
|
} else {
|
|
show_overview();
|
|
}
|
|
|
|
// Stores the size of the overview into local storage when it's resized
|
|
$("#overview-border").bind( "dragend", function( e, d ) {
|
|
var op = $(this).offsetParent();
|
|
var opo = op.offset();
|
|
var new_size = Math.max( op.width() - ( d.offsetX - opo.left ),
|
|
op.height() - ( d.offsetY - opo.top ) );
|
|
$.jStorage.set("overview-size", new_size + "px");
|
|
});
|
|
|
|
function show_overview() {
|
|
$.jStorage.set("overview-off", false);
|
|
$("#overview-border").css("right", "0px");
|
|
$("#close-viewport").css("background-position", "0px 0px");
|
|
}
|
|
|
|
function hide_overview() {
|
|
$.jStorage.set("overview-off", true);
|
|
$("#overview-border").css("right", "20000px");
|
|
$("#close-viewport").css("background-position", "12px 0px");
|
|
}
|
|
|
|
// Lets the overview be toggled visible and invisible, adjusting the arrows accordingly
|
|
$("#close-viewport").click( function() {
|
|
if ( $("#overview-border").css("right") === "0px" ) {
|
|
hide_overview();
|
|
} else {
|
|
show_overview();
|
|
}
|
|
});
|
|
|
|
// Unload handler
|
|
window.onbeforeunload = function() {
|
|
if ( workflow && workflow.has_changes ) {
|
|
return "There are unsaved changes to your workflow which will be lost.";
|
|
}
|
|
};
|
|
|
|
// Tool menu
|
|
$( "div.toolSectionBody" ).hide();
|
|
$( "div.toolSectionTitle > span" ).wrap( "<a href='#'></a>" );
|
|
var last_expanded = null;
|
|
$( "div.toolSectionTitle" ).each( function() {
|
|
var body = $(this).next( "div.toolSectionBody" );
|
|
$(this).click( function() {
|
|
if ( body.is( ":hidden" ) ) {
|
|
if ( last_expanded ) last_expanded.slideUp( "fast" );
|
|
last_expanded = body;
|
|
body.slideDown( "fast" );
|
|
}
|
|
else {
|
|
body.slideUp( "fast" );
|
|
last_expanded = null;
|
|
}
|
|
});
|
|
});
|
|
|
|
// Rename async.
|
|
async_save_text("workflow-name", "workflow-name", "${h.url_for( action='rename_async', id=trans.security.encode_id(stored.id) )}", "new_name");
|
|
|
|
// Tag async. Simply have the workflow edit element generate a click on the tag element to activate tagging.
|
|
$('#workflow-tag').click( function() {
|
|
$('.tag-area').click();
|
|
return false;
|
|
});
|
|
// Annotate async.
|
|
async_save_text("workflow-annotation", "workflow-annotation", "${h.url_for( action='annotate_async', id=trans.security.encode_id(stored.id) )}", "new_annotation", 25, true, 4);
|
|
});
|
|
|
|
// Global state for the whole workflow
|
|
function reset() {
|
|
if ( workflow ) {
|
|
workflow.remove_all();
|
|
}
|
|
workflow = new Workflow( $("#canvas-container") );
|
|
}
|
|
|
|
function scroll_to_nodes() {
|
|
var cv = $("#canvas-viewport");
|
|
var cc = $("#canvas-container");
|
|
var top, left;
|
|
if ( cc.width() < cv.width() ) {
|
|
left = ( cv.width() - cc.width() ) / 2;
|
|
} else {
|
|
left = 0;
|
|
}
|
|
if ( cc.height() < cv.height() ) {
|
|
top = ( cv.height() - cc.height() ) / 2;
|
|
} else {
|
|
top = 0;
|
|
}
|
|
cc.css( { left: left, top: top } );
|
|
}
|
|
|
|
// Add a new step to the workflow by tool id
|
|
function add_node_for_tool( id, title ) {
|
|
var node = prebuild_node( 'tool', title, id );
|
|
workflow.add_node( node );
|
|
workflow.fit_canvas_to_nodes();
|
|
canvas_manager.draw_overview();
|
|
workflow.activate_node( node );
|
|
$.ajax( {
|
|
url: "${h.url_for( action='get_new_module_info' )}",
|
|
data: { type: "tool", tool_id: id, "_": "true" },
|
|
global: false,
|
|
dataType: "json",
|
|
success: function( data ) {
|
|
node.init_field_data( data );
|
|
},
|
|
error: function( x, e ) {
|
|
var m = "error loading field data";
|
|
if ( x.status === 0 ) {
|
|
m += ", server unavailable";
|
|
}
|
|
node.error( m );
|
|
}
|
|
});
|
|
}
|
|
|
|
function add_node_for_module( type, title ) {
|
|
node = prebuild_node( type, title );
|
|
workflow.add_node( node );
|
|
workflow.fit_canvas_to_nodes();
|
|
canvas_manager.draw_overview();
|
|
workflow.activate_node( node );
|
|
$.ajax( {
|
|
url: "${h.url_for( action='get_new_module_info' )}",
|
|
data: { type: type, "_": "true" },
|
|
dataType: "json",
|
|
success: function( data ) {
|
|
node.init_field_data( data );
|
|
},
|
|
error: function( x, e ) {
|
|
var m = "error loading field data"
|
|
if ( x.status == 0 ) {
|
|
m += ", server unavailable"
|
|
}
|
|
node.error( m );
|
|
}
|
|
});
|
|
}
|
|
|
|
<%
|
|
from galaxy.jobs.actions.post import ActionBox
|
|
%>
|
|
|
|
// This function preloads how to display known pja's.
|
|
function display_pja(pja, node){
|
|
// DBTODO SANITIZE INPUTS.
|
|
p_str = '';
|
|
${ActionBox.get_forms(trans)}
|
|
$("#pja_container").append(p_str);
|
|
$("#pja_container>.toolForm:last>.toolFormTitle>.buttons").click(function (){
|
|
action_to_rem = $(this).closest(".toolForm", ".action_tag").children(".action_tag:first").text();
|
|
$(this).closest(".toolForm").remove();
|
|
delete workflow.active_node.post_job_actions[action_to_rem];
|
|
workflow.active_form_has_changes = true;
|
|
});
|
|
}
|
|
|
|
function display_pja_list(){
|
|
return "${ActionBox.get_add_list()}";
|
|
}
|
|
|
|
function display_file_list(node){
|
|
addlist = "<select id='node_data_list' name='node_data_list'>";
|
|
for (var out_terminal in node.output_terminals){
|
|
addlist += "<option value='" + out_terminal + "'>"+ out_terminal +"</option>";
|
|
}
|
|
addlist += "</select>";
|
|
return addlist;
|
|
}
|
|
|
|
function new_pja(action_type, target, node){
|
|
if (node.post_job_actions === undefined){
|
|
//New tool node, set up dict.
|
|
node.post_job_actions = {};
|
|
}
|
|
if (node.post_job_actions[action_type+target] === undefined){
|
|
var new_pja = {};
|
|
new_pja.action_type = action_type;
|
|
new_pja.output_name = target;
|
|
node.post_job_actions[action_type+target] = null;
|
|
node.post_job_actions[action_type+target] = new_pja;
|
|
display_pja(new_pja, node);
|
|
workflow.active_form_has_changes = true;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function show_workflow_parameters(){
|
|
var parameter_re = /\$\{.+?\}/g;
|
|
var workflow_parameters = [];
|
|
var wf_parm_container = $("#workflow-parameters-container");
|
|
var wf_parm_box = $("#workflow-parameters-box");
|
|
var new_parameter_content = "";
|
|
var matches = [];
|
|
$.each(workflow.nodes, function (k, node){
|
|
var form_matches = node.form_html.match(parameter_re);
|
|
if (form_matches){
|
|
matches = matches.concat(form_matches);
|
|
}
|
|
if (node.post_job_actions){
|
|
$.each(node.post_job_actions, function(k, pja){
|
|
if (pja.action_arguments){
|
|
$.each(pja.action_arguments, function(k, action_argument){
|
|
var arg_matches = action_argument.match(parameter_re);
|
|
if (arg_matches){
|
|
matches = matches.concat(arg_matches);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
if (matches){
|
|
$.each(matches, function(k, element){
|
|
if ($.inArray(element, workflow_parameters) === -1){
|
|
workflow_parameters.push(element);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
if (workflow_parameters && workflow_parameters.length !== 0){
|
|
$.each(workflow_parameters, function(k, element){
|
|
new_parameter_content += "<div>" + element.substring(2, element.length -1) + "</div>";
|
|
});
|
|
wf_parm_container.html(new_parameter_content);
|
|
wf_parm_box.show();
|
|
}else{
|
|
wf_parm_container.html(new_parameter_content);
|
|
wf_parm_box.hide();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function show_form_for_tool( text, node ) {
|
|
$('.right-content').hide();
|
|
$("#right-content").show().html( text );
|
|
// Add metadata form to tool.
|
|
if (node) {
|
|
$("#right-content").find(".toolForm:first").after( "<p><div class='metadataForm'> \
|
|
<div class='metadataFormTitle'>Edit Step Attributes</div> \
|
|
<div class='form-row'> \
|
|
<label>Annotation / Notes:</label> \
|
|
<div style='margin-right: 10px;'> \
|
|
<textarea name='annotation' rows='3' style='width: 100%'>" + node.annotation + "</textarea> \
|
|
<div class='toolParamHelp'>Add an annotation or notes to this step; annotations are available when a workflow is viewed.</div> \
|
|
</div> \
|
|
</div> \
|
|
</div>" );
|
|
}
|
|
// Add step actions.
|
|
if (node && node.type=='tool'){
|
|
pjastr = "<p><div class='metadataForm'><div class='metadataFormTitle'>Edit Step Actions</div><div class='form-row'> \
|
|
" + display_pja_list() + " <br/> "+ display_file_list(node) + " <div class='action-button' style='border:1px solid black;display:inline;' id='add_pja'>Create</div>\
|
|
</div><div class='form-row'>\
|
|
<div style='margin-right: 10px;'><span id='pja_container'></span>";
|
|
pjastr += "<div class='toolParamHelp'>Add actions to this step; actions are applied when this workflow step completes.</div></div></div></div>";
|
|
$("#right-content").find(".toolForm").after( pjastr );
|
|
for (var key in node.post_job_actions){
|
|
if (key != "undefined"){ //To make sure we haven't just deleted it.
|
|
display_pja(node.post_job_actions[key], node);
|
|
}
|
|
}
|
|
$("#add_pja").click(function (){
|
|
new_pja($("#new_pja_list").val(),$("#node_data_list").val(), node);
|
|
});
|
|
}
|
|
$("#right-content").find( "form" ).ajaxForm( {
|
|
type: 'POST',
|
|
dataType: 'json',
|
|
success: function( data ) {
|
|
workflow.active_form_has_changes = false;
|
|
node.update_field_data( data );
|
|
show_workflow_parameters();
|
|
},
|
|
beforeSubmit: function( data ) {
|
|
data.push( { name: 'tool_state', value: node.tool_state } );
|
|
data.push( { name: '_', value: "true" } );
|
|
}
|
|
}).each( function() {
|
|
var form = this;
|
|
$(this).find( "select[refresh_on_change='true']").change( function() {
|
|
$(form).submit();
|
|
});
|
|
$(this).find( ".popupmenu" ).each( function() {
|
|
var id = $(this).parents( "div.form-row" ).attr( 'id' );
|
|
var b = $('<a class="popup-arrow" id="popup-arrow-for-' + id + '">▼</a>');
|
|
var options = {};
|
|
$(this).find( "button" ).each( function() {
|
|
var name = $(this).attr( 'name' );
|
|
var value = $(this).attr( 'value' );
|
|
options[ $(this).text() ] = function() {
|
|
$(form).append( "<input type='hidden' name='"+name+"' value='"+value+"' />" ).submit();
|
|
};
|
|
});
|
|
b.insertAfter( this );
|
|
$(this).remove();
|
|
make_popupmenu( b, options );
|
|
});
|
|
// Implements auto-saving based on whether the inputs change. We consider
|
|
// "changed" to be when a field is accessed and not necessarily modified
|
|
// because of an issue where "onchange" is not triggered when activating
|
|
// another node, or saving the workflow.
|
|
$(this).find("input,textarea,select").each( function() {
|
|
$(this).bind("focus click", function() {
|
|
workflow.active_form_has_changes = true;
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
var close_editor = function() {
|
|
<% next_url = h.url_for( controller='workflow', action='index' ) %>
|
|
workflow.check_changes_in_active_form();
|
|
if ( workflow && workflow.has_changes ) {
|
|
do_close = function() {
|
|
window.onbeforeunload = undefined;
|
|
window.document.location = "${next_url}"
|
|
};
|
|
show_modal( "Close workflow editor",
|
|
"There are unsaved changes to your workflow which will be lost.",
|
|
{
|
|
"Cancel" : hide_modal,
|
|
"Save Changes" : function() {
|
|
save_current_workflow( null, do_close );
|
|
}
|
|
}, {
|
|
"Don't Save": do_close
|
|
} );
|
|
} else {
|
|
window.document.location = "${next_url}";
|
|
}
|
|
};
|
|
|
|
var save_current_workflow = function ( eventObj, success_callback ) {
|
|
show_modal( "Saving workflow", "progress" );
|
|
workflow.check_changes_in_active_form();
|
|
if (!workflow.has_changes) {
|
|
hide_modal();
|
|
if ( success_callback ) {
|
|
success_callback();
|
|
}
|
|
return;
|
|
}
|
|
workflow.rectify_workflow_outputs();
|
|
var savefn = function(callback) {
|
|
$.ajax( {
|
|
url: "${h.url_for( action='save_workflow' )}",
|
|
type: "POST",
|
|
data: {
|
|
id: "${trans.security.encode_id( stored.id )}",
|
|
workflow_data: function() { return JSON.stringify( workflow.to_simple() ); },
|
|
"_": "true"
|
|
},
|
|
dataType: 'json',
|
|
success: function( data ) {
|
|
var body = $("<div></div>").text( data.message );
|
|
if ( data.errors ) {
|
|
body.addClass( "warningmark" );
|
|
var errlist = $( "<ul/>" );
|
|
$.each( data.errors, function( i, v ) {
|
|
$("<li></li>").text( v ).appendTo( errlist );
|
|
});
|
|
body.append( errlist );
|
|
} else {
|
|
body.addClass( "donemark" );
|
|
}
|
|
workflow.name = data.name;
|
|
workflow.has_changes = false;
|
|
workflow.stored = true;
|
|
show_workflow_parameters();
|
|
if ( data.errors ) {
|
|
show_modal( "Saving workflow", body, { "Ok" : hide_modal } );
|
|
} else {
|
|
if (callback) {
|
|
callback();
|
|
}
|
|
hide_modal();
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
// We bind to ajaxStop because of auto-saving, since the form submission ajax
|
|
// call needs to be completed so that the new data is saved
|
|
if (active_ajax_call) {
|
|
$(document).bind('ajaxStop.save_workflow', function() {
|
|
$(document).unbind('ajaxStop.save_workflow');
|
|
savefn();
|
|
$(document).unbind('ajaxStop.save_workflow'); // IE7 needs it here
|
|
active_ajax_call = false;
|
|
});
|
|
} else {
|
|
savefn(success_callback);
|
|
}
|
|
};
|
|
|
|
</script>
|
|
</%def>
|
|
|
|
<%def name="stylesheets()">
|
|
|
|
## Include "base.css" for styling tool menu and forms (details)
|
|
${h.css( "base", "autocomplete_tagging", "tool_menu" )}
|
|
|
|
## But make sure styles for the layout take precedence
|
|
${parent.stylesheets()}
|
|
|
|
<style type="text/css">
|
|
body { margin: 0; padding: 0; overflow: hidden; }
|
|
|
|
/* Wider right panel */
|
|
#center { right: 309px; }
|
|
#right-border { right: 300px; }
|
|
#right { width: 300px; }
|
|
## /* Relative masthead size */
|
|
## #masthead { height: 2.5em; }
|
|
## #masthead div.title { font-size: 1.8em; }
|
|
## #left, #left-border, #center, #right-border, #right {
|
|
## top: 2.5em;
|
|
## margin-top: 7px;
|
|
## }
|
|
|
|
#left {
|
|
background: #C1C9E5 url(${h.url_for('/static/style/menu_bg.png')}) top repeat-x;
|
|
}
|
|
|
|
div.toolMenu {
|
|
margin: 5px;
|
|
margin-left: 10px;
|
|
margin-right: 10px;
|
|
}
|
|
div.toolMenuGroupHeader {
|
|
font-weight: bold;
|
|
padding-top: 0.5em;
|
|
padding-bottom: 0.5em;
|
|
color: #333;
|
|
font-style: italic;
|
|
border-bottom: dotted #333 1px;
|
|
margin-bottom: 0.5em;
|
|
}
|
|
div.toolTitleDisabled {
|
|
padding-top: 5px;
|
|
padding-bottom: 5px;
|
|
margin-left: 16px;
|
|
margin-right: 10px;
|
|
display: list-item;
|
|
list-style: square outside;
|
|
font-style: italic;
|
|
color: gray;
|
|
}
|
|
div.toolTitleNoSectionDisabled {
|
|
padding-bottom: 0px;
|
|
font-style: italic;
|
|
color: gray;
|
|
}
|
|
div.toolFormRow {
|
|
position: relative;
|
|
}
|
|
|
|
.right-content {
|
|
margin: 5px;
|
|
}
|
|
|
|
canvas { position: absolute; z-index: 10; }
|
|
canvas.dragging { position: absolute; z-index: 1000; }
|
|
.input-terminal { width: 12px; height: 12px; background: url(${h.url_for('/static/style/workflow_circle_open.png')}); position: absolute; top: 50%; margin-top: -6px; left: -6px; z-index: 1500; }
|
|
.output-terminal { width: 12px; height: 12px; background: url(${h.url_for('/static/style/workflow_circle_open.png')}); position: absolute; top: 50%; margin-top: -6px; right: -6px; z-index: 1500; }
|
|
.drag-terminal { width: 12px; height: 12px; background: url(${h.url_for('/static/style/workflow_circle_drag.png')}); position: absolute; z-index: 1500; }
|
|
.input-terminal-active { background: url(${h.url_for('/static/style/workflow_circle_green.png')}); }
|
|
## .input-terminal-hover { background: yellow; border: solid black 1px; }
|
|
.unselectable { -moz-user-select: none; -khtml-user-select: none; user-select: none; }
|
|
img { border: 0; }
|
|
|
|
div.buttons img {
|
|
width: 16px; height: 16px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
## Extra styles for the representation of a tool on the canvas (looks like
|
|
## a tiny tool form)
|
|
div.toolFormInCanvas {
|
|
z-index: 100;
|
|
position: absolute;
|
|
## min-width: 130px;
|
|
margin: 6px;
|
|
}
|
|
|
|
div.toolForm-active {
|
|
z-index: 1001;
|
|
border: solid #8080FF 4px;
|
|
margin: 3px;
|
|
}
|
|
|
|
div.toolFormTitle {
|
|
cursor: move;
|
|
min-height: 16px;
|
|
}
|
|
|
|
div.titleRow {
|
|
font-weight: bold;
|
|
border-bottom: dotted gray 1px;
|
|
margin-bottom: 0.5em;
|
|
padding-bottom: 0.25em;
|
|
}
|
|
div.form-row {
|
|
position: relative;
|
|
}
|
|
|
|
div.tool-node-error div.toolFormTitle {
|
|
background: #FFCCCC;
|
|
border-color: #AA6666;
|
|
}
|
|
div.tool-node-error {
|
|
border-color: #AA6666;
|
|
}
|
|
|
|
#canvas-area {
|
|
position: absolute;
|
|
top: 0; left: 305px; bottom: 0; right: 0;
|
|
border: solid red 1px;
|
|
overflow: none;
|
|
}
|
|
|
|
.form-row {
|
|
}
|
|
|
|
div.toolFormInCanvas div.toolFormBody {
|
|
padding: 0;
|
|
}
|
|
.form-row-clear {
|
|
clear: both;
|
|
}
|
|
|
|
div.rule {
|
|
height: 0;
|
|
border: none;
|
|
border-bottom: dotted black 1px;
|
|
margin: 0 5px;
|
|
}
|
|
|
|
.callout {
|
|
position: absolute;
|
|
z-index: 10000;
|
|
}
|
|
|
|
.pjaForm {
|
|
margin-bottom:10px;
|
|
}
|
|
|
|
.pjaForm .toolFormBody{
|
|
padding:10px;
|
|
}
|
|
|
|
.pjaForm .toolParamHelp{
|
|
padding:5px;
|
|
}
|
|
|
|
.panel-header-button-group {
|
|
margin-right: 5px;
|
|
padding-right: 5px;
|
|
border-right: solid gray 1px;
|
|
}
|
|
|
|
</style>
|
|
</%def>
|
|
|
|
## Render a tool in the tool panel
|
|
<%def name="render_tool( tool, section )">
|
|
%if not tool.hidden:
|
|
%if tool.is_workflow_compatible:
|
|
%if section:
|
|
<div class="toolTitle">
|
|
%else:
|
|
<div class="toolTitleNoSection">
|
|
%endif
|
|
%if "[[" in tool.description and "]]" in tool.description:
|
|
${tool.description.replace( '[[', '<a id="link-${tool.id}" href="javascript:add_node_for_tool( ${tool.id} )">' % tool.id ).replace( "]]", "</a>" )}
|
|
%elif tool.name:
|
|
<a id="link-${tool.id}" href="#" onclick="add_node_for_tool( '${tool.id}', '${tool.name}' )">${tool.name}</a> ${tool.description}
|
|
%else:
|
|
<a id="link-${tool.id}" href="#" onclick="add_node_for_tool( '${tool.id}', '${tool.name}' )">${tool.description}</a>
|
|
%endif
|
|
</div>
|
|
%else:
|
|
%if section:
|
|
<div class="toolTitleDisabled">
|
|
%else:
|
|
<div class="toolTitleNoSectionDisabled">
|
|
%endif
|
|
%if "[[" in tool.description and "]]" in tool.description:
|
|
${tool.description.replace( '[[', '' % tool.id ).replace( "]]", "" )}
|
|
%elif tool.name:
|
|
${tool.name} ${tool.description}
|
|
%else:
|
|
${tool.description}
|
|
%endif
|
|
</div>
|
|
%endif
|
|
%endif
|
|
</%def>
|
|
|
|
## Render a label in the tool panel
|
|
<%def name="render_label( label )">
|
|
<div class="toolPanelLabel" id="title_${label.id}">
|
|
<span>${label.text}</span>
|
|
</div>
|
|
</%def>
|
|
|
|
<%def name="overlay()">
|
|
${parent.overlay( "Loading workflow editor...",
|
|
"<img src='" + h.url_for('/static/images/yui/rel_interstitial_loading.gif') + "'/>" )}
|
|
</%def>
|
|
|
|
<%def name="left_panel()">
|
|
<div class="unified-panel-header" unselectable="on">
|
|
<div class='unified-panel-header-inner'>
|
|
<div style="float: right">
|
|
<a class='panel-header-button popup' id="tools-options-button" href="#">${_('Options')}</a>
|
|
</div>
|
|
${n_('Tools')}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="unified-panel-body" style="overflow: auto;">
|
|
<div class="toolMenu">
|
|
## Tool search.
|
|
<%
|
|
show_tool_search = False
|
|
if trans.user:
|
|
show_tool_search = trans.user.preferences.get( "workflow.show_tool_search", "True" )
|
|
|
|
if show_tool_search == "True":
|
|
display = "block"
|
|
else:
|
|
display = "none"
|
|
%>
|
|
<div id="tool-search" style="padding-bottom: 5px; position: relative; display: ${display}; width: 100%">
|
|
<input type="text" name="query" value="search tools" id="tool-search-query" style="width: 100%; font-style:italic; font-size: inherit" autocomplete="off"/>
|
|
<img src="${h.url_for('/static/images/loading_small_white_bg.gif')}" id="search-spinner" style="display: none; position: absolute; right: 0; top: 5px;"/>
|
|
</div>
|
|
|
|
<div class="toolSectionList">
|
|
%for key, val in app.toolbox.tool_panel.items():
|
|
<div class="toolSectionWrapper">
|
|
%if key.startswith( 'tool' ):
|
|
${render_tool( val, False )}
|
|
%elif key.startswith( 'section' ):
|
|
<% section = val %>
|
|
<div class="toolSectionTitle" id="title_${section.id}">
|
|
<span>${section.name}</span>
|
|
</div>
|
|
<div id="${section.id}" class="toolSectionBody">
|
|
<div class="toolSectionBg">
|
|
%for section_key, section_val in section.elems.items():
|
|
%if section_key.startswith( 'tool' ):
|
|
${render_tool( section_val, True )}
|
|
%elif section_key.startswith( 'label' ):
|
|
${render_label( section_val )}
|
|
%endif
|
|
%endfor
|
|
</div>
|
|
</div>
|
|
%elif key.startswith( 'label' ):
|
|
${render_label( val )}
|
|
%endif
|
|
<div class="toolSectionPad"></div>
|
|
</div>
|
|
%endfor
|
|
</div>
|
|
## Feedback when search returns no results.
|
|
<div id="search-no-results" style="display: none; padding-top: 5px">
|
|
<em><strong>Search did not match any tools.</strong></em>
|
|
</div>
|
|
<div> </div>
|
|
<div class="toolMenuGroupHeader">Workflow control</div>
|
|
<div class="toolSectionTitle" id="title___workflow__input__">
|
|
<span>Inputs</span>
|
|
</div>
|
|
<div id="__workflow__input__" class="toolSectionBody">
|
|
<div class="toolSectionBg">
|
|
<div class="toolTitle">
|
|
<a href="#" onclick="add_node_for_module( 'data_input', 'Input Dataset' )">Input dataset</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</%def>
|
|
|
|
<%def name="center_panel()">
|
|
|
|
<div class="unified-panel-header" unselectable="on">
|
|
<div class="unified-panel-header-inner" style="float: right">
|
|
<a id="workflow-options-button" class="panel-header-button popup" href="#">Options</a>
|
|
</div>
|
|
<div class="unified-panel-header-inner">
|
|
Workflow Canvas | ${h.to_unicode( stored.name ) | h}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="unified-panel-body">
|
|
<div id="canvas-viewport" style="width: 100%; height: 100%; position: absolute; overflow: hidden; background: #EEEEEE; background: white url(${h.url_for('/static/images/light_gray_grid.gif')}) repeat;">
|
|
<div id="canvas-container" style="position: absolute; width: 100%; height: 100%;"></div>
|
|
</div>
|
|
<div id="overview-border" style="position: absolute; width: 150px; height: 150px; right: 20000px; bottom: 0px; border-top: solid gray 1px; border-left: solid grey 1px; padding: 7px 0 0 7px; background: #EEEEEE no-repeat url(${h.url_for('/static/images/resizable.png')}); z-index: 20000; overflow: hidden; max-width: 300px; max-height: 300px; min-width: 50px; min-height: 50px">
|
|
<div style="position: relative; overflow: hidden; width: 100%; height: 100%; border-top: solid gray 1px; border-left: solid grey 1px;">
|
|
<div id="overview" style="position: absolute;">
|
|
<canvas width="0" height="0" style="background: white; width: 100%; height: 100%;" id="overview-canvas"></canvas>
|
|
<div id="overview-viewport" style="position: absolute; width: 0px; height: 0px; border: solid blue 1px; z-index: 10;"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div id='workflow-parameters-box' style="display:none; position: absolute; /*width: 150px; height: 150px;*/ right: 0px; top: 0px; border-bottom: solid gray 1px; border-left: solid grey 1px; padding: 7px; background: #EEEEEE; z-index: 20000; overflow: hidden; max-width: 300px; max-height: 300px; /*min-width: 50px; min-height: 50px*/">
|
|
<div style="margin-bottom:5px;"><b>Workflow Parameters</b></div>
|
|
<div id="workflow-parameters-container">
|
|
</div>
|
|
</div>
|
|
<div id="close-viewport" style="border-left: 1px solid #999; border-top: 1px solid #999; background: #ddd url(${h.url_for('/static/images/overview_arrows.png')}) 12px 0px; position: absolute; right: 0px; bottom: 0px; width: 12px; height: 12px; z-index: 25000;"></div>
|
|
</div>
|
|
|
|
</%def>
|
|
|
|
<%def name="right_panel()">
|
|
<div class="unified-panel-header" unselectable="on">
|
|
<div class="unified-panel-header-inner">
|
|
Details
|
|
</div>
|
|
</div>
|
|
<div class="unified-panel-body" style="overflow: auto;">
|
|
## Div for elements to modify workflow attributes.
|
|
<div id="edit-attributes" class="metadataForm right-content">
|
|
<div class="metadataFormTitle">Edit Workflow Attributes</div>
|
|
<div class="metadataFormBody">
|
|
## Workflow name.
|
|
<div id="workflow-name-area" class="form-row">
|
|
<label>Name:</label>
|
|
<span id="workflow-name" class="tooltip editable-text" original-title="Click to rename workflow">${h.to_unicode( stored.name ) | h}</span>
|
|
</div>
|
|
## Workflow tags.
|
|
<%namespace file="/tagging_common.mako" import="render_individual_tagging_element" />
|
|
<div class="form-row">
|
|
<label>
|
|
Tags:
|
|
</label>
|
|
<div style="float: left; width: 225px; margin-right: 10px; border-style: inset; border-width: 1px; margin-left: 2px">
|
|
<style>
|
|
.tag-area {
|
|
border: none;
|
|
}
|
|
</style>
|
|
${render_individual_tagging_element(user=trans.get_user(), tagged_item=stored, elt_context="edit_attributes.mako", use_toggle_link=False, input_size="20")}
|
|
</div>
|
|
<div class="toolParamHelp">Apply tags to make it easy to search for and find items with the same tag.</div>
|
|
</div>
|
|
## Workflow annotation.
|
|
## Annotation elt.
|
|
<div id="workflow-annotation-area" class="form-row">
|
|
<label>Annotation / Notes:</label>
|
|
<div id="workflow-annotation" class="tooltip editable-text" original-title="Click to edit annotation">
|
|
%if annotation:
|
|
${h.to_unicode( annotation ) | h}
|
|
%else:
|
|
<em>Describe or add notes to workflow</em>
|
|
%endif
|
|
</div>
|
|
<div class="toolParamHelp">Add an annotation or notes to a workflow; annotations are available when a workflow is viewed.</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
## Div where tool details are loaded and modified.
|
|
<div id="right-content" class="right-content"></div>
|
|
|
|
## Workflow output tagging
|
|
<div style="display:none;" id="workflow-output-area" class="metadataForm right-content">
|
|
<div class="metadataFormTitle">Edit Workflow Outputs</div>
|
|
<div class="metadataFormBody"><div class="form-row">
|
|
<div class="toolParamHelp">Tag step outputs to indicate the final dataset(s) to be generated by running this workflow.</div>
|
|
<div id="output-fill-area"></div>
|
|
</div></div>
|
|
</div>
|
|
|
|
</div>
|
|
</%def>
|