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 { useEffect, useMemo, useState } from "react";
|
||||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
import Markdown from "react-markdown";
|
||||
import { NodeToolbar, useUpdateNodeInternals } from "reactflow";
|
||||
import IconComponent, {
|
||||
ForwardedIconComponent,
|
||||
} from "../../components/genericIconComponent";
|
||||
import InputComponent from "../../components/inputComponent";
|
||||
import ShadTooltip from "../../components/shadTooltipComponent";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { Textarea } from "../../components/ui/textarea";
|
||||
import {
|
||||
RUN_TIMESTAMP_PREFIX,
|
||||
STATUS_BUILD,
|
||||
STATUS_BUILDING,
|
||||
STATUS_INACTIVE,
|
||||
TOOLTIP_OUTDATED_NODE,
|
||||
} from "../../constants/constants";
|
||||
import { BuildStatus } from "../../constants/enums";
|
||||
import { TOOLTIP_OUTDATED_NODE } from "../../constants/constants";
|
||||
import { postCustomComponent } from "../../controllers/API";
|
||||
import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
|
||||
import useAlertStore from "../../stores/alertStore";
|
||||
import { useDarkStore } from "../../stores/darkStore";
|
||||
import useFlowStore from "../../stores/flowStore";
|
||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||
import { useShortcutsStore } from "../../stores/shortcuts";
|
||||
import { useTypesStore } from "../../stores/typesStore";
|
||||
import { OutputFieldType, VertexBuildTypeAPI } from "../../types/api";
|
||||
import { OutputFieldType } from "../../types/api";
|
||||
import { NodeDataType } from "../../types/flow";
|
||||
import { handleKeyDown, scapedJSONStringfy } from "../../utils/reactflowUtils";
|
||||
import { scapedJSONStringfy } from "../../utils/reactflowUtils";
|
||||
import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils";
|
||||
import { classNames, cn } from "../../utils/utils";
|
||||
import { countHandlesFn } from "../helpers/count-handles";
|
||||
import { getSpecificClassFromBuildStatus } from "../helpers/get-class-from-build-status";
|
||||
import { getNodeInputColors } from "../helpers/get-node-input-colors";
|
||||
import { getNodeOutputColors } from "../helpers/get-node-output-colors";
|
||||
import useCheckCodeValidity from "../hooks/use-check-code-validity";
|
||||
import useIconNodeRender from "../hooks/use-icon-render";
|
||||
import useIconStatus from "../hooks/use-icons-status";
|
||||
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 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 NodeStatus from "./components/NodeStatus";
|
||||
|
||||
export default function GenericNode({
|
||||
data,
|
||||
|
||||
selected,
|
||||
}: {
|
||||
data: NodeDataType;
|
||||
|
|
@ -56,42 +43,18 @@ export default function GenericNode({
|
|||
xPos?: number;
|
||||
yPos?: number;
|
||||
}): JSX.Element {
|
||||
const preventDefault = true;
|
||||
const types = useTypesStore((state) => state.types);
|
||||
const templates = useTypesStore((state) => state.templates);
|
||||
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 updateNodeInternals = useUpdateNodeInternals();
|
||||
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 [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 [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 [validationString, setValidationString] = useState<string>("");
|
||||
|
||||
const iconStatus = useIconStatus(buildStatus, validationStatus);
|
||||
const [borderColor, setBorderColor] = useState<string>("");
|
||||
const [showNode, setShowNode] = useState(data.showNode ?? true);
|
||||
// State for outline color
|
||||
const isBuilding = useFlowStore((state) => state.isBuilding);
|
||||
|
||||
const updateNodeCode = useUpdateNodeCode(
|
||||
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!);
|
||||
|
||||
if (!data.node!.template) {
|
||||
|
|
@ -174,8 +96,6 @@ export default function GenericNode({
|
|||
}
|
||||
|
||||
useCheckCodeValidity(data, templates, setIsOutdated, setIsUserEdited, types);
|
||||
useUpdateValidationStatus(data?.id, flowPool, setValidationStatus);
|
||||
useValidationStatusString(validationStatus, setValidationString);
|
||||
|
||||
const iconNodeRender = useIconNodeRender(
|
||||
data,
|
||||
|
|
@ -197,46 +117,10 @@ export default function GenericNode({
|
|||
countHandles();
|
||||
}, [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(() => {
|
||||
setShowNode(data.showNode ?? true);
|
||||
}, [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 [showHiddenOutputs, setShowHiddenOutputs] = useState(false);
|
||||
|
|
@ -286,17 +170,8 @@ export default function GenericNode({
|
|||
const hiddenOutputs =
|
||||
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 play = useShortcutsStore((state) => state.play);
|
||||
|
||||
useHotkeys(update, handleUpdateCodeWShortcut, { preventDefault });
|
||||
useHotkeys(play, handlePlayWShortcut, { preventDefault });
|
||||
useHotkeys(update, handleUpdateCodeWShortcut, { preventDefault: true });
|
||||
|
||||
const shortcuts = useShortcutsStore((state) => state.shortcuts);
|
||||
|
||||
|
|
@ -376,24 +251,55 @@ export default function GenericNode({
|
|||
isUserEdited,
|
||||
selected,
|
||||
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 (
|
||||
<>
|
||||
{memoizedNodeToolbarComponent}
|
||||
<div
|
||||
// onDoubleClick={(event) => {
|
||||
// if (!isWrappedWithClass(event, "nodoubleclick"))
|
||||
// setOpenWDoubleCLick(true);
|
||||
// }}
|
||||
className={getNodeBorderClassName(
|
||||
selected,
|
||||
showNode,
|
||||
buildStatus,
|
||||
validationStatus,
|
||||
)}
|
||||
>
|
||||
<div className={borderColor}>
|
||||
{data.node?.beta && showNode && (
|
||||
<div className="beta-badge-wrapper">
|
||||
<div className="beta-badge-content">BETA</div>
|
||||
|
|
@ -419,68 +325,25 @@ export default function GenericNode({
|
|||
{iconNodeRender()}
|
||||
{showNode && (
|
||||
<div className="generic-node-tooltip-div">
|
||||
{nameEditable && inputName ? (
|
||||
<div>
|
||||
<InputComponent
|
||||
onBlur={() => {
|
||||
setInputName(false);
|
||||
if (nodeName.trim() !== "") {
|
||||
setNodeName(nodeName);
|
||||
setNode(data.id, (old) => ({
|
||||
...old,
|
||||
data: {
|
||||
...old.data,
|
||||
node: {
|
||||
...old.data.node,
|
||||
display_name: nodeName,
|
||||
},
|
||||
},
|
||||
}));
|
||||
} else {
|
||||
setNodeName(data.node!.display_name);
|
||||
}
|
||||
}}
|
||||
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>
|
||||
<NodeName
|
||||
display_name={data.node?.display_name}
|
||||
nodeId={data.id}
|
||||
selected={selected}
|
||||
/>
|
||||
{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>
|
||||
)}
|
||||
|
|
@ -488,271 +351,34 @@ export default function GenericNode({
|
|||
<div>
|
||||
{!showNode && (
|
||||
<>
|
||||
{Object.keys(data.node!.template)
|
||||
.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}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
{renderInputParameter}
|
||||
{shownOutputs &&
|
||||
shownOutputs.length > 0 &&
|
||||
renderOutputParameter(shownOutputs[0], 0)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{showNode && (
|
||||
<>
|
||||
<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"
|
||||
>
|
||||
{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>
|
||||
</>
|
||||
)}
|
||||
<NodeStatus
|
||||
frozen={data.node?.frozen}
|
||||
showNode={showNode}
|
||||
display_name={data.node?.display_name!}
|
||||
nodeId={data.id}
|
||||
selected={selected}
|
||||
setBorderColor={setBorderColor}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showNode && (
|
||||
<div
|
||||
className={cn(
|
||||
showNode
|
||||
? data.node?.description === "" && !nameEditable
|
||||
? "pb-8"
|
||||
: "pb-8 pt-5"
|
||||
: "",
|
||||
"relative",
|
||||
)}
|
||||
>
|
||||
<div className="relative pb-8 pt-5">
|
||||
{/* increase height!! */}
|
||||
<div className="generic-node-desc">
|
||||
{showNode && nameEditable && inputDescription ? (
|
||||
<Textarea
|
||||
className="nowheel min-h-40"
|
||||
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>
|
||||
<NodeDescription
|
||||
description={data.node?.description}
|
||||
nodeId={data.id}
|
||||
selected={selected}
|
||||
/>
|
||||
<>
|
||||
{Object.keys(data.node!.template)
|
||||
.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>
|
||||
))}
|
||||
{renderInputParameter}
|
||||
<div
|
||||
className={classNames(
|
||||
Object.keys(data.node!.template).length < 1 ? "hidden" : "",
|
||||
|
|
|
|||
|
|
@ -636,7 +636,7 @@ export function handleKeyDown(
|
|||
e:
|
||||
| React.KeyboardEvent<HTMLInputElement>
|
||||
| React.KeyboardEvent<HTMLTextAreaElement>,
|
||||
inputValue: string | string[] | null,
|
||||
inputValue: string | string[] | null | undefined,
|
||||
block: string,
|
||||
) {
|
||||
//condition to fix bug control+backspace on Windows/Linux
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue