Merge remote-tracking branch 'origin/jg_SCI_328_moving_workflows'

Conflicts:
	app/models/experiment.rb

SCI-328 #close
This commit is contained in:
Jure Grabnar 2016-08-24 13:11:10 +02:00
commit 00da7efb44
11 changed files with 537 additions and 42 deletions

View file

@ -162,6 +162,7 @@ function initializeEdit() {
var canEditModuleGroups = _.isEqual($("#update-canvas").data("can-edit-module-groups"), "yes");
var canCreateModules = _.isEqual($("#update-canvas").data("can-create-modules"), "yes");
var canCloneModules = _.isEqual($("#update-canvas").data("can-clone-modules"), "yes");
var canMoveModules = _.isEqual($("#update-canvas").data("can-move-modules"), "yes");
var canDeleteModules = _.isEqual($("#update-canvas").data("can-delete-modules"), "yes");
var canDragModules = _.isEqual($("#update-canvas").data("can-reposition-modules"), "yes");
var canEditConnections = _.isEqual($("#update-canvas").data("can-edit-connections"), "yes");
@ -223,6 +224,13 @@ function initializeEdit() {
GRID_DIST_EDIT_X,
GRID_DIST_EDIT_Y);
}
if (canMoveModules) {
initMoveModules();
$(".move-module").on("click touchstart", moveModuleHandler);
initMoveModuleGroups();
$(".move-module-group").on("click touchstart", moveModuleGroupHandler);
}
if (canDeleteModules) {
bindDeleteModuleAction();
bindDeleteModuleGroupAction();
@ -249,6 +257,7 @@ function destroyEdit() {
// Read permissions from the data attributes of the form
var canCreateModules = _.isEqual($("#update-canvas").data("can-create-modules"), "yes");
var canCloneModules = _.isEqual($("#update-canvas").data("can-clone-modules"), "yes");
var canMoveModules = _.isEqual($("#update-canvas").data("can-move-modules"), "yes");
var canDeleteModules = _.isEqual($("#update-canvas").data("can-delete-modules"), "yes");
instance.cleanupListeners();
@ -276,6 +285,10 @@ function destroyEdit() {
$(".buttons-container a.clone-module").off("click touchstart");
$(".buttons-container a.clone-module-group").off("click touchstart");
}
if (canMoveModules) {
$(".move-module").off("click touchstart");
$(".move-module-group").off("click touchstart");
}
$("#update-canvas .cancel-edit-canvas").off("click");
$(window).off("beforeunload");
@ -1325,6 +1338,38 @@ function updateModuleHtml(module, id, name, gridDistX, gridDistY) {
bindEditModeDropdownHandlers(module);
}
// Add move links if neccesary
if (_.isEqual($("#update-canvas").data("can-move-modules"), "yes")) {
var moveModuleItem = document.createElement("li");
$(moveModuleItem).appendTo(dropdownMenu);
var moveModuleLink = document.createElement("a");
$(moveModuleLink)
.attr("href", "")
.attr("data-module-id", id)
.addClass("move-module")
.html($("#move-link-placeholder").text().trim())
.appendTo(moveModuleItem);
// Add clone click handler for the new module
$(moveModuleLink).on("click touchstart", moveModuleHandler);
// Add buttons for module groups
var moveModuleGroupItem = document.createElement("li");
$(moveModuleGroupItem).appendTo(dropdownMenu);
$(moveModuleGroupItem).hide();
var moveModuleGroupLink = document.createElement("a");
$(moveModuleGroupLink)
.attr("href", "")
.attr("data-module-id", id)
.addClass("move-module-group")
.html($("#move-group-link-placeholder").text().trim())
.appendTo(moveModuleGroupItem);
$(moveModuleGroupLink).on("click touchstart", moveModuleGroupHandler);
}
// Add delete links if neccesary
if (_.isEqual($("#update-canvas").data("can-delete-modules"), "yes")) {
var deleteModuleItem = document.createElement("li");
@ -1823,6 +1868,186 @@ editModuleGroupHandler = function(ev) {
return false;
};
function initMoveModules() {
function handleMoveConfirm(modal) {
var moduleId = modal.attr("data-module-id");
var moduleEl = $("#" + moduleId);
var input = modal.find('.selectpicker');
var moveToExperimentId = input.val();
// Add this information to form
var formMoveInput = $("#update-canvas form input#move");
// Save mapping to input
var moveVal = JSON.parse(formMoveInput.attr("value"));
moveVal[moduleEl.attr("id")] = moveToExperimentId;
formMoveInput.attr("value", JSON.stringify(moveVal));
updateFormWithModulesData(moduleEl, '', GRID_DIST_EDIT_X, GRID_DIST_EDIT_Y);
// Delete module from canvas
deleteModule(moduleEl.attr("id"));
// Hide modal
modal.modal("hide");
}
$("#modal-move-module")
.on("show.bs.modal", function (event) {
var modal = $(this);
var moduleId = modal.attr("data-module-id");
var moduleEl = $("#" + moduleId);
var input = modal.find('.selectpicker');
// Bind on enter button
input.keydown(function(ev) {
if (ev.keyCode == 13) {
// "Submit" modal
handleMoveConfirm(modal);
// In any case, prevent form submission
ev.preventDefault();
ev.stopPropagation();
return false;
}
});
})
.on("shown.bs.modal", function(event) {
// Focus the text element
$(this).find(".selectpicker").focus();
})
.on("hide.bs.modal", function (event) {
// When hiding modal, re-enable events
toggleCanvasEvents(true);
});
// Bind the confirm button on modal
$("#modal-move-module").find("button[data-action='confirm']").on("click", function(event) {
var modal = $(this).closest(".modal");
handleMoveConfirm(modal);
});
}
/**
* Handler when trying to move a specific module.
*/
moveModuleHandler = function(ev) {
var modal = $("#modal-move-module");
var moduleEl = $(this).closest(".module");
// Set modal's module id
modal.attr("data-module-id", moduleEl.attr("id"));
// Disable dragging & zooming events on canvas temporarily
toggleCanvasEvents(false);
// Show modal
modal.modal("show");
ev.preventDefault();
ev.stopPropagation();
return false;
};
/**
* Initialize editing of module groups.
*/
function initMoveModuleGroups() {
function handleMoveModuleGroupConfirm(modal) {
var moduleId = modal.attr("data-module-id");
var moduleEl = $("#" + moduleId);
var input = modal.find('.selectpicker');
var moveToExperimentId = input.val();
// Retrieve all modules in this module group
var components = connectedComponents(graph, moduleId.toString());
var group = _.map(components, function(id) { return $("#" + id); });
var conns = _.filter(graph.edges(), function(conn) {
return _.contains(components, conn[0]) || _.contains(components, conn[1]);
});
updateFormWithModulesData(group, conns.toString(), GRID_DIST_EDIT_X, GRID_DIST_EDIT_Y);
// Add move information to form
var formMoveInput = $("#update-canvas form input#move");
moveModules = [];
_.each(group, function(m) {
moveModules.push(m.attr("id"));
});
// Put the array into input
var moveVal = JSON.parse(formMoveInput.attr("value"));
moveVal[moveModules] = moveToExperimentId;
formMoveInput.attr("value", JSON.stringify(moveVal));
_.each(group, function(m) {
deleteModule(m.attr("id"));
});
// Hide modal
modal.modal("hide");
}
$("#modal-move-module-group")
.on("show.bs.modal", function (event) {
var modal = $(this);
var moduleId = modal.attr("data-module-id");
var moduleEl = $("#" + moduleId);
var input = modal.find('.selectpicker');
// Bind on enter button
input.keydown(function(ev) {
if (ev.keyCode == 13) {
// "Submit" modal
handleMoveConfirm(modal);
// In any case, prevent form submission
ev.preventDefault();
ev.stopPropagation();
return false;
}
});
})
.on("shown.bs.modal", function(event) {
// Focus the text element
$(this).find(".selectpicker").focus();
})
.on("hide.bs.modal", function (event) {
// When hiding modal, re-enable events
toggleCanvasEvents(true);
});
// Bind the confirm button on modal
$("#modal-move-module-group").find("button[data-action='confirm']").on("click", function(event) {
var modal = $(this).closest(".modal");
handleMoveModuleGroupConfirm(modal);
});
}
/**
* Handler when editing a module group.
*/
moveModuleGroupHandler = function(ev) {
var modal = $("#modal-move-module-group");
var moduleEl = $(this).closest(".module");
// Set modal's module id
modal.attr("data-module-id", moduleEl.attr("id"));
// Disable dragging & zooming events on canvas temporarily
toggleCanvasEvents(false);
// Show modal
modal.modal("show");
ev.preventDefault();
ev.stopPropagation();
return false;
};
/**
* Bind the delete module buttons actions.
*/
@ -1897,6 +2122,7 @@ function deleteModule(id, linkConnections) {
tempModuleEl = $("#" + inEdge[0]);
tempModuleEl.find(".edit-module-group").parents("li").hide();
tempModuleEl.find(".clone-module-group").parents("li").hide();
tempModuleEl.find(".move-module-group").parents("li").hide();
tempModuleEl.find(".delete-module-group").parents("li").hide();
}
});
@ -1907,6 +2133,7 @@ function deleteModule(id, linkConnections) {
tempModuleEl = $("#" + outEdge[1]);
tempModuleEl.find(".edit-module-group").parents("li").hide();
tempModuleEl.find(".clone-module-group").parents("li").hide();
tempModuleEl.find(".move-module-group").parents("li").hide();
tempModuleEl.find(".delete-module-group").parents("li").hide();
}
});
@ -1918,10 +2145,28 @@ function deleteModule(id, linkConnections) {
var formAddNamesInput = $('#update-canvas form input#add-names');
var formClonedInput = $('#update-canvas form input#cloned');
var formRemoveInput = $('#update-canvas form input#remove');
var formMoveInput = $('#update-canvas form input#move');
var inputVal, newVal;
var vals, idx;
var addToRemoveList = true;
// If the module was moved, we don't need to do anything with it
inputVal = formMoveInput.attr("value");
if (!_.isUndefined(inputVal) && inputVal !== "") {
moved = [];
$.each(JSON.parse(formMoveInput.val()), function(key, value) {
if (key.match(/.*,.*/))
moved = moved.concat(key.split(','));
else
moved.push(key);
});
if (_.contains(moved, id)) {
addToRemoveList = false;
return;
}
}
// If the module we are deleting was added via JS
// (and hasn't been saved yet), we don't need to "add" it
// neither "remove" it, it simply ceases to exist
@ -2278,6 +2523,7 @@ cloneModuleGroupHandler = function(moduleId, modulesSel, gridDistX, gridDistY) {
//Show module group options
nm.find(".edit-module-group").parents("li").show();
nm.find(".clone-module-group").parents("li").show();
nm.find(".move-module-group").parents("li").show();
nm.find(".delete-module-group").parents("li").show();
clones[m.attr("id")] = nm.attr("id");
@ -2314,12 +2560,34 @@ cloneModuleGroupHandler = function(moduleId, modulesSel, gridDistX, gridDistY) {
function bindEditFormSubmission(gridDistX, gridDistY) {
$('#update-canvas form').submit(function(){
var modules = $(".diagram .module");
updateFormWithModulesData(modules, graph.edges().toString(), gridDistX, gridDistY)
ignoreUnsavedWorkAlert = true;
return true;
});
}
/**
* Update form with given modules position and connections.
* @param modules - Modules, which should be inserted into form
* @param connections- Connections, which should be inserted into form (empty if
* no connections)
* @param gridDistX - The canvas grid distance in X direction.
* @param gridDistY - The canvas grid distance in Y direction.
*/
function updateFormWithModulesData(modules, connections, gridDistX, gridDistY) {
var connectionsDiv = $('#update-canvas form input#connections');
var positionsDiv = $('#update-canvas form input#positions');
var moduleNamesDiv = $('#update-canvas form input#module-groups');
// Connections are easy, just copy graph data
connectionsDiv.attr("value", graph.edges().toString());
if (connections) {
if (connectionsDiv.val())
connectionsDiv.attr("value", connectionsDiv.val() + ',' + connections.toString());
else
connectionsDiv.attr("value", connections.toString());
}
// Positions are a bit more tricky, but still pretty straightforward
var moduleGroupNames = {};
@ -2333,12 +2601,13 @@ function bindEditFormSubmission(gridDistX, gridDistY) {
positionsVal += id + "," + x + "," + y + ";";
moduleGroupNames[id] = module.attr("data-module-group-name");
});
positionsDiv.attr("value", positionsVal);
moduleNamesDiv.attr("value", JSON.stringify(moduleGroupNames));
positionsDiv.attr("value", positionsDiv.val() + positionsVal);
ignoreUnsavedWorkAlert = true;
return true;
});
if (moduleNamesDiv.val())
moduleNamesDiv.attr("value", JSON.stringify($.extend(JSON.parse(moduleNamesDiv.val()),
moduleGroupNames)));
else
moduleNamesDiv.attr("value", JSON.stringify(moduleGroupNames));
}
/**
@ -2855,10 +3124,12 @@ function initJsPlumb(containerSel, containerChildSel, modulesSel, params) {
srcModuleEl.find(".edit-module-group").parents("li").show();
srcModuleEl.find(".clone-module-group").parents("li").show();
srcModuleEl.find(".move-module-group").parents("li").show();
srcModuleEl.find(".delete-module-group").parents("li").show();
targetModuleEl.find(".edit-module-group").parents("li").show();
targetModuleEl.find(".clone-module-group").parents("li").show();
targetModuleEl.find(".move-module-group").parents("li").show();
targetModuleEl.find(".delete-module-group").parents("li").show();
return true;
});
@ -2891,11 +3162,13 @@ function initJsPlumb(containerSel, containerChildSel, modulesSel, params) {
if (graph.degree(c.sourceId) === 0) {
srcModuleEl.find(".edit-module-group").parents("li").hide();
srcModuleEl.find(".clone-module-group").parents("li").hide();
srcModuleEl.find(".move-module-group").parents("li").hide();
srcModuleEl.find(".delete-module-group").parents("li").hide();
}
if (graph.degree(c.targetId) === 0) {
targetModuleEl.find(".edit-module-group").parents("li").hide();
targetModuleEl.find(".clone-module-group").parents("li").hide();
targetModuleEl.find(".move-module-group").parents("li").hide();
targetModuleEl.find(".delete-module-group").parents("li").hide();
}
});

View file

@ -45,7 +45,7 @@ class ApplicationController < ActionController::Base
render json: {}, status: :forbidden
}
end
return
return true
end
def render_404
@ -57,7 +57,7 @@ class ApplicationController < ActionController::Base
render json: {}, status: :not_found
}
end
return
return true
end
private

View file

@ -153,6 +153,36 @@ class CanvasController < ApplicationController
render_403 and return
end
# Make sure move parameter is valid
to_move = {}
if can_move_modules(@experiment) && update_params[:move].present?
begin
to_move = JSON.parse(update_params[:move])
# Okay, JSON parsed!
unless (
to_move.is_a? Hash and
to_move.keys.all? { |k| k.is_a? String } &&
to_move.values.all? { |k| k.is_a? String }
)
error = true
end
rescue
error = true
end
end
# Distinguish between moving modules/module_groups
to_move_groups = {}
to_move.each do |key, value|
if key =~ /.*,.*/
to_move_groups[key.split(',')] = value
to_move.delete(key)
end
end
render_403 and return if error
# Make sure that to_clone is an array of pairs,
# as well as that all IDs exist
to_clone = Hash.new
@ -204,6 +234,8 @@ class CanvasController < ApplicationController
to_archive,
to_add,
to_rename,
to_move,
to_move_groups,
to_clone,
connections,
positions,
@ -249,6 +281,7 @@ class CanvasController < ApplicationController
:add,
"add-names",
:rename,
:move,
:cloned,
:remove,
"module-groups"

View file

@ -402,6 +402,10 @@ module PermissionHelper
is_user_or_higher_of_project(experiment.project)
end
def can_move_modules(experiment)
is_user_or_higher_of_project(experiment.project)
end
def can_archive_modules(experiment)
is_user_or_higher_of_project(experiment.project)
end

View file

@ -102,6 +102,8 @@ class Experiment < ActiveRecord::Base
to_archive,
to_add,
to_rename,
to_move,
to_move_groups,
to_clone,
connections,
positions,
@ -155,6 +157,18 @@ class Experiment < ActiveRecord::Base
# Update connections, positions & module group variables
# with actual IDs retrieved from the new modules creation
updated_to_move = {}
to_move.each do |id, value|
updated_to_move[new_ids.fetch(id, id)] = value
end
updated_to_move_groups = {}
to_move_groups.each do |ids, value|
mapped = []
ids.each do |id|
mapped << new_ids.fetch(id, id)
end
updated_to_move_groups[mapped] = value
end
updated_connections = []
connections.each do |a,b|
updated_connections << [new_ids.fetch(a, a), new_ids.fetch(b, b)]
@ -180,6 +194,12 @@ class Experiment < ActiveRecord::Base
# Finally, update module groups
update_module_groups(updated_module_groups, current_user)
# Finally move any modules to another experiment
move_modules(updated_to_move)
# Everyhing is set, now we can move any module groups
move_module_groups(updated_to_move_groups)
# update Experiment timestamp
touch
end
@ -458,6 +478,75 @@ class Experiment < ActiveRecord::Base
end
end
# Move modules; this method accepts a map where keys
# represent IDs of modules, and values represent experiment
# IDs of new names to which the given modules should be moved.
# If a module with given ID doesn't exist (or experiment ID)
# it's obviously not updated. Any connection on module is destroyed.
def move_modules(to_move)
to_move.each do |id, experiment_id|
my_module = my_modules.find_by_id(id)
experiment = project.experiments.find_by_id(experiment_id)
next unless my_module.present? && experiment.present?
my_module.experiment = experiment
# Calculate new module position
new_pos = my_module.get_new_position
my_module.x = new_pos[:x]
my_module.y = new_pos[:y]
unless my_module.outputs.destroy_all && my_module.inputs.destroy_all
raise ActiveRecord::ActiveRecordError
end
my_module.save!
end
end
# Move module groups; this method accepts a map where keys
# represent IDs of modules which are in module group,
# and values represent experiment
# IDs of new names to which the given module group should be moved.
# If a module with given ID doesn't exist (or experiment ID)
# it's obviously not updated. Position for entire module group is updated
# to bottom left corner.
def move_module_groups(to_move)
to_move.each do |ids, experiment_id|
modules = my_modules.where(id: ids)
groups = Set.new(modules.map(&:my_module_group))
experiment = project.experiments.find_by_id(experiment_id)
groups.each do |group|
next unless group && experiment.present?
# Find the lowest point for current modules(max_y) and the leftmost
# module(min_x)
if experiment.active_modules.empty?
max_y = 0
min_x = 0
else
max_y = experiment.active_modules.maximum(:y) + MyModule::HEIGHT
min_x = experiment.active_modules.minimum(:x)
end
# Set new positions
curr_min_x = modules.min_by(&:x).x
curr_min_y = modules.min_by(&:y).y
modules.each { |m| m.x += -curr_min_x + min_x }
modules.each { |m| m.y += -curr_min_y + max_y }
modules.each do |m|
m.experiment = experiment
m.save!
end
group.experiment = experiment
group.save!
end
end
end
# Update connections for all modules in this project.
# Input is an array of arrays, where first element represents
# source node, and second element represents target node.

View file

@ -35,6 +35,10 @@ class MyModule < ActiveRecord::Base
scope :is_archived, ->(is_archived) { where('archived = ?', is_archived) }
# A module takes this much space in canvas (x, y) in database
WIDTH = 30
HEIGHT = 14
def self.search(user, include_archived, query = nil, page = 1)
exp_ids =
Experiment
@ -317,29 +321,31 @@ class MyModule < ActiveRecord::Base
experiment.project.log(final)
end
# Find an empty position for the restored module. It's
# basically a first empty row with empty space inside x=[0, 32).
def get_new_position
return { x: 0, y: 0 } if experiment.blank?
# Get all modules position that overlap with first column, [0, WIDTH) and
# sort them by y coordinate.
positions = experiment.active_modules.collect { |m| [m.x, m.y] }
.select { |x, _| x >= 0 && x < WIDTH }
.sort_by { |_, y| y }
return { x: 0, y: 0 } if positions.empty? || positions.first[1] >= HEIGHT
# It looks we'll have to find a gap between the modules if it exists (at
# least 2*HEIGHT wide
ind = positions.each_cons(2).map { |f, s| s[1] - f[1] }
.index { |y| y >= 2 * HEIGHT }
return { x: 0, y: positions[ind][1] + HEIGHT } if ind
# We lucked out, no gaps, therefore we need to add it after the last element
{ x: 0, y: positions.last[1] + HEIGHT }
end
private
def create_blank_protocol
protocols << Protocol.new_blank_for_module(self)
end
# Find an empty position for the restored module. It's
# basically a first empty row with x=0.
def get_new_position
if experiment.blank?
return { x: 0, y: 0 }
end
new_y = 0
positions = experiment.active_modules.collect{ |m| [m.x, m.y] }
(0..10000).each do |n|
unless positions.include? [0, n]
new_y = n
break
end
end
return { x: 0, y: new_y }
end
end

View file

@ -3,6 +3,7 @@
data-can-edit-modules="<%= can_edit_modules(@experiment) ? "yes" : "no" %>"
data-can-edit-module-groups="<%= can_edit_module_groups(@experiment) ? "yes" : "no" %>"
data-can-clone-modules="<%= can_clone_modules(@experiment) ? "yes" : "no" %>"
data-can-move-modules="<%= can_move_modules(@experiment) ? "yes" : "no" %>"
data-can-delete-modules="<%= can_archive_modules(@experiment) ? "yes" : "no" %>"
data-can-reposition-modules="<%= can_reposition_modules(@experiment) ? "yes" : "no" %>"
data-can-edit-connections="<%= can_edit_connections(@experiment) ? "yes" : "no" %>"
@ -37,6 +38,7 @@
<%= hidden_field_tag 'add', '' %>
<%= hidden_field_tag 'add-names', '' %>
<%= hidden_field_tag 'rename', '{}' %>
<%= hidden_field_tag 'move', '{}' %>
<%= hidden_field_tag 'cloned', '' %>
<%= hidden_field_tag 'remove', '' %>
<%= hidden_field_tag 'module-groups', '{}' %>
@ -56,6 +58,12 @@
<span style="display: none;" id="clone-group-link-placeholder">
<%=t "experiments.canvas.edit.clone_module_group" %>
</span>
<span style="display: none;" id="move-link-placeholder">
<%=t "experiments.canvas.edit.move_module" %>
</span>
<span style="display: none;" id="move-group-link-placeholder">
<%=t "experiments.canvas.edit.move_module_group" %>
</span>
<span style="display: none;" id="delete-link-placeholder">
<%=t "experiments.canvas.edit.delete_module" %>
</span>
@ -82,6 +90,10 @@
<% if can_edit_module_groups(@experiment) %>
<%= render partial: "canvas/edit/modal/edit_module_group", locals: {experiment: @experiment } %>
<% end %>
<% if can_move_modules(@experiment) %>
<%= render partial: "canvas/edit/modal/move_module", locals: {experiment: @experiment } %>
<%= render partial: "canvas/edit/modal/move_module_group", locals: {experiment: @experiment } %>
<% end %>
<% if can_archive_modules(@experiment) %>
<%= render partial: "canvas/edit/modal/delete_module", locals: {experiment: @experiment} %>
<%= render partial: "canvas/edit/modal/delete_module_group", locals: {experiment: @experiment} %>

View file

@ -39,6 +39,14 @@
<a class ="clone-module-group" href="" data-module-id="<%= my_module.id %>"><%=t "experiments.canvas.edit.clone_module_group" %></a>
</li>
<% end %>
<% if can_move_modules(my_module.experiment) %>
<li>
<a class="move-module" href="" data-module-id="<%= my_module.id %>"><%=t "experiments.canvas.edit.move_module" %></a>
</li>
<li <%= 'style=display:none;' if my_module.my_module_group.blank? %>>
<a class="move-module-group" href="" data-module-id="<%= my_module.id %>"><%=t "experiments.canvas.edit.move_module_group" %></a>
</li>
<% end %>
<% if can_archive_module(my_module) %>
<li>
<a class="delete-module" href="" data-module-id="<%= my_module.id %>"><%=t "experiments.canvas.edit.delete_module" %></a>

View file

@ -0,0 +1,30 @@
<div class="modal fade" id="modal-move-module" tabindex="-1" role="dialog" aria-labelledby="modal-move-module-label">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="modal-move-module-label"><%=t "experiments.canvas.edit.modal_move_module.title" %></h4>
</div>
<div class="modal-body">
<% if @experiment.project.experiments.is_archived(false).count > 1 %>
<%= bootstrap_form_tag do |f| %>
<%= f.select :experiment_id, @experiment.project.experiments.is_archived(false)
.select { |e| e != @experiment }
.collect { |e| [ e.name, e.id ] }, {},
{class: "form-control selectpicker", "data-role" => "clear"} %>
<% end %>
<% else %>
<div>
<em>
<%= t("experiments.canvas.edit.modal_move_module.no_experiments") %>
</em>
</div>
<% end %>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal"><%=t "general.cancel" %></button>
<button type="button" class="btn btn-primary" data-action="confirm"><%=t "experiments.canvas.edit.modal_move_module.confirm" %></button>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,30 @@
<div class="modal fade" id="modal-move-module-group" tabindex="-1" role="dialog" aria-labelledby="modal-move-module-group-label">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="modal-move-module-group-label"><%=t "experiments.canvas.edit.modal_move_module_group.title" %></h4>
</div>
<div class="modal-body">
<% if @experiment.project.experiments.is_archived(false).count > 1 %>
<%= bootstrap_form_tag do |f| %>
<%= f.select :experiment_id, @experiment.project.experiments.is_archived(false)
.select { |e| e != @experiment }
.collect { |e| [ e.name, e.id ] }, {},
{class: "form-control selectpicker", "data-role" => "clear"} %>
<% end %>
<% else %>
<div>
<em>
<%= t("experiments.canvas.edit.modal_move_module.no_experiments") %>
</em>
</div>
<% end %>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal"><%=t "general.cancel" %></button>
<button type="button" class="btn btn-primary" data-action="confirm"><%=t "experiments.canvas.edit.modal_move_module_group.confirm" %></button>
</div>
</div>
</div>
</div>

View file

@ -660,6 +660,8 @@ en:
edit_module_group: "Rename workflow"
clone_module: "Clone task"
clone_module_group: "Clone workflow"
move_module: "Move task to another experiment"
move_module_group: "Move workflow to another experiment"
delete_module: "Archive task"
delete_module_group: "Archive workflow"
modal_new_module:
@ -678,6 +680,14 @@ en:
title: "Rename workflow"
name: "Workflow name"
confirm: "Rename workflow"
modal_move_module:
title: "Move task to experiment"
confirm: "Move task"
no_experiments: "There are no other experiments."
modal_move_module_group:
title: "Move workflow to experiment"
confirm: "Move workflow"
no_experiments: "There are no other experiments."
modal_delete_module:
title: "Archive task"
confirm: "Archive task"