diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/edges/InvocationCollapsedEdge.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/edges/InvocationCollapsedEdge.tsx index 2e2fb31154..0d7e7b7d5e 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/edges/InvocationCollapsedEdge.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/edges/InvocationCollapsedEdge.tsx @@ -2,13 +2,13 @@ import { Badge, Flex } from '@invoke-ai/ui-library'; import { useStore } from '@nanostores/react'; import { useAppSelector } from 'app/store/storeHooks'; import { useChakraThemeTokens } from 'common/hooks/useChakraThemeTokens'; +import { getEdgeStyles } from 'features/nodes/components/flow/edges/util/getEdgeColor'; +import { makeEdgeSelector } from 'features/nodes/components/flow/edges/util/makeEdgeSelector'; import { $templates } from 'features/nodes/store/nodesSlice'; import { memo, useMemo } from 'react'; import type { EdgeProps } from 'reactflow'; import { BaseEdge, EdgeLabelRenderer, getBezierPath } from 'reactflow'; -import { makeEdgeSelector } from './util/makeEdgeSelector'; - const InvocationCollapsedEdge = ({ sourceX, sourceY, @@ -18,19 +18,19 @@ const InvocationCollapsedEdge = ({ targetPosition, markerEnd, data, - selected, + selected = false, source, - target, sourceHandleId, + target, targetHandleId, }: EdgeProps<{ count: number }>) => { const templates = useStore($templates); const selector = useMemo( - () => makeEdgeSelector(templates, source, sourceHandleId, target, targetHandleId, selected), - [templates, selected, source, sourceHandleId, target, targetHandleId] + () => makeEdgeSelector(templates, source, sourceHandleId, target, targetHandleId), + [templates, source, sourceHandleId, target, targetHandleId] ); - const { isSelected, shouldAnimate } = useAppSelector(selector); + const { shouldAnimateEdges, areConnectedNodesSelected } = useAppSelector(selector); const [edgePath, labelX, labelY] = getBezierPath({ sourceX, @@ -44,14 +44,8 @@ const InvocationCollapsedEdge = ({ const { base500 } = useChakraThemeTokens(); const edgeStyles = useMemo( - () => ({ - strokeWidth: isSelected ? 3 : 2, - stroke: base500, - opacity: isSelected ? 0.8 : 0.5, - animation: shouldAnimate ? 'dashdraw 0.5s linear infinite' : undefined, - strokeDasharray: shouldAnimate ? 5 : 'none', - }), - [base500, isSelected, shouldAnimate] + () => getEdgeStyles(base500, selected, shouldAnimateEdges, areConnectedNodesSelected), + [areConnectedNodesSelected, base500, selected, shouldAnimateEdges] ); return ( @@ -60,11 +54,15 @@ const InvocationCollapsedEdge = ({ {data?.count && data.count > 1 && ( - + {data.count} diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/edges/InvocationDefaultEdge.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/edges/InvocationDefaultEdge.tsx index 2e4340975b..5a27e974e5 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/edges/InvocationDefaultEdge.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/edges/InvocationDefaultEdge.tsx @@ -1,8 +1,8 @@ import { Flex, Text } from '@invoke-ai/ui-library'; import { useStore } from '@nanostores/react'; import { useAppSelector } from 'app/store/storeHooks'; +import { getEdgeStyles } from 'features/nodes/components/flow/edges/util/getEdgeColor'; import { $templates } from 'features/nodes/store/nodesSlice'; -import type { CSSProperties } from 'react'; import { memo, useMemo } from 'react'; import type { EdgeProps } from 'reactflow'; import { BaseEdge, EdgeLabelRenderer, getBezierPath } from 'reactflow'; @@ -17,7 +17,7 @@ const InvocationDefaultEdge = ({ sourcePosition, targetPosition, markerEnd, - selected, + selected = false, source, target, sourceHandleId, @@ -25,11 +25,11 @@ const InvocationDefaultEdge = ({ }: EdgeProps) => { const templates = useStore($templates); const selector = useMemo( - () => makeEdgeSelector(templates, source, sourceHandleId, target, targetHandleId, selected), - [templates, source, sourceHandleId, target, targetHandleId, selected] + () => makeEdgeSelector(templates, source, sourceHandleId, target, targetHandleId), + [templates, source, sourceHandleId, target, targetHandleId] ); - const { isSelected, shouldAnimate, stroke, label } = useAppSelector(selector); + const { shouldAnimateEdges, areConnectedNodesSelected, stroke, label } = useAppSelector(selector); const shouldShowEdgeLabels = useAppSelector((s) => s.workflowSettings.shouldShowEdgeLabels); const [edgePath, labelX, labelY] = getBezierPath({ @@ -41,15 +41,9 @@ const InvocationDefaultEdge = ({ targetPosition, }); - const edgeStyles = useMemo( - () => ({ - strokeWidth: isSelected ? 3 : 2, - stroke, - opacity: isSelected ? 0.8 : 0.5, - animation: shouldAnimate ? 'dashdraw 0.5s linear infinite' : undefined, - strokeDasharray: shouldAnimate ? 5 : 'none', - }), - [isSelected, shouldAnimate, stroke] + const edgeStyles = useMemo( + () => getEdgeStyles(stroke, selected, shouldAnimateEdges, areConnectedNodesSelected), + [areConnectedNodesSelected, stroke, selected, shouldAnimateEdges] ); return ( @@ -65,13 +59,13 @@ const InvocationDefaultEdge = ({ bg="base.800" borderRadius="base" borderWidth={1} - borderColor={isSelected ? 'undefined' : 'transparent'} - opacity={isSelected ? 1 : 0.5} + borderColor={selected ? 'undefined' : 'transparent'} + opacity={selected ? 1 : 0.5} py={1} px={3} shadow="md" > - + {label} diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/edges/util/getEdgeColor.ts b/invokeai/frontend/web/src/features/nodes/components/flow/edges/util/getEdgeColor.ts index e7fa43015b..91c834011c 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/edges/util/getEdgeColor.ts +++ b/invokeai/frontend/web/src/features/nodes/components/flow/edges/util/getEdgeColor.ts @@ -1,6 +1,7 @@ import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar'; import { FIELD_COLORS } from 'features/nodes/types/constants'; import type { FieldType } from 'features/nodes/types/field'; +import type { CSSProperties } from 'react'; export const getFieldColor = (fieldType: FieldType | null): string => { if (!fieldType) { @@ -10,3 +11,16 @@ export const getFieldColor = (fieldType: FieldType | null): string => { return color ? colorTokenToCssVar(color) : colorTokenToCssVar('base.500'); }; + +export const getEdgeStyles = ( + stroke: string, + selected: boolean, + shouldAnimateEdges: boolean, + areConnectedNodesSelected: boolean +): CSSProperties => ({ + strokeWidth: selected ? 3 : areConnectedNodesSelected ? 2 : 1, + stroke, + opacity: selected ? 1 : 0.5, + animation: shouldAnimateEdges ? 'dashdraw 0.5s linear infinite' : undefined, + strokeDasharray: selected || areConnectedNodesSelected ? 5 : 'none', +}); diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/edges/util/makeEdgeSelector.ts b/invokeai/frontend/web/src/features/nodes/components/flow/edges/util/makeEdgeSelector.ts index 87ef8eb629..9c67728722 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/edges/util/makeEdgeSelector.ts +++ b/invokeai/frontend/web/src/features/nodes/components/flow/edges/util/makeEdgeSelector.ts @@ -1,5 +1,6 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar'; +import { deepClone } from 'common/util/deepClone'; import { selectNodesSlice } from 'features/nodes/store/nodesSlice'; import type { Templates } from 'features/nodes/store/types'; import { selectWorkflowSettingsSlice } from 'features/nodes/store/workflowSettingsSlice'; @@ -8,8 +9,8 @@ import { isInvocationNode } from 'features/nodes/types/invocation'; import { getFieldColor } from './getEdgeColor'; const defaultReturnValue = { - isSelected: false, - shouldAnimate: false, + areConnectedNodesSelected: false, + shouldAnimateEdges: false, stroke: colorTokenToCssVar('base.500'), label: '', }; @@ -19,21 +20,27 @@ export const makeEdgeSelector = ( source: string, sourceHandleId: string | null | undefined, target: string, - targetHandleId: string | null | undefined, - selected?: boolean + targetHandleId: string | null | undefined ) => createMemoizedSelector( selectNodesSlice, selectWorkflowSettingsSlice, - (nodes, workflowSettings): { isSelected: boolean; shouldAnimate: boolean; stroke: string; label: string } => { + ( + nodes, + workflowSettings + ): { areConnectedNodesSelected: boolean; shouldAnimateEdges: boolean; stroke: string; label: string } => { + const { shouldAnimateEdges, shouldColorEdges } = workflowSettings; const sourceNode = nodes.nodes.find((node) => node.id === source); const targetNode = nodes.nodes.find((node) => node.id === target); + const returnValue = deepClone(defaultReturnValue); + returnValue.shouldAnimateEdges = shouldAnimateEdges; + const isInvocationToInvocationEdge = isInvocationNode(sourceNode) && isInvocationNode(targetNode); - const isSelected = Boolean(sourceNode?.selected || targetNode?.selected || selected); + returnValue.areConnectedNodesSelected = Boolean(sourceNode?.selected || targetNode?.selected); if (!sourceNode || !sourceHandleId || !targetNode || !targetHandleId) { - return defaultReturnValue; + return returnValue; } const sourceNodeTemplate = templates[sourceNode.data.type]; @@ -42,16 +49,10 @@ export const makeEdgeSelector = ( const outputFieldTemplate = sourceNodeTemplate?.outputs[sourceHandleId]; const sourceType = isInvocationToInvocationEdge ? outputFieldTemplate?.type : undefined; - const stroke = - sourceType && workflowSettings.shouldColorEdges ? getFieldColor(sourceType) : colorTokenToCssVar('base.500'); + returnValue.stroke = sourceType && shouldColorEdges ? getFieldColor(sourceType) : colorTokenToCssVar('base.500'); - const label = `${sourceNodeTemplate?.title || sourceNode.data?.label} -> ${targetNodeTemplate?.title || targetNode.data?.label}`; + returnValue.label = `${sourceNodeTemplate?.title || sourceNode.data?.label} -> ${targetNodeTemplate?.title || targetNode.data?.label}`; - return { - isSelected, - shouldAnimate: workflowSettings.shouldAnimateEdges && isSelected, - stroke, - label, - }; + return returnValue; } );