diff --git a/src/components/Edges/CustomEdge.tsx b/src/components/Edges/CustomEdge.tsx index c1033a5..8990801 100644 --- a/src/components/Edges/CustomEdge.tsx +++ b/src/components/Edges/CustomEdge.tsx @@ -33,7 +33,7 @@ const CustomEdge = ({ selected, }: EdgeProps) => { const edgeTypes = useGraphStore((state) => state.edgeTypes); - const { visibleRelationTypes } = useSearchStore(); + const { searchText, visibleRelationTypes } = useSearchStore(); // Calculate the bezier path const [edgePath, labelX, labelY] = getBezierPath({ @@ -64,16 +64,33 @@ const CustomEdge = ({ const directionality = data?.directionality || edgeTypeConfig?.defaultDirectionality || 'directed'; // Check if this edge matches the filter criteria - const isVisible = useMemo(() => { + const isMatch = useMemo(() => { + // Check type visibility const edgeType = data?.type || ''; - return visibleRelationTypes[edgeType] !== false; - }, [data?.type, visibleRelationTypes]); + const isTypeVisible = visibleRelationTypes[edgeType] !== false; + if (!isTypeVisible) { + return false; + } + + // Check search text match + if (searchText.trim()) { + const searchLower = searchText.toLowerCase(); + const label = data?.label?.toLowerCase() || ''; + const typeName = edgeTypeConfig?.label?.toLowerCase() || ''; + + return label.includes(searchLower) || typeName.includes(searchLower); + } + + return true; + }, [searchText, visibleRelationTypes, data?.type, data?.label, edgeTypeConfig?.label]); // Determine if filters are active - const hasActiveFilters = Object.values(visibleRelationTypes).some(v => v === false); + const hasActiveFilters = + searchText.trim() !== '' || + Object.values(visibleRelationTypes).some(v => v === false); // Calculate opacity based on visibility - const edgeOpacity = hasActiveFilters && !isVisible ? 0.2 : 1.0; + const edgeOpacity = hasActiveFilters && !isMatch ? 0.2 : 1.0; // Create unique marker IDs based on color (for reusability) const safeColor = edgeColor.replace('#', ''); diff --git a/src/components/Panels/LeftPanel.tsx b/src/components/Panels/LeftPanel.tsx index 5b7ce96..8d70163 100644 --- a/src/components/Panels/LeftPanel.tsx +++ b/src/components/Panels/LeftPanel.tsx @@ -50,7 +50,7 @@ const LeftPanel = forwardRef(({ onDeselectAll, onA expandLeftPanel, } = usePanelStore(); - const { nodeTypes, edgeTypes, addNode, nodes } = useGraphWithHistory(); + const { nodeTypes, edgeTypes, addNode, nodes, edges } = useGraphWithHistory(); const { selectedRelationType, setSelectedRelationType } = useEditorStore(); const [showNodeConfig, setShowNodeConfig] = useState(false); const [showEdgeConfig, setShowEdgeConfig] = useState(false); @@ -142,6 +142,38 @@ const LeftPanel = forwardRef(({ onDeselectAll, onA }); }, [nodes, searchText, visibleActorTypes, nodeTypes]); + // Calculate matching edges based on search and filters + const matchingEdges = useMemo(() => { + const searchLower = searchText.toLowerCase().trim(); + + return edges.filter((edge) => { + const edgeType = edge.data?.type || ''; + + // Filter by edge type visibility + const isTypeVisible = visibleRelationTypes[edgeType] !== false; + if (!isTypeVisible) { + return false; + } + + // Filter by search text + if (searchLower) { + const label = edge.data?.label?.toLowerCase() || ''; + const edgeTypeConfig = edgeTypes.find((et) => et.id === edgeType); + const typeName = edgeTypeConfig?.label?.toLowerCase() || ''; + + const matches = + label.includes(searchLower) || + typeName.includes(searchLower); + + if (!matches) { + return false; + } + } + + return true; + }); + }, [edges, searchText, visibleRelationTypes, edgeTypes]); + const handleAddNode = useCallback( (nodeTypeId: string) => { @@ -389,7 +421,7 @@ const LeftPanel = forwardRef(({ onDeselectAll, onA type="text" value={searchText} onChange={(e) => setSearchText(e.target.value)} - placeholder="Search actors..." + placeholder="Search actors and relations..." className="w-full pl-9 pr-9 py-2 text-sm border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500" /> {searchText && ( @@ -490,10 +522,17 @@ const LeftPanel = forwardRef(({ onDeselectAll, onA {/* Results Summary */}
-
- Results:{' '} - {matchingNodes.length} actor{matchingNodes.length !== 1 ? 's' : ''} - {searchText || hasActiveFilters() ? ` of ${nodes.length}` : ''} +
+
+ Actors:{' '} + {matchingNodes.length} + {searchText || hasActiveFilters() ? ` of ${nodes.length}` : ''} +
+
+ Relations:{' '} + {matchingEdges.length} + {searchText || hasActiveFilters() ? ` of ${edges.length}` : ''} +
diff --git a/src/stores/searchStore.ts b/src/stores/searchStore.ts index dd2dbdc..7211843 100644 --- a/src/stores/searchStore.ts +++ b/src/stores/searchStore.ts @@ -4,14 +4,14 @@ import { create } from 'zustand'; * SearchStore - Manages search and filter state * * Features: - * - Search text for filtering nodes by label, description, or type + * - Search text for filtering both actors (by label, description, or type) and relations (by label or type) * - Filter by actor types (show/hide specific node types) * - Filter by relation types (show/hide specific edge types) * - Results tracking */ interface SearchStore { - // Search text + // Search text (applies to both actors and edges) searchText: string; setSearchText: (text: string) => void;