Skip to content

Commit

Permalink
feat: Update node name and description UX editing (langflow-ai#5920)
Browse files Browse the repository at this point in the history
* ✨ (NodeName/index.tsx): add cursor-grab class to improve user experience when dragging the node
✨ (NodeName/index.tsx): add nodrag class to prevent text selection when dragging the node
✨ (NodeStatus/index.tsx): add nodrag class to prevent text selection when dragging the node
✨ (GenericNode/index.tsx): add nopan, nodelete, nodrag, noflow classes to improve node dragging behavior

* 📝 (NodeDescription/index.tsx): Update cursor style to 'auto' for better user experience
📝 (NodeName/index.tsx): Update cursor style to 'auto' for better user experience
📝 (NodeOutputfield/index.tsx): Add cursor style 'pointer' to improve interactivity

* ✨ (NodeDescription/index.tsx): Add support for editing node description when selected and editNameDescription is true
✨ (NodeName/index.tsx): Add support for editing node name when selected and editNameDescription is true
🔧 (GenericNode/index.tsx): Introduce useAlternate hook to handle toggling editNameDescription state
📝 (use-alternate.tsx): Add custom hook useAlternate to handle toggling boolean state
🔧 (style/index.css): Add new CSS variable --zinc-foreground for styling purposes
🔧 (tailwind.config.mjs): Add new tailwind color variable "zinc-foreground" for styling purposes

* 📝 (NodeDescription/index.tsx): Remove unnecessary setInputDescription call and update useEffect dependencies for better performance
📝 (NodeDescription/index.tsx): Update className for Textarea component to improve styling and readability
📝 (NodeDescription/index.tsx): Update className for generic-node-desc-text to improve styling and cursor behavior
📝 (NodeName/index.tsx): Remove unnecessary setInputName call and update useEffect dependencies for better performance
📝 (NodeName/index.tsx): Update className for span element to improve cursor behavior and styling
📝 (GenericNode/index.tsx): Add useRef for node element and implement useChangeOnUnfocus hook for better handling of focus events
📝 (GenericNode/index.tsx): Update className for pencil icon based on editNameDescription state for better visual feedback
📝 (GenericNode/index.tsx): Add editNameDescription to dependencies of useCallback to prevent unnecessary re-renders
📝 (GenericNode/index.tsx): Add editNameDescription to dependencies of useEffect to handle changes in editNameDescription state
📝 (use-change-on-unfocus.tsx): Implement custom hook useChangeOnUnfocus for handling focus events and state changes

* Refactor NodeDescription to remove old logic and variables

* Refactor NodeName component to remove unnecessary logic and variables

* [autofix.ci] apply automated fixes

* ✨ (NodeDescription/index.tsx): Add functionality to edit node description and handle events like blur, key down, and double click for sticky notes
📝 (NoteNode/index.tsx): Introduce useAlternate hook to toggle edit mode for node description in NoteNode component

* ♻️ (NoteNode/index.tsx): refactor useAlternate hook usage to simplify code and improve readability

* 🔧 (GenericNode/index.tsx): refactor className to conditionally apply translate-x styles based on showNode state for improved UI responsiveness

* 📝 (NodeDescription/index.tsx): Refactor handleBlurFn and handleKeyDownFn to improve code readability and maintainability
📝 (NodeName/index.tsx): Refactor handleBlur and handleKeyDown functions for better code organization and readability
📝 (GenericNode/index.tsx): Update toggleEditNameDescription prop to setEditNameDescription for consistency and clarity
📝 (use-change-on-unfocus.tsx): Remove unnecessary handleEscape function and handleBlur event listener for better code simplicity and performance

* 📝 (NodeDescription/index.tsx): Update CSS class name to use 'focus-border-primary' instead of 'focus-border-black' for consistency and clarity
📝 (GenericNode/index.tsx): Add data-testid attribute to save and edit name description buttons for testing purposes
📝 (edit-name-description-node.spec.ts): Add test to verify user can edit name and description of a node in the UI

* ✨ (GenericNode/index.tsx): Add functionality to show and hide toolbar with animation based on node selection status
📝 (get-class-toolbar-transform.ts): Create helper function to determine transform classes for toolbar animation based on showToolbar and showNode status

* ✨ (NodeDescription/index.tsx): add setHasChangedNodeDescription prop to update parent component when node description changes
✨ (NodeName/index.tsx): add setHasChangedNodeDescription prop to update parent component when node name changes
✨ (GenericNode/index.tsx): add hasChangedNodeDescription state and setHasChangedNodeDescription function to track changes in node description and update parent component
📝 (edit-name-description-node.spec.ts): add wait for sidebar custom component button and timeout to improve test reliability

* ✨ (group.spec.ts): Update click event on "title-Group" element to improve user interaction
🐛 (group.spec.ts): Fix click event on "save-name-description-button" element to properly save changes
🐛 (general-bugs-save-changes-on-node.spec.ts): Increase timeout for selectors to prevent test failures due to slow loading
🐛 (general-bugs-save-changes-on-node.spec.ts): Fix random value generation to ensure consistent length
🐛 (general-bugs-save-changes-on-node.spec.ts): Fix click event on "add-component-button-text-output" element to add component correctly
🐛 (general-bugs-save-changes-on-node.spec.ts): Fix timeout for selector to prevent test failures due to slow loading
🐛 (general-bugs-save-changes-on-node.spec.ts): Fix verifyTextareaValue function to properly verify textarea values

---------

Co-authored-by: anovazzi1 <[email protected]>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Feb 5, 2025
1 parent 6b17240 commit 8531e1b
Show file tree
Hide file tree
Showing 14 changed files with 465 additions and 127 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export default function NodeDescription({
inputClassName,
mdClassName,
style,
editNameDescription,
setEditNameDescription,
stickyNote,
setHasChangedNodeDescription,
}: {
description?: string;
selected?: boolean;
Expand All @@ -26,8 +30,11 @@ export default function NodeDescription({
inputClassName?: string;
mdClassName?: string;
style?: React.CSSProperties;
editNameDescription: boolean;
setEditNameDescription?: (value: boolean) => void;
stickyNote?: boolean;
setHasChangedNodeDescription?: (value: boolean) => void;
}) {
const [inputDescription, setInputDescription] = useState(false);
const [nodeDescription, setNodeDescription] = useState<string>(
description ?? "",
);
Expand All @@ -36,6 +43,12 @@ export default function NodeDescription({
const overflowRef = useRef<HTMLDivElement>(null);
const [hasScroll, sethasScroll] = useState(false);

useEffect(() => {
if (selected && editNameDescription) {
takeSnapshot();
}
}, [editNameDescription]);

useEffect(() => {
//timeout to wait for the dom to update
setTimeout(() => {
Expand All @@ -49,13 +62,7 @@ export default function NodeDescription({
}
}
}, 200);
}, [inputDescription]);

useEffect(() => {
if (!selected) {
setInputDescription(false);
}
}, [selected]);
}, [editNameDescription]);

useEffect(() => {
setNodeDescription(description ?? "");
Expand All @@ -80,55 +87,81 @@ export default function NodeDescription({
[description, emptyPlaceholder, mdClassName],
);

const handleBlurFn = () => {
setNodeDescription(nodeDescription);
setNode(nodeId, (old) => ({
...old,
data: {
...old.data,
node: {
...old.data.node,
description: nodeDescription,
},
},
}));
if (stickyNote) {
setEditNameDescription?.(false);
}
};

const handleKeyDownFn = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
handleKeyDown(e, nodeDescription, "");

if (e.key === "Escape") {
setEditNameDescription?.(false);
setNodeDescription(description ?? "");

if (stickyNote) {
setNodeDescription(nodeDescription);
setNode(nodeId, (old) => ({
...old,
data: {
...old.data,
node: {
...old.data.node,
description: nodeDescription,
},
},
}));
}
}
};

const handleDoubleClickFn = () => {
if (stickyNote) {
setEditNameDescription?.(true);
takeSnapshot();
}
};

const onChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setHasChangedNodeDescription?.(true);
setNodeDescription(e.target.value);
};

return (
<div
className={cn(
!inputDescription ? "overflow-auto" : "",
!editNameDescription ? "overflow-auto" : "",
hasScroll ? "nowheel" : "",
charLimit ? "px-2 pb-4" : "",
"w-full",
)}
>
{inputDescription ? (
{editNameDescription ? (
<>
<Textarea
maxLength={charLimit}
className={cn("nowheel h-full", inputClassName)}
className={cn(
"nowheel h-full w-full focus:border-primary focus:ring-0",
inputClassName,
)}
autoFocus
style={style}
onBlur={() => {
setInputDescription(false);
setNodeDescription(nodeDescription);
setNode(nodeId, (old) => ({
...old,
data: {
...old.data,
node: {
...old.data.node,
description: nodeDescription,
},
},
}));
}}
onBlur={handleBlurFn}
value={nodeDescription}
onChange={(e) => setNodeDescription(e.target.value)}
onKeyDown={(e) => {
handleKeyDown(e, nodeDescription, "");
if (e.key === "Escape") {
setInputDescription(false);
setNodeDescription(nodeDescription);
setNode(nodeId, (old) => ({
...old,
data: {
...old.data,
node: {
...old.data.node,
description: nodeDescription,
},
},
}));
}
}}
onChange={onChange}
onKeyDown={handleKeyDownFn}
/>
{charLimit && (nodeDescription?.length ?? 0) >= charLimit - 100 && (
<div
Expand All @@ -150,14 +183,11 @@ export default function NodeDescription({
data-testid="generic-node-desc"
ref={overflowRef}
className={cn(
"nodoubleclick generic-node-desc-text h-full cursor-text text-[13px] text-muted-foreground word-break-break-word",
"nodoubleclick generic-node-desc-text h-full cursor-grab text-[13px] text-muted-foreground word-break-break-word",
description === "" || !description ? "font-light italic" : "",
placeholderClassName,
)}
onDoubleClick={(e) => {
setInputDescription(true);
takeSnapshot();
}}
onDoubleClick={handleDoubleClickFn}
>
{renderedDescription}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export default function NodeName({
validationStatus,
isOutdated,
beta,
editNameDescription,
toggleEditNameDescription,
setHasChangedNodeDescription,
}: {
display_name?: string;
selected?: boolean;
Expand All @@ -21,70 +24,83 @@ export default function NodeName({
validationStatus: VertexBuildTypeAPI | null;
isOutdated: boolean;
beta: boolean;
editNameDescription: boolean;
toggleEditNameDescription: () => void;
setHasChangedNodeDescription: (hasChanged: boolean) => void;
}) {
const [inputName, setInputName] = useState(false);
const [nodeName, setNodeName] = useState<string>(display_name ?? "");
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
const setNode = useFlowStore((state) => state.setNode);

useEffect(() => {
if (!selected) {
setInputName(false);
if (selected && editNameDescription) {
takeSnapshot();
}
}, [selected]);
}, [editNameDescription]);

useEffect(() => {
setNodeName(display_name ?? "");
}, [display_name]);

return inputName ? (
const handleBlur = () => {
if (nodeName?.trim() !== "") {
setNodeName(nodeName);
setNode(nodeId, (old) => ({
...old,
data: {
...old.data,
node: {
...old.data.node,
display_name: nodeName,
},
},
}));
} else {
setNodeName(display_name ?? "");
}
};

const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
handleBlur();
toggleEditNameDescription();
}
if (e.key === "Escape") {
setNodeName(display_name ?? "");
toggleEditNameDescription();
}
};

const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setNodeName(e.target.value);
setHasChangedNodeDescription(true);
};

return editNameDescription ? (
<div className="m-[1px] w-full">
<Input
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 ?? "");
}
}}
onBlur={handleBlur}
value={nodeName}
autoFocus
onChange={(e) => setNodeName(e.target.value)}
onChange={onChange}
data-testid={`input-title-${display_name}`}
onKeyDown={handleKeyDown}
className="py-1"
/>
</div>
) : (
<div className="group flex w-full items-center gap-1">
<div
onDoubleClick={(event) => {
if (!showNode) {
return;
}
setInputName(true);
takeSnapshot();
event.stopPropagation();
event.preventDefault();
}}
data-testid={"title-" + display_name}
className={cn(
"nodoubleclick w-full truncate font-medium text-primary",
showNode ? "cursor-text" : "cursor-default",
)}
>
<div className="flex items-center gap-2">
<div className="flex cursor-grab items-center gap-2">
<span
className={cn(
"max-w-44 truncate text-[14px]",
"max-w-44 cursor-grab truncate text-[14px]",
validationStatus?.data?.duration && "max-w-36",
beta && "max-w-36",
validationStatus?.data?.duration && beta && "max-w-20",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ const HideShowButton = memo(
unstyled
onClick={onClick}
data-testid={`input-inspection-${title.toLowerCase()}`}
className="cursor-pointer"
>
<ShadTooltip
content={disabled ? null : hidden ? "Show output" : "Hide output"}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ export default function NodeStatus({
onClick={handleClickRun}
>
{showNode && (
<Button unstyled className="group">
<Button unstyled className="nodrag group">
<div data-testid={`button_run_` + display_name.toLowerCase()}>
<IconComponent
name={iconName}
Expand Down
Loading

0 comments on commit 8531e1b

Please sign in to comment.