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:
anovazzi1 2023-09-14 19:01:09 -03:00
commit f84df04f8a
6 changed files with 93 additions and 22 deletions

View file

@ -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}

View file

@ -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();
}}
/>
)}

View file

@ -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);
}
});

View file

@ -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) =>

View file

@ -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;

View file

@ -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,
},