fix: updated node toolbar to be more stable on zoom, fix success and error component tooltips, unselect nodes on flow opening (#4610)

* Unselect nodes and edges on enter

* Start with viewport fit

* Fix run component tooltip to match error and success

* Update scrollbar design

* Added node toolbar without portal to match the width of the node

* Updated color of scrollbar

* Updated NoteNode to have same behavior as GenericNode

* Updated logic for tests to pass

* Fix fitview to only occur when nodes are present

* Update z index of resize handle

* Sticky notes test fix

* [autofix.ci] apply automated fixes

* freeze test fixed

---------

Co-authored-by: Mike Fortman <michael.fortman@datastax.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: anovazzi1 <otavio2204@gmail.com>
Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
Co-authored-by: Cristhian Zanforlin Lousa <cristhian.lousa@gmail.com>
This commit is contained in:
Lucas Oliveira 2024-11-19 18:54:49 -03:00 • committed by GitHub
commit c9e0d5e4ba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 281 additions and 269 deletions

View file

@ -93,7 +93,7 @@ body {
} }
::-webkit-scrollbar-thumb:hover { ::-webkit-scrollbar-thumb:hover {
background-color: hsl(var(--ring)) !important; background-color: hsl(var(--placeholder-foreground)) !important;
} }
.jv-indent::-webkit-scrollbar-track { .jv-indent::-webkit-scrollbar-track {
@ -107,7 +107,7 @@ body {
} }
.jv-indent::-webkit-scrollbar-thumb:hover { .jv-indent::-webkit-scrollbar-thumb:hover {
background-color: hsl(var(--ring)) !important; background-color: hsl(var(--placeholder-foreground)) !important;
} }
.custom-hover { .custom-hover {
@ -170,7 +170,7 @@ body {
height: 0.75rem !important; height: 0.75rem !important;
background-color: white !important; background-color: white !important;
border-color: var(--border) !important; border-color: var(--border) !important;
z-index: 1000 !important; z-index: 50 !important;
border-radius: 20% !important; border-radius: 20% !important;
} }

View file

@ -151,7 +151,6 @@ export default function NodeStatus({
const divRef = useRef<HTMLDivElement>(null); const divRef = useRef<HTMLDivElement>(null);
const [isHovered, setIsHovered] = useState(false); const [isHovered, setIsHovered] = useState(false);
const runClass = "justify-left flex font-normal text-muted-foreground";
const stopBuilding = useFlowStore((state) => state.stopBuilding); const stopBuilding = useFlowStore((state) => state.stopBuilding);
const handleClickRun = () => { const handleClickRun = () => {
@ -207,6 +206,12 @@ export default function NodeStatus({
<div className="flex items-center gap-2 self-center"> <div className="flex items-center gap-2 self-center">
<ShadTooltip <ShadTooltip
styleClasses={cn(
"border rounded-xl",
conditionSuccess
? "border-accent-emerald-foreground bg-success-background"
: "border-destructive bg-error-background",
)}
content={ content={
buildStatus === BuildStatus.BUILDING ? ( buildStatus === BuildStatus.BUILDING ? (
<span> {STATUS_BUILDING} </span> <span> {STATUS_BUILDING} </span>
@ -215,26 +220,26 @@ export default function NodeStatus({
) : !validationStatus ? ( ) : !validationStatus ? (
<span className="flex">{STATUS_BUILD}</span> <span className="flex">{STATUS_BUILD}</span>
) : ( ) : (
<div className="max-h-100 p-2"> <div className="max-h-100 px-1 py-2.5">
<div className="max-h-80 overflow-auto"> <div className="flex max-h-80 flex-col gap-2 overflow-auto">
{validationString && ( {validationString && (
<div className="text-accent-red-foreground ml-1 pb-2"> <div className="text-sm text-foreground">
{validationString} {validationString}
</div> </div>
)} )}
{lastRunTime && ( {lastRunTime && (
<div className={runClass}> <div className="flex items-center text-sm text-secondary-foreground">
<div>{RUN_TIMESTAMP_PREFIX}</div> <div>{RUN_TIMESTAMP_PREFIX}</div>
<div className="ml-1 text-status-blue"> <div className="ml-1 text-secondary-foreground">
{lastRunTime} {lastRunTime}
</div> </div>
</div> </div>
)} )}
</div> <div className="flex items-center text-secondary-foreground">
<div className={runClass}> <div>Duration:</div>
<div>Duration:</div> <div className="ml-1">
<div className="ml-1 text-status-blue"> {validationStatus?.data.duration}
{validationStatus?.data.duration} </div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,7 +1,7 @@
import { usePostValidateComponentCode } from "@/controllers/API/queries/nodes/use-post-validate-component-code"; import { usePostValidateComponentCode } from "@/controllers/API/queries/nodes/use-post-validate-component-code";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { useHotkeys } from "react-hotkeys-hook"; import { useHotkeys } from "react-hotkeys-hook";
import { NodeToolbar, useUpdateNodeInternals } from "reactflow"; import { useUpdateNodeInternals } from "reactflow";
import { ForwardedIconComponent } from "../../components/genericIconComponent"; import { ForwardedIconComponent } from "../../components/genericIconComponent";
import ShadTooltip from "../../components/shadTooltipComponent"; import ShadTooltip from "../../components/shadTooltipComponent";
import { Button } from "../../components/ui/button"; import { Button } from "../../components/ui/button";
@ -214,8 +214,8 @@ export default function GenericNode({
}, [hiddenOutputs]); }, [hiddenOutputs]);
const memoizedNodeToolbarComponent = useMemo(() => { const memoizedNodeToolbarComponent = useMemo(() => {
return ( return selected ? (
<NodeToolbar> <div className={cn("absolute -top-12 left-1/2 z-50 -translate-x-1/2")}>
<NodeToolbarComponent <NodeToolbarComponent
data={data} data={data}
deleteNode={(id) => { deleteNode={(id) => {
@ -236,7 +236,9 @@ export default function GenericNode({
isOutdated={isOutdated && isUserEdited} isOutdated={isOutdated && isUserEdited}
setOpenShowMoreOptions={setOpenShowMoreOptions} setOpenShowMoreOptions={setOpenShowMoreOptions}
/> />
</NodeToolbar> </div>
) : (
<></>
); );
}, [ }, [
data, data,
@ -336,7 +338,6 @@ export default function GenericNode({
return ( return (
<> <>
{memoizedNodeToolbarComponent}
<div <div
className={cn( className={cn(
borderColor, borderColor,
@ -345,9 +346,9 @@ export default function GenericNode({
: `h-[4.065rem] w-48 rounded-[0.75rem] ${!selected ? "border-[1px] border-border ring-[0.5px] ring-border" : ""}`, : `h-[4.065rem] w-48 rounded-[0.75rem] ${!selected ? "border-[1px] border-border ring-[0.5px] ring-border" : ""}`,
"generic-node-div group/node relative", "generic-node-div group/node relative",
!hasOutputs && "pb-4", !hasOutputs && "pb-4",
openShowMoreOptions && "nowheel",
)} )}
> >
{memoizedNodeToolbarComponent}
<div <div
data-testid={`${data.id}-main-node`} data-testid={`${data.id}-main-node`}
className={cn( className={cn(

View file

@ -7,7 +7,7 @@ import {
} from "@/components/ui/popover"; } from "@/components/ui/popover";
import { import {
Select, Select,
SelectContent, SelectContentWithoutPortal,
SelectItem, SelectItem,
SelectTrigger, SelectTrigger,
} from "@/components/ui/select-custom"; } from "@/components/ui/select-custom";
@ -84,7 +84,7 @@ export default function NoteToolbarComponent({
<div className="w-26 noflow nowheel nopan nodelete nodrag h-10"> <div className="w-26 noflow nowheel nopan nodelete nodrag h-10">
<span className="isolate inline-flex rounded-md shadow-sm"> <span className="isolate inline-flex rounded-md shadow-sm">
<Popover> <Popover>
<ShadTooltip content="Color pick"> <ShadTooltip content="Pick Color">
<PopoverTrigger> <PopoverTrigger>
<div> <div>
<div <div
@ -145,8 +145,8 @@ export default function NoteToolbarComponent({
</PopoverContent> </PopoverContent>
</Popover> </Popover>
<Select onValueChange={handleSelectChange} value=""> <Select onValueChange={handleSelectChange} value="">
<ShadTooltip content="All" side="top"> <SelectTrigger>
<SelectTrigger> <ShadTooltip content="Show More" side="top">
<div> <div>
<div <div
data-testid="more-options-modal" data-testid="more-options-modal"
@ -160,9 +160,9 @@ export default function NoteToolbarComponent({
/> />
</div> </div>
</div> </div>
</SelectTrigger> </ShadTooltip>
</ShadTooltip> </SelectTrigger>
<SelectContent> <SelectContentWithoutPortal>
<SelectItem value={"duplicate"}> <SelectItem value={"duplicate"}>
<ToolbarSelectItem <ToolbarSelectItem
shortcut={ shortcut={
@ -211,7 +211,7 @@ export default function NoteToolbarComponent({
</span> </span>
</div> </div>
</SelectItem> </SelectItem>
</SelectContent> </SelectContentWithoutPortal>
</Select> </Select>
</span> </span>
</div> </div>

View file

@ -8,7 +8,7 @@ import {
import { noteDataType } from "@/types/flow"; import { noteDataType } from "@/types/flow";
import { cn } from "@/utils/utils"; import { cn } from "@/utils/utils";
import { useEffect, useMemo, useRef, useState } from "react"; import { useEffect, useMemo, useRef, useState } from "react";
import { NodeResizer, NodeToolbar } from "reactflow"; import { NodeResizer } from "reactflow";
import NodeDescription from "../GenericNode/components/NodeDescription"; import NodeDescription from "../GenericNode/components/NodeDescription";
import NoteToolbarComponent from "./NoteToolbarComponent"; import NoteToolbarComponent from "./NoteToolbarComponent";
function NoteNode({ function NoteNode({
@ -33,17 +33,20 @@ function NoteNode({
}); });
} }
}, []); }, []);
const MemoNoteToolbarComponent = useMemo( const MemoNoteToolbarComponent = useMemo(
() => ( () =>
<NodeToolbar> selected ? (
<NoteToolbarComponent data={data} bgColor={bgColor} /> <div className={cn("absolute -top-12 left-1/2 z-50 -translate-x-1/2")}>
</NodeToolbar> <NoteToolbarComponent data={data} bgColor={bgColor} />
), </div>
[data, bgColor], ) : (
<></>
),
[data, bgColor, selected],
); );
return ( return (
<> <>
{MemoNoteToolbarComponent}
<NodeResizer <NodeResizer
minWidth={NOTE_NODE_MIN_WIDTH} minWidth={NOTE_NODE_MIN_WIDTH}
minHeight={NOTE_NODE_MIN_HEIGHT} minHeight={NOTE_NODE_MIN_HEIGHT}
@ -67,11 +70,12 @@ function NoteNode({
}} }}
ref={nodeDiv} ref={nodeDiv}
className={cn( className={cn(
"flex h-full w-full flex-col gap-3 rounded-xl p-3 transition-all", "relative flex h-full w-full flex-col gap-3 rounded-xl p-3 transition-all",
COLOR_OPTIONS[bgColor] !== null && COLOR_OPTIONS[bgColor] !== null &&
`border ${!selected && "-z-50 shadow-sm"}`, `border ${!selected && "-z-50 shadow-sm"}`,
)} )}
> >
{MemoNoteToolbarComponent}
<div <div
style={{ style={{
width: size.width, width: size.width,

View file

@ -55,6 +55,34 @@ const SelectContent = React.forwardRef<
)); ));
SelectContent.displayName = SelectPrimitive.Content.displayName; SelectContent.displayName = SelectPrimitive.Content.displayName;
const SelectContentWithoutPortal = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Content
ref={ref}
className={cn(
"relative z-50 min-w-[11.5rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className,
)}
position={position}
{...props}
>
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]",
)}
>
{children}
</SelectPrimitive.Viewport>
</SelectPrimitive.Content>
));
SelectContentWithoutPortal.displayName = SelectPrimitive.Content.displayName;
const SelectLabel = React.forwardRef< const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>, React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label> React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
@ -99,6 +127,7 @@ SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
export { export {
Select, Select,
SelectContent, SelectContent,
SelectContentWithoutPortal,
SelectGroup, SelectGroup,
SelectItem, SelectItem,
SelectLabel, SelectLabel,

View file

@ -91,6 +91,7 @@ export default function Page({ view }: { view?: boolean }): JSX.Element {
); );
const nodes = useFlowStore((state) => state.nodes); const nodes = useFlowStore((state) => state.nodes);
const edges = useFlowStore((state) => state.edges); const edges = useFlowStore((state) => state.edges);
const isEmptyFlow = useRef(nodes.length === 0);
const onNodesChange = useFlowStore((state) => state.onNodesChange); const onNodesChange = useFlowStore((state) => state.onNodesChange);
const onEdgesChange = useFlowStore((state) => state.onEdgesChange); const onEdgesChange = useFlowStore((state) => state.onEdgesChange);
const setNodes = useFlowStore((state) => state.setNodes); const setNodes = useFlowStore((state) => state.setNodes);
@ -552,6 +553,7 @@ export default function Page({ view }: { view?: boolean }): JSX.Element {
onDrop={onDrop} onDrop={onDrop}
onSelectionChange={onSelectionChange} onSelectionChange={onSelectionChange}
deleteKeyCode={[]} deleteKeyCode={[]}
fitView={isEmptyFlow.current ? false : true}
className="theme-attribution" className="theme-attribution"
minZoom={0.01} minZoom={0.01}
maxZoom={8} maxZoom={8}

View file

@ -11,12 +11,12 @@ import CodeAreaModal from "@/modals/codeAreaModal";
import { APIClassType } from "@/types/api"; import { APIClassType } from "@/types/api";
import _, { cloneDeep } from "lodash"; import _, { cloneDeep } from "lodash";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { useStore, useUpdateNodeInternals } from "reactflow"; import { useUpdateNodeInternals } from "reactflow";
import IconComponent from "../../../../components/genericIconComponent"; import IconComponent from "../../../../components/genericIconComponent";
import ShadTooltip from "../../../../components/shadTooltipComponent"; import ShadTooltip from "../../../../components/shadTooltipComponent";
import { import {
Select, Select,
SelectContent, SelectContentWithoutPortal,
SelectItem, SelectItem,
SelectTrigger, SelectTrigger,
} from "../../../../components/ui/select-custom"; } from "../../../../components/ui/select-custom";
@ -370,31 +370,9 @@ export default function NodeToolbarComponent({
parameterId: "tool_mode", parameterId: "tool_mode",
}); });
// Use ReactFlow's store selector to get zoom updates
const zoom = useStore((state) => state.transform[2]);
const [scale, setScale] = useState<number | null>(null);
useEffect(() => {
if (!zoom) return;
if (zoom < 0.65) {
const newScale = Math.max(zoom * 1.2, 0.4);
setScale(newScale);
} else {
setScale(1);
}
}, [zoom]);
if (scale === null) return <></>;
return ( return (
<> <>
<div <div className="noflow nopan nodelete nodrag">
className="noflow nowheel nopan nodelete nodrag"
style={{
transform: `scale(${scale})`,
transformOrigin: "bottom",
}}
>
<div className="toolbar-wrapper"> <div className="toolbar-wrapper">
{hasCode && ( {hasCode && (
<ShadTooltip <ShadTooltip
@ -406,7 +384,6 @@ export default function NodeToolbarComponent({
/> />
} }
side="top" side="top"
styleClasses="relative bottom-2"
> >
<Button <Button
className="node-toolbar-buttons" className="node-toolbar-buttons"
@ -435,7 +412,6 @@ export default function NodeToolbarComponent({
/> />
} }
side="top" side="top"
styleClasses="relative bottom-2"
> >
<Button <Button
className="node-toolbar-buttons" className="node-toolbar-buttons"
@ -533,225 +509,223 @@ export default function NodeToolbarComponent({
</Button> </Button>
</ShadTooltip> </ShadTooltip>
)} )}
<ShadTooltip
content="Show More"
side="top"
styleClasses="relative bottom-2"
>
<Button
className="node-toolbar-buttons h-[2rem] w-[2rem]"
variant="ghost"
onClick={handleButtonClick}
size="node-toolbar"
data-testid="more-options-modal"
>
<IconComponent name="MoreHorizontal" className="h-4 w-4" />
</Button>
</ShadTooltip>
</div>
<Select <Select
onValueChange={handleSelectChange} onValueChange={handleSelectChange}
value={selectedValue!} value={selectedValue!}
onOpenChange={handleOpenChange} onOpenChange={handleOpenChange}
>
<SelectTrigger ref={selectTriggerRef} className="w-62">
<></>
</SelectTrigger>
<SelectContent
className={"relative top-1 w-56 bg-background"}
style={{
transform: `scale(${scale})`,
transformOrigin: "top",
left: scale === 1 ? "4.5rem" : `${1.7 * (scale - 1)}rem`,
}}
> >
{hasCode && ( <SelectTrigger className="w-62">
<SelectItem value={"code"}> <ShadTooltip content="Show More" side="top">
<div>
<Button
className="node-toolbar-buttons h-[2rem] w-[2rem]"
variant="ghost"
onClick={handleButtonClick}
size="node-toolbar"
data-testid="more-options-modal"
>
<IconComponent name="MoreHorizontal" className="h-4 w-4" />
</Button>
</div>
</ShadTooltip>
</SelectTrigger>
<SelectContentWithoutPortal
className={"relative top-1 w-56 bg-background"}
>
{hasCode && (
<SelectItem value={"code"}>
<ToolbarSelectItem
shortcut={
shortcuts.find((obj) => obj.name === "Code")?.shortcut!
}
value={"Code"}
icon={"Code"}
dataTestId="code-button-modal"
/>
</SelectItem>
)}
{nodeLength > 0 && (
<SelectItem value={nodeLength === 0 ? "disabled" : "advanced"}>
<ToolbarSelectItem
shortcut={
shortcuts.find((obj) => obj.name === "Advanced Settings")
?.shortcut!
}
value={"Controls"}
icon={"SlidersHorizontal"}
dataTestId="advanced-button-modal"
/>
</SelectItem>
)}
<SelectItem value={"save"}>
<ToolbarSelectItem <ToolbarSelectItem
shortcut={ shortcut={
shortcuts.find((obj) => obj.name === "Code")?.shortcut! shortcuts.find((obj) => obj.name === "Save Component")
}
value={"Code"}
icon={"Code"}
dataTestId="code-button-modal"
/>
</SelectItem>
)}
{nodeLength > 0 && (
<SelectItem value={nodeLength === 0 ? "disabled" : "advanced"}>
<ToolbarSelectItem
shortcut={
shortcuts.find((obj) => obj.name === "Advanced Settings")
?.shortcut! ?.shortcut!
} }
value={"Controls"} value={"Save"}
icon={"SlidersHorizontal"} icon={"SaveAll"}
dataTestId="advanced-button-modal" dataTestId="save-button-modal"
/> />
</SelectItem> </SelectItem>
)} <SelectItem value={"duplicate"}>
<SelectItem value={"save"}>
<ToolbarSelectItem
shortcut={
shortcuts.find((obj) => obj.name === "Save Component")
?.shortcut!
}
value={"Save"}
icon={"SaveAll"}
dataTestId="save-button-modal"
/>
</SelectItem>
<SelectItem value={"duplicate"}>
<ToolbarSelectItem
shortcut={
shortcuts.find((obj) => obj.name === "Duplicate")?.shortcut!
}
value={"Duplicate"}
icon={"Copy"}
dataTestId="copy-button-modal"
/>
</SelectItem>
<SelectItem value={"copy"}>
<ToolbarSelectItem
shortcut={
shortcuts.find((obj) => obj.name === "Copy")?.shortcut!
}
value={"Copy"}
icon={"Clipboard"}
dataTestId="copy-button-modal"
/>
</SelectItem>
{isOutdated && (
<SelectItem value={"update"}>
<ToolbarSelectItem <ToolbarSelectItem
shortcut={ shortcut={
shortcuts.find((obj) => obj.name === "Update")?.shortcut! shortcuts.find((obj) => obj.name === "Duplicate")?.shortcut!
} }
value={"Restore"} value={"Duplicate"}
icon={"RefreshCcwDot"} icon={"Copy"}
dataTestId="update-button-modal" dataTestId="copy-button-modal"
/> />
</SelectItem> </SelectItem>
)} <SelectItem value={"copy"}>
{hasStore && (
<SelectItem value={"Share"} disabled={!hasApiKey || !validApiKey}>
<ToolbarSelectItem <ToolbarSelectItem
shortcut={ shortcut={
shortcuts.find((obj) => obj.name === "Component Share") shortcuts.find((obj) => obj.name === "Copy")?.shortcut!
?.shortcut!
} }
value={"Copy"}
icon={"Clipboard"}
dataTestId="copy-button-modal"
/>
</SelectItem>
{isOutdated && (
<SelectItem value={"update"}>
<ToolbarSelectItem
shortcut={
shortcuts.find((obj) => obj.name === "Update")?.shortcut!
}
value={"Restore"}
icon={"RefreshCcwDot"}
dataTestId="update-button-modal"
/>
</SelectItem>
)}
{hasStore && (
<SelectItem
value={"Share"} value={"Share"}
icon={"Share3"} disabled={!hasApiKey || !validApiKey}
dataTestId="share-button-modal" >
/> <ToolbarSelectItem
</SelectItem> shortcut={
)} shortcuts.find((obj) => obj.name === "Component Share")
?.shortcut!
}
value={"Share"}
icon={"Share3"}
dataTestId="share-button-modal"
/>
</SelectItem>
)}
<SelectItem
value={"documentation"}
disabled={data.node?.documentation === ""}
>
<ToolbarSelectItem
shortcut={
shortcuts.find((obj) => obj.name === "Docs")?.shortcut!
}
value={"Docs"}
icon={"FileText"}
dataTestId="docs-button-modal"
/>
</SelectItem>
{(isMinimal || !showNode) && (
<SelectItem <SelectItem
value={"show"} value={"documentation"}
data-testid={`${showNode ? "minimize" : "expand"}-button-modal`} disabled={data.node?.documentation === ""}
> >
<ToolbarSelectItem <ToolbarSelectItem
shortcut={ shortcut={
shortcuts.find((obj) => obj.name === "Minimize")?.shortcut! shortcuts.find((obj) => obj.name === "Docs")?.shortcut!
} }
value={showNode ? "Minimize" : "Expand"} value={"Docs"}
icon={showNode ? "Minimize2" : "Maximize2"} icon={"FileText"}
dataTestId="minimize-button-modal" dataTestId="docs-button-modal"
/> />
</SelectItem> </SelectItem>
)} {(isMinimal || !showNode) && (
{isGroup && ( <SelectItem
<SelectItem value="ungroup"> value={"show"}
<ToolbarSelectItem data-testid={`${showNode ? "minimize" : "expand"}-button-modal`}
shortcut={
shortcuts.find((obj) => obj.name === "Group")?.shortcut!
}
value={"Ungroup"}
icon={"Ungroup"}
dataTestId="group-button-modal"
/>
</SelectItem>
)}
<SelectItem value="freeze">
<ToolbarSelectItem
shortcut={
shortcuts.find((obj) => obj.name === "Freeze")?.shortcut!
}
value={"Freeze"}
icon={"Snowflake"}
dataTestId="freeze-button"
style={`${frozen ? " text-ice" : ""} transition-all`}
/>
</SelectItem>
<SelectItem value="freezeAll">
<ToolbarSelectItem
shortcut={
shortcuts.find((obj) => obj.name === "Freeze Path")?.shortcut!
}
value={"Freeze Path"}
icon={"FreezeAll"}
dataTestId="freeze-path-button"
style={`${frozen ? " text-ice" : ""} transition-all`}
/>
</SelectItem>
<SelectItem value="Download">
<ToolbarSelectItem
shortcut={
shortcuts.find((obj) => obj.name === "Download")?.shortcut!
}
value={"Download"}
icon={"Download"}
dataTestId="download-button-modal"
/>
</SelectItem>
<SelectItem value={"delete"} className="focus:bg-red-400/[.20]">
<div className="font-red flex text-status-red">
<IconComponent
name="Trash2"
className="relative top-0.5 mr-2 h-4 w-4"
/>{" "}
<span className="">Delete</span>{" "}
<span
className={`absolute right-2 top-2 flex items-center justify-center rounded-sm px-1 py-[0.2]`}
> >
<IconComponent <ToolbarSelectItem
name="Delete" shortcut={
className="h-4 w-4 stroke-2 text-red-400" shortcuts.find((obj) => obj.name === "Minimize")
></IconComponent> ?.shortcut!
</span> }
</div> value={showNode ? "Minimize" : "Expand"}
</SelectItem> icon={showNode ? "Minimize2" : "Maximize2"}
{hasToolMode && ( dataTestId="minimize-button-modal"
<SelectItem value="toolMode"> />
</SelectItem>
)}
{isGroup && (
<SelectItem value="ungroup">
<ToolbarSelectItem
shortcut={
shortcuts.find((obj) => obj.name === "Group")?.shortcut!
}
value={"Ungroup"}
icon={"Ungroup"}
dataTestId="group-button-modal"
/>
</SelectItem>
)}
<SelectItem value="freeze">
<ToolbarSelectItem <ToolbarSelectItem
shortcut={ shortcut={
shortcuts.find((obj) => obj.name === "Tool Mode")?.shortcut! shortcuts.find((obj) => obj.name === "Freeze")?.shortcut!
} }
value={"Tool Mode"} value={"Freeze"}
icon={"Hammer"} icon={"Snowflake"}
dataTestId="tool-mode-button" dataTestId="freeze-button"
style={`${toolMode ? "text-primary" : ""} transition-all`} style={`${frozen ? " text-ice" : ""} transition-all`}
/> />
</SelectItem> </SelectItem>
)} <SelectItem value="freezeAll">
</SelectContent> <ToolbarSelectItem
</Select> shortcut={
shortcuts.find((obj) => obj.name === "Freeze Path")
?.shortcut!
}
value={"Freeze Path"}
icon={"FreezeAll"}
dataTestId="freeze-path-button"
style={`${frozen ? " text-ice" : ""} transition-all`}
/>
</SelectItem>
<SelectItem value="Download">
<ToolbarSelectItem
shortcut={
shortcuts.find((obj) => obj.name === "Download")?.shortcut!
}
value={"Download"}
icon={"Download"}
dataTestId="download-button-modal"
/>
</SelectItem>
<SelectItem value={"delete"} className="focus:bg-red-400/[.20]">
<div className="font-red flex text-status-red">
<IconComponent
name="Trash2"
className="relative top-0.5 mr-2 h-4 w-4"
/>{" "}
<span className="">Delete</span>{" "}
<span
className={`absolute right-2 top-2 flex items-center justify-center rounded-sm px-1 py-[0.2]`}
>
<IconComponent
name="Delete"
className="h-4 w-4 stroke-2 text-red-400"
></IconComponent>
</span>
</div>
</SelectItem>
{hasToolMode && (
<SelectItem value="toolMode">
<ToolbarSelectItem
shortcut={
shortcuts.find((obj) => obj.name === "Tool Mode")
?.shortcut!
}
value={"Tool Mode"}
icon={"Hammer"}
dataTestId="tool-mode-button"
style={`${toolMode ? "text-primary" : ""} transition-all`}
/>
</SelectItem>
)}
</SelectContentWithoutPortal>
</Select>
</div>
<ConfirmationModal <ConfirmationModal
open={showOverrideModal} open={showOverrideModal}

View file

@ -39,6 +39,7 @@ import {
getNodeId, getNodeId,
scapeJSONParse, scapeJSONParse,
scapedJSONStringfy, scapedJSONStringfy,
unselectAllNodesEdges,
updateGroupRecursion, updateGroupRecursion,
validateNodes, validateNodes,
} from "../utils/reactflowUtils"; } from "../utils/reactflowUtils";
@ -186,6 +187,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
let newEdges = cleanEdges(nodes, edges); let newEdges = cleanEdges(nodes, edges);
const { inputs, outputs } = getInputsAndOutputs(nodes); const { inputs, outputs } = getInputsAndOutputs(nodes);
get().updateComponentsToUpdate(nodes); get().updateComponentsToUpdate(nodes);
unselectAllNodesEdges(nodes, edges);
set({ set({
nodes, nodes,
edges: newEdges, edges: newEdges,
@ -212,7 +214,6 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
}, },
setReactFlowInstance: (newState) => { setReactFlowInstance: (newState) => {
set({ reactFlowInstance: newState }); set({ reactFlowInstance: newState });
get().reactFlowInstance?.fitView();
}, },
onNodesChange: (changes: NodeChange[]) => { onNodesChange: (changes: NodeChange[]) => {
set({ set({

View file

@ -68,7 +68,7 @@
.ag-body-horizontal-scroll-viewport::-webkit-scrollbar-thumb:hover, .ag-body-horizontal-scroll-viewport::-webkit-scrollbar-thumb:hover,
.ag-body-vertical-scroll-viewport::-webkit-scrollbar-thumb:hover { .ag-body-vertical-scroll-viewport::-webkit-scrollbar-thumb:hover {
background-color: hsl(var(--ring)); background-color: hsl(var(--placeholder-foreground));
} }
.ag-paging-page-size { .ag-paging-page-size {

View file

@ -26,6 +26,7 @@ pre {
} }
.react-flow__pane { .react-flow__pane {
pointer-events: all;
cursor: default; cursor: default;
} }
@ -147,7 +148,7 @@ textarea[class^="ag-"]:focus {
} }
.ace_scrollbar::-webkit-scrollbar-thumb:hover { .ace_scrollbar::-webkit-scrollbar-thumb:hover {
background-color: hsl(var(--ring)); background-color: hsl(var(--placeholder-foreground));
border-radius: 999px; border-radius: 999px;
} }

View file

@ -1,11 +1,6 @@
import { Edge, Node } from "reactflow"; import { Edge } from "reactflow";
import { FlowType, NodeType } from "../flow"; import { FlowType, NodeType } from "../flow";
export type unselectAllNodesType = {
updateNodes: (nodes: Node[]) => void;
data: Node[];
};
export type addEscapedHandleIdsToEdgesType = { export type addEscapedHandleIdsToEdgesType = {
edges: Edge[]; edges: Edge[];
}; };

View file

@ -41,7 +41,6 @@ import {
addEscapedHandleIdsToEdgesType, addEscapedHandleIdsToEdgesType,
findLastNodeType, findLastNodeType,
generateFlowType, generateFlowType,
unselectAllNodesType,
updateEdgesHandleIdsType, updateEdgesHandleIdsType,
} from "../types/utils/reactflowUtils"; } from "../types/utils/reactflowUtils";
import { getLayoutedNodes } from "./layoutUtils"; import { getLayoutedNodes } from "./layoutUtils";
@ -202,12 +201,13 @@ export function detectBrokenEdgesEdges(nodes: NodeType[], edges: Edge[]) {
return BrokenEdges; return BrokenEdges;
} }
export function unselectAllNodes({ updateNodes, data }: unselectAllNodesType) { export function unselectAllNodesEdges(nodes: Node[], edges: Edge[]) {
let newNodes = cloneDeep(data); nodes.forEach((node: Node) => {
newNodes.forEach((node: Node) => {
node.selected = false; node.selected = false;
}); });
updateNodes(newNodes!); edges.forEach((edge: Edge) => {
edge.selected = false;
});
} }
export function isValidConnection( export function isValidConnection(

View file

@ -353,7 +353,7 @@ const config = {
borderRadius: "999px", borderRadius: "999px",
}, },
"&::-webkit-scrollbar-thumb:hover": { "&::-webkit-scrollbar-thumb:hover": {
backgroundColor: "hsl(var(--ring))", backgroundColor: "hsl(var(--placeholder-foreground))",
}, },
cursor: "auto", cursor: "auto",
}, },

View file

@ -305,7 +305,7 @@ test("user must be able to freeze a component", async ({ page }) => {
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
await page.getByTestId("icon-Snowflake").last().click(); await page.getByText("Freeze", { exact: true }).click();
await page.waitForTimeout(1000); await page.waitForTimeout(1000);

View file

@ -162,7 +162,7 @@ The future of AI is both exciting and uncertain. As the technology continues to
await page.getByTestId("note_node").nth(0).focus(); await page.getByTestId("note_node").nth(0).focus();
await page.getByTestId("more-options-modal").click(); await page.getByTestId("more-options-modal").click();
await page.getByText("Delete").last().click(); await page.getByText("Delete").first().click();
await page.waitForTimeout(1000); await page.waitForTimeout(1000);