LFEN-215 HomePage Uplift (#4242)
* refactor: Remove CustomHeader component from AppWrapperPage * refactor: Remove unused code and fix formatting in MainPage component - Remove commented out code and unused imports - Fix indentation and formatting issues in the component * colors update and component building * refactor: Update grid and list components in MainPage - Update grid component in MainPage to use custom navigation hook - Add folderId parameter to editFlowLink in grid component - Update list component in MainPage to use custom navigation hook - Add folderId parameter to editFlowLink in list component Refactor the grid and list components in MainPage to use the custom navigation hook instead of the react-router-dom's useNavigate hook. This allows for better control and customization of navigation within the components. Additionally, the editFlowLink now includes the folderId parameter when navigating to the flow edit page. Closes #<issue_number> * refactor: Update grid and list components in MainPage Refactor the grid and list components in the MainPage to improve performance and enhance user experience. This includes updating the styling, optimizing code, and fixing formatting issues. * Refactor header component to dynamically display folder name * Refactor CSS styles and button component - Update CSS styles in applies.css to adjust the position of the header notifications dot. - Refactor the button component in button.tsx to fix a typo in the class names. Closes #4259 * Refactor folder store and sidebar components * Refactor header component to dynamically toggle folder sidebar visibility * Refactor CSS styles and button component * Refactor CSS styles and button component * Refactor CSS styles to adjust the width of the text container * Refactor dropdown and grid components to handle playground click * Refactor folder sidebar and list components * Refactor CSS styles and components for HeaderMenu, GridComponent, and ListComponent * Refactor CSS styles and components for HeaderMenu, GridComponent, ListComponent, dropdown, and button * Refactor CSS styles to adjust the width of the text container in ListComponent * refactor: Update folder name display in EmptyPage component * refactor: Update folder name display in FlowMenu component * refactor: Update folder name display in AccountMenu and EmptyPage components * refactor: Update folder name display in AccountMenu, EmptyPage, and FlowMenu components * refactor: Update folder name display in AppHeader, DashboardWrapperPage, and HomePage components * refactor: Update folder name display in MainPage components - Update folder name display in AppHeader, DashboardWrapperPage, and HomePage components - Update folder name display in AccountMenu, EmptyPage, and FlowMenu components - Update folder name display in AccountMenu and EmptyPage components - Update folder name display in FlowMenu component - Update folder name display in EmptyPage component Update the folder name display in various components of the MainPage. This ensures that the folder names are correctly displayed in the UI. The affected components include AppHeader, DashboardWrapperPage, HomePage, AccountMenu, EmptyPage, and FlowMenu. This refactor improves the consistency and user experience of the application. * refactor: Update folder name display in EmptyPage and HomePage components * refactor: Update folder name display in AppHeader, DashboardWrapperPage, and HomePage components * refactor: Update folder name display in MainPage components * file organization * Refactor folder name display in MainPage, ListComponent, and HomePage components * Refactor folder name display in MainPage components * test fixes * test fixes part 2
This commit is contained in:
parent
98ee051ca6
commit
85cffda753
133 changed files with 2002 additions and 419 deletions
|
|
@ -28,11 +28,6 @@ def build_vertex(self, vertex: Vertex) -> Vertex:
|
|||
|
||||
|
||||
@celery_app.task(acks_late=True)
|
||||
def process_graph_cached_task(
|
||||
data_graph: dict[str, Any],
|
||||
inputs: dict | list[dict] | None = None,
|
||||
clear_cache=False, # noqa: FBT002
|
||||
session_id=None,
|
||||
) -> dict[str, Any]:
|
||||
def process_graph_cached_task() -> dict[str, Any]:
|
||||
msg = "This task is not implemented yet"
|
||||
raise NotImplementedError(msg)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Chivo:ital,wght@0,100..900;1,100..900&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap"
|
||||
rel="stylesheet"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Cross2Icon } from "@radix-ui/react-icons";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import IconComponent from "../../components/genericIconComponent";
|
||||
import {
|
||||
Popover,
|
||||
|
|
@ -13,6 +13,8 @@ import SingleAlert from "./components/singleAlertComponent";
|
|||
|
||||
export default function AlertDropdown({
|
||||
children,
|
||||
notificationRef,
|
||||
onClose,
|
||||
}: AlertDropdownType): JSX.Element {
|
||||
const notificationList = useAlertStore((state) => state.notificationList);
|
||||
const clearNotificationList = useAlertStore(
|
||||
|
|
@ -27,16 +29,29 @@ export default function AlertDropdown({
|
|||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
onClose?.();
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<Popover
|
||||
data-testid="notification-dropdown"
|
||||
open={open}
|
||||
onOpenChange={(target) => {
|
||||
setOpen(target);
|
||||
if (target) setNotificationCenter(false);
|
||||
if (target) {
|
||||
setNotificationCenter(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<PopoverTrigger>{children}</PopoverTrigger>
|
||||
<PopoverContent className="noflow nowheel nopan nodelete nodrag flex h-[500px] w-[500px] flex-col">
|
||||
<PopoverContent
|
||||
ref={notificationRef}
|
||||
data-testid="notification-dropdown-content"
|
||||
className="noflow nowheel nopan nodelete nodrag z-10 flex h-[500px] w-[500px] flex-col"
|
||||
>
|
||||
<div className="text-md flex flex-row justify-between pl-3 font-medium text-foreground">
|
||||
Notifications
|
||||
<div className="flex gap-3 pr-3">
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import ForwardedIconComponent from "@/components/genericIconComponent";
|
||||
import { useLogout } from "@/controllers/API/queries/auth";
|
||||
import { CustomFeedbackDialog } from "@/customization/components/custom-feedback-dialog";
|
||||
import { CustomHeaderMenuItemsTitle } from "@/customization/components/custom-header-menu-items-title";
|
||||
|
|
@ -50,8 +51,10 @@ export const AccountMenu = () => {
|
|||
<HeaderMenuItems position="right">
|
||||
{ENABLE_DATASTAX_LANGFLOW && <CustomHeaderMenuItemsTitle />}
|
||||
<HeaderMenuItemsSection>
|
||||
<div className="flex h-[46px] w-full items-center justify-between pl-2">
|
||||
<div className="text-sm text-zinc-500">Version {version}</div>
|
||||
<div className="flex h-[46px] w-full items-center justify-between px-3">
|
||||
<div className="text-xs font-medium text-zinc-500">
|
||||
Version {version}
|
||||
</div>
|
||||
<ThemeButtons />
|
||||
</div>
|
||||
{ENABLE_DATASTAX_LANGFLOW ? (
|
||||
|
|
@ -60,6 +63,7 @@ export const AccountMenu = () => {
|
|||
</HeaderMenuItemLink>
|
||||
) : (
|
||||
<HeaderMenuItemButton
|
||||
icon="arrow-right"
|
||||
onClick={() => {
|
||||
navigate("/settings");
|
||||
}}
|
||||
|
|
@ -106,19 +110,19 @@ export const AccountMenu = () => {
|
|||
</HeaderMenuItemLink>
|
||||
)}
|
||||
<HeaderMenuItemLink newPage href="https://twitter.com/langflow_ai">
|
||||
Follow {ENABLE_DATASTAX_LANGFLOW ? "Langflow" : "us"} on X
|
||||
Follow Langflow on X
|
||||
</HeaderMenuItemLink>
|
||||
<HeaderMenuItemLink newPage href="https://discord.gg/EqksyE2EX9">
|
||||
Join our Discord
|
||||
Join the Langflow Discord
|
||||
</HeaderMenuItemLink>
|
||||
</HeaderMenuItemsSection>
|
||||
<HeaderMenuItemsSection>
|
||||
{ENABLE_DATASTAX_LANGFLOW ? (
|
||||
<HeaderMenuItemLink href="/session/logout">
|
||||
<HeaderMenuItemLink href="/session/logout" icon="log-out">
|
||||
Logout
|
||||
</HeaderMenuItemLink>
|
||||
) : (
|
||||
<HeaderMenuItemButton onClick={handleLogout}>
|
||||
<HeaderMenuItemButton onClick={handleLogout} icon="log-out">
|
||||
Logout
|
||||
</HeaderMenuItemButton>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -64,13 +64,15 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
|||
customStringify(currentFlow) !== customStringify(currentSavedFlow);
|
||||
|
||||
const savedText =
|
||||
updatedAt && changesNotSaved
|
||||
? SAVED_HOVER +
|
||||
new Date(updatedAt).toLocaleString("en-US", {
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
})
|
||||
: "Saved";
|
||||
updatedAt && changesNotSaved ? (
|
||||
SAVED_HOVER +
|
||||
new Date(updatedAt).toLocaleString("en-US", {
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
})
|
||||
) : (
|
||||
<div className="text-[#059669]">Saved</div>
|
||||
);
|
||||
|
||||
function handleAddFlow() {
|
||||
try {
|
||||
|
|
@ -91,11 +93,12 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
|||
|
||||
function printByBuildStatus() {
|
||||
if (isBuilding) {
|
||||
return "Building...";
|
||||
return <div className="truncate">Building...</div>;
|
||||
} else if (saveLoading) {
|
||||
return "Saving...";
|
||||
return <div className="truncate">Saving...</div>;
|
||||
}
|
||||
return savedText;
|
||||
// return savedText;
|
||||
return <div className="truncate text-[#059669]">Saved</div>;
|
||||
}
|
||||
|
||||
const handleSave = () => {
|
||||
|
|
@ -108,36 +111,44 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
|||
useHotkeys(changes, handleSave, { preventDefault: true });
|
||||
|
||||
return currentFlow && onFlowPage ? (
|
||||
<div className="flex items-center">
|
||||
<div className="header-menu-bar">
|
||||
<div className="flex items-baseline gap-2 truncate">
|
||||
<div className="header-menu-bar w-full justify-end truncate">
|
||||
{currentFolder?.name && (
|
||||
<>
|
||||
<div className="flex hidden truncate md:flex">
|
||||
<div
|
||||
className="cursor-pointer whitespace-nowrap font-normal text-zinc-500 dark:text-zinc-400"
|
||||
className="cursor-pointer truncate text-muted-foreground hover:text-primary"
|
||||
onClick={() => {
|
||||
navigate("/");
|
||||
}}
|
||||
>
|
||||
{currentFolder?.name}
|
||||
</div>
|
||||
<div className="px-2 font-normal text-zinc-500">/</div>
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="hidden w-fit font-normal text-muted-foreground md:flex">
|
||||
/
|
||||
</div>
|
||||
|
||||
<div className="w-fit truncate text-sm sm:overflow-visible sm:whitespace-normal">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<div className="header-menu-bar-display-2">
|
||||
<div className="header-menu-bar-display-2 group truncate">
|
||||
<div
|
||||
className="header-menu-flow-name-2 flex"
|
||||
className="header-menu-flow-name-2 truncate"
|
||||
data-testid="flow-configuration-button"
|
||||
>
|
||||
<div
|
||||
className="whitespace-nowrap font-semibold text-black dark:text-[white]"
|
||||
className="truncate font-semibold group-hover:text-primary dark:text-[white]"
|
||||
data-testid="flow_name"
|
||||
>
|
||||
{currentFlow.name}
|
||||
</div>
|
||||
</div>
|
||||
<IconComponent name="ChevronDown" className="w-4 text-zinc-500" />
|
||||
<IconComponent
|
||||
name="ChevronDown"
|
||||
className="flex h-5 w-5 text-muted-foreground group-hover:text-primary"
|
||||
/>
|
||||
</div>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-44 bg-white dark:bg-black">
|
||||
|
|
@ -268,7 +279,7 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
|||
></FlowSettingsModal>
|
||||
<FlowLogsModal open={openLogs} setOpen={setOpenLogs}></FlowLogsModal>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="hidden w-full items-center truncate sm:flex">
|
||||
{!autoSaving && (
|
||||
<Button
|
||||
variant="primary"
|
||||
|
|
@ -309,14 +320,16 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
|||
)
|
||||
}
|
||||
side="bottom"
|
||||
styleClasses="cursor-default"
|
||||
styleClasses="cursor-default z-10"
|
||||
>
|
||||
<div className="ml-2 flex cursor-default items-center gap-2 text-sm text-muted-foreground transition-all">
|
||||
<div className="flex cursor-default items-center gap-2 text-sm text-zinc-500 transition-all">
|
||||
<div>{printByBuildStatus()}</div>
|
||||
<div className="mr-3 flex cursor-default items-center gap-2 truncate text-sm text-muted-foreground">
|
||||
<div className="flex cursor-default items-center gap-2 truncate text-sm text-zinc-500">
|
||||
<div className="w-full truncate text-xs">
|
||||
{printByBuildStatus()}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
data-testid="stop_building_button"
|
||||
data-testid="stop_building_button "
|
||||
disabled={!isBuilding}
|
||||
onClick={(_) => {
|
||||
if (isBuilding) {
|
||||
|
|
@ -325,7 +338,7 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
|||
}}
|
||||
className={
|
||||
isBuilding
|
||||
? "flex items-center gap-1.5 text-status-red transition-all"
|
||||
? "flex hidden items-center gap-1.5 text-xs text-status-red sm:flex"
|
||||
: "hidden"
|
||||
}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import ShadTooltip from "@/components/shadTooltipComponent";
|
||||
import { useDarkStore } from "@/stores/darkStore";
|
||||
import { FaGithub } from "react-icons/fa";
|
||||
|
||||
|
|
@ -5,13 +6,17 @@ export const GithubStarComponent = () => {
|
|||
const stars = useDarkStore((state) => state.stars);
|
||||
|
||||
return (
|
||||
<div className="header-github-link gap-1 bg-zinc-100 dark:bg-zinc-900 dark:hover:bg-zinc-900">
|
||||
<FaGithub className="h-4 w-4 text-black dark:text-[white]" />
|
||||
<div className="hidden text-black dark:text-[white] lg:block">Star</div>
|
||||
<div className="header-github-display text-black dark:text-[white]">
|
||||
{stars?.toLocaleString() ?? 0}
|
||||
<ShadTooltip content="Go to Github repo" side="bottom" styleClasses="z-10">
|
||||
<div className="header-github-link-box gap-1 bg-muted hover:bg-zinc-200 dark:bg-zinc-900 dark:hover:bg-zinc-800">
|
||||
<FaGithub className="h-4 w-4 text-black dark:text-[white]" />
|
||||
<div className="hidden text-xs font-semibold text-black dark:text-[white] lg:block">
|
||||
Star
|
||||
</div>
|
||||
<div className="header-github-display text-xs font-semibold text-black dark:text-[white]">
|
||||
{stars.toLocaleString() ?? 0}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ShadTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import ForwardedIconComponent from "@/components/genericIconComponent";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import { ChevronsUpDown } from "lucide-react";
|
||||
|
|
@ -54,28 +55,36 @@ export const HeaderMenuSelector = ({
|
|||
);
|
||||
|
||||
const BASE_ITEM_STYLES =
|
||||
"group flex w-full items-center justify-between h-[46px] rounded-md pl-2 py-2 text-sm text-gray-900 dark:text-[white] dark:hover:bg-zinc-800 hover:bg-gray-100";
|
||||
"group flex w-full items-center justify-between h-[40px] my-1 rounded-md px-3 text-sm text-gray-900 dark:text-[white] dark:hover:bg-zinc-800 hover:bg-gray-100";
|
||||
|
||||
export const HeaderMenuItemLink = ({
|
||||
href = "#",
|
||||
selected = false,
|
||||
children,
|
||||
newPage = false,
|
||||
icon = "external-link",
|
||||
}) => (
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<a
|
||||
className={`${selected ? "bg-gray-50" : ""} ${BASE_ITEM_STYLES}`}
|
||||
className={`group ${selected ? "bg-gray-50" : ""} ${BASE_ITEM_STYLES}`}
|
||||
href={href}
|
||||
{...(newPage ? { rel: "noreferrer", target: "_blank" } : {})}
|
||||
>
|
||||
{children}
|
||||
{icon && (
|
||||
<ForwardedIconComponent
|
||||
name={icon}
|
||||
className="side-bar-button-size hidden h-[18px] w-[18px] group-hover:block" // Use group-hover:block to show on hover
|
||||
/>
|
||||
)}
|
||||
</a>
|
||||
)}
|
||||
</Menu.Item>
|
||||
);
|
||||
|
||||
export const HeaderMenuItemButton = ({
|
||||
icon = "",
|
||||
onClick,
|
||||
selected = false,
|
||||
children,
|
||||
|
|
@ -87,6 +96,12 @@ export const HeaderMenuItemButton = ({
|
|||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
{icon && (
|
||||
<ForwardedIconComponent
|
||||
name={icon}
|
||||
className="side-bar-button-size hidden h-[18px] w-[18px] group-hover:block" // Use group-hover:block to show on hover
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</Menu.Item>
|
||||
|
|
@ -108,7 +123,7 @@ export const HeaderMenuItems = ({
|
|||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items
|
||||
className={`absolute dark:bg-black ${positionClass} z-[999] mt-2 w-[20rem] origin-top-right rounded-md bg-[white] shadow-lg ring-1 ring-black/5 focus:outline-none`}
|
||||
className={`absolute dark:bg-black ${positionClass} z-[999] mt-2 w-[20rem] origin-top-right rounded-md border bg-[white] shadow-lg ring-1 ring-black/5 focus:outline-none dark:border-zinc-800`}
|
||||
>
|
||||
{children}
|
||||
</Menu.Items>
|
||||
|
|
@ -118,7 +133,7 @@ export const HeaderMenuItems = ({
|
|||
|
||||
export const HeaderMenuItemsSection = ({ children }) => (
|
||||
<>
|
||||
<div className="m-1 p-1">{children}</div>
|
||||
<div className="px-1">{children}</div>
|
||||
<hr className="border-gray-200 last:hidden dark:border-zinc-700" />
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
|
||||
import useTheme from "@/customization/hooks/use-custom-theme";
|
||||
import useAlertStore from "@/stores/alertStore";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import ForwardedIconComponent from "../genericIconComponent";
|
||||
import { Button } from "../ui/button";
|
||||
import { Separator } from "../ui/separator";
|
||||
|
|
@ -21,12 +22,34 @@ import GithubStarComponent from "./components/GithubStarButton";
|
|||
export default function AppHeader(): JSX.Element {
|
||||
const notificationCenter = useAlertStore((state) => state.notificationCenter);
|
||||
const navigate = useCustomNavigate();
|
||||
const [activeState, setActiveState] = useState<"notifications" | null>(null);
|
||||
const lastPath = window.location.pathname.split("/").filter(Boolean).pop();
|
||||
const notificationRef = useRef<HTMLButtonElement | null>(null);
|
||||
const notificationContentRef = useRef<HTMLDivElement | null>(null);
|
||||
useTheme();
|
||||
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
const target = event.target as Node;
|
||||
const isNotificationButton = notificationRef.current?.contains(target);
|
||||
const isNotificationContent =
|
||||
notificationContentRef.current?.contains(target);
|
||||
|
||||
if (!isNotificationButton && !isNotificationContent) {
|
||||
setActiveState(null);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="relative flex items-center border-b px-4 py-1.5 dark:bg-black">
|
||||
<div className="fixed left-0 top-0 z-[1] flex h-[62px] w-full items-center border-b px-5 py-2.5 dark:bg-background">
|
||||
{/* Left Section */}
|
||||
<div className="flex w-full items-center gap-2 lg:max-w-[475px]">
|
||||
<div className={`absolute left-[20px] flex gap-2`}>
|
||||
<Button
|
||||
unstyled
|
||||
onClick={() => navigate("/")}
|
||||
|
|
@ -36,12 +59,12 @@ export default function AppHeader(): JSX.Element {
|
|||
{ENABLE_DATASTAX_LANGFLOW ? (
|
||||
<ShortDataStaxLogo className="fill-black dark:fill-[white]" />
|
||||
) : ENABLE_NEW_LOGO ? (
|
||||
<ShortLangFlowIcon className="fill-black dark:fill-[white]" />
|
||||
<ShortLangFlowIcon className="h-5 w-5 fill-black dark:fill-[white]" />
|
||||
) : (
|
||||
<span className="fill-black text-2xl dark:fill-white">⛓️</span>
|
||||
)}
|
||||
</Button>
|
||||
{ENABLE_DATASTAX_LANGFLOW && (
|
||||
{!ENABLE_DATASTAX_LANGFLOW && (
|
||||
<>
|
||||
<CustomOrgSelector />
|
||||
<CustomProductSelector />
|
||||
|
|
@ -50,69 +73,93 @@ export default function AppHeader(): JSX.Element {
|
|||
</div>
|
||||
|
||||
{/* Middle Section */}
|
||||
<div className="mx-auto flex items-center px-5">
|
||||
<div className="absolute left-[225px] right-[225px] truncate md:left-[230px] md:right-[230px] lg:left-[350px] lg:right-[350px] xl:left-[350px] xl:right-[350px] 2xl:left-[500px] 2xl:right-[500px]">
|
||||
<FlowMenu />
|
||||
</div>
|
||||
|
||||
{/* Right Section */}
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={`absolute right-[20px] flex gap-2`}>
|
||||
{!ENABLE_DATASTAX_LANGFLOW && (
|
||||
<>
|
||||
<Button
|
||||
unstyled
|
||||
className="flex items-center"
|
||||
className="flex hidden items-center whitespace-nowrap 2xl:inline"
|
||||
onClick={() =>
|
||||
window.open("https://github.com/langflow-ai/langflow", "_blank")
|
||||
}
|
||||
>
|
||||
<GithubStarComponent />
|
||||
</Button>
|
||||
<Separator
|
||||
orientation="vertical"
|
||||
className="h-7 dark:border-zinc-700"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<AlertDropdown>
|
||||
<ShadTooltip content="Notifications" side="bottom">
|
||||
<Button variant="ghost" className="flex text-sm font-medium">
|
||||
{notificationCenter && (
|
||||
<div className="header-notifications-dot"></div>
|
||||
)}
|
||||
<AlertDropdown
|
||||
notificationRef={notificationContentRef}
|
||||
onClose={() => setActiveState(null)}
|
||||
>
|
||||
<ShadTooltip
|
||||
content="Notifications and errors"
|
||||
side="bottom"
|
||||
styleClasses="z-10"
|
||||
>
|
||||
<Button
|
||||
ref={notificationRef}
|
||||
variant="ghost"
|
||||
className={`relative ${activeState === "notifications" ? "bg-accent text-accent-foreground" : ""}`}
|
||||
onClick={() =>
|
||||
setActiveState((prev) =>
|
||||
prev === "notifications" ? null : "notifications",
|
||||
)
|
||||
}
|
||||
>
|
||||
<span
|
||||
className={
|
||||
notificationCenter ? `header-notifications-dot` : "hidden"
|
||||
}
|
||||
/>
|
||||
<ForwardedIconComponent
|
||||
name="bell"
|
||||
className="side-bar-button-size"
|
||||
className="side-bar-button-size h-[18px] w-[18px]"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
Notifications
|
||||
<span className="hidden whitespace-nowrap lg:inline">
|
||||
Notifications
|
||||
</span>
|
||||
</Button>
|
||||
</ShadTooltip>
|
||||
</AlertDropdown>
|
||||
{!ENABLE_DATASTAX_LANGFLOW && (
|
||||
<>
|
||||
<ShadTooltip content="Store" side="bottom">
|
||||
<ShadTooltip
|
||||
content="Go to LangflowStore"
|
||||
side="bottom"
|
||||
styleClasses="z-10"
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="flex items-center text-sm font-medium"
|
||||
onClick={() => navigate("/store")}
|
||||
className={`flex items-center text-sm font-medium ${lastPath === "store" ? "bg-accent text-accent-foreground" : ""}`}
|
||||
onClick={() => {
|
||||
navigate("/store");
|
||||
}}
|
||||
data-testid="button-store"
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
name="Store"
|
||||
className="side-bar-button-size"
|
||||
className="side-bar-button-size h-[18px] w-[18px]"
|
||||
/>
|
||||
Store
|
||||
<span className="hidden whitespace-nowrap lg:inline">
|
||||
Store
|
||||
</span>
|
||||
</Button>
|
||||
</ShadTooltip>
|
||||
<Separator
|
||||
orientation="vertical"
|
||||
className="h-7 dark:border-zinc-700"
|
||||
className="my-auto h-7 dark:border-zinc-700"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{ENABLE_DATASTAX_LANGFLOW && (
|
||||
<>
|
||||
<ShadTooltip content="Docs" side="bottom">
|
||||
<ShadTooltip content="Docs" side="bottom" styleClasses="z-10">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="flex text-sm font-medium"
|
||||
|
|
@ -125,13 +172,13 @@ export default function AppHeader(): JSX.Element {
|
|||
>
|
||||
<ForwardedIconComponent
|
||||
name="book-open-text"
|
||||
className="side-bar-button-size"
|
||||
className="side-bar-button-size h-[18px] w-[18px]"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
Docs
|
||||
</Button>
|
||||
</ShadTooltip>
|
||||
<ShadTooltip content="Settings" side="bottom">
|
||||
<ShadTooltip content="Settings" side="bottom" styleClasses="z-10">
|
||||
<Button
|
||||
data-testid="user-profile-settings"
|
||||
variant="ghost"
|
||||
|
|
@ -140,18 +187,20 @@ export default function AppHeader(): JSX.Element {
|
|||
>
|
||||
<ForwardedIconComponent
|
||||
name="Settings"
|
||||
className="side-bar-button-size"
|
||||
className="side-bar-button-size h-[18px] w-[18px]"
|
||||
/>
|
||||
Settings
|
||||
</Button>
|
||||
</ShadTooltip>
|
||||
<Separator
|
||||
orientation="vertical"
|
||||
className="h-7 dark:border-zinc-700"
|
||||
className="my-auto h-7 dark:border-zinc-700"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<AccountMenu />
|
||||
<div className="ml-3 flex">
|
||||
<AccountMenu />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -56,6 +56,10 @@ const SideBarFoldersButtonsComponent = ({
|
|||
pathname.split("/").length < (ENABLE_CUSTOM_PARAM ? 5 : 4);
|
||||
const myCollectionId = useFolderStore((state) => state.myCollectionId);
|
||||
const folderIdDragging = useFolderStore((state) => state.folderIdDragging);
|
||||
const showFolderModal = useFolderStore((state) => state.showFolderModal);
|
||||
const setShowFolderModal = useFolderStore(
|
||||
(state) => state.setShowFolderModal,
|
||||
);
|
||||
|
||||
const checkPathName = (itemId: string) => {
|
||||
if (urlWithoutPath && itemId === myCollectionId) {
|
||||
|
|
@ -252,8 +256,17 @@ const SideBarFoldersButtonsComponent = ({
|
|||
isDeletingFolder;
|
||||
|
||||
const HeaderButtons = () => (
|
||||
<div className="mt-4 flex shrink-0 items-center justify-between gap-2">
|
||||
<div className="text-md flex-1 font-semibold">Folders</div>
|
||||
<div className="my-4 flex shrink-0 items-center justify-between gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="h-7 w-7 border-0 text-zinc-500 hover:bg-zinc-200 dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-white lg:hidden"
|
||||
size="icon"
|
||||
onClick={() => setShowFolderModal(!showFolderModal)}
|
||||
data-testid="upload-folder-button"
|
||||
>
|
||||
<IconComponent name="panel-right-open" className="h-4 w-4" />
|
||||
</Button>
|
||||
<div className="flex-1 text-sm font-semibold">Folders</div>
|
||||
<UploadFolderButton
|
||||
onClick={handleUploadFlowsToFolder}
|
||||
disabled={isUpdatingFolder}
|
||||
|
|
@ -263,31 +276,31 @@ const SideBarFoldersButtonsComponent = ({
|
|||
);
|
||||
|
||||
const AddFolderButton = ({ onClick, disabled }) => (
|
||||
<ShadTooltip content="Add a new folder">
|
||||
<ShadTooltip content="Create new folder" styleClasses="z-10">
|
||||
<Button
|
||||
variant="primary"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="border-0"
|
||||
className="h-7 w-7 border-0 text-zinc-500 hover:bg-zinc-200 dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-white"
|
||||
onClick={onClick}
|
||||
data-testid="add-folder-button"
|
||||
disabled={disabled}
|
||||
>
|
||||
<IconComponent name="Plus" className="w-5" />
|
||||
<IconComponent name="Plus" className="h-4 w-4" />
|
||||
</Button>
|
||||
</ShadTooltip>
|
||||
);
|
||||
|
||||
const UploadFolderButton = ({ onClick, disabled }) => (
|
||||
<ShadTooltip content="Upload a folder">
|
||||
<ShadTooltip content="Upload a folder" styleClasses="z-10">
|
||||
<Button
|
||||
variant="primary"
|
||||
variant="ghost"
|
||||
className="h-7 w-7 border-0 text-zinc-500 hover:bg-zinc-200 dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-white"
|
||||
size="icon"
|
||||
className="border-0"
|
||||
onClick={onClick}
|
||||
data-testid="upload-folder-button"
|
||||
disabled={disabled}
|
||||
>
|
||||
<IconComponent name="Upload" className="w-4" />
|
||||
<IconComponent name="Upload" className="h-4 w-4" />
|
||||
</Button>
|
||||
</ShadTooltip>
|
||||
);
|
||||
|
|
@ -295,7 +308,7 @@ const SideBarFoldersButtonsComponent = ({
|
|||
const FolderSelectItem = ({ name, iconName }) => (
|
||||
<div
|
||||
className={cn(
|
||||
name === "Delete" ? "text-error" : "",
|
||||
name === "Delete" ? "text-destructive" : "",
|
||||
"flex items-center font-medium",
|
||||
)}
|
||||
>
|
||||
|
|
@ -380,7 +393,7 @@ const SideBarFoldersButtonsComponent = ({
|
|||
<>
|
||||
<HeaderButtons />
|
||||
|
||||
<div className="flex gap-2 overflow-auto lg:h-[70vh] lg:flex-col">
|
||||
<div className="flex h-[70vh] flex-col gap-2 overflow-auto">
|
||||
<>
|
||||
{!loading ? (
|
||||
folders.map((item, index) => {
|
||||
|
|
@ -398,8 +411,8 @@ const SideBarFoldersButtonsComponent = ({
|
|||
className={cn(
|
||||
buttonVariants({ variant: "ghost" }),
|
||||
checkPathName(item.id!)
|
||||
? "bg-muted hover:bg-muted"
|
||||
: "border hover:bg-transparent lg:border-transparent lg:hover:border-border",
|
||||
? "bg-zinc-200 hover:bg-zinc-200 dark:bg-zinc-800"
|
||||
: "hover:bg-transparent hover:bg-zinc-200 dark:hover:bg-zinc-800 lg:border-transparent",
|
||||
"group flex w-full shrink-0 cursor-pointer gap-2 opacity-100 lg:min-w-full",
|
||||
folderIdDragging === item.id! ? "bg-border" : "",
|
||||
)}
|
||||
|
|
@ -412,10 +425,6 @@ const SideBarFoldersButtonsComponent = ({
|
|||
className="flex w-full items-center justify-between"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<IconComponent
|
||||
name={"folder"}
|
||||
className="mr-2 w-4 flex-shrink-0 justify-start stroke-[1.5] opacity-100"
|
||||
/>
|
||||
{editFolderName?.edit && !isUpdatingFolder ? (
|
||||
<div>
|
||||
<Input
|
||||
|
|
@ -452,7 +461,7 @@ const SideBarFoldersButtonsComponent = ({
|
|||
/>
|
||||
</div>
|
||||
) : (
|
||||
<span className="block w-full grow truncate opacity-100">
|
||||
<span className="block w-full grow truncate text-[13px] opacity-100">
|
||||
{item.name}
|
||||
</span>
|
||||
)}
|
||||
|
|
@ -461,16 +470,24 @@ const SideBarFoldersButtonsComponent = ({
|
|||
onValueChange={(value) => handleSelectChange(value, item)}
|
||||
value=""
|
||||
>
|
||||
<SelectTrigger
|
||||
className="w-fit"
|
||||
id={`options-trigger-${item.name}`}
|
||||
data-testid="more-options-button"
|
||||
<ShadTooltip
|
||||
content="Options"
|
||||
side="right"
|
||||
styleClasses="z-10"
|
||||
>
|
||||
<IconComponent
|
||||
name={"MoreHorizontal"}
|
||||
className="hidden w-4 stroke-[1.5] px-0 text-primary group-hover:block"
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectTrigger
|
||||
className="w-fit"
|
||||
id={`options-trigger-${item.name}`}
|
||||
data-testid="more-options-button"
|
||||
>
|
||||
<IconComponent
|
||||
name={"MoreHorizontal"}
|
||||
className={`w-4 stroke-[1.5] px-0 text-zinc-500 group-hover:block group-hover:text-black dark:text-zinc-400 dark:group-hover:text-white ${
|
||||
checkPathName(item.id!) ? "block" : "hidden"
|
||||
}`}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
</ShadTooltip>
|
||||
<SelectContent
|
||||
align="end"
|
||||
alignOffset={-16}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,48 @@
|
|||
import * as React from "react";
|
||||
import { cn } from "../../utils/utils";
|
||||
import ForwardedIconComponent from "../genericIconComponent";
|
||||
|
||||
export interface InputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
icon?: string;
|
||||
inputClassName?: string;
|
||||
}
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, type, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
data-testid=""
|
||||
type={type}
|
||||
className={cn("nopan nodelete nodrag noflow primary-input", className)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
({ className, inputClassName, icon = "", type, ...props }, ref) => {
|
||||
if (icon) {
|
||||
return (
|
||||
<label className={cn("relative block w-full", className)}>
|
||||
<ForwardedIconComponent
|
||||
name={icon}
|
||||
className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 transform text-muted-foreground"
|
||||
/>
|
||||
<input
|
||||
data-testid=""
|
||||
type={type}
|
||||
className={cn(
|
||||
"nopan nodelete nodrag noflow form-input block w-full appearance-none truncate rounded-md border-border bg-background px-3 pl-9 text-left text-sm placeholder:text-muted-foreground focus:border-black focus:placeholder-transparent focus:ring-zinc-300 disabled:cursor-not-allowed disabled:opacity-50 dark:focus:border-white dark:focus:ring-zinc-800",
|
||||
inputClassName,
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<input
|
||||
data-testid=""
|
||||
type={type}
|
||||
className={cn(
|
||||
"nopan nodelete nodrag noflow primary-input",
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
Input.displayName = "Input";
|
||||
|
|
|
|||
|
|
@ -10,3 +10,4 @@ export const ENABLE_INTEGRATIONS = false;
|
|||
export const ENABLE_NEW_IO_MODAL = false;
|
||||
export const ENABLE_NEW_LOGO = false;
|
||||
export const ENABLE_DATASTAX_LANGFLOW = false;
|
||||
export const ENABLE_HOMEPAGE = true;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import AlertDisplayArea from "@/alerts/displayArea";
|
||||
import CrashErrorComponent from "@/components/crashErrorComponent";
|
||||
import { CustomHeader } from "@/customization/components/custom-header";
|
||||
import { ErrorBoundary } from "react-error-boundary";
|
||||
import { Outlet } from "react-router-dom";
|
||||
import { GenericErrorComponent } from "./components/GenericErrorComponent";
|
||||
|
|
@ -10,8 +9,7 @@ export function AppWrapperPage() {
|
|||
const { healthCheckTimeout, fetchingHealth, refetch } = useHealthCheck();
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col">
|
||||
<CustomHeader />
|
||||
<div className="flex flex-col">
|
||||
<ErrorBoundary
|
||||
onReset={() => {
|
||||
// any reset function
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ export function DashboardWrapperPage() {
|
|||
return (
|
||||
<div className="flex h-screen w-full flex-col">
|
||||
<AppHeader />
|
||||
<Outlet />
|
||||
<div className="mt-[62px] flex h-[calc(100vh-62px)] w-full flex-row">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
120
src/frontend/src/pages/MainPage/components/dropdown/index.tsx
Normal file
120
src/frontend/src/pages/MainPage/components/dropdown/index.tsx
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
import ForwardedIconComponent from "@/components/genericIconComponent";
|
||||
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
||||
import useAlertStore from "@/stores/alertStore";
|
||||
import { FlowType } from "@/types/flow";
|
||||
import { downloadFlow } from "@/utils/reactflowUtils";
|
||||
import useDuplicateFlows from "../../oldComponents/componentsComponent/hooks/use-handle-duplicate";
|
||||
import useSelectOptionsChange from "../../oldComponents/componentsComponent/hooks/use-select-options-change";
|
||||
|
||||
type DropdownComponentProps = {
|
||||
flowData: FlowType;
|
||||
setOpenDelete: (open: boolean) => void;
|
||||
handlePlaygroundClick?: () => void;
|
||||
};
|
||||
|
||||
const DropdownComponent = ({
|
||||
flowData,
|
||||
setOpenDelete,
|
||||
handlePlaygroundClick,
|
||||
}: DropdownComponentProps) => {
|
||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
|
||||
const { handleDuplicate } = useDuplicateFlows(
|
||||
[flowData.id],
|
||||
[flowData],
|
||||
() => {},
|
||||
setSuccessData,
|
||||
() => {},
|
||||
() => {},
|
||||
"flow",
|
||||
);
|
||||
|
||||
const handleExport = () => {
|
||||
downloadFlow(flowData, flowData.name, flowData.description);
|
||||
setSuccessData({ title: `${flowData.name} exported successfully` });
|
||||
};
|
||||
|
||||
const { handleSelectOptionsChange } = useSelectOptionsChange(
|
||||
[flowData.id],
|
||||
setErrorData,
|
||||
setOpenDelete,
|
||||
handleDuplicate,
|
||||
handleExport,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* <DropdownMenuItem onClick={() => {}} className="cursor-pointer">
|
||||
<ForwardedIconComponent
|
||||
name="square-pen"
|
||||
aria-hidden="true"
|
||||
className="mr-2 h-4 w-4"
|
||||
/>
|
||||
Edit details
|
||||
</DropdownMenuItem> */}
|
||||
{handlePlaygroundClick && (
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handlePlaygroundClick();
|
||||
}}
|
||||
className="cursor-pointer sm:hidden"
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
name="play"
|
||||
aria-hidden="true"
|
||||
className="mr-2 h-4 w-4"
|
||||
/>
|
||||
Playground
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleSelectOptionsChange("export");
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
data-testid="btn-download-json"
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
name="download"
|
||||
aria-hidden="true"
|
||||
className="mr-2 h-4 w-4"
|
||||
/>
|
||||
Download JSON
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleSelectOptionsChange("duplicate");
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
data-testid="btn-duplicate-flow"
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
name="copy-plus"
|
||||
aria-hidden="true"
|
||||
className="mr-2 h-4 w-4"
|
||||
/>
|
||||
Duplicate
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setOpenDelete(true);
|
||||
}}
|
||||
className="cursor-pointer text-red-500 focus:text-red-500 dark:text-red-500 dark:focus:text-red-500"
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
name="trash"
|
||||
aria-hidden="true"
|
||||
className="mr-2 h-4 w-4"
|
||||
/>
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DropdownComponent;
|
||||
203
src/frontend/src/pages/MainPage/components/grid/index.tsx
Normal file
203
src/frontend/src/pages/MainPage/components/grid/index.tsx
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
import ForwardedIconComponent from "@/components/genericIconComponent";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
|
||||
import { track } from "@/customization/utils/analytics";
|
||||
import useDeleteFlow from "@/hooks/flows/use-delete-flow";
|
||||
import DeleteConfirmationModal from "@/modals/deleteConfirmationModal";
|
||||
import IOModal from "@/modals/IOModal";
|
||||
import useAlertStore from "@/stores/alertStore";
|
||||
import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
||||
import { FlowType } from "@/types/flow";
|
||||
import { getInputsAndOutputs } from "@/utils/storeUtils";
|
||||
import { useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import useDescriptionModal from "../../oldComponents/componentsComponent/hooks/use-description-modal";
|
||||
import { getTemplateStyle } from "../../utils/get-template-style";
|
||||
import { timeElapsed } from "../../utils/time-elapse";
|
||||
import DropdownComponent from "../dropdown";
|
||||
|
||||
const GridComponent = ({ flowData }: { flowData: FlowType }) => {
|
||||
const navigate = useCustomNavigate();
|
||||
const [openPlayground, setOpenPlayground] = useState(false);
|
||||
const [loadingPlayground, setLoadingPlayground] = useState(false);
|
||||
const [openDelete, setOpenDelete] = useState(false);
|
||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||
const { deleteFlow } = useDeleteFlow();
|
||||
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
|
||||
const { folderId } = useParams();
|
||||
const isComponent = flowData.is_component ?? false;
|
||||
const setFlowToCanvas = useFlowsManagerStore(
|
||||
(state) => state.setFlowToCanvas,
|
||||
);
|
||||
|
||||
const { icon, icon_bg_color } = getTemplateStyle(flowData);
|
||||
|
||||
const editFlowLink = `/flow/${flowData.id}${folderId ? `/folder/${folderId}` : ""}`;
|
||||
|
||||
function hasPlayground(flow?: FlowType) {
|
||||
if (!flow) {
|
||||
return false;
|
||||
}
|
||||
const { inputs, outputs } = getInputsAndOutputs(flow?.data?.nodes ?? []);
|
||||
return inputs.length > 0 || outputs.length > 0;
|
||||
}
|
||||
|
||||
const handlePlaygroundClick = () => {
|
||||
track("Playground Button Clicked", { flowId: flowData.id });
|
||||
setLoadingPlayground(true);
|
||||
|
||||
if (flowData) {
|
||||
if (!hasPlayground(flowData)) {
|
||||
setErrorData({
|
||||
title: "Error",
|
||||
list: ["This flow doesn't have a playground."],
|
||||
});
|
||||
setLoadingPlayground(false);
|
||||
return;
|
||||
}
|
||||
setCurrentFlow(flowData);
|
||||
setOpenPlayground(true);
|
||||
setLoadingPlayground(false);
|
||||
} else {
|
||||
setErrorData({
|
||||
title: "Error",
|
||||
list: ["Error getting flow data."],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleClick = async () => {
|
||||
if (!isComponent) {
|
||||
await setFlowToCanvas(flowData);
|
||||
navigate(editFlowLink);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
deleteFlow({ id: [flowData.id] })
|
||||
.then(() => {
|
||||
setSuccessData({
|
||||
title: "Selected items deleted successfully",
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
setErrorData({
|
||||
title: "Error deleting items",
|
||||
list: ["Please try again"],
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const descriptionModal = useDescriptionModal([flowData?.id], "flow");
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
key={flowData.id}
|
||||
onClick={handleClick}
|
||||
className={`my-1 flex flex-col rounded-lg border border-zinc-100 p-5 shadow-sm hover:border-border dark:border-zinc-800 dark:hover:border-muted-foreground ${
|
||||
isComponent ? "cursor-default" : "cursor-pointer"
|
||||
}`}
|
||||
>
|
||||
<div className="flex w-full items-center gap-2">
|
||||
<div
|
||||
className={`mr-3 flex rounded-lg border ${flowData?.icon_bg_color || icon_bg_color} p-3`}
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
name={flowData?.icon || icon}
|
||||
aria-hidden="true"
|
||||
className="h-5 w-5 dark:text-black"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex w-full min-w-0 items-center justify-between">
|
||||
<div className="flex min-w-0 flex-col">
|
||||
<div className="text-md truncate font-semibold">
|
||||
{flowData.name}
|
||||
</div>
|
||||
<div className="truncate text-xs text-zinc-500 dark:text-zinc-400">
|
||||
Edited {timeElapsed(flowData.updated_at)} ago
|
||||
</div>
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
data-testid="home-dropdown-menu"
|
||||
size="icon"
|
||||
className="group ml-2 h-10 w-10 border-none dark:hover:bg-zinc-700"
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
name="ellipsis"
|
||||
aria-hidden="true"
|
||||
className="h-5 w-5 dark:text-zinc-400 dark:group-hover:text-white"
|
||||
/>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="mr-[30px] w-[185px] bg-white dark:bg-black"
|
||||
sideOffset={5}
|
||||
side="bottom"
|
||||
>
|
||||
<DropdownComponent
|
||||
flowData={flowData}
|
||||
setOpenDelete={setOpenDelete}
|
||||
/>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="line-clamp-2 h-full pt-5 text-sm text-zinc-800 dark:text-white">
|
||||
{flowData.description}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end pt-[24px]">
|
||||
{flowData.is_component ? (
|
||||
<></>
|
||||
) : (
|
||||
<Button
|
||||
disabled={loadingPlayground || !hasPlayground(flowData)}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handlePlaygroundClick();
|
||||
}}
|
||||
variant="outline"
|
||||
>
|
||||
Playground
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{openPlayground && (
|
||||
<IOModal
|
||||
key={flowData.id}
|
||||
cleanOnClose={true}
|
||||
open={openPlayground}
|
||||
setOpen={setOpenPlayground}
|
||||
>
|
||||
<></>
|
||||
</IOModal>
|
||||
)}
|
||||
{openDelete && (
|
||||
<DeleteConfirmationModal
|
||||
open={openDelete}
|
||||
setOpen={setOpenDelete}
|
||||
onConfirm={handleDelete}
|
||||
description={descriptionModal}
|
||||
>
|
||||
<></>
|
||||
</DeleteConfirmationModal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default GridComponent;
|
||||
160
src/frontend/src/pages/MainPage/components/header/index.tsx
Normal file
160
src/frontend/src/pages/MainPage/components/header/index.tsx
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
import ForwardedIconComponent from "@/components/genericIconComponent";
|
||||
import ShadTooltip from "@/components/shadTooltipComponent";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
|
||||
import { useFolderStore } from "@/stores/foldersStore";
|
||||
import { debounce } from "lodash";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
interface HeaderComponentProps {
|
||||
flowType: "flows" | "components";
|
||||
setFlowType: (flowType: "flows" | "components") => void;
|
||||
view: "list" | "grid";
|
||||
setView: (view: "list" | "grid") => void;
|
||||
setNewProjectModal: (newProjectModal: boolean) => void;
|
||||
folderName?: string;
|
||||
setSearch: (search: string) => void;
|
||||
}
|
||||
|
||||
const HeaderComponent = ({
|
||||
folderName = "",
|
||||
flowType,
|
||||
setFlowType,
|
||||
view,
|
||||
setView,
|
||||
setNewProjectModal,
|
||||
setSearch,
|
||||
}: HeaderComponentProps) => {
|
||||
const navigate = useCustomNavigate();
|
||||
const [debouncedSearch, setDebouncedSearch] = useState("");
|
||||
const { showFolderModal, setShowFolderModal } = useFolderStore();
|
||||
|
||||
// Debounce the setSearch function from the parent
|
||||
const debouncedSetSearch = useCallback(
|
||||
debounce((value: string) => {
|
||||
setSearch(value);
|
||||
}, 1000),
|
||||
[setSearch],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
debouncedSetSearch(debouncedSearch);
|
||||
|
||||
return () => {
|
||||
debouncedSetSearch.cancel(); // Cleanup on unmount
|
||||
};
|
||||
}, [debouncedSearch, debouncedSetSearch]);
|
||||
|
||||
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setDebouncedSearch(e.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="flex items-center pb-8 text-xl font-semibold"
|
||||
data-testid="mainpage_title"
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="mr-2 lg:hidden"
|
||||
size="icon"
|
||||
onClick={() => setShowFolderModal(!showFolderModal)}
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
name={showFolderModal ? "panel-right-open" : "panel-right-close"}
|
||||
aria-hidden="true"
|
||||
className="h-5 w-5 text-zinc-500 dark:text-zinc-400"
|
||||
/>
|
||||
</Button>
|
||||
{folderName}
|
||||
</div>
|
||||
<div className="flex flex-row-reverse pb-8">
|
||||
<div className="w-full border-b dark:border-border" />
|
||||
{["components", "flows"].map((type) => (
|
||||
<Button
|
||||
key={type}
|
||||
unstyled
|
||||
id={`${type}-btn`}
|
||||
onClick={() => setFlowType(type as "flows" | "components")}
|
||||
className={`border-b ${
|
||||
flowType === type
|
||||
? "border-b-2 border-black font-semibold dark:border-white dark:text-white"
|
||||
: "border-border text-zinc-400 hover:text-black dark:hover:text-white"
|
||||
} px-3 pb-2`}
|
||||
>
|
||||
{type.charAt(0).toUpperCase() + type.slice(1)}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
{/* Search and filters */}
|
||||
<div className="flex justify-between">
|
||||
<div className="flex w-full xl:w-5/12">
|
||||
<Input
|
||||
icon="search"
|
||||
data-testid="search-store-input"
|
||||
type="text"
|
||||
placeholder={`Search ${flowType}...`}
|
||||
className="mr-2"
|
||||
value={debouncedSearch}
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
<div className="px-py mr-2 flex rounded-lg border border-zinc-100 bg-zinc-100 dark:border-zinc-800 dark:bg-zinc-800">
|
||||
{["list", "grid"].map((viewType) => (
|
||||
<Button
|
||||
key={viewType}
|
||||
unstyled
|
||||
size="icon"
|
||||
className={`group mx-[2px] my-[2px] rounded-lg p-2 ${
|
||||
view === viewType
|
||||
? "bg-white text-black shadow-md dark:bg-black dark:text-white"
|
||||
: "bg-zinc-100 text-zinc-500 dark:bg-zinc-800 dark:hover:bg-zinc-800"
|
||||
}`}
|
||||
onClick={() => setView(viewType as "list" | "grid")}
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
name={viewType === "list" ? "menu" : "layout-grid"}
|
||||
aria-hidden="true"
|
||||
className="h-4 w-4 group-hover:text-black dark:group-hover:text-white"
|
||||
/>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<ShadTooltip content="Store" side="bottom">
|
||||
<Button variant="outline" onClick={() => navigate("/store")}>
|
||||
<ForwardedIconComponent
|
||||
name="store"
|
||||
aria-hidden="true"
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
<span className="hidden whitespace-nowrap font-semibold md:inline">
|
||||
Browse Store
|
||||
</span>
|
||||
</Button>
|
||||
</ShadTooltip>
|
||||
<ShadTooltip content="New Flow" side="bottom">
|
||||
<Button
|
||||
variant="default"
|
||||
onClick={() => setNewProjectModal(true)}
|
||||
id="new-project-btn"
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
name="plus"
|
||||
aria-hidden="true"
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
<span className="hidden whitespace-nowrap font-semibold md:inline">
|
||||
New Flow
|
||||
</span>
|
||||
</Button>
|
||||
</ShadTooltip>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default HeaderComponent;
|
||||
213
src/frontend/src/pages/MainPage/components/list/index.tsx
Normal file
213
src/frontend/src/pages/MainPage/components/list/index.tsx
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
import ForwardedIconComponent from "@/components/genericIconComponent";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
|
||||
import { track } from "@/customization/utils/analytics";
|
||||
import useDeleteFlow from "@/hooks/flows/use-delete-flow";
|
||||
import DeleteConfirmationModal from "@/modals/deleteConfirmationModal";
|
||||
import IOModal from "@/modals/IOModal";
|
||||
import useAlertStore from "@/stores/alertStore";
|
||||
import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
||||
import { FlowType } from "@/types/flow";
|
||||
import { getInputsAndOutputs } from "@/utils/storeUtils";
|
||||
import { useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import useDescriptionModal from "../../oldComponents/componentsComponent/hooks/use-description-modal";
|
||||
import { getTemplateStyle } from "../../utils/get-template-style";
|
||||
import { timeElapsed } from "../../utils/time-elapse";
|
||||
import DropdownComponent from "../dropdown";
|
||||
|
||||
const ListComponent = ({ flowData }: { flowData: FlowType }) => {
|
||||
const navigate = useCustomNavigate();
|
||||
const [openPlayground, setOpenPlayground] = useState(false);
|
||||
const [loadingPlayground, setLoadingPlayground] = useState(false);
|
||||
const [openDelete, setOpenDelete] = useState(false);
|
||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||
const { deleteFlow } = useDeleteFlow();
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
|
||||
const { folderId } = useParams();
|
||||
const isComponent = flowData.is_component ?? false;
|
||||
const setFlowToCanvas = useFlowsManagerStore(
|
||||
(state) => state.setFlowToCanvas,
|
||||
);
|
||||
const { icon, icon_bg_color } = getTemplateStyle(flowData);
|
||||
|
||||
const editFlowLink = `/flow/${flowData.id}${folderId ? `/folder/${folderId}` : ""}`;
|
||||
|
||||
function hasPlayground(flow?: FlowType) {
|
||||
if (!flow) {
|
||||
return false;
|
||||
}
|
||||
const { inputs, outputs } = getInputsAndOutputs(flow?.data?.nodes ?? []);
|
||||
return inputs.length > 0 || outputs.length > 0;
|
||||
}
|
||||
|
||||
const handlePlaygroundClick = () => {
|
||||
track("Playground Button Clicked", { flowId: flowData.id });
|
||||
setLoadingPlayground(true);
|
||||
|
||||
if (flowData) {
|
||||
if (!hasPlayground(flowData)) {
|
||||
setErrorData({
|
||||
title: "Error",
|
||||
list: ["This flow doesn't have a playground."],
|
||||
});
|
||||
setLoadingPlayground(false);
|
||||
return;
|
||||
}
|
||||
setCurrentFlow(flowData);
|
||||
setOpenPlayground(true);
|
||||
setLoadingPlayground(false);
|
||||
} else {
|
||||
setErrorData({
|
||||
title: "Error",
|
||||
list: ["Error getting flow data."],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleClick = async () => {
|
||||
if (!isComponent) {
|
||||
await setFlowToCanvas(flowData);
|
||||
navigate(editFlowLink);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
deleteFlow({ id: [flowData.id] })
|
||||
.then(() => {
|
||||
setSuccessData({
|
||||
title: "Selected items deleted successfully",
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
setErrorData({
|
||||
title: "Error deleting items",
|
||||
list: ["Please try again"],
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const descriptionModal = useDescriptionModal([flowData?.id], "flow");
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
key={flowData.id}
|
||||
onClick={handleClick}
|
||||
className={`my-2 flex h-[110px] ${
|
||||
isComponent ? "cursor-default" : "cursor-pointer"
|
||||
} justify-between rounded-lg border border-zinc-100 p-5 shadow-sm hover:border-border dark:border-zinc-800 dark:hover:border-muted-foreground`}
|
||||
>
|
||||
{/* left side */}
|
||||
<div
|
||||
className={`flex min-w-0 ${
|
||||
isComponent ? "cursor-default" : "cursor-pointer"
|
||||
} items-center gap-2`}
|
||||
>
|
||||
{/* Icon */}
|
||||
<div
|
||||
className={`item-center mr-3 flex justify-center rounded-lg border ${flowData?.icon_bg_color || icon_bg_color} p-3`}
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
name={flowData?.icon || icon}
|
||||
aria-hidden="true"
|
||||
className="flex h-5 w-5 items-center justify-center dark:text-black"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex min-w-0 flex-col justify-start">
|
||||
<div className="line-clamp-1 flex min-w-0 items-baseline truncate max-md:flex-col">
|
||||
<div className="text-md flex truncate pr-2 font-semibold max-md:w-full">
|
||||
<span className="truncate">{flowData.name}</span>
|
||||
</div>
|
||||
<div className="item-baseline flex text-xs text-zinc-500 dark:text-zinc-400">
|
||||
Edited {timeElapsed(flowData.updated_at)} ago
|
||||
</div>
|
||||
</div>
|
||||
<div className="line-clamp-2 flex text-sm text-zinc-800 truncate-doubleline dark:text-white">
|
||||
{flowData.description}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* right side */}
|
||||
<div className="ml-5 flex items-center gap-2">
|
||||
{flowData.is_component ? (
|
||||
<></>
|
||||
) : (
|
||||
<Button
|
||||
variant="outline"
|
||||
disabled={loadingPlayground || !hasPlayground(flowData)}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handlePlaygroundClick();
|
||||
}}
|
||||
className="hidden sm:block"
|
||||
>
|
||||
Playground
|
||||
</Button>
|
||||
)}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
data-testid="home-dropdown-menu"
|
||||
className="group h-10 w-10 border-none dark:hover:bg-zinc-700"
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
name="ellipsis"
|
||||
aria-hidden="true"
|
||||
className="h-5 w-5 dark:text-zinc-400 dark:group-hover:text-white"
|
||||
/>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="mr-[30px] w-[185px] bg-white dark:bg-black"
|
||||
sideOffset={5}
|
||||
side="bottom"
|
||||
>
|
||||
<DropdownComponent
|
||||
flowData={flowData}
|
||||
setOpenDelete={setOpenDelete}
|
||||
handlePlaygroundClick={() => {
|
||||
handlePlaygroundClick();
|
||||
}}
|
||||
/>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
{openPlayground && (
|
||||
<IOModal
|
||||
key={flowData.id}
|
||||
cleanOnClose={true}
|
||||
open={openPlayground}
|
||||
setOpen={setOpenPlayground}
|
||||
>
|
||||
<></>
|
||||
</IOModal>
|
||||
)}
|
||||
{openDelete && (
|
||||
<DeleteConfirmationModal
|
||||
open={openDelete}
|
||||
setOpen={setOpenDelete}
|
||||
onConfirm={handleDelete}
|
||||
description={descriptionModal}
|
||||
>
|
||||
<></>
|
||||
</DeleteConfirmationModal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ListComponent;
|
||||
54
src/frontend/src/pages/MainPage/constants.ts
Normal file
54
src/frontend/src/pages/MainPage/constants.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
export const TEMPLATES_DATA = {
|
||||
examples: [
|
||||
{
|
||||
name: "Basic Prompting (Hello, World)",
|
||||
icon: "BotMessageSquare",
|
||||
icon_bg_color: "bg-blue-500",
|
||||
},
|
||||
{
|
||||
name: "Memory Chatbot",
|
||||
icon: "MessagesSquare",
|
||||
icon_bg_color: "bg-purple-500",
|
||||
},
|
||||
{
|
||||
name: "Vector Store RAG",
|
||||
icon: "Database",
|
||||
icon_bg_color: "bg-green-500",
|
||||
},
|
||||
{
|
||||
name: "Travel Planning Agents",
|
||||
icon: "Plane",
|
||||
icon_bg_color: "bg-yellow-500",
|
||||
},
|
||||
{
|
||||
name: "Dynamic Agent",
|
||||
icon: "Users",
|
||||
icon_bg_color: "bg-red-500",
|
||||
},
|
||||
{
|
||||
name: "Blog Writer",
|
||||
icon: "FileText",
|
||||
icon_bg_color: "bg-indigo-500",
|
||||
},
|
||||
{
|
||||
name: "Sequential Tasks Agent",
|
||||
icon: "ListOrdered",
|
||||
icon_bg_color: "bg-pink-500",
|
||||
},
|
||||
{
|
||||
name: "Hierarchical Tasks Agent",
|
||||
icon: "GitFork",
|
||||
icon_bg_color: "bg-orange-500",
|
||||
},
|
||||
{
|
||||
name: "Simple Agent",
|
||||
icon: "Users",
|
||||
icon_bg_color: "bg-teal-500",
|
||||
},
|
||||
{
|
||||
name: "Document QA",
|
||||
icon: "FileText",
|
||||
icon_bg_color: "bg-cyan-500",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
|
||||
import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
||||
import { useParams } from "react-router-dom";
|
||||
import CollectionCardComponent from "../../../../../../components/cardComponent";
|
||||
const CollectionCard = ({ item, type, isLoading, control }) => {
|
||||
const navigate = useCustomNavigate();
|
||||
const isComponent = item.is_component ?? false;
|
||||
const editFlowButtonTestId = `edit-flow-button-${item.id}`;
|
||||
|
||||
const { folderId } = useParams();
|
||||
|
||||
const editFlowLink = `/flow/${item.id}${folderId ? `/folder/${folderId}` : ""}`;
|
||||
|
||||
const setFlowToCanvas = useFlowsManagerStore(
|
||||
(state) => state.setFlowToCanvas,
|
||||
);
|
||||
|
||||
const handleClick = async () => {
|
||||
if (!isComponent) {
|
||||
await setFlowToCanvas(item);
|
||||
navigate(editFlowLink);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<CollectionCardComponent
|
||||
data={{
|
||||
is_component: isComponent,
|
||||
...item,
|
||||
}}
|
||||
disabled={isLoading}
|
||||
data-testid={editFlowButtonTestId}
|
||||
onClick={!isComponent ? handleClick : undefined}
|
||||
control={control}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default CollectionCard;
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import { useMemo } from "react";
|
||||
|
||||
const useDescriptionModal = (
|
||||
selectedFlowsComponentsCards: string[] | undefined,
|
||||
type: string | undefined,
|
||||
) => {
|
||||
const getDescriptionModal = useMemo(() => {
|
||||
const getTypeLabel = (type) => {
|
||||
const labels = {
|
||||
all: "item",
|
||||
component: "component",
|
||||
flow: "flow",
|
||||
};
|
||||
return labels[type] || "";
|
||||
};
|
||||
|
||||
const getPluralizedLabel = (type) => {
|
||||
const labels = {
|
||||
all: "items",
|
||||
component: "components",
|
||||
flow: "flows",
|
||||
};
|
||||
return labels[type] || "";
|
||||
};
|
||||
|
||||
if (selectedFlowsComponentsCards?.length === 1) {
|
||||
return getTypeLabel(type);
|
||||
}
|
||||
return getPluralizedLabel(type);
|
||||
}, [selectedFlowsComponentsCards, type]);
|
||||
|
||||
return getDescriptionModal;
|
||||
};
|
||||
|
||||
export default useDescriptionModal;
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue