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>
This commit is contained in:
parent
8c0813f3d8
commit
d887646383
5 changed files with 90 additions and 131 deletions
|
|
@ -13,7 +13,8 @@ import {
|
||||||
type ReactFlowState,
|
type ReactFlowState,
|
||||||
} from "@xyflow/react";
|
} from "@xyflow/react";
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
import { useEffect } from "react";
|
import { useCallback, useEffect } from "react";
|
||||||
|
import { useShallow } from "zustand/react/shallow";
|
||||||
import { shallow } from "zustand/shallow";
|
import { shallow } from "zustand/shallow";
|
||||||
|
|
||||||
type CustomControlButtonProps = {
|
type CustomControlButtonProps = {
|
||||||
|
|
@ -70,20 +71,22 @@ const CanvasControls = ({ children }) => {
|
||||||
shallow,
|
shallow,
|
||||||
);
|
);
|
||||||
const saveFlow = useSaveFlow();
|
const saveFlow = useSaveFlow();
|
||||||
const currentFlow = useFlowStore((state) => state.currentFlow);
|
const isLocked = useFlowStore(
|
||||||
|
useShallow((state) => state.currentFlow?.locked),
|
||||||
|
);
|
||||||
const setCurrentFlow = useFlowStore((state) => state.setCurrentFlow);
|
const setCurrentFlow = useFlowStore((state) => state.setCurrentFlow);
|
||||||
const autoSaving = useFlowsManagerStore((state) => state.autoSaving);
|
const autoSaving = useFlowsManagerStore((state) => state.autoSaving);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const isLocked = currentFlow?.locked;
|
|
||||||
store.setState({
|
store.setState({
|
||||||
nodesDraggable: !isLocked,
|
nodesDraggable: !isLocked,
|
||||||
nodesConnectable: !isLocked,
|
nodesConnectable: !isLocked,
|
||||||
elementsSelectable: !isLocked,
|
elementsSelectable: !isLocked,
|
||||||
});
|
});
|
||||||
}, [currentFlow?.locked]);
|
}, [isLocked]);
|
||||||
|
|
||||||
const handleSaveFlow = () => {
|
const handleSaveFlow = useCallback(() => {
|
||||||
|
const currentFlow = useFlowStore.getState().currentFlow;
|
||||||
if (!currentFlow) return;
|
if (!currentFlow) return;
|
||||||
const newFlow = cloneDeep(currentFlow);
|
const newFlow = cloneDeep(currentFlow);
|
||||||
newFlow.locked = isInteractive;
|
newFlow.locked = isInteractive;
|
||||||
|
|
@ -92,16 +95,16 @@ const CanvasControls = ({ children }) => {
|
||||||
} else {
|
} else {
|
||||||
setCurrentFlow(newFlow);
|
setCurrentFlow(newFlow);
|
||||||
}
|
}
|
||||||
};
|
}, [isInteractive, autoSaving, saveFlow, setCurrentFlow]);
|
||||||
|
|
||||||
const onToggleInteractivity = () => {
|
const onToggleInteractivity = useCallback(() => {
|
||||||
store.setState({
|
store.setState({
|
||||||
nodesDraggable: !isInteractive,
|
nodesDraggable: !isInteractive,
|
||||||
nodesConnectable: !isInteractive,
|
nodesConnectable: !isInteractive,
|
||||||
elementsSelectable: !isInteractive,
|
elementsSelectable: !isInteractive,
|
||||||
});
|
});
|
||||||
handleSaveFlow();
|
handleSaveFlow();
|
||||||
};
|
}, [isInteractive, store, handleSaveFlow]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Panel
|
<Panel
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import ShadTooltip from "@/components/common/shadTooltipComponent";
|
import ShadTooltip from "@/components/common/shadTooltipComponent";
|
||||||
import { track } from "@/customization/utils/analytics";
|
import { track } from "@/customization/utils/analytics";
|
||||||
import { Panel } from "@xyflow/react";
|
import { Panel } from "@xyflow/react";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { memo, useEffect, useMemo, useState } from "react";
|
||||||
import { useHotkeys } from "react-hotkeys-hook";
|
import { useHotkeys } from "react-hotkeys-hook";
|
||||||
import ShareModal from "../../../modals/shareModal";
|
import ShareModal from "../../../modals/shareModal";
|
||||||
import useFlowStore from "../../../stores/flowStore";
|
import useFlowStore from "../../../stores/flowStore";
|
||||||
|
|
@ -11,7 +11,7 @@ import { classNames, cn, isThereModal } from "../../../utils/utils";
|
||||||
import ForwardedIconComponent from "../../common/genericIconComponent";
|
import ForwardedIconComponent from "../../common/genericIconComponent";
|
||||||
import FlowToolbarOptions from "./components/flow-toolbar-options";
|
import FlowToolbarOptions from "./components/flow-toolbar-options";
|
||||||
|
|
||||||
export default function FlowToolbar(): JSX.Element {
|
const FlowToolbar = memo(function FlowToolbar(): JSX.Element {
|
||||||
const preventDefault = true;
|
const preventDefault = true;
|
||||||
const [open, setOpen] = useState<boolean>(false);
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
const [openCodeModal, setOpenCodeModal] = useState<boolean>(false);
|
const [openCodeModal, setOpenCodeModal] = useState<boolean>(false);
|
||||||
|
|
@ -41,75 +41,12 @@ export default function FlowToolbar(): JSX.Element {
|
||||||
useHotkeys(api, handleAPIWShortcut, { preventDefault });
|
useHotkeys(api, handleAPIWShortcut, { preventDefault });
|
||||||
useHotkeys(flow, handleShareWShortcut, { 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(() => {
|
useEffect(() => {
|
||||||
if (open) {
|
if (open) {
|
||||||
track("Playground Button Clicked");
|
track("Playground Button Clicked");
|
||||||
}
|
}
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
const ModalMemo = useMemo(
|
|
||||||
() => (
|
|
||||||
<ShareModal
|
|
||||||
is_component={false}
|
|
||||||
component={currentFlow!}
|
|
||||||
disabled={!hasApiKey || !validApiKey || !hasStore}
|
|
||||||
open={openShareModal}
|
|
||||||
setOpen={setOpenShareModal}
|
|
||||||
>
|
|
||||||
<ShadTooltip
|
|
||||||
content={
|
|
||||||
!hasApiKey || !validApiKey || !hasStore
|
|
||||||
? "Store API Key Required"
|
|
||||||
: ""
|
|
||||||
}
|
|
||||||
side="bottom"
|
|
||||||
align="end"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
disabled={!hasApiKey || !validApiKey || !hasStore}
|
|
||||||
className={classNames(
|
|
||||||
"relative inline-flex h-8 w-full items-center justify-center gap-1.5 rounded px-3 py-1.5 text-sm font-semibold text-foreground transition-all duration-150 ease-in-out",
|
|
||||||
!hasApiKey || !validApiKey || !hasStore
|
|
||||||
? "cursor-not-allowed text-muted-foreground"
|
|
||||||
: "hover:bg-accent",
|
|
||||||
)}
|
|
||||||
data-testid="shared-button-flow"
|
|
||||||
onClick={() => {
|
|
||||||
setOpenShareModal(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<>
|
|
||||||
<ForwardedIconComponent
|
|
||||||
name="Share2"
|
|
||||||
className={classNames(
|
|
||||||
"h-4 w-4",
|
|
||||||
!hasApiKey || !validApiKey || !hasStore
|
|
||||||
? "extra-side-bar-save-disable"
|
|
||||||
: "",
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<span className="hidden md:block">Share</span>
|
|
||||||
</>
|
|
||||||
</button>
|
|
||||||
</ShadTooltip>
|
|
||||||
</ShareModal>
|
|
||||||
),
|
|
||||||
[
|
|
||||||
hasApiKey,
|
|
||||||
validApiKey,
|
|
||||||
currentFlow,
|
|
||||||
hasStore,
|
|
||||||
openShareModal,
|
|
||||||
setOpenShareModal,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Panel className="!m-2" position="top-right">
|
<Panel className="!m-2" position="top-right">
|
||||||
|
|
@ -123,4 +60,6 @@ export default function FlowToolbar(): JSX.Element {
|
||||||
</Panel>
|
</Panel>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
||||||
|
export default FlowToolbar;
|
||||||
|
|
|
||||||
|
|
@ -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(() => (
|
||||||
|
<Background size={2} gap={20} className="" />
|
||||||
|
));
|
||||||
|
|
||||||
|
interface MemoizedCanvasControlsProps {
|
||||||
|
setIsAddingNote: (value: boolean) => void;
|
||||||
|
position: { x: number; y: number };
|
||||||
|
shadowBoxWidth: number;
|
||||||
|
shadowBoxHeight: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MemoizedCanvasControls = memo(
|
||||||
|
({
|
||||||
|
setIsAddingNote,
|
||||||
|
position,
|
||||||
|
shadowBoxWidth,
|
||||||
|
shadowBoxHeight,
|
||||||
|
}: MemoizedCanvasControlsProps) => (
|
||||||
|
<CanvasControls>
|
||||||
|
<CustomControlButton
|
||||||
|
iconName="sticky-note"
|
||||||
|
tooltipText="Add Note"
|
||||||
|
onClick={() => {
|
||||||
|
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"
|
||||||
|
/>
|
||||||
|
</CanvasControls>
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const MemoizedSidebarTrigger = memo(() => (
|
||||||
|
<Panel
|
||||||
|
className={cn(
|
||||||
|
"react-flow__controls !m-2 flex gap-1.5 rounded-md border border-secondary-hover bg-background fill-foreground stroke-foreground p-1.5 text-primary shadow transition-all duration-300 [&>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"
|
||||||
|
>
|
||||||
|
<SidebarTrigger className="h-fit w-fit px-3 py-1.5">
|
||||||
|
<ForwardedIconComponent name="PanelRightClose" className="h-4 w-4" />
|
||||||
|
<span className="text-foreground">Components</span>
|
||||||
|
</SidebarTrigger>
|
||||||
|
</Panel>
|
||||||
|
));
|
||||||
|
|
@ -1,12 +1,6 @@
|
||||||
import { DefaultEdge } from "@/CustomEdges";
|
import { DefaultEdge } from "@/CustomEdges";
|
||||||
import NoteNode from "@/CustomNodes/NoteNode";
|
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 FlowToolbar from "@/components/core/flowToolbarComponent";
|
||||||
import { SidebarTrigger } from "@/components/ui/sidebar";
|
|
||||||
import {
|
import {
|
||||||
COLOR_OPTIONS,
|
COLOR_OPTIONS,
|
||||||
NOTE_NODE_MIN_HEIGHT,
|
NOTE_NODE_MIN_HEIGHT,
|
||||||
|
|
@ -21,12 +15,10 @@ import { useAddComponent } from "@/hooks/use-add-component";
|
||||||
import { nodeColorsName } from "@/utils/styleUtils";
|
import { nodeColorsName } from "@/utils/styleUtils";
|
||||||
import { cn, isSupportedNodeTypes } from "@/utils/utils";
|
import { cn, isSupportedNodeTypes } from "@/utils/utils";
|
||||||
import {
|
import {
|
||||||
Background,
|
|
||||||
Connection,
|
Connection,
|
||||||
Edge,
|
Edge,
|
||||||
OnNodeDrag,
|
OnNodeDrag,
|
||||||
OnSelectionChangeParams,
|
OnSelectionChangeParams,
|
||||||
Panel,
|
|
||||||
ReactFlow,
|
ReactFlow,
|
||||||
reconnectEdge,
|
reconnectEdge,
|
||||||
SelectionDragHandler,
|
SelectionDragHandler,
|
||||||
|
|
@ -67,6 +59,11 @@ import {
|
||||||
import ConnectionLineComponent from "../ConnectionLineComponent";
|
import ConnectionLineComponent from "../ConnectionLineComponent";
|
||||||
import SelectionMenu from "../SelectionMenuComponent";
|
import SelectionMenu from "../SelectionMenuComponent";
|
||||||
import UpdateAllComponents from "../UpdateAllComponents";
|
import UpdateAllComponents from "../UpdateAllComponents";
|
||||||
|
import {
|
||||||
|
MemoizedBackground,
|
||||||
|
MemoizedCanvasControls,
|
||||||
|
MemoizedSidebarTrigger,
|
||||||
|
} from "./MemoizedComponents";
|
||||||
import getRandomName from "./utils/get-random-name";
|
import getRandomName from "./utils/get-random-name";
|
||||||
import isWrappedWithClass from "./utils/is-wrapped-with-class";
|
import isWrappedWithClass from "./utils/is-wrapped-with-class";
|
||||||
|
|
||||||
|
|
@ -591,44 +588,19 @@ export default function Page({
|
||||||
onPaneClick={onPaneClick}
|
onPaneClick={onPaneClick}
|
||||||
onEdgeClick={handleEdgeClick}
|
onEdgeClick={handleEdgeClick}
|
||||||
>
|
>
|
||||||
<Background size={2} gap={20} className="" />
|
<MemoizedBackground />
|
||||||
{!view && (
|
{!view && (
|
||||||
<>
|
<>
|
||||||
<CanvasControls>
|
<MemoizedCanvasControls
|
||||||
<CustomControlButton
|
setIsAddingNote={setIsAddingNote}
|
||||||
iconName="sticky-note"
|
position={position.current}
|
||||||
tooltipText="Add Note"
|
shadowBoxWidth={shadowBoxWidth}
|
||||||
onClick={() => {
|
shadowBoxHeight={shadowBoxHeight}
|
||||||
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"
|
|
||||||
/>
|
|
||||||
</CanvasControls>
|
|
||||||
<FlowToolbar />
|
<FlowToolbar />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Panel
|
<MemoizedSidebarTrigger />
|
||||||
className={cn(
|
|
||||||
"react-flow__controls !m-2 flex gap-1.5 rounded-md border border-secondary-hover bg-background fill-foreground stroke-foreground p-1.5 text-primary shadow transition-all duration-300 [&>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"
|
|
||||||
>
|
|
||||||
<SidebarTrigger className="h-fit w-fit px-3 py-1.5">
|
|
||||||
<ForwardedIconComponent
|
|
||||||
name="PanelRightClose"
|
|
||||||
className="h-4 w-4"
|
|
||||||
/>
|
|
||||||
<span className="text-foreground">Components</span>
|
|
||||||
</SidebarTrigger>
|
|
||||||
</Panel>
|
|
||||||
<div className={cn(componentsToUpdate.length === 0 && "hidden")}>
|
<div className={cn(componentsToUpdate.length === 0 && "hidden")}>
|
||||||
<UpdateAllComponents />
|
<UpdateAllComponents />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,6 @@ const NodeToolbarComponent = memo(
|
||||||
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
|
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
|
||||||
const [openModal, setOpenModal] = useState(false);
|
const [openModal, setOpenModal] = useState(false);
|
||||||
const frozen = data.node?.frozen ?? false;
|
const frozen = data.node?.frozen ?? false;
|
||||||
const currentFlow = useFlowStore((state) => state.currentFlow);
|
|
||||||
const updateNodeInternals = useUpdateNodeInternals();
|
const updateNodeInternals = useUpdateNodeInternals();
|
||||||
|
|
||||||
const paste = useFlowStore((state) => state.paste);
|
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({
|
const postToolModeValue = usePostTemplateValue({
|
||||||
node: data.node!,
|
node: data.node!,
|
||||||
nodeId: data.id,
|
nodeId: data.id,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue