From d88764638372cbd5055f98f13469810f100f7d87 Mon Sep 17 00:00:00 2001 From: Mike Fortman Date: Wed, 7 May 2025 09:12:22 -0500 Subject: [PATCH] refactor: performance improvements for canvas controls/toolbar (#7930) * performance improvements for canvas controls/toolbar * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../core/canvasControlsComponent/index.tsx | 19 ++--- .../core/flowToolbarComponent/index.tsx | 71 ++----------------- .../PageComponent/MemoizedComponents.tsx | 61 ++++++++++++++++ .../components/PageComponent/index.tsx | 54 ++++---------- .../components/nodeToolbarComponent/index.tsx | 16 ----- 5 files changed, 90 insertions(+), 131 deletions(-) create mode 100644 src/frontend/src/pages/FlowPage/components/PageComponent/MemoizedComponents.tsx diff --git a/src/frontend/src/components/core/canvasControlsComponent/index.tsx b/src/frontend/src/components/core/canvasControlsComponent/index.tsx index 53cf7658c..1c3a9869c 100644 --- a/src/frontend/src/components/core/canvasControlsComponent/index.tsx +++ b/src/frontend/src/components/core/canvasControlsComponent/index.tsx @@ -13,7 +13,8 @@ import { type ReactFlowState, } from "@xyflow/react"; import { cloneDeep } from "lodash"; -import { useEffect } from "react"; +import { useCallback, useEffect } from "react"; +import { useShallow } from "zustand/react/shallow"; import { shallow } from "zustand/shallow"; type CustomControlButtonProps = { @@ -70,20 +71,22 @@ const CanvasControls = ({ children }) => { shallow, ); const saveFlow = useSaveFlow(); - const currentFlow = useFlowStore((state) => state.currentFlow); + const isLocked = useFlowStore( + useShallow((state) => state.currentFlow?.locked), + ); const setCurrentFlow = useFlowStore((state) => state.setCurrentFlow); const autoSaving = useFlowsManagerStore((state) => state.autoSaving); useEffect(() => { - const isLocked = currentFlow?.locked; store.setState({ nodesDraggable: !isLocked, nodesConnectable: !isLocked, elementsSelectable: !isLocked, }); - }, [currentFlow?.locked]); + }, [isLocked]); - const handleSaveFlow = () => { + const handleSaveFlow = useCallback(() => { + const currentFlow = useFlowStore.getState().currentFlow; if (!currentFlow) return; const newFlow = cloneDeep(currentFlow); newFlow.locked = isInteractive; @@ -92,16 +95,16 @@ const CanvasControls = ({ children }) => { } else { setCurrentFlow(newFlow); } - }; + }, [isInteractive, autoSaving, saveFlow, setCurrentFlow]); - const onToggleInteractivity = () => { + const onToggleInteractivity = useCallback(() => { store.setState({ nodesDraggable: !isInteractive, nodesConnectable: !isInteractive, elementsSelectable: !isInteractive, }); handleSaveFlow(); - }; + }, [isInteractive, store, handleSaveFlow]); return ( (false); const [openCodeModal, setOpenCodeModal] = useState(false); @@ -41,75 +41,12 @@ export default function FlowToolbar(): JSX.Element { useHotkeys(api, handleAPIWShortcut, { preventDefault }); useHotkeys(flow, handleShareWShortcut, { preventDefault }); - const hasIO = useFlowStore((state) => state.hasIO); - const hasStore = useStoreStore((state) => state.hasStore); - const validApiKey = useStoreStore((state) => state.validApiKey); - const hasApiKey = useStoreStore((state) => state.hasApiKey); - const currentFlow = useFlowStore((state) => state.currentFlow); - useEffect(() => { if (open) { track("Playground Button Clicked"); } }, [open]); - const ModalMemo = useMemo( - () => ( - - - - - - ), - [ - hasApiKey, - validApiKey, - currentFlow, - hasStore, - openShareModal, - setOpenShareModal, - ], - ); - return ( <> @@ -123,4 +60,6 @@ export default function FlowToolbar(): JSX.Element { ); -} +}); + +export default FlowToolbar; diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/MemoizedComponents.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/MemoizedComponents.tsx new file mode 100644 index 000000000..7d7c0110e --- /dev/null +++ b/src/frontend/src/pages/FlowPage/components/PageComponent/MemoizedComponents.tsx @@ -0,0 +1,61 @@ +import ForwardedIconComponent from "@/components/common/genericIconComponent"; +import CanvasControls, { + CustomControlButton, +} from "@/components/core/canvasControlsComponent"; +import { SidebarTrigger } from "@/components/ui/sidebar"; +import { cn } from "@/utils/utils"; +import { Background, Panel } from "@xyflow/react"; +import { memo } from "react"; + +export const MemoizedBackground = memo(() => ( + +)); + +interface MemoizedCanvasControlsProps { + setIsAddingNote: (value: boolean) => void; + position: { x: number; y: number }; + shadowBoxWidth: number; + shadowBoxHeight: number; +} + +export const MemoizedCanvasControls = memo( + ({ + setIsAddingNote, + position, + shadowBoxWidth, + shadowBoxHeight, + }: MemoizedCanvasControlsProps) => ( + + { + setIsAddingNote(true); + const shadowBox = document.getElementById("shadow-box"); + if (shadowBox) { + shadowBox.style.display = "block"; + shadowBox.style.left = `${position.x - shadowBoxWidth / 2}px`; + shadowBox.style.top = `${position.y - shadowBoxHeight / 2}px`; + } + }} + iconClasses="text-primary" + testId="add_note" + /> + + ), +); + +export const MemoizedSidebarTrigger = memo(() => ( + button]:border-0 [&>button]:bg-background hover:[&>button]:bg-accent", + "pointer-events-auto opacity-100 group-data-[open=true]/sidebar-wrapper:pointer-events-none group-data-[open=true]/sidebar-wrapper:-translate-x-full group-data-[open=true]/sidebar-wrapper:opacity-0", + )} + position="top-left" + > + + + Components + + +)); diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx index 0d4e7a1d2..55c976c3e 100644 --- a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx @@ -1,12 +1,6 @@ import { DefaultEdge } from "@/CustomEdges"; import NoteNode from "@/CustomNodes/NoteNode"; - -import ForwardedIconComponent from "@/components/common/genericIconComponent"; -import CanvasControls, { - CustomControlButton, -} from "@/components/core/canvasControlsComponent"; import FlowToolbar from "@/components/core/flowToolbarComponent"; -import { SidebarTrigger } from "@/components/ui/sidebar"; import { COLOR_OPTIONS, NOTE_NODE_MIN_HEIGHT, @@ -21,12 +15,10 @@ import { useAddComponent } from "@/hooks/use-add-component"; import { nodeColorsName } from "@/utils/styleUtils"; import { cn, isSupportedNodeTypes } from "@/utils/utils"; import { - Background, Connection, Edge, OnNodeDrag, OnSelectionChangeParams, - Panel, ReactFlow, reconnectEdge, SelectionDragHandler, @@ -67,6 +59,11 @@ import { import ConnectionLineComponent from "../ConnectionLineComponent"; import SelectionMenu from "../SelectionMenuComponent"; import UpdateAllComponents from "../UpdateAllComponents"; +import { + MemoizedBackground, + MemoizedCanvasControls, + MemoizedSidebarTrigger, +} from "./MemoizedComponents"; import getRandomName from "./utils/get-random-name"; import isWrappedWithClass from "./utils/is-wrapped-with-class"; @@ -591,44 +588,19 @@ export default function Page({ onPaneClick={onPaneClick} onEdgeClick={handleEdgeClick} > - + {!view && ( <> - - { - setIsAddingNote(true); - const shadowBox = document.getElementById("shadow-box"); - if (shadowBox) { - shadowBox.style.display = "block"; - shadowBox.style.left = `${position.current.x - shadowBoxWidth / 2}px`; - shadowBox.style.top = `${position.current.y - shadowBoxHeight / 2}px`; - } - }} - iconClasses="text-primary" - testId="add_note" - /> - + )} - button]:border-0 [&>button]:bg-background hover:[&>button]:bg-accent", - "pointer-events-auto opacity-100 group-data-[open=true]/sidebar-wrapper:pointer-events-none group-data-[open=true]/sidebar-wrapper:-translate-x-full group-data-[open=true]/sidebar-wrapper:opacity-0", - )} - position="top-left" - > - - - Components - - +
diff --git a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx index 93c8c3535..62133fbd1 100644 --- a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx @@ -73,7 +73,6 @@ const NodeToolbarComponent = memo( const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId); const [openModal, setOpenModal] = useState(false); const frozen = data.node?.frozen ?? false; - const currentFlow = useFlowStore((state) => state.currentFlow); const updateNodeInternals = useUpdateNodeInternals(); const paste = useFlowStore((state) => state.paste); @@ -93,21 +92,6 @@ const NodeToolbarComponent = memo( }, }); - const flowDataNodes = useMemo( - () => currentFlow?.data?.nodes, - [currentFlow], - ); - - const node = useMemo( - () => flowDataNodes?.find((n) => n.id === data.id), - [flowDataNodes, data.id], - ); - - const index = useMemo( - () => flowDataNodes?.indexOf(node!)!, - [flowDataNodes, node], - ); - const postToolModeValue = usePostTemplateValue({ node: data.node!, nodeId: data.id,