diff --git a/packages/graph-explorer/src/modules/SearchSidebar/BundleSearchResult.tsx b/packages/graph-explorer/src/modules/SearchSidebar/BundleSearchResult.tsx index 16a72415e..67d25ebc7 100644 --- a/packages/graph-explorer/src/modules/SearchSidebar/BundleSearchResult.tsx +++ b/packages/graph-explorer/src/modules/SearchSidebar/BundleSearchResult.tsx @@ -1,18 +1,29 @@ -import { BracketsIcon } from "lucide-react"; +import { useAtomValue } from "jotai"; +import { BracketsIcon, MinusCircleIcon, PlusCircleIcon } from "lucide-react"; import { + Button, + type ButtonProps, CollapsibleContent, SearchResultCollapsible, SearchResultCollapsibleTrigger, SearchResultSubtitle, SearchResultSymbol, SearchResultTitle, + Spinner, + stopPropagation, } from "@/components"; import { + getAllGraphableEntities, getDisplayValueForBundle, type PatchedResultBundle, } from "@/connector/entities"; -import { useTextTransform } from "@/hooks"; +import { edgesAtom, nodesAtom } from "@/core"; +import { + useAddToGraphMutation, + useRemoveFromGraph, + useTextTransform, +} from "@/hooks"; import { createEntityKey, EntitySearchResult } from "./EntitySearchResult"; @@ -33,10 +44,11 @@ export function BundleSearchResult({ -
+
{title && {title}} {subtitle}
+
    @@ -50,3 +62,72 @@ export function BundleSearchResult({ ); } + +/** + * Adds or removes all the graphable entities (vertices and edges) contained + * within a bundle, including nested bundles. + * + * - Hidden entirely when the bundle contains no graphable entities. + * - Shows an add button when any entity in the bundle is missing from the + * graph. Clicking it adds the remaining entities (already-added ones are + * left untouched). + * - Shows a remove button once every entity in the bundle has been added. + * Clicking it removes all of them from the graph. + */ +function AddOrRemoveAllButton({ + bundle, + ...props +}: ButtonProps & { bundle: PatchedResultBundle }) { + const graphableEntities = getAllGraphableEntities(bundle.values); + const vertexIds = graphableEntities.vertices.map(vertex => vertex.id); + const edgeIds = graphableEntities.edges.map(edge => edge.id); + + const nodesInGraph = useAtomValue(nodesAtom); + const edgesInGraph = useAtomValue(edgesAtom); + + const mutation = useAddToGraphMutation(); + const removeFromGraph = useRemoveFromGraph(); + + const hasGraphableEntities = vertexIds.length + edgeIds.length > 0; + if (!hasGraphableEntities) { + return null; + } + + const allAdded = + vertexIds.every(id => nodesInGraph.has(id)) && + edgeIds.every(id => edgesInGraph.has(id)); + + if (allAdded) { + const removeAllFromGraph = () => + removeFromGraph({ vertices: vertexIds, edges: edgeIds }); + + return ( + + ); + } + + const addAllToGraph = () => mutation.mutate(graphableEntities); + + return ( + + ); +}