From f9ae9f27f36a6ae0c55c68f156fa1b00fa89aaf8 Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa Date: Thu, 7 Nov 2024 15:45:26 -0300 Subject: [PATCH] feat: Enhance sidebar components (#4452) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 📝 (pageLayout/index.tsx): update beta icon text from "BETA" to "Beta" for consistency and readability * ✨ (custom-parameter.tsx): add support for flex view in custom parameter title to improve UI flexibility and readability * ✨ (NodeInputField/index.tsx): refactor NodeInputField component to pass isFlexView prop to getCustomParameterTitle function for improved customization and flexibility * 📝 (NodeStatus/index.tsx): update text from "BETA" to "Beta" for consistency and readability * ✨ (flowSidebarComponent/index.tsx): Add new components and features to the Flow Sidebar Component to enhance user experience and functionality. Update the structure and logic of the component to improve search functionality, error handling, and component filtering. * ✨ (emptySearchComponent): add a new component NoResultsMessage to display a message when no components are found in the search result. The component includes options to clear the search, filter, and try a different query. * ✨ (featureTogglesComponent/index.tsx): add a new component FeatureToggles to manage feature toggles for Beta and Legacy features in the sidebar of the FlowPage. * 📝 (sidebarDraggableComponent): Update CSS classes for better styling and spacing 💡 (sidebarDraggableComponent): Improve readability by changing font weight of text elements 💡 (sidebarDraggableComponent): Update text content to be more consistent and capitalized * ✨ (index.tsx): Add a new component SidebarFooterButtons to display buttons in the sidebar footer for adding custom components and navigating to the store page. * ✨ (index.tsx): add a new component SidebarItemsList to render a list of items in the sidebar with tooltips and draggable functionality * ✨ (apply-beta-filter.ts): add a new helper function applyBetaFilter to filter out beta items from the given data based on a specific condition * ✨ (apply-edge-filter.ts): introduce a new helper function applyEdgeFilter to filter data based on edge filters for a better user experience. * ✨ (apply-legacy-filter.ts): introduce a new helper function applyLegacyFilter to filter out legacy data from APIDataType object. * ✨ (combined-results.ts): add a new helper function combinedResultsFn to combine Fuse.js search results with API data for each category * ✨ (filtered-data.ts): introduce a new helper function filteredDataFn to merge and return filtered data from different sources for a given category in the FlowPage component. * ✨ (normalize-string.ts): introduce a new helper function normalizeString to convert a string to lowercase, remove underscores, and spaces for better string normalization. * ✨ (ParentDisclosureComponent): Change "BETA" to "Beta" for consistency in text formatting ✨ (search-on-metadata.ts, traditional-search-metadata.ts): Add helper functions for searching in metadata and traditional search metadata to improve search functionality in the flow sidebar component. --- .../components/NodeInputField/index.tsx | 10 +- .../components/NodeStatus/index.tsx | 2 +- .../src/components/pageLayout/index.tsx | 2 +- .../components/custom-parameter.tsx | 19 +- .../ParentDisclosureComponent/index.tsx | 2 +- .../components/emptySearchComponent/index.tsx | 24 + .../featureTogglesComponent/index.tsx | 51 ++ .../sidebarDraggableComponent/index.tsx | 8 +- .../components/sidebarFooterButtons/index.tsx | 62 ++ .../components/sidebarItemsList/index.tsx | 59 ++ .../helpers/apply-beta-filter.ts | 12 + .../helpers/apply-edge-filter.ts | 25 + .../helpers/apply-legacy-filter.ts | 12 + .../helpers/combined-results.ts | 19 + .../helpers/filtered-data.ts | 22 + .../helpers/normalize-string.ts | 3 + .../helpers/search-on-metadata.ts | 18 + .../helpers/traditional-search-metadata.ts | 23 + .../components/flowSidebarComponent/index.tsx | 764 ++++++------------ 19 files changed, 598 insertions(+), 539 deletions(-) create mode 100644 src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/emptySearchComponent/index.tsx create mode 100644 src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/featureTogglesComponent/index.tsx create mode 100644 src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarFooterButtons/index.tsx create mode 100644 src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarItemsList/index.tsx create mode 100644 src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/apply-beta-filter.ts create mode 100644 src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/apply-edge-filter.ts create mode 100644 src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/apply-legacy-filter.ts create mode 100644 src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/combined-results.ts create mode 100644 src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/filtered-data.ts create mode 100644 src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/normalize-string.ts create mode 100644 src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/search-on-metadata.ts create mode 100644 src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/traditional-search-metadata.ts diff --git a/src/frontend/src/CustomNodes/GenericNode/components/NodeInputField/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/NodeInputField/index.tsx index 28e672bbb..b00576c35 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/NodeInputField/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/NodeInputField/index.tsx @@ -125,7 +125,10 @@ export default function NodeInputField({ {proxy.id}}> { - {getCustomParameterTitle({ title, nodeId: data.id })} + {getCustomParameterTitle({ + title, + isFlexView, + })} } @@ -134,7 +137,10 @@ export default function NodeInputField({ { - {getCustomParameterTitle({ title, nodeId: data.id })} + {getCustomParameterTitle({ + title, + isFlexView, + })} } diff --git a/src/frontend/src/CustomNodes/GenericNode/components/NodeStatus/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/NodeStatus/index.tsx index 5cf4d1820..69e8175e8 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/NodeStatus/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/NodeStatus/index.tsx @@ -263,7 +263,7 @@ export default function NodeStatus({ size="sq" className="pointer-events-none mr-1 flex h-[22px] w-10 justify-center rounded-[8px] bg-accent-pink text-accent-pink-foreground" > - BETA + Beta )} diff --git a/src/frontend/src/components/pageLayout/index.tsx b/src/frontend/src/components/pageLayout/index.tsx index 271532c3c..b2336cb91 100644 --- a/src/frontend/src/components/pageLayout/index.tsx +++ b/src/frontend/src/components/pageLayout/index.tsx @@ -47,7 +47,7 @@ export default function PageLayout({ data-testid="mainpage_title" > {title} - {betaIcon && BETA} + {betaIcon && Beta}

{description}

diff --git a/src/frontend/src/customization/components/custom-parameter.tsx b/src/frontend/src/customization/components/custom-parameter.tsx index cd58c89b8..d357f9b55 100644 --- a/src/frontend/src/customization/components/custom-parameter.tsx +++ b/src/frontend/src/customization/components/custom-parameter.tsx @@ -1,6 +1,7 @@ import { ParameterRenderComponent } from "@/components/parameterRenderComponent"; import { handleOnNewValueType } from "@/CustomNodes/hooks/use-handle-new-value"; import { APIClassType, InputFieldType } from "@/types/api"; +import { cn } from "@/utils/utils"; export function CustomParameterComponent({ handleOnNewValue, @@ -40,18 +41,20 @@ export function CustomParameterComponent({ export function getCustomParameterTitle({ title, - nodeId, + isFlexView, }: { title: string; - nodeId: string; + isFlexView: boolean; }) { return ( - - {title} - +
+ + {title} + +
); } diff --git a/src/frontend/src/pages/FlowPage/components/ParentDisclosureComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/ParentDisclosureComponent/index.tsx index 04793b46e..2f5bd7602 100644 --- a/src/frontend/src/pages/FlowPage/components/ParentDisclosureComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/ParentDisclosureComponent/index.tsx @@ -21,7 +21,7 @@ export default function ParentDisclosureComponent({ {title} {beta && (
- BETA + Beta
)} diff --git a/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/emptySearchComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/emptySearchComponent/index.tsx new file mode 100644 index 000000000..54c03a24b --- /dev/null +++ b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/emptySearchComponent/index.tsx @@ -0,0 +1,24 @@ +import React from "react"; +const NoResultsMessage = ({ + onClearSearch, + message = "No components found.", + clearSearchText = "Clear your search", + additionalText = "or filter and try a different query.", +}) => { + return ( +
+

+ {message}{" "} + + {clearSearchText} + {" "} + {additionalText} +

+
+ ); +}; + +export default NoResultsMessage; diff --git a/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/featureTogglesComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/featureTogglesComponent/index.tsx new file mode 100644 index 000000000..d33fa3c57 --- /dev/null +++ b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/featureTogglesComponent/index.tsx @@ -0,0 +1,51 @@ +import { Badge } from "@/components/ui/badge"; +import { Switch } from "@/components/ui/switch"; +import React from "react"; + +const FeatureToggles = ({ + showBeta, + setShowBeta, + showLegacy, + setShowLegacy, +}) => { + const toggles = [ + { + label: "Beta", + checked: showBeta, + onChange: setShowBeta, + badgeVariant: "pinkStatic" as const, + testId: "sidebar-beta-switch", + }, + { + label: "Legacy", + checked: showLegacy, + onChange: setShowLegacy, + badgeVariant: "secondaryStatic" as const, + testId: "sidebar-legacy-switch", + }, + ]; + + return ( +
+ {toggles.map((toggle) => ( +
+
+ + Show + + {toggle.label} + + +
+ +
+ ))} +
+ ); +}; + +export default FeatureToggles; diff --git a/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarDraggableComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarDraggableComponent/index.tsx index da415e5e7..883c87dd8 100644 --- a/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarDraggableComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarDraggableComponent/index.tsx @@ -121,7 +121,7 @@ export const SidebarDraggableComponent = forwardRef( data-tooltip-id={itemName} tabIndex={0} onKeyDown={handleKeyDown} - className="rounded-md outline-none ring-ring focus-visible:ring-2" + className="m-[1px] rounded-md outline-none ring-ring focus-visible:ring-1" >
- + {display_name} @@ -160,7 +160,7 @@ export const SidebarDraggableComponent = forwardRef( size="xq" className="ml-1.5 shrink-0" > - BETA + Beta )} {legacy && ( @@ -169,7 +169,7 @@ export const SidebarDraggableComponent = forwardRef( size="xq" className="ml-1.5 shrink-0" > - LEGACY + Legacy )}
diff --git a/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarFooterButtons/index.tsx b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarFooterButtons/index.tsx new file mode 100644 index 000000000..6ad8d4965 --- /dev/null +++ b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarFooterButtons/index.tsx @@ -0,0 +1,62 @@ +import ForwardedIconComponent from "@/components/genericIconComponent"; +import { Button } from "@/components/ui/button"; +import { SidebarMenuButton } from "@/components/ui/sidebar"; +import { CustomLink } from "@/customization/components/custom-link"; +import React from "react"; + +const SidebarMenuButtons = ({ + hasStore = false, + customComponent, + addComponent, +}) => { + return ( + <> + {hasStore && ( + + +
+ + + Discover more components + + +
+
+
+ )} + + + + + ); +}; + +export default SidebarMenuButtons; diff --git a/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarItemsList/index.tsx b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarItemsList/index.tsx new file mode 100644 index 000000000..8328c4a64 --- /dev/null +++ b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarItemsList/index.tsx @@ -0,0 +1,59 @@ +import ShadTooltip from "@/components/shadTooltipComponent"; +import { removeCountFromString } from "@/utils/utils"; +import React from "react"; +import SidebarDraggableComponent from "../sidebarDraggableComponent"; + +const SidebarItemsList = ({ + item, + dataFilter, + nodeColors, + chatInputAdded, + onDragStart, + sensitiveSort, +}) => { + return ( +
+ {Object.keys(dataFilter[item.name]) + .sort((a, b) => + sensitiveSort( + dataFilter[item.name][a].display_name, + dataFilter[item.name][b].display_name, + ), + ) + .map((SBItemName, idx) => { + const currentItem = dataFilter[item.name][SBItemName]; + + return ( + + + onDragStart(event, { + type: removeCountFromString(SBItemName), + node: currentItem, + }) + } + color={nodeColors[item.name]} + itemName={SBItemName} + error={!!currentItem.error} + display_name={currentItem.display_name} + official={currentItem.official === false ? false : true} + beta={currentItem.beta ?? false} + legacy={currentItem.legacy ?? false} + disabled={SBItemName === "ChatInput" && chatInputAdded} + disabledTooltip="Chat input already added" + /> + + ); + })} +
+ ); +}; + +export default SidebarItemsList; diff --git a/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/apply-beta-filter.ts b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/apply-beta-filter.ts new file mode 100644 index 000000000..63f910d26 --- /dev/null +++ b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/apply-beta-filter.ts @@ -0,0 +1,12 @@ +import { APIDataType } from "@/types/api"; + +export const applyBetaFilter = (filteredData: APIDataType) => { + return Object.fromEntries( + Object.entries(filteredData).map(([category, items]) => [ + category, + Object.fromEntries( + Object.entries(items).filter(([_, value]) => !value.beta), + ), + ]), + ); +}; diff --git a/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/apply-edge-filter.ts b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/apply-edge-filter.ts new file mode 100644 index 000000000..5d981e98d --- /dev/null +++ b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/apply-edge-filter.ts @@ -0,0 +1,25 @@ +import { APIDataType } from "@/types/api"; + +export const applyEdgeFilter = (filteredData: APIDataType, getFilterEdge) => { + return Object.fromEntries( + Object.entries(filteredData).map(([family, familyData]) => { + const edgeFilter = getFilterEdge.find((x) => x.family === family); + if (!edgeFilter) return [family, {}]; + + const filteredTypes = edgeFilter.type + .split(",") + .map((t) => t.trim()) + .filter((t) => t !== ""); + + if (filteredTypes.length === 0) return [family, familyData]; + + const filteredFamilyData = Object.fromEntries( + Object.entries(familyData).filter(([key]) => + filteredTypes.includes(key), + ), + ); + + return [family, filteredFamilyData]; + }), + ); +}; diff --git a/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/apply-legacy-filter.ts b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/apply-legacy-filter.ts new file mode 100644 index 000000000..d7ac6d5c1 --- /dev/null +++ b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/apply-legacy-filter.ts @@ -0,0 +1,12 @@ +import { APIDataType } from "@/types/api"; + +export const applyLegacyFilter = (filteredData: APIDataType) => { + return Object.fromEntries( + Object.entries(filteredData).map(([category, items]) => [ + category, + Object.fromEntries( + Object.entries(items).filter(([_, value]) => !value.legacy), + ), + ]), + ); +}; diff --git a/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/combined-results.ts b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/combined-results.ts new file mode 100644 index 000000000..5f7e60ed1 --- /dev/null +++ b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/combined-results.ts @@ -0,0 +1,19 @@ +import { APIDataType } from "@/types/api"; +import { FuseResult } from "fuse.js"; + +export const combinedResultsFn = ( + fuseResults: FuseResult[], + data: APIDataType, +) => { + return Object.fromEntries( + Object.entries(data).map(([category]) => { + const categoryResults = fuseResults.filter( + (result) => result.item.category === category, + ); + const filteredItems = Object.fromEntries( + categoryResults.map((result) => [result.item.key, result.item]), + ); + return [category, filteredItems]; + }), + ); +}; diff --git a/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/filtered-data.ts b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/filtered-data.ts new file mode 100644 index 000000000..443a98bb1 --- /dev/null +++ b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/filtered-data.ts @@ -0,0 +1,22 @@ +import { APIDataType } from "@/types/api"; +import { FuseResult } from "fuse.js"; + +export const filteredDataFn = ( + data: APIDataType, + combinedResults, + traditionalResults, +) => { + return Object.fromEntries( + Object.entries(data).map(([category, _]) => { + const fuseItems = combinedResults[category] || {}; + const traditionalItems = traditionalResults[category] || {}; + + const mergedItems = { + ...fuseItems, + ...traditionalItems, + }; + + return [category, mergedItems]; + }), + ); +}; diff --git a/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/normalize-string.ts b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/normalize-string.ts new file mode 100644 index 000000000..6e5434c25 --- /dev/null +++ b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/normalize-string.ts @@ -0,0 +1,3 @@ +export function normalizeString(str: string): string { + return str.toLowerCase().replace(/_/g, " ").replace(/\s+/g, ""); +} diff --git a/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/search-on-metadata.ts b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/search-on-metadata.ts new file mode 100644 index 000000000..c454c058e --- /dev/null +++ b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/search-on-metadata.ts @@ -0,0 +1,18 @@ +import { normalizeString } from "./normalize-string"; + +export function searchInMetadata(metadata: any, searchTerm: string): boolean { + if (!metadata || typeof metadata !== "object") return false; + + return Object.entries(metadata).some(([key, value]) => { + if (typeof value === "string") { + return ( + normalizeString(key).includes(searchTerm) || + normalizeString(value).includes(searchTerm) + ); + } + if (typeof value === "object") { + return searchInMetadata(value, searchTerm); + } + return false; + }); +} diff --git a/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/traditional-search-metadata.ts b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/traditional-search-metadata.ts new file mode 100644 index 000000000..c7fa6deb6 --- /dev/null +++ b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/traditional-search-metadata.ts @@ -0,0 +1,23 @@ +import { APIDataType } from "@/types/api"; +import { normalizeString } from "./normalize-string"; +import { searchInMetadata } from "./search-on-metadata"; + +export const traditionalSearchMetadata = ( + data: APIDataType, + searchTerm: string, +) => { + return Object.fromEntries( + Object.entries(data).map(([category, items]) => { + const filteredItems = Object.fromEntries( + Object.entries(items).filter( + ([key, item]) => + normalizeString(key).includes(searchTerm) || + normalizeString(item.display_name).includes(searchTerm) || + normalizeString(category).includes(searchTerm) || + (item.metadata && searchInMetadata(item.metadata, searchTerm)), + ), + ); + return [category, filteredItems]; + }), + ); +}; diff --git a/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/index.tsx index 39f0e4912..1c4b204d0 100644 --- a/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/index.tsx @@ -45,34 +45,26 @@ import { APIClassType } from "../../../../types/api"; import { SidebarFilterComponent } from "../extraSidebarComponent/sidebarFilterComponent"; import sensitiveSort from "../extraSidebarComponent/utils/sensitive-sort"; import ShortcutDisplay from "../nodeToolbarComponent/shortcutDisplay"; +import NoResultsMessage from "./components/emptySearchComponent"; +import FeatureToggles from "./components/featureTogglesComponent"; import SidebarDraggableComponent from "./components/sidebarDraggableComponent"; +import SidebarMenuButtons from "./components/sidebarFooterButtons"; +import SidebarItemsList from "./components/sidebarItemsList"; +import { applyBetaFilter } from "./helpers/apply-beta-filter"; +import { applyEdgeFilter } from "./helpers/apply-edge-filter"; +import { applyLegacyFilter } from "./helpers/apply-legacy-filter"; +import { combinedResultsFn } from "./helpers/combined-results"; +import { filteredDataFn } from "./helpers/filtered-data"; +import { normalizeString } from "./helpers/normalize-string"; +import { traditionalSearchMetadata } from "./helpers/traditional-search-metadata"; + +const CATEGORIES = SIDEBAR_CATEGORIES; +const BUNDLES = SIDEBAR_BUNDLES; export function FlowSidebarComponent() { const [isInputFocused, setIsInputFocused] = useState(false); const searchInputRef = useRef(null); - useHotkeys("/", (event) => { - event.preventDefault(); - searchInputRef.current?.focus(); - }); - - // Add this new useHotkeys hook - useHotkeys( - "esc", - (event) => { - event.preventDefault(); - searchInputRef.current?.blur(); - }, - { - // Only enable this hotkey when the input is focused - enableOnFormTags: true, - enabled: isInputFocused, - }, - ); - - const categories = SIDEBAR_CATEGORIES; - const bundles = SIDEBAR_BUNDLES; - const data = useTypesStore((state) => state.data); const templates = useTypesStore((state) => state.templates); const getFilterEdge = useFlowStore((state) => state.getFilterEdge); @@ -93,6 +85,69 @@ export function FlowSidebarComponent() { const [showBeta, setShowBeta] = useState(true); const [showLegacy, setShowLegacy] = useState(false); + useHotkeys("/", (event) => { + event.preventDefault(); + searchInputRef.current?.focus(); + }); + + useHotkeys( + "esc", + (event) => { + event.preventDefault(); + searchInputRef.current?.blur(); + }, + { + enableOnFormTags: true, + enabled: isInputFocused, + }, + ); + + useEffect(() => { + filterComponents(); + }, [data, search, filterType, getFilterEdge, showBeta, showLegacy]); + + useEffect(() => { + // show components with error on load + let errors: string[] = []; + Object.keys(templates).forEach((component) => { + if (templates[component].error) { + errors.push(component); + } + }); + if (errors.length > 0) + setErrorData({ title: " Components with errors: ", list: errors }); + }, []); + + useEffect(() => { + if (getFilterEdge.length !== 0) { + setSearch(""); + } + }, [getFilterEdge, data]); + + useEffect(() => { + const options = { + keys: ["display_name", "description", "type", "category"], + threshold: 0.3, + }; + + const fuseData = Object.entries(data).flatMap(([category, items]) => + Object.entries(items).map(([key, value]) => ({ + ...value, + category, + key, + })), + ); + + setFuse(new Fuse(fuseData, options)); + handleSearchInput(search); + }, [data]); + + useEffect(() => { + if (search === "" && getFilterEdge.length === 0) { + setOpenCategories([]); + } + }, [search, getFilterEdge]); + const hasResults = useMemo(() => { return Object.values(dataFilter).some( (category) => Object.keys(category).length > 0, @@ -100,114 +155,48 @@ export function FlowSidebarComponent() { }, [dataFilter]); const [sortedCategories, setSortedCategories] = useState([]); - useEffect(() => { - filterComponents(); - }, [data, search, filterType, getFilterEdge, showBeta, showLegacy]); - function normalizeString(str: string): string { - return str.toLowerCase().replace(/_/g, " ").replace(/\s+/g, ""); - } - - function searchInMetadata(metadata: any, searchTerm: string): boolean { - if (!metadata || typeof metadata !== "object") return false; - - return Object.entries(metadata).some(([key, value]) => { - if (typeof value === "string") { - return ( - normalizeString(key).includes(searchTerm) || - normalizeString(value).includes(searchTerm) - ); - } - if (typeof value === "object") { - return searchInMetadata(value, searchTerm); - } - return false; - }); - } - const filterComponents = () => { let filteredData = cloneDeep(data); - // Apply search filter - if (search && fuse) { - const results = fuse.search(search); - setSortedCategories(results.map((result) => result.item.category)); - filteredData = Object.fromEntries( - Object.entries(data).map(([category, items]) => { - const categoryResults = results.filter( - (result) => result.item.category === category, - ); - const filteredItems = Object.fromEntries( - categoryResults.map((result) => [result.item.key, result.item]), - ); - return [category, filteredItems]; - }), - ); - } else { - // Fallback to traditional search if Fuse.js is not available + if (search) { const searchTerm = normalizeString(search); - filteredData = Object.fromEntries( - Object.entries(data).map(([category, items]) => { - const filteredItems = Object.fromEntries( - Object.entries(items).filter( - ([key, item]) => - normalizeString(key).includes(searchTerm) || - normalizeString(item.display_name).includes(searchTerm) || - normalizeString(category).includes(searchTerm) || - (item.metadata && searchInMetadata(item.metadata, searchTerm)), - ), - ); - return [category, filteredItems]; - }), - ); + let combinedResults = {}; + + if (fuse) { + const fuseResults = fuse.search(search); + setSortedCategories(fuseResults.map((result) => result.item.category)); + + combinedResults = combinedResultsFn(fuseResults, data); + + const traditionalResults = traditionalSearchMetadata(data, searchTerm); + + filteredData = filteredDataFn( + data, + combinedResults, + traditionalResults, + ); + + setSortedCategories( + Object.keys(filteredData).filter( + (category) => Object.keys(filteredData[category]).length > 0, + ), + ); + } } // Apply edge filter if (getFilterEdge?.length > 0) { - filteredData = Object.fromEntries( - Object.entries(filteredData).map(([family, familyData]) => { - const edgeFilter = getFilterEdge.find((x) => x.family === family); - if (!edgeFilter) return [family, {}]; - - const filteredTypes = edgeFilter.type - .split(",") - .map((t) => t.trim()) - .filter((t) => t !== ""); - - if (filteredTypes.length === 0) return [family, familyData]; - - const filteredFamilyData = Object.fromEntries( - Object.entries(familyData).filter(([key]) => - filteredTypes.includes(key), - ), - ); - - return [family, filteredFamilyData]; - }), - ); + filteredData = applyEdgeFilter(filteredData, getFilterEdge); } // Apply beta filter if (!showBeta) { - filteredData = Object.fromEntries( - Object.entries(filteredData).map(([category, items]) => [ - category, - Object.fromEntries( - Object.entries(items).filter(([_, value]) => !value.beta), - ), - ]), - ); + filteredData = applyBetaFilter(filteredData); } // Apply legacy filter if (!showLegacy) { - filteredData = Object.fromEntries( - Object.entries(filteredData).map(([category, items]) => [ - category, - Object.fromEntries( - Object.entries(items).filter(([_, value]) => !value.legacy), - ), - ]), - ); + filteredData = applyLegacyFilter(filteredData); } setFilterData(filteredData); @@ -220,12 +209,6 @@ export function FlowSidebarComponent() { } }; - useEffect(() => { - if (search === "" && getFilterEdge.length === 0) { - setOpenCategories([]); - } - }, [search, getFilterEdge]); - function handleSearchInput(e: string) { setSearch(e); filterComponents(); @@ -247,42 +230,6 @@ export function FlowSidebarComponent() { event.dataTransfer.setData("genericNode", JSON.stringify(data)); } - useEffect(() => { - // show components with error on load - let errors: string[] = []; - Object.keys(templates).forEach((component) => { - if (templates[component].error) { - errors.push(component); - } - }); - if (errors.length > 0) - setErrorData({ title: " Components with errors: ", list: errors }); - }, []); - - useEffect(() => { - if (getFilterEdge.length !== 0) { - setSearch(""); - } - }, [getFilterEdge, data]); - - useEffect(() => { - const options = { - keys: ["display_name", "description", "type"], - threshold: 0.3, - }; - - const fuseData = Object.entries(data).flatMap(([category, items]) => - Object.entries(items).map(([key, value]) => ({ - ...value, - category, - key, - })), - ); - - setFuse(new Fuse(fuseData, options)); - handleSearchInput(search); - }, [data]); - const customComponent = useMemo(() => { return data?.["custom_component"]?.["CustomComponent"] ?? null; }, [data]); @@ -301,12 +248,12 @@ export function FlowSidebarComponent() { } }; - const hasBundleItems = bundles.some( + const hasBundleItems = BUNDLES.some( (item) => dataFilter[item.name] && Object.keys(dataFilter[item.name]).length > 0, ); - const hasCategoryItems = categories.some( + const hasCategoryItems = CATEGORIES.some( (item) => dataFilter[item.name] && Object.keys(dataFilter[item.name]).length > 0, ); @@ -318,7 +265,6 @@ export function FlowSidebarComponent() { } const nodes = useFlowStore((state) => state.nodes); - const chatInputAdded = checkChatInput(nodes); return ( @@ -348,38 +294,12 @@ export function FlowSidebarComponent() {
-
-
-
- - Show - - BETA - - -
- -
-
-
- - Show - - LEGACY - - -
- -
-
+
@@ -399,8 +319,8 @@ export function FlowSidebarComponent() { onChange={(e) => handleSearchInput(e.target.value)} /> {!isInputFocused && search === "" && ( -
- Type{" "} +
+ Search{" "} @@ -432,151 +352,70 @@ export function FlowSidebarComponent() { )) - : categories - .toSorted( - (a, b) => - (search !== "" - ? sortedCategories - : categories - ).findIndex((value) => value === a.name) - - (search !== "" - ? sortedCategories - : categories - ).findIndex((value) => value === b.name), - ) - .map( - (item) => - dataFilter[item.name] && - Object.keys(dataFilter[item.name]).length > 0 && ( - { - setOpenCategories((prev) => - isOpen - ? [...prev, item.name] - : prev.filter( - (cat) => cat !== item.name, - ), - ); - }} - > - - - -
- handleKeyDown(e, item.name) - } - className="flex cursor-pointer items-center gap-2" - > - - - {item.display_name} - - -
-
-
- -
- {Object.keys(dataFilter[item.name]) - .sort((a, b) => - sensitiveSort( - dataFilter[item.name][a] - .display_name, - dataFilter[item.name][b] - .display_name, - ), - ) - .map((SBItemName: string, idx) => ( - - - onDragStart(event, { - type: removeCountFromString( - SBItemName, - ), - node: dataFilter[item.name][ - SBItemName - ], - }) - } - color={nodeColors[item.name]} - itemName={SBItemName} - error={ - !!dataFilter[item.name][ - SBItemName - ].error - } - display_name={ - dataFilter[item.name][ - SBItemName - ].display_name - } - official={ - dataFilter[item.name][ - SBItemName - ].official === false - ? false - : true - } - beta={ - dataFilter[item.name][ - SBItemName - ].beta ?? false - } - legacy={ - dataFilter[item.name][ - SBItemName - ].legacy ?? false - } - disabled={ - SBItemName === "ChatInput" && - chatInputAdded - } - disabledTooltip="Chat input already added" - /> - - ))} + : CATEGORIES.toSorted( + (a, b) => + (search !== "" + ? sortedCategories + : CATEGORIES + ).findIndex((value) => value === a.name) - + (search !== "" + ? sortedCategories + : CATEGORIES + ).findIndex((value) => value === b.name), + ).map( + (item) => + dataFilter[item.name] && + Object.keys(dataFilter[item.name]).length > 0 && ( + { + setOpenCategories((prev) => + isOpen + ? [...prev, item.name] + : prev.filter((cat) => cat !== item.name), + ); + }} + > + + + +
+ handleKeyDown(e, item.name) + } + className="flex cursor-pointer items-center gap-2" + > + + + {item.display_name} + +
- -
-
- ), - )} + + + + + + + + ), + )} @@ -586,201 +425,82 @@ export function FlowSidebarComponent() { Bundles - {bundles - .toSorted( - (a, b) => - (search !== "" - ? sortedCategories - : bundles - ).findIndex((value) => value === a.name) - - (search !== "" - ? sortedCategories - : bundles - ).findIndex((value) => value === b.name), - ) - .map( - (item) => - dataFilter[item.name] && - Object.keys(dataFilter[item.name]).length > 0 && ( - { - setOpenCategories((prev) => - isOpen - ? [...prev, item.name] - : prev.filter((cat) => cat !== item.name), - ); - }} - > - - - -
- handleKeyDown(e, item.name) - } - className="flex cursor-pointer items-center gap-2" - > - - - {item.display_name} - - -
-
-
- -
- {Object.keys(dataFilter[item.name]) - .sort((a, b) => - sensitiveSort( - dataFilter[item.name][a].display_name, - dataFilter[item.name][b].display_name, - ), - ) - .map((SBItemName: string, idx) => ( - - - onDragStart(event, { - type: removeCountFromString( - SBItemName, - ), - node: dataFilter[item.name][ - SBItemName - ], - }) - } - color={nodeColors[item.name]} - itemName={SBItemName} - error={ - !!dataFilter[item.name][ - SBItemName - ].error - } - display_name={ - dataFilter[item.name][SBItemName] - .display_name - } - official={ - dataFilter[item.name][SBItemName] - .official === false - ? false - : true - } - beta={ - dataFilter[item.name][SBItemName] - .beta ?? false - } - legacy={ - dataFilter[item.name][SBItemName] - .legacy ?? false - } - disabled={ - SBItemName === "ChatInput" && - chatInputAdded - } - disabledTooltip="Chat input already added" - /> - - ))} + {BUNDLES.toSorted( + (a, b) => + (search !== "" ? sortedCategories : BUNDLES).findIndex( + (value) => value === a.name, + ) - + (search !== "" ? sortedCategories : BUNDLES).findIndex( + (value) => value === b.name, + ), + ).map( + (item) => + dataFilter[item.name] && + Object.keys(dataFilter[item.name]).length > 0 && ( + { + setOpenCategories((prev) => + isOpen + ? [...prev, item.name] + : prev.filter((cat) => cat !== item.name), + ); + }} + > + + + +
+ handleKeyDown(e, item.name) + } + className="flex cursor-pointer items-center gap-2" + > + + + {item.display_name} + +
- -
-
- ), - )} + + + + + + + + ), + )} )} ) : ( -
-

- No components found.{" "} - - Clear your search - {" "} - or filter and try a different query. -

-
+ )} - {hasStore && ( - - -
- - - Discover more components - - -
-
-
- )} - - - +
);