mirror of https://github.com/apache/nifi.git
[NIFI-96] add align horizontal and align vertical capability to components on the canvas. This closes #1354
This commit is contained in:
parent
e65aad8fe6
commit
5ea17d30c5
|
@ -97,6 +97,11 @@ i[class^="icon-"]:before, i[class*=" icon-"]:before {
|
|||
margin: -2px;
|
||||
}
|
||||
|
||||
/*shift rotated font awesome icons*/
|
||||
.fa-rotate-90 {
|
||||
left: -2px !important;
|
||||
}
|
||||
|
||||
body {
|
||||
display: block;
|
||||
font-family: Roboto, sans-serif;
|
||||
|
|
|
@ -75,6 +75,13 @@ nf.Actions = (function () {
|
|||
});
|
||||
};
|
||||
|
||||
// determine if the source of this connection is part of the selection
|
||||
var isSourceSelected = function (connection, selection) {
|
||||
return selection.filter(function (d) {
|
||||
return nf.CanvasUtils.getConnectionSourceComponentId(connection) === d.id;
|
||||
}).size() > 0;
|
||||
};
|
||||
|
||||
return {
|
||||
/**
|
||||
* Initializes the actions.
|
||||
|
@ -867,7 +874,7 @@ nf.Actions = (function () {
|
|||
|
||||
// refresh the birdseye
|
||||
nf.Birdseye.refresh();
|
||||
|
||||
|
||||
// inform Angular app values have changed
|
||||
nf.ng.Bridge.digest();
|
||||
}).fail(nf.Common.handleAjaxError);
|
||||
|
@ -1094,6 +1101,145 @@ nf.Actions = (function () {
|
|||
nf.ComponentState.showState(processor, nf.CanvasUtils.isConfigurable(selection));
|
||||
},
|
||||
|
||||
/**
|
||||
* Aligns the components in the specified selection vertically along the center of the components.
|
||||
*
|
||||
* @param {array} selection The selection
|
||||
*/
|
||||
alignVertical: function (selection) {
|
||||
var updates = d3.map();
|
||||
// ensure every component is writable
|
||||
if (nf.CanvasUtils.canModify(selection) === false) {
|
||||
nf.Dialog.showOkDialog({
|
||||
headerText: 'Component Position',
|
||||
dialogContent: 'Must be authorized to modify every component selected.'
|
||||
});
|
||||
return;
|
||||
}
|
||||
// determine the extent
|
||||
var minX = null, maxX = null;
|
||||
selection.each(function (d) {
|
||||
if (d.type !== "Connection") {
|
||||
if (minX === null || d.position.x < minX) {
|
||||
minX = d.position.x;
|
||||
}
|
||||
var componentMaxX = d.position.x + d.dimensions.width;
|
||||
if (maxX === null || componentMaxX > maxX) {
|
||||
maxX = componentMaxX;
|
||||
}
|
||||
}
|
||||
});
|
||||
var center = (minX + maxX) / 2;
|
||||
|
||||
// align all components left
|
||||
selection.each(function(d) {
|
||||
if (d.type !== "Connection") {
|
||||
var delta = {
|
||||
x: center - (d.position.x + d.dimensions.width / 2),
|
||||
y: 0
|
||||
};
|
||||
// if this component is already centered, no need to updated it
|
||||
if (delta.x !== 0) {
|
||||
// consider any connections
|
||||
var connections = nf.Connection.getComponentConnections(d.id);
|
||||
$.each(connections, function(_, connection) {
|
||||
var connectionSelection = d3.select('#id-' + connection.id);
|
||||
|
||||
if (!updates.has(connection.id) && nf.CanvasUtils.getConnectionSourceComponentId(connection) === nf.CanvasUtils.getConnectionDestinationComponentId(connection)) {
|
||||
// this connection is self looping and hasn't been updated by the delta yet
|
||||
var connectionUpdate = nf.Draggable.updateConnectionPosition(nf.Connection.get(connection.id), delta);
|
||||
if (connectionUpdate !== null) {
|
||||
updates.set(connection.id, connectionUpdate);
|
||||
}
|
||||
} else if (!updates.has(connection.id) && connectionSelection.classed('selected') && nf.CanvasUtils.canModify(connectionSelection)) {
|
||||
// this is a selected connection that hasn't been updated by the delta yet
|
||||
if (nf.CanvasUtils.getConnectionSourceComponentId(connection) === d.id || !isSourceSelected(connection, selection)) {
|
||||
// the connection is either outgoing or incoming when the source of the connection is not part of the selection
|
||||
var connectionUpdate = nf.Draggable.updateConnectionPosition(nf.Connection.get(connection.id), delta);
|
||||
if (connectionUpdate !== null) {
|
||||
updates.set(connection.id, connectionUpdate);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
updates.set(d.id, nf.Draggable.updateComponentPosition(d, delta));
|
||||
}
|
||||
}
|
||||
});
|
||||
nf.Draggable.refreshConnections(updates);
|
||||
},
|
||||
|
||||
/**
|
||||
* Aligns the components in the specified selection horizontally along the center of the components.
|
||||
*
|
||||
* @param {array} selection The selection
|
||||
*/
|
||||
alignHorizontal: function (selection) {
|
||||
var updates = d3.map();
|
||||
// ensure every component is writable
|
||||
if (nf.CanvasUtils.canModify(selection) === false) {
|
||||
nf.Dialog.showOkDialog({
|
||||
headerText: 'Component Position',
|
||||
dialogContent: 'Must be authorized to modify every component selected.'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// determine the extent
|
||||
var minY = null, maxY = null;
|
||||
selection.each(function (d) {
|
||||
if (d.type !== "Connection") {
|
||||
if (minY === null || d.position.y < minY) {
|
||||
minY = d.position.y;
|
||||
}
|
||||
var componentMaxY = d.position.y + d.dimensions.height;
|
||||
if (maxY === null || componentMaxY > maxY) {
|
||||
maxY = componentMaxY;
|
||||
}
|
||||
}
|
||||
});
|
||||
var center = (minY + maxY) / 2;
|
||||
|
||||
// align all components with top most component
|
||||
selection.each(function(d) {
|
||||
if (d.type !== "Connection") {
|
||||
var delta = {
|
||||
x: 0,
|
||||
y: center - (d.position.y + d.dimensions.height / 2)
|
||||
};
|
||||
|
||||
// if this component is already centered, no need to updated it
|
||||
if (delta.y !== 0) {
|
||||
// consider any connections
|
||||
var connections = nf.Connection.getComponentConnections(d.id);
|
||||
$.each(connections, function(_, connection) {
|
||||
var connectionSelection = d3.select('#id-' + connection.id);
|
||||
|
||||
if (!updates.has(connection.id) && nf.CanvasUtils.getConnectionSourceComponentId(connection) === nf.CanvasUtils.getConnectionDestinationComponentId(connection)) {
|
||||
// this connection is self looping and hasn't been updated by the delta yet
|
||||
var connectionUpdate = nf.Draggable.updateConnectionPosition(nf.Connection.get(connection.id), delta);
|
||||
if (connectionUpdate !== null) {
|
||||
updates.set(connection.id, connectionUpdate);
|
||||
}
|
||||
} else if (!updates.has(connection.id) && connectionSelection.classed('selected') && nf.CanvasUtils.canModify(connectionSelection)) {
|
||||
// this is a selected connection that hasn't been updated by the delta yet
|
||||
if (nf.CanvasUtils.getConnectionSourceComponentId(connection) === d.id || !isSourceSelected(connection, selection)) {
|
||||
// the connection is either outgoing or incoming when the source of the connection is not part of the selection
|
||||
var connectionUpdate = nf.Draggable.updateConnectionPosition(nf.Connection.get(connection.id), delta);
|
||||
if (connectionUpdate !== null) {
|
||||
updates.set(connection.id, connectionUpdate);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
updates.set(d.id, nf.Draggable.updateComponentPosition(d, delta));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
nf.Draggable.refreshConnections(updates);
|
||||
},
|
||||
|
||||
/**
|
||||
* Opens the fill color dialog for the component in the specified selection.
|
||||
*
|
||||
|
|
|
@ -545,6 +545,34 @@ nf.CanvasUtils = (function () {
|
|||
tip.style('display', 'none');
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines if the specified selection is alignable (in a single action).
|
||||
*
|
||||
* @param {selection} selection The selection
|
||||
* @returns {boolean}
|
||||
*/
|
||||
canAlign: function(selection) {
|
||||
var canAlign = true;
|
||||
|
||||
// determine if the current selection is entirely connections
|
||||
var selectedConnections = selection.filter(function(d) {
|
||||
var connection = d3.select(this);
|
||||
return nf.CanvasUtils.isConnection(connection);
|
||||
});
|
||||
|
||||
// require multiple selections besides connections
|
||||
if (selection.size() - selectedConnections.size() < 2) {
|
||||
canAlign = false;
|
||||
}
|
||||
|
||||
// require write permissions
|
||||
if (nf.CanvasUtils.canModify(selection) === false) {
|
||||
canAlign = false;
|
||||
}
|
||||
|
||||
return canAlign;
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines if the specified selection is colorable (in a single action).
|
||||
|
|
|
@ -157,9 +157,18 @@ nf.ContextMenu = (function () {
|
|||
return nf.CanvasUtils.isConnection(selection);
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines whether the components in the specified selection are alignable.
|
||||
*
|
||||
* @param {selection} selection The selection
|
||||
*/
|
||||
var canAlign = function (selection) {
|
||||
return nf.CanvasUtils.canAlign(selection);
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines whether the components in the specified selection are colorable.
|
||||
*
|
||||
*
|
||||
* @param {selection} selection The selection
|
||||
*/
|
||||
var isColorable = function (selection) {
|
||||
|
@ -435,7 +444,9 @@ nf.ContextMenu = (function () {
|
|||
{condition: canMoveToParent, menuItem: {clazz: 'fa fa-arrows', text: 'Move to parent group', action: 'moveIntoParent'}},
|
||||
{condition: canListQueue, menuItem: {clazz: 'fa fa-list', text: 'List queue', action: 'listQueue'}},
|
||||
{condition: canEmptyQueue, menuItem: {clazz: 'fa fa-minus-circle', text: 'Empty queue', action: 'emptyQueue'}},
|
||||
{condition: isDeletable, menuItem: {clazz: 'fa fa-trash', text: 'Delete', action: 'delete'}}
|
||||
{condition: isDeletable, menuItem: {clazz: 'fa fa-trash', text: 'Delete', action: 'delete'}},
|
||||
{condition: canAlign, menuItem: {clazz: 'fa fa-align-center', text: 'Align vertical', action: 'alignVertical'}},
|
||||
{condition: canAlign, menuItem: {clazz: 'fa fa-align-center fa-rotate-90', text: 'Align horizontal', action: 'alignHorizontal'}}
|
||||
];
|
||||
|
||||
return {
|
||||
|
|
|
@ -23,7 +23,7 @@ nf.Draggable = (function () {
|
|||
|
||||
/**
|
||||
* Updates the positioning of all selected components.
|
||||
*
|
||||
*
|
||||
* @param {selection} dragSelection The current drag selection
|
||||
*/
|
||||
var updateComponentsPosition = function (dragSelection) {
|
||||
|
@ -40,107 +40,6 @@ nf.Draggable = (function () {
|
|||
if (delta.x === 0 && delta.y === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var updateComponentPosition = function(d) {
|
||||
var newPosition = {
|
||||
'x': d.position.x + delta.x,
|
||||
'y': d.position.y + delta.y
|
||||
};
|
||||
|
||||
// build the entity
|
||||
var entity = {
|
||||
'revision': nf.Client.getRevision(d),
|
||||
'component': {
|
||||
'id': d.id,
|
||||
'position': newPosition
|
||||
}
|
||||
};
|
||||
|
||||
// update the component positioning
|
||||
return $.Deferred(function (deferred) {
|
||||
$.ajax({
|
||||
type: 'PUT',
|
||||
url: d.uri,
|
||||
data: JSON.stringify(entity),
|
||||
dataType: 'json',
|
||||
contentType: 'application/json'
|
||||
}).done(function (response) {
|
||||
// update the component
|
||||
nf[d.type].set(response);
|
||||
|
||||
// resolve with an object so we can refresh when finished
|
||||
deferred.resolve({
|
||||
type: d.type,
|
||||
id: d.id
|
||||
});
|
||||
}).fail(function (xhr, status, error) {
|
||||
if (xhr.status === 400 || xhr.status === 404 || xhr.status === 409) {
|
||||
nf.Dialog.showOkDialog({
|
||||
headerText: 'Component Position',
|
||||
dialogContent: nf.Common.escapeHtml(xhr.responseText)
|
||||
});
|
||||
} else {
|
||||
nf.Common.handleAjaxError(xhr, status, error);
|
||||
}
|
||||
|
||||
deferred.reject();
|
||||
});
|
||||
}).promise();
|
||||
};
|
||||
|
||||
var updateConnectionPosition = function(d) {
|
||||
// only update if necessary
|
||||
if (d.bends.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// calculate the new bend points
|
||||
var newBends = $.map(d.bends, function (bend) {
|
||||
return {
|
||||
x: bend.x + delta.x,
|
||||
y: bend.y + delta.y
|
||||
};
|
||||
});
|
||||
|
||||
var entity = {
|
||||
'revision': nf.Client.getRevision(d),
|
||||
'component': {
|
||||
id: d.id,
|
||||
bends: newBends
|
||||
}
|
||||
};
|
||||
|
||||
// update the component positioning
|
||||
return $.Deferred(function (deferred) {
|
||||
$.ajax({
|
||||
type: 'PUT',
|
||||
url: d.uri,
|
||||
data: JSON.stringify(entity),
|
||||
dataType: 'json',
|
||||
contentType: 'application/json'
|
||||
}).done(function (response) {
|
||||
// update the component
|
||||
nf.Connection.set(response);
|
||||
|
||||
// resolve with an object so we can refresh when finished
|
||||
deferred.resolve({
|
||||
type: d.type,
|
||||
id: d.id
|
||||
});
|
||||
}).fail(function (xhr, status, error) {
|
||||
if (xhr.status === 400 || xhr.status === 404 || xhr.status === 409) {
|
||||
nf.Dialog.showOkDialog({
|
||||
headerText: 'Component Position',
|
||||
dialogContent: nf.Common.escapeHtml(xhr.responseText)
|
||||
});
|
||||
} else {
|
||||
nf.Common.handleAjaxError(xhr, status, error);
|
||||
}
|
||||
|
||||
deferred.reject();
|
||||
});
|
||||
}).promise();
|
||||
};
|
||||
|
||||
var selectedConnections = d3.selectAll('g.connection.selected');
|
||||
var selectedComponents = d3.selectAll('g.component.selected');
|
||||
|
@ -156,55 +55,30 @@ nf.Draggable = (function () {
|
|||
|
||||
// go through each selected connection
|
||||
selectedConnections.each(function (d) {
|
||||
var connectionUpdate = updateConnectionPosition(d);
|
||||
var connectionUpdate = nf.Draggable.updateConnectionPosition(d, delta);
|
||||
if (connectionUpdate !== null) {
|
||||
updates.set(d.id, connectionUpdate);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// go through each selected component
|
||||
selectedComponents.each(function (d) {
|
||||
// consider any self looping connections
|
||||
var connections = nf.Connection.getComponentConnections(d.id);
|
||||
$.each(connections, function(_, connection) {
|
||||
if (!updates.has(connection.id) && nf.CanvasUtils.getConnectionSourceComponentId(connection) === nf.CanvasUtils.getConnectionDestinationComponentId(connection)) {
|
||||
var connectionUpdate = updateConnectionPosition(nf.Connection.get(connection.id));
|
||||
var connectionUpdate = nf.Draggable.updateConnectionPosition(nf.Connection.get(connection.id), delta);
|
||||
if (connectionUpdate !== null) {
|
||||
updates.set(connection.id, connectionUpdate);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// consider the component itself
|
||||
updates.set(d.id, updateComponentPosition(d));
|
||||
updates.set(d.id, nf.Draggable.updateComponentPosition(d, delta));
|
||||
});
|
||||
|
||||
// wait for all updates to complete
|
||||
$.when.apply(window, updates.values()).done(function () {
|
||||
var dragged = $.makeArray(arguments);
|
||||
var connections = d3.set();
|
||||
|
||||
// refresh this component
|
||||
$.each(dragged, function (_, component) {
|
||||
// check if the component in question is a connection
|
||||
if (component.type === 'Connection') {
|
||||
connections.add(component.id);
|
||||
} else {
|
||||
// get connections that need to be refreshed because its attached to this component
|
||||
var componentConnections = nf.Connection.getComponentConnections(component.id);
|
||||
$.each(componentConnections, function (_, connection) {
|
||||
connections.add(connection.id);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// refresh the connections
|
||||
connections.forEach(function (connectionId) {
|
||||
nf.Connection.refresh(connectionId);
|
||||
});
|
||||
}).always(function(){
|
||||
nf.Birdseye.refresh();
|
||||
});
|
||||
nf.Draggable.refreshConnections(updates);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -246,7 +120,7 @@ nf.Draggable = (function () {
|
|||
|
||||
// lazily create the drag selection box
|
||||
if (dragSelection.empty()) {
|
||||
// get the current selection
|
||||
// get the current selection
|
||||
var selection = d3.selectAll('g.component.selected');
|
||||
|
||||
// determine the appropriate bounding box
|
||||
|
@ -327,10 +201,159 @@ nf.Draggable = (function () {
|
|||
dragSelection.remove();
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Update the component's position
|
||||
*
|
||||
* @param d The component
|
||||
* @param delta The change in position
|
||||
* @returns {*}
|
||||
*/
|
||||
updateComponentPosition: function(d, delta) {
|
||||
var newPosition = {
|
||||
'x': d.position.x + delta.x,
|
||||
'y': d.position.y + delta.y
|
||||
};
|
||||
|
||||
// build the entity
|
||||
var entity = {
|
||||
'revision': nf.Client.getRevision(d),
|
||||
'component': {
|
||||
'id': d.id,
|
||||
'position': newPosition
|
||||
}
|
||||
};
|
||||
|
||||
// update the component positioning
|
||||
return $.Deferred(function (deferred) {
|
||||
$.ajax({
|
||||
type: 'PUT',
|
||||
url: d.uri,
|
||||
data: JSON.stringify(entity),
|
||||
dataType: 'json',
|
||||
contentType: 'application/json'
|
||||
}).done(function (response) {
|
||||
// update the component
|
||||
nf[d.type].set(response);
|
||||
|
||||
// resolve with an object so we can refresh when finished
|
||||
deferred.resolve({
|
||||
type: d.type,
|
||||
id: d.id
|
||||
});
|
||||
}).fail(function (xhr, status, error) {
|
||||
if (xhr.status === 400 || xhr.status === 404 || xhr.status === 409) {
|
||||
nf.Dialog.showOkDialog({
|
||||
headerText: 'Component Position',
|
||||
dialogContent: nf.Common.escapeHtml(xhr.responseText)
|
||||
});
|
||||
} else {
|
||||
nf.Common.handleAjaxError(xhr, status, error);
|
||||
}
|
||||
|
||||
deferred.reject();
|
||||
});
|
||||
}).promise();
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the connection's position
|
||||
*
|
||||
* @param d The connection
|
||||
* @param delta The change in position
|
||||
* @returns {*}
|
||||
*/
|
||||
updateConnectionPosition: function(d, delta) {
|
||||
// only update if necessary
|
||||
if (d.bends.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// calculate the new bend points
|
||||
var newBends = $.map(d.bends, function (bend) {
|
||||
return {
|
||||
x: bend.x + delta.x,
|
||||
y: bend.y + delta.y
|
||||
};
|
||||
});
|
||||
|
||||
var entity = {
|
||||
'revision': nf.Client.getRevision(d),
|
||||
'component': {
|
||||
id: d.id,
|
||||
bends: newBends
|
||||
}
|
||||
};
|
||||
|
||||
// update the component positioning
|
||||
return $.Deferred(function (deferred) {
|
||||
$.ajax({
|
||||
type: 'PUT',
|
||||
url: d.uri,
|
||||
data: JSON.stringify(entity),
|
||||
dataType: 'json',
|
||||
contentType: 'application/json'
|
||||
}).done(function (response) {
|
||||
// update the component
|
||||
nf.Connection.set(response);
|
||||
|
||||
// resolve with an object so we can refresh when finished
|
||||
deferred.resolve({
|
||||
type: d.type,
|
||||
id: d.id
|
||||
});
|
||||
}).fail(function (xhr, status, error) {
|
||||
if (xhr.status === 400 || xhr.status === 404 || xhr.status === 409) {
|
||||
nf.Dialog.showOkDialog({
|
||||
headerText: 'Component Position',
|
||||
dialogContent: nf.Common.escapeHtml(xhr.responseText)
|
||||
});
|
||||
} else {
|
||||
nf.Common.handleAjaxError(xhr, status, error);
|
||||
}
|
||||
|
||||
deferred.reject();
|
||||
});
|
||||
}).promise();
|
||||
},
|
||||
|
||||
/**
|
||||
* Refresh the connections after dragging a component
|
||||
*
|
||||
* @param updates
|
||||
*/
|
||||
refreshConnections: function(updates) {
|
||||
// wait for all updates to complete
|
||||
$.when.apply(window, updates.values()).done(function () {
|
||||
var dragged = $.makeArray(arguments);
|
||||
var connections = d3.set();
|
||||
|
||||
// refresh this component
|
||||
$.each(dragged, function (_, component) {
|
||||
// check if the component in question is a connection
|
||||
if (component.type === 'Connection') {
|
||||
connections.add(component.id);
|
||||
} else {
|
||||
// get connections that need to be refreshed because its attached to this component
|
||||
var componentConnections = nf.Connection.getComponentConnections(component.id);
|
||||
$.each(componentConnections, function (_, connection) {
|
||||
connections.add(connection.id);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// refresh the connections
|
||||
connections.forEach(function (connectionId) {
|
||||
nf.Connection.refresh(connectionId);
|
||||
});
|
||||
}).always(function(){
|
||||
nf.Birdseye.refresh();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Activates the drag behavior for the components in the specified selection.
|
||||
*
|
||||
*
|
||||
* @param {selection} components
|
||||
*/
|
||||
activate: function (components) {
|
||||
|
@ -339,7 +362,7 @@ nf.Draggable = (function () {
|
|||
|
||||
/**
|
||||
* Deactivates the drag behavior for the components in the specified selection.
|
||||
*
|
||||
*
|
||||
* @param {selection} components
|
||||
*/
|
||||
deactivate: function (components) {
|
||||
|
|
Loading…
Reference in New Issue