diff --git a/src/frontend/src/CustomNodes/GenericNode/components/NodeInputField/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/NodeInputField/index.tsx index 362dad546..9a337e228 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/NodeInputField/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/NodeInputField/index.tsx @@ -9,6 +9,7 @@ import { import useAuthStore from "@/stores/authStore"; import { cn } from "@/utils/utils"; import { useEffect, useMemo, useRef } from "react"; +import { useShallow } from "zustand/react/shallow"; import { default as IconComponent } from "../../../../components/common/genericIconComponent"; import ShadTooltip from "../../../../components/common/shadTooltipComponent"; import { @@ -20,7 +21,6 @@ import { import useFlowStore from "../../../../stores/flowStore"; import { useTypesStore } from "../../../../stores/typesStore"; import { NodeInputFieldComponentType } from "../../../../types/components"; -import { scapedJSONStringfy } from "../../../../utils/reactflowUtils"; import useFetchDataOnMount from "../../../hooks/use-fetch-data-on-mount"; import useHandleOnNewValue from "../../../hooks/use-handle-new-value"; import NodeInputInfo from "../NodeInputInfo"; @@ -44,10 +44,13 @@ export default function NodeInputField({ isToolMode = false, }: NodeInputFieldComponentType): JSX.Element { const ref = useRef(null); - const nodes = useFlowStore((state) => state.nodes); - const edges = useFlowStore((state) => state.edges); const isAuth = useAuthStore((state) => state.isAuthenticated); - const currentFlow = useFlowStore((state) => state.currentFlow); + const { currentFlowId, currentFlowName } = useFlowStore( + useShallow((state) => ({ + currentFlowId: state.currentFlow?.id, + currentFlowName: state.currentFlow?.name, + })), + ); const myData = useTypesStore((state) => state.data); const postTemplateValue = usePostTemplateValue({ node: data.node!, @@ -56,11 +59,6 @@ export default function NodeInputField({ }); const setFilterEdge = useFlowStore((state) => state.setFilterEdge); const { handleNodeClass } = useHandleNodeClass(data.id); - let disabled = - edges.some( - (edge) => - edge.targetHandle === scapedJSONStringfy(proxy ? { ...id, proxy } : id), - ) || isToolMode; const { handleOnNewValue } = useHandleOnNewValue({ node: data.node!, @@ -74,9 +72,9 @@ export default function NodeInputField({ const nodeInformationMetadata: NodeInfoType = useMemo(() => { return { - flowId: currentFlow?.id ?? "", + flowId: currentFlowId ?? "", nodeType: data?.type?.toLowerCase() ?? "", - flowName: currentFlow?.name ?? "", + flowName: currentFlowName ?? "", isAuth, variableName: name, }; @@ -107,12 +105,10 @@ export default function NodeInputField({ const Handle = ( )} diff --git a/src/frontend/src/CustomNodes/GenericNode/components/NodeOutputParameter/NodeOutputs.tsx b/src/frontend/src/CustomNodes/GenericNode/components/NodeOutputParameter/NodeOutputs.tsx new file mode 100644 index 000000000..09f622575 --- /dev/null +++ b/src/frontend/src/CustomNodes/GenericNode/components/NodeOutputParameter/NodeOutputs.tsx @@ -0,0 +1,39 @@ +// NodeOutputs.tsx +import { OutputParameter } from "."; + +export default function NodeOutputs({ + outputs, + keyPrefix, + data, + types, + selected, + showNode, + isToolMode, + showHiddenOutputs, +}) { + if (!outputs?.length) return null; + + return outputs?.map((output, idx) => ( + out.name === output.name) ?? idx + } + lastOutput={idx === outputs.length - 1} + data={data} + types={types} + selected={selected} + showNode={showNode} + isToolMode={isToolMode} + showHiddenOutputs={showHiddenOutputs} + hidden={ + keyPrefix === "hidden" + ? showHiddenOutputs + ? output.hidden + : true + : false + } + /> + )); +} diff --git a/src/frontend/src/CustomNodes/GenericNode/components/NodeOutputfield/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/NodeOutputfield/index.tsx index 785eac633..b9771bafe 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/NodeOutputfield/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/NodeOutputfield/index.tsx @@ -177,7 +177,6 @@ function NodeOutputField({ const updateNodeInternals = useUpdateNodeInternals(); // Use selective store subscriptions - const nodes = useFlowStore((state) => state.nodes); const edges = useFlowStore((state) => state.edges); const setNode = useFlowStore((state) => state.setNode); const setFilterEdge = useFlowStore((state) => state.setFilterEdge); @@ -316,11 +315,9 @@ function NodeOutputField({ return ( ( ), [ - nodes, tooltipTitle, id, title, - edges, data.id, myData, colors, diff --git a/src/frontend/src/CustomNodes/GenericNode/components/handleRenderComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/handleRenderComponent/index.tsx index 8179feb09..74e3fc6b4 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/handleRenderComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/handleRenderComponent/index.tsx @@ -151,12 +151,10 @@ const HandleContent = memo(function HandleContent({ const HandleRenderComponent = memo(function HandleRenderComponent({ left, - nodes, tooltipTitle = "", proxy, id, title, - edges, myData, colors, setFilterEdge, @@ -166,12 +164,10 @@ const HandleRenderComponent = memo(function HandleRenderComponent({ colorName, }: { left: boolean; - nodes: any; tooltipTitle?: string; proxy?: any; id: any; title: string; - edges: any; myData: any; colors: string[]; setFilterEdge: (edges: any) => void; @@ -209,20 +205,17 @@ const HandleRenderComponent = memo(function HandleRenderComponent({ [id, proxy], ); - const getConnection = useCallback( - (semiConnection: { - source?: string; - sourceHandle?: string; - target?: string; - targetHandle?: string; - }) => ({ - source: semiConnection.source ?? nodeId, - sourceHandle: semiConnection.sourceHandle ?? myId, - target: semiConnection.target ?? nodeId, - targetHandle: semiConnection.targetHandle ?? myId, - }), - [nodeId, myId], - ); + const getConnection = (semiConnection: { + source?: string; + sourceHandle?: string; + target?: string; + targetHandle?: string; + }) => ({ + source: semiConnection.source ?? nodeId, + sourceHandle: semiConnection.sourceHandle ?? myId, + target: semiConnection.target ?? nodeId, + targetHandle: semiConnection.targetHandle ?? myId, + }); const { sameNode, @@ -255,25 +248,26 @@ const HandleRenderComponent = memo(function HandleRenderComponent({ handleDragging && (left ? handleDragging.source : handleDragging.target) && !ownDraggingHandle - ? isValidConnection(getConnection(handleDragging), nodes, edges) + ? isValidConnection(getConnection(handleDragging)) : false; const filterOpenHandle = filterType && (left ? filterType.source : filterType.target) && !ownFilterHandle - ? isValidConnection(getConnection(filterType), nodes, edges) + ? isValidConnection(getConnection(filterType)) : false; const openHandle = filterOpenHandle || draggingOpenHandle; 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 connectedEdge = useFlowStore + .getState() + .edges.find( + (edge) => edge.target === nodeId && edge.targetHandle === myId, + ); + const outputType = connectedEdge?.data?.sourceHandle?.output_types?.[0]; + const connectedColor = outputType ? nodeColorsName[outputType] : "gray"; const isNullHandle = filterPresent && !(openHandle || ownDraggingHandle || ownFilterHandle); @@ -341,9 +335,6 @@ const HandleRenderComponent = memo(function HandleRenderComponent({ filterType, nodeId, myId, - nodes, - edges, - getConnection, dark, colors, colorName, @@ -365,6 +356,7 @@ const HandleRenderComponent = memo(function HandleRenderComponent({ ); const handleClick = useCallback(() => { + const nodes = useFlowStore.getState().nodes; setFilterEdge(groupByFamily(myData, tooltipTitle!, left, nodes!)); setFilterType(currentFilter); if (filterOpenHandle && filterType) { @@ -376,14 +368,12 @@ const HandleRenderComponent = memo(function HandleRenderComponent({ myData, tooltipTitle, left, - nodes, setFilterEdge, setFilterType, currentFilter, filterOpenHandle, filterType, onConnect, - getConnection, ]); const handleMouseEnter = useCallback(() => setIsHovered(true), []); @@ -396,8 +386,8 @@ const HandleRenderComponent = memo(function HandleRenderComponent({ // Memoize the validation function const validateConnection = useCallback( - (connection: any) => isValidConnection(connection, nodes, edges), - [nodes, edges], + (connection: any) => isValidConnection(connection), + [], ); return ( @@ -424,7 +414,7 @@ const HandleRenderComponent = memo(function HandleRenderComponent({ position={left ? Position.Left : Position.Right} id={myId} isValidConnection={(connection) => - isValidConnection(connection as Connection, nodes, edges) + isValidConnection(connection as Connection) } className={cn( `group/handle z-50 transition-all`, diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index 9c1f21660..7a29b8c8b 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -24,25 +24,24 @@ import { useShortcutsStore } from "../../stores/shortcuts"; import { useTypesStore } from "../../stores/typesStore"; import { VertexBuildTypeAPI } from "../../types/api"; import { NodeDataType } from "../../types/flow"; -import { checkHasToolMode } from "../../utils/reactflowUtils"; import { classNames, cn } from "../../utils/utils"; import { processNodeAdvancedFields } from "../helpers/process-node-advanced-fields"; import useUpdateNodeCode from "../hooks/use-update-node-code"; import NodeDescription from "./components/NodeDescription"; import NodeName from "./components/NodeName"; -import { OutputParameter } from "./components/NodeOutputParameter"; +import NodeOutputs from "./components/NodeOutputParameter/NodeOutputs"; import NodeStatus from "./components/NodeStatus"; import NodeUpdateComponent from "./components/NodeUpdateComponent"; import RenderInputParameters from "./components/RenderInputParameters"; import { NodeIcon } from "./components/nodeIcon"; import { useBuildStatus } from "./hooks/use-get-build-status"; -const MemoizedOutputParameter = memo(OutputParameter); const MemoizedRenderInputParameters = memo(RenderInputParameters); const MemoizedNodeIcon = memo(NodeIcon); const MemoizedNodeName = memo(NodeName); const MemoizedNodeStatus = memo(CustomNodeStatus); const MemoizedNodeDescription = memo(NodeDescription); +const MemoizedNodeOutputs = memo(NodeOutputs); const HiddenOutputsButton = memo( ({ @@ -104,10 +103,10 @@ function GenericNode({ const showNode = data.showNode ?? true; - const getValidationStatus = (data) => { + const getValidationStatus = useCallback((data) => { setValidationStatus(data); return null; - }; + }, []); const { mutate: validateComponentCode } = usePostValidateComponentCode(); @@ -246,45 +245,18 @@ function GenericNode({ callback: toggleEditNameDescription, }); - const renderOutputs = useCallback( - (outputs, key?: string) => { - return outputs?.map((output, idx) => ( - out.name === output.name) ?? - idx - } - lastOutput={idx === outputs.length - 1} - data={data} - types={types} - selected={selected} - showNode={showNode} - isToolMode={isToolMode} - showHiddenOutputs={showHiddenOutputs} - hidden={ - key === "hidden" - ? showHiddenOutputs - ? output.hidden - : true - : false - } - /> - )); - }, - [data, types, selected, showNode, isToolMode, showHiddenOutputs], - ); - - const { shownOutputs, hiddenOutputs } = useMemo( - () => ({ - shownOutputs: - data.node?.outputs?.filter((output) => !output.hidden) ?? [], - hiddenOutputs: - data.node?.outputs?.filter((output) => output.hidden) ?? [], - }), - [data.node?.outputs], - ); + const { shownOutputs, hiddenOutputs } = useMemo(() => { + const shownOutputs: typeof data.node.outputs = []; + const hiddenOutputs: typeof data.node.outputs = []; + (data.node?.outputs ?? []).forEach((output) => { + if (output.hidden) { + hiddenOutputs.push(output); + } else { + shownOutputs.push(output); + } + }); + return { shownOutputs, hiddenOutputs }; + }, [data.node?.outputs]); const [hasChangedNodeDescription, setHasChangedNodeDescription] = useState(false); @@ -391,114 +363,26 @@ function GenericNode({ toggleEditNameDescription, selectedNodesCount, ]); + useEffect(() => { if (hiddenOutputs && hiddenOutputs.length === 0) { setShowHiddenOutputs(false); } }, [hiddenOutputs]); - const renderNodeIcon = useCallback(() => { - return ( - - ); - }, [data.type, showNode, data.node?.icon, data.node?.flow]); + const handleToggleHiddenOutputs = useCallback( + () => setShowHiddenOutputs((prev) => !prev), + [], + ); - const renderNodeName = useCallback(() => { - return ( - - ); - }, [ - data.node?.display_name, - data.id, - selected, - showNode, - validationStatus, - isOutdated, - data.node?.beta, - editNameDescription, - toggleEditNameDescription, - setHasChangedNodeDescription, - ]); - - const renderNodeStatus = useCallback(() => { - return ( - - ); - }, [ - data, - showNode, - selected, - buildStatus, - isOutdated, - isUserEdited, - getValidationStatus, - dismissAll, - handleUpdateCode, - ]); - - const renderDescription = useCallback(() => { - return ( - - ); - }, [ - data.node?.description, - data.id, - selected, - editNameDescription, - toggleEditNameDescription, - setHasChangedNodeDescription, - ]); - - const renderInputParameters = useCallback(() => { - return ( - - ); - }, [data, types, isToolMode, showNode, shownOutputs, showHiddenOutputs]); + const memoizedOnUpdateNode = useCallback( + () => handleUpdateCode(true), + [handleUpdateCode], + ); + const memoizedSetDismissAll = useCallback( + () => addDismissedNodes([data.id]), + [addDismissedNodes, data.id], + ); return (
@@ -510,20 +394,22 @@ function GenericNode({ !hasOutputs && "pb-4", )} > - handleUpdateCode(true)} - components={componentUpdate ? [componentUpdate] : []} - /> + {openUpdateModal && ( + + )} {memoizedNodeToolbarComponent} {shouldShowUpdateComponent && ( handleUpdateCode()} + handleUpdateCode={handleUpdateCode} loadingUpdate={loadingUpdate} - setDismissAll={() => addDismissedNodes([data.id])} + setDismissAll={memoizedSetDismissAll} /> )}
- {renderNodeIcon()} +
- {renderNodeName()} +
{!showNode && ( <> - {renderInputParameters()} - {shownOutputs.length > 0 && - renderOutputs(shownOutputs, "render-outputs")} + + )}
- {renderNodeStatus()} +
- {showNode &&
{renderDescription()}
} + {showNode && ( +
+ +
+ )} {showNode && (
<> - {renderInputParameters()} +
{" "}
- {!showHiddenOutputs && - shownOutputs && - renderOutputs(shownOutputs, "shown")} + {!showHiddenOutputs && shownOutputs && ( + + )}
- {renderOutputs(data.node!.outputs, "hidden")} +
{hiddenOutputs && hiddenOutputs.length > 0 && ( @@ -606,7 +574,7 @@ function GenericNode({ > setShowHiddenOutputs(!showHiddenOutputs)} + onClick={handleToggleHiddenOutputs} />
diff --git a/src/frontend/src/customization/components/custom-parameter.tsx b/src/frontend/src/customization/components/custom-parameter.tsx index b2aedb841..c2f8e55c5 100644 --- a/src/frontend/src/customization/components/custom-parameter.tsx +++ b/src/frontend/src/customization/components/custom-parameter.tsx @@ -1,36 +1,50 @@ import { ParameterRenderComponent } from "@/components/core/parameterRenderComponent"; import { NodeInfoType } from "@/components/core/parameterRenderComponent/types"; import { handleOnNewValueType } from "@/CustomNodes/hooks/use-handle-new-value"; +import useFlowStore from "@/stores/flowStore"; import { APIClassType, InputFieldType } from "@/types/api"; +import { targetHandleType } from "@/types/flow"; +import { scapedJSONStringfy } from "@/utils/reactflowUtils"; import { cn } from "@/utils/utils"; export function CustomParameterComponent({ handleOnNewValue, name, nodeId, + inputId, templateData, templateValue, editNode, handleNodeClass, nodeClass, - disabled, placeholder, - isToolMode, + isToolMode = false, nodeInformationMetadata, + proxy, }: { handleOnNewValue: handleOnNewValueType; name: string; nodeId: string; + inputId: targetHandleType; templateData: Partial; templateValue: any; editNode: boolean; handleNodeClass: (value: any, code?: string, type?: string) => void; nodeClass: APIClassType; - disabled: boolean; placeholder?: string; isToolMode?: boolean; nodeInformationMetadata?: NodeInfoType; + proxy: { field: string; id: string } | undefined; }) { + const edges = useFlowStore((state) => state.edges); + + let disabled = + edges.some( + (edge) => + edge.targetHandle === + scapedJSONStringfy(proxy ? { ...inputId, proxy } : inputId), + ) || isToolMode; + return ( state.paste); - const nodes = useFlowStore((state) => state.nodes); - const edges = useFlowStore((state) => state.edges); const setNodes = useFlowStore((state) => state.setNodes); const setEdges = useFlowStore((state) => state.setEdges); const getNodePosition = useFlowStore((state) => state.getNodePosition); @@ -200,8 +198,6 @@ const NodeToolbarComponent = memo( data.id, updateFlowPosition(getNodePosition(data.id), data.node?.flow!), data.node!.template, - nodes, - edges, setNodes, setEdges, data.node?.outputs, @@ -213,8 +209,6 @@ const NodeToolbarComponent = memo( data.node?.flow, data.node?.template, data.node?.outputs, - nodes, - edges, setNodes, setEdges, takeSnapshot, @@ -307,6 +301,7 @@ const NodeToolbarComponent = memo( const handleSelectChange = useCallback( (event) => { + let nodes; setSelectedValue(event); switch (event) { @@ -356,10 +351,12 @@ const NodeToolbarComponent = memo( updateNode(); break; case "copy": + nodes = useFlowStore.getState().nodes; const node = nodes.filter((node) => node.id === data.id); setLastCopiedSelection({ nodes: _.cloneDeep(node), edges: [] }); break; case "duplicate": + nodes = useFlowStore.getState().nodes; paste( { nodes: [nodes.find((node) => node.id === data.id)!], diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index dea4103cf..2c79d13c7 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -17,6 +17,7 @@ import { } from "@/CustomNodes/utils/get-handle-id"; import { INCOMPLETE_LOOP_ERROR_ALERT } from "@/constants/alerts_constants"; import { customDownloadFlow } from "@/customization/utils/custom-reactFlowUtils"; +import useFlowStore from "@/stores/flowStore"; import { Connection, Edge, @@ -320,12 +321,16 @@ export function unselectAllNodesEdges(nodes: Node[], edges: Edge[]) { export function isValidConnection( { source, target, sourceHandle, targetHandle }: Connection, - nodes: AllNodeType[], - edges: EdgeType[], + nodes?: AllNodeType[], + edges?: EdgeType[], ): boolean { if (source === target) { return false; } + + const nodesArray = nodes || useFlowStore.getState().nodes; + const edgesArray = edges || useFlowStore.getState().edges; + const targetHandleObject: targetHandleType = scapeJSONParse(targetHandle!); const sourceHandleObject: sourceHandleType = scapeJSONParse(sourceHandle!); if ( @@ -345,20 +350,20 @@ export function isValidConnection( t === targetHandleObject.type, ) ) { - let targetNode = nodes.find((node) => node.id === target!)?.data?.node; + let targetNode = nodesArray.find((node) => node.id === target!)?.data?.node; if (!targetNode) { - if (!edges.find((e) => e.targetHandle === targetHandle)) { + if (!edgesArray.find((e) => e.targetHandle === targetHandle)) { return true; } } else if ( targetHandleObject.output_types && - !edges.find((e) => e.targetHandle === targetHandle) + !edgesArray.find((e) => e.targetHandle === targetHandle) ) { return true; } else if ( !targetHandleObject.output_types && ((!targetNode.template[targetHandleObject.fieldName].list && - !edges.find((e) => e.targetHandle === targetHandle)) || + !edgesArray.find((e) => e.targetHandle === targetHandle)) || targetNode.template[targetHandleObject.fieldName].list) ) { return true; @@ -1476,8 +1481,6 @@ export function expandGroupNode( id: string, flow: FlowType, template: APITemplateType, - nodes: AllNodeType[], - edges: EdgeType[], setNodes: ( update: AllNodeType[] | ((oldState: AllNodeType[]) => AllNodeType[]), ) => void, @@ -1488,7 +1491,7 @@ export function expandGroupNode( ) { const idsMap = updateIds(flow!.data!); updateProxyIdsOnTemplate(template, idsMap); - let flowEdges = edges; + let flowEdges = useFlowStore.getState().edges; updateEdgesIds(flowEdges, idsMap); const gNodes: AllNodeType[] = cloneDeep(flow?.data?.nodes!); const gEdges = cloneDeep(flow!.data!.edges); @@ -1588,9 +1591,12 @@ export function expandGroupNode( } } }); - const filteredNodes = [...nodes.filter((n) => n.id !== id), ...gNodes]; + const filteredNodes = [ + ...useFlowStore.getState().nodes.filter((n) => n.id !== id), + ...gNodes, + ]; const filteredEdges = [ - ...edges.filter((e) => e.target !== id && e.source !== id), + ...flowEdges.filter((e) => e.target !== id && e.source !== id), ...gEdges, ]; setNodes(filteredNodes);