perf: Optimize component rendering with memoization and useCallback hooks (#5253)
* ✨ (NodeDescription/index.tsx): Memoize Markdown component and description rendering for performance optimization ♻️ (NodeOutputfield/index.tsx): Memoize HandleRenderComponent and Handle instance with useMemo and memo for performance optimization ✨ (RenderInputParameters/index.tsx): Add new component RenderInputParameters to render input parameters with memoization for improved performance 📝 (GenericNode/index.tsx): Remove unused imports and functions, refactor sortToolModeFields to be exported, and optimize key generation and memoization for NodeOutputField component 📝 (flowSidebarComponent/index.tsx): Refactor event handlers to use useCallback for better performance 📝 (nodeToolbarComponent/index.tsx): Refactor event handlers to use useCallback for better performance and readability * ♻️ (NodeDescription/index.tsx): Remove unnecessary comments and improve code readability by removing redundant comments ♻️ (NodeOutputfield/index.tsx): Remove unnecessary comments and improve code readability by removing redundant comments ♻️ (RenderInputParameters/index.tsx): Remove unnecessary comments and improve code readability by removing redundant comments ♻️ (index.tsx): Remove unnecessary comments and improve code readability by removing redundant comments * ✅ (decisionFlow.spec.ts): add @workflow tag to the test case for better categorization and organization. * 📝 (GenericNode/index.tsx): Organize imports and update component import for better code structure and readability 📝 (GenericNode/index.tsx): Refactor renderOutputs function to improve code readability and maintainability 📝 (GenericNode/index.tsx): Refactor renderOutputParameter function to use OutputParameter component for consistency 📝 (GenericNode/index.tsx): Refactor output rendering logic to use OutputParameter component for better code structure 📝 (sidebarDraggableComponent/index.tsx): Update logic to remove cursor element only if it exists to prevent errors * ✨ (NodeOutputParameter/index.tsx): Add a new component for rendering output parameters in the frontend to improve modularity and reusability.
This commit is contained in:
parent
32a0707f9f
commit
977ba926c6
9 changed files with 372 additions and 200 deletions
|
|
@ -3,7 +3,7 @@ import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
|||
import useFlowStore from "@/stores/flowStore";
|
||||
import { handleKeyDown } from "@/utils/reactflowUtils";
|
||||
import { cn } from "@/utils/utils";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { memo, useEffect, useMemo, useRef, useState } from "react";
|
||||
import Markdown from "react-markdown";
|
||||
|
||||
export default function NodeDescription({
|
||||
|
|
@ -59,6 +59,25 @@ export default function NodeDescription({
|
|||
setNodeDescription(description);
|
||||
}, [description]);
|
||||
|
||||
const MemoizedMarkdown = memo(Markdown);
|
||||
const renderedDescription = useMemo(
|
||||
() =>
|
||||
description === "" || !description ? (
|
||||
emptyPlaceholder
|
||||
) : (
|
||||
<MemoizedMarkdown
|
||||
linkTarget="_blank"
|
||||
className={cn(
|
||||
"markdown prose flex w-full flex-col text-[13px] leading-5 word-break-break-word [&_pre]:whitespace-break-spaces [&_pre]:!bg-code-description-background [&_pre_code]:!bg-code-description-background",
|
||||
mdClassName,
|
||||
)}
|
||||
>
|
||||
{String(description)}
|
||||
</MemoizedMarkdown>
|
||||
),
|
||||
[description, emptyPlaceholder, mdClassName],
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
|
|
@ -138,19 +157,7 @@ export default function NodeDescription({
|
|||
takeSnapshot();
|
||||
}}
|
||||
>
|
||||
{description === "" || !description ? (
|
||||
emptyPlaceholder
|
||||
) : (
|
||||
<Markdown
|
||||
linkTarget="_blank"
|
||||
className={cn(
|
||||
"markdown prose flex w-full flex-col text-[13px] leading-5 word-break-break-word [&_pre]:whitespace-break-spaces [&_pre]:!bg-code-description-background [&_pre_code]:!bg-code-description-background",
|
||||
mdClassName,
|
||||
)}
|
||||
>
|
||||
{String(description)}
|
||||
</Markdown>
|
||||
)}
|
||||
{renderedDescription}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
import React, { useMemo } from "react";
|
||||
|
||||
import { getNodeOutputColors } from "../../../helpers/get-node-output-colors";
|
||||
import { getNodeOutputColorsName } from "../../../helpers/get-node-output-colors-name";
|
||||
import NodeOutputField from "../NodeOutputfield";
|
||||
|
||||
export const OutputParameter = ({
|
||||
output,
|
||||
idx,
|
||||
lastOutput,
|
||||
data,
|
||||
types,
|
||||
selected,
|
||||
showNode,
|
||||
isToolMode,
|
||||
}) => {
|
||||
const id = useMemo(
|
||||
() => ({
|
||||
output_types: [output.selected ?? output.types[0]],
|
||||
id: data.id,
|
||||
dataType: data.type,
|
||||
name: output.name,
|
||||
}),
|
||||
[output.selected, output.types, data.id, data.type, output.name],
|
||||
);
|
||||
|
||||
const colors = useMemo(
|
||||
() => getNodeOutputColors(output, data, types),
|
||||
[output, data.type, data.id, types],
|
||||
);
|
||||
|
||||
const colorNames = useMemo(
|
||||
() => getNodeOutputColorsName(output, data, types),
|
||||
[output, data.type, data.id, types],
|
||||
);
|
||||
|
||||
return (
|
||||
<NodeOutputField
|
||||
index={idx}
|
||||
lastOutput={lastOutput}
|
||||
selected={selected}
|
||||
key={output.name + idx}
|
||||
data={data}
|
||||
colors={colors}
|
||||
outputProxy={output.proxy}
|
||||
title={output.display_name ?? output.name}
|
||||
tooltipTitle={output.selected ?? output.types[0]}
|
||||
id={id}
|
||||
type={output.types.join("|")}
|
||||
showNode={showNode}
|
||||
outputName={output.name}
|
||||
colorName={colorNames}
|
||||
isToolMode={isToolMode}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { ICON_STROKE_WIDTH } from "@/constants/constants";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { memo, useEffect, useMemo, useRef } from "react";
|
||||
import { useUpdateNodeInternals } from "reactflow";
|
||||
import { default as IconComponent } from "../../../../components/common/genericIconComponent";
|
||||
import ShadTooltip from "../../../../components/common/shadTooltipComponent";
|
||||
|
|
@ -105,22 +105,52 @@ export default function NodeOutputField({
|
|||
}
|
||||
}, [disabledOutput]);
|
||||
|
||||
const Handle = (
|
||||
<HandleRenderComponent
|
||||
left={false}
|
||||
nodes={nodes}
|
||||
tooltipTitle={tooltipTitle}
|
||||
id={id}
|
||||
title={title}
|
||||
edges={edges}
|
||||
nodeId={data.id}
|
||||
myData={myData}
|
||||
colors={colors}
|
||||
setFilterEdge={setFilterEdge}
|
||||
showNode={showNode}
|
||||
testIdComplement={`${data?.type?.toLowerCase()}-${showNode ? "shownode" : "noshownode"}`}
|
||||
colorName={colorName}
|
||||
/>
|
||||
const MemoizedHandleRenderComponent = memo(
|
||||
HandleRenderComponent,
|
||||
(prev, next) => {
|
||||
return (
|
||||
prev.nodeId === next.nodeId &&
|
||||
prev.myData === next.myData &&
|
||||
prev.showNode === next.showNode &&
|
||||
prev.tooltipTitle === next.tooltipTitle &&
|
||||
prev.colors === next.colors &&
|
||||
prev.colorName === next.colorName
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const Handle = useMemo(
|
||||
() => (
|
||||
<MemoizedHandleRenderComponent
|
||||
left={false}
|
||||
nodes={nodes}
|
||||
tooltipTitle={tooltipTitle}
|
||||
id={id}
|
||||
title={title}
|
||||
edges={edges}
|
||||
nodeId={data.id}
|
||||
myData={myData}
|
||||
colors={colors}
|
||||
setFilterEdge={setFilterEdge}
|
||||
showNode={showNode}
|
||||
testIdComplement={`${data?.type?.toLowerCase()}-${showNode ? "shownode" : "noshownode"}`}
|
||||
colorName={colorName}
|
||||
/>
|
||||
),
|
||||
[
|
||||
nodes,
|
||||
tooltipTitle,
|
||||
id,
|
||||
title,
|
||||
edges,
|
||||
data.id,
|
||||
myData,
|
||||
colors,
|
||||
setFilterEdge,
|
||||
showNode,
|
||||
data?.type,
|
||||
colorName,
|
||||
],
|
||||
);
|
||||
|
||||
return !showNode ? (
|
||||
|
|
|
|||
|
|
@ -0,0 +1,122 @@
|
|||
import { getNodeInputColors } from "@/CustomNodes/helpers/get-node-input-colors";
|
||||
import { getNodeInputColorsName } from "@/CustomNodes/helpers/get-node-input-colors-name";
|
||||
import getFieldTitle from "@/CustomNodes/utils/get-field-title";
|
||||
import { scapedJSONStringfy } from "@/utils/reactflowUtils";
|
||||
import { useMemo } from "react";
|
||||
import { sortToolModeFields } from "../..";
|
||||
import NodeInputField from "../NodeInputField";
|
||||
|
||||
const RenderInputParameters = ({
|
||||
data,
|
||||
types,
|
||||
isToolMode,
|
||||
showNode,
|
||||
shownOutputs,
|
||||
showHiddenOutputs,
|
||||
}) => {
|
||||
const templateFields = useMemo(() => {
|
||||
return Object.keys(data.node?.template || {})
|
||||
.filter((templateField) => templateField.charAt(0) !== "_")
|
||||
.sort((a, b) =>
|
||||
sortToolModeFields(
|
||||
a,
|
||||
b,
|
||||
data.node!.template,
|
||||
data.node?.field_order ?? [],
|
||||
isToolMode,
|
||||
),
|
||||
);
|
||||
}, [data.node?.template, data.node?.field_order, isToolMode]);
|
||||
|
||||
const memoizedColors = useMemo(() => {
|
||||
const colorMap = new Map();
|
||||
|
||||
templateFields.forEach((templateField) => {
|
||||
const template = data.node?.template[templateField];
|
||||
if (template) {
|
||||
colorMap.set(templateField, {
|
||||
colors: getNodeInputColors(
|
||||
template.input_types,
|
||||
template.type,
|
||||
types,
|
||||
),
|
||||
colorsName: getNodeInputColorsName(
|
||||
template.input_types,
|
||||
template.type,
|
||||
types,
|
||||
),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return colorMap;
|
||||
}, [templateFields, types, data.node?.template]);
|
||||
|
||||
const memoizedKeys = useMemo(() => {
|
||||
const keyMap = new Map();
|
||||
|
||||
templateFields.forEach((templateField) => {
|
||||
const template = data.node?.template[templateField];
|
||||
if (template) {
|
||||
keyMap.set(
|
||||
templateField,
|
||||
scapedJSONStringfy({
|
||||
inputTypes: template.input_types,
|
||||
type: template.type,
|
||||
id: data.id,
|
||||
fieldName: templateField,
|
||||
proxy: template.proxy,
|
||||
}),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return keyMap;
|
||||
}, [templateFields, data.id, data.node?.template]);
|
||||
|
||||
const renderInputParameter = templateFields.map(
|
||||
(templateField: string, idx) => {
|
||||
const template = data.node?.template[templateField];
|
||||
|
||||
if (!template?.show || template?.advanced) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const memoizedColor = memoizedColors.get(templateField);
|
||||
const memoizedKey = memoizedKeys.get(templateField);
|
||||
|
||||
return (
|
||||
<NodeInputField
|
||||
lastInput={
|
||||
idx === templateFields.length - 1 &&
|
||||
!(shownOutputs.length > 0 || showHiddenOutputs)
|
||||
}
|
||||
key={memoizedKey}
|
||||
data={data}
|
||||
colors={memoizedColor.colors}
|
||||
title={getFieldTitle(data.node?.template!, templateField)}
|
||||
info={template.info!}
|
||||
name={templateField}
|
||||
tooltipTitle={template.input_types?.join("\n") ?? template.type}
|
||||
required={template.required}
|
||||
id={{
|
||||
inputTypes: template.input_types,
|
||||
type: template.type,
|
||||
id: data.id,
|
||||
fieldName: templateField,
|
||||
}}
|
||||
type={template.type}
|
||||
optionalHandle={template.input_types}
|
||||
proxy={template.proxy}
|
||||
showNode={showNode}
|
||||
colorName={memoizedColor.colorsName}
|
||||
isToolMode={isToolMode && template.tool_mode}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
return <>{renderInputParameter}</>;
|
||||
};
|
||||
|
||||
export default RenderInputParameters;
|
||||
|
|
@ -17,29 +17,22 @@ import { useShortcutsStore } from "../../stores/shortcuts";
|
|||
import { useTypesStore } from "../../stores/typesStore";
|
||||
import { OutputFieldType, VertexBuildTypeAPI } from "../../types/api";
|
||||
import { NodeDataType } from "../../types/flow";
|
||||
import {
|
||||
checkHasToolMode,
|
||||
scapedJSONStringfy,
|
||||
} from "../../utils/reactflowUtils";
|
||||
import { checkHasToolMode } from "../../utils/reactflowUtils";
|
||||
import { classNames, cn } from "../../utils/utils";
|
||||
import { getNodeInputColors } from "../helpers/get-node-input-colors";
|
||||
import { getNodeInputColorsName } from "../helpers/get-node-input-colors-name";
|
||||
import { getNodeOutputColors } from "../helpers/get-node-output-colors";
|
||||
import { getNodeOutputColorsName } from "../helpers/get-node-output-colors-name";
|
||||
|
||||
import { processNodeAdvancedFields } from "../helpers/process-node-advanced-fields";
|
||||
import useCheckCodeValidity from "../hooks/use-check-code-validity";
|
||||
import useUpdateNodeCode from "../hooks/use-update-node-code";
|
||||
import getFieldTitle from "../utils/get-field-title";
|
||||
import sortFields from "../utils/sort-fields";
|
||||
import NodeDescription from "./components/NodeDescription";
|
||||
import NodeInputField from "./components/NodeInputField";
|
||||
import NodeName from "./components/NodeName";
|
||||
import NodeOutputField from "./components/NodeOutputfield";
|
||||
import { OutputParameter } from "./components/NodeOutputParameter";
|
||||
import NodeStatus from "./components/NodeStatus";
|
||||
import RenderInputParameters from "./components/RenderInputParameters";
|
||||
import { NodeIcon } from "./components/nodeIcon";
|
||||
import { useBuildStatus } from "./hooks/use-get-build-status";
|
||||
|
||||
const sortToolModeFields = (
|
||||
export const sortToolModeFields = (
|
||||
a: string,
|
||||
b: string,
|
||||
template: any,
|
||||
|
|
@ -172,42 +165,23 @@ export default function GenericNode({
|
|||
|
||||
const [openShowMoreOptions, setOpenShowMoreOptions] = useState(false);
|
||||
|
||||
const renderOutputParameter = (
|
||||
output: OutputFieldType,
|
||||
idx: number,
|
||||
lastOutput: boolean,
|
||||
) => {
|
||||
return (
|
||||
<NodeOutputField
|
||||
index={idx}
|
||||
lastOutput={lastOutput}
|
||||
selected={selected}
|
||||
key={
|
||||
scapedJSONStringfy({
|
||||
output_types: output.types,
|
||||
name: output.name,
|
||||
id: data.id,
|
||||
dataType: data.type,
|
||||
}) + idx
|
||||
const renderOutputs = (outputs) => {
|
||||
return outputs.map((output, idx) => (
|
||||
<OutputParameter
|
||||
key={output.name + idx}
|
||||
output={output}
|
||||
idx={
|
||||
data.node!.outputs?.findIndex((out) => out.name === output.name) ??
|
||||
idx
|
||||
}
|
||||
lastOutput={idx === outputs.length - 1}
|
||||
data={data}
|
||||
colors={getNodeOutputColors(output, data, types)}
|
||||
outputProxy={output.proxy}
|
||||
title={output.display_name ?? output.name}
|
||||
tooltipTitle={output.selected ?? output.types[0]}
|
||||
id={{
|
||||
output_types: [output.selected ?? output.types[0]],
|
||||
id: data.id,
|
||||
dataType: data.type,
|
||||
name: output.name,
|
||||
}}
|
||||
type={output.types.join("|")}
|
||||
types={types}
|
||||
selected={selected}
|
||||
showNode={showNode}
|
||||
outputName={output.name}
|
||||
colorName={getNodeOutputColorsName(output, data, types)}
|
||||
isToolMode={isToolMode}
|
||||
/>
|
||||
);
|
||||
));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -260,72 +234,6 @@ export default function GenericNode({
|
|||
data.node?.outputs?.some((output) => output.name === "component_as_tool") ??
|
||||
false;
|
||||
|
||||
const renderInputParameter = Object.keys(data.node!.template)
|
||||
.filter((templateField) => templateField.charAt(0) !== "_")
|
||||
.sort((a, b) =>
|
||||
sortToolModeFields(
|
||||
a,
|
||||
b,
|
||||
data.node!.template,
|
||||
data.node?.field_order ?? [],
|
||||
isToolMode,
|
||||
),
|
||||
)
|
||||
.map(
|
||||
(templateField: string, idx) =>
|
||||
data.node!.template[templateField]?.show &&
|
||||
!data.node!.template[templateField]?.advanced && (
|
||||
<NodeInputField
|
||||
lastInput={
|
||||
idx ===
|
||||
Object.keys(data.node!.template).filter(
|
||||
(templateField) => templateField.charAt(0) !== "_",
|
||||
).length -
|
||||
1 && !(shownOutputs.length > 0 || showHiddenOutputs)
|
||||
}
|
||||
key={scapedJSONStringfy({
|
||||
inputTypes: data.node!.template[templateField].input_types,
|
||||
type: data.node!.template[templateField].type,
|
||||
id: data.id,
|
||||
fieldName: templateField,
|
||||
proxy: data.node!.template[templateField].proxy,
|
||||
})}
|
||||
data={data}
|
||||
colors={getNodeInputColors(
|
||||
data.node?.template[templateField].input_types,
|
||||
data.node?.template[templateField].type,
|
||||
types,
|
||||
)}
|
||||
title={getFieldTitle(data.node?.template!, templateField)}
|
||||
info={data.node?.template[templateField].info!}
|
||||
name={templateField}
|
||||
tooltipTitle={
|
||||
data.node?.template[templateField].input_types?.join("\n") ??
|
||||
data.node?.template[templateField].type
|
||||
}
|
||||
required={data.node!.template[templateField].required}
|
||||
id={{
|
||||
inputTypes: data.node!.template[templateField].input_types,
|
||||
type: data.node!.template[templateField].type,
|
||||
id: data.id,
|
||||
fieldName: templateField,
|
||||
}}
|
||||
type={data.node?.template[templateField].type}
|
||||
optionalHandle={data.node?.template[templateField].input_types}
|
||||
proxy={data.node?.template[templateField].proxy}
|
||||
showNode={showNode}
|
||||
colorName={getNodeInputColorsName(
|
||||
data.node?.template[templateField].input_types,
|
||||
data.node?.template[templateField].type,
|
||||
types,
|
||||
)}
|
||||
isToolMode={
|
||||
isToolMode && data.node!.template[templateField].tool_mode
|
||||
}
|
||||
/>
|
||||
),
|
||||
);
|
||||
|
||||
const buildStatus = useBuildStatus(data, data.id);
|
||||
const hasOutputs = data.node?.outputs && data.node?.outputs.length > 0;
|
||||
const [validationStatus, setValidationStatus] =
|
||||
|
|
@ -412,16 +320,17 @@ export default function GenericNode({
|
|||
<div>
|
||||
{!showNode && (
|
||||
<>
|
||||
{renderInputParameter}
|
||||
<RenderInputParameters
|
||||
data={data}
|
||||
types={types}
|
||||
isToolMode={isToolMode}
|
||||
showNode={showNode}
|
||||
shownOutputs={shownOutputs}
|
||||
showHiddenOutputs={showHiddenOutputs}
|
||||
/>
|
||||
{shownOutputs &&
|
||||
shownOutputs.length > 0 &&
|
||||
renderOutputParameter(
|
||||
shownOutputs[0],
|
||||
data.node!.outputs?.findIndex(
|
||||
(out) => out.name === shownOutputs[0].name,
|
||||
) ?? 0,
|
||||
false,
|
||||
)}
|
||||
renderOutputs(shownOutputs)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -452,10 +361,15 @@ export default function GenericNode({
|
|||
</div>
|
||||
{showNode && (
|
||||
<div className="relative">
|
||||
{/* increase height!! */}
|
||||
|
||||
<>
|
||||
{renderInputParameter}
|
||||
<RenderInputParameters
|
||||
data={data}
|
||||
types={types}
|
||||
isToolMode={isToolMode}
|
||||
showNode={showNode}
|
||||
shownOutputs={shownOutputs}
|
||||
showHiddenOutputs={showHiddenOutputs}
|
||||
/>
|
||||
<div
|
||||
className={classNames(
|
||||
Object.keys(data.node!.template).length < 1 ? "hidden" : "",
|
||||
|
|
@ -466,29 +380,44 @@ export default function GenericNode({
|
|||
</div>
|
||||
{!showHiddenOutputs &&
|
||||
shownOutputs &&
|
||||
shownOutputs.map((output, idx) =>
|
||||
renderOutputParameter(
|
||||
output,
|
||||
data.node!.outputs?.findIndex(
|
||||
(out) => out.name === output.name,
|
||||
) ?? idx,
|
||||
idx === shownOutputs.length - 1,
|
||||
),
|
||||
)}
|
||||
shownOutputs.map((output, idx) => (
|
||||
<OutputParameter
|
||||
key={`shown-${output.name}-${idx}`}
|
||||
output={output}
|
||||
idx={
|
||||
data.node!.outputs?.findIndex(
|
||||
(out) => out.name === output.name,
|
||||
) ?? idx
|
||||
}
|
||||
lastOutput={idx === shownOutputs.length - 1}
|
||||
data={data}
|
||||
types={types}
|
||||
selected={selected}
|
||||
showNode={showNode}
|
||||
isToolMode={isToolMode}
|
||||
/>
|
||||
))}
|
||||
<div
|
||||
className={cn(showHiddenOutputs ? "" : "h-0 overflow-hidden")}
|
||||
>
|
||||
<div className="block">
|
||||
{data.node!.outputs &&
|
||||
data.node!.outputs.map((output, idx) => {
|
||||
return renderOutputParameter(
|
||||
output,
|
||||
{data.node!.outputs?.map((output, idx) => (
|
||||
<OutputParameter
|
||||
key={`hidden-${output.name}-${idx}`}
|
||||
output={output}
|
||||
idx={
|
||||
data.node!.outputs?.findIndex(
|
||||
(out) => out.name === output.name,
|
||||
) ?? idx,
|
||||
idx === (data.node!.outputs?.length ?? 0) - 1,
|
||||
);
|
||||
})}
|
||||
) ?? idx
|
||||
}
|
||||
lastOutput={idx === (data.node!.outputs?.length ?? 0) - 1}
|
||||
data={data}
|
||||
types={types}
|
||||
selected={selected}
|
||||
showNode={showNode}
|
||||
isToolMode={isToolMode}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{hiddenOutputs && hiddenOutputs.length > 0 && (
|
||||
|
|
|
|||
|
|
@ -140,9 +140,13 @@ export const SidebarDraggableComponent = forwardRef(
|
|||
}}
|
||||
onDragStart={onDragStart}
|
||||
onDragEnd={() => {
|
||||
document.body.removeChild(
|
||||
document.getElementsByClassName("cursor-grabbing")[0],
|
||||
);
|
||||
if (
|
||||
document.getElementsByClassName("cursor-grabbing").length > 0
|
||||
) {
|
||||
document.body.removeChild(
|
||||
document.getElementsByClassName("cursor-grabbing")[0],
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import Fuse from "fuse.js";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useHotkeys } from "react-hotkeys-hook"; // Import useHotkeys
|
||||
|
||||
import ForwardedIconComponent from "@/components/common/genericIconComponent";
|
||||
|
|
@ -289,6 +289,27 @@ export function FlowSidebarComponent() {
|
|||
const nodes = useFlowStore((state) => state.nodes);
|
||||
const chatInputAdded = checkChatInput(nodes);
|
||||
|
||||
const handleInputFocus = useCallback(
|
||||
(event: React.FocusEvent<HTMLInputElement>) => {
|
||||
setIsInputFocused(true);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleInputBlur = useCallback(
|
||||
(event: React.FocusEvent<HTMLInputElement>) => {
|
||||
setIsInputFocused(false);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleInputChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
handleSearchInput(event.target.value);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<Sidebar
|
||||
collapsible="offcanvas"
|
||||
|
|
@ -339,10 +360,10 @@ export function FlowSidebarComponent() {
|
|||
data-testid="sidebar-search-input"
|
||||
className="w-full rounded-lg bg-background pl-8 text-sm"
|
||||
placeholder=""
|
||||
onFocus={() => setIsInputFocused(true)}
|
||||
onBlur={() => setIsInputFocused(false)}
|
||||
onFocus={handleInputFocus}
|
||||
onBlur={handleInputBlur}
|
||||
onChange={handleInputChange}
|
||||
value={search}
|
||||
onChange={(e) => handleSearchInput(e.target.value)}
|
||||
/>
|
||||
{!isInputFocused && search === "" && (
|
||||
<div className="pointer-events-none absolute inset-y-0 left-8 top-1/2 flex w-4/5 -translate-y-1/2 items-center justify-between gap-2 text-sm text-muted-foreground">
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import useAddFlow from "@/hooks/flows/use-add-flow";
|
|||
import CodeAreaModal from "@/modals/codeAreaModal";
|
||||
import { APIClassType } from "@/types/api";
|
||||
import _, { cloneDeep } from "lodash";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useUpdateNodeInternals } from "reactflow";
|
||||
import IconComponent from "../../../../components/common/genericIconComponent";
|
||||
import {
|
||||
|
|
@ -395,6 +395,28 @@ export default function NodeToolbarComponent({
|
|||
tool_mode: data.node!.tool_mode ?? false,
|
||||
});
|
||||
|
||||
const handleConfirm = useCallback(() => {
|
||||
addFlow({
|
||||
flow: flowComponent,
|
||||
override: true,
|
||||
});
|
||||
setSuccessData({ title: `${data.id} successfully overridden!` });
|
||||
setShowOverrideModal(false);
|
||||
}, [flowComponent, setSuccessData, setShowOverrideModal]);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
setShowOverrideModal(false);
|
||||
}, []);
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
addFlow({
|
||||
flow: flowComponent,
|
||||
override: true,
|
||||
});
|
||||
setSuccessData({ title: "New component successfully saved!" });
|
||||
setShowOverrideModal(false);
|
||||
}, [flowComponent, setSuccessData, setShowOverrideModal]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="noflow nopan nodelete nodrag">
|
||||
|
|
@ -754,29 +776,10 @@ export default function NodeToolbarComponent({
|
|||
|
||||
<ConfirmationModal
|
||||
open={showOverrideModal}
|
||||
title={`Replace`}
|
||||
cancelText="Create New"
|
||||
confirmationText="Replace"
|
||||
size={"x-small"}
|
||||
icon={"SaveAll"}
|
||||
index={6}
|
||||
onConfirm={() => {
|
||||
addFlow({
|
||||
flow: flowComponent,
|
||||
override: true,
|
||||
});
|
||||
setSuccessData({ title: `${data.id} successfully overridden!` });
|
||||
setShowOverrideModal(false);
|
||||
}}
|
||||
onClose={() => setShowOverrideModal(false)}
|
||||
onCancel={() => {
|
||||
addFlow({
|
||||
flow: flowComponent,
|
||||
override: true,
|
||||
});
|
||||
setSuccessData({ title: "New component successfully saved!" });
|
||||
setShowOverrideModal(false);
|
||||
}}
|
||||
title="Replace"
|
||||
onConfirm={handleConfirm}
|
||||
onClose={handleClose}
|
||||
onCancel={handleCancel}
|
||||
>
|
||||
<ConfirmationModal.Content>
|
||||
<span>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ async function zoomOut(page: Page, times: number = 4) {
|
|||
|
||||
test(
|
||||
"should create a flow with decision",
|
||||
{ tag: ["@release", "@components"] },
|
||||
{ tag: ["@release", "@components", "@workflow"] },
|
||||
|
||||
async ({ page }) => {
|
||||
test.skip(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue