refactor: handle rendering and filtering functionality (#3512)

This commit is contained in:
Lucas Oliveira 2024-09-13 17:27:30 -03:00 • committed by GitHub
commit 2f1f1808b9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 755 additions and 316 deletions

View file

@ -145,6 +145,10 @@ body {
transition-duration: 150ms; transition-duration: 150ms;
} }
.react-flow__edge.running .react-flow__edge-path {
stroke: var(--status-blue) !important;
}
.ag-react-container { .ag-react-container {
width: 100%; width: 100%;
height: 100%; height: 100%;

View file

@ -0,0 +1,33 @@
import useFlowStore from "@/stores/flowStore";
import { BaseEdge, EdgeProps, getBezierPath, Position } from "reactflow";
export function DefaultEdge({
sourceHandleId,
source,
sourceX,
sourceY,
target,
targetHandleId,
targetX,
targetY,
...props
}: EdgeProps) {
const getNode = useFlowStore((state) => state.getNode);
const sourceNode = getNode(source);
const targetNode = getNode(target);
const sourceXNew = (sourceNode?.position.x ?? 0) + (sourceNode?.width ?? 0);
const targetXNew = targetNode?.position.x ?? 0;
const [edgePath] = getBezierPath({
sourceX: sourceXNew,
sourceY,
sourcePosition: Position.Right,
targetPosition: Position.Left,
targetX: targetXNew,
targetY,
});
return <BaseEdge path={edgePath} {...props} />;
}

View file

@ -1,30 +1,65 @@
import { TOOLTIP_EMPTY } from "../../../../constants/constants"; import { convertTestName } from "@/components/storeCardComponent/utils/convert-test-name";
import useFlowStore from "../../../../stores/flowStore";
import { useTypesStore } from "../../../../stores/typesStore";
import { NodeType } from "../../../../types/flow";
import { groupByFamily } from "../../../../utils/utils";
import TooltipRenderComponent from "../tooltipRenderComponent";
export default function HandleTooltips({ export default function HandleTooltipComponent({
left, isInput,
tooltipTitle, tooltipTitle,
colors,
isConnecting,
isCompatible,
isSameNode,
}: { }: {
left: boolean; isInput: boolean;
nodes: NodeType[]; colors: string[];
tooltipTitle: string; tooltipTitle: string;
isConnecting: boolean;
isCompatible: boolean;
isSameNode: boolean;
}) { }) {
const myData = useTypesStore((state) => state.data); const tooltips = tooltipTitle.split("\n");
const nodes = useFlowStore((state) => state.nodes); const plural = tooltips.length > 1 ? "s" : "";
return (
let groupedObj: any = groupByFamily(myData, tooltipTitle!, left, nodes!); <div className="py-1.5 font-medium text-muted-foreground">
{isSameNode ? (
if (groupedObj && groupedObj.length > 0) { "Can't connect to the same node"
//@ts-ignore ) : (
return groupedObj.map((item, index) => { <div className="flex items-start gap-1.5">
return <TooltipRenderComponent index={index} item={item} left={left} />; {isConnecting ? (
}); isCompatible ? (
} else { <span>
//@ts-ignore <span className="font-semibold text-foreground">Connect</span>{" "}
return <span data-testid={`empty-tooltip-filter`}>{TOOLTIP_EMPTY}</span>; to
} </span>
) : (
<span>Incompatible with</span>
)
) : (
<span className="text-foreground">
{isInput ? `Input${plural}` : `Output${plural}`}:{" "}
</span>
)}
{tooltips.map((word, index) => (
<div
className="rounded-sm px-1.5 text-background"
style={{ backgroundColor: colors[index] }}
data-testid={`${isInput ? "input" : "output"}-tooltip-${convertTestName(word)}`}
>
{word}
</div>
))}
{isConnecting && <span>{isInput ? `input` : `output`}</span>}
</div>
)}
{!isConnecting && (
<div className="mt-2 flex flex-col gap-0.5 text-xs">
<div>
<b>Drag</b> to connect compatible {!isInput ? "inputs" : "outputs"}
</div>
<div>
<b>Select</b> to filter compatible {!isInput ? "inputs" : "outputs"}{" "}
and components
</div>
</div>
)}
</div>
);
} }

View file

@ -81,6 +81,7 @@ export default function NodeInputField({
setFilterEdge={setFilterEdge} setFilterEdge={setFilterEdge}
showNode={showNode} showNode={showNode}
testIdComplement={`${data?.type?.toLowerCase()}-${showNode ? "shownode" : "noshownode"}`} testIdComplement={`${data?.type?.toLowerCase()}-${showNode ? "shownode" : "noshownode"}`}
nodeId={data.id}
/> />
); );

View file

@ -110,6 +110,7 @@ export default function NodeOutputField({
id={id} id={id}
title={title} title={title}
edges={edges} edges={edges}
nodeId={data.id}
myData={myData} myData={myData}
colors={colors} colors={colors}
setFilterEdge={setFilterEdge} setFilterEdge={setFilterEdge}

View file

@ -1,3 +1,6 @@
import { useDarkStore } from "@/stores/darkStore";
import useFlowStore from "@/stores/flowStore";
import { useMemo, useState } from "react";
import { Handle, Position } from "reactflow"; import { Handle, Position } from "reactflow";
import ShadTooltip from "../../../../components/shadTooltipComponent"; import ShadTooltip from "../../../../components/shadTooltipComponent";
import { import {
@ -5,7 +8,7 @@ import {
scapedJSONStringfy, scapedJSONStringfy,
} from "../../../../utils/reactflowUtils"; } from "../../../../utils/reactflowUtils";
import { classNames, cn, groupByFamily } from "../../../../utils/utils"; import { classNames, cn, groupByFamily } from "../../../../utils/utils";
import HandleTooltips from "../HandleTooltipComponent"; import HandleTooltipComponent from "../HandleTooltipComponent";
export default function HandleRenderComponent({ export default function HandleRenderComponent({
left, left,
@ -20,6 +23,7 @@ export default function HandleRenderComponent({
setFilterEdge, setFilterEdge,
showNode, showNode,
testIdComplement, testIdComplement,
nodeId,
}: { }: {
left: boolean; left: boolean;
nodes: any; nodes: any;
@ -33,17 +37,168 @@ export default function HandleRenderComponent({
setFilterEdge: any; setFilterEdge: any;
showNode: any; showNode: any;
testIdComplement?: string; testIdComplement?: string;
nodeId: string;
}) { }) {
const setHandleDragging = useFlowStore((state) => state.setHandleDragging);
const setFilterType = useFlowStore((state) => state.setFilterType);
const handleDragging = useFlowStore((state) => state.handleDragging);
const filterType = useFlowStore((state) => state.filterType);
const dark = useDarkStore((state) => state.dark);
const onConnect = useFlowStore((state) => state.onConnect);
const handleMouseUp = () => {
setHandleDragging(undefined);
document.removeEventListener("mouseup", handleMouseUp);
};
const myId = useMemo(
() => scapedJSONStringfy(proxy ? { ...id, proxy } : id),
[id, proxy],
);
const getConnection = useMemo(
() =>
(semiConnection: {
source: string | undefined;
sourceHandle: string | undefined;
target: string | undefined;
targetHandle: string | undefined;
}) => ({
source: semiConnection.source ?? nodeId,
sourceHandle: semiConnection.sourceHandle ?? myId,
target: semiConnection.target ?? nodeId,
targetHandle: semiConnection.targetHandle ?? myId,
}),
[nodeId, myId],
);
const sameDraggingNode = useMemo(
() => (!left ? handleDragging?.target : handleDragging?.source) === nodeId,
[left, handleDragging, nodeId],
);
const ownDraggingHandle = useMemo(
() =>
handleDragging &&
(left ? handleDragging?.target : handleDragging?.source) &&
(left ? handleDragging.targetHandle : handleDragging.sourceHandle) ===
myId,
[handleDragging, left, myId],
);
const sameFilterNode = useMemo(
() => (!left ? filterType?.target : filterType?.source) === nodeId,
[left, filterType, nodeId],
);
const ownFilterHandle = useMemo(
() =>
filterType &&
(left ? filterType?.target : filterType?.source) === nodeId &&
(left ? filterType.targetHandle : filterType.sourceHandle) === myId,
[filterType, left, myId],
);
const sameNode = useMemo(
() => sameDraggingNode || sameFilterNode,
[sameDraggingNode, sameFilterNode],
);
const ownHandle = useMemo(
() => ownDraggingHandle || ownFilterHandle,
[ownDraggingHandle, ownFilterHandle],
);
const draggingOpenHandle = useMemo(
() =>
handleDragging &&
(left ? handleDragging.source : handleDragging.target) &&
!ownDraggingHandle
? isValidConnection(getConnection(handleDragging), nodes, edges)
: false,
[handleDragging, left, ownDraggingHandle, getConnection, nodes, edges],
);
const filterOpenHandle = useMemo(
() =>
filterType &&
(left ? filterType.source : filterType.target) &&
!ownFilterHandle
? isValidConnection(getConnection(filterType), nodes, edges)
: false,
[filterType, left, ownFilterHandle, getConnection, nodes, edges],
);
const openHandle = useMemo(
() => filterOpenHandle || draggingOpenHandle,
[filterOpenHandle, draggingOpenHandle],
);
const filterPresent = useMemo(
() => handleDragging || filterType,
[handleDragging, filterType],
);
const currentFilter = useMemo(
() =>
left
? {
targetHandle: myId,
target: nodeId,
source: undefined,
sourceHandle: undefined,
type: tooltipTitle,
color: colors[0],
}
: {
sourceHandle: myId,
source: nodeId,
target: undefined,
targetHandle: undefined,
type: tooltipTitle,
color: colors[0],
},
[left, myId, nodeId, tooltipTitle, colors],
);
const handleColor = useMemo(
() =>
filterPresent && !(openHandle || ownHandle)
? dark
? "conic-gradient(#374151 0deg 360deg)"
: "conic-gradient(#cbd5e1 0deg 360deg)"
: "conic-gradient(" +
colors
.concat(colors[0])
.map(
(color, index) =>
color +
" " +
((360 / colors.length) * index - 360 / (colors.length * 4)) +
"deg " +
((360 / colors.length) * index + 360 / (colors.length * 4)) +
"deg",
)
.join(" ,") +
")",
[filterPresent, openHandle, ownHandle, dark, colors],
);
const [openTooltip, setOpenTooltip] = useState(false);
return ( return (
<div> <div>
<ShadTooltip <ShadTooltip
open={openTooltip}
setOpen={setOpenTooltip}
styleClasses={"tooltip-fixed-width custom-scroll nowheel"} styleClasses={"tooltip-fixed-width custom-scroll nowheel"}
delayDuration={1000} delayDuration={1000}
content={ content={
<HandleTooltips <HandleTooltipComponent
left={left} isInput={left}
nodes={nodes} colors={colors}
tooltipTitle={tooltipTitle!} tooltipTitle={tooltipTitle}
isConnecting={!!filterPresent && !ownHandle}
isCompatible={openHandle}
isSameNode={sameNode && !ownHandle}
/> />
} }
side={left ? "left" : "right"} side={left ? "left" : "right"}
@ -54,47 +209,73 @@ export default function HandleRenderComponent({
}`} }`}
type={left ? "target" : "source"} type={left ? "target" : "source"}
position={left ? Position.Left : Position.Right} position={left ? Position.Left : Position.Right}
key={scapedJSONStringfy(proxy ? { ...id, proxy } : id)} key={myId}
id={scapedJSONStringfy(proxy ? { ...id, proxy } : id)} id={myId}
isValidConnection={(connection) => isValidConnection={(connection) =>
isValidConnection(connection, nodes, edges) isValidConnection(connection, nodes, edges)
} }
className={classNames( className={classNames(
left ? "-ml-0.5" : "-mr-0.5", `group/handle z-20 h-6 w-6 rounded-full border-none bg-transparent transition-all`,
"z-20 h-3 w-3 rounded-full border-none bg-background",
)} )}
style={{
background:
"conic-gradient(" +
colors
.concat(colors[0])
.map(
(color, index) =>
color +
" " +
((360 / colors.length) * index -
360 / (colors.length * 4)) +
"deg " +
((360 / colors.length) * index +
360 / (colors.length * 4)) +
"deg",
)
.join(" ,") +
")",
WebkitMaskImage: "radial-gradient(transparent 40%, black 44%)",
maskImage: "radial-gradient(transparent 40%, black 44%)",
}}
onClick={() => { onClick={() => {
setFilterEdge(groupByFamily(myData, tooltipTitle!, left, nodes!)); setFilterEdge(groupByFamily(myData, tooltipTitle!, left, nodes!));
setFilterType(currentFilter);
if (filterOpenHandle && filterType) {
onConnect(getConnection(filterType));
setFilterType(undefined);
setFilterEdge([]);
}
}} }}
/> onMouseUp={() => {
</ShadTooltip> setOpenTooltip(false);
}}
onContextMenu={(event) => {
event.preventDefault();
}}
onMouseDown={(event) => {
if (event.button === 0) {
setHandleDragging(currentFilter);
document.addEventListener("mouseup", handleMouseUp);
}
}}
>
<div <div
className={cn( className={cn(
"absolute top-[50%] z-10 h-3 w-3 translate-y-[-50%] rounded-full bg-background", "pointer-events-none absolute left-1/2 top-[50%] z-30 flex h-0 w-0 -translate-x-1/2 translate-y-[-50%] items-center justify-center rounded-full bg-background transition-all group-hover/handle:bg-transparent",
left ? "-left-[4px] -ml-0.5" : "-right-[4px] -mr-0.5", filterPresent
? openHandle || ownHandle
? cn(
"h-4 w-4",
ownHandle ? "bg-transparent" : "bg-background",
)
: ""
: "group-hover/node:h-4 group-hover/node:w-4",
)} )}
></div>
<div
className="pointer-events-none absolute left-1/2 top-[50%] z-10 flex h-3 w-3 -translate-x-1/2 translate-y-[-50%] items-center justify-center rounded-full opacity-50 transition-all"
style={{
background: handleColor,
}}
/> />
<div
data-testid={`gradient-handle-${testIdComplement}-${title.toLowerCase()}-${
!showNode ? (left ? "target" : "source") : left ? "left" : "right"
}`}
className={classNames(
`pointer-events-none absolute left-1/2 top-[50%] z-10 flex -translate-x-1/2 translate-y-[-50%] items-center justify-center rounded-full transition-all`,
filterPresent
? openHandle || ownHandle
? cn("h-5 w-5")
: cn("h-1.5 w-1.5")
: cn("h-1.5 w-1.5 group-hover/node:h-5 group-hover/node:w-5"),
)}
style={{
background: handleColor,
}}
/>
</Handle>
</ShadTooltip>
</div> </div>
); );
} }

View file

@ -1,91 +0,0 @@
import React from "react";
import {
INPUT_HANDLER_HOVER,
OUTPUT_HANDLER_HOVER,
} from "../../../../constants/constants";
import {
nodeColors,
nodeIconsLucide,
nodeNames,
} from "../../../../utils/styleUtils";
import { classNames } from "../../../../utils/utils";
const TooltipRenderComponent = ({ item, index, left }) => {
const Icon = nodeIconsLucide[item.family] ?? nodeIconsLucide["unknown"];
return (
<div
key={index}
data-testid={`available-${left ? "input" : "output"}-${item.family}`}
>
{index === 0 && (
<span>{left ? INPUT_HANDLER_HOVER : OUTPUT_HANDLER_HOVER}</span>
)}
<span
key={index}
className={classNames(
index > 0 ? "mt-2 flex items-center" : "mt-3 flex items-center",
)}
>
<div
className="h-5 w-5"
style={{
color: nodeColors[item.family],
}}
>
<Icon
className="h-5 w-5"
strokeWidth={1.5}
style={{
color: nodeColors[item.family] ?? nodeColors.unknown,
}}
/>
</div>
<span
className="ps-2 text-xs text-foreground"
data-testid={`tooltip-${nodeNames[item.family] ?? "Other"}`}
>
{nodeNames[item.family] ?? "Other"}{" "}
{item?.display_name && item?.display_name?.length > 0 ? (
<span
className="text-xs"
data-testid={`tooltip-${item?.display_name}`}
>
{" "}
{item.display_name === "" ? "" : " - "}
{item.display_name.split(", ").length > 2
? item.display_name.split(", ").map((el, index) => (
<React.Fragment key={el + name}>
<span>
{index === item.display_name.split(", ").length - 1
? el
: (el += `, `)}
</span>
</React.Fragment>
))
: item.display_name}
</span>
) : (
<span className="text-xs" data-testid={`tooltip-${item?.type}`}>
{" "}
{item.type === "" ? "" : " - "}
{item.type.split(", ").length > 2
? item.type.split(", ").map((el, index) => (
<React.Fragment key={el + name}>
<span>
{index === item.type.split(", ").length - 1
? el
: (el += `, `)}
</span>
</React.Fragment>
))
: item.type}
</span>
)}
</span>
</span>
</div>
);
};
export default TooltipRenderComponent;

View file

@ -9,12 +9,19 @@ export default function ShadTooltip({
children, children,
styleClasses, styleClasses,
delayDuration = 500, delayDuration = 500,
open,
setOpen,
}: ShadToolTipType): JSX.Element { }: ShadToolTipType): JSX.Element {
return content ? ( return content ? (
<Tooltip defaultOpen={!children} delayDuration={delayDuration}> <Tooltip
defaultOpen={!children}
open={open}
onOpenChange={setOpen}
delayDuration={delayDuration}
>
<TooltipTrigger asChild={asChild}>{children}</TooltipTrigger> <TooltipTrigger asChild={asChild}>{children}</TooltipTrigger>
<TooltipContent <TooltipContent
className={cn(styleClasses, "max-w-96")} className={cn("max-w-96", styleClasses)}
side={side} side={side}
avoidCollisions={false} avoidCollisions={false}
sticky="always" sticky="always"

View file

@ -1,3 +1,4 @@
import { DefaultEdge } from "@/CustomEdges";
import NoteNode from "@/CustomNodes/NoteNode"; import NoteNode from "@/CustomNodes/NoteNode";
import IconComponent from "@/components/genericIconComponent"; import IconComponent from "@/components/genericIconComponent";
import LoadingComponent from "@/components/loadingComponent"; import LoadingComponent from "@/components/loadingComponent";
@ -8,7 +9,6 @@ import useAutoSaveFlow from "@/hooks/flows/use-autosave-flow";
import useUploadFlow from "@/hooks/flows/use-upload-flow"; import useUploadFlow from "@/hooks/flows/use-upload-flow";
import { getNodeRenderType, isSupportedNodeTypes } from "@/utils/utils"; import { getNodeRenderType, isSupportedNodeTypes } from "@/utils/utils";
import { ENABLE_MVPS } from "@/customization/feature-flags";
import _, { cloneDeep } from "lodash"; import _, { cloneDeep } from "lodash";
import { import {
KeyboardEvent, KeyboardEvent,
@ -457,6 +457,8 @@ export default function Page({ view }: { view?: boolean }): JSX.Element {
onSelectionDragStart={onSelectionDragStart} onSelectionDragStart={onSelectionDragStart}
onSelectionEnd={onSelectionEnd} onSelectionEnd={onSelectionEnd}
onSelectionStart={onSelectionStart} onSelectionStart={onSelectionStart}
connectionRadius={25}
edgeTypes={{ default: DefaultEdge }}
connectionLineComponent={ConnectionLineComponent} connectionLineComponent={ConnectionLineComponent}
onDragOver={onDragOver} onDragOver={onDragOver}
onNodeDragStop={onNodeDragStop} onNodeDragStop={onNodeDragStop}

View file

@ -18,7 +18,7 @@ export default function ParentDisclosureComponent({
data-testid={testId} data-testid={testId}
> >
<div className="flex items-baseline gap-1 align-baseline"> <div className="flex items-baseline gap-1 align-baseline">
<span className="parent-disclosure-title">{title}</span> <span className="text-sm font-medium">{title}</span>
{beta && ( {beta && (
<div className="h-fit rounded-full bg-beta-background px-2 py-1 text-xs/3 font-semibold text-beta-foreground-soft"> <div className="h-fit rounded-full bg-beta-background px-2 py-1 text-xs/3 font-semibold text-beta-foreground-soft">
BETA BETA

View file

@ -17,6 +17,7 @@ import { nodeIconsLucide } from "../../../../utils/styleUtils";
import ParentDisclosureComponent from "../ParentDisclosureComponent"; import ParentDisclosureComponent from "../ParentDisclosureComponent";
import { SidebarCategoryComponent } from "./SidebarCategoryComponent"; import { SidebarCategoryComponent } from "./SidebarCategoryComponent";
import { SidebarFilterComponent } from "./sidebarFilterComponent";
import { sortKeys } from "./utils"; import { sortKeys } from "./utils";
export default function ExtraSidebar(): JSX.Element { export default function ExtraSidebar(): JSX.Element {
@ -25,6 +26,7 @@ export default function ExtraSidebar(): JSX.Element {
const getFilterEdge = useFlowStore((state) => state.getFilterEdge); const getFilterEdge = useFlowStore((state) => state.getFilterEdge);
const setFilterEdge = useFlowStore((state) => state.setFilterEdge); const setFilterEdge = useFlowStore((state) => state.setFilterEdge);
const hasStore = useStoreStore((state) => state.hasStore); const hasStore = useStoreStore((state) => state.hasStore);
const filterType = useFlowStore((state) => state.filterType);
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);
const [dataFilter, setFilterData] = useState(data); const [dataFilter, setFilterData] = useState(data);
@ -222,8 +224,18 @@ export default function ExtraSidebar(): JSX.Element {
<div className="side-bar-components-div-arrangement"> <div className="side-bar-components-div-arrangement">
<div className="parent-disclosure-arrangement"> <div className="parent-disclosure-arrangement">
<div className="flex items-center gap-4 align-middle"> <div className="flex w-full flex-col items-start justify-between gap-2.5">
<span className="parent-disclosure-title">Components</span> <span className="text-sm font-medium">Components</span>
{filterType && (
<SidebarFilterComponent
isInput={!!filterType.source}
type={filterType.type}
resetFilters={() => {
setFilterEdge([]);
setFilterData(data);
}}
/>
)}
</div> </div>
</div> </div>
<Separator /> <Separator />

View file

@ -0,0 +1,40 @@
import ForwardedIconComponent from "@/components/genericIconComponent";
import ShadTooltip from "@/components/shadTooltipComponent";
import { Button } from "@/components/ui/button";
export function SidebarFilterComponent({
isInput,
type,
resetFilters,
}: {
isInput: boolean;
type: string;
resetFilters: () => void;
}) {
return (
<div className="mb-0.5 flex w-full items-center justify-between rounded border bg-muted p-1 px-2 text-xs font-medium text-primary">
<div className="flex flex-1 items-center gap-1.5">
<ForwardedIconComponent
name="ListFilter"
className="h-4 w-4 shrink-0 stroke-2"
/>
<div className="flex-1 overflow-hidden truncate">
{isInput ? "Input" : "Output"}: {type}
</div>
</div>
<ShadTooltip
side="right"
styleClasses="max-w-full"
content="Remove filter"
>
<Button unstyled className="shrink-0" onClick={resetFilters}>
<ForwardedIconComponent
name="X"
className="h-4 w-4 stroke-2"
aria-hidden="true"
/>
</Button>
</ShadTooltip>
</div>
);
}

View file

@ -431,6 +431,9 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
}); });
}, },
setFilterEdge: (newState) => { setFilterEdge: (newState) => {
if (newState.length === 0) {
set({ filterType: undefined });
}
set({ getFilterEdge: newState }); set({ getFilterEdge: newState });
}, },
getFilterEdge: [], getFilterEdge: [],
@ -469,8 +472,6 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
targetHandle: scapeJSONParse(connection.targetHandle!), targetHandle: scapeJSONParse(connection.targetHandle!),
sourceHandle: scapeJSONParse(connection.sourceHandle!), sourceHandle: scapeJSONParse(connection.sourceHandle!),
}, },
// style: { stroke: "#555" },
// className: "stroke-foreground stroke-connection",
}, },
oldEdges, oldEdges,
); );
@ -528,6 +529,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
get().updateBuildStatus(ids, BuildStatus.ERROR); get().updateBuildStatus(ids, BuildStatus.ERROR);
throw new Error("Invalid components"); throw new Error("Invalid components");
} }
get().updateEdgesRunningByNodes(nodes, true);
} }
function handleBuildUpdate( function handleBuildUpdate(
vertexBuildData: VertexBuildTypeAPI, vertexBuildData: VertexBuildTypeAPI,
@ -631,6 +633,10 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
}); });
} }
} }
get().updateEdgesRunningByNodes(
get().nodes.map((n) => n.id),
false,
);
get().setIsBuilding(false); get().setIsBuilding(false);
get().setLockChat(false); get().setLockChat(false);
}, },
@ -653,6 +659,10 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
title: title:
"There are outdated components in the flow. The error could be related to them.", "There are outdated components in the flow. The error could be related to them.",
}); });
get().updateEdgesRunningByNodes(
get().nodes.map((n) => n.id),
false,
);
setErrorData({ list, title }); setErrorData({ list, title });
get().setIsBuilding(false); get().setIsBuilding(false);
get().setLockChat(false); get().setLockChat(false);
@ -662,7 +672,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
// reference is the id of the vertex or the id of the parent in a group node // reference is the id of the vertex or the id of the parent in a group node
.map((element) => element.reference) .map((element) => element.reference)
.filter(Boolean) as string[]; .filter(Boolean) as string[];
useFlowStore.getState().updateBuildStatus(idList, BuildStatus.BUILDING); get().updateBuildStatus(idList, BuildStatus.BUILDING);
}, },
onValidateNodes: validateSubgraph, onValidateNodes: validateSubgraph,
nodes: get().nodes || undefined, nodes: get().nodes || undefined,
@ -680,6 +690,17 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
viewport: get().reactFlowInstance?.getViewport()!, viewport: get().reactFlowInstance?.getViewport()!,
}; };
}, },
updateEdgesRunningByNodes: (ids: string[], running: boolean) => {
const edges = get().edges;
const newEdges = edges.map((edge) => {
if (ids.includes(edge.source) && ids.includes(edge.target)) {
edge.animated = running;
edge.className = running ? "running" : "";
}
return edge;
});
set({ edges: newEdges });
},
updateVerticesBuild: ( updateVerticesBuild: (
vertices: { vertices: {
verticesIds: string[]; verticesIds: string[];
@ -762,6 +783,15 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
setBuildController: (controller) => { setBuildController: (controller) => {
set({ buildController: controller }); set({ buildController: controller });
}, },
handleDragging: undefined,
setHandleDragging: (handleDragging) => {
set({ handleDragging });
},
filterType: undefined,
setFilterType: (filterType) => {
set({ filterType });
},
})); }));
export default useFlowStore; export default useFlowStore;

View file

@ -231,7 +231,7 @@
@apply fill-chat-trigger-disabled stroke-chat-trigger-disabled stroke-1; @apply fill-chat-trigger-disabled stroke-chat-trigger-disabled stroke-1;
} }
.parent-disclosure-arrangement { .parent-disclosure-arrangement {
@apply flex w-full select-none items-center justify-between bg-background px-3 py-1; @apply flex w-full select-none items-center justify-between bg-background px-5 py-3;
} }
.components-disclosure-arrangement { .components-disclosure-arrangement {
@apply -mt-px flex w-full select-none items-center justify-between border-y border-y-input bg-muted px-3 py-2; @apply -mt-px flex w-full select-none items-center justify-between border-y border-y-input bg-muted px-3 py-2;
@ -240,9 +240,6 @@
/* different color than the non child */ /* different color than the non child */
@apply -mt-px flex w-full select-none items-center justify-between border-y border-y-input bg-muted px-3 py-2; @apply -mt-px flex w-full select-none items-center justify-between border-y border-y-input bg-muted px-3 py-2;
} }
.parent-disclosure-title {
@apply p-2 px-2 text-sm font-medium;
}
.components-disclosure-title { .components-disclosure-title {
@apply flex items-center text-sm text-primary; @apply flex items-center text-sm text-primary;
} }

