Modularized Tags Selection and created share modal
This commit is contained in:
parent
b21b756982
commit
0a0c9ac27b
3 changed files with 133 additions and 164 deletions
|
|
@ -1,26 +1,101 @@
|
||||||
import TagComponent from "./tagComponent";
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import { cn } from "../../utils/utils";
|
||||||
|
import { Badge } from "../ui/badge";
|
||||||
|
|
||||||
export function TagsSelector({
|
export function TagsSelector({
|
||||||
tags,
|
tags,
|
||||||
|
disabled = false,
|
||||||
|
loadingTags,
|
||||||
selectedTags,
|
selectedTags,
|
||||||
setSelectedTags,
|
setSelectedTags,
|
||||||
}: {
|
}: {
|
||||||
tags: string[];
|
tags: { id: string; name: string }[];
|
||||||
selectedTags: Set<string>;
|
disabled?: boolean;
|
||||||
setSelectedTags: (tag: string) => void;
|
loadingTags: boolean;
|
||||||
|
selectedTags: any[];
|
||||||
|
setSelectedTags: (tags: any[]) => void;
|
||||||
}) {
|
}) {
|
||||||
|
const updateTags = (tagName: string) => {
|
||||||
|
const index = selectedTags.indexOf(tagName);
|
||||||
|
let newArray =
|
||||||
|
index === -1
|
||||||
|
? [...selectedTags, tagName]
|
||||||
|
: selectedTags.filter((_, i) => i !== index);
|
||||||
|
setSelectedTags(newArray);
|
||||||
|
};
|
||||||
|
|
||||||
|
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const fadeContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [divWidth, setDivWidth] = useState<number>(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = () => {
|
||||||
|
if (scrollContainerRef.current) {
|
||||||
|
setDivWidth(scrollContainerRef.current.clientWidth);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("resize", handleResize);
|
||||||
|
handleResize(); // call the function at start to get the initial width
|
||||||
|
return () => window.removeEventListener("resize", handleResize);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleScroll = () => {
|
||||||
|
if (!scrollContainerRef.current || !fadeContainerRef.current) return;
|
||||||
|
|
||||||
|
const { scrollLeft, scrollWidth, clientWidth } =
|
||||||
|
scrollContainerRef.current;
|
||||||
|
const atStart = scrollLeft === 0;
|
||||||
|
const atEnd = scrollLeft === scrollWidth - clientWidth;
|
||||||
|
const isScrollable = scrollWidth > clientWidth;
|
||||||
|
|
||||||
|
fadeContainerRef.current.classList.toggle(
|
||||||
|
"fade-left",
|
||||||
|
isScrollable && !atStart
|
||||||
|
);
|
||||||
|
fadeContainerRef.current.classList.toggle(
|
||||||
|
"fade-right",
|
||||||
|
isScrollable && !atEnd
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const scrollContainer = scrollContainerRef.current;
|
||||||
|
if (scrollContainer) {
|
||||||
|
scrollContainer.addEventListener("scroll", handleScroll);
|
||||||
|
// Delay the initial scroll event dispatch to ensure correct calculation
|
||||||
|
scrollContainer.dispatchEvent(new Event("scroll"));
|
||||||
|
return () => scrollContainer.removeEventListener("scroll", handleScroll);
|
||||||
|
}
|
||||||
|
}, [divWidth, loadingTags]); // Depend on divWidth
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className=" flex h-full w-full flex-row flex-wrap gap-1 align-middle">
|
<div ref={fadeContainerRef} className="fade-container">
|
||||||
{tags.map((tag, index) => {
|
<div ref={scrollContainerRef} className="scroll-container flex gap-2">
|
||||||
return (
|
{!loadingTags &&
|
||||||
<TagComponent
|
tags.map((tag, idx) => (
|
||||||
tag={tag}
|
<button
|
||||||
selected={selectedTags.has(tag)}
|
disabled={disabled}
|
||||||
key={index}
|
className={disabled ? "cursor-not-allowed" : ""}
|
||||||
handleClick={setSelectedTags}
|
onClick={() => {
|
||||||
/>
|
updateTags(tag.name);
|
||||||
);
|
}}
|
||||||
})}
|
>
|
||||||
|
<Badge
|
||||||
|
key={idx}
|
||||||
|
variant="outline"
|
||||||
|
size="sq"
|
||||||
|
className={cn(
|
||||||
|
selectedTags.some((category) => category === tag.name)
|
||||||
|
? "bg-beta-foreground text-background hover:bg-beta-foreground"
|
||||||
|
: ""
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{tag.name}
|
||||||
|
</Badge>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import {
|
||||||
import EditFlowSettings from "../../components/EditFlowSettingsComponent";
|
import EditFlowSettings from "../../components/EditFlowSettingsComponent";
|
||||||
import IconComponent from "../../components/genericIconComponent";
|
import IconComponent from "../../components/genericIconComponent";
|
||||||
import { TagsSelector } from "../../components/tagsSelectorComponent";
|
import { TagsSelector } from "../../components/tagsSelectorComponent";
|
||||||
import ToggleShadComponent from "../../components/toggleShadComponent";
|
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
import { Checkbox } from "../../components/ui/checkbox";
|
import { Checkbox } from "../../components/ui/checkbox";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
import { alertContext } from "../../contexts/alertContext";
|
||||||
|
|
@ -42,36 +41,29 @@ const ShareModal = forwardRef(
|
||||||
|
|
||||||
const nameComponent = props.is_component ? "Component" : "Flow";
|
const nameComponent = props.is_component ? "Component" : "Flow";
|
||||||
|
|
||||||
const [tags, setTags] = useState<string[]>([]);
|
const [tags, setTags] = useState<{ id: string; name: string }[]>([]);
|
||||||
|
const [loadingTags, setLoadingTags] = useState<boolean>(false);
|
||||||
const [sharePublic, setSharePublic] = useState(true);
|
const [sharePublic, setSharePublic] = useState(true);
|
||||||
const [selectedTags, setSelectedTags] = useState<Set<string>>(new Set());
|
const [selectedTags, setSelectedTags] = useState<string[]>([]);
|
||||||
const tagListId = useRef<{ id: string; name: string }[]>([]);
|
const tagListId = useRef<{ id: string; name: string }[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getStoreTags().then((res) => {
|
handleGetTags();
|
||||||
tagListId.current = res;
|
|
||||||
let tags = res.map((tag) => tag.name);
|
|
||||||
setTags(tags);
|
|
||||||
});
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
function handleGetTags() {
|
||||||
|
setLoadingTags(true);
|
||||||
|
getStoreTags().then((res) => {
|
||||||
|
setTags(res);
|
||||||
|
setLoadingTags(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setName(props.component?.name ?? "");
|
setName(props.component?.name ?? "");
|
||||||
setDescription(props.component?.description ?? "");
|
setDescription(props.component?.description ?? "");
|
||||||
}, [props.component]);
|
}, [props.component]);
|
||||||
|
|
||||||
function handleTagSelection(tag: string) {
|
|
||||||
setSelectedTags((prev) => {
|
|
||||||
const newSet = new Set(prev);
|
|
||||||
if (newSet.has(tag)) {
|
|
||||||
newSet.delete(tag);
|
|
||||||
} else {
|
|
||||||
newSet.add(tag);
|
|
||||||
}
|
|
||||||
return newSet;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleShareComponent = () => {
|
const handleShareComponent = () => {
|
||||||
const saveFlow: FlowType = checked
|
const saveFlow: FlowType = checked
|
||||||
? {
|
? {
|
||||||
|
|
@ -92,7 +84,7 @@ const ShareModal = forwardRef(
|
||||||
});
|
});
|
||||||
saveFlowStore(
|
saveFlowStore(
|
||||||
saveFlow,
|
saveFlow,
|
||||||
getTagsIds(Array.from(selectedTags), tagListId),
|
getTagsIds(selectedTags, tagListId),
|
||||||
sharePublic
|
sharePublic
|
||||||
).then(
|
).then(
|
||||||
() => {
|
() => {
|
||||||
|
|
@ -138,6 +130,27 @@ const ShareModal = forwardRef(
|
||||||
setName={setName}
|
setName={setName}
|
||||||
setDescription={setDescription}
|
setDescription={setDescription}
|
||||||
/>
|
/>
|
||||||
|
<div className="mt-3 flex h-8 w-full">
|
||||||
|
<TagsSelector
|
||||||
|
tags={tags}
|
||||||
|
loadingTags={loadingTags}
|
||||||
|
disabled={false}
|
||||||
|
selectedTags={selectedTags}
|
||||||
|
setSelectedTags={setSelectedTags}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mt-5 flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="terms"
|
||||||
|
checked={sharePublic}
|
||||||
|
onCheckedChange={(event: boolean) => {
|
||||||
|
setSharePublic(event);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor="terms" className="export-modal-save-api text-sm ">
|
||||||
|
Make {nameComponent} Public
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<div className="mt-3 flex items-center space-x-2">
|
<div className="mt-3 flex items-center space-x-2">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="terms"
|
id="terms"
|
||||||
|
|
@ -154,42 +167,6 @@ const ShareModal = forwardRef(
|
||||||
Caution: Uncheck this box only removes API keys from fields
|
Caution: Uncheck this box only removes API keys from fields
|
||||||
specifically designated for API keys.
|
specifically designated for API keys.
|
||||||
</span>
|
</span>
|
||||||
<div className="flex h-full w-full flex-col gap-3">
|
|
||||||
<div className="flex justify-start pt-4 align-middle">
|
|
||||||
<ToggleShadComponent
|
|
||||||
disabled={false}
|
|
||||||
size="medium"
|
|
||||||
setEnabled={setSharePublic}
|
|
||||||
enabled={sharePublic}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className="cursor-pointer pl-1"
|
|
||||||
onClick={() => {
|
|
||||||
setSharePublic(!sharePublic);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{sharePublic ? (
|
|
||||||
<span>
|
|
||||||
This flow will be avaliable <b>for everyone</b>
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<span>
|
|
||||||
This flow will be avaliable <b>just for you</b>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="w-full pt-2">
|
|
||||||
<span className="text-sm">
|
|
||||||
Add some tags to your {nameComponent}
|
|
||||||
</span>
|
|
||||||
<TagsSelector
|
|
||||||
tags={tags}
|
|
||||||
selectedTags={selectedTags}
|
|
||||||
setSelectedTags={handleTagSelection}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</BaseModal.Content>
|
</BaseModal.Content>
|
||||||
|
|
||||||
<BaseModal.Footer>
|
<BaseModal.Footer>
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
import { uniqueId } from "lodash";
|
import { uniqueId } from "lodash";
|
||||||
import { useContext, useEffect, useRef, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import PaginatorComponent from "../../components/PaginatorComponent";
|
import PaginatorComponent from "../../components/PaginatorComponent";
|
||||||
import ShadTooltip from "../../components/ShadTooltipComponent";
|
import ShadTooltip from "../../components/ShadTooltipComponent";
|
||||||
import CollectionCardComponent from "../../components/cardComponent";
|
import CollectionCardComponent from "../../components/cardComponent";
|
||||||
import IconComponent from "../../components/genericIconComponent";
|
import IconComponent from "../../components/genericIconComponent";
|
||||||
import PageLayout from "../../components/pageLayout";
|
import PageLayout from "../../components/pageLayout";
|
||||||
import { SkeletonCardComponent } from "../../components/skeletonCardComponent";
|
import { SkeletonCardComponent } from "../../components/skeletonCardComponent";
|
||||||
import { Badge } from "../../components/ui/badge";
|
|
||||||
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 { TagsSelector } from "../../components/tagsSelectorComponent";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
|
|
@ -44,51 +44,6 @@ export default function StorePage(): JSX.Element {
|
||||||
const [searchNow, setSearchNow] = useState("");
|
const [searchNow, setSearchNow] = useState("");
|
||||||
const [selectFilter, setSelectFilter] = useState("all");
|
const [selectFilter, setSelectFilter] = useState("all");
|
||||||
|
|
||||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
|
||||||
const fadeContainerRef = useRef<HTMLDivElement>(null);
|
|
||||||
const [divWidth, setDivWidth] = useState<number>(0);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleResize = () => {
|
|
||||||
if (scrollContainerRef.current) {
|
|
||||||
setDivWidth(scrollContainerRef.current.clientWidth);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener("resize", handleResize);
|
|
||||||
handleResize(); // call the function at start to get the initial width
|
|
||||||
return () => window.removeEventListener("resize", handleResize);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleScroll = () => {
|
|
||||||
if (!scrollContainerRef.current || !fadeContainerRef.current) return;
|
|
||||||
|
|
||||||
const { scrollLeft, scrollWidth, clientWidth } =
|
|
||||||
scrollContainerRef.current;
|
|
||||||
const atStart = scrollLeft === 0;
|
|
||||||
const atEnd = scrollLeft === scrollWidth - clientWidth;
|
|
||||||
const isScrollable = scrollWidth > clientWidth;
|
|
||||||
|
|
||||||
fadeContainerRef.current.classList.toggle(
|
|
||||||
"fade-left",
|
|
||||||
isScrollable && !atStart
|
|
||||||
);
|
|
||||||
fadeContainerRef.current.classList.toggle(
|
|
||||||
"fade-right",
|
|
||||||
isScrollable && !atEnd
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const scrollContainer = scrollContainerRef.current;
|
|
||||||
if (scrollContainer) {
|
|
||||||
scrollContainer.addEventListener("scroll", handleScroll);
|
|
||||||
// Delay the initial scroll event dispatch to ensure correct calculation
|
|
||||||
scrollContainer.dispatchEvent(new Event("scroll"));
|
|
||||||
return () => scrollContainer.removeEventListener("scroll", handleScroll);
|
|
||||||
}
|
|
||||||
}, [divWidth, loadingTags]); // Depend on divWidth
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
handleGetTags();
|
handleGetTags();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
@ -184,19 +139,6 @@ export default function StorePage(): JSX.Element {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateTags = (tagName: string) => {
|
|
||||||
setFilterCategories((prevArray) => {
|
|
||||||
const index = prevArray.indexOf(tagName);
|
|
||||||
if (index === -1) {
|
|
||||||
// Item does not exist in array, add it
|
|
||||||
return [...prevArray, tagName];
|
|
||||||
} else {
|
|
||||||
// Item exists in array, remove it
|
|
||||||
return prevArray.filter((_, i) => i !== index);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageLayout
|
<PageLayout
|
||||||
title="Langflow Store"
|
title="Langflow Store"
|
||||||
|
|
@ -320,38 +262,13 @@ export default function StorePage(): JSX.Element {
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<div ref={fadeContainerRef} className="fade-container">
|
<TagsSelector
|
||||||
<div
|
tags={tags}
|
||||||
ref={scrollContainerRef}
|
loadingTags={loadingTags}
|
||||||
className="scroll-container flex gap-2"
|
disabled={loading}
|
||||||
>
|
selectedTags={filteredCategories}
|
||||||
{!loadingTags &&
|
setSelectedTags={setFilterCategories}
|
||||||
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>
|
||||||
<div className="flex items-end justify-between">
|
<div className="flex items-end justify-between">
|
||||||
<span className="px-0.5 text-sm text-muted-foreground">
|
<span className="px-0.5 text-sm text-muted-foreground">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue