Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 48 additions & 4 deletions packages/demo-app-ts/src/demos/stylesDemo/Styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,47 @@ export const EdgeStyles = withTopologySetup(() => {
return null;
});

export const FreezedEdge = withTopologySetup(() => {
useComponentFactory(defaultComponentFactory);
useComponentFactory(stylesComponentFactory);
const nodes: NodeModel[] = createGroupNodes('edges-group');
const edges: EdgeModel[] = [];

const middleNodeIndex = nodes.length - 1;
nodes.forEach((item, index) => {
if (index === middleNodeIndex) {
return;
}
const endIndex = index < nodes.length - 2 ? index + 1 : 0;
edges.push({
id: `edge-${index}-${endIndex}`,
type: 'edge',
source: nodes[index].id,
target: nodes[endIndex].id,
edgeStyle: EDGE_STYLES[index % EDGE_STYLE_COUNT],
data: { freezeEdgeDuringNodeDrag: true }
});
edges.push({
id: `edge-${middleNodeIndex}-${index}`,
type: 'edge',
source: nodes[middleNodeIndex].id,
target: nodes[index].id,
edgeStyle: EdgeStyle.default,
data: { freezeEdgeDuringNodeDrag: true }
});
});

useModel({
graph: {
id: 'g1',
type: 'graph'
},
nodes,
edges
});
return null;
});

export const EdgeAnimationStyles = withTopologySetup(() => {
useComponentFactory(defaultComponentFactory);
useComponentFactory(stylesComponentFactory);
Expand Down Expand Up @@ -977,16 +1018,19 @@ export const StyleEdges: React.FunctionComponent = () => {
<Tab eventKey={0} title={<TabTitleText>Edge Styles</TabTitleText>}>
<EdgeStyles />
</Tab>
<Tab eventKey={1} title={<TabTitleText>Animated Edges</TabTitleText>}>
<Tab eventKey={1} title={<TabTitleText>Freeze Edge When Dragging Node</TabTitleText>}>
<FreezedEdge />
</Tab>
<Tab eventKey={2} title={<TabTitleText>Animated Edges</TabTitleText>}>
<EdgeAnimationStyles />
</Tab>
<Tab eventKey={2} title={<TabTitleText>Edge Terminal Types</TabTitleText>}>
<Tab eventKey={3} title={<TabTitleText>Edge Terminal Types</TabTitleText>}>
<EdgeTerminalStyles />
</Tab>
<Tab eventKey={3} title={<TabTitleText>Edge Terminal Status</TabTitleText>}>
<Tab eventKey={4} title={<TabTitleText>Edge Terminal Status</TabTitleText>}>
<EdgeTerminalStatusStyles />
</Tab>
<Tab eventKey={4} title={<TabTitleText>Edge Terminal Tags</TabTitleText>}>
<Tab eventKey={5} title={<TabTitleText>Edge Terminal Tags</TabTitleText>}>
<EdgeTerminalTagStyles />
</Tab>
</Tabs>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ import {
groupDropTargetSpec,
graphDropTargetSpec,
NODE_DRAG_TYPE,
CREATE_CONNECTOR_DROP_TYPE
CREATE_CONNECTOR_DROP_TYPE,
edgeDropTargetSpec
} from '@patternfly/react-topology';
import StyleNode from './StyleNode';
import StyleGroup from './StyleGroup';
Expand Down Expand Up @@ -132,7 +133,11 @@ const stylesComponentFactory: ComponentFactory = (
collect: (monitor) => ({
dragging: monitor.isDragging()
})
})(withContextMenu(() => defaultMenu)(withSelection()(StyleEdge)))
})(
withDndDrop<Node, any, { droppable?: boolean; hover?: boolean; canDrop?: boolean }, EdgeProps>(
edgeDropTargetSpec
)(withContextMenu(() => defaultMenu)(withSelection()(StyleEdge)))
)
);
default:
return undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ export class DemoModel {
showStatus: false,
showAnimations: false,
showTags: false,
terminalTypes: false
terminalTypes: false,
freezeEdgeDuringNodeDrag: false
};
protected creationCountsP: { numNodes: number; numEdges: number; numGroups: number; nestedLevel: number } = {
numNodes: 6,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const DemoEdge: React.FunctionComponent<DemoEdgeProps> = ({ element, ...rest })
endTerminalStatus={options.showStatus && NODE_STATUSES[data.index % NODE_STATUSES.length]}
tag={options.showTags ? data.tag : undefined}
tagStatus={options.showStatus && NODE_STATUSES[data.index % NODE_STATUSES.length]}
freezeEdgeDuringNodeDrag={options.freezeEdgeDuringNodeDrag}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,19 @@ const OptionsContextBar: React.FC = observer(() => {
>
Tags
</SelectOption>
<SelectOption
value="Freeze Edge During Node Drag"
hasCheckbox
isSelected={options.edgeOptions.freezeEdgeDuringNodeDrag}
onClick={() =>
options.setEdgeOptions({
...options.edgeOptions,
freezeEdgeDuringNodeDrag: !options.edgeOptions.freezeEdgeDuringNodeDrag
})
}
>
Freeze Edge During Node Drag
</SelectOption>
</SelectList>
);
const edgeOptionsToggle = (toggleRef: React.Ref<MenuToggleElement>) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export interface GeneratorEdgeOptions {
showAnimations?: boolean;
showTags?: boolean;
terminalTypes?: boolean;
freezeEdgeDuringNodeDrag?: boolean;
}

const createNode = (index: number): NodeModel => ({
Expand Down
52 changes: 49 additions & 3 deletions packages/module/src/components/edges/DefaultEdge.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useLayoutEffect } from 'react';
import { useLayoutEffect, useRef } from 'react';
import { observer } from 'mobx-react';
import {
Edge,
Expand Down Expand Up @@ -71,6 +71,8 @@ interface DefaultEdgeProps {
canDrop?: boolean;
/** Flag if the node is the current drop target */
dropTarget?: boolean;
/** Flag indicating if an edge endpoint is currently being dragged */
endpointDragging?: boolean;
/** Flag indicating if the element is selected. Part of WithSelectionProps */
selected?: boolean;
/** Function to call when the element should become selected (or deselected). Part of WithSelectionProps */
Expand All @@ -79,6 +81,13 @@ interface DefaultEdgeProps {
onContextMenu?: (e: React.MouseEvent) => void;
/** Flag indicating that the context menu for the edge is currently open */
contextMenuOpen?: boolean;
/**
* When true, the edge path and terminals stay visually fixed (last position) while
* an associated node is being dragged. When false or omitted, the edge updates
* during drag as before. Can be set per edge via this prop, element state, or
* in the model as edge data: `data: { freezeEdgeDuringNodeDrag: true }`.
*/
freezeEdgeDuringNodeDrag?: boolean;
}

type DefaultEdgeInnerProps = Omit<DefaultEdgeProps, 'element'> & { element: Edge };
Expand All @@ -92,6 +101,7 @@ const DefaultEdgeInner: React.FunctionComponent<DefaultEdgeInnerProps> = observe
dndDropRef,
canDrop,
dropTarget,
endpointDragging,
edgeStyle,
animationDuration,
onShowRemoveConnector,
Expand All @@ -111,12 +121,23 @@ const DefaultEdgeInner: React.FunctionComponent<DefaultEdgeInnerProps> = observe
className,
selected,
onSelect,
onContextMenu
onContextMenu,
freezeEdgeDuringNodeDrag
}) => {
const freezeDuringDrag =
freezeEdgeDuringNodeDrag ??
(element.getData() as { freezeEdgeDuringNodeDrag?: boolean } | undefined)?.freezeEdgeDuringNodeDrag ??
false;

const [hover, hoverRef] = useHover();
const edgeRef = useCombineRefs(hoverRef, dndDropRef);
const startPoint = element.getStartPoint();
const endPoint = element.getEndPoint();
const edgeDRef = useRef<string | null>(null);
const startPointRef = useRef<Point | null>(null);
const endPointRef = useRef<Point | null>(null);
const targetStartPointRef = useRef<Point | null>(null);
const targetEndPointRef = useRef<Point | null>(null);

useLayoutEffect(() => {
if (hover && !dragging) {
Expand Down Expand Up @@ -175,6 +196,23 @@ const DefaultEdgeInner: React.FunctionComponent<DefaultEdgeInnerProps> = observe
const tagScale = hover && !(detailsLevel === ScaleDetailsLevel.high) ? Math.max(1, 1 / scale) : 1;
const tagPositionScale = hover && !(detailsLevel === ScaleDetailsLevel.high) ? Math.min(1, scale) : 1;

const shouldFreeze = freezeDuringDrag && endpointDragging;

if (
!shouldFreeze ||
!edgeDRef.current ||
!startPointRef.current ||
!endPointRef.current ||
!targetStartPointRef.current ||
!targetEndPointRef.current
) {
edgeDRef.current = d;
startPointRef.current = bendpoints[0] || endPoint;
endPointRef.current = startPoint;
targetStartPointRef.current = bendpoints[bendpoints.length - 1] || startPoint;
targetEndPointRef.current = endPoint;
}

return (
<Layer id={dragging || hover ? TOP_LAYER : undefined}>
<g
Expand All @@ -190,7 +228,11 @@ const DefaultEdgeInner: React.FunctionComponent<DefaultEdgeInnerProps> = observe
onMouseEnter={onShowRemoveConnector}
onMouseLeave={onHideRemoveConnector}
/>
<path className={linkClassName} d={d} style={{ animationDuration: `${edgeAnimationDuration}s` }} />
<path
className={linkClassName}
d={edgeDRef.current}
style={{ animationDuration: `${edgeAnimationDuration}s` }}
/>
{showTag && (
<g transform={`scale(${hover ? tagScale : 1})`}>
<DefaultConnectorTag
Expand All @@ -211,6 +253,8 @@ const DefaultEdgeInner: React.FunctionComponent<DefaultEdgeInnerProps> = observe
terminalType={startTerminalType}
status={startTerminalStatus}
highlight={dragging || hover}
startPoint={shouldFreeze ? startPointRef.current : undefined}
endPoint={shouldFreeze ? endPointRef.current : undefined}
/>
<DefaultConnectorTerminal
className={endTerminalClass}
Expand All @@ -221,6 +265,8 @@ const DefaultEdgeInner: React.FunctionComponent<DefaultEdgeInnerProps> = observe
terminalType={endTerminalType}
status={endTerminalStatus}
highlight={dragging || hover}
startPoint={shouldFreeze ? targetStartPointRef.current : undefined}
endPoint={shouldFreeze ? targetEndPointRef.current : undefined}
/>
{children}
</g>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,17 +278,31 @@ const edgeDragSourceSpec = (
})
});

const edgeEndpointIsDragging = (monitor: any, props: EdgeComponentProps) => {
if (!monitor.isDragging()) {
return false;
}
if (monitor.getItemType() === NODE_DRAG_TYPE) {
return (
monitor.getItem().element.id === props.element.getSource().getId() ||
monitor.getItem().element.id === props.element.getTarget().getId()
);
}
return false;
};

const edgeDropTargetSpec: DropTargetSpec<any, any, { droppable: boolean; dropTarget: boolean; canDrop: boolean }, any> =
{
accept: [NODE_DRAG_TYPE],
canDrop: (item, monitor, props) =>
!!props &&
(props.element as Edge).getSource().getId() !== item.id &&
(props.element as Edge).getTarget().getId() !== item.id,
collect: (monitor) => ({
collect: (monitor, props) => ({
droppable: monitor.isDragging(),
dropTarget: monitor.isOver(),
canDrop: monitor.canDrop()
canDrop: monitor.canDrop(),
endpointDragging: edgeEndpointIsDragging(monitor, { element: props.element as Edge })
})
};

Expand Down
Loading