fix(GenericNode): import missing InputComponent and Textarea components
feat(GenericNode): add support for editing node name and description fix(InputComponent): add autoFocus and onBlur props fix(tabsContext): update sourceHandle object in edge data fix(PageComponent): generate random name for new group node fix(reactflowUtils): update handleKeyDown function to accept HTMLTextAreaElement
This commit is contained in:
parent
7837de5343
commit
f84df04f8a
6 changed files with 93 additions and 22 deletions
|
|
@ -10,10 +10,12 @@ import { typesContext } from "../../contexts/typesContext";
|
|||
import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
|
||||
import { validationStatusType } from "../../types/components";
|
||||
import { NodeDataType } from "../../types/flow";
|
||||
import { cleanEdges, getGroupStatus, scapedJSONStringfy } from "../../utils/reactflowUtils";
|
||||
import { cleanEdges, getGroupStatus, handleKeyDown, scapedJSONStringfy } from "../../utils/reactflowUtils";
|
||||
import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils";
|
||||
import { classNames, toTitleCase } from "../../utils/utils";
|
||||
import ParameterComponent from "./components/parameterComponent";
|
||||
import InputComponent from "../../components/inputComponent";
|
||||
import { Textarea } from "../../components/ui/textarea";
|
||||
|
||||
export default function GenericNode({
|
||||
data: olddata,
|
||||
|
|
@ -31,6 +33,12 @@ export default function GenericNode({
|
|||
const updateNodeInternals = useUpdateNodeInternals();
|
||||
const { types, deleteNode, reactFlowInstance } = useContext(typesContext);
|
||||
const name = nodeIconsLucide[data.type] ? data.type : types[data.type];
|
||||
const [inputName, setInputName] = useState(true);
|
||||
const [nodeName, setNodeName] = useState(data.node!.display_name);
|
||||
const [inputDescription, setInputDescription] = useState(false);
|
||||
const [nodeDescription, setNodeDescription] = useState(
|
||||
data.node?.description!
|
||||
);
|
||||
const [validationStatus, setValidationStatus] =
|
||||
useState<validationStatusType | null>(null);
|
||||
// State for outline color
|
||||
|
|
@ -58,7 +66,7 @@ export default function GenericNode({
|
|||
|
||||
// New useEffect to watch for changes in sseData and update validation status
|
||||
useEffect(() => {
|
||||
const relevantData = data.node?.flow?getGroupStatus(data.node.flow,sseData):sseData[data.id];
|
||||
const relevantData = data.node?.flow ? getGroupStatus(data.node.flow, sseData) : sseData[data.id];
|
||||
if (relevantData) {
|
||||
// Extract validation information from relevantData and update the validationStatus state
|
||||
setValidationStatus(relevantData);
|
||||
|
|
@ -97,11 +105,31 @@ export default function GenericNode({
|
|||
iconColor={`${nodeColors[types[data.type]]}`}
|
||||
/>
|
||||
<div className="generic-node-tooltip-div">
|
||||
<ShadTooltip content={data.node?.display_name}>
|
||||
<div className="generic-node-tooltip-div text-primary">
|
||||
{data.node?.display_name}
|
||||
</div>
|
||||
</ShadTooltip>
|
||||
{data.node?.flow && inputName ?
|
||||
<div >
|
||||
<InputComponent
|
||||
autoFocus
|
||||
onBlur={() => {
|
||||
setInputName(false);
|
||||
if (nodeName.trim() !== "") {
|
||||
setNodeName(nodeName);
|
||||
data.node!.display_name = nodeName;
|
||||
} else {
|
||||
setNodeName(data.node!.display_name);
|
||||
}
|
||||
}}
|
||||
value={nodeName}
|
||||
onChange={setNodeName}
|
||||
password={false}
|
||||
blurOnEnter={true}
|
||||
/>
|
||||
</div> : <ShadTooltip content={data.node?.display_name}>
|
||||
<div className="generic-node-tooltip-div text-primary" onDoubleClick={() => setInputName(true)}>
|
||||
{nodeName}
|
||||
</div>
|
||||
</ShadTooltip>
|
||||
}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className="round-button-div">
|
||||
|
|
@ -123,10 +151,10 @@ export default function GenericNode({
|
|||
<div className="max-h-96 overflow-auto">
|
||||
{typeof validationStatus.params === "string"
|
||||
? validationStatus.params
|
||||
.split("\n")
|
||||
.map((line: string, index: number) => (
|
||||
<div key={index}>{line}</div>
|
||||
))
|
||||
.split("\n")
|
||||
.map((line: string, index: number) => (
|
||||
<div key={index}>{line}</div>
|
||||
))
|
||||
: ""}
|
||||
</div>
|
||||
)
|
||||
|
|
@ -164,7 +192,35 @@ export default function GenericNode({
|
|||
</div>
|
||||
|
||||
<div className="generic-node-desc">
|
||||
<div className="generic-node-desc-text">{data.node?.description}</div>
|
||||
{data.node?.flow && inputDescription ?
|
||||
<Textarea
|
||||
autoFocus
|
||||
onBlur={() => {
|
||||
setInputDescription(false);
|
||||
if (nodeDescription.trim() !== "") {
|
||||
setNodeDescription(nodeDescription);
|
||||
data.node!.description = nodeDescription;
|
||||
} else {
|
||||
setNodeDescription(data.node!.description);
|
||||
}
|
||||
}}
|
||||
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);
|
||||
if (nodeDescription.trim() !== "") {
|
||||
setNodeDescription(nodeDescription);
|
||||
data.node!.description = nodeDescription;
|
||||
} else {
|
||||
setNodeDescription(data.node!.description);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/> :
|
||||
<div className="generic-node-desc-text" onDoubleClick={() => setInputDescription(true)}>{nodeDescription}</div>
|
||||
}
|
||||
|
||||
<>
|
||||
{Object.keys(data.node!.template)
|
||||
|
|
@ -172,7 +228,7 @@ export default function GenericNode({
|
|||
.map((templateField: string, idx) => (
|
||||
<div key={idx}>
|
||||
{data.node!.template[templateField].show &&
|
||||
!data.node!.template[templateField].advanced ? (
|
||||
!data.node!.template[templateField].advanced ? (
|
||||
<ParameterComponent
|
||||
key={scapedJSONStringfy({
|
||||
inputTypes:
|
||||
|
|
@ -186,7 +242,7 @@ export default function GenericNode({
|
|||
setData={setData}
|
||||
color={
|
||||
nodeColors[
|
||||
types[data.node?.template[templateField].type!]
|
||||
types[data.node?.template[templateField].type!]
|
||||
] ??
|
||||
nodeColors[data.node?.template[templateField].type!] ??
|
||||
nodeColors.unknown
|
||||
|
|
@ -195,8 +251,8 @@ export default function GenericNode({
|
|||
data.node?.template[templateField].display_name
|
||||
? data.node.template[templateField].display_name
|
||||
: data.node?.template[templateField].name
|
||||
? toTitleCase(data.node.template[templateField].name)
|
||||
: toTitleCase(templateField)
|
||||
? toTitleCase(data.node.template[templateField].name)
|
||||
: toTitleCase(templateField)
|
||||
}
|
||||
info={data.node?.template[templateField].info}
|
||||
name={templateField}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import * as Form from "@radix-ui/react-form";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { InputComponentType } from "../../types/components";
|
||||
import { handleKeyDown } from "../../utils/reactflowUtils";
|
||||
import { classNames } from "../../utils/utils";
|
||||
import { Input } from "../ui/input";
|
||||
|
||||
export default function InputComponent({
|
||||
autoFocus =false,
|
||||
onBlur,
|
||||
value,
|
||||
onChange,
|
||||
disabled,
|
||||
|
|
@ -15,9 +17,10 @@ export default function InputComponent({
|
|||
editNode = false,
|
||||
placeholder = "Type something...",
|
||||
className,
|
||||
blurOnEnter=false
|
||||
}: InputComponentType): JSX.Element {
|
||||
const [pwdVisible, setPwdVisible] = useState(false);
|
||||
|
||||
const refInput = useRef<HTMLInputElement>(null);
|
||||
// Clear component state
|
||||
useEffect(() => {
|
||||
if (disabled) {
|
||||
|
|
@ -30,6 +33,9 @@ export default function InputComponent({
|
|||
{isForm ? (
|
||||
<Form.Control asChild>
|
||||
<Input
|
||||
ref={refInput}
|
||||
onBlur={onBlur}
|
||||
autoFocus={autoFocus}
|
||||
type={password && !pwdVisible ? "password" : "text"}
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
|
|
@ -49,13 +55,17 @@ export default function InputComponent({
|
|||
}}
|
||||
onKeyDown={(e) => {
|
||||
handleKeyDown(e, value, "");
|
||||
if(blurOnEnter && e.key === "Enter") refInput.current?.blur();
|
||||
}}
|
||||
/>
|
||||
</Form.Control>
|
||||
) : (
|
||||
<Input
|
||||
ref={refInput}
|
||||
type="text"
|
||||
onBlur={onBlur}
|
||||
value={value}
|
||||
autoFocus={autoFocus}
|
||||
disabled={disabled}
|
||||
required={required}
|
||||
className={classNames(
|
||||
|
|
@ -73,6 +83,7 @@ export default function InputComponent({
|
|||
}}
|
||||
onKeyDown={(e) => {
|
||||
handleKeyDown(e, value, "");
|
||||
if(blurOnEnter && e.key === "Enter") refInput.current?.blur();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -230,6 +230,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
edge.sourceHandle!
|
||||
);
|
||||
sourceHandleObject.baseClasses = template["base_classes"];
|
||||
edge.data.sourceHandle = sourceHandleObject;
|
||||
edge.sourceHandle = scapedJSONStringfy(sourceHandleObject);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ import {
|
|||
scapeJSONParse,
|
||||
validateSelection,
|
||||
} from "../../../../utils/reactflowUtils";
|
||||
import { isWrappedWithClass } from "../../../../utils/utils";
|
||||
import { getRandomName, isWrappedWithClass } from "../../../../utils/utils";
|
||||
import ConnectionLineComponent from "../ConnectionLineComponent";
|
||||
import ExtraSidebar from "../extraSidebarComponent";
|
||||
import SelectionMenu from "../SelectionMenuComponent";
|
||||
|
|
@ -447,7 +447,7 @@ export default function Page({
|
|||
<SelectionMenu isVisible={selectionMenuVisible} nodes={lastSelection?.nodes}
|
||||
onClick={()=>{
|
||||
if(validateSelection(lastSelection!).length===0){
|
||||
const {newFlow} = generateFlow(lastSelection!,reactFlowInstance!,"new Component");
|
||||
const {newFlow} = generateFlow(lastSelection!,reactFlowInstance!,getRandomName());
|
||||
const newGroupNode = generateNodeFromFlow(newFlow)
|
||||
setNodes((oldNodes)=>[...oldNodes.filter((oldNodes)=>!lastSelection?.nodes.some((selectionNode)=>selectionNode.id===oldNodes.id)),newGroupNode])
|
||||
setEdges((oldEdges) =>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import { FlowStyleType, FlowType, NodeDataType, NodeType } from "../flow/index";
|
|||
import { typesContextType } from "../typesContext";
|
||||
import { sourceHandleType, targetHandleType } from "./../flow/index";
|
||||
export type InputComponentType = {
|
||||
autoFocus?: boolean;
|
||||
onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void;
|
||||
value: string;
|
||||
disabled?: boolean;
|
||||
onChange: (value: string) => void;
|
||||
|
|
@ -17,6 +19,7 @@ export type InputComponentType = {
|
|||
showPass?: boolean;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
blurOnEnter?: boolean;
|
||||
};
|
||||
export type ToggleComponentType = {
|
||||
enabled: boolean;
|
||||
|
|
|
|||
|
|
@ -315,7 +315,7 @@ export function updateEdgesHandleIds({
|
|||
}
|
||||
|
||||
export function handleKeyDown(
|
||||
e: React.KeyboardEvent<HTMLInputElement>,
|
||||
e: React.KeyboardEvent<HTMLInputElement |HTMLTextAreaElement>,
|
||||
inputValue: string | string[] | null,
|
||||
block: string
|
||||
) {
|
||||
|
|
@ -648,7 +648,7 @@ export function generateNodeFromFlow(flow: FlowType): NodeType {
|
|||
display_name: "group Node",
|
||||
documentation: "",
|
||||
base_classes: outputNode!.data.node!.base_classes,
|
||||
description: "group Node",
|
||||
description: "double click to edit description",
|
||||
template: generateNodeTemplate(data),
|
||||
flow: data,
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue