Combines the two start node components into one. Removes use of document.getElementById in workflow components in favor of refs.

This commit is contained in:
mabashian
2020-01-24 11:24:46 -05:00
parent f98b274177
commit eddb6e1faf
12 changed files with 82 additions and 101 deletions

View File

@@ -1,4 +1,4 @@
import React, { useState } from 'react'; import React, { useRef, useState } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
@@ -14,19 +14,19 @@ const StartG = styled.g`
pointer-events: ${props => (props.ignorePointerEvents ? 'none' : 'auto')}; pointer-events: ${props => (props.ignorePointerEvents ? 'none' : 'auto')};
`; `;
function VisualizerStartNode({ function WorkflowStartNode({
addingLink, addingLink,
i18n, i18n,
nodePositions, nodePositions,
onAddNodeClick, onAddNodeClick,
onUpdateHelpText, onUpdateHelpText,
readOnly, showActionTooltip,
}) { }) {
const ref = useRef(null);
const [hovering, setHovering] = useState(false); const [hovering, setHovering] = useState(false);
const handleNodeMouseEnter = () => { const handleNodeMouseEnter = () => {
const nodeEl = document.getElementById('node-1'); ref.current.parentNode.appendChild(ref.current);
nodeEl.parentNode.appendChild(nodeEl);
setHovering(true); setHovering(true);
}; };
@@ -36,6 +36,7 @@ function VisualizerStartNode({
ignorePointerEvents={addingLink} ignorePointerEvents={addingLink}
onMouseEnter={handleNodeMouseEnter} onMouseEnter={handleNodeMouseEnter}
onMouseLeave={() => setHovering(false)} onMouseLeave={() => setHovering(false)}
ref={ref}
transform={`translate(${nodePositions[1].x},0)`} transform={`translate(${nodePositions[1].x},0)`}
> >
<rect <rect
@@ -50,7 +51,7 @@ function VisualizerStartNode({
<text x="13" y="30" dy=".35em" fill="white"> <text x="13" y="30" dy=".35em" fill="white">
START START
</text> </text>
{!readOnly && hovering && ( {showActionTooltip && hovering && (
<WorkflowActionTooltip <WorkflowActionTooltip
actions={[ actions={[
<WorkflowActionTooltipItem <WorkflowActionTooltipItem
@@ -75,12 +76,18 @@ function VisualizerStartNode({
); );
} }
VisualizerStartNode.propTypes = { WorkflowStartNode.propTypes = {
addingLink: bool.isRequired, addingLink: bool,
nodePositions: shape().isRequired, nodePositions: shape().isRequired,
onAddNodeClick: func.isRequired, onAddNodeClick: func,
readOnly: bool.isRequired, showActionTooltip: bool.isRequired,
onUpdateHelpText: func.isRequired, onUpdateHelpText: func,
}; };
export default withI18n()(VisualizerStartNode); WorkflowStartNode.defaultProps = {
addingLink: false,
onAddNodeClick: () => {},
onUpdateHelpText: () => {},
};
export default withI18n()(WorkflowStartNode);

View File

@@ -0,0 +1,36 @@
import React from 'react';
import { mount } from 'enzyme';
import WorkflowStartNode from './WorkflowStartNode';
const nodePositions = {
1: {
x: 0,
y: 0,
},
};
describe('WorkflowStartNode', () => {
test('mounts successfully', () => {
const wrapper = mount(
<svg>
<WorkflowStartNode
nodePositions={nodePositions}
showActionTooltip={false}
/>
</svg>
);
expect(wrapper).toHaveLength(1);
});
test('tooltip shown on hover', () => {
const wrapper = mount(
<svg>
<WorkflowStartNode nodePositions={nodePositions} showActionTooltip />
</svg>
);
expect(wrapper.find('WorkflowActionTooltip')).toHaveLength(0);
wrapper.find('WorkflowStartNode').simulate('mouseenter');
expect(wrapper.find('WorkflowActionTooltip')).toHaveLength(1);
wrapper.find('WorkflowStartNode').simulate('mouseleave');
expect(wrapper.find('WorkflowActionTooltip')).toHaveLength(0);
});
});

View File

@@ -7,4 +7,5 @@ export { default as WorkflowKey } from './WorkflowKey';
export { default as WorkflowLinkHelp } from './WorkflowLinkHelp'; export { default as WorkflowLinkHelp } from './WorkflowLinkHelp';
export { default as WorkflowNodeHelp } from './WorkflowNodeHelp'; export { default as WorkflowNodeHelp } from './WorkflowNodeHelp';
export { default as WorkflowNodeTypeLetter } from './WorkflowNodeTypeLetter'; export { default as WorkflowNodeTypeLetter } from './WorkflowNodeTypeLetter';
export { default as WorkflowStartNode } from './WorkflowStartNode';
export { default as WorkflowTools } from './WorkflowTools'; export { default as WorkflowTools } from './WorkflowTools';

View File

@@ -8,13 +8,13 @@ import {
import { import {
WorkflowOutputLink, WorkflowOutputLink,
WorkflowOutputNode, WorkflowOutputNode,
WorkflowOutputStartNode,
} from '@screens/Job/WorkflowOutput'; } from '@screens/Job/WorkflowOutput';
import { import {
WorkflowHelp, WorkflowHelp,
WorkflowKey, WorkflowKey,
WorkflowLinkHelp, WorkflowLinkHelp,
WorkflowNodeHelp, WorkflowNodeHelp,
WorkflowStartNode,
WorkflowTools, WorkflowTools,
} from '@components/Workflow'; } from '@components/Workflow';
@@ -75,8 +75,7 @@ function WorkflowOutputGraph({
}; };
const handlePanToMiddle = () => { const handlePanToMiddle = () => {
const svgElement = document.getElementById('workflow-svg'); const svgBoundingClientRect = svgRef.current.getBoundingClientRect();
const svgBoundingClientRect = svgElement.getBoundingClientRect();
d3.select(svgRef.current).call( d3.select(svgRef.current).call(
zoomRef.transform, zoomRef.transform,
d3.zoomIdentity d3.zoomIdentity
@@ -88,8 +87,7 @@ function WorkflowOutputGraph({
}; };
const handleZoomChange = newScale => { const handleZoomChange = newScale => {
const svgElement = document.getElementById('workflow-svg'); const svgBoundingClientRect = svgRef.current.getBoundingClientRect();
const svgBoundingClientRect = svgElement.getBoundingClientRect();
const currentScaleAndOffset = d3.zoomTransform( const currentScaleAndOffset = d3.zoomTransform(
d3.select(svgRef.current).node() d3.select(svgRef.current).node()
); );
@@ -121,8 +119,7 @@ function WorkflowOutputGraph({
.node() .node()
.getBBox(); .getBBox();
const svgElement = document.getElementById('workflow-svg'); const svgBoundingClientRect = svgRef.current.getBoundingClientRect();
const svgBoundingClientRect = svgElement.getBoundingClientRect();
const [scaleToFit, yTranslate] = getScaleAndOffsetToFit( const [scaleToFit, yTranslate] = getScaleAndOffsetToFit(
gBoundingClientRect, gBoundingClientRect,
@@ -175,9 +172,10 @@ function WorkflowOutputGraph({
> >
<g id="workflow-g" ref={gRef}> <g id="workflow-g" ref={gRef}>
{nodePositions && [ {nodePositions && [
<WorkflowOutputStartNode <WorkflowStartNode
key="start" key="start"
nodePositions={nodePositions} nodePositions={nodePositions}
showActionTooltip={false}
/>, />,
links.map(link => ( links.map(link => (
<WorkflowOutputLink <WorkflowOutputLink

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import { shape } from 'prop-types'; import { shape } from 'prop-types';
import { import {
generateLine, generateLine,
@@ -7,23 +7,18 @@ import {
} from '@util/workflow'; } from '@util/workflow';
function WorkflowOutputLink({ link, nodePositions, onUpdateLinkHelp }) { function WorkflowOutputLink({ link, nodePositions, onUpdateLinkHelp }) {
const ref = useRef(null);
const [hovering, setHovering] = useState(false); const [hovering, setHovering] = useState(false);
const [pathD, setPathD] = useState(); const [pathD, setPathD] = useState();
const [pathStroke, setPathStroke] = useState('#CCCCCC'); const [pathStroke, setPathStroke] = useState('#CCCCCC');
const handleLinkMouseEnter = () => { const handleLinkMouseEnter = () => {
const linkEl = document.getElementById( ref.current.parentNode.appendChild(ref.current);
`link-${link.source.id}-${link.target.id}`
);
linkEl.parentNode.appendChild(linkEl);
setHovering(true); setHovering(true);
}; };
const handleLinkMouseLeave = () => { const handleLinkMouseLeave = () => {
const linkEl = document.getElementById( ref.current.parentNode.prepend(ref.current);
`link-${link.source.id}-${link.target.id}`
);
linkEl.parentNode.prepend(linkEl);
setHovering(null); setHovering(null);
}; };
@@ -46,6 +41,7 @@ function WorkflowOutputLink({ link, nodePositions, onUpdateLinkHelp }) {
return ( return (
<g <g
ref={ref}
id={`link-${link.source.id}-${link.target.id}`} id={`link-${link.source.id}-${link.target.id}`}
onMouseEnter={handleLinkMouseEnter} onMouseEnter={handleLinkMouseEnter}
onMouseLeave={handleLinkMouseLeave} onMouseLeave={handleLinkMouseLeave}

View File

@@ -1,28 +0,0 @@
import React from 'react';
import { shape } from 'prop-types';
import { constants as wfConstants } from '@util/workflow';
function WorkflowOutputStartNode({ nodePositions }) {
return (
<g id="node-1" transform={`translate(${nodePositions[1].x},0)`}>
<rect
fill="#0279BC"
height={wfConstants.rootH}
rx="2"
ry="2"
width={wfConstants.rootW}
y="10"
/>
{/* TODO: We need to be able to handle translated text here */}
<text x="13" y="30" dy=".35em" fill="white">
START
</text>
</g>
);
}
WorkflowOutputStartNode.propTypes = {
nodePositions: shape().isRequired,
};
export default WorkflowOutputStartNode;

View File

@@ -1,21 +0,0 @@
import React from 'react';
import { mount } from 'enzyme';
import WorkflowOutputStartNode from './WorkflowOutputStartNode';
const nodePositions = {
1: {
x: 0,
y: 0,
},
};
describe('WorkflowOutputStartNode', () => {
test('mounts successfully', () => {
const wrapper = mount(
<svg>
<WorkflowOutputStartNode nodePositions={nodePositions} />
</svg>
);
expect(wrapper).toHaveLength(1);
});
});

View File

@@ -2,5 +2,4 @@ export { default as WorkflowOutput } from './WorkflowOutput';
export { default as WorkflowOutputGraph } from './WorkflowOutputGraph'; export { default as WorkflowOutputGraph } from './WorkflowOutputGraph';
export { default as WorkflowOutputLink } from './WorkflowOutputLink'; export { default as WorkflowOutputLink } from './WorkflowOutputLink';
export { default as WorkflowOutputNode } from './WorkflowOutputNode'; export { default as WorkflowOutputNode } from './WorkflowOutputNode';
export { default as WorkflowOutputStartNode } from './WorkflowOutputStartNode';
export { default as WorkflowOutputToolbar } from './WorkflowOutputToolbar'; export { default as WorkflowOutputToolbar } from './WorkflowOutputToolbar';

View File

@@ -14,12 +14,12 @@ import {
WorkflowKey, WorkflowKey,
WorkflowLinkHelp, WorkflowLinkHelp,
WorkflowNodeHelp, WorkflowNodeHelp,
WorkflowStartNode,
WorkflowTools, WorkflowTools,
} from '@components/Workflow'; } from '@components/Workflow';
import { import {
VisualizerLink, VisualizerLink,
VisualizerNode, VisualizerNode,
VisualizerStartNode,
} from '@screens/Template/WorkflowJobTemplateVisualizer'; } from '@screens/Template/WorkflowJobTemplateVisualizer';
const PotentialLink = styled.polyline` const PotentialLink = styled.polyline`
@@ -148,8 +148,7 @@ function VisualizerGraph({
}; };
const handlePanToMiddle = () => { const handlePanToMiddle = () => {
const svgElement = document.getElementById('workflow-svg'); const svgBoundingClientRect = svgRef.current.getBoundingClientRect();
const svgBoundingClientRect = svgElement.getBoundingClientRect();
d3.select(svgRef.current).call( d3.select(svgRef.current).call(
zoomRef.transform, zoomRef.transform,
d3.zoomIdentity d3.zoomIdentity
@@ -161,8 +160,7 @@ function VisualizerGraph({
}; };
const handleZoomChange = newScale => { const handleZoomChange = newScale => {
const svgElement = document.getElementById('workflow-svg'); const svgBoundingClientRect = svgRef.current.getBoundingClientRect();
const svgBoundingClientRect = svgElement.getBoundingClientRect();
const currentScaleAndOffset = d3.zoomTransform( const currentScaleAndOffset = d3.zoomTransform(
d3.select(svgRef.current).node() d3.select(svgRef.current).node()
); );
@@ -194,8 +192,7 @@ function VisualizerGraph({
.node() .node()
.getBBox(); .getBBox();
const svgElement = document.getElementById('workflow-svg'); const svgBoundingClientRect = svgRef.current.getBoundingClientRect();
const svgBoundingClientRect = svgElement.getBoundingClientRect();
const [scaleToFit, yTranslate] = getScaleAndOffsetToFit( const [scaleToFit, yTranslate] = getScaleAndOffsetToFit(
gBoundingClientRect, gBoundingClientRect,
@@ -276,12 +273,12 @@ function VisualizerGraph({
/> />
<g id="workflow-g" ref={gRef}> <g id="workflow-g" ref={gRef}>
{nodePositions && [ {nodePositions && [
<VisualizerStartNode <WorkflowStartNode
addingLink={addingLink} addingLink={addingLink}
key="start" key="start"
nodePositions={nodePositions} nodePositions={nodePositions}
onAddNodeClick={onAddNodeClick} onAddNodeClick={onAddNodeClick}
readOnly={readOnly} showActionTooltip={!readOnly}
onUpdateHelpText={setHelpText} onUpdateHelpText={setHelpText}
/>, />,
links.map(link => { links.map(link => {

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
@@ -30,6 +30,7 @@ function VisualizerLink({
onUpdateLinkHelp, onUpdateLinkHelp,
readOnly, readOnly,
}) { }) {
const ref = useRef(null);
const [hovering, setHovering] = useState(false); const [hovering, setHovering] = useState(false);
const [pathD, setPathD] = useState(); const [pathD, setPathD] = useState();
const [pathStroke, setPathStroke] = useState('#CCCCCC'); const [pathStroke, setPathStroke] = useState('#CCCCCC');
@@ -80,18 +81,12 @@ function VisualizerLink({
]; ];
const handleLinkMouseEnter = () => { const handleLinkMouseEnter = () => {
const linkEl = document.getElementById( ref.current.parentNode.appendChild(ref.current);
`link-${link.source.id}-${link.target.id}`
);
linkEl.parentNode.appendChild(linkEl);
setHovering(true); setHovering(true);
}; };
const handleLinkMouseLeave = () => { const handleLinkMouseLeave = () => {
const linkEl = document.getElementById( ref.current.parentNode.prepend(ref.current);
`link-${link.source.id}-${link.target.id}`
);
linkEl.parentNode.prepend(linkEl);
setHovering(null); setHovering(null);
}; };
@@ -120,6 +115,7 @@ function VisualizerLink({
ignorePointerEvents={addingLink} ignorePointerEvents={addingLink}
onMouseEnter={handleLinkMouseEnter} onMouseEnter={handleLinkMouseEnter}
onMouseLeave={handleLinkMouseLeave} onMouseLeave={handleLinkMouseLeave}
ref={ref}
> >
<polygon <polygon
fill="#E1E1E1" fill="#E1E1E1"

View File

@@ -1,4 +1,4 @@
import React, { useState } from 'react'; import React, { useRef, useState } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
@@ -54,11 +54,11 @@ function VisualizerNode({
onUpdateHelpText, onUpdateHelpText,
updateNodeHelp, updateNodeHelp,
}) { }) {
const ref = useRef(null);
const [hovering, setHovering] = useState(false); const [hovering, setHovering] = useState(false);
const handleNodeMouseEnter = () => { const handleNodeMouseEnter = () => {
const nodeEl = document.getElementById(`node-${node.id}`); ref.current.parentNode.appendChild(ref.current);
nodeEl.parentNode.appendChild(nodeEl);
setHovering(true); setHovering(true);
if (addingLink) { if (addingLink) {
onUpdateHelpText( onUpdateHelpText(
@@ -168,6 +168,7 @@ function VisualizerNode({
noPointerEvents={isAddLinkSourceNode} noPointerEvents={isAddLinkSourceNode}
onMouseEnter={handleNodeMouseEnter} onMouseEnter={handleNodeMouseEnter}
onMouseLeave={handleNodeMouseLeave} onMouseLeave={handleNodeMouseLeave}
ref={ref}
transform={`translate(${nodePositions[node.id].x},${nodePositions[node.id] transform={`translate(${nodePositions[node.id].x},${nodePositions[node.id]
.y - nodePositions[1].y})`} .y - nodePositions[1].y})`}
> >

View File

@@ -2,6 +2,5 @@ export { default as Visualizer } from './Visualizer';
export { default as VisualizerGraph } from './VisualizerGraph'; export { default as VisualizerGraph } from './VisualizerGraph';
export { default as VisualizerLink } from './VisualizerLink'; export { default as VisualizerLink } from './VisualizerLink';
export { default as VisualizerNode } from './VisualizerNode'; export { default as VisualizerNode } from './VisualizerNode';
export { default as VisualizerStartNode } from './VisualizerStartNode';
export { default as VisualizerStartScreen } from './VisualizerStartScreen'; export { default as VisualizerStartScreen } from './VisualizerStartScreen';
export { default as VisualizerToolbar } from './VisualizerToolbar'; export { default as VisualizerToolbar } from './VisualizerToolbar';