Skip to content

Commit

Permalink
Merge branch 'main' into upgrade_reacr_redux
Browse files Browse the repository at this point in the history
  • Loading branch information
tomlyn authored Sep 26, 2024
2 parents 85e7216 + 187b348 commit 2baf80e
Show file tree
Hide file tree
Showing 12 changed files with 196 additions and 85 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,23 @@ export default class CanvasUtils {
return { x: startPointX, y: startPointY, originX, originY, dir };
}

// Assisted by WCA@IBM
// Latest GenAI contribution: ibm/granite-20b-code-instruct-v2
// Returns the angle between two points where the angle
// returned is always positive. The angle starts at the 3 o'clock
// position which is 0 degrees and increases in a clock-wise
// direction.
static calculateAngle(x1, y1, x2, y2) {
const dx = x2 - x1;
const dy = y2 - y1;
const angle = Math.atan2(dy, dx);
let angleInDegrees = angle * (180 / Math.PI);
if (angleInDegrees < 0) {
angleInDegrees += 360;
}
return angleInDegrees;
}

// Returns a direction NORTH, SOUTH, EAST or WEST which is the direction
// from the origin position within the rectangle described by x, y, w and h
// to the end position described by endX and endY.
Expand Down
128 changes: 80 additions & 48 deletions canvas_modules/common-canvas/src/common-canvas/svg-canvas-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -5040,7 +5040,7 @@ export default class SVGCanvasRenderer {
// Updates the data links for all the nodes with two optional fields
// (called srcFreeformInfo and trgFreeformInfo) based on the location of the
// nodes the links go from and to. The info in these fields is used to
// calculate the starting and ending position of freeform line links.
// calculate the starting and ending position of freeform link lines.
// This ensures that input and output links that go in a certain direction
// (NORTH, SOUTH, EAST or WEST) are grouped together so they can be
// separated out when freeform lines are drawn between nodes.
Expand All @@ -5063,13 +5063,13 @@ export default class SVGCanvasRenderer {
// Self-referencing link
if (node.id === link.srcObj?.id &&
link.srcObj?.id === link.trgNode?.id) {
linksInfo[NORTH].push({ type: "in", endNode: link.srcObj, link });
linksInfo[EAST].push({ type: "out", endNode: link.trgNode, link });
linksInfo[NORTH].push({ type: "in", startNode: link.srcObj, endNode: link.trgNode, link });
linksInfo[EAST].push({ type: "out", startNode: link.srcObj, endNode: link.trgNode, link });

} else if (link.trgNode && link.trgNode.id === node.id) {
if (link.srcObj) {
const dir = this.getDirAdjusted(link.trgNode, link.srcObj);
linksInfo[dir].push({ type: "in", endNode: link.srcObj, link });
const dir = this.getDirToNode(link.trgNode, link.srcObj);
linksInfo[dir].push({ type: "in", startNode: link.trgNode, endNode: link.srcObj, link });

} else if (link.srcPos) {
const dir = this.getDirToEndPos(link.trgNode, link.srcPos.x_pos, link.srcPos.y_pos);
Expand All @@ -5078,8 +5078,8 @@ export default class SVGCanvasRenderer {

} else if (link.srcObj && link.srcObj.id === node.id) {
if (link.trgNode) {
const dir = this.getDirAdjusted(link.srcObj, link.trgNode);
linksInfo[dir].push({ type: "out", endNode: link.trgNode, link });
const dir = this.getDirToNode(link.srcObj, link.trgNode);
linksInfo[dir].push({ type: "out", startNode: link.srcObj, endNode: link.trgNode, link });

} else if (link.trgPos) {
const dir = this.getDirToEndPos(link.srcObj, link.trgPos.x_pos, link.trgPos.y_pos);
Expand All @@ -5089,42 +5089,22 @@ export default class SVGCanvasRenderer {
}
});

linksInfo.n = this.sortLinksInfo(linksInfo.n, NORTH);
linksInfo.s = this.sortLinksInfo(linksInfo.s, SOUTH);
linksInfo.e = this.sortLinksInfo(linksInfo.e, EAST);
linksInfo.w = this.sortLinksInfo(linksInfo.w, WEST);
const startCenter = {
x: node.x_pos + node.width / 2,
y: node.y_pos + node.height / 2
};

linksInfo.n = this.sortLinksInfo(linksInfo.n, NORTH, startCenter);
linksInfo.s = this.sortLinksInfo(linksInfo.s, SOUTH, startCenter);
linksInfo.e = this.sortLinksInfo(linksInfo.e, EAST, startCenter);
linksInfo.w = this.sortLinksInfo(linksInfo.w, WEST, startCenter);

this.updateLinksInfo(linksInfo.n, NORTH);
this.updateLinksInfo(linksInfo.s, SOUTH);
this.updateLinksInfo(linksInfo.e, EAST);
this.updateLinksInfo(linksInfo.w, WEST);
}

// Returns the direction of a link from the start node to the end node
// as either NORTH, SOUTH, EAST or WEST. Some direction combinations
// have to be overriden to prevent link lines overlapping.
getDirAdjusted(startNode, endNode) {
let dir = this.getDirToNode(startNode, endNode);

// When start -> end is SOUTH and end -> start is WEST the returned direction
// becomes EAST instead of SOUTH to prevent link lines overlapping.
if (dir === SOUTH) {
const dir2 = this.getDirToNode(endNode, startNode);
if (dir2 === WEST) {
dir = EAST;
}

// When start -> end is NORTH and end -> start is EAST the returned direction
// becomes WEST instead of NORTH to prevent link lines overlapping.
} else if (dir === NORTH) {
const dir2 = this.getDirToNode(endNode, startNode);
if (dir2 === EAST) {
dir = WEST;
}
}
return dir;
}

// Returns the direction (NORTH, SOUTH, EAST or WEST) from the start node
// to the end node.
getDirToNode(startNode, endNode) {
Expand All @@ -5148,10 +5128,13 @@ export default class SVGCanvasRenderer {
}

// Returns the linksDirArray passed in with the linkInfo objects in the
// array ordered by the position of the end of each link line, depending on
// the direction (dir) of the lines. This is achieved by spliting the links
// into groups where links in the same group go to/from the same node.
sortLinksInfo(linksDirArrayIn, dir) {
// array ordered by the angle that each link makes with the center of the
// start node. When handling multiple links that go to the same node
// the links have to be grouped where links in the same group go to/from
// the same node. For groups, the x and y are *projected* corrdinates to
// allow us to calculate the angles and ordering etc. The actual x and y
// for the links is calculated in getAttachedLinkObj and getDetachedLinkObj.
sortLinksInfo(linksDirArrayIn, dir, startCenter) {
let linksDirArray = linksDirArrayIn;
if (linksDirArray.length > 1) {
const groups = this.getLinkInfoGroups(linksDirArray);
Expand All @@ -5168,23 +5151,72 @@ export default class SVGCanvasRenderer {
li.x = this.nodeUtils.getNodeCenterPosX(node);
li.y = node.y_pos + ((node.height / parts) * (i + 1));
}
// Special case where links that go SOUTH from the node and
// point to the WEST of the end node, get crossed over each other.
// In this case the x coordinates of the link items need to be
// reversed.
if (dir === SOUTH) {
const reverseDir = this.getDirToNode(li.endNode, li.startNode);
if (reverseDir === WEST) {
li.x = node.x_pos + ((node.width / parts) * (group.length - i));
}
}
// Special case where links that go NORTH from the node and
// point to the EAST of the end node, get crossed over each other.
// In this case the x coordinates of the link items need to be
// reversed.
if (dir === NORTH) {
const reverseDir = this.getDirToNode(li.endNode, li.startNode);
if (reverseDir === EAST) {
li.x = node.x_pos + ((node.width / parts) * (group.length - i));
}
}
});
});

// For NORTH and SOUTH links we sort linksDirArray by the x coordinate
// of the end of each link. For EAST and WEST we sort by the y
// coordinate.
if (dir === NORTH || dir === SOUTH) {
linksDirArray = linksDirArray.sort((a, b) => (a.x > b.x ? 1 : -1));
// Set an angle for each linkDir so that they can be sorted so they do not
// overlap when being drawn to or from the node. The angle is from the
// center of the node we are handling to their projected end point.
linksDirArray.forEach((ld) => {
ld.angle = CanvasUtils.calculateAngle(startCenter.x, startCenter.y, ld.x, ld.y);

// Make sure the angles for links on the EAST side of the node are
// increasing in the clockwise direction by decrementing the angles from
// 270 to 360 by 360 degrees. (This is because calculateAngle returns
// positive angles from the 3 o'clock position in clockwise direction.)
if (dir === EAST && ld.angle >= 270) {
ld.angle -= 360;
}

// For self-referencing links we overwrite the angle to ensure that
// the outward direction (EAST) is always drawn at the top of any
// EAST links and the inward direction (NORTH) is always drawn to
// the right of any NORTH links.
if (ld.startNode && ld.endNode && ld.startNode === ld.endNode) {
if (dir === EAST) {
ld.angle = -90;
} else if (dir === NORTH) {
ld.angle = 360;
}
}
});

// Sort the linksDirArray by the angle that each link forms with the
// center of the start node.
if (dir === NORTH || dir === EAST) {
linksDirArray = linksDirArray.sort((a, b) => (a.angle > b.angle ? 1 : -1));
} else {
linksDirArray = linksDirArray.sort((a, b) => (a.y > b.y ? 1 : -1));
linksDirArray = linksDirArray.sort((a, b) => (a.angle < b.angle ? 1 : -1));
}
}
return linksDirArray;
}

// Returns a 'groups' object where each field is index by a node ID and
// contains an array of linkInfo objects that go to/from the node.
// Returns a 'groups' object where each field is indexed by a node ID and
// contains an array of linkInfo objects that go to/from the node. Note:
// endNode is the target node for link that point away from the node we
// are handling and is the source node for links the point to the node
// we are handling.
getLinkInfoGroups(linksDirArray) {
const groups = {};
linksDirArray.forEach((li) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,7 @@ export default class SvgCanvasLinks {

const path = "M " + link.x1 + " " + link.y1 +
" L " +
rightInc + " " + link.y1 + " " +
rightInc + " " + topInc + " " +
link.x2 + " " + topInc + " " +
link.x2 + " " + link.y2;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,10 @@ export default class CanavasStore {
return this.cloneData(this.store.getState().tooltip);
}

isLeftFlyoutOpen() {
return this.store.getState().leftflyout.isOpen;
}

isRightFlyoutOpen() {
return this.store.getState().rightflyout.isOpen;
}
Expand Down
4 changes: 4 additions & 0 deletions canvas_modules/common-canvas/src/palette/palette.scss
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ $palette-search-container-height: 41px;
}
}

.palette-nav {
height: 100%;
}

.palette-flyout-content {
position: absolute; // Needed to allow the scroll of categories/nodes to work.
height: 100%;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ class ToolbarButtonItem extends React.Component {
const direction = this.props.tooltipDirection ? this.props.tooltipDirection : "bottom";

return (
<Tooltip id={tooltipId} tip={tip} disable={!enableTooltip} className="icon-tooltip" direction={direction}>
<Tooltip id={tooltipId} tip={tip} disable={!enableTooltip} className="icon-tooltip" direction={direction} hoverable>
{content}
</Tooltip>
);
Expand Down
27 changes: 24 additions & 3 deletions canvas_modules/common-canvas/src/tooltip/tooltip.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class ToolTip extends React.Component {
this.tabKeyPressed = false;
// Tooltip should not close if link inside tooltip is clicked.
this.linkClicked = false;
this.inTooltip = false; // A boolean variable that determines if the cursor is over the tooltip
}

componentDidMount() {
Expand Down Expand Up @@ -356,7 +357,18 @@ class ToolTip extends React.Component {
if (this.props.children) {
// when children are passed in, tooltip will handle show/hide, otherwise consumer has to hide show/hide tooltip
const mouseover = () => this.setTooltipVisible(true);
const mouseleave = () => this.setTooltipVisible(false);
let mouseleave;
if (this.props.hoverable) {
mouseleave = () => {
setTimeout(() => {
if (!this.inTooltip) {
this.setTooltipVisible(false);
}
}, 100);
};
} else {
mouseleave = () => this.setTooltipVisible(false);
}
const mousedown = () => this.setTooltipVisible(false);
// `focus` event occurs before `click`. Adding timeout in onFocus function to ensure click is executed first.
// Ref - https://stackoverflow.com/a/49512400
Expand Down Expand Up @@ -487,6 +499,13 @@ class ToolTip extends React.Component {
aria-hidden={!this.state.isTooltipVisible}
direction={this.props.direction}
ref={(ref) => (this.targetRef = ref)}
onMouseEnter={() => {
this.inTooltip = true;
}}
onMouseLeave={() => {
this.inTooltip = false;
this.setTooltipVisible(false);
}}
>
<svg className="tipArrow" x="0px" y="0px" viewBox="0 0 9.1 16.1">
<polyline points="9.1,15.7 1.4,8.1 9.1,0.5" />
Expand Down Expand Up @@ -522,14 +541,16 @@ ToolTip.propTypes = {
showToolTipIfTruncated: PropTypes.bool, // Set to true to only display tooltip if full text does not fit in displayable width
truncatedRef: PropTypes.object,
delay: PropTypes.number,
showToolTipOnClick: PropTypes.bool
showToolTipOnClick: PropTypes.bool,
hoverable: PropTypes.bool, // If true, mouse cursor can be hovered over to the tooltip, instead of immediately disappearing.
};

ToolTip.defaultProps = {
delay: 200,
direction: "bottom",
showToolTipIfTruncated: false, // False will always show Tooltip even when whole word can be displayed
showToolTipOnClick: false
showToolTipOnClick: false,
hoverable: false
};

export default ToolTip;
1 change: 0 additions & 1 deletion canvas_modules/common-canvas/src/tooltip/tooltip.scss
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
line-height: 1.2;
text-align: left;
z-index: 10000; // Modal layout has z-index 9000. Show tooltip on top of modal.
pointer-events: none;
word-wrap: break-word;
max-width: 228px;
border-radius: 2px;
Expand Down
9 changes: 5 additions & 4 deletions canvas_modules/harness/cypress/e2e/canvas/links.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,19 +214,20 @@ describe("Test basic link construction", function() {
"M 367 167.5 L 397 167.5 L 397 118.5 L 267 118.5 L 267 167.5 L 297 167.5"
);

cy.setCanvasConfig({ "selectedLinkType": "Straight", "selectedLinkMethod": "Ports" });
cy.setCanvasConfig({ "selectedLinkType": "Straight", "selectedLinkMethod": "Ports",
"selectedStraightLinksAsFreeform": false });
cy.wait(10);
cy.verifyLinkPath(
"Execution node", "outPort", "Execution node", "inPort",
"M 374 176 L 404 101.5 332 101.5 332 131.5"
"M 367 167 L 397 118 L 267 118.5 L 267 167.5 L 297 167.5"
);

// Test the 4 Freeform combinations

cy.setCanvasConfig({ "selectedLinkType": "Curve", "selectedLinkMethod": "Freeform" });
cy.verifyLinkPath(
"Execution node", "outPort", "Execution node", "inPort",
"M 374 176 L 404 101.5 332 101.5 332 131.5"
"M 367 167.5 L 397 118.5 L 267 118.5 L 267 167.5 L 297 167.5"
);

cy.setCanvasConfig({ "selectedLinkType": "Elbow", "selectedLinkMethod": "Freeform" });
Expand All @@ -248,7 +249,7 @@ describe("Test basic link construction", function() {
cy.wait(10);
cy.verifyLinkPath(
"Execution node", "outPort", "Execution node", "inPort",
"M 374 176 L 404 101.5 332 101.5 332 131.5"
"M 374 176 L 404 176 404 101.5 332 101.5 332 131.5"
);
});
});
Expand Down
Loading

0 comments on commit 2baf80e

Please sign in to comment.