Skip to content

Commit

Permalink
feat: Flow's canvas actions design uplift (langflow-ai#4260)
Browse files Browse the repository at this point in the history
* init

* move panels and make custom

* post review changes

* [autofix.ci] apply automated fixes

* pr comment fix

* design review fixes

* [autofix.ci] apply automated fixes

* action toolbar positioning feedback

* [autofix.ci] apply automated fixes

* remove extra imports

* test selector updates

* missed a couple

* one more

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
  • Loading branch information
mfortman11 and autofix-ci[bot] authored Oct 31, 2024
1 parent fab2d9a commit 20b3a6b
Show file tree
Hide file tree
Showing 75 changed files with 584 additions and 452 deletions.
118 changes: 118 additions & 0 deletions src/frontend/src/components/canvasControlsComponent/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import IconComponent from "@/components/genericIconComponent";
import ShadTooltip from "@/components/shadTooltipComponent";
import { cn } from "@/utils/utils";
import {
ControlButton,
Panel,
useReactFlow,
useStore,
useStoreApi,
type ReactFlowState,
} from "reactflow";
import { shallow } from "zustand/shallow";

type CustomControlButtonProps = {
iconName: string;
tooltipText: string;
onClick: () => void;
disabled?: boolean;
backgroundClasses?: string;
iconClasses?: string;
testId?: string;
};

export const CustomControlButton = ({
iconName,
tooltipText,
onClick,
disabled,
backgroundClasses,
iconClasses,
testId,
}: CustomControlButtonProps): JSX.Element => {
return (
<ControlButton
data-testid={testId}
className="!h-8 !w-8 rounded !p-0"
onClick={onClick}
disabled={disabled}
>
<ShadTooltip content={tooltipText}>
<div className={cn("rounded p-2.5", backgroundClasses)}>
<IconComponent
name={iconName}
aria-hidden="true"
className={cn("scale-150 text-muted-foreground", iconClasses)}
/>
</div>
</ShadTooltip>
</ControlButton>
);
};

const selector = (s: ReactFlowState) => ({
isInteractive: s.nodesDraggable || s.nodesConnectable || s.elementsSelectable,
minZoomReached: s.transform[2] <= s.minZoom,
maxZoomReached: s.transform[2] >= s.maxZoom,
});

const CanvasControls = ({ children }) => {
const store = useStoreApi();
const { fitView, zoomIn, zoomOut } = useReactFlow();
const { isInteractive, minZoomReached, maxZoomReached } = useStore(
selector,
shallow,
);

const onToggleInteractivity = () => {
store.setState({
nodesDraggable: !isInteractive,
nodesConnectable: !isInteractive,
elementsSelectable: !isInteractive,
});
};

return (
<Panel
data-testid="canvas_controls"
className="react-flow__controls !m-2 flex gap-1.5 rounded-md border border-secondary-hover bg-background fill-foreground stroke-foreground p-1.5 text-primary shadow [&>button]:border-0 [&>button]:bg-background hover:[&>button]:bg-accent"
position="bottom-left"
>
{/* Zoom In */}
<CustomControlButton
iconName="ZoomIn"
tooltipText="Zoom In"
onClick={zoomIn}
disabled={maxZoomReached}
testId="zoom_in"
/>
{/* Zoom Out */}
<CustomControlButton
iconName="ZoomOut"
tooltipText="Zoom Out"
onClick={zoomOut}
disabled={minZoomReached}
testId="zoom_out"
/>
{/* Zoom To Fit */}
<CustomControlButton
iconName="maximize"
tooltipText="Fit To Zoom"
onClick={fitView}
testId="fit_view"
/>
{/* Lock/Unlock */}
<CustomControlButton
iconName={isInteractive ? "LockOpen" : "Lock"}
tooltipText={isInteractive ? "Lock" : "Unlock"}
onClick={onToggleInteractivity}
backgroundClasses={isInteractive ? "" : "bg-destructive"}
iconClasses={isInteractive ? "" : "text-primary-foreground"}
testId="lock_unlock"
/>
{children}
</Panel>
);
};

