Skip to content

Commit 23f1bfb

Browse files
gridtech-isclaude
andcommitted
feat: expand LN nodes in IED Explorer with DO and DA from DataTypeTemplates
Logical Nodes now show Data Objects (with CDC) and Data Attributes (with FC and bType) when DataTypeTemplates are present in the SCD file. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent decdda0 commit 23f1bfb

2 files changed

Lines changed: 123 additions & 7 deletions

File tree

src/components/IedExplorer.tsx

Lines changed: 121 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { useMemo, useState } from 'react';
22
import type {
3-
SclModel, IedModel, LDeviceModel, DataSetModel,
3+
SclModel, IedModel, LDeviceModel, LnModel, DataSetModel,
44
GooseControlModel, SvControlModel, ReportControlModel, FcdaModel,
5+
DataTypeTemplatesModel,
56
} from '../model/types';
67

78
interface IedExplorerProps {
@@ -123,6 +124,7 @@ function IedTree({ ied, model }: { ied: IedModel; model: SclModel }): JSX.Elemen
123124
goose={goose.filter((g) => g.ldInst === ld.inst)}
124125
sv={sv.filter((s) => s.ldInst === ld.inst)}
125126
reports={reports.filter((r) => r.ldInst === ld.inst)}
127+
dtt={model.dataTypeTemplates ?? { lNodeTypes: new Map(), doTypes: new Map(), daTypes: new Map(), enumTypes: new Map(), duplicateTypeIds: [] }}
126128
isOpen={isOpen}
127129
onToggle={toggle}
128130
depth={1}
@@ -136,14 +138,15 @@ function IedTree({ ied, model }: { ied: IedModel; model: SclModel }): JSX.Elemen
136138
/* ── LDevice node ──────────────────────────────────────────────────── */
137139

138140
function LDeviceNode({
139-
ied, ld, datasets, goose, sv, reports, isOpen, onToggle, depth,
141+
ied, ld, datasets, goose, sv, reports, dtt, isOpen, onToggle, depth,
140142
}: {
141143
ied: IedModel;
142144
ld: LDeviceModel;
143145
datasets: DataSetModel[];
144146
goose: GooseControlModel[];
145147
sv: SvControlModel[];
146148
reports: ReportControlModel[];
149+
dtt: DataTypeTemplatesModel;
147150
isOpen: (key: string) => boolean;
148151
onToggle: (key: string) => void;
149152
depth: number;
@@ -272,12 +275,123 @@ function LDeviceNode({
272275

273276
{/* Other LNs */}
274277
{ld.lns.map((ln) => (
275-
<LeafNode
278+
<LnNode
276279
key={`${ln.lnClass}${ln.inst}`}
277-
icon="◈"
278-
iconClass={`ied-tree-icon-ln ied-tree-ln-${getLnGroup(ln.lnClass)}`}
279-
label={`${ln.prefix ?? ''}${ln.lnClass}${ln.inst}`}
280-
sublabel={ln.lnType}
280+
nodeKey={`ln:${ied.name}:${ld.inst}:${ln.prefix ?? ''}${ln.lnClass}${ln.inst}`}
281+
ln={ln}
282+
dtt={dtt}
283+
isOpen={isOpen}
284+
onToggle={onToggle}
285+
depth={depth + 1}
286+
/>
287+
))}
288+
</TreeNode>
289+
);
290+
}
291+
292+
/* ── LN node (with DO/DA from DataTypeTemplates) ─────────────────── */
293+
294+
function LnNode({
295+
nodeKey, ln, dtt, isOpen, onToggle, depth,
296+
}: {
297+
nodeKey: string;
298+
ln: LnModel;
299+
dtt: DataTypeTemplatesModel;
300+
isOpen: (key: string) => boolean;
301+
onToggle: (key: string) => void;
302+
depth: number;
303+
}): JSX.Element {
304+
const label = `${ln.prefix ?? ''}${ln.lnClass}${ln.inst}`;
305+
const lnTypeDef = ln.lnType ? dtt.lNodeTypes.get(ln.lnType) : undefined;
306+
const dos = lnTypeDef?.dos ?? [];
307+
308+
if (dos.length === 0) {
309+
return (
310+
<LeafNode
311+
icon="◈"
312+
iconClass={`ied-tree-icon-ln ied-tree-ln-${getLnGroup(ln.lnClass)}`}
313+
label={label}
314+
sublabel={ln.lnType}
315+
depth={depth}
316+
/>
317+
);
318+
}
319+
320+
return (
321+
<TreeNode
322+
nodeKey={nodeKey}
323+
icon="◈"
324+
iconClass={`ied-tree-icon-ln ied-tree-ln-${getLnGroup(ln.lnClass)}`}
325+
label={label}
326+
sublabel={ln.lnType}
327+
isOpen={isOpen(nodeKey)}
328+
onToggle={onToggle}
329+
depth={depth}
330+
>
331+
{dos.map((doEntry) => (
332+
<DoNode
333+
key={doEntry.name}
334+
nodeKey={`${nodeKey}:do:${doEntry.name}`}
335+
doName={doEntry.name}
336+
doType={doEntry.type}
337+
dtt={dtt}
338+
isOpen={isOpen}
339+
onToggle={onToggle}
340+
depth={depth + 1}
341+
/>
342+
))}
343+
</TreeNode>
344+
);
345+
}
346+
347+
/* ── DO node ──────────────────────────────────────────────────────── */
348+
349+
function DoNode({
350+
nodeKey, doName, doType, dtt, isOpen, onToggle, depth,
351+
}: {
352+
nodeKey: string;
353+
doName: string;
354+
doType: string;
355+
dtt: DataTypeTemplatesModel;
356+
isOpen: (key: string) => boolean;
357+
onToggle: (key: string) => void;
358+
depth: number;
359+
}): JSX.Element {
360+
const doTypeDef = dtt.doTypes.get(doType);
361+
const das = doTypeDef?.das ?? [];
362+
const cdc = doTypeDef?.cdc;
363+
364+
if (das.length === 0) {
365+
return (
366+
<LeafNode
367+
icon="◇"
368+
iconClass="ied-tree-icon-do"
369+
label={doName}
370+
sublabel={cdc}
371+
depth={depth}
372+
/>
373+
);
374+
}
375+
376+
return (
377+
<TreeNode
378+
nodeKey={nodeKey}
379+
icon="◇"
380+
iconClass="ied-tree-icon-do"
381+
label={doName}
382+
sublabel={cdc}
383+
isOpen={isOpen(nodeKey)}
384+
onToggle={onToggle}
385+
depth={depth}
386+
>
387+
{das.map((da) => (
388+
<LeafNode
389+
key={da.name}
390+
icon="·"
391+
iconClass="ied-tree-icon-da"
392+
label={da.name}
393+
sublabel={da.bType ?? da.type}
394+
tag={da.fc}
281395
depth={depth + 1}
282396
/>
283397
))}

src/styles.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3354,6 +3354,8 @@ summary:focus-visible {
33543354
.ied-tree-icon-sv { color: var(--sv, #f97316); }
33553355
.ied-tree-icon-rpt { color: var(--mms, #3b82f6); }
33563356
.ied-tree-icon-fcda { color: var(--muted); }
3357+
.ied-tree-icon-do { color: #7dd3fc; }
3358+
.ied-tree-icon-da { color: var(--muted); }
33573359
.ied-tree-icon-ln { color: #94a3b8; }
33583360

33593361
/* LN group colours */

0 commit comments

Comments
 (0)