fix: refactor loading of main page, use new Sidebar in main page, fix small UI bugs (#4451)

* Tighten space between main page sidebar buttons

* Fixed skeleton size

* Added playground button back

* Updated no components and flows state

* Update default icon to Workflow

* Fixed size of list cards

* Removed browse store

* Removed playground button

* Removed black background from empty folder state

* Update empty state color

* Fix color of empty state

* fix text not selectable

* updated border color

* added shadow only on hover

* Remove JSON from Download JSON

* Fixed colors and weight of tabs on home page

* Fixed padding on list and grid components

* Update icons that take long to load

* Fixed icon and bg color for home tiles

* Removed unused code

* removed placeholder data for skeleton to not appear on first load

* Make onSuccess refetch the queries so that the loading waits for it

* Removed unused divs on foldersidebarnav

* Refactor sidebar buttons to use new shadcn sidebar

* Created skeletons for folder, grid and list

* Added new sidebar size

* Use new sidebar button on header and implemented animation

* Changed icon to getIcon

* Added sidebar provider and fixed loading states of the main page

* Removed folder buttons on emptyPage

* Fixed foldername to appear immediatly, and fixed loading states for the folders

* Removed unused state from folders store

* Removed unused states from folders store type

* Added new icon

* fixed modals component to not show a trash icon

* Changed icons to load immediatly

* Added empty folder condition to not display header info

* Added conditions to show loading state until everything loads

* Created empty folder state

* Changed empty page to correct colors

* Added skeletons while flows of the folder are loading

* Removed shadow from text

* Fixed font chivo taking long time to load

* Fix adding new folder not redirecting

* [autofix.ci] apply automated fixes

* Fixed colors and paddings on list and grid components

* Re added tooltips to upload and create folders

* fix input for name editing

* Fix tests

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Lucas Oliveira 2024-11-08 14:33:52 -03:00 committed by GitHub
commit a09652ca5f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 697 additions and 631 deletions

View file

@ -9,11 +9,7 @@
<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"
href="https://fonts.googleapis.com/css2?family=Chivo:ital,wght@0,100..900;1,100..900&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"
/>
<script

View file

@ -5,6 +5,17 @@ import {
SelectItem,
SelectTrigger,
} from "@/components/ui/select-custom";
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarTrigger,
} from "@/components/ui/sidebar";
import {
usePatchFolders,
usePostFolders,
@ -18,7 +29,7 @@ import { getObjectsFromFilelist } from "@/helpers/get-objects-from-filelist";
import useUploadFlow from "@/hooks/flows/use-upload-flow";
import { useIsFetching, useIsMutating } from "@tanstack/react-query";
import { useEffect, useRef, useState } from "react";
import { useParams } from "react-router-dom";
import { useLocation, useParams } from "react-router-dom";
import { FolderType } from "../../../../pages/MainPage/entities";
import useAlertStore from "../../../../stores/alertStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
@ -26,25 +37,28 @@ import { useFolderStore } from "../../../../stores/foldersStore";
import { handleKeyDown } from "../../../../utils/reactflowUtils";
import { cn } from "../../../../utils/utils";
import IconComponent from "../../../genericIconComponent";
import { Button, buttonVariants } from "../../../ui/button";
import { Button } from "../../../ui/button";
import { Input } from "../../../ui/input";
import useFileDrop from "../../hooks/use-on-file-drop";
import { SidebarFolderSkeleton } from "../sidebarFolderSkeleton";
type SideBarFoldersButtonsComponentProps = {
pathname: string;
handleChangeFolder?: (id: string) => void;
handleDeleteFolder?: (item: FolderType) => void;
folders: FolderType[] | undefined;
loading?: boolean;
};
const SideBarFoldersButtonsComponent = ({
pathname,
handleChangeFolder,
handleDeleteFolder,
folders = [],
loading,
}: SideBarFoldersButtonsComponentProps) => {
const location = useLocation();
const pathname = location.pathname;
const folders = useFolderStore((state) => state.folders);
const isFetchingFolders = !!useIsFetching({
queryKey: ["useGetFolders"],
exact: false,
});
const loading = !folders;
const refInput = useRef<HTMLInputElement>(null);
const [foldersNames, setFoldersNames] = useState({});
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
@ -56,10 +70,6 @@ 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) {
@ -152,14 +162,21 @@ const SideBarFoldersButtonsComponent = ({
const { mutate: mutateUpdateFolder } = usePatchFolders();
function addNewFolder() {
mutateAddFolder({
data: {
name: "New Folder",
parent_id: null,
description: "",
mutateAddFolder(
{
data: {
name: "New Folder",
parent_id: null,
description: "",
},
},
});
track("Create New Folder");
{
onSuccess: (folder) => {
track("Create New Folder");
handleChangeFolder!(folder.id);
},
},
);
}
function handleEditFolderName(e, name): void {
@ -232,11 +249,6 @@ const SideBarFoldersButtonsComponent = ({
}
};
const isFetchingFolders = !!useIsFetching({
queryKey: ["useGetFolders"],
exact: false,
});
const isFetchingFolder = !!useIsFetching({
queryKey: ["useGetFolder"],
exact: false,
@ -254,27 +266,28 @@ const SideBarFoldersButtonsComponent = ({
isDeletingFolder;
const HeaderButtons = () => (
<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 shrink-0 items-center justify-between gap-2">
<SidebarTrigger className="lg:hidden">
<IconComponent name="PanelLeftClose" className="h-4 w-4" />
</SidebarTrigger>
<div className="flex-1 text-sm font-semibold">Folders</div>
<UploadFolderButton
onClick={handleUploadFlowsToFolder}
disabled={isUpdatingFolder}
/>
<AddFolderButton onClick={addNewFolder} disabled={isUpdatingFolder} />
<div className="flex items-center gap-1">
<UploadFolderButton
onClick={handleUploadFlowsToFolder}
disabled={isUpdatingFolder}
/>
<AddFolderButton
onClick={addNewFolder}
disabled={isUpdatingFolder}
loading={isPending}
/>
</div>
</div>
);
const AddFolderButton = ({ onClick, disabled }) => (
<ShadTooltip content="Create new folder" styleClasses="z-10">
const AddFolderButton = ({ onClick, disabled, loading }) => (
<ShadTooltip content="Create new folder" styleClasses="z-50">
<Button
variant="ghost"
size="icon"
@ -282,6 +295,7 @@ const SideBarFoldersButtonsComponent = ({
onClick={onClick}
data-testid="add-folder-button"
disabled={disabled}
loading={loading}
>
<IconComponent name="Plus" className="h-4 w-4" />
</Button>
@ -289,12 +303,11 @@ const SideBarFoldersButtonsComponent = ({
);
const UploadFolderButton = ({ onClick, disabled }) => (
/* Todo: change this back to being a folder upload */
<ShadTooltip content="Upload a flow" styleClasses="z-10">
<ShadTooltip content="Upload a flow" styleClasses="z-50">
<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"
size="icon"
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="upload-folder-button"
disabled={disabled}
@ -389,153 +402,158 @@ const SideBarFoldersButtonsComponent = ({
};
return (
<>
<HeaderButtons />
<Sidebar collapsible="offcanvas" data-testid="folder-sidebar">
<SidebarHeader className="p-4">
<HeaderButtons />
</SidebarHeader>
<SidebarContent>
<SidebarGroup className="p-4 py-2">
<SidebarGroupContent>
<SidebarMenu>
{!loading ? (
folders.map((item, index) => {
const editFolderName = editFolders?.filter(
(folder) => folder.name === item.name,
)[0];
return (
<SidebarMenuItem>
<SidebarMenuButton
size="md"
onDragOver={(e) => dragOver(e, item.id!)}
onDragEnter={(e) => dragEnter(e, item.id!)}
onDragLeave={dragLeave}
onDrop={(e) => onDrop(e, item.id!)}
key={item.id}
data-testid={`sidebar-nav-${item.name}`}
isActive={checkPathName(item.id!)}
onClick={() => handleChangeFolder!(item.id!)}
className="group/menu-button"
>
<div
onDoubleClick={(event) => {
handleDoubleClick(event, item);
}}
className="flex w-full items-center justify-between gap-2"
>
<div className="flex flex-1 items-center gap-2">
{editFolderName?.edit && !isUpdatingFolder ? (
<Input
className="h-6 flex-1 focus:border-0"
onChange={(e) => {
handleEditFolderName(e, item.name);
}}
ref={refInput}
onKeyDown={(e) => {
handleKeyDownFn(e, item);
handleKeyDown(e, e.key, "");
}}
autoFocus={true}
onBlur={(e) => {
// fixes autofocus problem where cursor isn't present
if (
e.relatedTarget?.id ===
`options-trigger-${item.name}`
) {
refInput.current?.focus();
return;
}
<div className="flex h-[70vh] flex-col gap-2 overflow-auto">
<>
{!loading ? (
folders.map((item, index) => {
const editFolderName = editFolders?.filter(
(folder) => folder.name === item.name,
)[0];
return (
<div
onDragOver={(e) => dragOver(e, item.id!)}
onDragEnter={(e) => dragEnter(e, item.id!)}
onDragLeave={dragLeave}
onDrop={(e) => onDrop(e, item.id!)}
key={item.id}
data-testid={`sidebar-nav-${item.name}`}
className={cn(
buttonVariants({ variant: "ghost" }),
checkPathName(item.id!)
? "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" : "",
)}
onClick={() => handleChangeFolder!(item.id!)}
>
<div
onDoubleClick={(event) => {
handleDoubleClick(event, item);
}}
className="flex w-full items-center justify-between"
>
<div className="flex items-center gap-2">
{editFolderName?.edit && !isUpdatingFolder ? (
<div>
<Input
className="w-36"
onChange={(e) => {
handleEditFolderName(e, item.name);
}}
ref={refInput}
onKeyDown={(e) => {
handleKeyDownFn(e, item);
handleKeyDown(e, e.key, "");
}}
autoFocus={true}
onBlur={(e) => {
// fixes autofocus problem where cursor isn't present
if (
e.relatedTarget?.id ===
`options-trigger-${item.name}`
) {
refInput.current?.focus();
return;
}
if (refInput.current?.value !== item.name) {
handleEditNameFolder(item);
} else {
editFolderName.edit = false;
}
refInput.current?.blur();
}}
value={foldersNames[item.name]}
id={`input-folder-${item.name}`}
data-testid={`input-folder`}
/>
if (refInput.current?.value !== item.name) {
handleEditNameFolder(item);
} else {
editFolderName.edit = false;
}
refInput.current?.blur();
}}
value={foldersNames[item.name]}
id={`input-folder-${item.name}`}
data-testid={`input-folder`}
/>
) : (
<span className="block w-full grow truncate text-[13px] opacity-100">
{item.name}
</span>
)}
</div>
<Select
onValueChange={(value) =>
handleSelectChange(value, item)
}
value=""
>
<ShadTooltip
content="Options"
side="right"
styleClasses="z-50"
>
<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-muted-foreground group-hover/menu-button:block group-hover/menu-button:text-foreground ${
checkPathName(item.id!) ? "block" : "hidden"
}`}
/>
</SelectTrigger>
</ShadTooltip>
<SelectContent
align="end"
alignOffset={-16}
position="popper"
>
{item.name !== "My Projects" && (
<SelectItem
id="rename-button"
value="rename"
data-testid="btn-rename-folder"
>
<FolderSelectItem
name="Rename"
iconName="SquarePen"
/>
</SelectItem>
)}
<SelectItem
value="download"
data-testid="btn-download-folder"
>
<FolderSelectItem
name="Download Content"
iconName="Download"
/>
</SelectItem>
{index > 0 && (
<SelectItem
value="delete"
data-testid="btn-delete-folder"
>
<FolderSelectItem
name="Delete"
iconName="Trash2"
/>
</SelectItem>
)}
</SelectContent>
</Select>
</div>
) : (
<span className="block w-full grow truncate text-[13px] opacity-100">
{item.name}
</span>
)}
</div>
<Select
onValueChange={(value) => handleSelectChange(value, item)}
value=""
>
<ShadTooltip
content="Options"
side="right"
styleClasses="z-10"
>
<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}
position="popper"
>
{item.name !== "My Projects" && (
<SelectItem
id="rename-button"
value="rename"
data-testid="btn-rename-folder"
>
<FolderSelectItem
name="Rename"
iconName="square-pen"
/>
</SelectItem>
)}
<SelectItem
value="download"
data-testid="btn-download-folder"
>
<FolderSelectItem
name="Download Content"
iconName="download"
/>
</SelectItem>
{index > 0 && (
<SelectItem
value="delete"
data-testid="btn-delete-folder"
>
<FolderSelectItem name="Delete" iconName="trash" />
</SelectItem>
)}
</SelectContent>
</Select>
</div>
</div>
);
})
) : (
<>
<SidebarFolderSkeleton />
<SidebarFolderSkeleton />
</>
)}
</>
</div>
</>
</SidebarMenuButton>
</SidebarMenuItem>
);
})
) : (
<>
<SidebarFolderSkeleton />
<SidebarFolderSkeleton />
</>
)}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
</Sidebar>
);
};
export default SideBarFoldersButtonsComponent;

View file

@ -2,8 +2,7 @@ import { Skeleton } from "@/components/ui/skeleton";
export function SidebarFolderSkeleton() {
return (
<div className="flex h-10 w-full shrink-0 animate-pulse cursor-pointer items-center gap-4 rounded-md border bg-background px-4 opacity-100 lg:min-w-full">
<Skeleton className="h-3 w-4 rounded-full" />
<div className="flex h-9 w-full shrink-0 animate-pulse cursor-pointer items-center gap-4 rounded-md border bg-background px-2 opacity-100 lg:min-w-full">
<Skeleton className="h-3 w-[40%]" />
</div>
);

View file

@ -1,10 +1,7 @@
import { useGetFoldersQuery } from "@/controllers/API/queries/folders/use-get-folders";
import { useFolderStore } from "@/stores/foldersStore";
import { useIsFetching } from "@tanstack/react-query";
import { useLocation } from "react-router-dom";
import { FolderType } from "../../pages/MainPage/entities";
import { cn } from "../../utils/utils";
import HorizontalScrollFadeComponent from "../horizontalScrollFadeComponent";
import SideBarFoldersButtonsComponent from "./components/sideBarFolderButtons";
type SidebarNavProps = {
@ -29,16 +26,9 @@ export default function FolderSidebarNav({
});
return (
<nav className={cn(className)} {...props}>
<HorizontalScrollFadeComponent>
<SideBarFoldersButtonsComponent
loading={isPending || !folders}
pathname={pathname}
handleChangeFolder={handleChangeFolder}
handleDeleteFolder={handleDeleteFolder}
folders={folders}
/>
</HorizontalScrollFadeComponent>
</nav>
<SideBarFoldersButtonsComponent
handleChangeFolder={handleChangeFolder}
handleDeleteFolder={handleDeleteFolder}
/>
);
}

View file

@ -518,6 +518,7 @@ const sidebarMenuButtonVariants = cva(
},
size: {
default: "h-8 text-sm",
md: "h-9 text-sm",
sm: "h-7 text-xs",
lg: "h-12 text-sm group-data-[collapsible=icon]:!p-0",
},

View file

@ -13,6 +13,7 @@ interface IPostAddFlow {
is_component: boolean;
folder_id: string;
endpoint_name: string | undefined;
icon: string | undefined;
}
export const usePostAddFlow: useMutationFunctionType<
@ -29,6 +30,7 @@ export const usePostAddFlow: useMutationFunctionType<
description: payload.description,
is_component: payload.is_component,
folder_id: payload.folder_id || null,
icon: payload.icon || null,
endpoint_name: payload.endpoint_name || null,
});

View file

@ -72,7 +72,6 @@ export const useGetFolderQuery: useQueryFunctionType<
() => getFolderFn(params),
{
refetchOnWindowFocus: false,
placeholderData: true,
...options,
},
);

View file

@ -28,8 +28,8 @@ export const usePostFolders: useMutationFunctionType<
const mutation = mutate(["usePostFolders"], addFoldersFn, {
...options,
onSettled: () => {
queryClient.refetchQueries({ queryKey: ["useGetFolders"] });
onSuccess: () => {
return queryClient.refetchQueries({ queryKey: ["useGetFolders"] });
},
});

View file

@ -35,23 +35,10 @@ export default function TemplateCardComponent({
onKeyDown={handleKeyDown}
onClick={onClick}
>
<div
className="relative h-20 w-20 shrink-0 overflow-hidden rounded-md p-4 outline-none ring-ring"
style={{
backgroundImage: bgGradient,
transform: "scale(1)",
transition: "transform 0.3s ease-in-out",
}}
>
<div
className="absolute inset-0 transition-transform duration-300 group-hover:scale-125 group-focus-visible:scale-125"
style={{
backgroundImage: bgGradient,
}}
/>
<div className="relative h-20 w-20 shrink-0 overflow-hidden rounded-md bg-muted p-4 outline-none ring-ring group-hover:bg-border group-focus-visible:bg-border">
<IconComponent
name={example.icon || "FileText"}
className="absolute left-1/2 top-1/2 h-10 w-10 -translate-x-1/2 -translate-y-1/2 text-white duration-300 group-hover:scale-105 group-focus-visible:scale-105"
className="absolute left-1/2 top-1/2 h-10 w-10 -translate-x-1/2 -translate-y-1/2 text-muted-foreground duration-300 group-hover:scale-105 group-hover:text-foreground group-focus-visible:scale-105 group-focus-visible:text-foreground"
/>
</div>
<div className="flex flex-1 flex-col justify-between">

View file

@ -78,11 +78,11 @@ const DropdownComponent = ({
data-testid="btn-download-json"
>
<ForwardedIconComponent
name="download"
name="Download"
aria-hidden="true"
className="mr-2 h-4 w-4"
/>
Download JSON
Download
</DropdownMenuItem>
<DropdownMenuItem
onClick={(e) => {
@ -93,7 +93,7 @@ const DropdownComponent = ({
data-testid="btn-duplicate-flow"
>
<ForwardedIconComponent
name="copy-plus"
name="CopyPlus"
aria-hidden="true"
className="mr-2 h-4 w-4"
/>
@ -104,10 +104,10 @@ const DropdownComponent = ({
e.stopPropagation();
setOpenDelete(true);
}}
className="cursor-pointer text-red-500 focus:text-red-500 dark:text-red-500 dark:focus:text-red-500"
className="cursor-pointer text-destructive"
>
<ForwardedIconComponent
name="trash"
name="Trash2"
aria-hidden="true"
className="mr-2 h-4 w-4"
/>

View file

@ -8,10 +8,8 @@ import {
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";
@ -19,27 +17,27 @@ 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 { useGetTemplateStyle } 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 [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 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 { getIcon } = useGetTemplateStyle(flowData);
const editFlowLink = `/flow/${flowData.id}${folderId ? `/folder/${folderId}` : ""}`;
@ -51,7 +49,7 @@ const GridComponent = ({ flowData }: { flowData: FlowType }) => {
return inputs.length > 0 || outputs.length > 0;
}
const handlePlaygroundClick = () => {
/* const handlePlaygroundClick = () => {
track("Playground Button Clicked", { flowId: flowData.id });
setLoadingPlayground(true);
@ -73,7 +71,7 @@ const GridComponent = ({ flowData }: { flowData: FlowType }) => {
list: ["Error getting flow data."],
});
}
};
}; */
const handleClick = async () => {
if (!isComponent) {
@ -108,18 +106,16 @@ const GridComponent = ({ flowData }: { flowData: FlowType }) => {
draggable
onDragStart={onDragStart}
onClick={handleClick}
className={`my-1 flex flex-col rounded-lg border border-zinc-100 bg-background p-5 shadow-sm hover:border-border dark:border-zinc-800 dark:hover:border-muted-foreground ${
className={`my-1 flex flex-col rounded-lg border border-border bg-background p-4 hover:border-placeholder-foreground hover:shadow-sm ${
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`}
>
<div className="flex w-full items-center gap-4">
<div className={`flex rounded-lg bg-muted p-3`}>
<ForwardedIconComponent
name={flowData?.icon || icon}
name={getIcon()}
aria-hidden="true"
className="h-5 w-5 dark:text-black"
className="h-5 w-5 text-foreground"
/>
</div>
<div className="flex w-full min-w-0 items-center justify-between">
@ -127,27 +123,27 @@ const GridComponent = ({ flowData }: { flowData: FlowType }) => {
<div className="text-md truncate font-semibold">
{flowData.name}
</div>
<div className="truncate text-xs text-zinc-500 dark:text-zinc-400">
<div className="truncate text-xs text-muted-foreground">
Edited {timeElapsed(flowData.updated_at)} ago
</div>
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
variant="ghost"
data-testid="home-dropdown-menu"
size="icon"
className="group ml-2 h-10 w-10 border-none dark:hover:bg-zinc-700"
size="iconMd"
className="group"
>
<ForwardedIconComponent
name="ellipsis"
name="Ellipsis"
aria-hidden="true"
className="h-5 w-5 dark:text-zinc-400 dark:group-hover:text-white"
className="h-5 w-5 text-muted-foreground group-hover:text-foreground"
/>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
className="mr-[30px] w-[185px] bg-white dark:bg-black"
className="w-[185px]"
sideOffset={5}
side="bottom"
>
@ -160,7 +156,7 @@ const GridComponent = ({ flowData }: { flowData: FlowType }) => {
</div>
</div>
<div className="line-clamp-2 h-full pt-5 text-sm text-zinc-800 dark:text-white">
<div className="line-clamp-2 h-full pt-5 text-sm text-primary">
{flowData.description}
</div>

View file

@ -0,0 +1,34 @@
import { Card } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
const GridSkeleton = () => {
return (
<Card className="my-1 flex flex-col rounded-lg border border-border bg-background p-4">
<div className="flex w-full items-center gap-4">
{/* Icon skeleton */}
<div className="flex rounded-lg">
<Skeleton className="h-[44px] w-[44px] rounded-lg" />
</div>
<div className="flex w-full min-w-0 items-center justify-between">
<div className="flex min-w-0 flex-col gap-2">
{/* Title skeleton */}
<Skeleton className="h-5 w-[120px]" />
{/* Time skeleton */}
<Skeleton className="h-4 w-[150px]" />
</div>
{/* Dropdown button skeleton */}
<Skeleton className="ml-2 h-10 w-10 rounded-md" />
</div>
</div>
{/* Description skeleton */}
<div className="pt-5">
<Skeleton className="h-4 w-full" />
<Skeleton className="mt-2 h-4 w-3/4" />
</div>
</Card>
);
};
export default GridSkeleton;

View file

@ -2,8 +2,8 @@ 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 { SidebarTrigger, useSidebar } from "@/components/ui/sidebar";
import { cn } from "@/utils/utils";
import { debounce } from "lodash";
import { useCallback, useEffect, useState } from "react";
@ -15,6 +15,7 @@ interface HeaderComponentProps {
setNewProjectModal: (newProjectModal: boolean) => void;
folderName?: string;
setSearch: (search: string) => void;
isEmptyFolder: boolean;
}
const HeaderComponent = ({
@ -25,10 +26,10 @@ const HeaderComponent = ({
setView,
setNewProjectModal,
setSearch,
isEmptyFolder,
}: HeaderComponentProps) => {
const navigate = useCustomNavigate();
const [debouncedSearch, setDebouncedSearch] = useState("");
const { showFolderModal, setShowFolderModal } = useFolderStore();
const { open } = useSidebar();
// Debounce the setSearch function from the parent
const debouncedSetSearch = useCallback(
@ -56,75 +57,83 @@ const HeaderComponent = ({
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>
<div className={cn("w-10 transition-all lg:hidden", open && "md:w-0")}>
<div
className={cn(
"relative left-0 opacity-100 transition-all",
open ? "md:opacity-0" : "",
)}
>
<SidebarTrigger>
<ForwardedIconComponent
name="PanelLeftOpen"
aria-hidden="true"
className="text-zinc-500 dark:text-zinc-400"
/>
</SidebarTrigger>
</div>
</div>
{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`}
data-testid={`${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) => (
{!isEmptyFolder && (
<>
<div className="flex flex-row-reverse pb-8">
<div className="w-full border-b dark:border-border" />
{["components", "flows"].map((type) => (
<Button
key={viewType}
key={type}
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")}
id={`${type}-btn`}
data-testid={`${type}-btn`}
onClick={() => setFlowType(type as "flows" | "components")}
className={`border-b ${
flowType === type
? "border-b-2 border-foreground text-foreground"
: "border-border text-muted-foreground hover:text-foreground"
} px-3 pb-2 text-sm`}
>
<ForwardedIconComponent
name={viewType === "list" ? "menu" : "layout-grid"}
aria-hidden="true"
className="h-4 w-4 group-hover:text-black dark:group-hover:text-white"
/>
<div className={flowType === type ? "-mb-px" : ""}>
{type.charAt(0).toUpperCase() + type.slice(1)}
</div>
</Button>
))}
</div>
</div>
<div className="flex gap-2">
<ShadTooltip content="Store" side="bottom">
{/* 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" : "LayoutGrid"}
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"
@ -135,25 +144,27 @@ const HeaderComponent = ({
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>
</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>
</>
)}
</>
);
};

View file

@ -5,74 +5,70 @@ import { Card } from "@/components/ui/card";
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 { useGetTemplateStyle } 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 [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 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 { getIcon } = useGetTemplateStyle(flowData);
const editFlowLink = `/flow/${flowData.id}${folderId ? `/folder/${folderId}` : ""}`;
function hasPlayground(flow?: FlowType) {
/* 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);
/* 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."],
// });
// }
// };
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) {
@ -107,24 +103,24 @@ const ListComponent = ({ flowData }: { flowData: FlowType }) => {
draggable
onDragStart={onDragStart}
onClick={handleClick}
className={`my-2 flex h-[110px] flex-row bg-background ${
className={`my-2 flex flex-row bg-background ${
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`}
} group justify-between rounded-lg border border-border p-4 hover:border-placeholder-foreground hover:shadow-sm`}
>
{/* left side */}
<div
className={`flex min-w-0 ${
isComponent ? "cursor-default" : "cursor-pointer"
} items-center gap-2`}
} items-center gap-4`}
>
{/* Icon */}
<div
className={`item-center mr-3 flex justify-center rounded-lg border ${flowData?.icon_bg_color || icon_bg_color} p-3`}
className={`item-center flex justify-center rounded-lg bg-muted p-3`}
>
<ForwardedIconComponent
name={flowData?.icon || icon}
name={flowData?.icon || getIcon()}
aria-hidden="true"
className="flex h-5 w-5 items-center justify-center dark:text-black"
className="flex h-5 w-5 items-center justify-center text-foreground"
/>
</div>
@ -133,11 +129,11 @@ const ListComponent = ({ flowData }: { flowData: FlowType }) => {
<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">
<div className="item-baseline flex text-xs text-muted-foreground">
Edited {timeElapsed(flowData.updated_at)} ago
</div>
</div>
<div className="line-clamp-2 flex text-sm text-zinc-800 truncate-doubleline dark:text-white">
<div className="overflow-hidden truncate text-sm text-primary">
{flowData.description}
</div>
</div>
@ -164,20 +160,20 @@ const ListComponent = ({ flowData }: { flowData: FlowType }) => {
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
size="icon"
variant="ghost"
size="iconMd"
data-testid="home-dropdown-menu"
className="group h-10 w-10 border-none dark:hover:bg-zinc-700"
className="group"
>
<ForwardedIconComponent
name="ellipsis"
name="Ellipsis"
aria-hidden="true"
className="h-5 w-5 dark:text-zinc-400 dark:group-hover:text-white"
className="h-5 w-5 text-muted-foreground group-hover:text-foreground"
/>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
className="mr-[30px] w-[185px] bg-white dark:bg-black"
className="w-[185px]"
sideOffset={5}
side="bottom"
>

View file

@ -0,0 +1,33 @@
import { Card } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
const ListSkeleton = () => {
return (
<Card className="my-2 flex flex-row justify-between rounded-lg border border-border bg-background p-4">
{/* left side */}
<div className="flex min-w-0 items-center gap-4">
{/* Icon skeleton */}
<div className="flex h-[52px] w-[52px] items-center justify-center rounded-lg">
<Skeleton className="h-full w-full rounded-lg" />
</div>
<div className="flex min-w-0 flex-col justify-start gap-2">
{/* Title and time skeleton */}
<div className="flex min-w-0 items-baseline max-md:flex-col">
<Skeleton className="h-5 w-[150px]" />
<Skeleton className="ml-2 h-4 w-[180px]" />
</div>
{/* Description skeleton */}
<Skeleton className="h-4 w-[250px]" />
</div>
</div>
{/* right side */}
<div className="ml-5 flex items-center gap-2">
<Skeleton className="h-10 w-10 rounded-md" />
</div>
</Card>
);
};
export default ListSkeleton;

View file

@ -0,0 +1,41 @@
// Modals.tsx
import TemplatesModal from "@/modals/templatesModal";
import DeleteConfirmationModal from "../../../../modals/deleteConfirmationModal";
interface ModalsProps {
openModal: boolean;
setOpenModal: (value: boolean) => void;
openDeleteFolderModal: boolean;
setOpenDeleteFolderModal: (value: boolean) => void;
handleDeleteFolder: () => void;
}
const ModalsComponent = ({
openModal = false,
setOpenModal = () => {},
openDeleteFolderModal = false,
setOpenDeleteFolderModal = () => {},
handleDeleteFolder = () => {},
}: ModalsProps) => (
<>
{openModal && <TemplatesModal open={openModal} setOpen={setOpenModal} />}
{openDeleteFolderModal && (
<DeleteConfirmationModal
open={openDeleteFolderModal}
setOpen={setOpenDeleteFolderModal}
onConfirm={() => {
handleDeleteFolder();
setOpenDeleteFolderModal(false);
}}
description="folder"
note={
"Deleting the selected folder will remove all associated flows and components."
}
>
<></>
</DeleteConfirmationModal>
)}
</>
);
export default ModalsComponent;

View file

@ -1,9 +1,6 @@
// Modals.tsx
import TemplatesModal from "@/modals/templatesModal";
import IconComponent from "../../../../components/genericIconComponent";
import { Button } from "../../../../components/ui/button";
import DeleteConfirmationModal from "../../../../modals/deleteConfirmationModal";
import { cn } from "../../../../utils/utils";
interface ModalsProps {
openModal: boolean;
@ -35,13 +32,7 @@ const ModalsComponent = ({
"Deleting the selected folder will remove all associated flows and components."
}
>
<Button variant="ghost" size="icon" className={"whitespace-nowrap"}>
<IconComponent
data-testid={`delete-folder`}
name="Trash2"
className={cn("h-5 w-5")}
/>
</Button>
<></>
</DeleteConfirmationModal>
)}
</>

View file

@ -0,0 +1,45 @@
import ForwardedIconComponent from "@/components/genericIconComponent";
import { Button } from "@/components/ui/button";
import { useFolderStore } from "@/stores/foldersStore";
type EmptyFolderProps = {
setOpenModal: (open: boolean) => void;
};
export const EmptyFolder = ({ setOpenModal }: EmptyFolderProps) => {
const folders = useFolderStore((state) => state.folders);
return (
<div className="m-0 h-full w-full bg-secondary p-0">
<div className="text-container">
<div className="relative z-20 flex w-full flex-col items-center justify-center gap-2">
<h3
className="pt-5 font-chivo text-2xl font-semibold"
data-testid="mainpage_title"
>
{folders?.length > 1 ? "Empty folder" : "Start building"}
</h3>
<p className="pb-5 text-sm text-secondary-foreground">
Begin with a template, or start from scratch.
</p>
<Button
variant="default"
onClick={() => setOpenModal(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>
</div>
</div>
</div>
);
};
export default EmptyFolder;

File diff suppressed because one or more lines are too long

View file

@ -1,16 +1,18 @@
import CardsWrapComponent from "@/components/cardsWrapComponent";
import ForwardedIconComponent from "@/components/genericIconComponent";
import PaginatorComponent from "@/components/paginatorComponent";
import { useGetFolderQuery } from "@/controllers/API/queries/folders/use-get-folder";
import { ENABLE_DATASTAX_LANGFLOW } from "@/customization/feature-flags";
import useFlowsManagerStore from "@/stores/flowsManagerStore";
import { useFolderStore } from "@/stores/foldersStore";
import { useCallback, useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import GridComponent from "../../components/grid";
import GridSkeleton from "../../components/gridSkeleton";
import HeaderComponent from "../../components/header";
import ListComponent from "../../components/list";
import ListSkeleton from "../../components/listSkeleton";
import useFileDrop from "../../hooks/use-on-file-drop";
import ModalsComponent from "../../oldComponents/modalsComponent";
import EmptyFolder from "../emptyFolder";
const HomePage = ({ type }) => {
const [view, setView] = useState<"grid" | "list">(() => {
@ -25,9 +27,14 @@ const HomePage = ({ type }) => {
const handleFileDrop = useFileDrop("flows");
const [flowType, setFlowType] = useState<"flows" | "components">(type);
const myCollectionId = useFolderStore((state) => state.myCollectionId);
const [folderName, setFolderName] = useState("");
const folders = useFolderStore((state) => state.folders);
const folderName =
folders.find((folder) => folder.id === folderId)?.name ??
folders[0]?.name ??
"";
const flows = useFlowsManagerStore((state) => state.flows);
const { data: folderData, isFetching } = useGetFolderQuery({
const { data: folderData, isLoading } = useGetFolderQuery({
id: folderId ?? myCollectionId!,
page: pageIndex,
size: pageSize,
@ -50,12 +57,6 @@ const HomePage = ({ type }) => {
},
};
useEffect(() => {
if (folderData && folderData?.folder?.name) {
setFolderName(folderData.folder.name);
}
}, [folderData, folderData?.folder?.name]);
useEffect(() => {
localStorage.setItem("view", view);
}, [view]);
@ -70,6 +71,10 @@ const HomePage = ({ type }) => {
setPageIndex(1);
}, []);
const isEmptyFolder =
flows?.find((flow) => flow.folder_id === (folderId ?? myCollectionId)) ===
undefined;
return (
<CardsWrapComponent
onFileDrop={handleFileDrop}
@ -110,11 +115,25 @@ const HomePage = ({ type }) => {
setView={setView}
setNewProjectModal={setNewProjectModal}
setSearch={onSearch}
isEmptyFolder={isEmptyFolder}
/>
{flowType === "flows" ? (
{isEmptyFolder ? (
<EmptyFolder setOpenModal={setNewProjectModal} />
) : (
<div className="mt-6">
{data && data.pagination.total > 0 ? (
{isLoading ? (
view === "grid" ? (
<div className="mt-1 grid grid-cols-1 gap-3 md:grid-cols-2 lg:grid-cols-3">
<GridSkeleton />
<GridSkeleton />
</div>
) : (
<div className="flex flex-col">
<ListSkeleton />
<ListSkeleton />
</div>
)
) : data && data.pagination.total > 0 ? (
view === "grid" ? (
<div className="mt-1 grid grid-cols-1 gap-3 md:grid-cols-2 lg:grid-cols-3">
{data.flows.map((flow) => (
@ -128,39 +147,19 @@ const HomePage = ({ type }) => {
))}
</div>
)
) : (
<div className="pt-2 text-center">
No saved or custom components. Learn more about{" "}
) : flowType === "flows" ? (
<div className="pt-2 text-center text-sm text-secondary-foreground">
No flows in this folder.{" "}
<a
href="https://docs.langflow.org/components-custom-components"
target="_blank"
rel="noreferrer"
className="underline"
onClick={() => setNewProjectModal(true)}
className="cursor-pointer underline"
>
creating custom components
Create a new flow
</a>
, or browse the store.
</div>
)}
</div>
) : (
<div className="mt-6">
{data && data.pagination.total > 0 ? (
view === "grid" ? (
<div className="mt-1 grid grid-cols-1 gap-3 md:grid-cols-2 lg:grid-cols-3">
{data.flows.map((flow) => (
<GridComponent key={flow.id} flowData={flow} />
))}
</div>
) : (
<div className="flex flex-col">
{data.flows.map((flow) => (
<ListComponent key={flow.id} flowData={flow} />
))}
</div>
)
) : (
<div className="pt-2 text-center">
<div className="pt-2 text-center text-sm text-secondary-foreground">
No saved or custom components. Learn more about{" "}
<a
href="https://docs.langflow.org/components-custom-components"
@ -177,7 +176,7 @@ const HomePage = ({ type }) => {
)}
</div>
{!isFetching && data.pagination.total >= 10 && (
{!isLoading && !isEmptyFolder && data.pagination.total >= 10 && (
<div className="relative flex justify-end px-3 py-6">
<PaginatorComponent
storeComponent={true}
@ -191,6 +190,7 @@ const HomePage = ({ type }) => {
</div>
)}
</div>
<ModalsComponent
openModal={newProjectModal}
setOpenModal={setNewProjectModal}

View file

@ -1,15 +1,15 @@
import CardsWrapComponent from "@/components/cardsWrapComponent";
import FolderSidebarNav from "@/components/folderSidebarComponent";
import SideBarFoldersButtonsComponent from "@/components/folderSidebarComponent/components/sideBarFolderButtons";
import LoadingComponent from "@/components/loadingComponent";
import { SidebarProvider } from "@/components/ui/sidebar";
import { useDeleteFolders } from "@/controllers/API/queries/folders";
import { useGetFolderQuery } from "@/controllers/API/queries/folders/use-get-folder";
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import { LoadingPage } from "@/pages/LoadingPage";
import useAlertStore from "@/stores/alertStore";
import useFlowsManagerStore from "@/stores/flowsManagerStore";
import { useFolderStore } from "@/stores/foldersStore";
import { useQueryClient } from "@tanstack/react-query";
import { useEffect, useState } from "react";
import { Outlet, useParams } from "react-router-dom";
import { PaginatedFolderType } from "../entities";
import { Outlet } from "react-router-dom";
import useFileDrop from "../hooks/use-on-file-drop";
import ModalsComponent from "../oldComponents/modalsComponent";
import EmptyPage from "./emptyPage";
@ -19,35 +19,19 @@ export default function CollectionPage(): JSX.Element {
const [openDeleteFolderModal, setOpenDeleteFolderModal] = useState(false);
const setFolderToEdit = useFolderStore((state) => state.setFolderToEdit);
const navigate = useCustomNavigate();
const { folderId } = useParams();
const myCollectionId = useFolderStore((state) => state.myCollectionId);
const flows = useFlowsManagerStore((state) => state.flows);
const examples = useFlowsManagerStore((state) => state.examples);
const handleFileDrop = useFileDrop("flow");
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData);
const folderToEdit = useFolderStore((state) => state.folderToEdit);
const showFolderModal = useFolderStore((state) => state.showFolderModal);
const folders = useFolderStore((state) => state.folders);
const setShowFolderModal = useFolderStore(
(state) => state.setShowFolderModal,
);
const queryClient = useQueryClient();
useEffect(() => {
return () => queryClient.removeQueries({ queryKey: ["useGetFolder"] });
}, []);
const { isFetching, data } = useGetFolderQuery({
id: folderId ?? myCollectionId!,
});
const [folderData, setFolderData] = useState<PaginatedFolderType | null>(
null,
);
useEffect(() => {
setFolderData(data ?? null);
}, [data]);
const { mutate } = useDeleteFolders();
const handleDeleteFolder = () => {
@ -73,58 +57,41 @@ export default function CollectionPage(): JSX.Element {
};
return (
<>
{(folderData?.flows?.items?.length !== 0 || folders?.length > 1) && (
<aside
className={`flex w-2/6 min-w-[220px] max-w-[20rem] flex-col border-r bg-background px-4 lg:inline ${
showFolderModal ? "" : "hidden"
}`}
>
<FolderSidebarNav
<SidebarProvider>
{flows &&
examples &&
folders &&
(flows?.length !== examples?.length || folders?.length > 1) && (
<SideBarFoldersButtonsComponent
handleChangeFolder={(id: string) => {
navigate(`all/folder/${id}`);
setShowFolderModal(false);
}}
handleDeleteFolder={(item) => {
setFolderToEdit(item);
setOpenDeleteFolderModal(true);
}}
/>
</aside>
)}
{!isFetching && folderData ? (
<div
className={`relative mx-auto h-full w-full overflow-y-scroll ${
showFolderModal ? "opacity-80 blur-[2px]" : ""
}`}
onClick={(e) => {
e.stopPropagation();
if (showFolderModal) {
setShowFolderModal(false);
}
}}
>
<CardsWrapComponent
onFileDrop={handleFileDrop}
dragMessage={`Drop your file(s) here`}
>
{folderData && folderData?.flows?.items?.length !== 0 ? (
<Outlet />
) : (
<EmptyPage
setOpenModal={setOpenModal}
setShowFolderModal={setShowFolderModal}
folderData={folderData}
/>
)}
</CardsWrapComponent>
</div>
) : (
<LoadingPage />
)}
)}
<main className="flex flex-1">
{flows && examples && folders ? (
<div className={`relative mx-auto h-full w-full overflow-y-scroll`}>
<CardsWrapComponent
onFileDrop={handleFileDrop}
dragMessage={`Drop your file(s) here`}
>
{flows?.length !== examples?.length || folders?.length > 1 ? (
<Outlet />
) : (
<EmptyPage setOpenModal={setOpenModal} />
)}
</CardsWrapComponent>
</div>
) : (
<div className="flex h-full w-full items-center justify-center">
<LoadingComponent remSize={30} />
</div>
)}
</main>
<ModalsComponent
openModal={openModal}
setOpenModal={setOpenModal}
@ -132,6 +99,6 @@ export default function CollectionPage(): JSX.Element {
setOpenDeleteFolderModal={setOpenDeleteFolderModal}
handleDeleteFolder={handleDeleteFolder}
/>
</>
</SidebarProvider>
);
}

View file

@ -1,10 +1,23 @@
import { TEMPLATES_DATA } from "../constants";
import { useTypesStore } from "@/stores/typesStore";
import { FlowType } from "@/types/flow";
import { nodeIconsLucide } from "@/utils/styleUtils";
export const getTemplateStyle = (flowData: {
name: string;
}): { icon: string; icon_bg_color: string } => {
const { icon, icon_bg_color } = TEMPLATES_DATA.examples.find((example) =>
flowData.name.includes(example.name),
) ?? { icon: "circle-help", icon_bg_color: "bg-purple-300" };
return { icon, icon_bg_color };
export const useGetTemplateStyle = (
flowData: FlowType,
): { getIcon: () => string } => {
const getIcon = () => {
if (flowData.is_component) {
const dataType = flowData.data?.nodes[0].data.type;
const isGroup = !!flowData.data?.nodes[0].data.node?.flow;
const icon = flowData.data?.nodes[0].data.node?.icon;
const types = useTypesStore((state) => state.types);
const name = nodeIconsLucide[dataType] ? dataType : types[dataType];
const iconName = icon || (isGroup ? "group_components" : name);
return iconName;
} else {
return flowData.icon ?? "Workflow";
}
};
return { getIcon };
};

View file

@ -17,7 +17,4 @@ export const useFolderStore = create<FoldersStoreType>((set, get) => ({
setStarterProjectId: (id) => set(() => ({ starterProjectId: id })),
folders: [],
setFolders: (folders) => set(() => ({ folders: folders })),
showFolderModal: false,
setShowFolderModal: (showFolderModal) =>
set(() => ({ showFolderModal: showFolderModal })),
}));

View file

@ -1281,8 +1281,6 @@
left: 0;
justify-content: center;
align-items: center;
user-select: none;
text-shadow: 1px 1px rgba(0, 0, 0, 0.1);
}
:root {
@ -1326,7 +1324,7 @@
var(--color-bg1);
top: 0;
left: 0;
opacity: 0.6;
background-blend-mode: overlay;
svg {
display: none;

View file

@ -8,6 +8,7 @@
:root {
--font-sans: "Inter", sans-serif;
--font-mono: "JetBrains Mono", monospace;
--font-chivo: "Chivo", sans-serif;
--foreground: 0 0% 0%; /* hsl(0, 0%, 0%) */
--background: 0 0% 100%; /* hsl(0, 0%, 100%) */

View file

@ -13,6 +13,4 @@ export type FoldersStoreType = {
setStarterProjectId: (id: string) => void;
folders: FolderType[];
setFolders: (folders: FolderType[]) => void;
showFolderModal: boolean;
setShowFolderModal: (show: boolean) => void;
};

View file

@ -1596,6 +1596,7 @@ export const createNewFlow = (
name: flow?.name ? flow.name : "Untitled document",
data: flowData,
id: "",
icon: flow?.icon ?? undefined,
is_component: flow?.is_component ?? false,
folder_id: folderId,
endpoint_name: flow?.endpoint_name ?? undefined,

View file

@ -48,6 +48,7 @@ import {
Command,
Compass,
Copy,
CopyPlus,
CornerDownLeft,
Cpu,
CpuIcon,
@ -96,6 +97,7 @@ import {
Keyboard,
Laptop2,
Layers,
LayoutGrid,
LayoutPanelTop,
Link,
Link2,
@ -127,6 +129,7 @@ import {
PanelLeftClose,
PanelLeftOpen,
PanelRightClose,
PanelRightOpen,
Paperclip,
PaperclipIcon,
Pen,
@ -792,6 +795,7 @@ export const nodeIconsLucide: iconsType = {
ArrowBigUp,
PanelRightClose,
Dot,
LayoutGrid,
StickyNote,
note: StickyNote,
RotateCcw,
@ -808,7 +812,9 @@ export const nodeIconsLucide: iconsType = {
ArrowUpRight,
Scroll,
Image,
CopyPlus,
Pen,
PanelRightOpen,
CornerDownLeft,
ChevronsDownUp,
OptionIcon,

View file

@ -258,6 +258,7 @@ const config = {
fontFamily: {
sans: ["var(--font-sans)", ...fontFamily.sans],
mono: ["var(--font-mono)", ...fontFamily.mono],
chivo: ["var(--font-chivo)", ...fontFamily.sans],
},
boxShadow: {
"frozen-ring": "0 0 10px 2px rgba(128, 190, 230, 0.5)",

View file

@ -41,9 +41,17 @@ test("CRUD folders", async ({ page }) => {
await page.getByText("Select All").first().isVisible();
await page.getByTestId("add-folder-button").click();
await page.getByText("New Folder").last().isVisible();
await page
.locator("[data-testid='folder-sidebar']")
.getByText("New Folder")
.last()
.isVisible();
await page.waitForTimeout(1000);
await page.getByText("New Folder").last().dblclick();
await page
.locator("[data-testid='folder-sidebar']")
.getByText("New Folder")
.last()
.dblclick();
const element = await page.getByTestId("input-folder");
await element.fill("new folder test name");
@ -172,9 +180,17 @@ test("change flow folder", async ({ page }) => {
await page.getByText("Select All").first().isVisible();
await page.getByTestId("add-folder-button").click();
await page.getByText("New Folder").last().isVisible();
await page
.locator("[data-testid='folder-sidebar']")
.getByText("New Folder")
.last()
.isVisible();
await page.waitForTimeout(1000);
await page.getByText("New Folder").last().dblclick();
await page
.locator("[data-testid='folder-sidebar']")
.getByText("New Folder")
.last()
.dblclick();
await page.getByTestId("input-folder").fill("new folder test name");
await page.keyboard.press("Enter");
await page.getByText("new folder test name").last().isVisible();