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 useFlowStore from "@/stores/flowStore";
|
||||||
import { handleKeyDown } from "@/utils/reactflowUtils";
|
import { handleKeyDown } from "@/utils/reactflowUtils";
|
||||||
import { cn } from "@/utils/utils";
|
import { cn } from "@/utils/utils";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { memo, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import Markdown from "react-markdown";
|
import Markdown from "react-markdown";
|
||||||
|
|
||||||
export default function NodeDescription({
|
export default function NodeDescription({
|
||||||
|
|
@ -59,6 +59,25 @@ export default function NodeDescription({
|
||||||
setNodeDescription(description);
|
setNodeDescription(description);
|
||||||
}, [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 (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
@ -138,19 +157,7 @@ export default function NodeDescription({
|
||||||
takeSnapshot();
|
takeSnapshot();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{description === "" || !description ? (
|
{renderedDescription}
|
||||||
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>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</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 { ICON_STROKE_WIDTH } from "@/constants/constants";
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
import { useEffect, useRef } from "react";
|
import { memo, useEffect, useMemo, useRef } from "react";
|
||||||
import { useUpdateNodeInternals } from "reactflow";
|
import { useUpdateNodeInternals } from "reactflow";
|
||||||
import { default as IconComponent } from "../../../../components/common/genericIconComponent";
|
import { default as IconComponent } from "../../../../components/common/genericIconComponent";
|
||||||
import ShadTooltip from "../../../../components/common/shadTooltipComponent";
|
import ShadTooltip from "../../../../components/common/shadTooltipComponent";
|
||||||
|
|
@ -105,8 +105,23 @@ export default function NodeOutputField({
|
||||||
}
|
}
|
||||||
}, [disabledOutput]);
|
}, [disabledOutput]);
|
||||||
|
|
||||||
const Handle = (
|
const MemoizedHandleRenderComponent = memo(
|
||||||
<HandleRenderComponent
|
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}
|
left={false}
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
tooltipTitle={tooltipTitle}
|
tooltipTitle={tooltipTitle}
|
||||||
|
|
@ -121,6 +136,21 @@ export default function NodeOutputField({
|
||||||
testIdComplement={`${data?.type?.toLowerCase()}-${showNode ? "shownode" : "noshownode"}`}
|
testIdComplement={`${data?.type?.toLowerCase()}-${showNode ? "shownode" : "noshownode"}`}
|
||||||
colorName={colorName}
|
colorName={colorName}
|
||||||
/>
|
/>
|
||||||
|
),
|
||||||
|
[
|
||||||
|
nodes,
|
||||||
|
tooltipTitle,
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
edges,
|
||||||
|
data.id,
|
||||||
|
myData,
|
||||||
|
colors,
|
||||||
|
setFilterEdge,
|
||||||
|
showNode,
|
||||||
|
data?.type,
|
||||||
|
colorName,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
return !showNode ? (
|
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 { useTypesStore } from "../../stores/typesStore";
|
||||||
import { OutputFieldType, VertexBuildTypeAPI } from "../../types/api";
|
import { OutputFieldType, VertexBuildTypeAPI } from "../../types/api";
|
||||||
import { NodeDataType } from "../../types/flow";
|
import { NodeDataType } from "../../types/flow";
|
||||||
import {
|
import { checkHasToolMode } from "../../utils/reactflowUtils";
|
||||||
checkHasToolMode,
|
|
||||||
scapedJSONStringfy,
|
|
||||||
} from "../../utils/reactflowUtils";
|
|
||||||
import { classNames, cn } from "../../utils/utils";
|
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 { processNodeAdvancedFields } from "../helpers/process-node-advanced-fields";
|
||||||
import useCheckCodeValidity from "../hooks/use-check-code-validity";
|
import useCheckCodeValidity from "../hooks/use-check-code-validity";
|
||||||
import useUpdateNodeCode from "../hooks/use-update-node-code";
|
import useUpdateNodeCode from "../hooks/use-update-node-code";
|
||||||
import getFieldTitle from "../utils/get-field-title";
|
|
||||||
import sortFields from "../utils/sort-fields";
|
import sortFields from "../utils/sort-fields";
|
||||||
import NodeDescription from "./components/NodeDescription";
|
import NodeDescription from "./components/NodeDescription";
|
||||||
import NodeInputField from "./components/NodeInputField";
|
|
||||||
import NodeName from "./components/NodeName";
|
import NodeName from "./components/NodeName";
|
||||||
import NodeOutputField from "./components/NodeOutputfield";
|
import { OutputParameter } from "./components/NodeOutputParameter";
|
||||||
import NodeStatus from "./components/NodeStatus";
|
import NodeStatus from "./components/NodeStatus";
|
||||||
|
import RenderInputParameters from "./components/RenderInputParameters";
|
||||||
import { NodeIcon } from "./components/nodeIcon";
|
import { NodeIcon } from "./components/nodeIcon";
|
||||||
import { useBuildStatus } from "./hooks/use-get-build-status";
|
import { useBuildStatus } from "./hooks/use-get-build-status";
|
||||||
|
|
||||||
const sortToolModeFields = (
|
export const sortToolModeFields = (
|
||||||
a: string,
|
a: string,
|
||||||
b: string,
|
b: string,
|
||||||
template: any,
|
template: any,
|
||||||
|
|
@ -172,42 +165,23 @@ export default function GenericNode({
|
||||||
|
|
||||||
const [openShowMoreOptions, setOpenShowMoreOptions] = useState(false);
|
const [openShowMoreOptions, setOpenShowMoreOptions] = useState(false);
|
||||||
|
|
||||||
const renderOutputParameter = (
|
const renderOutputs = (outputs) => {
|
||||||
output: OutputFieldType,
|
return outputs.map((output, idx) => (
|
||||||
idx: number,
|
<OutputParameter
|
||||||
lastOutput: boolean,
|
key={output.name + idx}
|
||||||
) => {
|
output={output}
|
||||||
return (
|
idx={
|
||||||
<NodeOutputField
|
data.node!.outputs?.findIndex((out) => out.name === output.name) ??
|
||||||
index={idx}
|
idx
|
||||||
lastOutput={lastOutput}
|
|
||||||
selected={selected}
|
|
||||||
key={
|
|
||||||
scapedJSONStringfy({
|
|
||||||
output_types: output.types,
|
|
||||||
name: output.name,
|
|
||||||
id: data.id,
|
|
||||||
dataType: data.type,
|
|
||||||
}) + idx
|
|
||||||
}
|
}
|
||||||
|
lastOutput={idx === outputs.length - 1}
|
||||||
data={data}
|
data={data}
|
||||||
colors={getNodeOutputColors(output, data, types)}
|
types={types}
|
||||||
outputProxy={output.proxy}
|
selected={selected}
|
||||||
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("|")}
|
|
||||||
showNode={showNode}
|
showNode={showNode}
|
||||||
outputName={output.name}
|
|
||||||
colorName={getNodeOutputColorsName(output, data, types)}
|
|
||||||
isToolMode={isToolMode}
|
isToolMode={isToolMode}
|
||||||
/>
|
/>
|
||||||
);
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -260,72 +234,6 @@ export default function GenericNode({
|
||||||
data.node?.outputs?.some((output) => output.name === "component_as_tool") ??
|
data.node?.outputs?.some((output) => output.name === "component_as_tool") ??
|
||||||
false;
|
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 buildStatus = useBuildStatus(data, data.id);
|
||||||
const hasOutputs = data.node?.outputs && data.node?.outputs.length > 0;
|
const hasOutputs = data.node?.outputs && data.node?.outputs.length > 0;
|
||||||
const [validationStatus, setValidationStatus] =
|
const [validationStatus, setValidationStatus] =
|
||||||
|
|
@ -412,16 +320,17 @@ export default function GenericNode({
|
||||||
<div>
|
<div>
|
||||||
{!showNode && (
|
{!showNode && (
|
||||||
<>
|
<>
|
||||||
{renderInputParameter}
|
<RenderInputParameters
|
||||||
|
data={data}
|
||||||
|
types={types}
|
||||||
|
isToolMode={isToolMode}
|
||||||
|
showNode={showNode}
|
||||||
|
shownOutputs={shownOutputs}
|
||||||
|
showHiddenOutputs={showHiddenOutputs}
|
||||||
|
/>
|
||||||
{shownOutputs &&
|
{shownOutputs &&
|
||||||
shownOutputs.length > 0 &&
|
shownOutputs.length > 0 &&
|
||||||
renderOutputParameter(
|
renderOutputs(shownOutputs)}
|
||||||
shownOutputs[0],
|
|
||||||
data.node!.outputs?.findIndex(
|
|
||||||
(out) => out.name === shownOutputs[0].name,
|
|
||||||
) ?? 0,
|
|
||||||
false,
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -452,10 +361,15 @@ export default function GenericNode({
|
||||||
</div>
|
</div>
|
||||||
{showNode && (
|
{showNode && (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
{/* increase height!! */}
|
|
||||||
|
|
||||||
<>
|
<>
|
||||||
{renderInputParameter}
|
<RenderInputParameters
|
||||||
|
data={data}
|
||||||
|
types={types}
|
||||||
|
isToolMode={isToolMode}
|
||||||
|
showNode={showNode}
|
||||||
|
shownOutputs={shownOutputs}
|
||||||
|
showHiddenOutputs={showHiddenOutputs}
|
||||||
|
/>
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
Object.keys(data.node!.template).length < 1 ? "hidden" : "",
|
Object.keys(data.node!.template).length < 1 ? "hidden" : "",
|
||||||
|
|
@ -466,29 +380,44 @@ export default function GenericNode({
|
||||||
</div>
|
</div>
|
||||||
{!showHiddenOutputs &&
|
{!showHiddenOutputs &&
|
||||||
shownOutputs &&
|
shownOutputs &&
|
||||||
shownOutputs.map((output, idx) =>
|
shownOutputs.map((output, idx) => (
|
||||||
renderOutputParameter(
|
<OutputParameter
|
||||||
output,
|
key={`shown-${output.name}-${idx}`}
|
||||||
|
output={output}
|
||||||
|
idx={
|
||||||
data.node!.outputs?.findIndex(
|
data.node!.outputs?.findIndex(
|
||||||
(out) => out.name === output.name,
|
(out) => out.name === output.name,
|
||||||
) ?? idx,
|
) ?? idx
|
||||||
idx === shownOutputs.length - 1,
|
}
|
||||||
),
|
lastOutput={idx === shownOutputs.length - 1}
|
||||||
)}
|
data={data}
|
||||||
|
types={types}
|
||||||
|
selected={selected}
|
||||||
|
showNode={showNode}
|
||||||
|
isToolMode={isToolMode}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
<div
|
<div
|
||||||
className={cn(showHiddenOutputs ? "" : "h-0 overflow-hidden")}
|
className={cn(showHiddenOutputs ? "" : "h-0 overflow-hidden")}
|
||||||
>
|
>
|
||||||
<div className="block">
|
<div className="block">
|
||||||
{data.node!.outputs &&
|
{data.node!.outputs?.map((output, idx) => (
|
||||||
data.node!.outputs.map((output, idx) => {
|
<OutputParameter
|
||||||
return renderOutputParameter(
|
key={`hidden-${output.name}-${idx}`}
|
||||||
output,
|
output={output}
|
||||||
|
idx={
|
||||||
data.node!.outputs?.findIndex(
|
data.node!.outputs?.findIndex(
|
||||||
(out) => out.name === output.name,
|
(out) => out.name === output.name,
|
||||||
) ?? idx,
|
) ?? idx
|
||||||
idx === (data.node!.outputs?.length ?? 0) - 1,
|
}
|
||||||
);
|
lastOutput={idx === (data.node!.outputs?.length ?? 0) - 1}
|
||||||
})}
|
data={data}
|
||||||
|
types={types}
|
||||||
|
selected={selected}
|
||||||
|
showNode={showNode}
|
||||||
|
isToolMode={isToolMode}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{hiddenOutputs && hiddenOutputs.length > 0 && (
|
{hiddenOutputs && hiddenOutputs.length > 0 && (
|
||||||
|
|
|
||||||
|
|
@ -140,9 +140,13 @@ export const SidebarDraggableComponent = forwardRef(
|
||||||
}}
|
}}
|
||||||
onDragStart={onDragStart}
|
onDragStart={onDragStart}
|
||||||
onDragEnd={() => {
|
onDragEnd={() => {
|
||||||
|
if (
|
||||||
|
document.getElementsByClassName("cursor-grabbing").length > 0
|
||||||
|
) {
|
||||||
document.body.removeChild(
|
document.body.removeChild(
|
||||||
document.getElementsByClassName("cursor-grabbing")[0],
|
document.getElementsByClassName("cursor-grabbing")[0],
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ForwardedIconComponent
|
<ForwardedIconComponent
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import Fuse from "fuse.js";
|
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 { useHotkeys } from "react-hotkeys-hook"; // Import useHotkeys
|
||||||
|
|
||||||
import ForwardedIconComponent from "@/components/common/genericIconComponent";
|
import ForwardedIconComponent from "@/components/common/genericIconComponent";
|
||||||
|
|
@ -289,6 +289,27 @@ export function FlowSidebarComponent() {
|
||||||
const nodes = useFlowStore((state) => state.nodes);
|
const nodes = useFlowStore((state) => state.nodes);
|
||||||
const chatInputAdded = checkChatInput(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 (
|
return (
|
||||||
<Sidebar
|
<Sidebar
|
||||||
collapsible="offcanvas"
|
collapsible="offcanvas"
|
||||||
|
|
@ -339,10 +360,10 @@ export function FlowSidebarComponent() {
|
||||||
data-testid="sidebar-search-input"
|
data-testid="sidebar-search-input"
|
||||||
className="w-full rounded-lg bg-background pl-8 text-sm"
|
className="w-full rounded-lg bg-background pl-8 text-sm"
|
||||||
placeholder=""
|
placeholder=""
|
||||||
onFocus={() => setIsInputFocused(true)}
|
onFocus={handleInputFocus}
|
||||||
onBlur={() => setIsInputFocused(false)}
|
onBlur={handleInputBlur}
|
||||||
|
onChange={handleInputChange}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => handleSearchInput(e.target.value)}
|
|
||||||
/>
|
/>
|
||||||
{!isInputFocused && search === "" && (
|
{!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">
|
<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 CodeAreaModal from "@/modals/codeAreaModal";
|
||||||
import { APIClassType } from "@/types/api";
|
import { APIClassType } from "@/types/api";
|
||||||
import _, { cloneDeep } from "lodash";
|
import _, { cloneDeep } from "lodash";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { useUpdateNodeInternals } from "reactflow";
|
import { useUpdateNodeInternals } from "reactflow";
|
||||||
import IconComponent from "../../../../components/common/genericIconComponent";
|
import IconComponent from "../../../../components/common/genericIconComponent";
|
||||||
import {
|
import {
|
||||||
|
|
@ -395,6 +395,28 @@ export default function NodeToolbarComponent({
|
||||||
tool_mode: data.node!.tool_mode ?? false,
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="noflow nopan nodelete nodrag">
|
<div className="noflow nopan nodelete nodrag">
|
||||||
|
|
@ -754,29 +776,10 @@ export default function NodeToolbarComponent({
|
||||||
|
|
||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
open={showOverrideModal}
|
open={showOverrideModal}
|
||||||
title={`Replace`}
|
title="Replace"
|
||||||
cancelText="Create New"
|
onConfirm={handleConfirm}
|
||||||
confirmationText="Replace"
|
onClose={handleClose}
|
||||||
size={"x-small"}
|
onCancel={handleCancel}
|
||||||
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);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<ConfirmationModal.Content>
|
<ConfirmationModal.Content>
|
||||||
<span>
|
<span>
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ async function zoomOut(page: Page, times: number = 4) {
|
||||||
|
|
||||||
test(
|
test(
|
||||||
"should create a flow with decision",
|
"should create a flow with decision",
|
||||||
{ tag: ["@release", "@components"] },
|
{ tag: ["@release", "@components", "@workflow"] },
|
||||||
|
|
||||||
async ({ page }) => {
|
async ({ page }) => {
|
||||||
test.skip(
|
test.skip(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue