Sync with Langflow Store

This commit is contained in:
Lucas Oliveira 2023-11-18 18:06:33 -03:00
commit a81abb4c2d
4 changed files with 353 additions and 250 deletions

View file

@ -3,6 +3,7 @@ import { alertContext } from "../../contexts/alertContext";
import { FlowsContext } from "../../contexts/flowsContext";
import { StoreContext } from "../../contexts/storeContext";
import { getComponent, postLikeComponent } from "../../controllers/API";
import DeleteConfirmationModal from "../../modals/DeleteConfirmationModal";
import { storeComponent } from "../../types/store";
import cloneFLowWithParent from "../../utils/storeUtils";
import { cn } from "../../utils/utils";
@ -166,13 +167,17 @@ export default function CollectionCardComponent({
</div>
)}
{onDelete && (
<button onClick={onDelete}>
{onDelete && !data.metadata && (
<DeleteConfirmationModal
onConfirm={() => {
onDelete();
}}
>
<IconComponent
name="Trash2"
className="h-5 w-5 text-primary opacity-0 transition-all hover:text-destructive group-hover:opacity-100"
/>
</button>
</DeleteConfirmationModal>
)}
</CardTitle>
</div>
@ -207,36 +212,69 @@ export default function CollectionCardComponent({
</div>
{data.liked_by_count != undefined && (
<div className="flex gap-0.5">
<ShadTooltip
content={authorized ? "Like" : "Please review your API key."}
>
<Button
disabled={loadingLike}
variant="ghost"
size="xs"
className={
"whitespace-nowrap" +
(!authorized ? " cursor-not-allowed" : "")
{onDelete && data.metadata ? (
<ShadTooltip
content={
authorized ? "Delete" : "Please review your API key."
}
onClick={() => {
if (!authorized) {
return;
}
handleLike();
}}
>
<IconComponent
name="Heart"
className={cn(
"h-5 w-5",
liked_by_user
? "fill-destructive stroke-destructive"
: "",
!authorized ? " text-ring" : ""
)}
/>
</Button>
</ShadTooltip>
<DeleteConfirmationModal
onConfirm={() => {
onDelete();
}}
>
<Button
variant="ghost"
size="xs"
className={
"whitespace-nowrap" +
(!authorized ? " cursor-not-allowed" : "")
}
>
<IconComponent
name="Trash2"
className={cn(
"h-5 w-5",
!authorized ? " text-ring" : ""
)}
/>
</Button>
</DeleteConfirmationModal>
</ShadTooltip>
) : (
<ShadTooltip
content={
authorized ? "Like" : "Please review your API key."
}
>
<Button
disabled={loadingLike}
variant="ghost"
size="xs"
className={
"whitespace-nowrap" +
(!authorized ? " cursor-not-allowed" : "")
}
onClick={() => {
if (!authorized) {
return;
}
handleLike();
}}
>
<IconComponent
name="Heart"
className={cn(
"h-5 w-5",
liked_by_user
? "fill-destructive stroke-destructive"
: "",
!authorized ? " text-ring" : ""
)}
/>
</Button>
</ShadTooltip>
)}
<ShadTooltip
content={
authorized

View file

@ -0,0 +1,26 @@
import Header from "../headerComponent";
import { Separator } from "../ui/separator";
export default function PageLayout({
title,
description,
children,
}: {
title: string;
description: string;
children: React.ReactNode;
}) {
return (
<div className="flex h-screen w-full flex-col">
<Header />
<div className="flex h-full w-full flex-col justify-between overflow-auto bg-background px-16">
<div className="flex w-full flex-col justify-between space-y-0.5 py-8 pb-2">
<h2 className="text-2xl font-bold tracking-tight">{title}</h2>
<p className="text-muted-foreground">{description}</p>
</div>
<Separator className="my-6 flex" />
{children}
</div>
</div>
);
}

View file

@ -0,0 +1,56 @@
import { DialogClose } from "@radix-ui/react-dialog";
import { Trash2 } from "lucide-react";
import { Button } from "../../components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "../../components/ui/dialog";
export default function DeleteConfirmationModal({
children,
onConfirm,
description,
}: {
children: JSX.Element;
onConfirm: () => void;
description?: string;
}) {
return (
<Dialog>
<DialogTrigger>{children}</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>
<div className="flex">
Delete <Trash2 className="ml-3 h-4 w-4" strokeWidth={1.5} />
</div>
</DialogTitle>
<DialogDescription>
Are you sure you want to delete this {description ?? "component"}?
<br></br>
This action cannot be undone.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose>
<Button className="mr-3">Cancel</Button>
<Button
type="submit"
onClick={() => {
onConfirm();
}}
>
Delete
</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View file

@ -4,10 +4,9 @@ import PaginatorComponent from "../../components/PaginatorComponent";
import ShadTooltip from "../../components/ShadTooltipComponent";
import CollectionCardComponent from "../../components/cardComponent";
import IconComponent from "../../components/genericIconComponent";
import Header from "../../components/headerComponent";
import PageLayout from "../../components/pageLayout";
import { SkeletonCardComponent } from "../../components/skeletonCardComponent";
import { Badge } from "../../components/ui/badge";
import { Button } from "../../components/ui/button";
import { Input } from "../../components/ui/input";
import {
Select,
@ -21,7 +20,6 @@ import { alertContext } from "../../contexts/alertContext";
import { AuthContext } from "../../contexts/authContext";
import { StoreContext } from "../../contexts/storeContext";
import { getStoreComponents, getStoreTags } from "../../controllers/API";
import StoreApiKeyModal from "../../modals/StoreApiKeyModal";
import { storeComponent } from "../../types/store";
import { cn } from "../../utils/utils";
export default function StorePage(): JSX.Element {
@ -197,237 +195,222 @@ export default function StorePage(): JSX.Element {
};
return (
<>
<Header />
<div className="flex h-full w-full flex-col justify-between overflow-auto bg-background px-16">
<div>
<div className="flex w-full justify-between py-8 pb-2">
<span className="flex items-center justify-center gap-2 text-2xl font-semibold">
<IconComponent name="Store" className="w-6" />
Langflow Store
</span>
<div className="flex gap-2">
{StoreApiKeyModal && (
<StoreApiKeyModal disabled={loading}>
<Button
disabled={loading}
className={cn(
`${!validApiKey ? "animate-pulse border-error" : ""}`,
loading ? "cursor-not-allowed" : ""
)}
variant="primary"
>
<IconComponent name="Key" className="mr-2 w-4" />
API Key
</Button>
</StoreApiKeyModal>
)}
</div>
</div>
<span className="flex w-[70%] pb-8 text-muted-foreground">
Search flows and components from the community.
</span>
<div className="flex w-full flex-col gap-4 p-0">
<div className="flex items-end gap-4">
<div className="relative h-12 w-[40%]">
<Input
disabled={loading}
placeholder="Search Flows and Components"
className="absolute h-12 pl-5 pr-12"
onChange={(e) => {
setInputText(e.target.value);
}}
onKeyDown={(e) => {
if (e.key === "Enter") {
setSearchNow(uniqueId());
}
}}
value={inputText}
/>
<button
disabled={loading}
className="absolute bottom-0 right-4 top-0 my-auto h-6 cursor-pointer stroke-1 text-muted-foreground"
onClick={() => {
<PageLayout
title="Langflow Store"
description="Search flows and components from the community."
/* button={{StoreApiKeyModal && (
<StoreApiKeyModal disabled={loading}>
<Button
disabled={loading}
className={cn(
`${!validApiKey ? "animate-pulse border-error" : ""}`,
loading ? "cursor-not-allowed" : ""
)}
variant="primary"
>
<IconComponent name="Key" className="mr-2 w-4" />
API Key
</Button>
</StoreApiKeyModal>
)}} */
>
<div className="flex h-full w-full flex-col justify-between">
<div className="flex w-full flex-col gap-4 p-0">
<div className="flex items-end gap-4">
<div className="relative h-12 w-[40%]">
<Input
disabled={loading}
placeholder="Search Flows and Components"
className="absolute h-12 pl-5 pr-12"
onChange={(e) => {
setInputText(e.target.value);
}}
onKeyDown={(e) => {
if (e.key === "Enter") {
setSearchNow(uniqueId());
}}
>
<IconComponent
name={loading ? "Loader2" : "Search"}
className={
loading ? " animate-spin cursor-not-allowed" : ""
}
/>
</button>
</div>
<div className="ml-4 flex w-full gap-2 border-b border-border">
<button
disabled={loading}
onClick={() => {
setTabActive("All");
}}
className={
(tabActive === "All"
? "border-b-2 border-primary p-3"
: " border-b-2 border-transparent p-3 text-muted-foreground hover:text-primary") +
(loading ? " cursor-not-allowed " : "")
}
>
All
</button>
<button
disabled={loading}
onClick={() => {
setTabActive("Flows");
}}
className={
(tabActive === "Flows"
? "border-b-2 border-primary p-3"
: " border-b-2 border-transparent p-3 text-muted-foreground hover:text-primary") +
(loading ? " cursor-not-allowed " : "")
}
>
Flows
</button>
<button
disabled={loading}
onClick={() => {
setTabActive("Components");
}}
className={
(tabActive === "Components"
? "border-b-2 border-primary p-3"
: " border-b-2 border-transparent p-3 text-muted-foreground hover:text-primary") +
(loading ? " cursor-not-allowed " : "")
}
>
Components
</button>
<ShadTooltip content="Coming Soon">
<button className="cursor-not-allowed p-3 text-muted-foreground">
Bundles
</button>
</ShadTooltip>
</div>
</div>
<div className="flex items-center gap-2">
<Select
}}
value={inputText}
/>
<button
disabled={loading}
onValueChange={setSelectFilter}
value={selectFilter}
>
<SelectTrigger className="mr-4 w-[160px] flex-shrink-0">
<SelectValue placeholder="Filter Values" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="all">All</SelectItem>
<SelectItem value="createdbyme">Created By Me</SelectItem>
<SelectItem value="likedbyme">Liked By Me</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<div ref={fadeContainerRef} className="fade-container">
<div
ref={scrollContainerRef}
className="scroll-container flex gap-2"
>
{!loadingTags &&
tags.map((tag, idx) => (
<button
disabled={loading}
className={loading ? "cursor-not-allowed" : ""}
onClick={() => {
updateTags(tag.name);
}}
>
<Badge
key={idx}
variant="outline"
size="sq"
className={cn(
filteredCategories.some(
(category) => category === tag.name
)
? "bg-beta-foreground text-background hover:bg-beta-foreground"
: ""
)}
>
{tag.name}
</Badge>
</button>
))}
</div>
</div>
</div>
<div className="flex items-end justify-between">
<span className="px-0.5 text-sm text-muted-foreground">
{(!loading || searchData.length !== 0) && (
<>
{totalRowsCount}{" "}
{totalRowsCount !== 1 ? "results" : "result"}
</>
)}
</span>
<Select
disabled={loading}
onValueChange={(e) => {
setPageOrder(e);
className="absolute bottom-0 right-4 top-0 my-auto h-6 cursor-pointer stroke-1 text-muted-foreground"
onClick={() => {
setSearchNow(uniqueId());
}}
>
<SelectTrigger>
<SelectValue placeholder="Popular" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Popular">Popular</SelectItem>
{/* <SelectItem value="Recent">Most Recent</SelectItem> */}
<SelectItem value="Alphabetical">Alphabetical</SelectItem>
</SelectContent>
</Select>
<IconComponent
name={loading ? "Loader2" : "Search"}
className={loading ? " animate-spin cursor-not-allowed" : ""}
/>
</button>
</div>
<div className="ml-4 flex w-full gap-2 border-b border-border">
<button
disabled={loading}
onClick={() => {
setTabActive("All");
}}
className={
(tabActive === "All"
? "border-b-2 border-primary p-3"
: " border-b-2 border-transparent p-3 text-muted-foreground hover:text-primary") +
(loading ? " cursor-not-allowed " : "")
}
>
All
</button>
<button
disabled={loading}
onClick={() => {
setTabActive("Flows");
}}
className={
(tabActive === "Flows"
? "border-b-2 border-primary p-3"
: " border-b-2 border-transparent p-3 text-muted-foreground hover:text-primary") +
(loading ? " cursor-not-allowed " : "")
}
>
Flows
</button>
<button
disabled={loading}
onClick={() => {
setTabActive("Components");
}}
className={
(tabActive === "Components"
? "border-b-2 border-primary p-3"
: " border-b-2 border-transparent p-3 text-muted-foreground hover:text-primary") +
(loading ? " cursor-not-allowed " : "")
}
>
Components
</button>
<ShadTooltip content="Coming Soon">
<button className="cursor-not-allowed p-3 text-muted-foreground">
Bundles
</button>
</ShadTooltip>
</div>
</div>
<div className="grid w-full gap-4 md:grid-cols-2 lg:grid-cols-3">
{!loading || searchData.length !== 0 ? (
searchData.map((item) => {
return (
<>
<CollectionCardComponent
key={item.id}
data={item}
authorized={validApiKey}
disabled={loading}
/>
</>
);
})
) : (
<div className="flex items-center gap-2">
<Select
disabled={loading}
onValueChange={setSelectFilter}
value={selectFilter}
>
<SelectTrigger className="mr-4 w-[160px] flex-shrink-0">
<SelectValue placeholder="Filter Values" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="all">All</SelectItem>
<SelectItem value="createdbyme">Created By Me</SelectItem>
<SelectItem value="likedbyme">Liked By Me</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<div ref={fadeContainerRef} className="fade-container">
<div
ref={scrollContainerRef}
className="scroll-container flex gap-2"
>
{!loadingTags &&
tags.map((tag, idx) => (
<button
disabled={loading}
className={loading ? "cursor-not-allowed" : ""}
onClick={() => {
updateTags(tag.name);
}}
>
<Badge
key={idx}
variant="outline"
size="sq"
className={cn(
filteredCategories.some(
(category) => category === tag.name
)
? "bg-beta-foreground text-background hover:bg-beta-foreground"
: ""
)}
>
{tag.name}
</Badge>
</button>
))}
</div>
</div>
</div>
<div className="flex items-end justify-between">
<span className="px-0.5 text-sm text-muted-foreground">
{(!loading || searchData.length !== 0) && (
<>
<SkeletonCardComponent />
<SkeletonCardComponent />
<SkeletonCardComponent />
{totalRowsCount} {totalRowsCount !== 1 ? "results" : "result"}
</>
)}
</div>
</span>
{!loading && searchData?.length === 0 && (
<div className="mt-6 flex w-full items-center justify-center text-center">
<div className="flex h-full w-full flex-col">
<div className="flex w-full flex-col gap-4">
<div className="grid w-full gap-4">
You haven't{" "}
{selectFilter === "createdbyme" ? "created" : "liked"}{" "}
anything yet.
</div>
<Select
disabled={loading}
onValueChange={(e) => {
setPageOrder(e);
}}
>
<SelectTrigger>
<SelectValue placeholder="Popular" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Popular">Popular</SelectItem>
{/* <SelectItem value="Recent">Most Recent</SelectItem> */}
<SelectItem value="Alphabetical">Alphabetical</SelectItem>
</SelectContent>
</Select>
</div>
<div className="grid w-full gap-4 md:grid-cols-2 lg:grid-cols-3">
{!loading || searchData.length !== 0 ? (
searchData.map((item) => {
return (
<>
<CollectionCardComponent
key={item.id}
data={item}
authorized={validApiKey}
disabled={loading}
/>
</>
);
})
) : (
<>
<SkeletonCardComponent />
<SkeletonCardComponent />
<SkeletonCardComponent />
</>
)}
</div>
{!loading && searchData?.length === 0 && (
<div className="mt-6 flex w-full items-center justify-center text-center">
<div className="flex h-full w-full flex-col">
<div className="flex w-full flex-col gap-4">
<div className="grid w-full gap-4">
You haven't{" "}
{selectFilter === "createdbyme" ? "created" : "liked"}{" "}
anything yet.
</div>
</div>
</div>
)}
</div>
</div>
)}
</div>
{!loading && searchData.length > 0 && (
<div className="relative my-6">
<div className="relative py-6">
<PaginatorComponent
storeComponent={true}
pageIndex={pageIndex}
@ -441,6 +424,6 @@ export default function StorePage(): JSX.Element {
</div>
)}
</div>
</>
</PageLayout>
);
}