export default CanvasControls;
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import ShadTooltip from "@/components/shadTooltipComponent";
import { ENABLE_API, ENABLE_NEW_IO_MODAL } from "@/customization/feature-flags";
import { track } from "@/customization/utils/analytics";
import { Transition } from "@headlessui/react";
import { useEffect, useMemo, useState } from "react";
import { useHotkeys } from "react-hotkeys-hook";
import { Panel } from "reactflow";
import ApiModal from "../../modals/apiModal";
import IOModalOld from "../../modals/IOModal";
import IOModalNew from "../../modals/IOModal/newModal";
Expand All @@ -12,7 +13,7 @@ import { useShortcutsStore } from "../../stores/shortcuts";
import { useStoreStore } from "../../stores/storeStore";
import { classNames, isThereModal } from "../../utils/utils";
import ForwardedIconComponent from "../genericIconComponent";
import { Separator } from "../ui/separator";

const IOModal = ENABLE_NEW_IO_MODAL ? IOModalNew : IOModalOld;

export default function FlowToolbar(): JSX.Element {
Expand Down Expand Up @@ -66,27 +67,39 @@ export default function FlowToolbar(): JSX.Element {
open={openShareModal}
setOpen={setOpenShareModal}
>
<button
disabled={!hasApiKey || !validApiKey || !hasStore}
className={classNames(
"relative inline-flex h-full w-full items-center justify-center gap-[4px] bg-muted px-5 py-3 text-sm font-semibold text-foreground transition-all duration-150 ease-in-out hover:bg-background hover:bg-hover",
<ShadTooltip
content={
!hasApiKey || !validApiKey || !hasStore
? "button-disable text-muted-foreground"
: "",
)}
data-testid="shared-button-flow"
? "Store API Key Required"
: ""
}
side="bottom"
align="end"
>
<ForwardedIconComponent
name="Share3"
<button
disabled={!hasApiKey || !validApiKey || !hasStore}
className={classNames(
"-m-0.5 -ml-1 h-6 w-6",
"share-button",
!hasApiKey || !validApiKey || !hasStore
? "extra-side-bar-save-disable"
? "text-muted-foreground"
: "",
)}
/>
Share
</button>
data-testid="shared-button-flow"
>
<>
<ForwardedIconComponent
name="Share2"
className={classNames(
"-m-0.5 -ml-1 h-4 w-4",
!hasApiKey || !validApiKey || !hasStore
? "extra-side-bar-save-disable"
: "",
)}
/>
Share
</>
</button>
</ShadTooltip>
</ShareModal>
),
[
Expand All @@ -101,52 +114,40 @@ export default function FlowToolbar(): JSX.Element {

return (
<>
<Transition
show={true}
appear={true}
enter="transition ease-out duration-300"
enterFrom="translate-y-96"
enterTo="translate-y-0"
leave="transition ease-in duration-300"
leaveFrom="translate-y-0"
leaveTo="translate-y-96"
>
<Panel className="!m-2" position="top-right">
<div
className={
"shadow-round-btn-shadow hover:shadow-round-btn-shadow message-button-position flex items-center justify-center gap-7 rounded-sm border bg-muted shadow-md transition-all"
"hover:shadow-round-btn-shadow flex items-center justify-center gap-7 rounded-md border bg-background p-1.5 shadow transition-all"
}
>
<div className="flex">
<div className="flex h-full w-full gap-1 rounded-sm transition-all">
<div className="flex gap-1.5">
<div className="flex h-full w-full gap-1.5 rounded-sm transition-all">
{hasIO ? (
<IOModal open={open} setOpen={setOpen} disable={!hasIO}>
<div
data-testid="playground-btn-flow-io"
className="relative inline-flex w-full items-center justify-center gap-1 px-5 py-3 text-sm font-semibold transition-all duration-500 ease-in-out hover:bg-hover"
className="relative inline-flex w-full items-center justify-center gap-1.5 rounded px-3 py-1.5 text-sm font-semibold transition-all duration-500 ease-in-out hover:bg-accent"
>
<ForwardedIconComponent
name="BotMessageSquareIcon"
className={"h-5 w-5 transition-all"}
name="Play"
className={"h-4 w-4 transition-all"}
/>
Playground
</div>
</IOModal>
) : (
<div
className={`relative inline-flex w-full cursor-not-allowed items-center justify-center gap-1 px-5 py-3 text-sm font-semibold text-muted-foreground transition-all duration-150 ease-in-out`}
className={`relative inline-flex w-full cursor-not-allowed items-center justify-center gap-1.5 rounded px-3 py-1.5 text-sm font-semibold text-muted-foreground transition-all duration-150 ease-in-out`}
data-testid="playground-btn-flow"
>
<ForwardedIconComponent
name="BotMessageSquareIcon"
className={"h-5 w-5 transition-all"}
name="Play"
className={"h-4 w-4 transition-all"}
/>
Playground
</div>
)}
</div>
<div>
<Separator orientation="vertical" />
</div>
{ENABLE_API && (
<>
<div className="flex cursor-pointer items-center gap-2">
Expand All @@ -158,21 +159,18 @@ export default function FlowToolbar(): JSX.Element {
>
<div
className={classNames(
"relative inline-flex w-full items-center justify-center gap-1 px-5 py-3 text-sm font-semibold text-foreground transition-all duration-150 ease-in-out hover:bg-hover",
"relative inline-flex w-full items-center justify-center gap-1.5 rounded px-3 py-1.5 text-sm font-semibold text-foreground transition-all duration-150 ease-in-out hover:bg-accent",
)}
>
<ForwardedIconComponent
name="Code2"
className={"h-5 w-5"}
className={"h-4 w-4"}
/>
API
</div>
</ApiModal>
)}
</div>
<div>
<Separator orientation="vertical" />
</div>
</>
)}
<div className="flex items-center gap-2">
Expand All @@ -188,7 +186,7 @@ export default function FlowToolbar(): JSX.Element {
</div>
</div>
</div>
</Transition>
</Panel>
</>
);
}
7 changes: 6 additions & 1 deletion src/frontend/src/components/shadTooltipComponent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export default function ShadTooltip({
styleClasses,
delayDuration = 500,
open,
align,
setOpen,
}: ShadToolTipType): JSX.Element {
return content ? (
Expand All @@ -21,9 +22,13 @@ export default function ShadTooltip({
>
<TooltipTrigger asChild={asChild}>{children}</TooltipTrigger>
<TooltipContent
className={cn("max-w-96", styleClasses)}
className={cn(
"max-w-96 bg-tooltip text-tooltip-foreground",
styleClasses,
)}
side={side}
avoidCollisions={false}
align={align}
sticky="always"
>
{content}
Expand Down
46 changes: 23 additions & 23 deletions src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { DefaultEdge } from "@/CustomEdges";
import NoteNode from "@/CustomNodes/NoteNode";
import IconComponent from "@/components/genericIconComponent";
import CanvasControls, {
CustomControlButton,
} from "@/components/canvasControlsComponent";
import FlowToolbar from "@/components/flowToolbarComponent";
import LoadingComponent from "@/components/loadingComponent";
import ShadTooltip from "@/components/shadTooltipComponent";
import {
NOTE_NODE_MIN_HEIGHT,
NOTE_NODE_MIN_WIDTH,
Expand All @@ -26,13 +28,13 @@ import { useHotkeys } from "react-hotkeys-hook";
import ReactFlow, {
Background,
Connection,
ControlButton,
Controls,
Edge,
NodeDragHandler,
OnSelectionChangeParams,
SelectionDragHandler,
updateEdge,
useReactFlow,
useViewport,
} from "reactflow";
import GenericNode from "../../../../CustomNodes/GenericNode";
import {
Expand Down Expand Up @@ -117,6 +119,9 @@ export default function Page({ view }: { view?: boolean }): JSX.Element {
const [isAddingNote, setIsAddingNote] = useState(false);
const [isHighlightingCursor, setIsHighlightingCursor] = useState(false);

const { zoomIn, zoomOut, fitView } = useReactFlow();
const { zoom } = useViewport();

useEffect(() => {
const handleVisibilityChange = () => {
if (!document.hidden) {
Expand Down Expand Up @@ -617,25 +622,20 @@ export default function Page({ view }: { view?: boolean }): JSX.Element {
>
<Background className="" />
{!view && (
<Controls className="fill-foreground stroke-foreground text-primary [&>button]:border-b-border [&>button]:bg-muted hover:[&>button]:bg-border">
<ControlButton
data-testid="add_note"
onClick={() => {
setIsAddingNote(true);
}}
className="postion react-flow__controls absolute -top-10"
>
<ShadTooltip content="Add note">
<div>
<IconComponent
name="SquarePen"
aria-hidden="true"
className="scale-125"
/>
</div>
</ShadTooltip>
</ControlButton>
</Controls>
<>
<CanvasControls>
<CustomControlButton
iconName="sticky-note"
tooltipText="Add Note"
onClick={() => {
setIsAddingNote(true);
}}
iconClasses="text-primary"
testId="add_note"
/>
</CanvasControls>
<FlowToolbar />
</>
)}
<SelectionMenu
lastSelection={lastSelection}
Expand Down
Loading

0 comments on commit 20b3a6b

Please sign in to comment.