Merge pull request #5306 from mabashian/4796-workflow-resize

Workflow editor/details graph responsiveness
This commit is contained in:
Michael Abashian
2017-02-10 20:24:54 -05:00
committed by GitHub
5 changed files with 677 additions and 559 deletions

View File

@@ -46,7 +46,7 @@ angular.module('ModalDialog', ['Utilities', 'ParseHelper'])
return function(params) {
var scope = params.scope,
let scope = params.scope,
buttonSet = params.buttons,
width = params.width || 500,
height = params.height || 600,

View File

@@ -4,8 +4,8 @@
* All Rights Reserved
*************************************************/
export default [ '$state','moment',
function($state, moment) {
export default [ '$state','moment', '$timeout', '$window',
function($state, moment, $timeout, $window) {
return {
scope: {
@@ -21,38 +21,89 @@ export default [ '$state','moment',
link: function(scope, element) {
let margin = {top: 20, right: 20, bottom: 20, left: 20},
width = 950,
height = 550,
i = 0,
nodeW = 120,
nodeH = 60,
rootW = 60,
rootH = 40;
rootH = 40,
startNodeOffsetY = scope.mode === 'details' ? 17 : 10,
verticalSpaceBetweenNodes = 20,
windowHeight,
windowWidth,
tree,
line,
zoomObj,
baseSvg,
svgGroup;
let tree = d3.layout.tree()
.size([height, width]);
scope.dimensionsSet = false;
let line = d3.svg.line()
$timeout(function(){
let dimensions = calcAvailableScreenSpace();
windowHeight = dimensions.height;
windowWidth = dimensions.width;
$('.WorkflowMaker-chart').css("height", windowHeight);
$('.WorkflowMaker-chart').css("width", windowWidth);
scope.dimensionsSet = true;
init();
});
function init() {
tree = d3.layout.tree()
.nodeSize([nodeH + verticalSpaceBetweenNodes,nodeW])
.separation(function(a, b) {
// This should tighten up some of the other nodes so there's not so much wasted space
return a.parent === b.parent ? 1 : 1.25;
});
line = d3.svg.line()
.x(function(d){return d.x;})
.y(function(d){return d.y;});
let zoomObj = d3.behavior.zoom().scaleExtent([0.5, 2]);
zoomObj = d3.behavior.zoom().scaleExtent([0.5, 2]);
let baseSvg = d3.select(element[0]).append("svg")
.attr("width", width - margin.right - margin.left)
.attr("height", height - margin.top - margin.bottom)
baseSvg = d3.select(element[0]).append("svg")
.attr("class", "WorkflowChart-svg")
.call(zoomObj
.on("zoom", naturalZoom)
);
let svgGroup = baseSvg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svgGroup = baseSvg.append("g")
.attr("transform", "translate(" + margin.left + "," + (windowHeight/2 - rootH/2 - startNodeOffsetY) + ")");
}
function calcAvailableScreenSpace() {
let dimensions = {};
if(scope.mode !== 'details') {
// This is the workflow editor
dimensions.height = $('.WorkflowMaker-contentLeft').outerHeight() - $('.WorkflowLegend-maker').outerHeight();
dimensions.width = $('#workflow-modal-dialog').width() - $('.WorkflowMaker-contentRight').outerWidth();
}
else {
// This is the workflow details view
let panel = $('.WorkflowResults-rightSide').children('.Panel')[0];
let panelWidth = $(panel).width();
let panelHeight = $(panel).height();
let headerHeight = $('.StandardOut-panelHeader').outerHeight();
let legendHeight = $('.WorkflowLegend-details').outerHeight();
let proposedHeight = panelHeight - headerHeight - legendHeight - 40;
dimensions.height = proposedHeight > 200 ? proposedHeight : 200;
dimensions.width = panelWidth;
}
return dimensions;
}
function lineData(d){
let sourceX = d.source.isStartNode ? d.source.y + rootW : d.source.y + nodeW;
let sourceY = d.source.isStartNode ? d.source.x + 10 + rootH / 2 : d.source.x + nodeH / 2;
let sourceY = d.source.isStartNode ? d.source.x + startNodeOffsetY + rootH / 2 : d.source.x + nodeH / 2;
let targetX = d.target.y;
let targetY = d.target.x + nodeH / 2;
@@ -108,7 +159,7 @@ export default [ '$state','moment',
let scale = d3.event.scale,
translation = d3.event.translate;
translation = [translation[0] + (margin.left*scale), translation[1] + (margin.top*scale)];
translation = [translation[0] + (margin.left*scale), translation[1] + ((windowHeight/2 - rootH/2 - startNodeOffsetY)*scale)];
svgGroup.attr("transform", "translate(" + translation + ")scale(" + scale + ")");
@@ -122,12 +173,12 @@ export default [ '$state','moment',
let scale = zoom / 100,
translation = zoomObj.translate(),
origZoom = zoomObj.scale(),
unscaledOffsetX = (translation[0] + ((width*origZoom) - width)/2)/origZoom,
unscaledOffsetY = (translation[1] + ((height*origZoom) - height)/2)/origZoom,
translateX = unscaledOffsetX*scale - ((scale*width)-width)/2,
translateY = unscaledOffsetY*scale - ((scale*height)-height)/2;
unscaledOffsetX = (translation[0] + ((windowWidth*origZoom) - windowWidth)/2)/origZoom,
unscaledOffsetY = (translation[1] + ((windowHeight*origZoom) - windowHeight)/2)/origZoom,
translateX = unscaledOffsetX*scale - ((scale*windowWidth)-windowWidth)/2,
translateY = unscaledOffsetY*scale - ((scale*windowHeight)-windowHeight)/2;
svgGroup.attr("transform", "translate(" + [translateX + (margin.left*scale), translateY + (margin.top*scale)] + ")scale(" + scale + ")");
svgGroup.attr("transform", "translate(" + [translateX + (margin.left*scale), translateY + ((windowHeight/2 - rootH/2 - startNodeOffsetY)*scale)] + ")scale(" + scale + ")");
zoomObj.scale(scale);
zoomObj.translate([translateX, translateY]);
}
@@ -145,18 +196,19 @@ export default [ '$state','moment',
translateX = translateCoords[0];
translateY = direction === 'up' ? translateCoords[1] - distance : translateCoords[1] + distance;
}
svgGroup.attr("transform", "translate(" + translateX + "," + translateY + ")scale(" + scale + ")");
svgGroup.attr("transform", "translate(" + translateX + "," + (translateY + ((windowHeight/2 - rootH/2 - startNodeOffsetY)*scale)) + ")scale(" + scale + ")");
zoomObj.translate([translateX, translateY]);
}
function resetZoomAndPan() {
svgGroup.attr("transform", "translate(" + margin.left + "," + margin.top + ")scale(" + 1 + ")");
svgGroup.attr("transform", "translate(" + margin.left + "," + (windowHeight/2 - rootH/2 - startNodeOffsetY) + ")scale(" + 1 + ")");
// Update the zoomObj
zoomObj.scale(1);
zoomObj.translate([0,0]);
}
function update() {
if(scope.dimensionsSet) {
// Declare the nodes
let nodes = tree.nodes(scope.treeData),
links = tree.links(nodes);
@@ -181,7 +233,7 @@ export default [ '$state','moment',
thisNode.append("rect")
.attr("width", rootW)
.attr("height", rootH)
.attr("y", 10)
.attr("y", startNodeOffsetY)
.attr("rx", 5)
.attr("ry", 5)
.attr("fill", "#337ab7")
@@ -191,6 +243,7 @@ export default [ '$state','moment',
thisNode.append("rect")
.attr("width", rootW)
.attr("height", rootH)
//.attr("y", (windowHeight-margin.top-margin.bottom)/2 - rootH)
.attr("y", 10)
.attr("rx", 5)
.attr("ry", 5)
@@ -199,6 +252,7 @@ export default [ '$state','moment',
.call(add_node);
thisNode.append("text")
.attr("x", 13)
//.attr("y", (windowHeight-margin.top-margin.bottom)/2 - rootH + rootH/2)
.attr("y", 30)
.attr("dy", ".35em")
.attr("class", "WorkflowChart-startText")
@@ -491,7 +545,7 @@ export default [ '$state','moment',
return (d.source.isStartNode) ? (d.target.y + d.source.y + rootW) / 2 : (d.target.y + d.source.y + nodeW) / 2;
})
.attr("cy", function(d) {
return (d.source.isStartNode) ? ((d.target.x + 10 + rootH/2) + (d.source.x + nodeH/2)) / 2 : (d.target.x + d.source.x + nodeH) / 2;
return (d.source.isStartNode) ? ((d.target.x + startNodeOffsetY + rootH/2) + (d.source.x + nodeH/2)) / 2 : (d.target.x + d.source.x + nodeH) / 2;
})
.attr("r", 10)
.attr("class", "addCircle linkCircle")
@@ -516,7 +570,7 @@ export default [ '$state','moment',
.attr("transform", function(d) {
let translate;
if(d.source.isStartNode) {
translate = "translate(" + (d.target.y + d.source.y + rootW) / 2 + "," + ((d.target.x + 10 + rootH/2) + (d.source.x + nodeH/2)) / 2 + ")";
translate = "translate(" + (d.target.y + d.source.y + rootW) / 2 + "," + ((d.target.x + startNodeOffsetY + rootH/2) + (d.source.x + nodeH/2)) / 2 + ")";
}
else {
translate = "translate(" + (d.target.y + d.source.y + nodeW) / 2 + "," + (d.target.x + d.source.x + nodeH) / 2 + ")";
@@ -587,7 +641,7 @@ export default [ '$state','moment',
return (d.source.isStartNode) ? (d.target.y + d.source.y + rootW) / 2 : (d.target.y + d.source.y + nodeW) / 2;
})
.attr("cy", function(d) {
return (d.source.isStartNode) ? ((d.target.x + 10 + rootH/2) + (d.source.x + nodeH/2)) / 2 : (d.target.x + d.source.x + nodeH) / 2;
return (d.source.isStartNode) ? ((d.target.x + startNodeOffsetY + rootH/2) + (d.source.x + nodeH/2)) / 2 : (d.target.x + d.source.x + nodeH) / 2;
});
t.selectAll(".linkCross")
@@ -595,7 +649,7 @@ export default [ '$state','moment',
.attr("transform", function(d) {
let translate;
if(d.source.isStartNode) {
translate = "translate(" + (d.target.y + d.source.y + rootW) / 2 + "," + ((d.target.x + 10 + rootH/2) + (d.source.x + nodeH/2)) / 2 + ")";
translate = "translate(" + (d.target.y + d.source.y + rootW) / 2 + "," + ((d.target.x + startNodeOffsetY + rootH/2) + (d.source.x + nodeH/2)) / 2 + ")";
}
else {
translate = "translate(" + (d.target.y + d.source.y + nodeW) / 2 + "," + (d.target.x + d.source.x + nodeH) / 2 + ")";
@@ -710,7 +764,16 @@ export default [ '$state','moment',
t.selectAll(".WorkflowChart-elapsed")
.style("display", function(d) { return (d.job && d.job.elapsed) ? null : "none"; });
}
else if(!scope.watchDimensionsSet){
scope.watchDimensionsSet = scope.$watch('dimensionsSet', function(){
if(scope.dimensionsSet) {
scope.watchDimensionsSet();
scope.watchDimensionsSet = null;
update();
}
});
}
}
function add_node() {
@@ -809,6 +872,29 @@ export default [ '$state','moment',
}
});
function onResize(){
let dimensions = calcAvailableScreenSpace();
$('.WorkflowMaker-chart').css("width", dimensions.width);
$('.WorkflowMaker-chart').css("height", dimensions.height);
}
function cleanUpResize() {
angular.element($window).off('resize', onResize);
}
if(scope.mode === 'details') {
angular.element($window).on('resize', onResize);
scope.$on('$destroy', cleanUpResize);
}
else {
scope.$on('workflowMakerModalResized', function(){
let dimensions = calcAvailableScreenSpace();
$('.WorkflowMaker-chart').css("width", dimensions.width);
$('.WorkflowMaker-chart').css("height", dimensions.height);
});
}
}
};
}];

View File

@@ -888,6 +888,10 @@ export default ['$scope', 'WorkflowService', 'generateList', 'TemplateList', 'Pr
});
};
$scope.$on('WorkflowDialogReady', function(){
$scope.modalOpen = true;
});
init();
}

View File

@@ -6,8 +6,8 @@
import workflowMakerController from './workflow-maker.controller';
export default ['templateUrl', 'CreateDialog', 'Wait', '$state',
function(templateUrl, CreateDialog, Wait, $state) {
export default ['templateUrl', 'CreateDialog', 'Wait', '$state', '$window',
function(templateUrl, CreateDialog, Wait, $state, $window) {
return {
scope: {
workflowJobTemplateObj: '=',
@@ -17,11 +17,17 @@ export default ['templateUrl', 'CreateDialog', 'Wait', '$state',
templateUrl: templateUrl('templates/workflows/workflow-maker/workflow-maker'),
controller: workflowMakerController,
link: function(scope) {
let availableHeight = $(window).height(),
availableWidth = $(window).width(),
minimumWidth = 1300,
minimumHeight = 550;
CreateDialog({
id: 'workflow-modal-dialog',
scope: scope,
width: 1400,
height: 720,
width: availableWidth > minimumWidth ? availableWidth : minimumWidth,
height: availableHeight > minimumHeight ? availableHeight : minimumHeight,
draggable: false,
dialogClass: 'SurveyMaker-dialog',
position: ['center', 20],
@@ -34,6 +40,10 @@ export default ['templateUrl', 'CreateDialog', 'Wait', '$state',
// Let the modal height be variable based on the content
// and set a uniform padding
$('#workflow-modal-dialog').css({ 'padding': '20px' });
$('#workflow-modal-dialog').parent('.ui-dialog').height(availableHeight > minimumHeight ? availableHeight : minimumHeight);
$('#workflow-modal-dialog').parent('.ui-dialog').width(availableWidth > minimumWidth ? availableWidth : minimumWidth);
$('#workflow-modal-dialog').outerHeight(availableHeight > minimumHeight ? availableHeight : minimumHeight);
$('#workflow-modal-dialog').outerWidth(availableWidth > minimumWidth ? availableWidth : minimumWidth);
},
_allowInteraction: function(e) {
@@ -55,6 +65,24 @@ export default ['templateUrl', 'CreateDialog', 'Wait', '$state',
$state.go('^');
};
function onResize(){
availableHeight = $(window).height();
availableWidth = $(window).width();
$('#workflow-modal-dialog').parent('.ui-dialog').height(availableHeight > minimumHeight ? availableHeight : minimumHeight);
$('#workflow-modal-dialog').parent('.ui-dialog').width(availableWidth > minimumWidth ? availableWidth : minimumWidth);
$('#workflow-modal-dialog').outerHeight(availableHeight > minimumHeight ? availableHeight : minimumHeight);
$('#workflow-modal-dialog').outerWidth(availableWidth > minimumWidth ? availableWidth : minimumWidth);
scope.$broadcast('workflowMakerModalResized');
}
function cleanUpResize() {
angular.element($window).off('resize', onResize);
}
angular.element($window).on('resize', onResize);
scope.$on('$destroy', cleanUpResize);
}
};
}

View File

@@ -64,7 +64,7 @@
</div>
</div>
</div>
<workflow-chart tree-data="treeData.data" add-node="startAddNode(parent, betweenTwoNodes)" edit-node="startEditNode(nodeToEdit)" delete-node="startDeleteNode(nodeToDelete)" workflow-zoomed="workflowZoomed(zoom)" can-add-workflow-job-template="canAddWorkflowJobTemplate" mode="edit" class="WorkflowMaker-chart"></workflow-chart>
<workflow-chart ng-if="modalOpen" tree-data="treeData.data" add-node="startAddNode(parent, betweenTwoNodes)" edit-node="startEditNode(nodeToEdit)" delete-node="startDeleteNode(nodeToDelete)" workflow-zoomed="workflowZoomed(zoom)" can-add-workflow-job-template="canAddWorkflowJobTemplate" mode="edit" class="WorkflowMaker-chart"></workflow-chart>
</div>
<div class="WorkflowMaker-contentRight">
<div class="WorkflowMaker-formTitle">{{(workflowMakerFormConfig.nodeMode === 'edit' && nodeBeingEdited) ? ((nodeBeingEdited.unifiedJobTemplate && nodeBeingEdited.unifiedJobTemplate.name) ? nodeBeingEdited.unifiedJobTemplate.name : "EDIT TEMPLATE") : "ADD A TEMPLATE"}}</div>