mirror of
https://github.com/OFFIS-ESC/constellation-analyzer
synced 2026-01-27 07:43:41 +00:00
feat: add edge search functionality to filter section
Extends the search and filter system to include edge/relation searching alongside the existing actor search functionality. Changes: - Search input now searches both actors AND relations - Edges are filtered by custom label or relation type name - Updated placeholder text to "Search actors and relations..." - Results summary now shows separate counts for actors and relations - Non-matching edges are dimmed to 20% opacity when filters are active - Search logic applies consistently across CustomEdge and LeftPanel The same search text field is used for both actors and relations, providing a unified search experience across the entire graph. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
ba6606d8b9
commit
f9c208d7ac
3 changed files with 70 additions and 14 deletions
|
|
@ -33,7 +33,7 @@ const CustomEdge = ({
|
|||
selected,
|
||||
}: EdgeProps<RelationData>) => {
|
||||
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('#', '');
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ const LeftPanel = forwardRef<LeftPanelRef, LeftPanelProps>(({ 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<LeftPanelRef, LeftPanelProps>(({ 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<LeftPanelRef, LeftPanelProps>(({ 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<LeftPanelRef, LeftPanelProps>(({ onDeselectAll, onA
|
|||
|
||||
{/* Results Summary */}
|
||||
<div className="pt-2 border-t border-gray-100">
|
||||
<div className="text-xs text-gray-600">
|
||||
<span className="font-medium">Results:</span>{' '}
|
||||
{matchingNodes.length} actor{matchingNodes.length !== 1 ? 's' : ''}
|
||||
{searchText || hasActiveFilters() ? ` of ${nodes.length}` : ''}
|
||||
<div className="text-xs text-gray-600 space-y-1">
|
||||
<div>
|
||||
<span className="font-medium">Actors:</span>{' '}
|
||||
{matchingNodes.length}
|
||||
{searchText || hasActiveFilters() ? ` of ${nodes.length}` : ''}
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium">Relations:</span>{' '}
|
||||
{matchingEdges.length}
|
||||
{searchText || hasActiveFilters() ? ` of ${edges.length}` : ''}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue