refactor: Reduce Sidebar Rerenders (#7699)

* menubar and hook optimizations

* sidebar optimizations

* [autofix.ci] apply automated fixes

* cleanup

* [autofix.ci] apply automated fixes

* sidebare button optimization

* 🐛 (typescript_test.yml): increase the maximum shard count to 40 to improve test distribution and performance

* Get state on focus instead of on keypress

* revert test shard change

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: cristhianzl <cristhian.lousa@gmail.com>
This commit is contained in:
Mike Fortman 2025-05-06 11:11:47 -05:00 • committed by GitHub
commit 710b6f99b5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 208 additions and 186 deletions

View file

@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate"; import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import useAddFlow from "@/hooks/flows/use-add-flow"; import useAddFlow from "@/hooks/flows/use-add-flow";
@ -35,7 +35,7 @@ import { cn, getNumberFromString } from "@/utils/utils";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import { useShallow } from "zustand/react/shallow"; import { useShallow } from "zustand/react/shallow";
export const MenuBar = ({}: {}): JSX.Element => { export const MenuBar = memo((): JSX.Element => {
const shortcuts = useShortcutsStore((state) => state.shortcuts); const shortcuts = useShortcutsStore((state) => state.shortcuts);
const addFlow = useAddFlow(); const addFlow = useAddFlow();
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);
@ -81,20 +81,10 @@ export const MenuBar = ({}: {}): JSX.Element => {
const [inputWidth, setInputWidth] = useState<number>(0); const [inputWidth, setInputWidth] = useState<number>(0);
const measureRef = useRef<HTMLSpanElement>(null); const measureRef = useRef<HTMLSpanElement>(null);
const changesNotSaved = useUnsavedChanges(); const changesNotSaved = useUnsavedChanges();
const [flowNames, setFlowNames] = useState<string[]>([]);
const { data: folders, isFetched: isFoldersFetched } = useGetFoldersQuery(); const { data: folders, isFetched: isFoldersFetched } = useGetFoldersQuery();
const flows = useFlowsManagerStore((state) => state.flows); const flows = useFlowsManagerStore((state) => state.flows);
const [nameLists, setNameList] = useState<string[]>([]);
useEffect(() => {
if (flows) {
const tempNameList: string[] = [];
flows.forEach((flow) => {
tempNameList.push(flow.name);
});
setNameList(tempNameList.filter((name) => name !== currentFlowName));
}
}, [flows, currentFlowName]);
useGetRefreshFlowsQuery( useGetRefreshFlowsQuery(
{ {
@ -162,17 +152,11 @@ export const MenuBar = ({}: {}): JSX.Element => {
const handleEditName = useCallback( const handleEditName = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => { (e: React.ChangeEvent<HTMLInputElement>) => {
const { value } = e.target; const { value } = e.target;
let invalid = false; const invalid = flowNames.includes(value);
for (let i = 0; i < nameLists.length; i++) {
if (value === nameLists[i]) {
invalid = true;
break;
}
}
setIsInvalidName(invalid); setIsInvalidName(invalid);
setFlowName(value); setFlowName(value);
}, },
[nameLists], [flowNames],
); );
const handleKeyDown = useCallback( const handleKeyDown = useCallback(
@ -195,7 +179,6 @@ export const MenuBar = ({}: {}): JSX.Element => {
flowName !== currentFlowName && flowName !== currentFlowName &&
!isInvalidName !isInvalidName
) { ) {
// Get a one-time snapshot of currentFlow using get()
const currentFlowSnapshot = useFlowStore.getState().currentFlow; const currentFlowSnapshot = useFlowStore.getState().currentFlow;
const newFlow = { const newFlow = {
@ -327,6 +310,12 @@ export const MenuBar = ({}: {}): JSX.Element => {
onFocus={() => { onFocus={() => {
setEditingName(true); setEditingName(true);
setFlowName(currentFlowName); setFlowName(currentFlowName);
const flows = useFlowsManagerStore.getState().flows;
setFlowNames(
flows
?.map((flow) => flow.name)
.filter((name) => name !== currentFlowName) ?? [],
);
}} }}
onBlur={handleNameSubmit} onBlur={handleNameSubmit}
value={flowName} value={flowName}
@ -578,6 +567,6 @@ export const MenuBar = ({}: {}): JSX.Element => {
) : ( ) : (
<></> <></>
); );
}; });
export default MenuBar; export default MenuBar;

View file

@ -85,8 +85,8 @@ const SidebarProvider = React.forwardRef<
// Helper to toggle the sidebar. // Helper to toggle the sidebar.
const toggleSidebar = React.useCallback(() => { const toggleSidebar = React.useCallback(() => {
return setOpen((open) => !open); return setOpen((prev) => !prev);
}, [setOpen, open]); }, [setOpen]);
// We add a state so that we can do data-state="expanded" or "collapsed". // We add a state so that we can do data-state="expanded" or "collapsed".
// This makes it easier to style the sidebar with Tailwind classes. // This makes it easier to style the sidebar with Tailwind classes.
@ -275,6 +275,14 @@ const SidebarTrigger = React.forwardRef<
>(({ className, onClick, ...props }, ref) => { >(({ className, onClick, ...props }, ref) => {
const { toggleSidebar } = useSidebar(); const { toggleSidebar } = useSidebar();
const handleClick = React.useCallback(
(event: React.MouseEvent<HTMLButtonElement>) => {
onClick?.(event);
toggleSidebar();
},
[onClick, toggleSidebar],
);
return ( return (
<Button <Button
ref={ref} ref={ref}
@ -282,10 +290,7 @@ const SidebarTrigger = React.forwardRef<
variant="ghost" variant="ghost"
size="icon" size="icon"
className={cn("h-7 w-7 text-muted-foreground", className)} className={cn("h-7 w-7 text-muted-foreground", className)}
onClick={(event) => { onClick={handleClick}
onClick?.(event);
toggleSidebar();
}}
{...props} {...props}
> >
{props.children ? ( {props.children ? (
@ -444,25 +449,27 @@ const SidebarGroup = React.forwardRef<
}); });
SidebarGroup.displayName = "SidebarGroup"; SidebarGroup.displayName = "SidebarGroup";
const SidebarGroupLabel = React.forwardRef< const SidebarGroupLabel = React.memo(
HTMLDivElement, React.forwardRef<
React.ComponentProps<"div"> & { asChild?: boolean } HTMLDivElement,
>(({ className, asChild = false, ...props }, ref) => { React.ComponentProps<"div"> & { asChild?: boolean }
const Comp = asChild ? Slot : "div"; >(({ className, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "div";
return ( return (
<Comp <Comp
ref={ref} ref={ref}
data-sidebar="group-label" data-sidebar="group-label"
className={cn( className={cn(
"flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-semibold text-foreground/70 outline-none ring-ring transition-[margin,opa] duration-200 ease-linear focus-visible:ring-1 [&>svg]:size-4 [&>svg]:shrink-0", "flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-semibold text-foreground/70 outline-none ring-ring transition-[margin,opa] duration-200 ease-linear focus-visible:ring-1 [&>svg]:size-4 [&>svg]:shrink-0",
"group-data-[collapsible=icon]:pointer-events-none group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0", "group-data-[collapsible=icon]:pointer-events-none group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
className, className,
)} )}
{...props} {...props}
/> />
); );
}); }),
);
SidebarGroupLabel.displayName = "SidebarGroupLabel"; SidebarGroupLabel.displayName = "SidebarGroupLabel";
const SidebarGroupAction = React.forwardRef< const SidebarGroupAction = React.forwardRef<

View file

@ -7,7 +7,6 @@ import {
} from "@/utils/reactflowUtils"; } from "@/utils/reactflowUtils";
const useDeleteFlow = () => { const useDeleteFlow = () => {
const flows = useFlowsManagerStore((state) => state.flows);
const setFlows = useFlowsManagerStore((state) => state.setFlows); const setFlows = useFlowsManagerStore((state) => state.setFlows);
const { mutate, isPending } = useDeleteDeleteFlows(); const { mutate, isPending } = useDeleteDeleteFlows();
@ -17,6 +16,7 @@ const useDeleteFlow = () => {
}: { }: {
id: string | string[]; id: string | string[];
}): Promise<void> => { }): Promise<void> => {
const flows = useFlowsManagerStore.getState().flows;
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
if (!Array.isArray(id)) { if (!Array.isArray(id)) {
id = [id]; id = [id];

View file

@ -6,29 +6,28 @@ import useFlowStore from "@/stores/flowStore";
import { AllNodeType, EdgeType, FlowType } from "@/types/flow"; import { AllNodeType, EdgeType, FlowType } from "@/types/flow";
import { customStringify } from "@/utils/reactflowUtils"; import { customStringify } from "@/utils/reactflowUtils";
import { ReactFlowJsonObject } from "@xyflow/react"; import { ReactFlowJsonObject } from "@xyflow/react";
const useSaveFlow = () => { const useSaveFlow = () => {
const flows = useFlowsManagerStore((state) => state.flows);
const setFlows = useFlowsManagerStore((state) => state.setFlows); const setFlows = useFlowsManagerStore((state) => state.setFlows);
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);
const reactFlowInstance = useFlowStore((state) => state.reactFlowInstance);
const nodes = useFlowStore((state) => state.nodes);
const edges = useFlowStore((state) => state.edges);
const setSaveLoading = useFlowsManagerStore((state) => state.setSaveLoading); const setSaveLoading = useFlowsManagerStore((state) => state.setSaveLoading);
const setCurrentFlow = useFlowStore((state) => state.setCurrentFlow); const setCurrentFlow = useFlowStore((state) => state.setCurrentFlow);
const currentSavedFlow = useFlowsManagerStore((state) => state.currentFlow);
const { mutate: getFlow } = useGetFlow(); const { mutate: getFlow } = useGetFlow();
const { mutate } = usePatchUpdateFlow(); const { mutate } = usePatchUpdateFlow();
const currentFlow = useFlowStore((state) => state.currentFlow);
const flowData = currentFlow?.data;
const saveFlow = async (flow?: FlowType): Promise<void> => { const saveFlow = async (flow?: FlowType): Promise<void> => {
const currentFlow = useFlowStore.getState().currentFlow;
const currentSavedFlow = useFlowsManagerStore.getState().currentFlow;
if ( if (
customStringify(flow || currentFlow) !== customStringify(currentSavedFlow) customStringify(flow || currentFlow) !== customStringify(currentSavedFlow)
) { ) {
setSaveLoading(true); setSaveLoading(true);
const flowData = currentFlow?.data;
const nodes = useFlowStore.getState().nodes;
const edges = useFlowStore.getState().edges;
const reactFlowInstance = useFlowStore.getState().reactFlowInstance;
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
if (currentFlow) { if (currentFlow) {
flow = flow || { flow = flow || {
@ -83,6 +82,7 @@ const useSaveFlow = () => {
}, },
{ {
onSuccess: (updatedFlow) => { onSuccess: (updatedFlow) => {
const flows = useFlowsManagerStore.getState().flows;
setSaveLoading(false); setSaveLoading(false);
if (flows) { if (flows) {
// updates flow in state // updates flow in state

View file

@ -542,6 +542,13 @@ export default function Page({
const componentsToUpdate = useFlowStore((state) => state.componentsToUpdate); const componentsToUpdate = useFlowStore((state) => state.componentsToUpdate);
const MIN_ZOOM = 0.2;
const MAX_ZOOM = 8;
const fitViewOptions = {
minZoom: MIN_ZOOM,
maxZoom: MAX_ZOOM,
};
return ( return (
<div className="h-full w-full bg-canvas" ref={reactFlowWrapper}> <div className="h-full w-full bg-canvas" ref={reactFlowWrapper}>
{showCanvas ? ( {showCanvas ? (
@ -572,13 +579,10 @@ export default function Page({
onSelectionChange={onSelectionChange} onSelectionChange={onSelectionChange}
deleteKeyCode={[]} deleteKeyCode={[]}
fitView={isEmptyFlow.current ? false : true} fitView={isEmptyFlow.current ? false : true}
fitViewOptions={{ fitViewOptions={fitViewOptions}
minZoom: 0.2,
maxZoom: 8,
}}
className="theme-attribution" className="theme-attribution"
minZoom={0.2} minZoom={MIN_ZOOM}
maxZoom={3} maxZoom={MAX_ZOOM}
zoomOnScroll={!view} zoomOnScroll={!view}
zoomOnPinch={!view} zoomOnPinch={!view}
panOnDrag={!view} panOnDrag={!view}

View file

@ -5,18 +5,17 @@ import {
DisclosureTrigger, DisclosureTrigger,
} from "@/components/ui/disclosure"; } from "@/components/ui/disclosure";
import { SidebarMenuButton, SidebarMenuItem } from "@/components/ui/sidebar"; import { SidebarMenuButton, SidebarMenuItem } from "@/components/ui/sidebar";
import { memo } from "react"; import { memo, useCallback } from "react";
import { BundleItemProps } from "../../types"; import { BundleItemProps } from "../../types";
import SidebarItemsList from "../sidebarItemsList"; import SidebarItemsList from "../sidebarItemsList";
export const BundleItem = memo( export const BundleItem = memo(
({ ({
item, item,
isOpen, openCategories,
onOpenChange, setOpenCategories,
dataFilter, dataFilter,
nodeColors, nodeColors,
uniqueInputsComponents,
onDragStart, onDragStart,
sensitiveSort, sensitiveSort,
handleKeyDownInput, handleKeyDownInput,
@ -28,45 +27,55 @@ export const BundleItem = memo(
return null; return null;
} }
const isOpen = openCategories.includes(item.name);
const handleOpenChange = useCallback(
(isOpen: boolean) => {
setOpenCategories((prev: string[]) =>
isOpen
? [...prev, item.name]
: prev.filter((cat) => cat !== item.name),
);
},
[item.name, setOpenCategories],
);
return ( return (
<> <Disclosure key={item.name} open={isOpen} onOpenChange={handleOpenChange}>
<Disclosure key={item.name} open={isOpen} onOpenChange={onOpenChange}> <SidebarMenuItem>
<SidebarMenuItem> <DisclosureTrigger className="group/collapsible">
<DisclosureTrigger className="group/collapsible"> <SidebarMenuButton asChild>
<SidebarMenuButton asChild> <div
<div tabIndex={0}
tabIndex={0} onKeyDown={(e) => handleKeyDownInput(e, item.name)}
onKeyDown={(e) => handleKeyDownInput(e, item.name)} className="flex cursor-pointer items-center gap-2"
className="flex cursor-pointer items-center gap-2" data-testid={`disclosure-bundles-${item.display_name.toLowerCase()}`}
data-testid={`disclosure-bundles-${item.display_name.toLowerCase()}`} >
> <ForwardedIconComponent
<ForwardedIconComponent name={item.icon}
name={item.icon} className="h-4 w-4 text-muted-foreground group-aria-expanded/collapsible:text-primary"
className="h-4 w-4 text-muted-foreground group-aria-expanded/collapsible:text-primary" />
/> <span className="flex-1 group-aria-expanded/collapsible:font-semibold">
<span className="flex-1 group-aria-expanded/collapsible:font-semibold"> {item.display_name}
{item.display_name} </span>
</span> <ForwardedIconComponent
<ForwardedIconComponent name="ChevronRight"
name="ChevronRight" className="-mr-1 h-4 w-4 text-muted-foreground transition-all group-aria-expanded/collapsible:rotate-90"
className="-mr-1 h-4 w-4 text-muted-foreground transition-all group-aria-expanded/collapsible:rotate-90" />
/> </div>
</div> </SidebarMenuButton>
</SidebarMenuButton> </DisclosureTrigger>
</DisclosureTrigger> <DisclosureContent>
<DisclosureContent> <SidebarItemsList
<SidebarItemsList item={item}
item={item} dataFilter={dataFilter}
dataFilter={dataFilter} nodeColors={nodeColors}
nodeColors={nodeColors} onDragStart={onDragStart}
uniqueInputsComponents={uniqueInputsComponents} sensitiveSort={sensitiveSort}
onDragStart={onDragStart} />
sensitiveSort={sensitiveSort} </DisclosureContent>
/> </SidebarMenuItem>
</DisclosureContent> </Disclosure>
</SidebarMenuItem>
</Disclosure>
</>
); );
}, },
); );

View file

@ -15,7 +15,6 @@ export const CategoryDisclosure = memo(function CategoryDisclosure({
setOpenCategories, setOpenCategories,
dataFilter, dataFilter,
nodeColors, nodeColors,
uniqueInputsComponents,
onDragStart, onDragStart,
sensitiveSort, sensitiveSort,
}: { }: {
@ -24,10 +23,6 @@ export const CategoryDisclosure = memo(function CategoryDisclosure({
setOpenCategories; setOpenCategories;
dataFilter: any; dataFilter: any;
nodeColors: any; nodeColors: any;
uniqueInputsComponents: {
chatInput: boolean;
webhookInput: boolean;
};
onDragStart: ( onDragStart: (
event: React.DragEvent<any>, event: React.DragEvent<any>,
data: { type: string; node?: APIClassType }, data: { type: string; node?: APIClassType },
@ -48,17 +43,17 @@ export const CategoryDisclosure = memo(function CategoryDisclosure({
[item.name, setOpenCategories], [item.name, setOpenCategories],
); );
const isOpen = openCategories.includes(item.name);
const handleOpenChange = useCallback(
(isOpen: boolean) => {
setOpenCategories((prev) =>
isOpen ? [...prev, item.name] : prev.filter((cat) => cat !== item.name),
);
},
[item.name, setOpenCategories],
);
return ( return (
<Disclosure <Disclosure open={isOpen} onOpenChange={handleOpenChange}>
open={openCategories.includes(item.name)}
onOpenChange={(isOpen) => {
setOpenCategories((prev) =>
isOpen
? [...prev, item.name]
: prev.filter((cat) => cat !== item.name),
);
}}
>
<SidebarMenuItem> <SidebarMenuItem>
<DisclosureTrigger className="group/collapsible"> <DisclosureTrigger className="group/collapsible">
<SidebarMenuButton asChild> <SidebarMenuButton asChild>
@ -87,7 +82,6 @@ export const CategoryDisclosure = memo(function CategoryDisclosure({
item={item} item={item}
dataFilter={dataFilter} dataFilter={dataFilter}
nodeColors={nodeColors} nodeColors={nodeColors}
uniqueInputsComponents={uniqueInputsComponents}
onDragStart={onDragStart} onDragStart={onDragStart}
sensitiveSort={sensitiveSort} sensitiveSort={sensitiveSort}
/> />

View file

@ -4,7 +4,7 @@ import {
SidebarMenu, SidebarMenu,
} from "@/components/ui/sidebar"; } from "@/components/ui/sidebar";
import { SIDEBAR_BUNDLES } from "@/utils/styleUtils"; import { SIDEBAR_BUNDLES } from "@/utils/styleUtils";
import { memo } from "react"; import { memo, useState } from "react";
import { CategoryGroupProps } from "../../types"; import { CategoryGroupProps } from "../../types";
import { CategoryDisclosure } from "../categoryDisclouse"; import { CategoryDisclosure } from "../categoryDisclouse";
@ -16,7 +16,6 @@ export const CategoryGroup = memo(function CategoryGroup({
setOpenCategories, setOpenCategories,
search, search,
nodeColors, nodeColors,
uniqueInputsComponents,
onDragStart, onDragStart,
sensitiveSort, sensitiveSort,
}: CategoryGroupProps) { }: CategoryGroupProps) {
@ -67,7 +66,6 @@ export const CategoryGroup = memo(function CategoryGroup({
nodeColors={nodeColors} nodeColors={nodeColors}
onDragStart={onDragStart} onDragStart={onDragStart}
sensitiveSort={sensitiveSort} sensitiveSort={sensitiveSort}
uniqueInputsComponents={uniqueInputsComponents}
/> />
); );
})} })}

View file

@ -4,7 +4,7 @@ import {
SidebarGroupLabel, SidebarGroupLabel,
SidebarMenu, SidebarMenu,
} from "@/components/ui/sidebar"; } from "@/components/ui/sidebar";
import { memo, useMemo } from "react"; import { memo, useCallback, useMemo, useState } from "react";
import { SidebarGroupProps } from "../../types"; import { SidebarGroupProps } from "../../types";
import { BundleItem } from "../bundleItems"; import { BundleItem } from "../bundleItems";
@ -17,10 +17,9 @@ export const MemoizedSidebarGroup = memo(
nodeColors, nodeColors,
onDragStart, onDragStart,
sensitiveSort, sensitiveSort,
handleKeyDownInput,
openCategories, openCategories,
setOpenCategories, setOpenCategories,
handleKeyDownInput,
uniqueInputsComponents,
}: SidebarGroupProps) => { }: SidebarGroupProps) => {
const sortedBundles = useMemo(() => { const sortedBundles = useMemo(() => {
return BUNDLES.toSorted((a, b) => { return BUNDLES.toSorted((a, b) => {
@ -41,17 +40,10 @@ export const MemoizedSidebarGroup = memo(
<BundleItem <BundleItem
key={item.name} key={item.name}
item={item} item={item}
isOpen={openCategories.includes(item.name)} openCategories={openCategories}
onOpenChange={(isOpen) => { setOpenCategories={setOpenCategories}
setOpenCategories((prev) =>
isOpen
? [...prev, item.name]
: prev.filter((cat) => cat !== item.name),
);
}}
dataFilter={dataFilter} dataFilter={dataFilter}
nodeColors={nodeColors} nodeColors={nodeColors}
uniqueInputsComponents={uniqueInputsComponents}
onDragStart={onDragStart} onDragStart={onDragStart}
sensitiveSort={sensitiveSort} sensitiveSort={sensitiveSort}
handleKeyDownInput={handleKeyDownInput} handleKeyDownInput={handleKeyDownInput}

View file

@ -1,14 +1,17 @@
import ShadTooltip from "@/components/common/shadTooltipComponent"; import ShadTooltip from "@/components/common/shadTooltipComponent";
import useFlowStore from "@/stores/flowStore";
import { checkChatInput, checkWebhookInput } from "@/utils/reactflowUtils";
import { removeCountFromString } from "@/utils/utils"; import { removeCountFromString } from "@/utils/utils";
import { useMemo } from "react";
import { disableItem } from "../../helpers/disable-item"; import { disableItem } from "../../helpers/disable-item";
import { getDisabledTooltip } from "../../helpers/get-disabled-tooltip"; import { getDisabledTooltip } from "../../helpers/get-disabled-tooltip";
import { UniqueInputsComponents } from "../../types";
import SidebarDraggableComponent from "../sidebarDraggableComponent"; import SidebarDraggableComponent from "../sidebarDraggableComponent";
const SidebarItemsList = ({ const SidebarItemsList = ({
item, item,
dataFilter, dataFilter,
nodeColors, nodeColors,
uniqueInputsComponents,
onDragStart, onDragStart,
sensitiveSort, sensitiveSort,
}) => { }) => {
@ -36,6 +39,18 @@ const SidebarItemsList = ({
.map((SBItemName, idx) => { .map((SBItemName, idx) => {
const currentItem = dataFilter[item.name][SBItemName]; const currentItem = dataFilter[item.name][SBItemName];
if (SBItemName === "ChatInput" || SBItemName === "Webhook") {
return (
<UniqueInputsDraggableComponent
item={item}
currentItem={currentItem}
SBItemName={SBItemName}
idx={idx}
onDragStart={onDragStart}
nodeColors={nodeColors}
/>
);
}
return ( return (
<ShadTooltip <ShadTooltip
content={currentItem.display_name} content={currentItem.display_name}
@ -59,11 +74,8 @@ const SidebarItemsList = ({
official={currentItem.official === false ? false : true} official={currentItem.official === false ? false : true}
beta={currentItem.beta ?? false} beta={currentItem.beta ?? false}
legacy={currentItem.legacy ?? false} legacy={currentItem.legacy ?? false}
disabled={disableItem(SBItemName, uniqueInputsComponents)} disabled={false}
disabledTooltip={getDisabledTooltip( disabledTooltip={""}
SBItemName,
uniqueInputsComponents,
)}
/> />
</ShadTooltip> </ShadTooltip>
); );
@ -73,3 +85,51 @@ const SidebarItemsList = ({
}; };
export default SidebarItemsList; export default SidebarItemsList;
const UniqueInputsDraggableComponent = ({
item,
currentItem,
SBItemName,
idx,
onDragStart,
nodeColors,
}) => {
const nodes = useFlowStore((state) => state.nodes);
const chatInputAdded = useMemo(() => checkChatInput(nodes), [nodes]);
const webhookInputAdded = useMemo(() => checkWebhookInput(nodes), [nodes]);
const uniqueInputsComponents: UniqueInputsComponents = useMemo(() => {
console.log("uniqueInputsComponents", {
chatInputAdded,
webhookInputAdded,
});
return {
chatInput: chatInputAdded,
webhookInput: webhookInputAdded,
};
}, [chatInputAdded, webhookInputAdded]);
return (
<ShadTooltip content={currentItem.display_name} side="right" key={idx}>
<SidebarDraggableComponent
sectionName={item.name}
apiClass={currentItem}
icon={currentItem.icon ?? item.icon ?? "Unknown"}
onDragStart={(event) =>
onDragStart(event, {
type: removeCountFromString(SBItemName),
node: currentItem,
})
}
color={nodeColors[item.name]}
itemName={SBItemName}
error={!!currentItem.error}
display_name={currentItem.display_name}
official={currentItem.official === false ? false : true}
beta={currentItem.beta ?? false}
legacy={currentItem.legacy ?? false}
disabled={disableItem(SBItemName, uniqueInputsComponents)}
disabledTooltip={getDisabledTooltip(SBItemName, uniqueInputsComponents)}
/>
</ShadTooltip>
);
};

View file

@ -18,6 +18,7 @@ import Fuse from "fuse.js";
import { cloneDeep } from "lodash"; import { cloneDeep } from "lodash";
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useHotkeys } from "react-hotkeys-hook"; import { useHotkeys } from "react-hotkeys-hook";
import { useShallow } from "zustand/react/shallow";
import useAlertStore from "../../../../stores/alertStore"; import useAlertStore from "../../../../stores/alertStore";
import useFlowStore from "../../../../stores/flowStore"; import useFlowStore from "../../../../stores/flowStore";
import { useTypesStore } from "../../../../stores/typesStore"; import { useTypesStore } from "../../../../stores/typesStore";
@ -48,30 +49,17 @@ interface FlowSidebarComponentProps {
} }
export function FlowSidebarComponent({ isLoading }: FlowSidebarComponentProps) { export function FlowSidebarComponent({ isLoading }: FlowSidebarComponentProps) {
const { data, templates } = useTypesStore( const data = useTypesStore((state) => state.data);
useCallback(
(state) => ({
data: state.data,
templates: state.templates,
}),
[],
),
);
const { getFilterEdge, setFilterEdge, filterType, nodes } = useFlowStore( const { getFilterEdge, setFilterEdge, filterType } = useFlowStore(
useCallback( useShallow((state) => ({
(state) => ({ getFilterEdge: state.getFilterEdge,
getFilterEdge: state.getFilterEdge, setFilterEdge: state.setFilterEdge,
setFilterEdge: state.setFilterEdge, filterType: state.filterType,
filterType: state.filterType, })),
nodes: state.nodes,
}),
[],
),
); );
const hasStore = useStoreStore((state) => state.hasStore); const hasStore = useStoreStore((state) => state.hasStore);
const setErrorData = useAlertStore((state) => state.setErrorData);
const { setOpen } = useSidebar(); const { setOpen } = useSidebar();
const addComponent = useAddComponent(); const addComponent = useAddComponent();
@ -87,15 +75,6 @@ export function FlowSidebarComponent({ isLoading }: FlowSidebarComponentProps) {
const searchInputRef = useRef<HTMLInputElement | null>(null); const searchInputRef = useRef<HTMLInputElement | null>(null);
const chatInputAdded = useMemo(() => checkChatInput(nodes), [nodes]);
const webhookInputAdded = useMemo(() => checkWebhookInput(nodes), [nodes]);
const uniqueInputsComponents: UniqueInputsComponents = useMemo(() => {
return {
chatInput: chatInputAdded,
webhookInput: webhookInputAdded,
};
}, [chatInputAdded, webhookInputAdded]);
const customComponent = useMemo(() => { const customComponent = useMemo(() => {
return data?.["custom_component"]?.["CustomComponent"] ?? null; return data?.["custom_component"]?.["CustomComponent"] ?? null;
}, [data]); }, [data]);
@ -358,7 +337,6 @@ export function FlowSidebarComponent({ isLoading }: FlowSidebarComponentProps) {
nodeColors={nodeColors} nodeColors={nodeColors}
onDragStart={onDragStart} onDragStart={onDragStart}
sensitiveSort={sensitiveSort} sensitiveSort={sensitiveSort}
uniqueInputsComponents={uniqueInputsComponents}
/> />
{hasBundleItems && ( {hasBundleItems && (
@ -373,7 +351,6 @@ export function FlowSidebarComponent({ isLoading }: FlowSidebarComponentProps) {
openCategories={openCategories} openCategories={openCategories}
setOpenCategories={setOpenCategories} setOpenCategories={setOpenCategories}
handleKeyDownInput={handleKeyDownInput} handleKeyDownInput={handleKeyDownInput}
uniqueInputsComponents={uniqueInputsComponents}
/> />
)} )}
</> </>

View file

@ -14,7 +14,7 @@ export interface CategoryGroupProps {
icon: string; icon: string;
}[]; }[];
openCategories: string[]; openCategories: string[];
setOpenCategories: (categories: string[]) => void; setOpenCategories: Dispatch<SetStateAction<string[]>>;
search: string; search: string;
nodeColors: NodeColors; nodeColors: NodeColors;
onDragStart: ( onDragStart: (
@ -22,10 +22,6 @@ export interface CategoryGroupProps {
data: { type: string; node?: APIClassType }, data: { type: string; node?: APIClassType },
) => void; ) => void;
sensitiveSort: (a: string, b: string) => number; sensitiveSort: (a: string, b: string) => number;
uniqueInputsComponents: {
chatInput: boolean;
webhookInput: boolean;
};
} }
export interface SidebarGroupProps { export interface SidebarGroupProps {
@ -39,15 +35,12 @@ export interface SidebarGroupProps {
data: { type: string; node?: APIClassType }, data: { type: string; node?: APIClassType },
) => void; ) => void;
sensitiveSort: (a: string, b: string) => number; sensitiveSort: (a: string, b: string) => number;
openCategories: string[];
setOpenCategories: (
categories: string[] | ((prev: string[]) => string[]),
) => void;
handleKeyDownInput: ( handleKeyDownInput: (
event: React.KeyboardEvent<HTMLDivElement>, event: React.KeyboardEvent<HTMLDivElement>,
name: string, name: string,
) => void; ) => void;
uniqueInputsComponents: UniqueInputsComponents; openCategories: string[];
setOpenCategories: Dispatch<SetStateAction<string[]>>;
} }
export interface BundleItemProps { export interface BundleItemProps {
@ -56,11 +49,10 @@ export interface BundleItemProps {
display_name: string; display_name: string;
icon: string; icon: string;
}; };
isOpen: boolean; openCategories: string[];
onOpenChange: (isOpen: boolean) => void; setOpenCategories: Dispatch<SetStateAction<string[]>>;
dataFilter: APIDataType; dataFilter: APIDataType;
nodeColors: NodeColors; nodeColors: NodeColors;
uniqueInputsComponents: UniqueInputsComponents;
onDragStart: ( onDragStart: (
event: React.DragEvent<any>, event: React.DragEvent<any>,
data: { type: string; node?: APIClassType }, data: { type: string; node?: APIClassType },