View file

@ -169,6 +169,26 @@ textarea[class^="ag-"]:focus {
cursor: grabbing !important; cursor: grabbing !important;
} }
.react-flow__handle-right {
right: 0 !important;
transform: translate(50%, -50%) !important;
}
.react-flow__handle-left {
left: 0 !important;
transform: translate(-50%, -50%) !important;
}
.react-flow__handle-right {
right: 0 !important;
transform: translate(50%, -50%) !important;
}
.react-flow__handle-left {
left: 0 !important;
transform: translate(-50%, -50%) !important;
}
.react-flow__node-noteNode:not(.selected) { .react-flow__node-noteNode:not(.selected) {
z-index: -1 !important; z-index: -1 !important;
} }

View file

@ -57,6 +57,8 @@
--hover: #f2f4f5; --hover: #f2f4f5;
--disabled-run: #6366f1; --disabled-run: #6366f1;
--filter-foreground: #4f46e5;
--filter-background: #eef2ff;
/* Colors that are shared in dark and light mode */ /* Colors that are shared in dark and light mode */
--blur-shared: #151923de; --blur-shared: #151923de;
--build-trigger: #dc735b; --build-trigger: #dc735b;
@ -114,6 +116,9 @@
--destructive: 0 60% 25%; /* hsl(0 60% 25%) */ --destructive: 0 60% 25%; /* hsl(0 60% 25%) */
--destructive-foreground: 210 40% 98%; /* hsl(210 40% 98%) */ --destructive-foreground: 210 40% 98%; /* hsl(210 40% 98%) */
--filter-foreground: #eef2ff;
--filter-background: #4e46e599;
--ring: 216 24% 30%; /* hsl(216 24% 30%) */ --ring: 216 24% 30%; /* hsl(216 24% 30%) */
--radius: 0.5rem; --radius: 0.5rem;

View file

@ -339,6 +339,8 @@ export type ShadTooltipProps = {
style?: string; style?: string;
}; };
export type ShadToolTipType = { export type ShadToolTipType = {
open?: boolean;
setOpen?: (open: boolean) => void;
content?: ReactNode | null; content?: ReactNode | null;
side?: "top" | "right" | "bottom" | "left"; side?: "top" | "right" | "bottom" | "left";
asChild?: boolean; asChild?: boolean;

View file

@ -180,6 +180,52 @@ export type FlowStoreType = {
edges?: Edge[]; edges?: Edge[];
viewport?: Viewport; viewport?: Viewport;
}) => void; }) => void;
handleDragging:
| {
source: string | undefined;
sourceHandle: string | undefined;
target: string | undefined;
targetHandle: string | undefined;
type: string;
color: string;
}
| undefined;
setHandleDragging: (
data:
| {
source: string | undefined;
sourceHandle: string | undefined;
target: string | undefined;
targetHandle: string | undefined;
type: string;
color: string;
}
| undefined,
) => void;
filterType:
| {
source: string | undefined;
sourceHandle: string | undefined;
target: string | undefined;
targetHandle: string | undefined;
type: string;
color: string;
}
| undefined;
setFilterType: (
data:
| {
source: string | undefined;
sourceHandle: string | undefined;
target: string | undefined;
targetHandle: string | undefined;
type: string;
color: string;
}
| undefined,
) => void;
updateEdgesRunningByNodes: (ids: string[], running: boolean) => void;
stopBuilding: () => void; stopBuilding: () => void;
buildController: AbortController; buildController: AbortController;
setBuildController: (controller: AbortController) => void; setBuildController: (controller: AbortController) => void;

