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