mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-10-06 03:46:39 +08:00
Merge remote-tracking branch 'origin/jg_SCI_328_moving_workflows'
Conflicts: app/models/experiment.rb SCI-328 #close
This commit is contained in:
commit
00da7efb44
11 changed files with 537 additions and 42 deletions
|
@ -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,33 +2560,56 @@ cloneModuleGroupHandler = function(moduleId, modulesSel, gridDistX, gridDistY) {
|
|||
function bindEditFormSubmission(gridDistX, gridDistY) {
|
||||
$('#update-canvas form').submit(function(){
|
||||
var modules = $(".diagram .module");
|
||||
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());
|
||||
|
||||
// Positions are a bit more tricky, but still pretty straightforward
|
||||
var moduleGroupNames = {};
|
||||
var positionsVal = "";
|
||||
var module, id, x, y;
|
||||
_.each(modules, function(m) {
|
||||
module = $(m);
|
||||
id = module.attr("id");
|
||||
x = elLeft(module) / gridDistX;
|
||||
y = elTop(module) / gridDistY;
|
||||
positionsVal += id + "," + x + "," + y + ";";
|
||||
moduleGroupNames[id] = module.attr("data-module-group-name");
|
||||
});
|
||||
positionsDiv.attr("value", positionsVal);
|
||||
moduleNamesDiv.attr("value", JSON.stringify(moduleGroupNames));
|
||||
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
|
||||
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 = {};
|
||||
var positionsVal = "";
|
||||
var module, id, x, y;
|
||||
_.each(modules, function(m) {
|
||||
module = $(m);
|
||||
id = module.attr("id");
|
||||
x = elLeft(module) / gridDistX;
|
||||
y = elTop(module) / gridDistY;
|
||||
positionsVal += id + "," + x + "," + y + ";";
|
||||
moduleGroupNames[id] = module.attr("data-module-group-name");
|
||||
});
|
||||
positionsDiv.attr("value", positionsDiv.val() + positionsVal);
|
||||
|
||||
if (moduleNamesDiv.val())
|
||||
moduleNamesDiv.attr("value", JSON.stringify($.extend(JSON.parse(moduleNamesDiv.val()),
|
||||
moduleGroupNames)));
|
||||
else
|
||||
moduleNamesDiv.attr("value", JSON.stringify(moduleGroupNames));
|
||||
}
|
||||
|
||||
/**
|
||||
* Position the modules onto the canvas.
|
||||
* @param modulesSel - The jQuery selector text of module elements.
|
||||
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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} %>
|
||||
|
|
|
@ -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>
|
||||
|
|
30
app/views/canvas/edit/modal/_move_module.html.erb
Normal file
30
app/views/canvas/edit/modal/_move_module.html.erb
Normal 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">×</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>
|
30
app/views/canvas/edit/modal/_move_module_group.html.erb
Normal file
30
app/views/canvas/edit/modal/_move_module_group.html.erb
Normal 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">×</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>
|
|
@ -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"
|
||||
|
|
Loading…
Add table
Reference in a new issue