refactor: add components for node name, description, and status (#3248)
* remove repeated code * refactor: remove useless code * feat: sort input parameters in GenericNode renderInputParameter * refactor: remove unused code in GenericNode component * refactor: add NodeName component for displaying and editing node names * refactor: add NodeDescription component for displaying and editing node descriptions * fix import and add autofocus on nodeName * feat: add NodeStatus component for displaying and managing node status * [autofix.ci] apply automated fixes * refactor: remove unused code in GenericNode component * fix bugs on minimize * [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
952ba5eef1
commit
64aecddeca
5 changed files with 457 additions and 463 deletions
|
|
@ -0,0 +1,100 @@
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
||||||
|
import useFlowStore from "@/stores/flowStore";
|
||||||
|
import { handleKeyDown } from "@/utils/reactflowUtils";
|
||||||
|
import { cn } from "@/utils/utils";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import Markdown from "react-markdown";
|
||||||
|
|
||||||
|
export default function NodeDescription({
|
||||||
|
description,
|
||||||
|
selected,
|
||||||
|
nodeId,
|
||||||
|
}: {
|
||||||
|
description?: string;
|
||||||
|
selected: boolean;
|
||||||
|
nodeId: string;
|
||||||
|
}) {
|
||||||
|
const [inputDescription, setInputDescription] = useState(false);
|
||||||
|
const [nodeDescription, setNodeDescription] = useState(description);
|
||||||
|
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
|
||||||
|
const setNode = useFlowStore((state) => state.setNode);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!selected) {
|
||||||
|
setInputDescription(false);
|
||||||
|
}
|
||||||
|
}, [selected]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setNodeDescription(description);
|
||||||
|
}, [description]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="generic-node-desc">
|
||||||
|
{inputDescription ? (
|
||||||
|
<Textarea
|
||||||
|
className="nowheel min-h-40"
|
||||||
|
autoFocus
|
||||||
|
onBlur={() => {
|
||||||
|
setInputDescription(false);
|
||||||
|
setNodeDescription(nodeDescription);
|
||||||
|
setNode(nodeId, (old) => ({
|
||||||
|
...old,
|
||||||
|
data: {
|
||||||
|
...old.data,
|
||||||
|
node: {
|
||||||
|
...old.data.node,
|
||||||
|
description: nodeDescription,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
value={nodeDescription}
|
||||||
|
onChange={(e) => setNodeDescription(e.target.value)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
handleKeyDown(e, nodeDescription, "");
|
||||||
|
if (
|
||||||
|
e.key === "Enter" &&
|
||||||
|
e.shiftKey === false &&
|
||||||
|
e.ctrlKey === false &&
|
||||||
|
e.altKey === false
|
||||||
|
) {
|
||||||
|
setInputDescription(false);
|
||||||
|
setNodeDescription(nodeDescription);
|
||||||
|
setNode(nodeId, (old) => ({
|
||||||
|
...old,
|
||||||
|
data: {
|
||||||
|
...old.data,
|
||||||
|
node: {
|
||||||
|
...old.data.node,
|
||||||
|
description: nodeDescription,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"nodoubleclick generic-node-desc-text cursor-text word-break-break-word",
|
||||||
|
description === "" || !description ? "font-light italic" : "",
|
||||||
|
)}
|
||||||
|
onDoubleClick={(e) => {
|
||||||
|
setInputDescription(true);
|
||||||
|
takeSnapshot();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{description === "" || !description ? (
|
||||||
|
"Double Click to Edit Description"
|
||||||
|
) : (
|
||||||
|
<Markdown className="markdown prose flex flex-col text-primary word-break-break-word dark:prose-invert">
|
||||||
|
{String(description)}
|
||||||
|
</Markdown>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
import InputComponent from "@/components/inputComponent";
|
||||||
|
import ShadTooltip from "@/components/shadTooltipComponent";
|
||||||
|
import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
||||||
|
import useFlowStore from "@/stores/flowStore";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
export default function NodeName({
|
||||||
|
display_name,
|
||||||
|
selected,
|
||||||
|
nodeId,
|
||||||
|
}: {
|
||||||
|
display_name?: string;
|
||||||
|
selected: boolean;
|
||||||
|
nodeId: string;
|
||||||
|
}) {
|
||||||
|
const [inputName, setInputName] = useState(false);
|
||||||
|
const [nodeName, setNodeName] = useState(display_name);
|
||||||
|
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
|
||||||
|
const setNode = useFlowStore((state) => state.setNode);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!selected) {
|
||||||
|
setInputName(false);
|
||||||
|
}
|
||||||
|
}, [selected]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setNodeName(display_name);
|
||||||
|
}, [display_name]);
|
||||||
|
|
||||||
|
return inputName ? (
|
||||||
|
<div>
|
||||||
|
<InputComponent
|
||||||
|
onBlur={() => {
|
||||||
|
setInputName(false);
|
||||||
|
if (nodeName?.trim() !== "") {
|
||||||
|
setNodeName(nodeName);
|
||||||
|
setNode(nodeId, (old) => ({
|
||||||
|
...old,
|
||||||
|
data: {
|
||||||
|
...old.data,
|
||||||
|
node: {
|
||||||
|
...old.data.node,
|
||||||
|
display_name: nodeName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
setNodeName(display_name);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
value={nodeName}
|
||||||
|
autoFocus
|
||||||
|
onChange={setNodeName}
|
||||||
|
password={false}
|
||||||
|
blurOnEnter={true}
|
||||||
|
id={`input-title-${display_name}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="group flex items-center gap-1">
|
||||||
|
<ShadTooltip content={display_name}>
|
||||||
|
<div
|
||||||
|
onDoubleClick={(event) => {
|
||||||
|
setInputName(true);
|
||||||
|
takeSnapshot();
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
}}
|
||||||
|
data-testid={"title-" + display_name}
|
||||||
|
className="nodoubleclick generic-node-tooltip-div cursor-text text-primary"
|
||||||
|
>
|
||||||
|
{display_name}
|
||||||
|
</div>
|
||||||
|
</ShadTooltip>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,190 @@
|
||||||
|
import { getSpecificClassFromBuildStatus } from "@/CustomNodes/helpers/get-class-from-build-status";
|
||||||
|
import useIconStatus from "@/CustomNodes/hooks/use-icons-status";
|
||||||
|
import useUpdateValidationStatus from "@/CustomNodes/hooks/use-update-validation-status";
|
||||||
|
import useValidationStatusString from "@/CustomNodes/hooks/use-validation-status-string";
|
||||||
|
import ShadTooltip from "@/components/shadTooltipComponent";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
RUN_TIMESTAMP_PREFIX,
|
||||||
|
STATUS_BUILD,
|
||||||
|
STATUS_BUILDING,
|
||||||
|
STATUS_INACTIVE,
|
||||||
|
} from "@/constants/constants";
|
||||||
|
import { BuildStatus } from "@/constants/enums";
|
||||||
|
import { useDarkStore } from "@/stores/darkStore";
|
||||||
|
import useFlowStore from "@/stores/flowStore";
|
||||||
|
import { useShortcutsStore } from "@/stores/shortcuts";
|
||||||
|
import { VertexBuildTypeAPI } from "@/types/api";
|
||||||
|
import { classNames } from "@/utils/utils";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useHotkeys } from "react-hotkeys-hook";
|
||||||
|
import IconComponent from "../../../../components/genericIconComponent";
|
||||||
|
|
||||||
|
export default function NodeStatus({
|
||||||
|
nodeId,
|
||||||
|
display_name,
|
||||||
|
selected,
|
||||||
|
setBorderColor,
|
||||||
|
frozen,
|
||||||
|
showNode,
|
||||||
|
}: {
|
||||||
|
nodeId: string;
|
||||||
|
display_name: string;
|
||||||
|
selected: boolean;
|
||||||
|
setBorderColor: (color: string) => void;
|
||||||
|
frozen?: boolean;
|
||||||
|
showNode: boolean;
|
||||||
|
}) {
|
||||||
|
const [validationString, setValidationString] = useState<string>("");
|
||||||
|
const [validationStatus, setValidationStatus] =
|
||||||
|
useState<VertexBuildTypeAPI | null>(null);
|
||||||
|
const buildStatus = useFlowStore(
|
||||||
|
(state) => state.flowBuildStatus[nodeId]?.status,
|
||||||
|
);
|
||||||
|
const lastRunTime = useFlowStore(
|
||||||
|
(state) => state.flowBuildStatus[nodeId]?.timestamp,
|
||||||
|
);
|
||||||
|
const iconStatus = useIconStatus(buildStatus, validationStatus);
|
||||||
|
const buildFlow = useFlowStore((state) => state.buildFlow);
|
||||||
|
const isBuilding = useFlowStore((state) => state.isBuilding);
|
||||||
|
const setNode = useFlowStore((state) => state.setNode);
|
||||||
|
const version = useDarkStore((state) => state.version);
|
||||||
|
const isDark = useDarkStore((state) => state.dark);
|
||||||
|
|
||||||
|
function handlePlayWShortcut() {
|
||||||
|
if (buildStatus === BuildStatus.BUILDING || isBuilding || !selected) return;
|
||||||
|
setValidationStatus(null);
|
||||||
|
buildFlow({ stopNodeId: nodeId });
|
||||||
|
}
|
||||||
|
|
||||||
|
const play = useShortcutsStore((state) => state.play);
|
||||||
|
const flowPool = useFlowStore((state) => state.flowPool);
|
||||||
|
useHotkeys(play, handlePlayWShortcut, { preventDefault: true });
|
||||||
|
useValidationStatusString(validationStatus, setValidationString);
|
||||||
|
useUpdateValidationStatus(nodeId, flowPool, setValidationStatus);
|
||||||
|
|
||||||
|
const getBaseBorderClass = (selected) => {
|
||||||
|
let className = selected
|
||||||
|
? "border border-ring hover:shadow-node"
|
||||||
|
: "border hover:shadow-node";
|
||||||
|
let frozenClass = selected ? "border-ring-frozen" : "border-frozen";
|
||||||
|
return frozen ? frozenClass : className;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNodeSizeClass = (showNode) =>
|
||||||
|
showNode ? "w-96 rounded-lg" : "w-26 h-26 rounded-full";
|
||||||
|
|
||||||
|
const getNodeBorderClassName = (
|
||||||
|
selected: boolean,
|
||||||
|
showNode: boolean,
|
||||||
|
buildStatus: BuildStatus | undefined,
|
||||||
|
validationStatus: VertexBuildTypeAPI | null,
|
||||||
|
) => {
|
||||||
|
const specificClassFromBuildStatus = getSpecificClassFromBuildStatus(
|
||||||
|
buildStatus,
|
||||||
|
validationStatus,
|
||||||
|
isDark,
|
||||||
|
);
|
||||||
|
|
||||||
|
const baseBorderClass = getBaseBorderClass(selected);
|
||||||
|
const nodeSizeClass = getNodeSizeClass(showNode);
|
||||||
|
const names = classNames(
|
||||||
|
baseBorderClass,
|
||||||
|
nodeSizeClass,
|
||||||
|
"generic-node-div group/node",
|
||||||
|
specificClassFromBuildStatus,
|
||||||
|
);
|
||||||
|
return names;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(selected);
|
||||||
|
setBorderColor(
|
||||||
|
getNodeBorderClassName(selected, showNode, buildStatus, validationStatus),
|
||||||
|
);
|
||||||
|
}, [selected, showNode, buildStatus, validationStatus, frozen]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (buildStatus === BuildStatus.BUILT && !isBuilding) {
|
||||||
|
setNode(
|
||||||
|
nodeId,
|
||||||
|
(old) => {
|
||||||
|
return {
|
||||||
|
...old,
|
||||||
|
data: {
|
||||||
|
...old.data,
|
||||||
|
node: {
|
||||||
|
...old.data.node,
|
||||||
|
lf_version: version,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [buildStatus, isBuilding]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-shrink-0 items-center gap-2">
|
||||||
|
<ShadTooltip
|
||||||
|
content={
|
||||||
|
buildStatus === BuildStatus.BUILDING ? (
|
||||||
|
<span> {STATUS_BUILDING} </span>
|
||||||
|
) : buildStatus === BuildStatus.INACTIVE ? (
|
||||||
|
<span> {STATUS_INACTIVE} </span>
|
||||||
|
) : !validationStatus ? (
|
||||||
|
<span className="flex">{STATUS_BUILD}</span>
|
||||||
|
) : (
|
||||||
|
<div className="max-h-100 p-2">
|
||||||
|
<div className="max-h-80 overflow-auto">
|
||||||
|
{validationString && (
|
||||||
|
<div className="ml-1 pb-2 text-status-red">
|
||||||
|
{validationString}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{lastRunTime && (
|
||||||
|
<div className="justify-left flex font-normal text-muted-foreground">
|
||||||
|
<div>{RUN_TIMESTAMP_PREFIX}</div>
|
||||||
|
<div className="ml-1 text-status-blue">{lastRunTime}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="justify-left flex font-normal text-muted-foreground">
|
||||||
|
<div>Duration:</div>
|
||||||
|
<div className="ml-1 text-status-blue">
|
||||||
|
{validationStatus?.data.duration}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
side="bottom"
|
||||||
|
>
|
||||||
|
<div className="cursor-help">{iconStatus}</div>
|
||||||
|
</ShadTooltip>
|
||||||
|
{showNode && (
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (buildStatus === BuildStatus.BUILDING || isBuilding) return;
|
||||||
|
setValidationStatus(null);
|
||||||
|
buildFlow({ stopNodeId: nodeId });
|
||||||
|
}}
|
||||||
|
unstyled
|
||||||
|
className="group p-1"
|
||||||
|
>
|
||||||
|
<div data-testid={`button_run_` + display_name.toLowerCase()}>
|
||||||
|
<IconComponent
|
||||||
|
name="Play"
|
||||||
|
className={
|
||||||
|
"h-5 w-5 fill-current stroke-2 text-muted-foreground transition-all group-hover:text-medium-indigo group-hover/node:opacity-100"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,54 +1,41 @@
|
||||||
import emojiRegex from "emoji-regex";
|
import emojiRegex from "emoji-regex";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { useHotkeys } from "react-hotkeys-hook";
|
import { useHotkeys } from "react-hotkeys-hook";
|
||||||
import Markdown from "react-markdown";
|
|
||||||
import { NodeToolbar, useUpdateNodeInternals } from "reactflow";
|
import { NodeToolbar, useUpdateNodeInternals } from "reactflow";
|
||||||
import IconComponent, {
|
import IconComponent, {
|
||||||
ForwardedIconComponent,
|
ForwardedIconComponent,
|
||||||
} from "../../components/genericIconComponent";
|
} from "../../components/genericIconComponent";
|
||||||
import InputComponent from "../../components/inputComponent";
|
|
||||||
import ShadTooltip from "../../components/shadTooltipComponent";
|
import ShadTooltip from "../../components/shadTooltipComponent";
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
import { Textarea } from "../../components/ui/textarea";
|
import { TOOLTIP_OUTDATED_NODE } from "../../constants/constants";
|
||||||
import {
|
|
||||||
RUN_TIMESTAMP_PREFIX,
|
|
||||||
STATUS_BUILD,
|
|
||||||
STATUS_BUILDING,
|
|
||||||
STATUS_INACTIVE,
|
|
||||||
TOOLTIP_OUTDATED_NODE,
|
|
||||||
} from "../../constants/constants";
|
|
||||||
import { BuildStatus } from "../../constants/enums";
|
|
||||||
import { postCustomComponent } from "../../controllers/API";
|
import { postCustomComponent } from "../../controllers/API";
|
||||||
import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
|
import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
|
||||||
import useAlertStore from "../../stores/alertStore";
|
import useAlertStore from "../../stores/alertStore";
|
||||||
import { useDarkStore } from "../../stores/darkStore";
|
|
||||||
import useFlowStore from "../../stores/flowStore";
|
import useFlowStore from "../../stores/flowStore";
|
||||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||||
import { useShortcutsStore } from "../../stores/shortcuts";
|
import { useShortcutsStore } from "../../stores/shortcuts";
|
||||||
import { useTypesStore } from "../../stores/typesStore";
|
import { useTypesStore } from "../../stores/typesStore";
|
||||||
import { OutputFieldType, VertexBuildTypeAPI } from "../../types/api";
|
import { OutputFieldType } from "../../types/api";
|
||||||
import { NodeDataType } from "../../types/flow";
|
import { NodeDataType } from "../../types/flow";
|
||||||
import { handleKeyDown, scapedJSONStringfy } from "../../utils/reactflowUtils";
|
import { scapedJSONStringfy } from "../../utils/reactflowUtils";
|
||||||
import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils";
|
import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils";
|
||||||
import { classNames, cn } from "../../utils/utils";
|
import { classNames, cn } from "../../utils/utils";
|
||||||
import { countHandlesFn } from "../helpers/count-handles";
|
import { countHandlesFn } from "../helpers/count-handles";
|
||||||
import { getSpecificClassFromBuildStatus } from "../helpers/get-class-from-build-status";
|
|
||||||
import { getNodeInputColors } from "../helpers/get-node-input-colors";
|
import { getNodeInputColors } from "../helpers/get-node-input-colors";
|
||||||
import { getNodeOutputColors } from "../helpers/get-node-output-colors";
|
import { getNodeOutputColors } from "../helpers/get-node-output-colors";
|
||||||
import useCheckCodeValidity from "../hooks/use-check-code-validity";
|
import useCheckCodeValidity from "../hooks/use-check-code-validity";
|
||||||
import useIconNodeRender from "../hooks/use-icon-render";
|
import useIconNodeRender from "../hooks/use-icon-render";
|
||||||
import useIconStatus from "../hooks/use-icons-status";
|
|
||||||
import useUpdateNodeCode from "../hooks/use-update-node-code";
|
import useUpdateNodeCode from "../hooks/use-update-node-code";
|
||||||
import useUpdateValidationStatus from "../hooks/use-update-validation-status";
|
|
||||||
import useValidationStatusString from "../hooks/use-validation-status-string";
|
|
||||||
import getFieldTitle from "../utils/get-field-title";
|
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 NodeInputField from "./components/NodeInputField";
|
import NodeInputField from "./components/NodeInputField";
|
||||||
|
import NodeName from "./components/NodeName";
|
||||||
import NodeOutputField from "./components/NodeOutputfield";
|
import NodeOutputField from "./components/NodeOutputfield";
|
||||||
|
import NodeStatus from "./components/NodeStatus";
|
||||||
|
|
||||||
export default function GenericNode({
|
export default function GenericNode({
|
||||||
data,
|
data,
|
||||||
|
|
||||||
selected,
|
selected,
|
||||||
}: {
|
}: {
|
||||||
data: NodeDataType;
|
data: NodeDataType;
|
||||||
|
|
@ -56,42 +43,18 @@ export default function GenericNode({
|
||||||
xPos?: number;
|
xPos?: number;
|
||||||
yPos?: number;
|
yPos?: number;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const preventDefault = true;
|
|
||||||
const types = useTypesStore((state) => state.types);
|
const types = useTypesStore((state) => state.types);
|
||||||
const templates = useTypesStore((state) => state.templates);
|
const templates = useTypesStore((state) => state.templates);
|
||||||
const deleteNode = useFlowStore((state) => state.deleteNode);
|
const deleteNode = useFlowStore((state) => state.deleteNode);
|
||||||
const flowPool = useFlowStore((state) => state.flowPool);
|
|
||||||
const buildFlow = useFlowStore((state) => state.buildFlow);
|
|
||||||
const setNode = useFlowStore((state) => state.setNode);
|
const setNode = useFlowStore((state) => state.setNode);
|
||||||
const updateNodeInternals = useUpdateNodeInternals();
|
const updateNodeInternals = useUpdateNodeInternals();
|
||||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
const isDark = useDarkStore((state) => state.dark);
|
|
||||||
const version = useDarkStore((state) => state.version);
|
|
||||||
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
|
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
|
||||||
|
|
||||||
const [inputName, setInputName] = useState(false);
|
|
||||||
const [nodeName, setNodeName] = useState(data.node!.display_name);
|
|
||||||
const [inputDescription, setInputDescription] = useState(false);
|
|
||||||
const [nodeDescription, setNodeDescription] = useState(
|
|
||||||
data.node?.description!,
|
|
||||||
);
|
|
||||||
const [isOutdated, setIsOutdated] = useState(false);
|
const [isOutdated, setIsOutdated] = useState(false);
|
||||||
const [isUserEdited, setIsUserEdited] = useState(false);
|
const [isUserEdited, setIsUserEdited] = useState(false);
|
||||||
const buildStatus = useFlowStore(
|
|
||||||
(state) => state.flowBuildStatus[data.id]?.status,
|
|
||||||
);
|
|
||||||
const lastRunTime = useFlowStore(
|
|
||||||
(state) => state.flowBuildStatus[data.id]?.timestamp,
|
|
||||||
);
|
|
||||||
const [validationStatus, setValidationStatus] =
|
|
||||||
useState<VertexBuildTypeAPI | null>(null);
|
|
||||||
const [handles, setHandles] = useState<number>(0);
|
const [handles, setHandles] = useState<number>(0);
|
||||||
const [validationString, setValidationString] = useState<string>("");
|
const [borderColor, setBorderColor] = useState<string>("");
|
||||||
|
|
||||||
const iconStatus = useIconStatus(buildStatus, validationStatus);
|
|
||||||
const [showNode, setShowNode] = useState(data.showNode ?? true);
|
const [showNode, setShowNode] = useState(data.showNode ?? true);
|
||||||
// State for outline color
|
|
||||||
const isBuilding = useFlowStore((state) => state.isBuilding);
|
|
||||||
|
|
||||||
const updateNodeCode = useUpdateNodeCode(
|
const updateNodeCode = useUpdateNodeCode(
|
||||||
data?.id,
|
data?.id,
|
||||||
|
|
@ -118,47 +81,6 @@ export default function GenericNode({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderIconStatus = () => {
|
|
||||||
return <div className="cursor-help">{iconStatus}</div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getNodeBorderClassName = (
|
|
||||||
selected: boolean,
|
|
||||||
showNode: boolean,
|
|
||||||
buildStatus: BuildStatus | undefined,
|
|
||||||
validationStatus: VertexBuildTypeAPI | null,
|
|
||||||
) => {
|
|
||||||
const specificClassFromBuildStatus = getSpecificClassFromBuildStatus(
|
|
||||||
buildStatus,
|
|
||||||
validationStatus,
|
|
||||||
isDark,
|
|
||||||
);
|
|
||||||
|
|
||||||
const baseBorderClass = getBaseBorderClass(selected);
|
|
||||||
const nodeSizeClass = getNodeSizeClass(showNode);
|
|
||||||
const names = classNames(
|
|
||||||
baseBorderClass,
|
|
||||||
nodeSizeClass,
|
|
||||||
"generic-node-div group/node",
|
|
||||||
specificClassFromBuildStatus,
|
|
||||||
);
|
|
||||||
return names;
|
|
||||||
};
|
|
||||||
|
|
||||||
// const [openWDoubleCLick, setOpenWDoubleCLick] = useState(false);
|
|
||||||
|
|
||||||
const getBaseBorderClass = (selected) => {
|
|
||||||
let className = selected
|
|
||||||
? "border border-ring hover:shadow-node"
|
|
||||||
: "border hover:shadow-node";
|
|
||||||
let frozenClass = selected ? "border-ring-frozen" : "border-frozen";
|
|
||||||
return data.node?.frozen ? frozenClass : className;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getNodeSizeClass = (showNode) =>
|
|
||||||
showNode ? "w-96 rounded-lg" : "w-26 h-26 rounded-full";
|
|
||||||
|
|
||||||
const nameEditable = true;
|
|
||||||
const isEmoji = emojiRegex().test(data?.node?.icon!);
|
const isEmoji = emojiRegex().test(data?.node?.icon!);
|
||||||
|
|
||||||
if (!data.node!.template) {
|
if (!data.node!.template) {
|
||||||
|
|
@ -174,8 +96,6 @@ export default function GenericNode({
|
||||||
}
|
}
|
||||||
|
|
||||||
useCheckCodeValidity(data, templates, setIsOutdated, setIsUserEdited, types);
|
useCheckCodeValidity(data, templates, setIsOutdated, setIsUserEdited, types);
|
||||||
useUpdateValidationStatus(data?.id, flowPool, setValidationStatus);
|
|
||||||
useValidationStatusString(validationStatus, setValidationString);
|
|
||||||
|
|
||||||
const iconNodeRender = useIconNodeRender(
|
const iconNodeRender = useIconNodeRender(
|
||||||
data,
|
data,
|
||||||
|
|
@ -197,46 +117,10 @@ export default function GenericNode({
|
||||||
countHandles();
|
countHandles();
|
||||||
}, [data, data.node]);
|
}, [data, data.node]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!selected) {
|
|
||||||
setInputName(false);
|
|
||||||
setInputDescription(false);
|
|
||||||
}
|
|
||||||
}, [selected]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setNodeDescription(data.node!.description);
|
|
||||||
}, [data.node!.description]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setNodeName(data.node!.display_name);
|
|
||||||
}, [data.node!.display_name]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setShowNode(data.showNode ?? true);
|
setShowNode(data.showNode ?? true);
|
||||||
}, [data.showNode]);
|
}, [data.showNode]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (buildStatus === BuildStatus.BUILT && !isBuilding) {
|
|
||||||
setNode(
|
|
||||||
data.id,
|
|
||||||
(old) => {
|
|
||||||
return {
|
|
||||||
...old,
|
|
||||||
data: {
|
|
||||||
...old.data,
|
|
||||||
node: {
|
|
||||||
...old.data.node,
|
|
||||||
lf_version: version,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [buildStatus, isBuilding]);
|
|
||||||
|
|
||||||
const [loadingUpdate, setLoadingUpdate] = useState(false);
|
const [loadingUpdate, setLoadingUpdate] = useState(false);
|
||||||
|
|
||||||
const [showHiddenOutputs, setShowHiddenOutputs] = useState(false);
|
const [showHiddenOutputs, setShowHiddenOutputs] = useState(false);
|
||||||
|
|
@ -286,17 +170,8 @@ export default function GenericNode({
|
||||||
const hiddenOutputs =
|
const hiddenOutputs =
|
||||||
data.node!.outputs?.filter((output) => output.hidden) ?? [];
|
data.node!.outputs?.filter((output) => output.hidden) ?? [];
|
||||||
|
|
||||||
function handlePlayWShortcut() {
|
|
||||||
if (buildStatus === BuildStatus.BUILDING || isBuilding || !selected) return;
|
|
||||||
setValidationStatus(null);
|
|
||||||
buildFlow({ stopNodeId: data.id });
|
|
||||||
}
|
|
||||||
|
|
||||||
const update = useShortcutsStore((state) => state.update);
|
const update = useShortcutsStore((state) => state.update);
|
||||||
const play = useShortcutsStore((state) => state.play);
|
useHotkeys(update, handleUpdateCodeWShortcut, { preventDefault: true });
|
||||||
|
|
||||||
useHotkeys(update, handleUpdateCodeWShortcut, { preventDefault });
|
|
||||||
useHotkeys(play, handlePlayWShortcut, { preventDefault });
|
|
||||||
|
|
||||||
const shortcuts = useShortcutsStore((state) => state.shortcuts);
|
const shortcuts = useShortcutsStore((state) => state.shortcuts);
|
||||||
|
|
||||||
|
|
@ -376,24 +251,55 @@ export default function GenericNode({
|
||||||
isUserEdited,
|
isUserEdited,
|
||||||
selected,
|
selected,
|
||||||
shortcuts,
|
shortcuts,
|
||||||
// openWDoubleCLick,
|
|
||||||
// setOpenWDoubleCLick,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const renderInputParameter = Object.keys(data.node!.template)
|
||||||
|
.filter((templateField) => templateField.charAt(0) !== "_")
|
||||||
|
.sort((a, b) => sortFields(a, b, data.node?.field_order ?? []))
|
||||||
|
.map(
|
||||||
|
(templateField: string, idx) =>
|
||||||
|
data.node!.template[templateField]?.show &&
|
||||||
|
!data.node!.template[templateField]?.advanced && (
|
||||||
|
<NodeInputField
|
||||||
|
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}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{memoizedNodeToolbarComponent}
|
{memoizedNodeToolbarComponent}
|
||||||
<div
|
<div className={borderColor}>
|
||||||
// onDoubleClick={(event) => {
|
|
||||||
// if (!isWrappedWithClass(event, "nodoubleclick"))
|
|
||||||
// setOpenWDoubleCLick(true);
|
|
||||||
// }}
|
|
||||||
className={getNodeBorderClassName(
|
|
||||||
selected,
|
|
||||||
showNode,
|
|
||||||
buildStatus,
|
|
||||||
validationStatus,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{data.node?.beta && showNode && (
|
{data.node?.beta && showNode && (
|
||||||
<div className="beta-badge-wrapper">
|
<div className="beta-badge-wrapper">
|
||||||
<div className="beta-badge-content">BETA</div>
|
<div className="beta-badge-content">BETA</div>
|
||||||
|
|
@ -419,68 +325,25 @@ export default function GenericNode({
|
||||||
{iconNodeRender()}
|
{iconNodeRender()}
|
||||||
{showNode && (
|
{showNode && (
|
||||||
<div className="generic-node-tooltip-div">
|
<div className="generic-node-tooltip-div">
|
||||||
{nameEditable && inputName ? (
|
<NodeName
|
||||||
<div>
|
display_name={data.node?.display_name}
|
||||||
<InputComponent
|
nodeId={data.id}
|
||||||
onBlur={() => {
|
selected={selected}
|
||||||
setInputName(false);
|
/>
|
||||||
if (nodeName.trim() !== "") {
|
{isOutdated && !isUserEdited && (
|
||||||
setNodeName(nodeName);
|
<ShadTooltip content={TOOLTIP_OUTDATED_NODE}>
|
||||||
setNode(data.id, (old) => ({
|
<Button
|
||||||
...old,
|
onClick={handleUpdateCode}
|
||||||
data: {
|
unstyled
|
||||||
...old.data,
|
className={"group p-1"}
|
||||||
node: {
|
loading={loadingUpdate}
|
||||||
...old.data.node,
|
>
|
||||||
display_name: nodeName,
|
<IconComponent
|
||||||
},
|
name="AlertTriangle"
|
||||||
},
|
className="h-5 w-5 fill-status-yellow text-muted"
|
||||||
}));
|
/>
|
||||||
} else {
|
</Button>
|
||||||
setNodeName(data.node!.display_name);
|
</ShadTooltip>
|
||||||
}
|
|
||||||
}}
|
|
||||||
value={nodeName}
|
|
||||||
onChange={setNodeName}
|
|
||||||
password={false}
|
|
||||||
blurOnEnter={true}
|
|
||||||
id={`input-title-${data.node?.display_name}`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="group flex items-center gap-1">
|
|
||||||
<ShadTooltip content={data.node?.display_name}>
|
|
||||||
<div
|
|
||||||
onDoubleClick={(event) => {
|
|
||||||
if (nameEditable) {
|
|
||||||
setInputName(true);
|
|
||||||
}
|
|
||||||
takeSnapshot();
|
|
||||||
event.stopPropagation();
|
|
||||||
event.preventDefault();
|
|
||||||
}}
|
|
||||||
data-testid={"title-" + data.node?.display_name}
|
|
||||||
className="nodoubleclick generic-node-tooltip-div cursor-text text-primary"
|
|
||||||
>
|
|
||||||
{data.node?.display_name}
|
|
||||||
</div>
|
|
||||||
</ShadTooltip>
|
|
||||||
{isOutdated && !isUserEdited && (
|
|
||||||
<ShadTooltip content={TOOLTIP_OUTDATED_NODE}>
|
|
||||||
<Button
|
|
||||||
onClick={handleUpdateCode}
|
|
||||||
unstyled
|
|
||||||
className={"group p-1"}
|
|
||||||
loading={loadingUpdate}
|
|
||||||
>
|
|
||||||
<IconComponent
|
|
||||||
name="AlertTriangle"
|
|
||||||
className="h-5 w-5 fill-status-yellow text-muted"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
</ShadTooltip>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -488,271 +351,34 @@ export default function GenericNode({
|
||||||
<div>
|
<div>
|
||||||
{!showNode && (
|
{!showNode && (
|
||||||
<>
|
<>
|
||||||
{Object.keys(data.node!.template)
|
{renderInputParameter}
|
||||||
.filter((templateField) => templateField.charAt(0) !== "_")
|
|
||||||
.map(
|
|
||||||
(templateField: string, idx) =>
|
|
||||||
data.node!.template[templateField]?.show &&
|
|
||||||
!data.node!.template[templateField]?.advanced && (
|
|
||||||
<NodeInputField
|
|
||||||
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}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
{shownOutputs &&
|
{shownOutputs &&
|
||||||
shownOutputs.length > 0 &&
|
shownOutputs.length > 0 &&
|
||||||
renderOutputParameter(shownOutputs[0], 0)}
|
renderOutputParameter(shownOutputs[0], 0)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{showNode && (
|
<NodeStatus
|
||||||
<>
|
frozen={data.node?.frozen}
|
||||||
<div className="flex flex-shrink-0 items-center gap-2">
|
showNode={showNode}
|
||||||
<ShadTooltip
|
display_name={data.node?.display_name!}
|
||||||
content={
|
nodeId={data.id}
|
||||||
buildStatus === BuildStatus.BUILDING ? (
|
selected={selected}
|
||||||
<span> {STATUS_BUILDING} </span>
|
setBorderColor={setBorderColor}
|
||||||
) : buildStatus === BuildStatus.INACTIVE ? (
|
/>
|
||||||
<span> {STATUS_INACTIVE} </span>
|
|
||||||
) : !validationStatus ? (
|
|
||||||
<span className="flex">{STATUS_BUILD}</span>
|
|
||||||
) : (
|
|
||||||
<div className="max-h-100 p-2">
|
|
||||||
<div className="max-h-80 overflow-auto">
|
|
||||||
{validationString && (
|
|
||||||
<div className="ml-1 pb-2 text-status-red">
|
|
||||||
{validationString}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{lastRunTime && (
|
|
||||||
<div className="justify-left flex font-normal text-muted-foreground">
|
|
||||||
<div>{RUN_TIMESTAMP_PREFIX}</div>
|
|
||||||
<div className="ml-1 text-status-blue">
|
|
||||||
{lastRunTime}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="justify-left flex font-normal text-muted-foreground">
|
|
||||||
<div>Duration:</div>
|
|
||||||
<div className="ml-1 text-status-blue">
|
|
||||||
{validationStatus?.data.duration}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
side="bottom"
|
|
||||||
>
|
|
||||||
{renderIconStatus()}
|
|
||||||
</ShadTooltip>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
if (buildStatus === BuildStatus.BUILDING || isBuilding)
|
|
||||||
return;
|
|
||||||
setValidationStatus(null);
|
|
||||||
buildFlow({ stopNodeId: data.id });
|
|
||||||
}}
|
|
||||||
unstyled
|
|
||||||
className="group p-1"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
data-testid={
|
|
||||||
`button_run_` + data?.node?.display_name.toLowerCase()
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<IconComponent
|
|
||||||
name="Play"
|
|
||||||
className={
|
|
||||||
"h-5 w-5 fill-current stroke-2 text-muted-foreground transition-all group-hover:text-medium-indigo group-hover/node:opacity-100"
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showNode && (
|
{showNode && (
|
||||||
<div
|
<div className="relative pb-8 pt-5">
|
||||||
className={cn(
|
|
||||||
showNode
|
|
||||||
? data.node?.description === "" && !nameEditable
|
|
||||||
? "pb-8"
|
|
||||||
: "pb-8 pt-5"
|
|
||||||
: "",
|
|
||||||
"relative",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{/* increase height!! */}
|
{/* increase height!! */}
|
||||||
<div className="generic-node-desc">
|
<NodeDescription
|
||||||
{showNode && nameEditable && inputDescription ? (
|
description={data.node?.description}
|
||||||
<Textarea
|
nodeId={data.id}
|
||||||
className="nowheel min-h-40"
|
selected={selected}
|
||||||
autoFocus
|
/>
|
||||||
onBlur={() => {
|
|
||||||
setInputDescription(false);
|
|
||||||
setInputName(false);
|
|
||||||
setNodeDescription(nodeDescription);
|
|
||||||
setNode(data.id, (old) => ({
|
|
||||||
...old,
|
|
||||||
data: {
|
|
||||||
...old.data,
|
|
||||||
node: {
|
|
||||||
...old.data.node,
|
|
||||||
description: nodeDescription,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
value={nodeDescription}
|
|
||||||
onChange={(e) => setNodeDescription(e.target.value)}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
handleKeyDown(e, nodeDescription, "");
|
|
||||||
if (
|
|
||||||
e.key === "Enter" &&
|
|
||||||
e.shiftKey === false &&
|
|
||||||
e.ctrlKey === false &&
|
|
||||||
e.altKey === false
|
|
||||||
) {
|
|
||||||
setInputDescription(false);
|
|
||||||
setNodeDescription(nodeDescription);
|
|
||||||
setNode(data.id, (old) => ({
|
|
||||||
...old,
|
|
||||||
data: {
|
|
||||||
...old.data,
|
|
||||||
node: {
|
|
||||||
...old.data.node,
|
|
||||||
description: nodeDescription,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"nodoubleclick generic-node-desc-text cursor-text word-break-break-word",
|
|
||||||
(data.node?.description === "" ||
|
|
||||||
!data.node?.description) &&
|
|
||||||
nameEditable
|
|
||||||
? "font-light italic"
|
|
||||||
: "",
|
|
||||||
)}
|
|
||||||
onDoubleClick={(e) => {
|
|
||||||
setInputDescription(true);
|
|
||||||
takeSnapshot();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{(data.node?.description === "" || !data.node?.description) &&
|
|
||||||
nameEditable ? (
|
|
||||||
"Double Click to Edit Description"
|
|
||||||
) : (
|
|
||||||
<Markdown className="markdown prose flex flex-col text-primary word-break-break-word dark:prose-invert">
|
|
||||||
{String(data.node?.description)}
|
|
||||||
</Markdown>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<>
|
<>
|
||||||
{Object.keys(data.node!.template)
|
{renderInputParameter}
|
||||||
.filter((templateField) => templateField.charAt(0) !== "_")
|
|
||||||
.sort((a, b) => sortFields(a, b, data.node?.field_order ?? []))
|
|
||||||
.map((templateField: string, idx) => (
|
|
||||||
<div key={idx}>
|
|
||||||
{data.node!.template[templateField]?.show &&
|
|
||||||
!data.node!.template[templateField]?.advanced ? (
|
|
||||||
<NodeInputField
|
|
||||||
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}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
Object.keys(data.node!.template).length < 1 ? "hidden" : "",
|
Object.keys(data.node!.template).length < 1 ? "hidden" : "",
|
||||||
|
|
|
||||||
|
|
@ -636,7 +636,7 @@ export function handleKeyDown(
|
||||||
e:
|
e:
|
||||||
| React.KeyboardEvent<HTMLInputElement>
|
| React.KeyboardEvent<HTMLInputElement>
|
||||||
| React.KeyboardEvent<HTMLTextAreaElement>,
|
| React.KeyboardEvent<HTMLTextAreaElement>,
|
||||||
inputValue: string | string[] | null,
|
inputValue: string | string[] | null | undefined,
|
||||||
block: string,
|
block: string,
|
||||||
) {
|
) {
|
||||||
//condition to fix bug control+backspace on Windows/Linux
|
//condition to fix bug control+backspace on Windows/Linux
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue