From 5db478a4a0b0aaf14eb3598dd15105475a0e9342 Mon Sep 17 00:00:00 2001 From: mabashian Date: Mon, 16 Oct 2017 16:10:51 -0400 Subject: [PATCH] Zoom workflow graph to fit screen on initial load --- awx/ui/client/src/templates/workflows.form.js | 4 +- .../workflow-chart.directive.js | 50 ++++++++++++++++--- .../workflow-controls.block.less | 23 +++++---- .../workflow-controls.directive.js | 7 ++- .../workflow-controls.partial.html | 3 ++ .../workflow-maker/workflow-maker.block.less | 10 ++-- .../workflow-maker.controller.js | 12 ++--- .../workflow-maker.directive.js | 2 + .../workflow-maker.partial.html | 2 +- .../workflow-results.controller.js | 4 ++ .../workflow-results.partial.html | 2 +- 11 files changed, 85 insertions(+), 34 deletions(-) diff --git a/awx/ui/client/src/templates/workflows.form.js b/awx/ui/client/src/templates/workflows.form.js index d41cb7fb97..1825b8cf65 100644 --- a/awx/ui/client/src/templates/workflows.form.js +++ b/awx/ui/client/src/templates/workflows.form.js @@ -163,7 +163,7 @@ export default ['NotificationsList', 'i18n', function(NotificationsList, i18n) { }, add_survey: { ngClick: 'addSurvey()', - ngShow: '$state.is(\'templates.addWorkflowJobTemplate\') || $state.is(\'templates.editWorkflowJobTemplate\') || $state.is(\'templates.editWorkflowJobTemplate.workflowMaker\')', + ngShow: '!survey_exists && ($state.is(\'templates.addWorkflowJobTemplate\') || $state.is(\'templates.editWorkflowJobTemplate\') || $state.is(\'templates.editWorkflowJobTemplate.workflowMaker\'))', awFeature: 'surveys', awToolTip: '{{surveyTooltip}}', dataPlacement: 'top', @@ -173,7 +173,7 @@ export default ['NotificationsList', 'i18n', function(NotificationsList, i18n) { edit_survey: { ngClick: 'editSurvey()', awFeature: 'surveys', - ngShow: '$state.is(\'templates.addWorkflowJobTemplate\') || $state.is(\'templates.editWorkflowJobTemplate\') || $state.is(\'templates.editWorkflowJobTemplate.workflowMaker\')', + ngShow: 'survey_exists && ($state.is(\'templates.addWorkflowJobTemplate\') || $state.is(\'templates.editWorkflowJobTemplate\') || $state.is(\'templates.editWorkflowJobTemplate.workflowMaker\'))', label: i18n._('Edit Survey'), class: 'Form-primaryButton', awToolTip: '{{surveyTooltip}}', diff --git a/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js b/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js index 0a945398a2..d0e0c14af0 100644 --- a/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js +++ b/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js @@ -21,7 +21,7 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge restrict: 'E', link: function(scope, element) { - let margin = {top: 20, right: 20, bottom: 20, left: 20}, + let marginLeft = 20, i = 0, nodeW = 180, nodeH = 60, @@ -36,7 +36,8 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge line, zoomObj, baseSvg, - svgGroup; + svgGroup, + graphLoaded; scope.dimensionsSet = false; @@ -75,7 +76,8 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge ); svgGroup = baseSvg.append("g") - .attr("transform", "translate(" + margin.left + "," + (windowHeight/2 - rootH/2 - startNodeOffsetY) + ")"); + .attr("id", "aw-workflow-chart-g") + .attr("transform", "translate(" + marginLeft + "," + (windowHeight/2 - rootH/2 - startNodeOffsetY) + ")"); } function calcAvailableScreenSpace() { @@ -158,7 +160,7 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge let scale = d3.event.scale, translation = d3.event.translate; - translation = [translation[0] + (margin.left*scale), translation[1] + ((windowHeight/2 - rootH/2 - startNodeOffsetY)*scale)]; + translation = [translation[0] + (marginLeft*scale), translation[1] + ((windowHeight/2 - rootH/2 - startNodeOffsetY)*scale)]; svgGroup.attr("transform", "translate(" + translation + ")scale(" + scale + ")"); @@ -177,7 +179,7 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge translateX = unscaledOffsetX*scale - ((scale*windowWidth)-windowWidth)/2, translateY = unscaledOffsetY*scale - ((scale*windowHeight)-windowHeight)/2; - svgGroup.attr("transform", "translate(" + [translateX + (margin.left*scale), translateY + ((windowHeight/2 - rootH/2 - startNodeOffsetY)*scale)] + ")scale(" + scale + ")"); + svgGroup.attr("transform", "translate(" + [translateX + (marginLeft*scale), translateY + ((windowHeight/2 - rootH/2 - startNodeOffsetY)*scale)] + ")scale(" + scale + ")"); zoomObj.scale(scale); zoomObj.translate([translateX, translateY]); } @@ -200,12 +202,36 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge } function resetZoomAndPan() { - svgGroup.attr("transform", "translate(" + margin.left + "," + (windowHeight/2 - rootH/2 - startNodeOffsetY) + ")scale(" + 1 + ")"); + svgGroup.attr("transform", "translate(" + marginLeft + "," + (windowHeight/2 - rootH/2 - startNodeOffsetY) + ")scale(" + 1 + ")"); // Update the zoomObj zoomObj.scale(1); zoomObj.translate([0,0]); } + function zoomToFitChart() { + let graphDimensions = d3.select('#aw-workflow-chart-g')[0][0].getBoundingClientRect(), + startNodeDimensions = d3.select('.WorkflowChart-rootNode')[0][0].getBoundingClientRect(), + availableScreenSpace = calcAvailableScreenSpace(), + currentZoomValue = zoomObj.scale(), + unscaledH = graphDimensions.height/currentZoomValue, + unscaledW = graphDimensions.width/currentZoomValue, + scaleNeededForMaxHeight = (availableScreenSpace.height)/unscaledH, + scaleNeededForMaxWidth = (availableScreenSpace.width - marginLeft)/unscaledW, + lowerScale = Math.min(scaleNeededForMaxHeight, scaleNeededForMaxWidth), + scaleToFit = lowerScale < 0.5 ? 0.5 : (lowerScale > 2 ? 2 : Math.floor(lowerScale * 10)/10), + startNodeOffsetFromGraphCenter = Math.round((((rootH/2) + (startNodeDimensions.top/currentZoomValue)) - ((graphDimensions.top/currentZoomValue) + (unscaledH/2)))*scaleToFit); + + manualZoom(scaleToFit*100); + + scope.workflowZoomed({ + zoom: scaleToFit + }); + + svgGroup.attr("transform", "translate(" + marginLeft + "," + (windowHeight/2 - (nodeH*scaleToFit/2) + startNodeOffsetFromGraphCenter) + ")scale(" + scaleToFit + ")"); + zoomObj.translate([marginLeft - scaleToFit*marginLeft, windowHeight/2 - (nodeH*scaleToFit/2) + startNodeOffsetFromGraphCenter - ((windowHeight/2 - rootH/2 - startNodeOffsetY)*scaleToFit)]); + + } + function update() { let userCanAddEdit = (scope.workflowJobTemplateObj && scope.workflowJobTemplateObj.summary_fields && scope.workflowJobTemplateObj.summary_fields.user_capabilities && scope.workflowJobTemplateObj.summary_fields.user_capabilities.edit) || scope.canAddWorkflowJobTemplate; if(scope.dimensionsSet) { @@ -243,7 +269,6 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge 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) @@ -252,7 +277,6 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge .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") @@ -544,6 +568,12 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge node.exit().remove(); + if(nodes && nodes.length > 1 && !graphLoaded) { + zoomToFitChart(); + } + + graphLoaded = true; + let link = svgGroup.selectAll("g.link") .data(links, function(d) { return d.source.id + "-" + d.target.id; @@ -927,6 +957,10 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge manualZoom(params.zoom); }); + scope.$on('zoomToFitChart', function() { + zoomToFitChart(); + }); + let clearWatchTreeData = scope.$watch('treeData', function(newVal) { if(newVal) { update(); diff --git a/awx/ui/client/src/templates/workflows/workflow-controls/workflow-controls.block.less b/awx/ui/client/src/templates/workflows/workflow-controls/workflow-controls.block.less index f8ee0523a4..555c6fa2c7 100644 --- a/awx/ui/client/src/templates/workflows/workflow-controls/workflow-controls.block.less +++ b/awx/ui/client/src/templates/workflows/workflow-controls/workflow-controls.block.less @@ -7,7 +7,7 @@ flex: 1 0 auto; } .WorkflowControls-Pan { - flex: 0 0 85px; + flex: 0 0 87px; } .WorkflowControls-Pan--button { color: @default-icon; @@ -49,17 +49,10 @@ .WorkflowControls-Zoom--button:hover { color: @default-link-hov; } -.WorkflowControls-Zoom--minus { - margin-left: 20px; - padding-right: 8px; -} -.WorkflowControls-Zoom--plus { - padding-left: 8px; -} .WorkflowControls-zoomSlider { width: 150px; - padding-left: 10px; - padding-right: 10px; + padding-left: 8px; + padding-right: 8px; } .WorkflowControls-zoomPercentage { text-align: center; @@ -67,6 +60,16 @@ height: 24px; line-height: 24px; } +.WorkflowControls-ZoomToFit { + display: flex; + flex: 0 0 62px; + align-items: center; + justify-content: center; + color: @default-icon; +} +.WorkflowControls-ZoomToFit:hover { + color: @default-link-hov; +} .ui-slider-handle.ui-state-default.ui-corner-all { border-radius: 50%; diff --git a/awx/ui/client/src/templates/workflows/workflow-controls/workflow-controls.directive.js b/awx/ui/client/src/templates/workflows/workflow-controls/workflow-controls.directive.js index 811cfbaafb..8b0e3bfe47 100644 --- a/awx/ui/client/src/templates/workflows/workflow-controls/workflow-controls.directive.js +++ b/awx/ui/client/src/templates/workflows/workflow-controls/workflow-controls.directive.js @@ -10,7 +10,8 @@ export default ['templateUrl', scope: { panChart: '&', resetChart: '&', - zoomChart: '&' + zoomChart: '&', + zoomToFitChart: '&' }, templateUrl: templateUrl('templates/workflows/workflow-controls/workflow-controls'), restrict: 'E', @@ -60,6 +61,10 @@ export default ['templateUrl', }); }; + scope.zoomToFit = function() { + scope.zoomToFitChart(); + }; + scope.$on('workflowZoomed', function(evt, params) { scope.zoom = Math.round(params.zoom * 10) * 10; $("#slider").slider('value',scope.zoom); diff --git a/awx/ui/client/src/templates/workflows/workflow-controls/workflow-controls.partial.html b/awx/ui/client/src/templates/workflows/workflow-controls/workflow-controls.partial.html index 2115bba62c..78cf63d78b 100644 --- a/awx/ui/client/src/templates/workflows/workflow-controls/workflow-controls.partial.html +++ b/awx/ui/client/src/templates/workflows/workflow-controls/workflow-controls.partial.html @@ -1,3 +1,6 @@ +
+ +
diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less index 3de45ca157..7a6dfdd650 100644 --- a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less +++ b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less @@ -168,7 +168,7 @@ flex: 1 0 auto; } .WorkflowLegend-maker--right { - flex: 0 0 215px; + flex: 0 0 217px; text-align: right; padding-right: 20px; position: relative; @@ -246,9 +246,9 @@ } .WorkflowMaker-manualControls { position: absolute; - left: -77px; + left: -106px; height: 60px; - width: 293px; + width: 322px; background-color: @default-bg; display: flex; border: 1px solid @b7grey; @@ -259,10 +259,10 @@ } .WorkflowLegend-manualControls { position: absolute; - left: -239px; + left: -272px; top: 38px; height: 60px; - width: 290px; + width: 322px; background-color: @default-bg; display: flex; border: 1px solid @d7grey; diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.controller.js b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.controller.js index f511c7a297..9bbe6e8996 100644 --- a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.controller.js +++ b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.controller.js @@ -31,9 +31,9 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService', showTypeOptions: false }; - $scope.editRequests = []; - $scope.associateRequests = []; - $scope.disassociateRequests = []; + $scope.editRequests = []; + $scope.associateRequests = []; + $scope.disassociateRequests = []; $scope.showKey = false; $scope.toggleKey = () => $scope.showKey = !$scope.showKey; @@ -940,9 +940,9 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService', }); }; - $scope.$on('WorkflowDialogReady', function(){ - $scope.modalOpen = true; - }); + $scope.zoomToFitChart = function() { + $scope.$broadcast('zoomToFitChart'); + }; init(); diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.directive.js b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.directive.js index 03ee776dfe..add6d5cd65 100644 --- a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.directive.js +++ b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.directive.js @@ -57,6 +57,8 @@ export default ['templateUrl', 'CreateDialog', 'Wait', '$state', '$window', scope.removeWorkflowDialogReady = scope.$on('WorkflowDialogReady', function() { $('#workflow-modal-dialog').dialog('open'); + scope.modalOpen = true; + scope.$broadcast("refreshWorkflowChart"); }); diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.partial.html b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.partial.html index 57e1344ac0..adc7efb47b 100644 --- a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.partial.html +++ b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.partial.html @@ -69,7 +69,7 @@
- +
diff --git a/awx/ui/client/src/workflow-results/workflow-results.controller.js b/awx/ui/client/src/workflow-results/workflow-results.controller.js index 13bcca8c0d..be5d26e928 100644 --- a/awx/ui/client/src/workflow-results/workflow-results.controller.js +++ b/awx/ui/client/src/workflow-results/workflow-results.controller.js @@ -165,6 +165,10 @@ export default ['workflowData', 'workflowResultsService', 'workflowDataOptions', $scope.$broadcast('resetWorkflowChart'); }; + $scope.zoomToFitChart = function() { + $scope.$broadcast('zoomToFitChart'); + }; + $scope.workflowZoomed = function(zoom) { $scope.$broadcast('workflowZoomed', { zoom: zoom diff --git a/awx/ui/client/src/workflow-results/workflow-results.partial.html b/awx/ui/client/src/workflow-results/workflow-results.partial.html index 64c0700f54..2cb7f3fedb 100644 --- a/awx/ui/client/src/workflow-results/workflow-results.partial.html +++ b/awx/ui/client/src/workflow-results/workflow-results.partial.html @@ -289,7 +289,7 @@
- +