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:
parent
6761df6110
commit
a09652ca5f
30 changed files with 697 additions and 631 deletions
|
|
@ -9,11 +9,7 @@
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
<link
|
<link
|
||||||
href="https://fonts.googleapis.com/css2?family=Chivo:ital,wght@0,100..900;1,100..900&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"
|
|
||||||
/>
|
|
||||||
<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"
|
rel="stylesheet"
|
||||||
/>
|
/>
|
||||||
<script
|
<script
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,17 @@ import {
|
||||||
SelectItem,
|
SelectItem,
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
} from "@/components/ui/select-custom";
|
} from "@/components/ui/select-custom";
|
||||||
|
import {
|
||||||
|
Sidebar,
|
||||||
|
SidebarContent,
|
||||||
|
SidebarGroup,
|
||||||
|
SidebarGroupContent,
|
||||||
|
SidebarHeader,
|
||||||
|
SidebarMenu,
|
||||||
|
SidebarMenuButton,
|
||||||
|
SidebarMenuItem,
|
||||||
|
SidebarTrigger,
|
||||||
|
} from "@/components/ui/sidebar";
|
||||||
import {
|
import {
|
||||||
usePatchFolders,
|
usePatchFolders,
|
||||||
usePostFolders,
|
usePostFolders,
|
||||||
|
|
@ -18,7 +29,7 @@ import { getObjectsFromFilelist } from "@/helpers/get-objects-from-filelist";
|
||||||
import useUploadFlow from "@/hooks/flows/use-upload-flow";
|
import useUploadFlow from "@/hooks/flows/use-upload-flow";
|
||||||
import { useIsFetching, useIsMutating } from "@tanstack/react-query";
|
import { useIsFetching, useIsMutating } from "@tanstack/react-query";
|
||||||
import { useEffect, useRef, useState } from "react";
|
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 { FolderType } from "../../../../pages/MainPage/entities";
|
||||||
import useAlertStore from "../../../../stores/alertStore";
|
import useAlertStore from "../../../../stores/alertStore";
|
||||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
||||||
|
|
@ -26,25 +37,28 @@ import { useFolderStore } from "../../../../stores/foldersStore";
|
||||||
import { handleKeyDown } from "../../../../utils/reactflowUtils";
|
import { handleKeyDown } from "../../../../utils/reactflowUtils";
|
||||||
import { cn } from "../../../../utils/utils";
|
import { cn } from "../../../../utils/utils";
|
||||||
import IconComponent from "../../../genericIconComponent";
|
import IconComponent from "../../../genericIconComponent";
|
||||||
import { Button, buttonVariants } from "../../../ui/button";
|
import { Button } from "../../../ui/button";
|
||||||
import { Input } from "../../../ui/input";
|
import { Input } from "../../../ui/input";
|
||||||
import useFileDrop from "../../hooks/use-on-file-drop";
|
import useFileDrop from "../../hooks/use-on-file-drop";
|
||||||
import { SidebarFolderSkeleton } from "../sidebarFolderSkeleton";
|
import { SidebarFolderSkeleton } from "../sidebarFolderSkeleton";
|
||||||
|
|
||||||
type SideBarFoldersButtonsComponentProps = {
|
type SideBarFoldersButtonsComponentProps = {
|
||||||
pathname: string;
|
|
||||||
handleChangeFolder?: (id: string) => void;
|
handleChangeFolder?: (id: string) => void;
|
||||||
handleDeleteFolder?: (item: FolderType) => void;
|
handleDeleteFolder?: (item: FolderType) => void;
|
||||||
folders: FolderType[] | undefined;
|
|
||||||
loading?: boolean;
|
|
||||||
};
|
};
|
||||||
const SideBarFoldersButtonsComponent = ({
|
const SideBarFoldersButtonsComponent = ({
|
||||||
pathname,
|
|
||||||
handleChangeFolder,
|
handleChangeFolder,
|
||||||
handleDeleteFolder,
|
handleDeleteFolder,
|
||||||
folders = [],
|
|
||||||
loading,
|
|
||||||
}: SideBarFoldersButtonsComponentProps) => {
|
}: 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 refInput = useRef<HTMLInputElement>(null);
|
||||||
const [foldersNames, setFoldersNames] = useState({});
|
const [foldersNames, setFoldersNames] = useState({});
|
||||||
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
|
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
|
||||||
|
|
@ -56,10 +70,6 @@ const SideBarFoldersButtonsComponent = ({
|
||||||
pathname.split("/").length < (ENABLE_CUSTOM_PARAM ? 5 : 4);
|
pathname.split("/").length < (ENABLE_CUSTOM_PARAM ? 5 : 4);
|
||||||
const myCollectionId = useFolderStore((state) => state.myCollectionId);
|
const myCollectionId = useFolderStore((state) => state.myCollectionId);
|
||||||
const folderIdDragging = useFolderStore((state) => state.folderIdDragging);
|
const folderIdDragging = useFolderStore((state) => state.folderIdDragging);
|
||||||
const showFolderModal = useFolderStore((state) => state.showFolderModal);
|
|
||||||
const setShowFolderModal = useFolderStore(
|
|
||||||
(state) => state.setShowFolderModal,
|
|
||||||
);
|
|
||||||
|
|
||||||
const checkPathName = (itemId: string) => {
|
const checkPathName = (itemId: string) => {
|
||||||
if (urlWithoutPath && itemId === myCollectionId) {
|
if (urlWithoutPath && itemId === myCollectionId) {
|
||||||
|
|
@ -152,14 +162,21 @@ const SideBarFoldersButtonsComponent = ({
|
||||||
const { mutate: mutateUpdateFolder } = usePatchFolders();
|
const { mutate: mutateUpdateFolder } = usePatchFolders();
|
||||||
|
|
||||||
function addNewFolder() {
|
function addNewFolder() {
|
||||||
mutateAddFolder({
|
mutateAddFolder(
|
||||||
data: {
|
{
|
||||||
name: "New Folder",
|
data: {
|
||||||
parent_id: null,
|
name: "New Folder",
|
||||||
description: "",
|
parent_id: null,
|
||||||
|
description: "",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
{
|
||||||
track("Create New Folder");
|
onSuccess: (folder) => {
|
||||||
|
track("Create New Folder");
|
||||||
|
handleChangeFolder!(folder.id);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleEditFolderName(e, name): void {
|
function handleEditFolderName(e, name): void {
|
||||||
|
|
@ -232,11 +249,6 @@ const SideBarFoldersButtonsComponent = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const isFetchingFolders = !!useIsFetching({
|
|
||||||
queryKey: ["useGetFolders"],
|
|
||||||
exact: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const isFetchingFolder = !!useIsFetching({
|
const isFetchingFolder = !!useIsFetching({
|
||||||
queryKey: ["useGetFolder"],
|
queryKey: ["useGetFolder"],
|
||||||
exact: false,
|
exact: false,
|
||||||
|
|
@ -254,27 +266,28 @@ const SideBarFoldersButtonsComponent = ({
|
||||||
isDeletingFolder;
|
isDeletingFolder;
|
||||||
|
|
||||||
const HeaderButtons = () => (
|
const HeaderButtons = () => (
|
||||||
<div className="my-4 flex shrink-0 items-center justify-between gap-1">
|
<div className="flex shrink-0 items-center justify-between gap-2">
|
||||||
<Button
|
<SidebarTrigger className="lg:hidden">
|
||||||
variant="ghost"
|
<IconComponent name="PanelLeftClose" className="h-4 w-4" />
|
||||||
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"
|
</SidebarTrigger>
|
||||||
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>
|
<div className="flex-1 text-sm font-semibold">Folders</div>
|
||||||
<UploadFolderButton
|
<div className="flex items-center gap-1">
|
||||||
onClick={handleUploadFlowsToFolder}
|
<UploadFolderButton
|
||||||
disabled={isUpdatingFolder}
|
onClick={handleUploadFlowsToFolder}
|
||||||
/>
|
disabled={isUpdatingFolder}
|
||||||
<AddFolderButton onClick={addNewFolder} disabled={isUpdatingFolder} />
|
/>
|
||||||
|
<AddFolderButton
|
||||||
|
onClick={addNewFolder}
|
||||||
|
disabled={isUpdatingFolder}
|
||||||
|
loading={isPending}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const AddFolderButton = ({ onClick, disabled }) => (
|
const AddFolderButton = ({ onClick, disabled, loading }) => (
|
||||||
<ShadTooltip content="Create new folder" styleClasses="z-10">
|
<ShadTooltip content="Create new folder" styleClasses="z-50">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
|
|
@ -282,6 +295,7 @@ const SideBarFoldersButtonsComponent = ({
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
data-testid="add-folder-button"
|
data-testid="add-folder-button"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
loading={loading}
|
||||||
>
|
>
|
||||||
<IconComponent name="Plus" className="h-4 w-4" />
|
<IconComponent name="Plus" className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -289,12 +303,11 @@ const SideBarFoldersButtonsComponent = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
const UploadFolderButton = ({ onClick, disabled }) => (
|
const UploadFolderButton = ({ onClick, disabled }) => (
|
||||||
/* Todo: change this back to being a folder upload */
|
<ShadTooltip content="Upload a flow" styleClasses="z-50">
|
||||||
<ShadTooltip content="Upload a flow" styleClasses="z-10">
|
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
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"
|
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}
|
onClick={onClick}
|
||||||
data-testid="upload-folder-button"
|
data-testid="upload-folder-button"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|
@ -389,153 +402,158 @@ const SideBarFoldersButtonsComponent = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Sidebar collapsible="offcanvas" data-testid="folder-sidebar">
|
||||||
<HeaderButtons />
|
<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">
|
if (refInput.current?.value !== item.name) {
|
||||||
<>
|
handleEditNameFolder(item);
|
||||||
{!loading ? (
|
} else {
|
||||||
folders.map((item, index) => {
|
editFolderName.edit = false;
|
||||||
const editFolderName = editFolders?.filter(
|
}
|
||||||
(folder) => folder.name === item.name,
|
refInput.current?.blur();
|
||||||
)[0];
|
}}
|
||||||
return (
|
value={foldersNames[item.name]}
|
||||||
<div
|
id={`input-folder-${item.name}`}
|
||||||
onDragOver={(e) => dragOver(e, item.id!)}
|
data-testid={`input-folder`}
|
||||||
onDragEnter={(e) => dragEnter(e, item.id!)}
|
/>
|
||||||
onDragLeave={dragLeave}
|
) : (
|
||||||
onDrop={(e) => onDrop(e, item.id!)}
|
<span className="block w-full grow truncate text-[13px] opacity-100">
|
||||||
key={item.id}
|
{item.name}
|
||||||
data-testid={`sidebar-nav-${item.name}`}
|
</span>
|
||||||
className={cn(
|
)}
|
||||||
buttonVariants({ variant: "ghost" }),
|
</div>
|
||||||
checkPathName(item.id!)
|
<Select
|
||||||
? "bg-zinc-200 hover:bg-zinc-200 dark:bg-zinc-800"
|
onValueChange={(value) =>
|
||||||
: "hover:bg-transparent hover:bg-zinc-200 dark:hover:bg-zinc-800 lg:border-transparent",
|
handleSelectChange(value, item)
|
||||||
"group flex w-full shrink-0 cursor-pointer gap-2 opacity-100 lg:min-w-full",
|
}
|
||||||
folderIdDragging === item.id! ? "bg-border" : "",
|
value=""
|
||||||
)}
|
>
|
||||||
onClick={() => handleChangeFolder!(item.id!)}
|
<ShadTooltip
|
||||||
>
|
content="Options"
|
||||||
<div
|
side="right"
|
||||||
onDoubleClick={(event) => {
|
styleClasses="z-50"
|
||||||
handleDoubleClick(event, item);
|
>
|
||||||
}}
|
<SelectTrigger
|
||||||
className="flex w-full items-center justify-between"
|
className="w-fit"
|
||||||
>
|
id={`options-trigger-${item.name}`}
|
||||||
<div className="flex items-center gap-2">
|
data-testid="more-options-button"
|
||||||
{editFolderName?.edit && !isUpdatingFolder ? (
|
>
|
||||||
<div>
|
<IconComponent
|
||||||
<Input
|
name={"MoreHorizontal"}
|
||||||
className="w-36"
|
className={`w-4 stroke-[1.5] px-0 text-muted-foreground group-hover/menu-button:block group-hover/menu-button:text-foreground ${
|
||||||
onChange={(e) => {
|
checkPathName(item.id!) ? "block" : "hidden"
|
||||||
handleEditFolderName(e, item.name);
|
}`}
|
||||||
}}
|
/>
|
||||||
ref={refInput}
|
</SelectTrigger>
|
||||||
onKeyDown={(e) => {
|
</ShadTooltip>
|
||||||
handleKeyDownFn(e, item);
|
<SelectContent
|
||||||
handleKeyDown(e, e.key, "");
|
align="end"
|
||||||
}}
|
alignOffset={-16}
|
||||||
autoFocus={true}
|
position="popper"
|
||||||
onBlur={(e) => {
|
>
|
||||||
// fixes autofocus problem where cursor isn't present
|
{item.name !== "My Projects" && (
|
||||||
if (
|
<SelectItem
|
||||||
e.relatedTarget?.id ===
|
id="rename-button"
|
||||||
`options-trigger-${item.name}`
|
value="rename"
|
||||||
) {
|
data-testid="btn-rename-folder"
|
||||||
refInput.current?.focus();
|
>
|
||||||
return;
|
<FolderSelectItem
|
||||||
}
|
name="Rename"
|
||||||
|
iconName="SquarePen"
|
||||||
if (refInput.current?.value !== item.name) {
|
/>
|
||||||
handleEditNameFolder(item);
|
</SelectItem>
|
||||||
} else {
|
)}
|
||||||
editFolderName.edit = false;
|
<SelectItem
|
||||||
}
|
value="download"
|
||||||
refInput.current?.blur();
|
data-testid="btn-download-folder"
|
||||||
}}
|
>
|
||||||
value={foldersNames[item.name]}
|
<FolderSelectItem
|
||||||
id={`input-folder-${item.name}`}
|
name="Download Content"
|
||||||
data-testid={`input-folder`}
|
iconName="Download"
|
||||||
/>
|
/>
|
||||||
|
</SelectItem>
|
||||||
|
{index > 0 && (
|
||||||
|
<SelectItem
|
||||||
|
value="delete"
|
||||||
|
data-testid="btn-delete-folder"
|
||||||
|
>
|
||||||
|
<FolderSelectItem
|
||||||
|
name="Delete"
|
||||||
|
iconName="Trash2"
|
||||||
|
/>
|
||||||
|
</SelectItem>
|
||||||
|
)}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
</SidebarMenuButton>
|
||||||
<span className="block w-full grow truncate text-[13px] opacity-100">
|
</SidebarMenuItem>
|
||||||
{item.name}
|
);
|
||||||
</span>
|
})
|
||||||
)}
|
) : (
|
||||||
</div>
|
<>
|
||||||
<Select
|
<SidebarFolderSkeleton />
|
||||||
onValueChange={(value) => handleSelectChange(value, item)}
|
<SidebarFolderSkeleton />
|
||||||
value=""
|
</>
|
||||||
>
|
)}
|
||||||
<ShadTooltip
|
</SidebarMenu>
|
||||||
content="Options"
|
</SidebarGroupContent>
|
||||||
side="right"
|
</SidebarGroup>
|
||||||
styleClasses="z-10"
|
</SidebarContent>
|
||||||
>
|
</Sidebar>
|
||||||
<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>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default SideBarFoldersButtonsComponent;
|
export default SideBarFoldersButtonsComponent;
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@ import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
|
||||||
export function SidebarFolderSkeleton() {
|
export function SidebarFolderSkeleton() {
|
||||||
return (
|
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">
|
<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-4 rounded-full" />
|
|
||||||
<Skeleton className="h-3 w-[40%]" />
|
<Skeleton className="h-3 w-[40%]" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
import { useGetFoldersQuery } from "@/controllers/API/queries/folders/use-get-folders";
|
|
||||||
import { useFolderStore } from "@/stores/foldersStore";
|
import { useFolderStore } from "@/stores/foldersStore";
|
||||||
import { useIsFetching } from "@tanstack/react-query";
|
import { useIsFetching } from "@tanstack/react-query";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import { FolderType } from "../../pages/MainPage/entities";
|
import { FolderType } from "../../pages/MainPage/entities";
|
||||||
import { cn } from "../../utils/utils";
|
|
||||||
import HorizontalScrollFadeComponent from "../horizontalScrollFadeComponent";
|
|
||||||
import SideBarFoldersButtonsComponent from "./components/sideBarFolderButtons";
|
import SideBarFoldersButtonsComponent from "./components/sideBarFolderButtons";
|
||||||
|
|
||||||
type SidebarNavProps = {
|
type SidebarNavProps = {
|
||||||
|
|
@ -29,16 +26,9 @@ export default function FolderSidebarNav({
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className={cn(className)} {...props}>
|
<SideBarFoldersButtonsComponent
|
||||||
<HorizontalScrollFadeComponent>
|
handleChangeFolder={handleChangeFolder}
|
||||||
<SideBarFoldersButtonsComponent
|
handleDeleteFolder={handleDeleteFolder}
|
||||||
loading={isPending || !folders}
|
/>
|
||||||
pathname={pathname}
|
|
||||||
handleChangeFolder={handleChangeFolder}
|
|
||||||
handleDeleteFolder={handleDeleteFolder}
|
|
||||||
folders={folders}
|
|
||||||
/>
|
|
||||||
</HorizontalScrollFadeComponent>
|
|
||||||
</nav>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -518,6 +518,7 @@ const sidebarMenuButtonVariants = cva(
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "h-8 text-sm",
|
default: "h-8 text-sm",
|
||||||
|
md: "h-9 text-sm",
|
||||||
sm: "h-7 text-xs",
|
sm: "h-7 text-xs",
|
||||||
lg: "h-12 text-sm group-data-[collapsible=icon]:!p-0",
|
lg: "h-12 text-sm group-data-[collapsible=icon]:!p-0",
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ interface IPostAddFlow {
|
||||||
is_component: boolean;
|
is_component: boolean;
|
||||||
folder_id: string;
|
folder_id: string;
|
||||||
endpoint_name: string | undefined;
|
endpoint_name: string | undefined;
|
||||||
|
icon: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const usePostAddFlow: useMutationFunctionType<
|
export const usePostAddFlow: useMutationFunctionType<
|
||||||
|
|
@ -29,6 +30,7 @@ export const usePostAddFlow: useMutationFunctionType<
|
||||||
description: payload.description,
|
description: payload.description,
|
||||||
is_component: payload.is_component,
|
is_component: payload.is_component,
|
||||||
folder_id: payload.folder_id || null,
|
folder_id: payload.folder_id || null,
|
||||||
|
icon: payload.icon || null,
|
||||||
endpoint_name: payload.endpoint_name || null,
|
endpoint_name: payload.endpoint_name || null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,6 @@ export const useGetFolderQuery: useQueryFunctionType<
|
||||||
() => getFolderFn(params),
|
() => getFolderFn(params),
|
||||||
{
|
{
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
placeholderData: true,
|
|
||||||
...options,
|
...options,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,8 @@ export const usePostFolders: useMutationFunctionType<
|
||||||
|
|
||||||
const mutation = mutate(["usePostFolders"], addFoldersFn, {
|
const mutation = mutate(["usePostFolders"], addFoldersFn, {
|
||||||
...options,
|
...options,
|
||||||
onSettled: () => {
|
onSuccess: () => {
|
||||||
queryClient.refetchQueries({ queryKey: ["useGetFolders"] });
|
return queryClient.refetchQueries({ queryKey: ["useGetFolders"] });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,23 +35,10 @@ export default function TemplateCardComponent({
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
<div
|
<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">
|
||||||
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,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<IconComponent
|
<IconComponent
|
||||||
name={example.icon || "FileText"}
|
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>
|
||||||
<div className="flex flex-1 flex-col justify-between">
|
<div className="flex flex-1 flex-col justify-between">
|
||||||
|
|
|
||||||
|
|
@ -78,11 +78,11 @@ const DropdownComponent = ({
|
||||||
data-testid="btn-download-json"
|
data-testid="btn-download-json"
|
||||||
>
|
>
|
||||||
<ForwardedIconComponent
|
<ForwardedIconComponent
|
||||||
name="download"
|
name="Download"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="mr-2 h-4 w-4"
|
className="mr-2 h-4 w-4"
|
||||||
/>
|
/>
|
||||||
Download JSON
|
Download
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
|
|
@ -93,7 +93,7 @@ const DropdownComponent = ({
|
||||||
data-testid="btn-duplicate-flow"
|
data-testid="btn-duplicate-flow"
|
||||||
>
|
>
|
||||||
<ForwardedIconComponent
|
<ForwardedIconComponent
|
||||||
name="copy-plus"
|
name="CopyPlus"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="mr-2 h-4 w-4"
|
className="mr-2 h-4 w-4"
|
||||||
/>
|
/>
|
||||||
|
|
@ -104,10 +104,10 @@ const DropdownComponent = ({
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setOpenDelete(true);
|
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
|
<ForwardedIconComponent
|
||||||
name="trash"
|
name="Trash2"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="mr-2 h-4 w-4"
|
className="mr-2 h-4 w-4"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,8 @@ import {
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
|
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
|
||||||
import { track } from "@/customization/utils/analytics";
|
|
||||||
import useDeleteFlow from "@/hooks/flows/use-delete-flow";
|
import useDeleteFlow from "@/hooks/flows/use-delete-flow";
|
||||||
import DeleteConfirmationModal from "@/modals/deleteConfirmationModal";
|
import DeleteConfirmationModal from "@/modals/deleteConfirmationModal";
|
||||||
import IOModal from "@/modals/IOModal";
|
|
||||||
import useAlertStore from "@/stores/alertStore";
|
import useAlertStore from "@/stores/alertStore";
|
||||||
import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
||||||
import { FlowType } from "@/types/flow";
|
import { FlowType } from "@/types/flow";
|
||||||
|
|
@ -19,27 +17,27 @@ import { getInputsAndOutputs } from "@/utils/storeUtils";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import useDescriptionModal from "../../oldComponents/componentsComponent/hooks/use-description-modal";
|
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 { timeElapsed } from "../../utils/time-elapse";
|
||||||
import DropdownComponent from "../dropdown";
|
import DropdownComponent from "../dropdown";
|
||||||
|
|
||||||
const GridComponent = ({ flowData }: { flowData: FlowType }) => {
|
const GridComponent = ({ flowData }: { flowData: FlowType }) => {
|
||||||
const navigate = useCustomNavigate();
|
const navigate = useCustomNavigate();
|
||||||
// const [openPlayground, setOpenPlayground] = useState(false);
|
/* const [openPlayground, setOpenPlayground] = useState(false);
|
||||||
const [loadingPlayground, setLoadingPlayground] = useState(false);
|
const [loadingPlayground, setLoadingPlayground] = useState(false); */
|
||||||
const [openDelete, setOpenDelete] = useState(false);
|
const [openDelete, setOpenDelete] = useState(false);
|
||||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||||
const { deleteFlow } = useDeleteFlow();
|
const { deleteFlow } = useDeleteFlow();
|
||||||
|
|
||||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
|
/* const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow); */
|
||||||
const { folderId } = useParams();
|
const { folderId } = useParams();
|
||||||
const isComponent = flowData.is_component ?? false;
|
const isComponent = flowData.is_component ?? false;
|
||||||
const setFlowToCanvas = useFlowsManagerStore(
|
const setFlowToCanvas = useFlowsManagerStore(
|
||||||
(state) => state.setFlowToCanvas,
|
(state) => state.setFlowToCanvas,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { icon, icon_bg_color } = getTemplateStyle(flowData);
|
const { getIcon } = useGetTemplateStyle(flowData);
|
||||||
|
|
||||||
const editFlowLink = `/flow/${flowData.id}${folderId ? `/folder/${folderId}` : ""}`;
|
const editFlowLink = `/flow/${flowData.id}${folderId ? `/folder/${folderId}` : ""}`;
|
||||||
|
|
||||||
|
|
@ -51,7 +49,7 @@ const GridComponent = ({ flowData }: { flowData: FlowType }) => {
|
||||||
return inputs.length > 0 || outputs.length > 0;
|
return inputs.length > 0 || outputs.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlePlaygroundClick = () => {
|
/* const handlePlaygroundClick = () => {
|
||||||
track("Playground Button Clicked", { flowId: flowData.id });
|
track("Playground Button Clicked", { flowId: flowData.id });
|
||||||
setLoadingPlayground(true);
|
setLoadingPlayground(true);
|
||||||
|
|
||||||
|
|
@ -73,7 +71,7 @@ const GridComponent = ({ flowData }: { flowData: FlowType }) => {
|
||||||
list: ["Error getting flow data."],
|
list: ["Error getting flow data."],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
}; */
|
||||||
|
|
||||||
const handleClick = async () => {
|
const handleClick = async () => {
|
||||||
if (!isComponent) {
|
if (!isComponent) {
|
||||||
|
|
@ -108,18 +106,16 @@ const GridComponent = ({ flowData }: { flowData: FlowType }) => {
|
||||||
draggable
|
draggable
|
||||||
onDragStart={onDragStart}
|
onDragStart={onDragStart}
|
||||||
onClick={handleClick}
|
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"
|
isComponent ? "cursor-default" : "cursor-pointer"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex w-full items-center gap-2">
|
<div className="flex w-full items-center gap-4">
|
||||||
<div
|
<div className={`flex rounded-lg bg-muted p-3`}>
|
||||||
className={`mr-3 flex rounded-lg border ${flowData?.icon_bg_color || icon_bg_color} p-3`}
|
|
||||||
>
|
|
||||||
<ForwardedIconComponent
|
<ForwardedIconComponent
|
||||||
name={flowData?.icon || icon}
|
name={getIcon()}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="h-5 w-5 dark:text-black"
|
className="h-5 w-5 text-foreground"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full min-w-0 items-center justify-between">
|
<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">
|
<div className="text-md truncate font-semibold">
|
||||||
{flowData.name}
|
{flowData.name}
|
||||||
</div>
|
</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
|
Edited {timeElapsed(flowData.updated_at)} ago
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="ghost"
|
||||||
data-testid="home-dropdown-menu"
|
data-testid="home-dropdown-menu"
|
||||||
size="icon"
|
size="iconMd"
|
||||||
className="group ml-2 h-10 w-10 border-none dark:hover:bg-zinc-700"
|
className="group"
|
||||||
>
|
>
|
||||||
<ForwardedIconComponent
|
<ForwardedIconComponent
|
||||||
name="ellipsis"
|
name="Ellipsis"
|
||||||
aria-hidden="true"
|
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>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent
|
<DropdownMenuContent
|
||||||
className="mr-[30px] w-[185px] bg-white dark:bg-black"
|
className="w-[185px]"
|
||||||
sideOffset={5}
|
sideOffset={5}
|
||||||
side="bottom"
|
side="bottom"
|
||||||
>
|
>
|
||||||
|
|
@ -160,7 +156,7 @@ const GridComponent = ({ flowData }: { flowData: FlowType }) => {
|
||||||
</div>
|
</div>
|
||||||
</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}
|
{flowData.description}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -2,8 +2,8 @@ import ForwardedIconComponent from "@/components/genericIconComponent";
|
||||||
import ShadTooltip from "@/components/shadTooltipComponent";
|
import ShadTooltip from "@/components/shadTooltipComponent";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
|
import { SidebarTrigger, useSidebar } from "@/components/ui/sidebar";
|
||||||
import { useFolderStore } from "@/stores/foldersStore";
|
import { cn } from "@/utils/utils";
|
||||||
import { debounce } from "lodash";
|
import { debounce } from "lodash";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
|
|
@ -15,6 +15,7 @@ interface HeaderComponentProps {
|
||||||
setNewProjectModal: (newProjectModal: boolean) => void;
|
setNewProjectModal: (newProjectModal: boolean) => void;
|
||||||
folderName?: string;
|
folderName?: string;
|
||||||
setSearch: (search: string) => void;
|
setSearch: (search: string) => void;
|
||||||
|
isEmptyFolder: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const HeaderComponent = ({
|
const HeaderComponent = ({
|
||||||
|
|
@ -25,10 +26,10 @@ const HeaderComponent = ({
|
||||||
setView,
|
setView,
|
||||||
setNewProjectModal,
|
setNewProjectModal,
|
||||||
setSearch,
|
setSearch,
|
||||||
|
isEmptyFolder,
|
||||||
}: HeaderComponentProps) => {
|
}: HeaderComponentProps) => {
|
||||||
const navigate = useCustomNavigate();
|
|
||||||
const [debouncedSearch, setDebouncedSearch] = useState("");
|
const [debouncedSearch, setDebouncedSearch] = useState("");
|
||||||
const { showFolderModal, setShowFolderModal } = useFolderStore();
|
const { open } = useSidebar();
|
||||||
|
|
||||||
// Debounce the setSearch function from the parent
|
// Debounce the setSearch function from the parent
|
||||||
const debouncedSetSearch = useCallback(
|
const debouncedSetSearch = useCallback(
|
||||||
|
|
@ -56,75 +57,83 @@ const HeaderComponent = ({
|
||||||
className="flex items-center pb-8 text-xl font-semibold"
|
className="flex items-center pb-8 text-xl font-semibold"
|
||||||
data-testid="mainpage_title"
|
data-testid="mainpage_title"
|
||||||
>
|
>
|
||||||
<Button
|
<div className={cn("w-10 transition-all lg:hidden", open && "md:w-0")}>
|
||||||
variant="ghost"
|
<div
|
||||||
className="mr-2 lg:hidden"
|
className={cn(
|
||||||
size="icon"
|
"relative left-0 opacity-100 transition-all",
|
||||||
onClick={() => setShowFolderModal(!showFolderModal)}
|
open ? "md:opacity-0" : "",
|
||||||
>
|
)}
|
||||||
<ForwardedIconComponent
|
>
|
||||||
name={showFolderModal ? "panel-right-open" : "panel-right-close"}
|
<SidebarTrigger>
|
||||||
aria-hidden="true"
|
<ForwardedIconComponent
|
||||||
className="h-5 w-5 text-zinc-500 dark:text-zinc-400"
|
name="PanelLeftOpen"
|
||||||
/>
|
aria-hidden="true"
|
||||||
</Button>
|
className="text-zinc-500 dark:text-zinc-400"
|
||||||
|
/>
|
||||||
|
</SidebarTrigger>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{folderName}
|
{folderName}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row-reverse pb-8">
|
{!isEmptyFolder && (
|
||||||
<div className="w-full border-b dark:border-border" />
|
<>
|
||||||
{["components", "flows"].map((type) => (
|
<div className="flex flex-row-reverse pb-8">
|
||||||
<Button
|
<div className="w-full border-b dark:border-border" />
|
||||||
key={type}
|
{["components", "flows"].map((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) => (
|
|
||||||
<Button
|
<Button
|
||||||
key={viewType}
|
key={type}
|
||||||
unstyled
|
unstyled
|
||||||
size="icon"
|
id={`${type}-btn`}
|
||||||
className={`group mx-[2px] my-[2px] rounded-lg p-2 ${
|
data-testid={`${type}-btn`}
|
||||||
view === viewType
|
onClick={() => setFlowType(type as "flows" | "components")}
|
||||||
? "bg-white text-black shadow-md dark:bg-black dark:text-white"
|
className={`border-b ${
|
||||||
: "bg-zinc-100 text-zinc-500 dark:bg-zinc-800 dark:hover:bg-zinc-800"
|
flowType === type
|
||||||
}`}
|
? "border-b-2 border-foreground text-foreground"
|
||||||
onClick={() => setView(viewType as "list" | "grid")}
|
: "border-border text-muted-foreground hover:text-foreground"
|
||||||
|
} px-3 pb-2 text-sm`}
|
||||||
>
|
>
|
||||||
<ForwardedIconComponent
|
<div className={flowType === type ? "-mb-px" : ""}>
|
||||||
name={viewType === "list" ? "menu" : "layout-grid"}
|
{type.charAt(0).toUpperCase() + type.slice(1)}
|
||||||
aria-hidden="true"
|
</div>
|
||||||
className="h-4 w-4 group-hover:text-black dark:group-hover:text-white"
|
|
||||||
/>
|
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{/* Search and filters */}
|
||||||
<div className="flex gap-2">
|
<div className="flex justify-between">
|
||||||
<ShadTooltip content="Store" side="bottom">
|
<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")}>
|
<Button variant="outline" onClick={() => navigate("/store")}>
|
||||||
<ForwardedIconComponent
|
<ForwardedIconComponent
|
||||||
name="store"
|
name="store"
|
||||||
|
|
@ -135,25 +144,27 @@ const HeaderComponent = ({
|
||||||
Browse Store
|
Browse Store
|
||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
</ShadTooltip>
|
</ShadTooltip> */}
|
||||||
<ShadTooltip content="New Flow" side="bottom">
|
<ShadTooltip content="New Flow" side="bottom">
|
||||||
<Button
|
<Button
|
||||||
variant="default"
|
variant="default"
|
||||||
onClick={() => setNewProjectModal(true)}
|
onClick={() => setNewProjectModal(true)}
|
||||||
id="new-project-btn"
|
id="new-project-btn"
|
||||||
>
|
>
|
||||||
<ForwardedIconComponent
|
<ForwardedIconComponent
|
||||||
name="plus"
|
name="Plus"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="h-4 w-4"
|
className="h-4 w-4"
|
||||||
/>
|
/>
|
||||||
<span className="hidden whitespace-nowrap font-semibold md:inline">
|
<span className="hidden whitespace-nowrap font-semibold md:inline">
|
||||||
New Flow
|
New Flow
|
||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
</ShadTooltip>
|
</ShadTooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,74 +5,70 @@ import { Card } from "@/components/ui/card";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
|
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
|
||||||
import { track } from "@/customization/utils/analytics";
|
|
||||||
import useDeleteFlow from "@/hooks/flows/use-delete-flow";
|
import useDeleteFlow from "@/hooks/flows/use-delete-flow";
|
||||||
import DeleteConfirmationModal from "@/modals/deleteConfirmationModal";
|
import DeleteConfirmationModal from "@/modals/deleteConfirmationModal";
|
||||||
import IOModal from "@/modals/IOModal";
|
|
||||||
import useAlertStore from "@/stores/alertStore";
|
import useAlertStore from "@/stores/alertStore";
|
||||||
import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
||||||
import { FlowType } from "@/types/flow";
|
import { FlowType } from "@/types/flow";
|
||||||
import { getInputsAndOutputs } from "@/utils/storeUtils";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import useDescriptionModal from "../../oldComponents/componentsComponent/hooks/use-description-modal";
|
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 { timeElapsed } from "../../utils/time-elapse";
|
||||||
import DropdownComponent from "../dropdown";
|
import DropdownComponent from "../dropdown";
|
||||||
|
|
||||||
const ListComponent = ({ flowData }: { flowData: FlowType }) => {
|
const ListComponent = ({ flowData }: { flowData: FlowType }) => {
|
||||||
const navigate = useCustomNavigate();
|
const navigate = useCustomNavigate();
|
||||||
// const [openPlayground, setOpenPlayground] = useState(false);
|
/* const [openPlayground, setOpenPlayground] = useState(false);
|
||||||
// const [loadingPlayground, setLoadingPlayground] = useState(false);
|
const [loadingPlayground, setLoadingPlayground] = useState(false); */
|
||||||
const [openDelete, setOpenDelete] = useState(false);
|
const [openDelete, setOpenDelete] = useState(false);
|
||||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||||
const { deleteFlow } = useDeleteFlow();
|
const { deleteFlow } = useDeleteFlow();
|
||||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
|
/* const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow); */
|
||||||
const { folderId } = useParams();
|
const { folderId } = useParams();
|
||||||
const isComponent = flowData.is_component ?? false;
|
const isComponent = flowData.is_component ?? false;
|
||||||
const setFlowToCanvas = useFlowsManagerStore(
|
const setFlowToCanvas = useFlowsManagerStore(
|
||||||
(state) => state.setFlowToCanvas,
|
(state) => state.setFlowToCanvas,
|
||||||
);
|
);
|
||||||
const { icon, icon_bg_color } = getTemplateStyle(flowData);
|
const { getIcon } = useGetTemplateStyle(flowData);
|
||||||
|
|
||||||
const editFlowLink = `/flow/${flowData.id}${folderId ? `/folder/${folderId}` : ""}`;
|
const editFlowLink = `/flow/${flowData.id}${folderId ? `/folder/${folderId}` : ""}`;
|
||||||
|
|
||||||
function hasPlayground(flow?: FlowType) {
|
/* function hasPlayground(flow?: FlowType) {
|
||||||
if (!flow) {
|
if (!flow) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const { inputs, outputs } = getInputsAndOutputs(flow?.data?.nodes ?? []);
|
const { inputs, outputs } = getInputsAndOutputs(flow?.data?.nodes ?? []);
|
||||||
return inputs.length > 0 || outputs.length > 0;
|
return inputs.length > 0 || outputs.length > 0;
|
||||||
}
|
} */
|
||||||
|
|
||||||
// const handlePlaygroundClick = () => {
|
/* const handlePlaygroundClick = () => {
|
||||||
// track("Playground Button Clicked", { flowId: flowData.id });
|
track("Playground Button Clicked", { flowId: flowData.id });
|
||||||
// setLoadingPlayground(true);
|
setLoadingPlayground(true);
|
||||||
|
|
||||||
// if (flowData) {
|
if (flowData) {
|
||||||
// if (!hasPlayground(flowData)) {
|
if (!hasPlayground(flowData)) {
|
||||||
// setErrorData({
|
setErrorData({
|
||||||
// title: "Error",
|
title: "Error",
|
||||||
// list: ["This flow doesn't have a playground."],
|
list: ["This flow doesn't have a playground."],
|
||||||
// });
|
});
|
||||||
// setLoadingPlayground(false);
|
setLoadingPlayground(false);
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
// setCurrentFlow(flowData);
|
setCurrentFlow(flowData);
|
||||||
// setOpenPlayground(true);
|
setOpenPlayground(true);
|
||||||
// setLoadingPlayground(false);
|
setLoadingPlayground(false);
|
||||||
// } else {
|
} else {
|
||||||
// setErrorData({
|
setErrorData({
|
||||||
// title: "Error",
|
title: "Error",
|
||||||
// list: ["Error getting flow data."],
|
list: ["Error getting flow data."],
|
||||||
// });
|
});
|
||||||
// }
|
}
|
||||||
// };
|
}; */
|
||||||
|
|
||||||
const handleClick = async () => {
|
const handleClick = async () => {
|
||||||
if (!isComponent) {
|
if (!isComponent) {
|
||||||
|
|
@ -107,24 +103,24 @@ const ListComponent = ({ flowData }: { flowData: FlowType }) => {
|
||||||
draggable
|
draggable
|
||||||
onDragStart={onDragStart}
|
onDragStart={onDragStart}
|
||||||
onClick={handleClick}
|
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"
|
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 */}
|
{/* left side */}
|
||||||
<div
|
<div
|
||||||
className={`flex min-w-0 ${
|
className={`flex min-w-0 ${
|
||||||
isComponent ? "cursor-default" : "cursor-pointer"
|
isComponent ? "cursor-default" : "cursor-pointer"
|
||||||
} items-center gap-2`}
|
} items-center gap-4`}
|
||||||
>
|
>
|
||||||
{/* Icon */}
|
{/* Icon */}
|
||||||
<div
|
<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
|
<ForwardedIconComponent
|
||||||
name={flowData?.icon || icon}
|
name={flowData?.icon || getIcon()}
|
||||||
aria-hidden="true"
|
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>
|
</div>
|
||||||
|
|
||||||
|
|
@ -133,11 +129,11 @@ const ListComponent = ({ flowData }: { flowData: FlowType }) => {
|
||||||
<div className="text-md flex truncate pr-2 font-semibold max-md:w-full">
|
<div className="text-md flex truncate pr-2 font-semibold max-md:w-full">
|
||||||
<span className="truncate">{flowData.name}</span>
|
<span className="truncate">{flowData.name}</span>
|
||||||
</div>
|
</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
|
Edited {timeElapsed(flowData.updated_at)} ago
|
||||||
</div>
|
</div>
|
||||||
</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}
|
{flowData.description}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -164,20 +160,20 @@ const ListComponent = ({ flowData }: { flowData: FlowType }) => {
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="ghost"
|
||||||
size="icon"
|
size="iconMd"
|
||||||
data-testid="home-dropdown-menu"
|
data-testid="home-dropdown-menu"
|
||||||
className="group h-10 w-10 border-none dark:hover:bg-zinc-700"
|
className="group"
|
||||||
>
|
>
|
||||||
<ForwardedIconComponent
|
<ForwardedIconComponent
|
||||||
name="ellipsis"
|
name="Ellipsis"
|
||||||
aria-hidden="true"
|
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>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent
|
<DropdownMenuContent
|
||||||
className="mr-[30px] w-[185px] bg-white dark:bg-black"
|
className="w-[185px]"
|
||||||
sideOffset={5}
|
sideOffset={5}
|
||||||
side="bottom"
|
side="bottom"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
// Modals.tsx
|
// Modals.tsx
|
||||||
import TemplatesModal from "@/modals/templatesModal";
|
import TemplatesModal from "@/modals/templatesModal";
|
||||||
import IconComponent from "../../../../components/genericIconComponent";
|
|
||||||
import { Button } from "../../../../components/ui/button";
|
|
||||||
import DeleteConfirmationModal from "../../../../modals/deleteConfirmationModal";
|
import DeleteConfirmationModal from "../../../../modals/deleteConfirmationModal";
|
||||||
import { cn } from "../../../../utils/utils";
|
|
||||||
|
|
||||||
interface ModalsProps {
|
interface ModalsProps {
|
||||||
openModal: boolean;
|
openModal: boolean;
|
||||||
|
|
@ -35,13 +32,7 @@ const ModalsComponent = ({
|
||||||
"Deleting the selected folder will remove all associated flows and components."
|
"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>
|
</DeleteConfirmationModal>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
45
src/frontend/src/pages/MainPage/pages/emptyFolder/index.tsx
Normal file
45
src/frontend/src/pages/MainPage/pages/emptyFolder/index.tsx
Normal 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
|
|
@ -1,16 +1,18 @@
|
||||||
import CardsWrapComponent from "@/components/cardsWrapComponent";
|
import CardsWrapComponent from "@/components/cardsWrapComponent";
|
||||||
import ForwardedIconComponent from "@/components/genericIconComponent";
|
|
||||||
import PaginatorComponent from "@/components/paginatorComponent";
|
import PaginatorComponent from "@/components/paginatorComponent";
|
||||||
import { useGetFolderQuery } from "@/controllers/API/queries/folders/use-get-folder";
|
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 { useFolderStore } from "@/stores/foldersStore";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import GridComponent from "../../components/grid";
|
import GridComponent from "../../components/grid";
|
||||||
|
import GridSkeleton from "../../components/gridSkeleton";
|
||||||
import HeaderComponent from "../../components/header";
|
import HeaderComponent from "../../components/header";
|
||||||
import ListComponent from "../../components/list";
|
import ListComponent from "../../components/list";
|
||||||
|
import ListSkeleton from "../../components/listSkeleton";
|
||||||
import useFileDrop from "../../hooks/use-on-file-drop";
|
import useFileDrop from "../../hooks/use-on-file-drop";
|
||||||
import ModalsComponent from "../../oldComponents/modalsComponent";
|
import ModalsComponent from "../../oldComponents/modalsComponent";
|
||||||
|
import EmptyFolder from "../emptyFolder";
|
||||||
|
|
||||||
const HomePage = ({ type }) => {
|
const HomePage = ({ type }) => {
|
||||||
const [view, setView] = useState<"grid" | "list">(() => {
|
const [view, setView] = useState<"grid" | "list">(() => {
|
||||||
|
|
@ -25,9 +27,14 @@ const HomePage = ({ type }) => {
|
||||||
const handleFileDrop = useFileDrop("flows");
|
const handleFileDrop = useFileDrop("flows");
|
||||||
const [flowType, setFlowType] = useState<"flows" | "components">(type);
|
const [flowType, setFlowType] = useState<"flows" | "components">(type);
|
||||||
const myCollectionId = useFolderStore((state) => state.myCollectionId);
|
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!,
|
id: folderId ?? myCollectionId!,
|
||||||
page: pageIndex,
|
page: pageIndex,
|
||||||
size: pageSize,
|
size: pageSize,
|
||||||
|
|
@ -50,12 +57,6 @@ const HomePage = ({ type }) => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (folderData && folderData?.folder?.name) {
|
|
||||||
setFolderName(folderData.folder.name);
|
|
||||||
}
|
|
||||||
}, [folderData, folderData?.folder?.name]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
localStorage.setItem("view", view);
|
localStorage.setItem("view", view);
|
||||||
}, [view]);
|
}, [view]);
|
||||||
|
|
@ -70,6 +71,10 @@ const HomePage = ({ type }) => {
|
||||||
setPageIndex(1);
|
setPageIndex(1);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const isEmptyFolder =
|
||||||
|
flows?.find((flow) => flow.folder_id === (folderId ?? myCollectionId)) ===
|
||||||
|
undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CardsWrapComponent
|
<CardsWrapComponent
|
||||||
onFileDrop={handleFileDrop}
|
onFileDrop={handleFileDrop}
|
||||||
|
|
@ -110,11 +115,25 @@ const HomePage = ({ type }) => {
|
||||||
setView={setView}
|
setView={setView}
|
||||||
setNewProjectModal={setNewProjectModal}
|
setNewProjectModal={setNewProjectModal}
|
||||||
setSearch={onSearch}
|
setSearch={onSearch}
|
||||||
|
isEmptyFolder={isEmptyFolder}
|
||||||
/>
|
/>
|
||||||
|
{isEmptyFolder ? (
|
||||||
{flowType === "flows" ? (
|
<EmptyFolder setOpenModal={setNewProjectModal} />
|
||||||
|
) : (
|
||||||
<div className="mt-6">
|
<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" ? (
|
view === "grid" ? (
|
||||||
<div className="mt-1 grid grid-cols-1 gap-3 md:grid-cols-2 lg:grid-cols-3">
|
<div className="mt-1 grid grid-cols-1 gap-3 md:grid-cols-2 lg:grid-cols-3">
|
||||||
{data.flows.map((flow) => (
|
{data.flows.map((flow) => (
|
||||||
|
|
@ -128,39 +147,19 @@ const HomePage = ({ type }) => {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
) : (
|
) : flowType === "flows" ? (
|
||||||
<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{" "}
|
No flows in this folder.{" "}
|
||||||
<a
|
<a
|
||||||
href="https://docs.langflow.org/components-custom-components"
|
onClick={() => setNewProjectModal(true)}
|
||||||
target="_blank"
|
className="cursor-pointer underline"
|
||||||
rel="noreferrer"
|
|
||||||
className="underline"
|
|
||||||
>
|
>
|
||||||
creating custom components
|
Create a new flow
|
||||||
</a>
|
</a>
|
||||||
, or browse the store.
|
, or browse the store.
|
||||||
</div>
|
</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{" "}
|
No saved or custom components. Learn more about{" "}
|
||||||
<a
|
<a
|
||||||
href="https://docs.langflow.org/components-custom-components"
|
href="https://docs.langflow.org/components-custom-components"
|
||||||
|
|
@ -177,7 +176,7 @@ const HomePage = ({ type }) => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!isFetching && data.pagination.total >= 10 && (
|
{!isLoading && !isEmptyFolder && data.pagination.total >= 10 && (
|
||||||
<div className="relative flex justify-end px-3 py-6">
|
<div className="relative flex justify-end px-3 py-6">
|
||||||
<PaginatorComponent
|
<PaginatorComponent
|
||||||
storeComponent={true}
|
storeComponent={true}
|
||||||
|
|
@ -191,6 +190,7 @@ const HomePage = ({ type }) => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ModalsComponent
|
<ModalsComponent
|
||||||
openModal={newProjectModal}
|
openModal={newProjectModal}
|
||||||
setOpenModal={setNewProjectModal}
|
setOpenModal={setNewProjectModal}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
import CardsWrapComponent from "@/components/cardsWrapComponent";
|
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 { 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 { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
|
||||||
import { LoadingPage } from "@/pages/LoadingPage";
|
|
||||||
import useAlertStore from "@/stores/alertStore";
|
import useAlertStore from "@/stores/alertStore";
|
||||||
|
import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
||||||
import { useFolderStore } from "@/stores/foldersStore";
|
import { useFolderStore } from "@/stores/foldersStore";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Outlet, useParams } from "react-router-dom";
|
import { Outlet } from "react-router-dom";
|
||||||
import { PaginatedFolderType } from "../entities";
|
|
||||||
import useFileDrop from "../hooks/use-on-file-drop";
|
import useFileDrop from "../hooks/use-on-file-drop";
|
||||||
import ModalsComponent from "../oldComponents/modalsComponent";
|
import ModalsComponent from "../oldComponents/modalsComponent";
|
||||||
import EmptyPage from "./emptyPage";
|
import EmptyPage from "./emptyPage";
|
||||||
|
|
@ -19,35 +19,19 @@ export default function CollectionPage(): JSX.Element {
|
||||||
const [openDeleteFolderModal, setOpenDeleteFolderModal] = useState(false);
|
const [openDeleteFolderModal, setOpenDeleteFolderModal] = useState(false);
|
||||||
const setFolderToEdit = useFolderStore((state) => state.setFolderToEdit);
|
const setFolderToEdit = useFolderStore((state) => state.setFolderToEdit);
|
||||||
const navigate = useCustomNavigate();
|
const navigate = useCustomNavigate();
|
||||||
const { folderId } = useParams();
|
const flows = useFlowsManagerStore((state) => state.flows);
|
||||||
const myCollectionId = useFolderStore((state) => state.myCollectionId);
|
const examples = useFlowsManagerStore((state) => state.examples);
|
||||||
const handleFileDrop = useFileDrop("flow");
|
const handleFileDrop = useFileDrop("flow");
|
||||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
const folderToEdit = useFolderStore((state) => state.folderToEdit);
|
const folderToEdit = useFolderStore((state) => state.folderToEdit);
|
||||||
const showFolderModal = useFolderStore((state) => state.showFolderModal);
|
|
||||||
const folders = useFolderStore((state) => state.folders);
|
const folders = useFolderStore((state) => state.folders);
|
||||||
const setShowFolderModal = useFolderStore(
|
|
||||||
(state) => state.setShowFolderModal,
|
|
||||||
);
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => queryClient.removeQueries({ queryKey: ["useGetFolder"] });
|
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 { mutate } = useDeleteFolders();
|
||||||
|
|
||||||
const handleDeleteFolder = () => {
|
const handleDeleteFolder = () => {
|
||||||
|
|
@ -73,58 +57,41 @@ export default function CollectionPage(): JSX.Element {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<SidebarProvider>
|
||||||
{(folderData?.flows?.items?.length !== 0 || folders?.length > 1) && (
|
{flows &&
|
||||||
<aside
|
examples &&
|
||||||
className={`flex w-2/6 min-w-[220px] max-w-[20rem] flex-col border-r bg-background px-4 lg:inline ${
|
folders &&
|
||||||
showFolderModal ? "" : "hidden"
|
(flows?.length !== examples?.length || folders?.length > 1) && (
|
||||||
}`}
|
<SideBarFoldersButtonsComponent
|
||||||
>
|
|
||||||
<FolderSidebarNav
|
|
||||||
handleChangeFolder={(id: string) => {
|
handleChangeFolder={(id: string) => {
|
||||||
navigate(`all/folder/${id}`);
|
navigate(`all/folder/${id}`);
|
||||||
setShowFolderModal(false);
|
|
||||||
}}
|
}}
|
||||||
handleDeleteFolder={(item) => {
|
handleDeleteFolder={(item) => {
|
||||||
setFolderToEdit(item);
|
setFolderToEdit(item);
|
||||||
setOpenDeleteFolderModal(true);
|
setOpenDeleteFolderModal(true);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</aside>
|
)}
|
||||||
)}
|
<main className="flex flex-1">
|
||||||
|
{flows && examples && folders ? (
|
||||||
{!isFetching && folderData ? (
|
<div className={`relative mx-auto h-full w-full overflow-y-scroll`}>
|
||||||
<div
|
<CardsWrapComponent
|
||||||
className={`relative mx-auto h-full w-full overflow-y-scroll ${
|
onFileDrop={handleFileDrop}
|
||||||
showFolderModal ? "opacity-80 blur-[2px]" : ""
|
dragMessage={`Drop your file(s) here`}
|
||||||
}`}
|
>
|
||||||
onClick={(e) => {
|
{flows?.length !== examples?.length || folders?.length > 1 ? (
|
||||||
e.stopPropagation();
|
<Outlet />
|
||||||
|
) : (
|
||||||
if (showFolderModal) {
|
<EmptyPage setOpenModal={setOpenModal} />
|
||||||
setShowFolderModal(false);
|
)}
|
||||||
}
|
</CardsWrapComponent>
|
||||||
}}
|
</div>
|
||||||
>
|
) : (
|
||||||
<CardsWrapComponent
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
onFileDrop={handleFileDrop}
|
<LoadingComponent remSize={30} />
|
||||||
dragMessage={`Drop your file(s) here`}
|
</div>
|
||||||
>
|
)}
|
||||||
{folderData && folderData?.flows?.items?.length !== 0 ? (
|
</main>
|
||||||
<Outlet />
|
|
||||||
) : (
|
|
||||||
<EmptyPage
|
|
||||||
setOpenModal={setOpenModal}
|
|
||||||
setShowFolderModal={setShowFolderModal}
|
|
||||||
folderData={folderData}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</CardsWrapComponent>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<LoadingPage />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<ModalsComponent
|
<ModalsComponent
|
||||||
openModal={openModal}
|
openModal={openModal}
|
||||||
setOpenModal={setOpenModal}
|
setOpenModal={setOpenModal}
|
||||||
|
|
@ -132,6 +99,6 @@ export default function CollectionPage(): JSX.Element {
|
||||||
setOpenDeleteFolderModal={setOpenDeleteFolderModal}
|
setOpenDeleteFolderModal={setOpenDeleteFolderModal}
|
||||||
handleDeleteFolder={handleDeleteFolder}
|
handleDeleteFolder={handleDeleteFolder}
|
||||||
/>
|
/>
|
||||||
</>
|
</SidebarProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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: {
|
export const useGetTemplateStyle = (
|
||||||
name: string;
|
flowData: FlowType,
|
||||||
}): { icon: string; icon_bg_color: string } => {
|
): { getIcon: () => string } => {
|
||||||
const { icon, icon_bg_color } = TEMPLATES_DATA.examples.find((example) =>
|
const getIcon = () => {
|
||||||
flowData.name.includes(example.name),
|
if (flowData.is_component) {
|
||||||
) ?? { icon: "circle-help", icon_bg_color: "bg-purple-300" };
|
const dataType = flowData.data?.nodes[0].data.type;
|
||||||
return { icon, icon_bg_color };
|
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 };
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,4 @@ export const useFolderStore = create<FoldersStoreType>((set, get) => ({
|
||||||
setStarterProjectId: (id) => set(() => ({ starterProjectId: id })),
|
setStarterProjectId: (id) => set(() => ({ starterProjectId: id })),
|
||||||
folders: [],
|
folders: [],
|
||||||
setFolders: (folders) => set(() => ({ folders: folders })),
|
setFolders: (folders) => set(() => ({ folders: folders })),
|
||||||
showFolderModal: false,
|
|
||||||
setShowFolderModal: (showFolderModal) =>
|
|
||||||
set(() => ({ showFolderModal: showFolderModal })),
|
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
|
|
@ -1281,8 +1281,6 @@
|
||||||
left: 0;
|
left: 0;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
user-select: none;
|
|
||||||
text-shadow: 1px 1px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
|
|
@ -1326,7 +1324,7 @@
|
||||||
var(--color-bg1);
|
var(--color-bg1);
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
opacity: 0.6;
|
background-blend-mode: overlay;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
:root {
|
:root {
|
||||||
--font-sans: "Inter", sans-serif;
|
--font-sans: "Inter", sans-serif;
|
||||||
--font-mono: "JetBrains Mono", monospace;
|
--font-mono: "JetBrains Mono", monospace;
|
||||||
|
--font-chivo: "Chivo", sans-serif;
|
||||||
|
|
||||||
--foreground: 0 0% 0%; /* hsl(0, 0%, 0%) */
|
--foreground: 0 0% 0%; /* hsl(0, 0%, 0%) */
|
||||||
--background: 0 0% 100%; /* hsl(0, 0%, 100%) */
|
--background: 0 0% 100%; /* hsl(0, 0%, 100%) */
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,4 @@ export type FoldersStoreType = {
|
||||||
setStarterProjectId: (id: string) => void;
|
setStarterProjectId: (id: string) => void;
|
||||||
folders: FolderType[];
|
folders: FolderType[];
|
||||||
setFolders: (folders: FolderType[]) => void;
|
setFolders: (folders: FolderType[]) => void;
|
||||||
showFolderModal: boolean;
|
|
||||||
setShowFolderModal: (show: boolean) => void;
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1596,6 +1596,7 @@ export const createNewFlow = (
|
||||||
name: flow?.name ? flow.name : "Untitled document",
|
name: flow?.name ? flow.name : "Untitled document",
|
||||||
data: flowData,
|
data: flowData,
|
||||||
id: "",
|
id: "",
|
||||||
|
icon: flow?.icon ?? undefined,
|
||||||
is_component: flow?.is_component ?? false,
|
is_component: flow?.is_component ?? false,
|
||||||
folder_id: folderId,
|
folder_id: folderId,
|
||||||
endpoint_name: flow?.endpoint_name ?? undefined,
|
endpoint_name: flow?.endpoint_name ?? undefined,
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ import {
|
||||||
Command,
|
Command,
|
||||||
Compass,
|
Compass,
|
||||||
Copy,
|
Copy,
|
||||||
|
CopyPlus,
|
||||||
CornerDownLeft,
|
CornerDownLeft,
|
||||||
Cpu,
|
Cpu,
|
||||||
CpuIcon,
|
CpuIcon,
|
||||||
|
|
@ -96,6 +97,7 @@ import {
|
||||||
Keyboard,
|
Keyboard,
|
||||||
Laptop2,
|
Laptop2,
|
||||||
Layers,
|
Layers,
|
||||||
|
LayoutGrid,
|
||||||
LayoutPanelTop,
|
LayoutPanelTop,
|
||||||
Link,
|
Link,
|
||||||
Link2,
|
Link2,
|
||||||
|
|
@ -127,6 +129,7 @@ import {
|
||||||
PanelLeftClose,
|
PanelLeftClose,
|
||||||
PanelLeftOpen,
|
PanelLeftOpen,
|
||||||
PanelRightClose,
|
PanelRightClose,
|
||||||
|
PanelRightOpen,
|
||||||
Paperclip,
|
Paperclip,
|
||||||
PaperclipIcon,
|
PaperclipIcon,
|
||||||
Pen,
|
Pen,
|
||||||
|
|
@ -792,6 +795,7 @@ export const nodeIconsLucide: iconsType = {
|
||||||
ArrowBigUp,
|
ArrowBigUp,
|
||||||
PanelRightClose,
|
PanelRightClose,
|
||||||
Dot,
|
Dot,
|
||||||
|
LayoutGrid,
|
||||||
StickyNote,
|
StickyNote,
|
||||||
note: StickyNote,
|
note: StickyNote,
|
||||||
RotateCcw,
|
RotateCcw,
|
||||||
|
|
@ -808,7 +812,9 @@ export const nodeIconsLucide: iconsType = {
|
||||||
ArrowUpRight,
|
ArrowUpRight,
|
||||||
Scroll,
|
Scroll,
|
||||||
Image,
|
Image,
|
||||||
|
CopyPlus,
|
||||||
Pen,
|
Pen,
|
||||||
|
PanelRightOpen,
|
||||||
CornerDownLeft,
|
CornerDownLeft,
|
||||||
ChevronsDownUp,
|
ChevronsDownUp,
|
||||||
OptionIcon,
|
OptionIcon,
|
||||||
|
|
|
||||||
|
|
@ -258,6 +258,7 @@ const config = {
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
sans: ["var(--font-sans)", ...fontFamily.sans],
|
sans: ["var(--font-sans)", ...fontFamily.sans],
|
||||||
mono: ["var(--font-mono)", ...fontFamily.mono],
|
mono: ["var(--font-mono)", ...fontFamily.mono],
|
||||||
|
chivo: ["var(--font-chivo)", ...fontFamily.sans],
|
||||||
},
|
},
|
||||||
boxShadow: {
|
boxShadow: {
|
||||||
"frozen-ring": "0 0 10px 2px rgba(128, 190, 230, 0.5)",
|
"frozen-ring": "0 0 10px 2px rgba(128, 190, 230, 0.5)",
|
||||||
|
|
|
||||||
|
|
@ -41,9 +41,17 @@ test("CRUD folders", async ({ page }) => {
|
||||||
await page.getByText("Select All").first().isVisible();
|
await page.getByText("Select All").first().isVisible();
|
||||||
|
|
||||||
await page.getByTestId("add-folder-button").click();
|
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.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");
|
const element = await page.getByTestId("input-folder");
|
||||||
await element.fill("new folder test name");
|
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.getByText("Select All").first().isVisible();
|
||||||
|
|
||||||
await page.getByTestId("add-folder-button").click();
|
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.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.getByTestId("input-folder").fill("new folder test name");
|
||||||
await page.keyboard.press("Enter");
|
await page.keyboard.press("Enter");
|
||||||
await page.getByText("new folder test name").last().isVisible();
|
await page.getByText("new folder test name").last().isVisible();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue