refactor(markers): optimze marker creation, pass ids closes #2188
This commit is contained in:
@@ -53,6 +53,7 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
||||
onEdgeUpdateEnd,
|
||||
markerEnd,
|
||||
markerStart,
|
||||
rfId,
|
||||
}: WrapEdgeProps): JSX.Element | null => {
|
||||
const [updating, setUpdating] = useState<boolean>(false);
|
||||
const { addSelectedEdges, connectionMode } = useStore(selector, shallow);
|
||||
@@ -120,8 +121,8 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
||||
|
||||
const onEdgeUpdaterMouseEnter = () => setUpdating(true);
|
||||
const onEdgeUpdaterMouseOut = () => setUpdating(false);
|
||||
const markerStartUrl = useMemo(() => `url(#${getMarkerId(markerStart)})`, [markerStart]);
|
||||
const markerEndUrl = useMemo(() => `url(#${getMarkerId(markerEnd)})`, [markerEnd]);
|
||||
const markerStartUrl = useMemo(() => `url(#${getMarkerId(markerStart, rfId)})`, [markerStart, rfId]);
|
||||
const markerEndUrl = useMemo(() => `url(#${getMarkerId(markerEnd, rfId)})`, [markerEnd, rfId]);
|
||||
|
||||
if (hidden) {
|
||||
return null;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { memo, useCallback } from 'react';
|
||||
|
||||
import { useStore } from '../../store';
|
||||
import { EdgeMarker, ReactFlowState } from '../../types';
|
||||
@@ -9,6 +9,7 @@ interface MarkerProps extends EdgeMarker {
|
||||
}
|
||||
interface MarkerDefinitionsProps {
|
||||
defaultColor: string;
|
||||
rfId?: string;
|
||||
}
|
||||
|
||||
const Marker = ({
|
||||
@@ -40,26 +41,36 @@ const Marker = ({
|
||||
);
|
||||
};
|
||||
|
||||
const edgesSelector = (s: ReactFlowState) => s.edges;
|
||||
|
||||
const MarkerDefinitions = ({ defaultColor }: MarkerDefinitionsProps) => {
|
||||
const edges = useStore(edgesSelector);
|
||||
const markers = useMemo(() => {
|
||||
const markerSelector =
|
||||
({ defaultColor, rfId }: { defaultColor: string; rfId?: string }) =>
|
||||
(s: ReactFlowState) => {
|
||||
const ids: string[] = [];
|
||||
|
||||
return edges.reduce<MarkerProps[]>((markers, edge) => {
|
||||
[edge.markerStart, edge.markerEnd].forEach((marker) => {
|
||||
if (marker && typeof marker === 'object') {
|
||||
const markerId = getMarkerId(marker);
|
||||
if (!ids.includes(markerId)) {
|
||||
markers.push({ id: markerId, color: marker.color || defaultColor, ...marker });
|
||||
ids.push(markerId);
|
||||
return s.edges
|
||||
.reduce<MarkerProps[]>((markers, edge) => {
|
||||
[edge.markerStart, edge.markerEnd].forEach((marker) => {
|
||||
if (marker && typeof marker === 'object') {
|
||||
const markerId = getMarkerId(marker, rfId);
|
||||
if (!ids.includes(markerId)) {
|
||||
markers.push({ id: markerId, color: marker.color || defaultColor, ...marker });
|
||||
ids.push(markerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return markers.sort((a, b) => a.id.localeCompare(b.id));
|
||||
}, []);
|
||||
}, [edges, defaultColor]);
|
||||
});
|
||||
return markers;
|
||||
}, [])
|
||||
.sort((a, b) => a.id.localeCompare(b.id));
|
||||
};
|
||||
|
||||
// when you have multiple flows on a page and you hide the first one, the other ones have no markers anymore
|
||||
// when they do have markers with the same ids. To prevent this the user can pass a unique id to the react flow wrapper
|
||||
// that we can then use for creating our unique marker ids
|
||||
const MarkerDefinitions = ({ defaultColor, rfId }: MarkerDefinitionsProps) => {
|
||||
const markers = useStore(
|
||||
useCallback(markerSelector({ defaultColor, rfId }), [defaultColor, rfId]),
|
||||
// the id includes all marker options, so we just need to look at that part of the marker
|
||||
(a, b) => !(a.length !== b.length || a.some((m, i) => m.id !== b[i].id))
|
||||
);
|
||||
|
||||
return (
|
||||
<defs>
|
||||
@@ -82,4 +93,4 @@ const MarkerDefinitions = ({ defaultColor }: MarkerDefinitionsProps) => {
|
||||
|
||||
MarkerDefinitions.displayName = 'MarkerDefinitions';
|
||||
|
||||
export default MarkerDefinitions;
|
||||
export default memo(MarkerDefinitions);
|
||||
|
||||
@@ -40,6 +40,7 @@ interface EdgeRendererProps {
|
||||
edgeUpdaterRadius?: number;
|
||||
noPanClassName?: string;
|
||||
elevateEdgesOnSelect: boolean;
|
||||
rfId?: string;
|
||||
}
|
||||
|
||||
const selector = (s: ReactFlowState) => ({
|
||||
@@ -93,7 +94,7 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
|
||||
height={height}
|
||||
className="react-flow__edges react-flow__container"
|
||||
>
|
||||
{isMaxLevel && <MarkerDefinitions defaultColor={defaultMarkerColor} />}
|
||||
{isMaxLevel && <MarkerDefinitions defaultColor={defaultMarkerColor} rfId={props.rfId} />}
|
||||
<g>
|
||||
{edges.map((edge: Edge) => {
|
||||
const [sourceNodeRect, sourceHandleBounds, sourceIsValid] = getNodeData(nodeInternals, edge.source);
|
||||
@@ -184,6 +185,7 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
|
||||
onEdgeDoubleClick={props.onEdgeDoubleClick}
|
||||
onEdgeUpdateStart={props.onEdgeUpdateStart}
|
||||
onEdgeUpdateEnd={props.onEdgeUpdateEnd}
|
||||
rfId={props.rfId}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -84,6 +84,7 @@ const GraphView = ({
|
||||
noWheelClassName,
|
||||
noPanClassName,
|
||||
elevateEdgesOnSelect,
|
||||
id,
|
||||
}: GraphViewProps) => {
|
||||
useOnInitHandler(onInit);
|
||||
|
||||
@@ -139,6 +140,7 @@ const GraphView = ({
|
||||
defaultMarkerColor={defaultMarkerColor}
|
||||
noPanClassName={noPanClassName}
|
||||
elevateEdgesOnSelect={!!elevateEdgesOnSelect}
|
||||
rfId={id}
|
||||
/>
|
||||
<NodeRenderer
|
||||
nodeTypes={nodeTypes}
|
||||
|
||||
@@ -211,6 +211,7 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
||||
noWheelClassName={noWheelClassName}
|
||||
noPanClassName={noPanClassName}
|
||||
elevateEdgesOnSelect={elevateEdgesOnSelect}
|
||||
id={rest?.id}
|
||||
/>
|
||||
<StoreUpdater
|
||||
nodes={nodes}
|
||||
|
||||
@@ -121,6 +121,7 @@ export interface WrapEdgeProps<T = any> {
|
||||
onEdgeUpdateEnd?: (event: MouseEvent, edge: Edge, handleType: HandleType) => void;
|
||||
markerStart?: EdgeMarkerType;
|
||||
markerEnd?: EdgeMarkerType;
|
||||
rfId?: string;
|
||||
}
|
||||
|
||||
export interface EdgeSmoothStepProps<T = any> extends EdgeProps<T> {
|
||||
|
||||
+5
-3
@@ -30,7 +30,7 @@ export const getIncomers = <T = any, U extends T = T>(node: Node<U>, nodes: Node
|
||||
const getEdgeId = ({ source, sourceHandle, target, targetHandle }: Connection): string =>
|
||||
`reactflow__edge-${source}${sourceHandle || ''}-${target}${targetHandle || ''}`;
|
||||
|
||||
export const getMarkerId = (marker: EdgeMarkerType | undefined): string => {
|
||||
export const getMarkerId = (marker: EdgeMarkerType | undefined, rfId?: string): string => {
|
||||
if (typeof marker === 'undefined') {
|
||||
return '';
|
||||
}
|
||||
@@ -39,10 +39,12 @@ export const getMarkerId = (marker: EdgeMarkerType | undefined): string => {
|
||||
return marker;
|
||||
}
|
||||
|
||||
return Object.keys(marker)
|
||||
const idPrefix = rfId ? `${rfId}__` : '';
|
||||
|
||||
return `${idPrefix}${Object.keys(marker)
|
||||
.sort()
|
||||
.map((key: string) => `${key}=${(marker as any)[key]}`)
|
||||
.join('&');
|
||||
.join('&')}`;
|
||||
};
|
||||
|
||||
const connectionExists = (edge: Edge, edges: Edge[]) => {
|
||||
|
||||
Reference in New Issue
Block a user