View file

@ -215,6 +215,9 @@ export function isValidConnection(
nodes: Node[], nodes: Node[],
edges: Edge[], edges: Edge[],
) { ) {
if (source === target) {
return false;
}
const targetHandleObject: targetHandleType = scapeJSONParse(targetHandle!); const targetHandleObject: targetHandleType = scapeJSONParse(targetHandle!);
const sourceHandleObject: sourceHandleType = scapeJSONParse(sourceHandle!); const sourceHandleObject: sourceHandleType = scapeJSONParse(sourceHandle!);
if ( if (

View file

@ -8,6 +8,7 @@ import {
AlertTriangle, AlertTriangle,
ArrowBigUp, ArrowBigUp,
ArrowLeft, ArrowLeft,
ArrowRight,
ArrowUpToLine, ArrowUpToLine,
Bell, Bell,
Binary, Binary,
@ -63,6 +64,7 @@ import {
FileText, FileText,
FileType2, FileType2,
FileUp, FileUp,
Filter,
FlaskConical, FlaskConical,
FolderIcon, FolderIcon,
FolderPlus, FolderPlus,
@ -87,6 +89,7 @@ import {
Layers, Layers,
Link, Link,
Link2, Link2,
ListFilter,
Loader2, Loader2,
Lock, Lock,
LogIn, LogIn,
@ -401,10 +404,12 @@ export const nodeIconsLucide: iconsType = {
GoogleSearchRun: GoogleIcon, GoogleSearchRun: GoogleIcon,
Google: GoogleIcon, Google: GoogleIcon,
GoogleGenerativeAI: GoogleGenerativeAIIcon, GoogleGenerativeAI: GoogleGenerativeAIIcon,
ArrowRight,
Groq: GroqIcon, Groq: GroqIcon,
HCD: HCDIcon, HCD: HCDIcon,
HNLoader: HackerNewsIcon, HNLoader: HackerNewsIcon,
Unstructured: UnstructuredIcon, Unstructured: UnstructuredIcon,
Filter: Filter,
HuggingFaceHub: HuggingFaceIcon, HuggingFaceHub: HuggingFaceIcon,
HuggingFace: HuggingFaceIcon, HuggingFace: HuggingFaceIcon,
HuggingFaceEmbeddings: HuggingFaceIcon, HuggingFaceEmbeddings: HuggingFaceIcon,
@ -414,6 +419,7 @@ export const nodeIconsLucide: iconsType = {
Meta: MetaIcon, Meta: MetaIcon,
CheckCheck, CheckCheck,
Midjorney: MidjourneyIcon, Midjorney: MidjourneyIcon,
ListFilter,
MongoDBAtlasVectorSearch: MongoDBIcon, MongoDBAtlasVectorSearch: MongoDBIcon,
MongoDB: MongoDBIcon, MongoDB: MongoDBIcon,
MongoDBChatMessageHistory: MongoDBIcon, MongoDBChatMessageHistory: MongoDBIcon,

View file

@ -97,9 +97,15 @@ const config = {
"status-gray": "var(--status-gray)", "status-gray": "var(--status-gray)",
"success-background": "var(--success-background)", "success-background": "var(--success-background)",
"success-foreground": "var(--success-foreground)", "success-foreground": "var(--success-foreground)",
"beta-background": "var(--beta-background)", filter: {
"beta-foreground": "var(--beta-foreground)", foreground: "var(--filter-foreground)",
"beta-foreground-soft": "var(--beta-foreground-soft)", background: "var(--filter-background)",
},
beta: {
background: "var(--beta-background)",
foreground: "var(--beta-foreground)",
"foreground-soft": "var(--beta-foreground-soft)",
},
"chat-bot-icon": "var(--chat-bot-icon)", "chat-bot-icon": "var(--chat-bot-icon)",
"chat-user-icon": "var(--chat-user-icon)", "chat-user-icon": "var(--chat-user-icon)",
ice: "var(--ice)", ice: "var(--ice)",

View file

@ -54,28 +54,21 @@ test("user must see on handle hover a tooltip with possibility connections", asy
} }
await visibleElementHandle.hover().then(async () => { await visibleElementHandle.hover().then(async () => {
const testIds = [ await expect(
"available-output-inputs", page.getByText("Drag to connect compatible inputs").first(),
"available-output-chains", ).toBeVisible();
"available-output-textsplitters",
"available-output-retrievers",
"available-output-prototypes",
"available-output-embeddings",
"available-output-data",
"available-output-vectorstores",
"available-output-memories",
"available-output-models",
"available-output-outputs",
"available-output-agents",
"available-output-helpers",
];
await Promise.all( await expect(
testIds.map((id) => expect(page.getByTestId(id).first()).toBeVisible()), page
); .getByText("Select to filter compatible inputs and components")
.first(),
).toBeVisible();
await page.getByTestId("icon-X").click(); await expect(page.getByText("Output:").first()).toBeVisible();
await page.waitForTimeout(500);
await expect(
page.getByTestId("output-tooltip-message").first(),
).toBeVisible();
}); });
await page.getByTitle("fit view").click(); await page.getByTitle("fit view").click();
@ -96,13 +89,20 @@ test("user must see on handle hover a tooltip with possibility connections", asy
await visibleElementHandle.hover().then(async () => { await visibleElementHandle.hover().then(async () => {
await expect( await expect(
page.getByTestId("available-input-models").first(), page.getByText("Drag to connect compatible outputs").first(),
).toBeVisible(); ).toBeVisible();
await page.waitForTimeout(1000);
await page.getByTestId("icon-Search").click(); await expect(
page
.getByText("Select to filter compatible outputs and components")
.first(),
).toBeVisible();
await page.waitForTimeout(500); await expect(page.getByText("Input:").first()).toBeVisible();
await expect(
page.getByTestId("input-tooltip-languagemodel").first(),
).toBeVisible();
}); });
await page.getByTitle("fit view").click(); await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click(); await page.getByTitle("zoom out").click();
@ -121,16 +121,21 @@ test("user must see on handle hover a tooltip with possibility connections", asy
} }
await visibleElementHandle.hover().then(async () => { await visibleElementHandle.hover().then(async () => {
await page.waitForTimeout(2500);
await expect( await expect(
page.getByTestId("available-input-retrievers").first(), page.getByText("Drag to connect compatible outputs").first(),
).toBeVisible();
await expect(
page.getByTestId("available-input-vectorstores").first(),
).toBeVisible(); ).toBeVisible();
await page.waitForTimeout(500); await expect(
page
.getByText("Select to filter compatible outputs and components")
.first(),
).toBeVisible();
await expect(page.getByText("Input:").first()).toBeVisible();
await expect(
page.getByTestId("input-tooltip-retriever").first(),
).toBeVisible();
}); });
await page.getByTitle("fit view").click(); await page.getByTitle("fit view").click();
@ -151,7 +156,19 @@ test("user must see on handle hover a tooltip with possibility connections", asy
await visibleElementHandle.hover().then(async () => { await visibleElementHandle.hover().then(async () => {
await expect( await expect(
page.getByTestId("available-input-helpers").first(), page.getByText("Drag to connect compatible outputs").first(),
).toBeVisible();
await expect(
page
.getByText("Select to filter compatible outputs and components")
.first(),
).toBeVisible();
await expect(page.getByText("Input:").first()).toBeVisible();
await expect(
page.getByTestId("input-tooltip-basechatmemory").first(),
).toBeVisible(); ).toBeVisible();
}); });
}); });

View file

@ -54,99 +54,86 @@ test("user must see on handle click the possibility connections - LLMChain", asy
await page.getByTestId("handle-apirequest-shownode-urls-left").click(); await page.getByTestId("handle-apirequest-shownode-urls-left").click();
let disclosureTestIds = [ await page.waitForTimeout(500);
"disclosure-inputs",
"disclosure-outputs",
"disclosure-prompts",
"disclosure-models",
"disclosure-helpers",
"disclosure-agents",
"disclosure-chains",
"disclosure-prototypes",
];
let specificTestIds = [ expect(await page.getByTestId("icon-ListFilter")).toBeVisible();
"inputsChat Input",
"outputsChat Output",
"promptsPrompt",
"modelsAmazon Bedrock",
"helpersChat Memory",
"agentsCSVAgent",
"chainsConversationChain",
"prototypesConditional Router",
];
await Promise.all( await page
disclosureTestIds.map((id) => expect(page.getByTestId(id)).toBeVisible()), .getByTestId("icon-X")
); .first()
.hover()
.then(async () => {
await page
.getByText("Remove filter", {
exact: false,
})
.first()
.isVisible();
});
await Promise.all( await expect(page.getByTestId("disclosure-inputs")).toBeVisible();
specificTestIds.map((id) => expect(page.getByTestId(id)).toBeVisible()), await expect(page.getByTestId("disclosure-outputs")).toBeVisible();
); await expect(page.getByTestId("disclosure-prompts")).toBeVisible();
await expect(page.getByTestId("disclosure-models")).toBeVisible();
await expect(page.getByTestId("disclosure-helpers")).toBeVisible();
await expect(page.getByTestId("disclosure-agents")).toBeVisible();
await expect(page.getByTestId("disclosure-chains")).toBeVisible();
await expect(page.getByTestId("disclosure-prototypes")).toBeVisible();
await expect(page.getByTestId("inputsChat Input")).toBeVisible();
await expect(page.getByTestId("outputsChat Output")).toBeVisible();
await expect(page.getByTestId("promptsPrompt")).toBeVisible();
await expect(page.getByTestId("modelsAmazon Bedrock")).toBeVisible();
await expect(page.getByTestId("helpersChat Memory")).toBeVisible();
await expect(page.getByTestId("agentsCSVAgent")).toBeVisible();
await expect(page.getByTestId("chainsConversationChain")).toBeVisible();
await expect(page.getByTestId("prototypesConditional Router")).toBeVisible();
await page.getByPlaceholder("Search").click(); await page.getByPlaceholder("Search").click();
let notVisibleTestIds = [ await expect(page.getByTestId("inputsChat Input")).not.toBeVisible();
"inputsChat Input", await expect(page.getByTestId("outputsChat Output")).not.toBeVisible();
"outputsChat Output", await expect(page.getByTestId("promptsPrompt")).not.toBeVisible();
"promptsPrompt", await expect(page.getByTestId("modelsAmazon Bedrock")).not.toBeVisible();
"modelsAmazon Bedrock", await expect(page.getByTestId("helpersChat Memory")).not.toBeVisible();
"helpersChat Memory", await expect(page.getByTestId("agentsTool Calling Agent")).not.toBeVisible();
"agentsTool Calling Agent", await expect(page.getByTestId("chainsConversationChain")).not.toBeVisible();
"chainsConversationChain", await expect(
"prototypesConditional Router", page.getByTestId("prototypesConditional Router"),
]; ).not.toBeVisible();
await Promise.all(
notVisibleTestIds.map((id) =>
expect(page.getByTestId(id)).not.toBeVisible(),
),
);
await page.getByTestId("handle-apirequest-shownode-headers-left").click(); await page.getByTestId("handle-apirequest-shownode-headers-left").click();
disclosureTestIds = [ await expect(page.getByTestId("disclosure-data")).toBeVisible();
"disclosure-data", await expect(page.getByTestId("disclosure-helpers")).toBeVisible();
"disclosure-helpers", await expect(page.getByTestId("disclosure-vector stores")).toBeVisible();
"disclosure-vector stores", await expect(page.getByTestId("disclosure-utilities")).toBeVisible();
"disclosure-utilities", await expect(page.getByTestId("disclosure-prototypes")).toBeVisible();
"disclosure-prototypes", await expect(page.getByTestId("disclosure-retrievers")).toBeVisible();
"disclosure-retrievers", await expect(page.getByTestId("disclosure-embeddings")).toBeVisible();
"disclosure-tools", await expect(page.getByTestId("disclosure-tools")).toBeVisible();
];
specificTestIds = [ await expect(page.getByTestId("dataAPI Request")).toBeVisible();
"dataAPI Request", await expect(page.getByTestId("helpersChat Memory")).toBeVisible();
"helpersChat Memory", await expect(page.getByTestId("vectorstoresAstra DB")).toBeVisible();
"vectorstoresAstra DB", await expect(page.getByTestId("toolsSearch API")).toBeVisible();
"toolsSearch API", await expect(page.getByTestId("prototypesSub Flow")).toBeVisible();
"prototypesSub Flow", await expect(
"retrieversSelf Query Retriever", page.getByTestId("retrieversSelf Query Retriever"),
]; ).toBeVisible();
await expect(page.getByTestId("helpersSplit Text")).toBeVisible();
await expect(page.getByTestId("toolsSearch API")).toBeVisible();
await Promise.all( await page.getByTestId("icon-X").first().click();
disclosureTestIds.map((id) => expect(page.getByTestId(id)).toBeVisible()),
);
await Promise.all( await expect(page.getByTestId("dataAPI Request")).not.toBeVisible();
specificTestIds.map((id) => expect(page.getByTestId(id)).toBeVisible()), await expect(page.getByTestId("helpersChat Memory")).not.toBeVisible();
); await expect(page.getByTestId("vectorstoresAstra DB")).not.toBeVisible();
await expect(page.getByTestId("toolsSearch API")).not.toBeVisible();
await page.getByPlaceholder("Search").click(); await expect(page.getByTestId("prototypesSub Flow")).not.toBeVisible();
await expect(
notVisibleTestIds = [ page.getByTestId("retrieversSelf Query Retriever"),
"dataAPI Request", ).not.toBeVisible();
"helpersChat Memory", await expect(page.getByTestId("helpersSplit Text")).not.toBeVisible();
"vectorstoresAstra DB", await expect(page.getByTestId("toolsSearch API")).not.toBeVisible();
"toolsSearch API",
"prototypesSub Flow",
"retrieversSelf Query Retriever",
"textsplittersCharacterTextSplitter",
];
await Promise.all(
notVisibleTestIds.map((id) =>
expect(page.getByTestId(id)).not.toBeVisible(),
),
);
}); });

View file

@ -1,6 +1,4 @@
import { expect, test } from "@playwright/test"; import { expect, test } from "@playwright/test";
import * as dotenv from "dotenv";
import path from "path";
test("should be able to move flow from folder, rename it and be displayed on correct folder", async ({ test("should be able to move flow from folder, rename it and be displayed on correct folder", async ({
page, page,

View file

@ -1,6 +1,6 @@
import { expect, test } from "@playwright/test"; import { expect, test } from "@playwright/test";
test("should be able to see output preview from grouped components", async ({ test("should be able to see output preview from grouped components and connect components with a single click", async ({
page, page,
}) => { }) => {
await page.goto("/"); await page.goto("/");
@ -51,7 +51,7 @@ test("should be able to see output preview from grouped components", async ({
.hover() .hover()
.then(async () => { .then(async () => {
await page.mouse.down(); await page.mouse.down();
await page.mouse.move(-1000, 500); await page.mouse.move(-600, 300);
await page.waitForTimeout(400); await page.waitForTimeout(400);
}); });
@ -71,7 +71,7 @@ test("should be able to see output preview from grouped components", async ({
.hover() .hover()
.then(async () => { .then(async () => {
await page.mouse.down(); await page.mouse.down();
await page.mouse.move(-1000, 800); await page.mouse.move(-600, 300);
await page.waitForTimeout(400); await page.waitForTimeout(400);
}); });
@ -86,7 +86,7 @@ test("should be able to see output preview from grouped components", async ({
.hover() .hover()
.then(async () => { .then(async () => {
await page.mouse.down(); await page.mouse.down();
await page.mouse.move(-800, 800); await page.mouse.move(-600, 300);
await page.waitForTimeout(400); await page.waitForTimeout(400);
}); });
@ -101,7 +101,7 @@ test("should be able to see output preview from grouped components", async ({
.hover() .hover()
.then(async () => { .then(async () => {
await page.mouse.down(); await page.mouse.down();
await page.mouse.move(-200, 800); await page.mouse.move(-600, 300);
await page.waitForTimeout(200); await page.waitForTimeout(200);
}); });
@ -117,7 +117,7 @@ test("should be able to see output preview from grouped components", async ({
.hover() .hover()
.then(async () => { .then(async () => {
await page.mouse.down(); await page.mouse.down();
await page.mouse.move(-200, 500); await page.mouse.move(-600, 300);
}); });
await page.mouse.up(); await page.mouse.up();
@ -134,13 +134,118 @@ test("should be able to see output preview from grouped components", async ({
const elementCombineTextOutput0 = await page const elementCombineTextOutput0 = await page
.getByTestId("handle-combinetext-shownode-combined text-right") .getByTestId("handle-combinetext-shownode-combined text-right")
.nth(0); .nth(0);
await elementCombineTextOutput0.hover(); await elementCombineTextOutput0.click();
await page.mouse.down();
const blockedHandle = await page
.getByTestId("gradient-handle-textinput-shownode-text-right")
.nth(2);
const secondBlockedHandle = await page
.getByTestId("gradient-handle-combinetext-shownode-combined text-right")
.nth(2);
const thirdBlockedHandle = await page
.getByTestId("gradient-handle-textoutput-shownode-text-right")
.nth(0);
const hasGradient = await blockedHandle?.evaluate((el) => {
const style = window.getComputedStyle(el);
return (
style.backgroundImage.includes("conic-gradient") &&
style.backgroundImage.includes("rgb(203, 213, 225)")
);
});
await page.waitForTimeout(500);
const secondHasGradient = await secondBlockedHandle?.evaluate((el) => {
const style = window.getComputedStyle(el);
return (
style.backgroundImage.includes("conic-gradient") &&
style.backgroundImage.includes("rgb(203, 213, 225)")
);
});
await page.waitForTimeout(500);
const thirdHasGradient = await thirdBlockedHandle?.evaluate((el) => {
const style = window.getComputedStyle(el);
return (
style.backgroundImage.includes("conic-gradient") &&
style.backgroundImage.includes("rgb(203, 213, 225)")
);
});
await page.waitForTimeout(500);
expect(hasGradient).toBe(true);
expect(secondHasGradient).toBe(true);
expect(thirdHasGradient).toBe(true);
const unlockedHandle = await page
.getByTestId("gradient-handle-textinput-shownode-text-left")
.last();
const secondUnlockedHandle = await page
.getByTestId("gradient-handle-combinetext-shownode-second text-left")
.last();
const thirdUnlockedHandle = await page
.getByTestId("gradient-handle-combinetext-shownode-second text-left")
.first();
const fourthUnlockedHandle = await page
.getByTestId("gradient-handle-textoutput-shownode-text-left")
.first();
const hasGradientUnlocked = await unlockedHandle?.evaluate((el) => {
const style = window.getComputedStyle(el);
return (
style.backgroundImage.includes("conic-gradient") &&
style.backgroundImage.includes("rgb(79, 70, 229)")
);
});
await page.waitForTimeout(500);
const secondHasGradientUnlocked = await secondUnlockedHandle?.evaluate(
(el) => {
const style = window.getComputedStyle(el);
return (
style.backgroundImage.includes("conic-gradient") &&
style.backgroundImage.includes("rgb(79, 70, 229)")
);
},
);
await page.waitForTimeout(500);
const thirdHasGradientLocked = await thirdUnlockedHandle?.evaluate((el) => {
const style = window.getComputedStyle(el);
return (
style.backgroundImage.includes("conic-gradient") &&
style.backgroundImage.includes("rgb(203, 213, 225)")
);
});
await page.waitForTimeout(500);
const fourthHasGradientUnlocked = await fourthUnlockedHandle?.evaluate(
(el) => {
const style = window.getComputedStyle(el);
return (
style.backgroundImage.includes("conic-gradient") &&
style.backgroundImage.includes("rgb(79, 70, 229)")
);
},
);
await page.waitForTimeout(500);
expect(hasGradientUnlocked).toBe(true);
expect(secondHasGradientUnlocked).toBe(true);
expect(thirdHasGradientLocked).toBe(true);
expect(fourthHasGradientUnlocked).toBe(true);
const elementCombineTextInput1 = await page const elementCombineTextInput1 = await page
.getByTestId("handle-combinetext-shownode-first text-left") .getByTestId("handle-combinetext-shownode-first text-left")
.nth(1); .nth(1);
await elementCombineTextInput1.hover(); await elementCombineTextInput1.click();
await page.mouse.up();
await page await page
.getByTestId("title-Combine Text") .getByTestId("title-Combine Text")
@ -157,68 +262,60 @@ test("should be able to see output preview from grouped components", async ({
const elementTextOutput0 = await page const elementTextOutput0 = await page
.getByTestId("handle-textinput-shownode-text-right") .getByTestId("handle-textinput-shownode-text-right")
.nth(0); .nth(0);
await elementTextOutput0.hover(); await elementTextOutput0.click();
await page.mouse.down();
const elementGroupInput0 = await page.getByTestId( const elementGroupInput0 = await page.getByTestId(
"handle-groupnode-shownode-first text-left", "handle-groupnode-shownode-first text-left",
); );
await elementGroupInput0.click();
await elementGroupInput0.hover();
await page.mouse.up();
//connection 3 //connection 3
const elementTextOutput1 = await page const elementTextOutput1 = await page
.getByTestId("handle-textinput-shownode-text-right") .getByTestId("handle-textinput-shownode-text-right")
.nth(2); .nth(2);
await elementTextOutput1.hover(); await elementTextOutput1.click();
await page.mouse.down();
const elementGroupInput1 = await page const elementGroupInput1 = await page
.getByTestId("handle-groupnode-shownode-second text-left") .getByTestId("handle-groupnode-shownode-second text-left")
.nth(1); .nth(1);
await elementGroupInput1.click();
await elementGroupInput1.hover();
await page.mouse.up();
//connection 4 //connection 4
const elementGroupOutput = await page const elementGroupOutput = await page
.getByTestId("handle-groupnode-shownode-combined text-right") .getByTestId("handle-groupnode-shownode-combined text-right")
.nth(0); .nth(0);
await elementGroupOutput.hover(); await elementGroupOutput.click();
await page.mouse.down();
const elementTextOutputInput = await page const elementTextOutputInput = await page
.getByTestId("handle-textoutput-shownode-text-left") .getByTestId("handle-textoutput-shownode-text-left")
.nth(0); .nth(0);
await elementTextOutputInput.hover(); await elementTextOutputInput.click();
await page.mouse.up();
await page.getByTestId("textarea_str_input_value").nth(0).fill(randomName); await page.getByTestId("textarea_str_input_value").nth(0).fill(randomName);
await page.waitForTimeout(1000); await page.waitForTimeout(500);
await page await page
.getByTestId("textarea_str_input_value") .getByTestId("textarea_str_input_value")
.nth(1) .nth(1)
.fill(secondRandomName); .fill(secondRandomName);
await page.waitForTimeout(1000); await page.waitForTimeout(500);
await page await page
.getByPlaceholder("Type something...", { exact: true }) .getByPlaceholder("Type something...", { exact: true })
.nth(6) .nth(6)
.fill(thirdRandomName); .fill(thirdRandomName);
await page.waitForTimeout(1000); await page.waitForTimeout(500);
await page await page
.getByPlaceholder("Type something...", { exact: true }) .getByPlaceholder("Type something...", { exact: true })
.nth(3) .nth(3)
.fill("-"); .fill("-");
await page.waitForTimeout(1000); await page.waitForTimeout(500);
await page await page
.getByPlaceholder("Type something...", { exact: true }) .getByPlaceholder("Type something...", { exact: true })
.nth(4) .nth(4)
.fill("-"); .fill("-");
await page.waitForTimeout(3000); await page.waitForTimeout(500);
await page.getByTestId("button_run_text output").last().click(); await page.getByTestId("button_run_text output").last().click();
@ -227,13 +324,13 @@ test("should be able to see output preview from grouped components", async ({
await page.getByText("built successfully").last().click({ await page.getByText("built successfully").last().click({
timeout: 15000, timeout: 15000,
}); });
await page.waitForTimeout(3000); await page.waitForTimeout(500);
expect( expect(
await page.getByTestId("output-inspection-combined text").first(), await page.getByTestId("output-inspection-combined text").first(),
).not.toBeDisabled(); ).not.toBeDisabled();
await page.getByTestId("output-inspection-combined text").first().click(); await page.getByTestId("output-inspection-combined text").first().click();
await page.waitForTimeout(1000); await page.waitForTimeout(500);
await page.getByText("Component Output").isVisible(); await page.getByText("Component Output").isVisible();