feat: add generic handle that inherits colors for multiple types (#5414)

* Fixed HandleTooltipComponent to use the type instead of passed color

* Fixed handle colors to use the connected edge color

* Changed color of generic handle

* Fixed edge color when clicking for generic handles

* Updated sidebar filter to contain more than one type

* fixed gradient generalBugs
This commit is contained in:
Lucas Oliveira 2024-12-23 19:18:11 -03:00 • committed by GitHub
commit ca8f3cad62
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 75 additions and 81 deletions

View file

@ -1,5 +1,6 @@
import { convertTestName } from "@/components/common/storeCardComponent/utils/convert-test-name"; import { convertTestName } from "@/components/common/storeCardComponent/utils/convert-test-name";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { nodeColorsName } from "@/utils/styleUtils";
export default function HandleTooltipComponent({ export default function HandleTooltipComponent({
isInput, isInput,
@ -7,8 +8,6 @@ export default function HandleTooltipComponent({
isConnecting, isConnecting,
isCompatible, isCompatible,
isSameNode, isSameNode,
accentColorName,
accentForegroundColorName,
left, left,
}: { }: {
isInput: boolean; isInput: boolean;
@ -16,8 +15,6 @@ export default function HandleTooltipComponent({
isConnecting: boolean; isConnecting: boolean;
isCompatible: boolean; isCompatible: boolean;
isSameNode: boolean; isSameNode: boolean;
accentColorName: string;
accentForegroundColorName: string;
left: boolean; left: boolean;
}) { }) {
const tooltips = tooltipTitle.split("\n"); const tooltips = tooltipTitle.split("\n");
@ -51,11 +48,11 @@ export default function HandleTooltipComponent({
key={`${index}-${word.toLowerCase()}`} key={`${index}-${word.toLowerCase()}`}
style={{ style={{
backgroundColor: left backgroundColor: left
? `hsl(var(--${accentColorName}))` ? `hsl(var(--datatype-${nodeColorsName[word]}))`
: `hsl(var(--${accentColorName}-foreground))`, : `hsl(var(--datatype-${nodeColorsName[word]}-foreground))`,
color: left color: left
? `hsl(var(--${accentForegroundColorName}))` ? `hsl(var(--datatype-${nodeColorsName[word]}-foreground))`
: `hsl(var(--${accentColorName}))`, : `hsl(var(--datatype-${nodeColorsName[word]}))`,
}} }}
data-testid={`${isInput ? "input" : "output"}-tooltip-${convertTestName(word)}`} data-testid={`${isInput ? "input" : "output"}-tooltip-${convertTestName(word)}`}
> >

View file

@ -1,7 +1,8 @@
import { useDarkStore } from "@/stores/darkStore"; import { useDarkStore } from "@/stores/darkStore";
import useFlowStore from "@/stores/flowStore"; import useFlowStore from "@/stores/flowStore";
import { nodeColorsName } from "@/utils/styleUtils";
import { Connection, Handle, Position } from "@xyflow/react"; import { Connection, Handle, Position } from "@xyflow/react";
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { memo, useCallback, useEffect, useMemo, useState } from "react";
import ShadTooltip from "../../../../components/common/shadTooltipComponent"; import ShadTooltip from "../../../../components/common/shadTooltipComponent";
import { import {
isValidConnection, isValidConnection,
@ -31,7 +32,6 @@ const HandleContent = memo(function HandleContent({
showNode, showNode,
left, left,
nodeId, nodeId,
colorName,
}: { }: {
isNullHandle: boolean; isNullHandle: boolean;
handleColor: string; handleColor: string;
@ -43,7 +43,6 @@ const HandleContent = memo(function HandleContent({
showNode: boolean; showNode: boolean;
left: boolean; left: boolean;
nodeId: string; nodeId: string;
colorName?: string[];
}) { }) {
// Restore animation effect // Restore animation effect
useEffect(() => { useEffect(() => {
@ -51,36 +50,36 @@ const HandleContent = memo(function HandleContent({
const styleSheet = document.createElement("style"); const styleSheet = document.createElement("style");
styleSheet.id = `pulse-${nodeId}`; styleSheet.id = `pulse-${nodeId}`;
styleSheet.textContent = ` styleSheet.textContent = `
@keyframes pulseNeon { @keyframes pulseNeon-${nodeId} {
0% { 0% {
box-shadow: 0 0 0 2px hsl(var(--node-ring)), box-shadow: 0 0 0 2px hsl(var(--node-ring)),
0 0 2px hsl(var(--datatype-${colorName?.[0]})), 0 0 2px ${handleColor},
0 0 4px hsl(var(--datatype-${colorName?.[0]})), 0 0 4px ${handleColor},
0 0 6px hsl(var(--datatype-${colorName?.[0]})), 0 0 6px ${handleColor},
0 0 8px hsl(var(--datatype-${colorName?.[0]})), 0 0 8px ${handleColor},
0 0 10px hsl(var(--datatype-${colorName?.[0]})), 0 0 10px ${handleColor},
0 0 15px hsl(var(--datatype-${colorName?.[0]})), 0 0 15px ${handleColor},
0 0 20px hsl(var(--datatype-${colorName?.[0]})); 0 0 20px ${handleColor};
} }
50% { 50% {
box-shadow: 0 0 0 2px hsl(var(--node-ring)), box-shadow: 0 0 0 2px hsl(var(--node-ring)),
0 0 4px hsl(var(--datatype-${colorName?.[0]})), 0 0 4px ${handleColor},
0 0 8px hsl(var(--datatype-${colorName?.[0]})), 0 0 8px ${handleColor},
0 0 12px hsl(var(--datatype-${colorName?.[0]})), 0 0 12px ${handleColor},
0 0 16px hsl(var(--datatype-${colorName?.[0]})), 0 0 16px ${handleColor},
0 0 20px hsl(var(--datatype-${colorName?.[0]})), 0 0 20px ${handleColor},
0 0 25px hsl(var(--datatype-${colorName?.[0]})), 0 0 25px ${handleColor},
0 0 30px hsl(var(--datatype-${colorName?.[0]})); 0 0 30px ${handleColor};
} }
100% { 100% {
box-shadow: 0 0 0 2px hsl(var(--node-ring)), box-shadow: 0 0 0 2px hsl(var(--node-ring)),
0 0 2px hsl(var(--datatype-${colorName?.[0]})), 0 0 2px ${handleColor},
0 0 4px hsl(var(--datatype-${colorName?.[0]})), 0 0 4px ${handleColor},
0 0 6px hsl(var(--datatype-${colorName?.[0]})), 0 0 6px ${handleColor},
0 0 8px hsl(var(--datatype-${colorName?.[0]})), 0 0 8px ${handleColor},
0 0 10px hsl(var(--datatype-${colorName?.[0]})), 0 0 10px ${handleColor},
0 0 15px hsl(var(--datatype-${colorName?.[0]})), 0 0 15px ${handleColor},
0 0 20px hsl(var(--datatype-${colorName?.[0]})); 0 0 20px ${handleColor};
} }
} }
`; `;
@ -93,12 +92,12 @@ const HandleContent = memo(function HandleContent({
} }
}; };
} }
}, [isHovered, openHandle, isNullHandle, nodeId, colorName]); }, [isHovered, openHandle, isNullHandle, nodeId, handleColor]);
const getNeonShadow = useCallback( const getNeonShadow = useCallback(
(color: string, isActive: boolean) => { (color: string, isActive: boolean) => {
if (isNullHandle) return "none"; if (isNullHandle) return "none";
if (!isActive) return `0 0 0 3px hsl(var(--${color}))`; if (!isActive) return `0 0 0 3px ${color}`;
return [ return [
"0 0 0 1px hsl(var(--border))", "0 0 0 1px hsl(var(--border))",
`0 0 2px ${color}`, `0 0 2px ${color}`,
@ -125,7 +124,7 @@ const HandleContent = memo(function HandleContent({
), ),
animation: animation:
(isHovered || openHandle) && !isNullHandle (isHovered || openHandle) && !isNullHandle
? "pulseNeon 1.1s ease-in-out infinite" ? `pulseNeon-${nodeId} 1.1s ease-in-out infinite`
: "none", : "none",
border: isNullHandle ? "2px solid hsl(var(--muted))" : "none", border: isNullHandle ? "2px solid hsl(var(--muted))" : "none",
}), }),
@ -181,10 +180,6 @@ const HandleRenderComponent = memo(function HandleRenderComponent({
nodeId: string; nodeId: string;
colorName?: string[]; colorName?: string[];
}) { }) {
const handleColorName = colorName?.[0] ?? "";
const accentColorName = `datatype-${handleColorName}`;
const accentForegroundColorName = `${accentColorName}-foreground`;
const [isHovered, setIsHovered] = useState(false); const [isHovered, setIsHovered] = useState(false);
const [openTooltip, setOpenTooltip] = useState(false); const [openTooltip, setOpenTooltip] = useState(false);
@ -238,6 +233,7 @@ const HandleRenderComponent = memo(function HandleRenderComponent({
currentFilter, currentFilter,
isNullHandle, isNullHandle,
handleColor, handleColor,
accentForegroundColorName,
} = useMemo(() => { } = useMemo(() => {
const sameDraggingNode = const sameDraggingNode =
(!left ? handleDragging?.target : handleDragging?.source) === nodeId; (!left ? handleDragging?.target : handleDragging?.source) === nodeId;
@ -272,6 +268,38 @@ const HandleRenderComponent = memo(function HandleRenderComponent({
const openHandle = filterOpenHandle || draggingOpenHandle; const openHandle = filterOpenHandle || draggingOpenHandle;
const filterPresent = handleDragging || filterType; const filterPresent = handleDragging || filterType;
const connectedEdge = edges.find(
(edge) => edge.target === nodeId && edge.targetHandle === myId,
);
const connectedColor =
nodeColorsName[connectedEdge?.data?.sourceHandle?.output_types[0]] ||
"gray";
const isNullHandle =
filterPresent && !(openHandle || ownDraggingHandle || ownFilterHandle);
const handleColorName = connectedEdge
? connectedColor
: colorName!.length > 1
? "secondary-foreground"
: "datatype-" + colorName![0];
const handleColor = isNullHandle
? dark
? "hsl(var(--accent-gray))"
: "hsl(var(--accent-gray-foreground)"
: connectedEdge
? "hsl(var(--datatype-" + connectedColor + "))"
: colorName!.length > 1
? "hsl(var(--secondary-foreground))"
: "hsl(var(--datatype-" + colorName![0] + "))";
const accentForegroundColorName = connectedEdge
? "hsl(var(--datatype-" + connectedColor + "-foreground))"
: colorName!.length > 1
? "hsl(var(--input))"
: "hsl(var(--datatype-" + colorName![0] + "-foreground))";
const currentFilter = left const currentFilter = left
? { ? {
targetHandle: myId, targetHandle: myId,
@ -290,31 +318,10 @@ const HandleRenderComponent = memo(function HandleRenderComponent({
color: handleColorName, color: handleColorName,
}; };
const isNullHandle =
filterPresent && !(openHandle || ownDraggingHandle || ownFilterHandle);
const handleColor = isNullHandle
? dark
? "conic-gradient(hsl(var(--accent-gray)) 0deg 360deg)"
: "conic-gradient(hsl(var(--accent-gray-foreground)) 0deg 360deg)"
: "conic-gradient(" +
colorName!
.concat(colorName![0])
.map(
(color, index) =>
`hsl(var(--datatype-${color}))` +
" " +
((360 / colors.length) * index - 360 / (colors.length * 4)) +
"deg " +
((360 / colors.length) * index + 360 / (colors.length * 4)) +
"deg",
)
.join(" ,") +
")";
return { return {
sameNode: sameDraggingNode || sameFilterNode, sameNode: sameDraggingNode || sameFilterNode,
ownHandle: ownDraggingHandle || ownFilterHandle, ownHandle: ownDraggingHandle || ownFilterHandle,
accentForegroundColorName,
openHandle, openHandle,
filterOpenHandle, filterOpenHandle,
filterPresent, filterPresent,
@ -335,7 +342,6 @@ const HandleRenderComponent = memo(function HandleRenderComponent({
colors, colors,
colorName, colorName,
tooltipTitle, tooltipTitle,
handleColorName,
]); ]);
const handleMouseDown = useCallback( const handleMouseDown = useCallback(
@ -402,8 +408,6 @@ const HandleRenderComponent = memo(function HandleRenderComponent({
isConnecting={!!filterPresent && !ownHandle} isConnecting={!!filterPresent && !ownHandle}
isCompatible={openHandle} isCompatible={openHandle}
isSameNode={sameNode && !ownHandle} isSameNode={sameNode && !ownHandle}
accentColorName={accentColorName}
accentForegroundColorName={accentForegroundColorName}
left={left} left={left}
/> />
} }
@ -442,7 +446,6 @@ const HandleRenderComponent = memo(function HandleRenderComponent({
showNode={showNode} showNode={showNode}
left={left} left={left}
nodeId={nodeId} nodeId={nodeId}
colorName={colorName}
/> />
</Handle> </Handle>
</ShadTooltip> </ShadTooltip>

View file

@ -493,7 +493,7 @@ export default function Page({ view }: { view?: boolean }): JSX.Element {
const handleEdgeClick = (event, edge) => { const handleEdgeClick = (event, edge) => {
const color = const color =
nodeColorsName[edge?.data?.targetHandle?.inputTypes[0]] || "cyan"; nodeColorsName[edge?.data?.sourceHandle?.output_types[0]] || "cyan";
const accentColor = `hsl(var(--datatype-${color}))`; const accentColor = `hsl(var(--datatype-${color}))`;
reactFlowWrapper.current?.style.setProperty("--selected", accentColor); reactFlowWrapper.current?.style.setProperty("--selected", accentColor);

View file

@ -13,6 +13,8 @@ export function SidebarFilterComponent({
color: string; color: string;
resetFilters: () => void; resetFilters: () => void;
}) { }) {
const tooltips = type.split("\n");
const plural = tooltips.length > 1 ? "s" : "";
return ( return (
<div <div
className={`mb-0.5 flex w-full items-center justify-between rounded border p-2 text-sm text-foreground`} className={`mb-0.5 flex w-full items-center justify-between rounded border p-2 text-sm text-foreground`}
@ -26,9 +28,10 @@ export function SidebarFilterComponent({
className={`h-4 w-4 shrink-0 stroke-2`} className={`h-4 w-4 shrink-0 stroke-2`}
/> />
<div className="flex flex-1"> <div className="flex flex-1">
{isInput ? "Input" : "Output"}:{" "} {isInput ? "Input" : "Output"}
{plural}:{" "}
<div className="w-[5.7rem] flex-1 overflow-hidden truncate pl-1"> <div className="w-[5.7rem] flex-1 overflow-hidden truncate pl-1">
{type} {tooltips.join(", ")}
</div> </div>
</div> </div>
</div> </div>

View file

@ -113,19 +113,13 @@ test(
const hasGradientUnlocked = await unlockedHandle?.evaluate((el) => { const hasGradientUnlocked = await unlockedHandle?.evaluate((el) => {
const style = window.getComputedStyle(el); const style = window.getComputedStyle(el);
return ( return style.backgroundColor === "rgb(79, 70, 229)";
style.backgroundImage.includes("conic-gradient") &&
style.backgroundImage.includes("rgb(79, 70, 229)")
);
}); });
const secondHasGradientUnlocked = await secondUnlockedHandle?.evaluate( const secondHasGradientUnlocked = await secondUnlockedHandle?.evaluate(
(el) => { (el) => {
const style = window.getComputedStyle(el); const style = window.getComputedStyle(el);
return ( return style.backgroundColor === "rgb(79, 70, 229)";
style.backgroundImage.includes("conic-gradient") &&
style.backgroundImage.includes("rgb(79, 70, 229)")
);
}, },
); );
@ -137,10 +131,7 @@ test(
const fourthHasGradientUnlocked = await fourthUnlockedHandle?.evaluate( const fourthHasGradientUnlocked = await fourthUnlockedHandle?.evaluate(
(el) => { (el) => {
const style = window.getComputedStyle(el); const style = window.getComputedStyle(el);
return ( return style.backgroundColor === "rgb(79, 70, 229)";
style.backgroundImage.includes("conic-gradient") &&
style.backgroundImage.includes("rgb(79, 70, 229)")
);
}, },
); );