|
1 | 1 | import { DiagramModel, NodeModel } from '@projectstorm/react-diagrams'; |
2 | 2 |
|
3 | 3 | export interface SearchResult { |
4 | | - /** total number of matches */ |
| 4 | + /** total number of matches */ |
5 | 5 | count: number; |
6 | | - /** zero-based indices of matching nodes in model.getNodes() */ |
| 6 | + /** zero-based indices of matching nodes in model.getNodes() */ |
7 | 7 | indices: number[]; |
| 8 | + /** target ports to highlight in UI when a Literal is attached */ |
| 9 | + portHits: { nodeId: string; portName: string }[]; |
| 10 | +} |
| 11 | + |
| 12 | +const getOptions = (n: any) => (n?.getOptions?.() ?? {}) as any; |
| 13 | +const getNodeID = (n: any) => n?.getID?.() ?? n?.options?.id ?? getOptions(n)?.id; |
| 14 | +const getPort = (n: any) => (Object.values(n?.getPorts?.() ?? {}) as any[])[0]; |
| 15 | + |
| 16 | +/** |
| 17 | + * Collect searchable texts only from ports (for literals). |
| 18 | + */ |
| 19 | +function collectLiteralValues(node: NodeModel): string[] { |
| 20 | + const rawName = String(getOptions(node).name || ''); |
| 21 | + if (!rawName.startsWith('Literal ')) return []; |
| 22 | + |
| 23 | + const port = getPort(node); |
| 24 | + if (!port) return []; |
| 25 | + |
| 26 | + const label = port.getOptions?.()?.label; |
| 27 | + return label != null ? [String(label).toLowerCase()] : []; |
8 | 28 | } |
9 | 29 |
|
10 | 30 | /** |
11 | | - * Search all nodes’ `options.name` (case-insensitive). |
| 31 | + * Return (nodeId, portName) of the opposite end connected to a Literal node. |
| 32 | + */ |
| 33 | +function getAttachedTargetByPortName(node: NodeModel): { nodeId: string; portName: string }[] { |
| 34 | + const results: { nodeId: string; portName: string }[] = []; |
| 35 | + |
| 36 | + const port = getPort(node); |
| 37 | + if (!port) return results; |
| 38 | + |
| 39 | + const links = Object.values(port.getLinks?.() ?? {}) as any[]; |
| 40 | + for (const link of links) { |
| 41 | + const src = link.getSourcePort?.(); |
| 42 | + const trg = link.getTargetPort?.(); |
| 43 | + |
| 44 | + // Identify the opposite port on the link |
| 45 | + const otherPort = src?.getNode?.() === node ? trg : src; |
| 46 | + if (!otherPort) continue; |
| 47 | + |
| 48 | + const otherNodeId = getNodeID(otherPort.getNode?.()); |
| 49 | + const otherPortName = |
| 50 | + otherPort.getName?.() ?? |
| 51 | + otherPort.options?.name ?? |
| 52 | + otherPort.getOptions?.()?.name; |
| 53 | + |
| 54 | + if (otherNodeId && otherPortName) { |
| 55 | + results.push({ nodeId: String(otherNodeId), portName: String(otherPortName) }); |
| 56 | + } |
| 57 | + } |
| 58 | + |
| 59 | + return results; |
| 60 | +} |
| 61 | + |
| 62 | +/** |
| 63 | + * Search all nodes’ `options.name` (case-insensitive), |
| 64 | + * and also inside literal values from ports. |
12 | 65 | */ |
13 | 66 | export function searchModel(model: DiagramModel, text: string): SearchResult { |
14 | 67 | const nodes = model.getNodes(); |
15 | 68 | const query = text.trim().toLowerCase(); |
16 | 69 | if (!query) { |
17 | | - return { count: 0, indices: [] }; |
| 70 | + return { count: 0, indices: [], portHits: [] }; |
18 | 71 | } |
19 | 72 |
|
20 | | - const indices: number[] = []; |
21 | | - const matches: string[] = []; |
| 73 | + const idToIdx = new Map<string, number>(); |
| 74 | + nodes.forEach((node: NodeModel, i) => { |
| 75 | + const id = getNodeID(node); |
| 76 | + if (id) idToIdx.set(String(id), i); |
| 77 | + }); |
| 78 | + |
| 79 | + const indices = new Set<number>(); |
| 80 | + const portHits: { nodeId: string; portName: string }[] = []; |
| 81 | + |
| 82 | + nodes.forEach((node, idx) => { |
| 83 | + const rawName = String(getOptions(node).name || ''); |
| 84 | + const nameLower = rawName.toLowerCase(); |
| 85 | + const texts = [nameLower, ...collectLiteralValues(node)]; |
| 86 | + if (!texts.some((t) => t.includes(query))) return; |
| 87 | + |
| 88 | + const isLiteral = rawName.startsWith('Literal '); |
| 89 | + const isAttached = Boolean(getOptions(node).extras?.attached); |
22 | 90 |
|
23 | | - nodes.forEach((node: NodeModel, idx: number) => { |
24 | | - const opts = node.getOptions() as any; |
25 | | - const name: string = (opts.name || '').toString(); |
26 | | - if (name.toLowerCase().includes(query)) { |
27 | | - indices.push(idx); |
28 | | - matches.push(name); |
| 91 | + if (isLiteral && isAttached) { |
| 92 | + const targets = getAttachedTargetByPortName(node); |
| 93 | + portHits.push(...targets); |
| 94 | + targets.forEach(({ nodeId }) => { |
| 95 | + const i = idToIdx.get(nodeId); |
| 96 | + if (typeof i === 'number') indices.add(i); |
| 97 | + }); |
| 98 | + } else { |
| 99 | + indices.add(idx); |
29 | 100 | } |
30 | 101 | }); |
31 | 102 |
|
| 103 | + const idxArr = Array.from(indices); |
32 | 104 | return { |
33 | | - count: indices.length, |
34 | | - indices |
| 105 | + count: idxArr.length, |
| 106 | + indices: idxArr, |
| 107 | + portHits |
35 | 108 | }; |
36 | 109 | } |
0 commit comments