mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
fix(ui): edge styling
This commit is contained in:
@ -2,13 +2,13 @@ import { Badge, Flex } from '@invoke-ai/ui-library';
|
|||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { useChakraThemeTokens } from 'common/hooks/useChakraThemeTokens';
|
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 { $templates } from 'features/nodes/store/nodesSlice';
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import type { EdgeProps } from 'reactflow';
|
import type { EdgeProps } from 'reactflow';
|
||||||
import { BaseEdge, EdgeLabelRenderer, getBezierPath } from 'reactflow';
|
import { BaseEdge, EdgeLabelRenderer, getBezierPath } from 'reactflow';
|
||||||
|
|
||||||
import { makeEdgeSelector } from './util/makeEdgeSelector';
|
|
||||||
|
|
||||||
const InvocationCollapsedEdge = ({
|
const InvocationCollapsedEdge = ({
|
||||||
sourceX,
|
sourceX,
|
||||||
sourceY,
|
sourceY,
|
||||||
@ -18,19 +18,19 @@ const InvocationCollapsedEdge = ({
|
|||||||
targetPosition,
|
targetPosition,
|
||||||
markerEnd,
|
markerEnd,
|
||||||
data,
|
data,
|
||||||
selected,
|
selected = false,
|
||||||
source,
|
source,
|
||||||
target,
|
|
||||||
sourceHandleId,
|
sourceHandleId,
|
||||||
|
target,
|
||||||
targetHandleId,
|
targetHandleId,
|
||||||
}: EdgeProps<{ count: number }>) => {
|
}: EdgeProps<{ count: number }>) => {
|
||||||
const templates = useStore($templates);
|
const templates = useStore($templates);
|
||||||
const selector = useMemo(
|
const selector = useMemo(
|
||||||
() => makeEdgeSelector(templates, source, sourceHandleId, target, targetHandleId, selected),
|
() => makeEdgeSelector(templates, source, sourceHandleId, target, targetHandleId),
|
||||||
[templates, selected, source, sourceHandleId, target, targetHandleId]
|
[templates, source, sourceHandleId, target, targetHandleId]
|
||||||
);
|
);
|
||||||
|
|
||||||
const { isSelected, shouldAnimate } = useAppSelector(selector);
|
const { shouldAnimateEdges, areConnectedNodesSelected } = useAppSelector(selector);
|
||||||
|
|
||||||
const [edgePath, labelX, labelY] = getBezierPath({
|
const [edgePath, labelX, labelY] = getBezierPath({
|
||||||
sourceX,
|
sourceX,
|
||||||
@ -44,14 +44,8 @@ const InvocationCollapsedEdge = ({
|
|||||||
const { base500 } = useChakraThemeTokens();
|
const { base500 } = useChakraThemeTokens();
|
||||||
|
|
||||||
const edgeStyles = useMemo(
|
const edgeStyles = useMemo(
|
||||||
() => ({
|
() => getEdgeStyles(base500, selected, shouldAnimateEdges, areConnectedNodesSelected),
|
||||||
strokeWidth: isSelected ? 3 : 2,
|
[areConnectedNodesSelected, base500, selected, shouldAnimateEdges]
|
||||||
stroke: base500,
|
|
||||||
opacity: isSelected ? 0.8 : 0.5,
|
|
||||||
animation: shouldAnimate ? 'dashdraw 0.5s linear infinite' : undefined,
|
|
||||||
strokeDasharray: shouldAnimate ? 5 : 'none',
|
|
||||||
}),
|
|
||||||
[base500, isSelected, shouldAnimate]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -60,11 +54,15 @@ const InvocationCollapsedEdge = ({
|
|||||||
{data?.count && data.count > 1 && (
|
{data?.count && data.count > 1 && (
|
||||||
<EdgeLabelRenderer>
|
<EdgeLabelRenderer>
|
||||||
<Flex
|
<Flex
|
||||||
|
data-testid="asdfasdfasdf"
|
||||||
position="absolute"
|
position="absolute"
|
||||||
transform={`translate(-50%, -50%) translate(${labelX}px,${labelY}px)`}
|
transform={`translate(-50%, -50%) translate(${labelX}px,${labelY}px)`}
|
||||||
className="nodrag nopan"
|
className="nodrag nopan"
|
||||||
|
// Unfortunately edge labels do not get the same zIndex treatment as edges do, so we need to manage this ourselves
|
||||||
|
// See: https://github.com/xyflow/xyflow/issues/3658
|
||||||
|
zIndex={1001}
|
||||||
>
|
>
|
||||||
<Badge variant="solid" bg="base.500" opacity={isSelected ? 0.8 : 0.5} boxShadow="base">
|
<Badge variant="solid" bg="base.500" opacity={selected ? 0.8 : 0.5} boxShadow="base">
|
||||||
{data.count}
|
{data.count}
|
||||||
</Badge>
|
</Badge>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Flex, Text } from '@invoke-ai/ui-library';
|
import { Flex, Text } from '@invoke-ai/ui-library';
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { getEdgeStyles } from 'features/nodes/components/flow/edges/util/getEdgeColor';
|
||||||
import { $templates } from 'features/nodes/store/nodesSlice';
|
import { $templates } from 'features/nodes/store/nodesSlice';
|
||||||
import type { CSSProperties } from 'react';
|
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import type { EdgeProps } from 'reactflow';
|
import type { EdgeProps } from 'reactflow';
|
||||||
import { BaseEdge, EdgeLabelRenderer, getBezierPath } from 'reactflow';
|
import { BaseEdge, EdgeLabelRenderer, getBezierPath } from 'reactflow';
|
||||||
@ -17,7 +17,7 @@ const InvocationDefaultEdge = ({
|
|||||||
sourcePosition,
|
sourcePosition,
|
||||||
targetPosition,
|
targetPosition,
|
||||||
markerEnd,
|
markerEnd,
|
||||||
selected,
|
selected = false,
|
||||||
source,
|
source,
|
||||||
target,
|
target,
|
||||||
sourceHandleId,
|
sourceHandleId,
|
||||||
@ -25,11 +25,11 @@ const InvocationDefaultEdge = ({
|
|||||||
}: EdgeProps) => {
|
}: EdgeProps) => {
|
||||||
const templates = useStore($templates);
|
const templates = useStore($templates);
|
||||||
const selector = useMemo(
|
const selector = useMemo(
|
||||||
() => makeEdgeSelector(templates, source, sourceHandleId, target, targetHandleId, selected),
|
() => makeEdgeSelector(templates, source, sourceHandleId, target, targetHandleId),
|
||||||
[templates, source, sourceHandleId, target, targetHandleId, selected]
|
[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 shouldShowEdgeLabels = useAppSelector((s) => s.workflowSettings.shouldShowEdgeLabels);
|
||||||
|
|
||||||
const [edgePath, labelX, labelY] = getBezierPath({
|
const [edgePath, labelX, labelY] = getBezierPath({
|
||||||
@ -41,15 +41,9 @@ const InvocationDefaultEdge = ({
|
|||||||
targetPosition,
|
targetPosition,
|
||||||
});
|
});
|
||||||
|
|
||||||
const edgeStyles = useMemo<CSSProperties>(
|
const edgeStyles = useMemo(
|
||||||
() => ({
|
() => getEdgeStyles(stroke, selected, shouldAnimateEdges, areConnectedNodesSelected),
|
||||||
strokeWidth: isSelected ? 3 : 2,
|
[areConnectedNodesSelected, stroke, selected, shouldAnimateEdges]
|
||||||
stroke,
|
|
||||||
opacity: isSelected ? 0.8 : 0.5,
|
|
||||||
animation: shouldAnimate ? 'dashdraw 0.5s linear infinite' : undefined,
|
|
||||||
strokeDasharray: shouldAnimate ? 5 : 'none',
|
|
||||||
}),
|
|
||||||
[isSelected, shouldAnimate, stroke]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -65,13 +59,13 @@ const InvocationDefaultEdge = ({
|
|||||||
bg="base.800"
|
bg="base.800"
|
||||||
borderRadius="base"
|
borderRadius="base"
|
||||||
borderWidth={1}
|
borderWidth={1}
|
||||||
borderColor={isSelected ? 'undefined' : 'transparent'}
|
borderColor={selected ? 'undefined' : 'transparent'}
|
||||||
opacity={isSelected ? 1 : 0.5}
|
opacity={selected ? 1 : 0.5}
|
||||||
py={1}
|
py={1}
|
||||||
px={3}
|
px={3}
|
||||||
shadow="md"
|
shadow="md"
|
||||||
>
|
>
|
||||||
<Text size="sm" fontWeight="semibold" color={isSelected ? 'base.100' : 'base.300'}>
|
<Text size="sm" fontWeight="semibold" color={selected ? 'base.100' : 'base.300'}>
|
||||||
{label}
|
{label}
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
|
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
|
||||||
import { FIELD_COLORS } from 'features/nodes/types/constants';
|
import { FIELD_COLORS } from 'features/nodes/types/constants';
|
||||||
import type { FieldType } from 'features/nodes/types/field';
|
import type { FieldType } from 'features/nodes/types/field';
|
||||||
|
import type { CSSProperties } from 'react';
|
||||||
|
|
||||||
export const getFieldColor = (fieldType: FieldType | null): string => {
|
export const getFieldColor = (fieldType: FieldType | null): string => {
|
||||||
if (!fieldType) {
|
if (!fieldType) {
|
||||||
@ -10,3 +11,16 @@ export const getFieldColor = (fieldType: FieldType | null): string => {
|
|||||||
|
|
||||||
return color ? colorTokenToCssVar(color) : colorTokenToCssVar('base.500');
|
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',
|
||||||
|
});
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||||
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
|
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
|
||||||
|
import { deepClone } from 'common/util/deepClone';
|
||||||
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
|
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
|
||||||
import type { Templates } from 'features/nodes/store/types';
|
import type { Templates } from 'features/nodes/store/types';
|
||||||
import { selectWorkflowSettingsSlice } from 'features/nodes/store/workflowSettingsSlice';
|
import { selectWorkflowSettingsSlice } from 'features/nodes/store/workflowSettingsSlice';
|
||||||
@ -8,8 +9,8 @@ import { isInvocationNode } from 'features/nodes/types/invocation';
|
|||||||
import { getFieldColor } from './getEdgeColor';
|
import { getFieldColor } from './getEdgeColor';
|
||||||
|
|
||||||
const defaultReturnValue = {
|
const defaultReturnValue = {
|
||||||
isSelected: false,
|
areConnectedNodesSelected: false,
|
||||||
shouldAnimate: false,
|
shouldAnimateEdges: false,
|
||||||
stroke: colorTokenToCssVar('base.500'),
|
stroke: colorTokenToCssVar('base.500'),
|
||||||
label: '',
|
label: '',
|
||||||
};
|
};
|
||||||
@ -19,21 +20,27 @@ export const makeEdgeSelector = (
|
|||||||
source: string,
|
source: string,
|
||||||
sourceHandleId: string | null | undefined,
|
sourceHandleId: string | null | undefined,
|
||||||
target: string,
|
target: string,
|
||||||
targetHandleId: string | null | undefined,
|
targetHandleId: string | null | undefined
|
||||||
selected?: boolean
|
|
||||||
) =>
|
) =>
|
||||||
createMemoizedSelector(
|
createMemoizedSelector(
|
||||||
selectNodesSlice,
|
selectNodesSlice,
|
||||||
selectWorkflowSettingsSlice,
|
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 sourceNode = nodes.nodes.find((node) => node.id === source);
|
||||||
const targetNode = nodes.nodes.find((node) => node.id === target);
|
const targetNode = nodes.nodes.find((node) => node.id === target);
|
||||||
|
|
||||||
|
const returnValue = deepClone(defaultReturnValue);
|
||||||
|
returnValue.shouldAnimateEdges = shouldAnimateEdges;
|
||||||
|
|
||||||
const isInvocationToInvocationEdge = isInvocationNode(sourceNode) && isInvocationNode(targetNode);
|
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) {
|
if (!sourceNode || !sourceHandleId || !targetNode || !targetHandleId) {
|
||||||
return defaultReturnValue;
|
return returnValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sourceNodeTemplate = templates[sourceNode.data.type];
|
const sourceNodeTemplate = templates[sourceNode.data.type];
|
||||||
@ -42,16 +49,10 @@ export const makeEdgeSelector = (
|
|||||||
const outputFieldTemplate = sourceNodeTemplate?.outputs[sourceHandleId];
|
const outputFieldTemplate = sourceNodeTemplate?.outputs[sourceHandleId];
|
||||||
const sourceType = isInvocationToInvocationEdge ? outputFieldTemplate?.type : undefined;
|
const sourceType = isInvocationToInvocationEdge ? outputFieldTemplate?.type : undefined;
|
||||||
|
|
||||||
const stroke =
|
returnValue.stroke = sourceType && shouldColorEdges ? getFieldColor(sourceType) : colorTokenToCssVar('base.500');
|
||||||
sourceType && workflowSettings.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 {
|
return returnValue;
|
||||||
isSelected,
|
|
||||||
shouldAnimate: workflowSettings.shouldAnimateEdges && isSelected,
|
|
||||||
stroke,
|
|
||||||
label,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user