Skip to content

Commit d06d0b4

Browse files
author
Dennis Labordus
authored
feat(editor/laterbinding): Show connected and available ExtRef Element from selected FCDA Element (openscd#941)
* Added right list of ExtRef to show subscription of selected FCDA Element. * Review comments processed * Review comments processed * Fixed tests. * Fixed translation.
1 parent 30d568f commit d06d0b4

File tree

14 files changed

+1616
-114
lines changed

14 files changed

+1616
-114
lines changed
Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { css, html, LitElement, property, TemplateResult } from 'lit-element';
22

33
import './subscription/smv-laterbinding/svc-laterbinding-list.js';
4+
import './subscription/smv-laterbinding/ext-ref-laterbinding-list.js';
45

56
/** An editor [[`plugin`]] for Subscribe Later Binding (SMV). */
67
export default class SMVSubscribeLaterBindingPlugin extends LitElement {
@@ -10,10 +11,10 @@ export default class SMVSubscribeLaterBindingPlugin extends LitElement {
1011
render(): TemplateResult {
1112
return html`<div>
1213
<div class="container">
13-
<svc-later-binding-list
14-
class="column"
15-
.doc=${this.doc}
16-
></svc-later-binding-list>
14+
<svc-later-binding-list class="column" .doc=${this.doc}>
15+
</svc-later-binding-list>
16+
<extref-later-binding-list class="column" .doc=${this.doc}>
17+
</extref-later-binding-list>
1718
</div>
1819
</div>`;
1920
}
@@ -24,15 +25,17 @@ export default class SMVSubscribeLaterBindingPlugin extends LitElement {
2425
}
2526
2627
.container {
28+
display: flex;
2729
padding: 8px 6px 16px;
30+
height: calc(100vh - 136px);
2831
}
2932
3033
.column {
3134
flex: 50%;
3235
margin: 0px 6px 0px;
3336
min-width: 300px;
3437
height: 100%;
35-
overflow-y: scroll;
38+
overflow-y: auto;
3639
}
3740
`;
3841
}
Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
import {
2+
css,
3+
customElement,
4+
html,
5+
LitElement,
6+
property,
7+
state,
8+
TemplateResult,
9+
} from 'lit-element';
10+
import { nothing } from 'lit-html';
11+
import { translate } from 'lit-translate';
12+
13+
import {
14+
compareNames,
15+
getDescriptionAttribute,
16+
getNameAttribute,
17+
identity,
18+
} from '../../../foundation.js';
19+
20+
import { styles } from '../foundation.js';
21+
import { FcdaSelectEvent, getFcdaTitleValue } from './foundation.js';
22+
23+
/**
24+
* A sub element for showing all Ext Refs from a FCDA Element.
25+
* The List reacts on a custom event to know which FCDA Element was selected and updated the view.
26+
*/
27+
@customElement('extref-later-binding-list')
28+
export class ExtRefLaterBindingList extends LitElement {
29+
@property({ attribute: false })
30+
doc!: XMLDocument;
31+
32+
@state()
33+
currentSelectedSvcElement: Element | undefined;
34+
@state()
35+
currentSelectedFcdaElement: Element | undefined;
36+
@state()
37+
currentIedElement: Element | undefined;
38+
39+
constructor() {
40+
super();
41+
this.onFcdaSelectEvent = this.onFcdaSelectEvent.bind(this);
42+
43+
const parentDiv = this.closest('.container');
44+
if (parentDiv) {
45+
parentDiv.addEventListener('fcda-select', this.onFcdaSelectEvent);
46+
}
47+
}
48+
49+
private getExtRefElements(): Element[] {
50+
if (this.doc) {
51+
return Array.from(this.doc.querySelectorAll('ExtRef'))
52+
.filter(element => element.hasAttribute('intAddr'))
53+
.filter(element => element.closest('IED') !== this.currentIedElement)
54+
.sort((a, b) =>
55+
compareNames(
56+
`${a.getAttribute('intAddr')}`,
57+
`${b.getAttribute('intAddr')}`
58+
)
59+
);
60+
}
61+
return [];
62+
}
63+
64+
private getSubscribedExtRefElements(): Element[] {
65+
return this.getExtRefElements().filter(element =>
66+
this.isSubscribedTo(element)
67+
);
68+
}
69+
70+
private getAvailableExtRefElements(): Element[] {
71+
return this.getExtRefElements().filter(
72+
element => !this.isSubscribed(element)
73+
);
74+
}
75+
76+
private async onFcdaSelectEvent(event: FcdaSelectEvent) {
77+
this.currentSelectedSvcElement = event.detail.svc;
78+
this.currentSelectedFcdaElement = event.detail.fcda;
79+
80+
// Retrieve the IED Element to which the FCDA belongs.
81+
// These ExtRef Elements will be excluded.
82+
this.currentIedElement = this.currentSelectedFcdaElement
83+
? this.currentSelectedFcdaElement.closest('IED') ?? undefined
84+
: undefined;
85+
}
86+
87+
private sameAttributeValue(
88+
extRefElement: Element,
89+
attributeName: string
90+
): boolean {
91+
return (
92+
extRefElement.getAttribute(attributeName) ===
93+
this.currentSelectedFcdaElement?.getAttribute(attributeName)
94+
);
95+
}
96+
97+
/**
98+
* Check if specific attributes from the ExtRef Element are the same as the ones from the FCDA Element
99+
* and also if the IED Name is the same. If that is the case this ExtRef subscribes to the selected FCDA
100+
* Element.
101+
*
102+
* @param extRefElement - The Ext Ref Element to check.
103+
*/
104+
private isSubscribedTo(extRefElement: Element): boolean {
105+
return (
106+
extRefElement.getAttribute('iedName') ===
107+
this.currentIedElement?.getAttribute('name') &&
108+
this.sameAttributeValue(extRefElement, 'ldInst') &&
109+
this.sameAttributeValue(extRefElement, 'prefix') &&
110+
this.sameAttributeValue(extRefElement, 'lnClass') &&
111+
this.sameAttributeValue(extRefElement, 'lnInst') &&
112+
this.sameAttributeValue(extRefElement, 'doName') &&
113+
this.sameAttributeValue(extRefElement, 'daName')
114+
);
115+
}
116+
117+
/**
118+
* Check if the ExtRef is already subscribed to a FCDA Element.
119+
*
120+
* @param extRefElement - The Ext Ref Element to check.
121+
*/
122+
private isSubscribed(extRefElement: Element): boolean {
123+
return (
124+
extRefElement.hasAttribute('iedName') &&
125+
extRefElement.hasAttribute('ldInst') &&
126+
extRefElement.hasAttribute('prefix') &&
127+
extRefElement.hasAttribute('lnClass') &&
128+
extRefElement.hasAttribute('lnInst') &&
129+
extRefElement.hasAttribute('doName') &&
130+
extRefElement.hasAttribute('daName')
131+
);
132+
}
133+
134+
/**
135+
* The data attribute check using attributes pLN, pDO, pDA and pServT is not supported yet by this plugin.
136+
* To make sure the user does not do anything prohibited, this type of ExtRef cannot be manipulated for the time being.
137+
* (Will be updated in the future).
138+
*
139+
* @param extRefElement - The Ext Ref Element to check.
140+
*/
141+
private unsupportedExtRefElement(extRefElement: Element): boolean {
142+
return (
143+
extRefElement.hasAttribute('pLN') ||
144+
extRefElement.hasAttribute('pDO') ||
145+
extRefElement.hasAttribute('pDA') ||
146+
extRefElement.hasAttribute('pServT')
147+
);
148+
}
149+
150+
private renderTitle(): TemplateResult {
151+
const svcName = this.currentSelectedSvcElement
152+
? getNameAttribute(this.currentSelectedSvcElement)
153+
: undefined;
154+
const fcdaName = this.currentSelectedFcdaElement
155+
? getFcdaTitleValue(this.currentSelectedFcdaElement)
156+
: undefined;
157+
158+
return html`<h1>
159+
${translate('subscription.smvLaterBinding.extRefList.title', {
160+
svcName: svcName ?? '-',
161+
fcdaName: fcdaName ?? '-',
162+
})}
163+
</h1>`;
164+
}
165+
166+
private renderSubscribedExtRefs(): TemplateResult {
167+
const subscribedExtRefs = this.getSubscribedExtRefElements();
168+
return html`
169+
<mwc-list-item
170+
noninteractive
171+
value="${subscribedExtRefs
172+
.map(
173+
extRefElement =>
174+
getDescriptionAttribute(extRefElement) +
175+
' ' +
176+
identity(extRefElement)
177+
)
178+
.join(' ')}"
179+
>
180+
<span>${translate('subscription.subscriber.subscribed')}</span>
181+
</mwc-list-item>
182+
<li divider role="separator"></li>
183+
${subscribedExtRefs.length > 0
184+
? html`${subscribedExtRefs.map(
185+
extRefElement => html` <mwc-list-item
186+
graphic="large"
187+
twoline
188+
value="${identity(extRefElement)}"
189+
>
190+
<span>
191+
${extRefElement.getAttribute('intAddr')}
192+
${getDescriptionAttribute(extRefElement)
193+
? html` (${getDescriptionAttribute(extRefElement)})`
194+
: nothing}
195+
</span>
196+
<span slot="secondary">${identity(extRefElement)}</span>
197+
<mwc-icon slot="graphic">swap_horiz</mwc-icon>
198+
</mwc-list-item>`
199+
)}`
200+
: html`<mwc-list-item graphic="large" noninteractive>
201+
${translate(
202+
'subscription.smvLaterBinding.extRefList.noSubscribedExtRefs'
203+
)}
204+
</mwc-list-item>`}
205+
`;
206+
}
207+
208+
private renderAvailableExtRefs(): TemplateResult {
209+
const availableExtRefs = this.getAvailableExtRefElements();
210+
return html`
211+
<mwc-list-item
212+
noninteractive
213+
value="${availableExtRefs
214+
.map(
215+
extRefElement =>
216+
getDescriptionAttribute(extRefElement) +
217+
' ' +
218+
identity(extRefElement)
219+
)
220+
.join(' ')}"
221+
>
222+
<span>
223+
${translate('subscription.subscriber.availableToSubscribe')}
224+
</span>
225+
</mwc-list-item>
226+
<li divider role="separator"></li>
227+
${availableExtRefs.length > 0
228+
? html`${availableExtRefs.map(
229+
extRefElement => html` <mwc-list-item
230+
graphic="large"
231+
?disabled=${this.unsupportedExtRefElement(extRefElement)}
232+
twoline
233+
value="${identity(extRefElement)}"
234+
>
235+
<span>
236+
${extRefElement.getAttribute('intAddr')}
237+
${getDescriptionAttribute(extRefElement)
238+
? html` (${getDescriptionAttribute(extRefElement)})`
239+
: nothing}
240+
</span>
241+
<span slot="secondary">${identity(extRefElement)}</span>
242+
<mwc-icon slot="graphic">arrow_back</mwc-icon>
243+
</mwc-list-item>`
244+
)}`
245+
: html`<mwc-list-item graphic="large" noninteractive>
246+
${translate(
247+
'subscription.smvLaterBinding.extRefList.noAvailableExtRefs'
248+
)}
249+
</mwc-list-item>`}
250+
`;
251+
}
252+
253+
render(): TemplateResult {
254+
return html` <section tabindex="0">
255+
${this.currentSelectedSvcElement && this.currentSelectedFcdaElement
256+
? html`
257+
${this.renderTitle()}
258+
<filtered-list>
259+
${this.renderSubscribedExtRefs()} ${this.renderAvailableExtRefs()}
260+
</filtered-list>
261+
`
262+
: html`
263+
<h1>
264+
${translate(
265+
'subscription.smvLaterBinding.extRefList.noSelection'
266+
)}
267+
</h1>
268+
`}
269+
</section>`;
270+
}
271+
272+
static styles = css`
273+
${styles}
274+
275+
mwc-list-item.hidden[noninteractive] + li[divider] {
276+
display: none;
277+
}
278+
`;
279+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
export function getFcdaTitleValue(fcdaElement: Element): string {
2+
return `${fcdaElement.getAttribute('doName')}${
3+
fcdaElement.hasAttribute('doName') && fcdaElement.hasAttribute('daName')
4+
? `.`
5+
: ``
6+
}${fcdaElement.getAttribute('daName')}`;
7+
}
8+
9+
export interface FcdaSelectDetail {
10+
svc: Element | undefined;
11+
fcda: Element | undefined;
12+
}
13+
export type FcdaSelectEvent = CustomEvent<FcdaSelectDetail>;
14+
export function newFcdaSelectEvent(
15+
svc: Element | undefined,
16+
fcda: Element | undefined,
17+
eventInitDict?: CustomEventInit<FcdaSelectDetail>
18+
): FcdaSelectEvent {
19+
return new CustomEvent<FcdaSelectDetail>('fcda-select', {
20+
bubbles: true,
21+
composed: true,
22+
...eventInitDict,
23+
detail: { svc, fcda, ...eventInitDict?.detail },
24+
});
25+
}
26+
27+
declare global {
28+
interface ElementEventMap {
29+
['fcda-select']: FcdaSelectEvent;
30+
}
31+
}

0 commit comments

Comments
 (0)