From b4090e03eb7853397baa6f29c5d4fd8b37800ef3 Mon Sep 17 00:00:00 2001 From: Flurb Date: Wed, 23 Feb 2022 00:46:33 +0100 Subject: [PATCH 01/26] Added event handling --- .../{ => elements}/goose-message.ts | 5 +- .../subscription/elements/ied-element.ts | 36 +++++++++++ .../subscription/publisher-goose-list.ts | 2 +- .../subscription/subscriber-ied-list.ts | 61 +++++++++++-------- src/foundation.ts | 17 ++++++ .../__snapshots__/goose-message.test.snap.js | 2 +- .../subscriber-ied-list.test.snap.js | 6 +- .../subscription/goose-message.test.ts | 4 +- 8 files changed, 102 insertions(+), 31 deletions(-) rename src/editors/subscription/{ => elements}/goose-message.ts (89%) create mode 100644 src/editors/subscription/elements/ied-element.ts diff --git a/src/editors/subscription/goose-message.ts b/src/editors/subscription/elements/goose-message.ts similarity index 89% rename from src/editors/subscription/goose-message.ts rename to src/editors/subscription/elements/goose-message.ts index 229584e338..2374cd5b97 100644 --- a/src/editors/subscription/goose-message.ts +++ b/src/editors/subscription/elements/goose-message.ts @@ -6,7 +6,7 @@ import { property, TemplateResult, } from 'lit-element'; -import { newGOOSEDataSetEvent } from '../../foundation.js'; +import { newGOOSEDataSetEvent } from '../../../foundation.js'; @customElement('goose-message') export class GOOSEMessage extends LitElement { @@ -29,7 +29,8 @@ export class GOOSEMessage extends LitElement { render(): TemplateResult { return html` + graphic="icon" + tabindex="0"> ${this.element.getAttribute('name')} `; } diff --git a/src/editors/subscription/elements/ied-element.ts b/src/editors/subscription/elements/ied-element.ts new file mode 100644 index 0000000000..9a2ae1f21e --- /dev/null +++ b/src/editors/subscription/elements/ied-element.ts @@ -0,0 +1,36 @@ +import { + css, + customElement, + html, + LitElement, + property, + TemplateResult, +} from 'lit-element'; +import { newIEDSubscriptionEvent } from '../../../foundation.js'; + +@customElement('ied-element') +export class IEDElement extends LitElement { + /** Holding the IED element */ + @property({ attribute: false }) + element!: Element; + + private onIedSelect = () => { + document.querySelector('open-scd')!.dispatchEvent( + newIEDSubscriptionEvent( + this.element!.getAttribute('name') ?? '' + ) + ); + }; + + render(): TemplateResult { + return html` + ${this.element.getAttribute('name')} + add + `; + } + + static styles = css``; +} diff --git a/src/editors/subscription/publisher-goose-list.ts b/src/editors/subscription/publisher-goose-list.ts index 0ced9fde27..7e64ab84a8 100644 --- a/src/editors/subscription/publisher-goose-list.ts +++ b/src/editors/subscription/publisher-goose-list.ts @@ -7,7 +7,7 @@ import { TemplateResult, } from 'lit-element'; -import './goose-message.js'; +import './elements/goose-message.js'; import { translate } from 'lit-translate'; import { compareNames, getNameAttribute } from '../../foundation.js'; diff --git a/src/editors/subscription/subscriber-ied-list.ts b/src/editors/subscription/subscriber-ied-list.ts index fe0eb9b8a2..6f066f50d0 100644 --- a/src/editors/subscription/subscriber-ied-list.ts +++ b/src/editors/subscription/subscriber-ied-list.ts @@ -6,8 +6,11 @@ import { property, TemplateResult, } from 'lit-element'; + +import './elements/ied-element.js'; + import { translate } from 'lit-translate'; -import { GOOSEDataSetEvent } from '../../foundation.js'; +import { GOOSEDataSetEvent, IEDSubscriptionEvent } from '../../foundation.js'; /** * An IED within this IED list has 2 properties: @@ -37,12 +40,18 @@ export class SubscriberIEDList extends LitElement { /** Current selected GOOSE message. */ gseName!: string; + /** Current selected dataset, linked to the current selected GOOSE message. */ + dataSet!: Element; + constructor() { super(); this.onGOOSEDataSetEvent = this.onGOOSEDataSetEvent.bind(this); + this.onIEDSubscriptionEvent = this.onIEDSubscriptionEvent.bind(this); + const openScdElement = document.querySelector('open-scd'); if (openScdElement) { openScdElement.addEventListener('goose-dataset', this.onGOOSEDataSetEvent); + openScdElement.addEventListener('ied-subscription', this.onIEDSubscriptionEvent); } } @@ -55,20 +64,26 @@ export class SubscriberIEDList extends LitElement { this.iedName = event.detail.iedName; this.gseName = event.detail.gseName; - const dataSet = event.detail.dataset; + this.dataSet = event.detail.dataset; this.clearIedLists(); Array.from(this.doc.querySelectorAll(':root > IED')).forEach(ied => { const extRefs = Array.from(ied.querySelectorAll(`LN0[lnClass="LLN0"] > Inputs > ExtRef[iedName=${event.detail.iedName}]`)); - let linkedExtRefCount = 0; + /** + * If no ExtRef element is found, we can safely say it's not subscribed. + */ if (extRefs.length == 0) { this.availableIeds.push({element: ied}); return; } - dataSet.querySelectorAll('FCDA').forEach(fcda => { + /** + * If there is an ExtRef element, we just search for the first linked FCDA that cannot be found. + * It so, it's partially subscribed. + */ + this.dataSet.querySelectorAll('FCDA').forEach(fcda => { if (extRefs.filter(extRef => extRef.parentElement!.querySelector(` ExtRef[ldInst="${fcda.getAttribute('ldInst')}"] @@ -77,18 +92,28 @@ export class SubscriberIEDList extends LitElement { [prefix="${fcda.getAttribute('prefix')}"] [doName="${fcda.getAttribute('doName')}"] [daName="${fcda.getAttribute('daName')}"] - [serviceType="GOOSE"]`) != null)) { - linkedExtRefCount++; - } + [serviceType="GOOSE"]`)) == null) { + this.availableIeds.push({element: ied, partial: true}); + return; + } }) - linkedExtRefCount == dataSet.childElementCount ? - this.subscribedIeds.push({element: ied}) : this.availableIeds.push({element: ied, partial: true}) + /** + * Otherwise, it's full subscribed! + */ + this.subscribedIeds.push({element: ied}) }) this.requestUpdate(); } + private async onIEDSubscriptionEvent(event: IEDSubscriptionEvent) { + const inputsElement = this.doc.querySelector(`IED[name="${event.detail.iedName}"] > AccessPoint > Server > LDevice > LN0[lnClass="LLN0"] > Inputs`); + const extRefs = Array.from(inputsElement!.querySelectorAll(`ExtRef[iedName=${this.iedName}]`)); + console.log(this.dataSet) + extRefs.forEach(ref => console.log(ref)) + } + /** * Clear all the IED lists. */ @@ -111,11 +136,7 @@ export class SubscriberIEDList extends LitElement {
  • ${this.subscribedIeds.length > 0 ? - this.subscribedIeds.map(ied => html` - - ${ied.element.getAttribute('name')} - clear - `) + this.subscribedIeds.map(ied => html``) : html` ${translate('subscription.none')} `} @@ -126,11 +147,7 @@ export class SubscriberIEDList extends LitElement {
  • ${partialSubscribedIeds.length > 0 ? - partialSubscribedIeds.map(ied => html` - - ${ied.element.getAttribute('name')} - add - `) + partialSubscribedIeds.map(ied => html``) : html` ${translate('subscription.none')} `} @@ -141,11 +158,7 @@ export class SubscriberIEDList extends LitElement {
  • ${this.availableIeds.length > 0 ? - this.availableIeds.map(ied => html` - - ${ied.element.getAttribute('name')} - add - `) + this.availableIeds.map(ied => html``) : html` ${translate('subscription.none')} `} diff --git a/src/foundation.ts b/src/foundation.ts index 2fa4bf0bed..f0bc6ca598 100644 --- a/src/foundation.ts +++ b/src/foundation.ts @@ -300,6 +300,22 @@ export function newGOOSEDataSetEvent( }); } +export interface IEDSubscriptionDetail { + iedName: string; +} +export type IEDSubscriptionEvent = CustomEvent; +export function newIEDSubscriptionEvent( + iedName: string, + eventInitDict?: CustomEventInit +): IEDSubscriptionEvent { + return new CustomEvent('ied-subscription', { + bubbles: true, + composed: true, + ...eventInitDict, + detail: { iedName, ...eventInitDict?.detail }, + }); +} + export type LogDetail = InfoDetail | CommitDetail | ResetDetail; export type LogEvent = CustomEvent; export function newLogEvent( @@ -2658,6 +2674,7 @@ declare global { ['wizard']: WizardEvent; ['validate']: ValidateEvent; ['goose-dataset']: GOOSEDataSetEvent; + ['ied-subscription']: IEDSubscriptionEvent; ['log']: LogEvent; ['issue']: IssueEvent; } diff --git a/test/unit/editors/subscription/__snapshots__/goose-message.test.snap.js b/test/unit/editors/subscription/__snapshots__/goose-message.test.snap.js index c66b5c3311..30c93fc674 100644 --- a/test/unit/editors/subscription/__snapshots__/goose-message.test.snap.js +++ b/test/unit/editors/subscription/__snapshots__/goose-message.test.snap.js @@ -6,7 +6,7 @@ snapshots["goose-message looks like the latest snapshot"] = aria-disabled="false" graphic="icon" mwc-list-item="" - tabindex="-1" + tabindex="0" > GCB diff --git a/test/unit/editors/subscription/__snapshots__/subscriber-ied-list.test.snap.js b/test/unit/editors/subscription/__snapshots__/subscriber-ied-list.test.snap.js index eee81fe932..318943d314 100644 --- a/test/unit/editors/subscription/__snapshots__/subscriber-ied-list.test.snap.js +++ b/test/unit/editors/subscription/__snapshots__/subscriber-ied-list.test.snap.js @@ -6,7 +6,11 @@ snapshots["subscriber-ied-list looks like the latest snapshot"] = [subscription.subscriberIed.title] - + [subscription.subscriberIed.noGooseMessageSelected] diff --git a/test/unit/editors/subscription/goose-message.test.ts b/test/unit/editors/subscription/goose-message.test.ts index fcd972e5d0..68c945d354 100644 --- a/test/unit/editors/subscription/goose-message.test.ts +++ b/test/unit/editors/subscription/goose-message.test.ts @@ -1,7 +1,7 @@ import { html, fixture, expect } from '@open-wc/testing'; -import '../../../../src/editors/subscription/goose-message.js' -import { GOOSEMessage } from '../../../../src/editors/subscription/goose-message.js'; +import '../../../../src/editors/subscription/elements/goose-message.js' +import { GOOSEMessage } from '../../../../src/editors/subscription/elements/goose-message.js'; describe('goose-message', () => { let element: GOOSEMessage; From 0c794734b4490bd7962df86faafa9f6a3a85d1ee Mon Sep 17 00:00:00 2001 From: Flurb Date: Wed, 23 Feb 2022 13:57:59 +0100 Subject: [PATCH 02/26] Intermediate commit --- .../subscription/subscriber-ied-list.ts | 87 ++++++++++++++----- 1 file changed, 64 insertions(+), 23 deletions(-) diff --git a/src/editors/subscription/subscriber-ied-list.ts b/src/editors/subscription/subscriber-ied-list.ts index 6f066f50d0..abdf928a2a 100644 --- a/src/editors/subscription/subscriber-ied-list.ts +++ b/src/editors/subscription/subscriber-ied-list.ts @@ -69,12 +69,14 @@ export class SubscriberIEDList extends LitElement { this.clearIedLists(); Array.from(this.doc.querySelectorAll(':root > IED')).forEach(ied => { - const extRefs = Array.from(ied.querySelectorAll(`LN0[lnClass="LLN0"] > Inputs > ExtRef[iedName=${event.detail.iedName}]`)); + const inputs = ied.querySelector(`LN0[lnClass="LLN0"] > Inputs`); + + let partiallySubscribed = false; /** - * If no ExtRef element is found, we can safely say it's not subscribed. + * If no Inputs element is found, we can safely say it's not subscribed. */ - if (extRefs.length == 0) { + if (!inputs) { this.availableIeds.push({element: ied}); return; } @@ -84,34 +86,73 @@ export class SubscriberIEDList extends LitElement { * It so, it's partially subscribed. */ this.dataSet.querySelectorAll('FCDA').forEach(fcda => { - if (extRefs.filter(extRef => - extRef.parentElement!.querySelector(` - ExtRef[ldInst="${fcda.getAttribute('ldInst')}"] - [lnClass="${fcda.getAttribute('lnClass')}"] - [lnInst="${fcda.getAttribute('lnInst')}"] - [prefix="${fcda.getAttribute('prefix')}"] - [doName="${fcda.getAttribute('doName')}"] - [daName="${fcda.getAttribute('daName')}"] - [serviceType="GOOSE"]`)) == null) { - this.availableIeds.push({element: ied, partial: true}); - return; - } + if(!inputs.querySelector(`ExtRef[iedName=${event.detail.iedName}][serviceType="GOOSE"]` + + `${ + fcda.getAttribute('ldInst') + ? `[ldInst="${fcda.getAttribute('ldInst')}"]` + : `` + }${ + fcda.getAttribute('lnClass') + ? `[lnClass="${fcda.getAttribute('lnClass')}"]` + : `` + }${ + fcda.getAttribute('lnInst') + ? `[lnInst="${fcda.getAttribute('lnInst')}"]` + : `` + }${ + fcda.getAttribute('prefix') + ? `[prefix="${fcda.getAttribute('prefix')}"]` + : `` + }${ + fcda.getAttribute('doName') + ? `[doName="${fcda.getAttribute('doName')}"]` + : `` + }${ + fcda.getAttribute('daName') + ? `[daName="${fcda.getAttribute('daName')}"]` + : `` + }`)) { + partiallySubscribed = true; + } }) - /** - * Otherwise, it's full subscribed! - */ - this.subscribedIeds.push({element: ied}) + partiallySubscribed ? this.availableIeds.push({element: ied, partial: true}) : this.subscribedIeds.push({element: ied}) + }) this.requestUpdate(); } private async onIEDSubscriptionEvent(event: IEDSubscriptionEvent) { - const inputsElement = this.doc.querySelector(`IED[name="${event.detail.iedName}"] > AccessPoint > Server > LDevice > LN0[lnClass="LLN0"] > Inputs`); - const extRefs = Array.from(inputsElement!.querySelectorAll(`ExtRef[iedName=${this.iedName}]`)); - console.log(this.dataSet) - extRefs.forEach(ref => console.log(ref)) + const extRefs = Array.from(this.doc.querySelectorAll(`IED[name="${event.detail.iedName}"] > AccessPoint > Server > LDevice > LN0[lnClass="LLN0"] > Inputs > ExtRef[iedName=${this.iedName}]`)); + + if (extRefs.length == 0) { + console.log('No ExtRefs found at all for this specific IED, subbing right now!'); + return; + } + + let fullySubscribed = true; + + this.dataSet.querySelectorAll('FCDA').forEach(fcda => { + if (extRefs.filter(extRef => + extRef.parentElement!.querySelector(` + ExtRef[ldInst="${fcda.getAttribute('ldInst')}"] + [lnClass="${fcda.getAttribute('lnClass')}"] + [lnInst="${fcda.getAttribute('lnInst')}"] + [prefix="${fcda.getAttribute('prefix')}"] + [doName="${fcda.getAttribute('doName')}"] + [daName="${fcda.getAttribute('daName')}"] + [serviceType="GOOSE"]`)) == null) { + fullySubscribed = false; + } + }) + + if (fullySubscribed) { + console.log("Fully subscribed, unsubscribing right now."); + } else { + console.log("Partially subscribed!, subscribing right now!"); + } + } /** From 9d9a0306d9638f9eb16e8b6613213f4951c47244 Mon Sep 17 00:00:00 2001 From: Flurb Date: Wed, 23 Feb 2022 14:50:34 +0100 Subject: [PATCH 03/26] Intermediate commit --- .../subscription/subscriber-ied-list.ts | 54 ++++++++++++------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/src/editors/subscription/subscriber-ied-list.ts b/src/editors/subscription/subscriber-ied-list.ts index abdf928a2a..130186221a 100644 --- a/src/editors/subscription/subscriber-ied-list.ts +++ b/src/editors/subscription/subscriber-ied-list.ts @@ -124,35 +124,51 @@ export class SubscriberIEDList extends LitElement { } private async onIEDSubscriptionEvent(event: IEDSubscriptionEvent) { - const extRefs = Array.from(this.doc.querySelectorAll(`IED[name="${event.detail.iedName}"] > AccessPoint > Server > LDevice > LN0[lnClass="LLN0"] > Inputs > ExtRef[iedName=${this.iedName}]`)); + const inputs = this.doc.querySelector(`IED[name="${event.detail.iedName}"] > AccessPoint > Server > LDevice > LN0[lnClass="LLN0"] > Inputs`); - if (extRefs.length == 0) { - console.log('No ExtRefs found at all for this specific IED, subbing right now!'); + if (!inputs) { + console.log('No Inputs element found at all for this specific IED, subbing right now!'); return; } - let fullySubscribed = true; + let partiallySubscribed = false; this.dataSet.querySelectorAll('FCDA').forEach(fcda => { - if (extRefs.filter(extRef => - extRef.parentElement!.querySelector(` - ExtRef[ldInst="${fcda.getAttribute('ldInst')}"] - [lnClass="${fcda.getAttribute('lnClass')}"] - [lnInst="${fcda.getAttribute('lnInst')}"] - [prefix="${fcda.getAttribute('prefix')}"] - [doName="${fcda.getAttribute('doName')}"] - [daName="${fcda.getAttribute('daName')}"] - [serviceType="GOOSE"]`)) == null) { - fullySubscribed = false; - } + if(!inputs.querySelector(`ExtRef[iedName=${this.iedName}][serviceType="GOOSE"]` + + `${ + fcda.getAttribute('ldInst') + ? `[ldInst="${fcda.getAttribute('ldInst')}"]` + : `` + }${ + fcda.getAttribute('lnClass') + ? `[lnClass="${fcda.getAttribute('lnClass')}"]` + : `` + }${ + fcda.getAttribute('lnInst') + ? `[lnInst="${fcda.getAttribute('lnInst')}"]` + : `` + }${ + fcda.getAttribute('prefix') + ? `[prefix="${fcda.getAttribute('prefix')}"]` + : `` + }${ + fcda.getAttribute('doName') + ? `[doName="${fcda.getAttribute('doName')}"]` + : `` + }${ + fcda.getAttribute('daName') + ? `[daName="${fcda.getAttribute('daName')}"]` + : `` + }`)) { + partiallySubscribed = true; + } }) - if (fullySubscribed) { - console.log("Fully subscribed, unsubscribing right now."); - } else { + if (partiallySubscribed) { console.log("Partially subscribed!, subscribing right now!"); + } else { + console.log("Fully subscribed, unsubscribing right now."); } - } /** From c7085b135f5c4698c1ae9257f3cb9d5fb950214e Mon Sep 17 00:00:00 2001 From: Flurb Date: Wed, 23 Feb 2022 23:26:15 +0100 Subject: [PATCH 04/26] Small variable fix --- src/editors/subscription/subscriber-ied-list.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/editors/subscription/subscriber-ied-list.ts b/src/editors/subscription/subscriber-ied-list.ts index 130186221a..246621bd49 100644 --- a/src/editors/subscription/subscriber-ied-list.ts +++ b/src/editors/subscription/subscriber-ied-list.ts @@ -131,7 +131,7 @@ export class SubscriberIEDList extends LitElement { return; } - let partiallySubscribed = false; + let partiallyOrNotSubscribed = false; this.dataSet.querySelectorAll('FCDA').forEach(fcda => { if(!inputs.querySelector(`ExtRef[iedName=${this.iedName}][serviceType="GOOSE"]` + @@ -160,11 +160,11 @@ export class SubscriberIEDList extends LitElement { ? `[daName="${fcda.getAttribute('daName')}"]` : `` }`)) { - partiallySubscribed = true; + partiallyOrNotSubscribed = true; } }) - if (partiallySubscribed) { + if (partiallyOrNotSubscribed) { console.log("Partially subscribed!, subscribing right now!"); } else { console.log("Fully subscribed, unsubscribing right now."); From 8108f502af43eba784572d7f196100b86c74f04c Mon Sep 17 00:00:00 2001 From: Flurb Date: Thu, 24 Feb 2022 00:48:02 +0100 Subject: [PATCH 05/26] Refactor --- .../subscription/subscriber-ied-list.ts | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/editors/subscription/subscriber-ied-list.ts b/src/editors/subscription/subscriber-ied-list.ts index a969735898..2a0e5603d1 100644 --- a/src/editors/subscription/subscriber-ied-list.ts +++ b/src/editors/subscription/subscriber-ied-list.ts @@ -203,11 +203,7 @@ export class SubscriberIEDList extends LitElement {
  • ${this.subscribedIeds.length > 0 ? - this.subscribedIeds.map(ied => html` - - ${ied.element.getAttribute('name')} - clear - `) + this.subscribedIeds.map(ied => html``) : html` ${translate('subscription.none')} `} @@ -218,11 +214,7 @@ export class SubscriberIEDList extends LitElement {
  • ${partialSubscribedIeds.length > 0 ? - partialSubscribedIeds.map(ied => html` - - ${ied.element.getAttribute('name')} - add - `) + partialSubscribedIeds.map(ied => html``) : html` ${translate('subscription.none')} `} @@ -233,11 +225,7 @@ export class SubscriberIEDList extends LitElement {
  • ${this.availableIeds.length > 0 ? - this.availableIeds.map(ied => html` - - ${ied.element.getAttribute('name')} - add - `) + this.availableIeds.map(ied => html``) : html` ${translate('subscription.none')} `} From c076189066f7e8ee9db3eb8f97decd187dc7dd57 Mon Sep 17 00:00:00 2001 From: Flurb Date: Fri, 25 Feb 2022 15:26:58 +0100 Subject: [PATCH 06/26] Refactoring --- .../subscription/elements/ied-element.ts | 7 +- .../subscription/subscriber-ied-list.ts | 97 +++++++------------ src/foundation.ts | 15 ++- 3 files changed, 50 insertions(+), 69 deletions(-) diff --git a/src/editors/subscription/elements/ied-element.ts b/src/editors/subscription/elements/ied-element.ts index 9a2ae1f21e..1ed740116a 100644 --- a/src/editors/subscription/elements/ied-element.ts +++ b/src/editors/subscription/elements/ied-element.ts @@ -6,7 +6,7 @@ import { property, TemplateResult, } from 'lit-element'; -import { newIEDSubscriptionEvent } from '../../../foundation.js'; +import { newIEDSubscriptionEvent, SubscribeStatus } from '../../../foundation.js'; @customElement('ied-element') export class IEDElement extends LitElement { @@ -14,10 +14,13 @@ export class IEDElement extends LitElement { @property({ attribute: false }) element!: Element; + status!: SubscribeStatus; + private onIedSelect = () => { document.querySelector('open-scd')!.dispatchEvent( newIEDSubscriptionEvent( - this.element!.getAttribute('name') ?? '' + this.element!.getAttribute('name') ?? '', + this.status ) ); }; diff --git a/src/editors/subscription/subscriber-ied-list.ts b/src/editors/subscription/subscriber-ied-list.ts index c24c91ff0a..6cd9bb447b 100644 --- a/src/editors/subscription/subscriber-ied-list.ts +++ b/src/editors/subscription/subscriber-ied-list.ts @@ -11,7 +11,7 @@ import { import './elements/ied-element.js'; import { translate } from 'lit-translate'; -import { GOOSEDataSetEvent, IEDSubscriptionEvent } from '../../foundation.js'; +import { GOOSEDataSetEvent, IEDSubscriptionEvent, SubscribeStatus } from '../../foundation.js'; import { styles } from '../templates/foundation.js'; /** @@ -48,17 +48,14 @@ export class SubscriberIEDList extends LitElement { /** List holding all current avaialble IEDs which are not subscribed. */ availableIeds: IED[] = []; - /** Current selected IED. */ - iedName!: string; + currentSelectedGooseIED!: string; - /** Current selected GOOSE message. */ - gseName!: string; + currentSelectedGoose!: string; + + currentSelectedGooseDataset!: Element; @query('div') subscriberWrapper!: Element; - /** Current selected dataset, linked to the current selected GOOSE message. */ - dataSet!: Element; - constructor() { super(); this.onGOOSEDataSetEvent = this.onGOOSEDataSetEvent.bind(this); @@ -77,10 +74,9 @@ export class SubscriberIEDList extends LitElement { * @param event - Incoming event. */ private async onGOOSEDataSetEvent(event: GOOSEDataSetEvent) { - this.iedName = event.detail.iedName; - this.gseName = event.detail.gseName; - - this.dataSet = event.detail.dataset; + this.currentSelectedGooseIED = event.detail.iedName; + this.currentSelectedGoose = event.detail.gseName; + this.currentSelectedGooseDataset = event.detail.dataset; this.clearIedLists(); @@ -100,8 +96,8 @@ export class SubscriberIEDList extends LitElement { /** * Count all the linked ExtRefs. */ - this.dataSet.querySelectorAll('FCDA').forEach(fcda => { - if(inputs.querySelector(`ExtRef[iedName=${event.detail.iedName}][serviceType="GOOSE"]` + + this.currentSelectedGooseDataset.querySelectorAll('FCDA').forEach(fcda => { + if(inputs.querySelector(`ExtRef[iedName=${this.currentSelectedGooseIED}][serviceType="GOOSE"]` + `${fcdaReferences.map(fcdaRef => fcda.getAttribute(fcdaRef) ? `[${fcdaRef}="${fcda.getAttribute(fcdaRef)}"]` @@ -120,7 +116,7 @@ export class SubscriberIEDList extends LitElement { return; } - if (numberOfLinkedExtRefs == this.dataSet.querySelectorAll('FCDA').length) { + if (numberOfLinkedExtRefs == this.currentSelectedGooseDataset.querySelectorAll('FCDA').length) { this.subscribedIeds.push({element: ied}); } else { this.availableIeds.push({element: ied, partial: true}); @@ -131,51 +127,24 @@ export class SubscriberIEDList extends LitElement { this.requestUpdate(); } + /** + * When a IEDSubscriptionEvent is received, check if + * @param event - Incoming event. + */ private async onIEDSubscriptionEvent(event: IEDSubscriptionEvent) { - const inputs = this.doc.querySelector(`IED[name="${event.detail.iedName}"] > AccessPoint > Server > LDevice > LN0[lnClass="LLN0"] > Inputs`); - - if (!inputs) { - console.log('No Inputs element found at all for this specific IED, subbing right now!'); - return; - } - - let partiallyOrNotSubscribed = false; - - this.dataSet.querySelectorAll('FCDA').forEach(fcda => { - if(!inputs.querySelector(`ExtRef[iedName=${this.iedName}][serviceType="GOOSE"]` + - `${ - fcda.getAttribute('ldInst') - ? `[ldInst="${fcda.getAttribute('ldInst')}"]` - : `` - }${ - fcda.getAttribute('lnClass') - ? `[lnClass="${fcda.getAttribute('lnClass')}"]` - : `` - }${ - fcda.getAttribute('lnInst') - ? `[lnInst="${fcda.getAttribute('lnInst')}"]` - : `` - }${ - fcda.getAttribute('prefix') - ? `[prefix="${fcda.getAttribute('prefix')}"]` - : `` - }${ - fcda.getAttribute('doName') - ? `[doName="${fcda.getAttribute('doName')}"]` - : `` - }${ - fcda.getAttribute('daName') - ? `[daName="${fcda.getAttribute('daName')}"]` - : `` - }`)) { - partiallyOrNotSubscribed = true; - } - }) - - if (partiallyOrNotSubscribed) { - console.log("Partially subscribed!, subscribing right now!"); - } else { - console.log("Fully subscribed, unsubscribing right now."); + switch (event.detail.subscribeStatus) { + case SubscribeStatus.Full: { + console.log("Full Subscribed"); + break; + } + case SubscribeStatus.Partial: { + console.log("Partial Subscribed"); + break; + } + case SubscribeStatus.None: { + console.log("Not Subscribed"); + break; + } } } @@ -199,9 +168,9 @@ export class SubscriberIEDList extends LitElement { return html`

    ${translate('subscription.subscriberIed.title', { - selected: this.gseName ? this.iedName + ' > ' + this.gseName : 'IED' + selected: this.currentSelectedGoose ? this.currentSelectedGooseIED + ' > ' + this.currentSelectedGoose : 'IED' })}

    - ${this.gseName ? + ${this.currentSelectedGoose ? html`
    @@ -209,7 +178,7 @@ export class SubscriberIEDList extends LitElement {
  • ${this.subscribedIeds.length > 0 ? - this.subscribedIeds.map(ied => html``) + this.subscribedIeds.map(ied => html``) : html` ${translate('subscription.none')} `} @@ -220,7 +189,7 @@ export class SubscriberIEDList extends LitElement {
  • ${partialSubscribedIeds.length > 0 ? - partialSubscribedIeds.map(ied => html``) + partialSubscribedIeds.map(ied => html``) : html` ${translate('subscription.none')} `} @@ -231,7 +200,7 @@ export class SubscriberIEDList extends LitElement {
  • ${this.availableIeds.length > 0 ? - this.availableIeds.map(ied => html``) + this.availableIeds.map(ied => html``) : html` ${translate('subscription.none')} `} diff --git a/src/foundation.ts b/src/foundation.ts index f0bc6ca598..692a7bc584 100644 --- a/src/foundation.ts +++ b/src/foundation.ts @@ -300,19 +300,28 @@ export function newGOOSEDataSetEvent( }); } +/** + * Enumeration stating the Subscribe status of a IED to a GOOSE. + */ +export enum SubscribeStatus { + Full, + Partial, + None +} + export interface IEDSubscriptionDetail { iedName: string; + subscribeStatus: SubscribeStatus; } export type IEDSubscriptionEvent = CustomEvent; export function newIEDSubscriptionEvent( iedName: string, - eventInitDict?: CustomEventInit + subscribeStatus: SubscribeStatus ): IEDSubscriptionEvent { return new CustomEvent('ied-subscription', { bubbles: true, composed: true, - ...eventInitDict, - detail: { iedName, ...eventInitDict?.detail }, + detail: { iedName, subscribeStatus }, }); } From 0416268e7505688f339d21e9b9ca6a3bd26e234b Mon Sep 17 00:00:00 2001 From: Flurb Date: Mon, 28 Feb 2022 08:53:37 +0100 Subject: [PATCH 07/26] Create subscribe/unsubscribe actions --- .../subscription/elements/ied-element.ts | 2 +- .../subscription/subscriber-ied-list.ts | 83 ++++++++++++++++++- src/foundation.ts | 6 +- 3 files changed, 86 insertions(+), 5 deletions(-) diff --git a/src/editors/subscription/elements/ied-element.ts b/src/editors/subscription/elements/ied-element.ts index 1ed740116a..ecffde6ad3 100644 --- a/src/editors/subscription/elements/ied-element.ts +++ b/src/editors/subscription/elements/ied-element.ts @@ -19,7 +19,7 @@ export class IEDElement extends LitElement { private onIedSelect = () => { document.querySelector('open-scd')!.dispatchEvent( newIEDSubscriptionEvent( - this.element!.getAttribute('name') ?? '', + this.element, this.status ) ); diff --git a/src/editors/subscription/subscriber-ied-list.ts b/src/editors/subscription/subscriber-ied-list.ts index 6cd9bb447b..a5a524e2cc 100644 --- a/src/editors/subscription/subscriber-ied-list.ts +++ b/src/editors/subscription/subscriber-ied-list.ts @@ -11,7 +11,7 @@ import { import './elements/ied-element.js'; import { translate } from 'lit-translate'; -import { GOOSEDataSetEvent, IEDSubscriptionEvent, SubscribeStatus } from '../../foundation.js'; +import { GOOSEDataSetEvent, IEDSubscriptionEvent, newActionEvent, SubscribeStatus } from '../../foundation.js'; import { styles } from '../templates/foundation.js'; /** @@ -135,19 +135,100 @@ export class SubscriberIEDList extends LitElement { switch (event.detail.subscribeStatus) { case SubscribeStatus.Full: { console.log("Full Subscribed"); + this.unsubscribe(event.detail.element) break; } case SubscribeStatus.Partial: { console.log("Partial Subscribed"); + this.subscribe(event.detail.element) break; } case SubscribeStatus.None: { console.log("Not Subscribed"); + this.subscribe(event.detail.element) break; } } } + /** + * Full subscribe a given IED to the current dataset. + * @param ied - Given IED to subscribe. + */ + private subscribe(ied: Element): void { + const parent: Element = ied.parentElement!; + const clone: Element = ied.cloneNode(true); + + let inputsElement = clone.querySelector('LN0[lnClass="LLN0"] > Inputs'); + if (!inputsElement) { + inputsElement = document.createElementNS(document.documentElement.namespaceURI, 'Inputs'); + } + + this.currentSelectedGooseDataset.querySelectorAll('FCDA').forEach(fcda => { + if(!inputsElement!.querySelector(`ExtRef[iedName=${this.currentSelectedGooseIED}][serviceType="GOOSE"]` + + `${fcdaReferences.map(fcdaRef => + fcda.getAttribute(fcdaRef) + ? `[${fcdaRef}="${fcda.getAttribute(fcdaRef)}"]` + : '').join('') + }`)) { + const extRef = document.createElementNS(document.documentElement.namespaceURI, 'ExtRef'); + extRef.setAttribute('serviceType', 'GOOSE'); + extRef.setAttribute('iedName', this.currentSelectedGooseIED); + fcdaReferences.map(fcdaRef => { + extRef.setAttribute(fcdaRef, fcda.getAttribute(fcdaRef) ?? ''); + }); + inputsElement?.appendChild(extRef); + } + }); + + clone.querySelector('LN0[lnClass="LLN0"] > Inputs')?.remove(); + clone.querySelector('LN0[lnClass="LLN0"]')?.append(inputsElement); + + this.dispatchEvent( + newActionEvent({ + new: { + parent: parent, + element: clone, + reference: clone.nextSibling, + }, + }) + ); + } + + /** + * Unsubscribing a given IED to the current dataset. + * @param ied - Given IED to unsubscribe. + */ + private unsubscribe(ied: Element): void { + const parent: Element = ied.parentElement!; + const clone: Element = ied.cloneNode(true); + + const inputsElement = clone.querySelector('LN0[lnClass="LLN0"] > Inputs'); + + this.currentSelectedGooseDataset.querySelectorAll('FCDA').forEach(fcda => { + const extRef = document.createElement('ExtRef'); + extRef.setAttribute('serviceType', 'GOOSE'); + extRef.setAttribute('iedName', this.currentSelectedGooseIED); + fcdaReferences.map(fcdaRef => { + extRef.setAttribute(fcdaRef, fcda.getAttribute(fcdaRef) ?? ''); + }); + + inputsElement?.removeChild(extRef); + }); + + clone.replaceChild(inputsElement!, clone.querySelector('LN0[lnClass="LLN0"] > Inputs')!) + + this.dispatchEvent( + newActionEvent({ + new: { + parent: parent, + element: clone, + reference: clone.nextSibling, + }, + }) + ); + } + /** * Clear all the IED lists. */ diff --git a/src/foundation.ts b/src/foundation.ts index 692a7bc584..5f442c6bb4 100644 --- a/src/foundation.ts +++ b/src/foundation.ts @@ -310,18 +310,18 @@ export enum SubscribeStatus { } export interface IEDSubscriptionDetail { - iedName: string; + element: Element; subscribeStatus: SubscribeStatus; } export type IEDSubscriptionEvent = CustomEvent; export function newIEDSubscriptionEvent( - iedName: string, + element: Element, subscribeStatus: SubscribeStatus ): IEDSubscriptionEvent { return new CustomEvent('ied-subscription', { bubbles: true, composed: true, - detail: { iedName, subscribeStatus }, + detail: { element, subscribeStatus }, }); } From c79713d1a79a075b789d7eba08f9bad6731c78db Mon Sep 17 00:00:00 2001 From: Flurb Date: Mon, 28 Feb 2022 15:04:05 +0100 Subject: [PATCH 08/26] Intermediate commit --- src/Editing.ts | 4 ++++ src/editors/subscription/subscriber-ied-list.ts | 13 +++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Editing.ts b/src/Editing.ts index 7e26faaed7..f98c645ae4 100644 --- a/src/Editing.ts +++ b/src/Editing.ts @@ -72,6 +72,7 @@ export function Editing(Base: TBase) { } private onCreate(action: Create) { + console.log('onCreate') if (!this.checkCreateValidity(action)) return false; if (action.new.reference === undefined) @@ -97,6 +98,7 @@ export function Editing(Base: TBase) { } private onDelete(action: Delete) { + console.log('onDelete') if (!action.old.reference) action.old.reference = action.old.element.nextSibling; @@ -147,6 +149,7 @@ export function Editing(Base: TBase) { } private onMove(action: Move) { + console.log('onMove') if (!this.checkMoveValidity(action)) return false; if (!action.old.reference) @@ -206,6 +209,7 @@ export function Editing(Base: TBase) { } private onUpdate(action: Update) { + console.log('onUpdate') if (!this.checkUpdateValidity(action)) return false; action.new.element.append(...Array.from(action.old.element.children)); diff --git a/src/editors/subscription/subscriber-ied-list.ts b/src/editors/subscription/subscriber-ied-list.ts index a5a524e2cc..5c94f10b39 100644 --- a/src/editors/subscription/subscriber-ied-list.ts +++ b/src/editors/subscription/subscriber-ied-list.ts @@ -156,7 +156,6 @@ export class SubscriberIEDList extends LitElement { * @param ied - Given IED to subscribe. */ private subscribe(ied: Element): void { - const parent: Element = ied.parentElement!; const clone: Element = ied.cloneNode(true); let inputsElement = clone.querySelector('LN0[lnClass="LLN0"] > Inputs'); @@ -187,10 +186,11 @@ export class SubscriberIEDList extends LitElement { this.dispatchEvent( newActionEvent({ new: { - parent: parent, - element: clone, - reference: clone.nextSibling, + element: clone }, + old: { + element: ied + } }) ); } @@ -225,6 +225,11 @@ export class SubscriberIEDList extends LitElement { element: clone, reference: clone.nextSibling, }, + old: { + parent: parent, + element: ied, + reference: ied.nextSibling + } }) ); } From 40efed9383eb84001af5bc63abc19e8491ef9966 Mon Sep 17 00:00:00 2001 From: Flurb Date: Mon, 28 Feb 2022 15:18:27 +0100 Subject: [PATCH 09/26] Intermediate commit --- .../subscription/subscriber-ied-list.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/editors/subscription/subscriber-ied-list.ts b/src/editors/subscription/subscriber-ied-list.ts index 5c94f10b39..03841471e5 100644 --- a/src/editors/subscription/subscriber-ied-list.ts +++ b/src/editors/subscription/subscriber-ied-list.ts @@ -158,11 +158,12 @@ export class SubscriberIEDList extends LitElement { private subscribe(ied: Element): void { const clone: Element = ied.cloneNode(true); - let inputsElement = clone.querySelector('LN0[lnClass="LLN0"] > Inputs'); + let inputsElement = clone.querySelector('LN0 > Inputs'); if (!inputsElement) { inputsElement = document.createElementNS(document.documentElement.namespaceURI, 'Inputs'); } + /** Updating the ExtRefs according the dataset */ this.currentSelectedGooseDataset.querySelectorAll('FCDA').forEach(fcda => { if(!inputsElement!.querySelector(`ExtRef[iedName=${this.currentSelectedGooseIED}][serviceType="GOOSE"]` + `${fcdaReferences.map(fcdaRef => @@ -171,18 +172,22 @@ export class SubscriberIEDList extends LitElement { : '').join('') }`)) { const extRef = document.createElementNS(document.documentElement.namespaceURI, 'ExtRef'); - extRef.setAttribute('serviceType', 'GOOSE'); - extRef.setAttribute('iedName', this.currentSelectedGooseIED); + extRef.setAttributeNS(document.documentElement.namespaceURI, 'iedName', this.currentSelectedGooseIED); + extRef.setAttributeNS(document.documentElement.namespaceURI, 'serviceType', 'GOOSE'); fcdaReferences.map(fcdaRef => { - extRef.setAttribute(fcdaRef, fcda.getAttribute(fcdaRef) ?? ''); + extRef.setAttributeNS(document.documentElement.namespaceURI, fcdaRef, fcda.getAttribute(fcdaRef) ?? ''); }); inputsElement?.appendChild(extRef); } }); - clone.querySelector('LN0[lnClass="LLN0"] > Inputs')?.remove(); - clone.querySelector('LN0[lnClass="LLN0"]')?.append(inputsElement); - + if (!inputsElement.parentElement) { + // Inputs isn't available, we need to add it manually. + + // clone.querySelector('LN0[lnClass="LLN0"] > Inputs')?.remove(); + // clone.querySelector('LN0[lnClass="LLN0"]')?.append(inputsElement); + } + this.dispatchEvent( newActionEvent({ new: { From 4c287c8ab13598f1cd3b7a608172e79f784aba2b Mon Sep 17 00:00:00 2001 From: Flurb Date: Mon, 28 Feb 2022 16:37:23 +0100 Subject: [PATCH 10/26] Intermediate commit --- .../subscription/publisher-goose-list.ts | 2 +- .../subscription/subscriber-ied-list.ts | 67 +++++++++---------- 2 files changed, 34 insertions(+), 35 deletions(-) diff --git a/src/editors/subscription/publisher-goose-list.ts b/src/editors/subscription/publisher-goose-list.ts index 4368defefe..de3b5b6837 100644 --- a/src/editors/subscription/publisher-goose-list.ts +++ b/src/editors/subscription/publisher-goose-list.ts @@ -31,7 +31,7 @@ export class PublisherGOOSEList extends LitElement { * @returns All the published GOOSE messages of this specific IED. */ private getGSEControls(ied: Element) : Element[] { - return Array.from(ied.querySelectorAll(':scope > AccessPoint > Server > LDevice > LN0[lnClass="LLN0"] > GSEControl')); + return Array.from(ied.querySelectorAll(':scope > AccessPoint > Server > LDevice > LN0 > GSEControl')); } render(): TemplateResult { diff --git a/src/editors/subscription/subscriber-ied-list.ts b/src/editors/subscription/subscriber-ied-list.ts index 03841471e5..10a7d0a87a 100644 --- a/src/editors/subscription/subscriber-ied-list.ts +++ b/src/editors/subscription/subscriber-ied-list.ts @@ -11,7 +11,7 @@ import { import './elements/ied-element.js'; import { translate } from 'lit-translate'; -import { GOOSEDataSetEvent, IEDSubscriptionEvent, newActionEvent, SubscribeStatus } from '../../foundation.js'; +import { createElement, GOOSEDataSetEvent, IEDSubscriptionEvent, newActionEvent, SubscribeStatus } from '../../foundation.js'; import { styles } from '../templates/foundation.js'; /** @@ -81,7 +81,7 @@ export class SubscriberIEDList extends LitElement { this.clearIedLists(); Array.from(this.doc.querySelectorAll(':root > IED')).forEach(ied => { - const inputs = ied.querySelector(`LN0[lnClass="LLN0"] > Inputs`); + const inputs = ied.querySelector(`LN0 > Inputs`); let numberOfLinkedExtRefs = 0; @@ -134,17 +134,14 @@ export class SubscriberIEDList extends LitElement { private async onIEDSubscriptionEvent(event: IEDSubscriptionEvent) { switch (event.detail.subscribeStatus) { case SubscribeStatus.Full: { - console.log("Full Subscribed"); this.unsubscribe(event.detail.element) break; } case SubscribeStatus.Partial: { - console.log("Partial Subscribed"); this.subscribe(event.detail.element) break; } case SubscribeStatus.None: { - console.log("Not Subscribed"); this.subscribe(event.detail.element) break; } @@ -160,7 +157,7 @@ export class SubscriberIEDList extends LitElement { let inputsElement = clone.querySelector('LN0 > Inputs'); if (!inputsElement) { - inputsElement = document.createElementNS(document.documentElement.namespaceURI, 'Inputs'); + inputsElement = createElement(ied.ownerDocument, 'Inputs', {}); } /** Updating the ExtRefs according the dataset */ @@ -171,21 +168,27 @@ export class SubscriberIEDList extends LitElement { ? `[${fcdaRef}="${fcda.getAttribute(fcdaRef)}"]` : '').join('') }`)) { - const extRef = document.createElementNS(document.documentElement.namespaceURI, 'ExtRef'); - extRef.setAttributeNS(document.documentElement.namespaceURI, 'iedName', this.currentSelectedGooseIED); - extRef.setAttributeNS(document.documentElement.namespaceURI, 'serviceType', 'GOOSE'); - fcdaReferences.map(fcdaRef => { - extRef.setAttributeNS(document.documentElement.namespaceURI, fcdaRef, fcda.getAttribute(fcdaRef) ?? ''); - }); - inputsElement?.appendChild(extRef); + const extRef = createElement( + ied.ownerDocument, + 'ExtRef', + { + iedName: this.currentSelectedGooseIED, + serviceType: 'GOOSE', + ldInst: fcda.getAttribute('ldInst') ?? '', + lnClass: fcda.getAttribute('lnClass') ?? '', + lnInst: fcda.getAttribute('lnInst') ?? '', + prefix: fcda.getAttribute('prefix') ?? '', + doName: fcda.getAttribute('doName') ?? '', + daName: fcda.getAttribute('daName') ?? '' + }); + + inputsElement?.appendChild(extRef); } }); + /** If the IED doesn't have a Inputs element, just append it to the first LN0 element. */ if (!inputsElement.parentElement) { - // Inputs isn't available, we need to add it manually. - - // clone.querySelector('LN0[lnClass="LLN0"] > Inputs')?.remove(); - // clone.querySelector('LN0[lnClass="LLN0"]')?.append(inputsElement); + clone.querySelector('LN0')?.append(inputsElement); } this.dispatchEvent( @@ -205,35 +208,31 @@ export class SubscriberIEDList extends LitElement { * @param ied - Given IED to unsubscribe. */ private unsubscribe(ied: Element): void { - const parent: Element = ied.parentElement!; const clone: Element = ied.cloneNode(true); - const inputsElement = clone.querySelector('LN0[lnClass="LLN0"] > Inputs'); + const inputsElement = clone.querySelector('LN0 > Inputs'); this.currentSelectedGooseDataset.querySelectorAll('FCDA').forEach(fcda => { - const extRef = document.createElement('ExtRef'); - extRef.setAttribute('serviceType', 'GOOSE'); - extRef.setAttribute('iedName', this.currentSelectedGooseIED); - fcdaReferences.map(fcdaRef => { - extRef.setAttribute(fcdaRef, fcda.getAttribute(fcdaRef) ?? ''); - }); - - inputsElement?.removeChild(extRef); + const extRef = inputsElement?.querySelector(`ExtRef[iedName=${this.currentSelectedGooseIED}][serviceType="GOOSE"]` + + `${fcdaReferences.map(fcdaRef => + fcda.getAttribute(fcdaRef) + ? `[${fcdaRef}="${fcda.getAttribute(fcdaRef)}"]` + : '').join('') + }`); + + inputsElement?.removeChild(extRef!); }); - clone.replaceChild(inputsElement!, clone.querySelector('LN0[lnClass="LLN0"] > Inputs')!) + clone.querySelector('LN0 > Inputs')?.remove(); + clone.querySelector('LN0')?.appendChild(inputsElement!) this.dispatchEvent( newActionEvent({ new: { - parent: parent, - element: clone, - reference: clone.nextSibling, + element: clone }, old: { - parent: parent, - element: ied, - reference: ied.nextSibling + element: ied } }) ); From aa5eda8a114e36ac8d0f93821381ba5ad389ffcc Mon Sep 17 00:00:00 2001 From: Flurb Date: Mon, 28 Feb 2022 23:22:51 +0100 Subject: [PATCH 11/26] Added correct subscribing/unsubscribing --- .../subscription/subscriber-ied-list.ts | 51 ++++++++++++------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/src/editors/subscription/subscriber-ied-list.ts b/src/editors/subscription/subscriber-ied-list.ts index 10a7d0a87a..038abcc032 100644 --- a/src/editors/subscription/subscriber-ied-list.ts +++ b/src/editors/subscription/subscriber-ied-list.ts @@ -80,7 +80,9 @@ export class SubscriberIEDList extends LitElement { this.clearIedLists(); - Array.from(this.doc.querySelectorAll(':root > IED')).forEach(ied => { + Array.from(this.doc.querySelectorAll(':root > IED')) + .filter(ied => ied.getAttribute('name') != this.currentSelectedGooseIED) + .forEach(ied => { const inputs = ied.querySelector(`LN0 > Inputs`); let numberOfLinkedExtRefs = 0; @@ -152,7 +154,7 @@ export class SubscriberIEDList extends LitElement { * Full subscribe a given IED to the current dataset. * @param ied - Given IED to subscribe. */ - private subscribe(ied: Element): void { + private async subscribe(ied: Element): Promise { const clone: Element = ied.cloneNode(true); let inputsElement = clone.querySelector('LN0 > Inputs'); @@ -191,16 +193,7 @@ export class SubscriberIEDList extends LitElement { clone.querySelector('LN0')?.append(inputsElement); } - this.dispatchEvent( - newActionEvent({ - new: { - element: clone - }, - old: { - element: ied - } - }) - ); + this.replaceElement(ied, clone); } /** @@ -224,15 +217,37 @@ export class SubscriberIEDList extends LitElement { }); clone.querySelector('LN0 > Inputs')?.remove(); - clone.querySelector('LN0')?.appendChild(inputsElement!) - + clone.querySelector('LN0')?.appendChild(inputsElement!); + + this.replaceElement(ied, clone); + } + + /** + * Replacing an element in the current opened file. + * @param original - The original element. + * @param clone - The element to replace the original with. + */ + private async replaceElement(original: Element, clone: Element) { + const parent = original.parentElement; + this.dispatchEvent( newActionEvent({ - new: { - element: clone - }, old: { - element: ied + parent: parent!, + element: original, + reference: original.nextSibling + } + }) + ); + + await this.updateComplete; + + this.dispatchEvent( + newActionEvent({ + new: { + parent: parent!, + element: clone, + reference: clone.nextSibling } }) ); From dceae3d54500207368aba4842093f58774fed827 Mon Sep 17 00:00:00 2001 From: Flurb Date: Wed, 2 Mar 2022 14:30:17 +0100 Subject: [PATCH 12/26] Refactor --- .../subscription/subscriber-ied-list.ts | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/editors/subscription/subscriber-ied-list.ts b/src/editors/subscription/subscriber-ied-list.ts index a3e9189ab3..5bfc8e7108 100644 --- a/src/editors/subscription/subscriber-ied-list.ts +++ b/src/editors/subscription/subscriber-ied-list.ts @@ -48,9 +48,10 @@ export class SubscriberIEDList extends LitElement { /** List holding all current avaialble IEDs which are not subscribed. */ availableIeds: IED[] = []; - currentSelectedGooseIED!: string; + selectedGooseIEDName!: string; + + selectedDataset!: Element; - currentSelectedGooseDataset!: Element; /** Current selected GSEControl element. */ gseControl!: Element; @@ -74,14 +75,14 @@ export class SubscriberIEDList extends LitElement { * @param event - Incoming event. */ private async onGOOSEDataSetEvent(event: GOOSESelectEvent) { - this.currentSelectedGooseIED = event.detail.iedName; + this.selectedGooseIEDName = event.detail.iedName; this.gseControl = event.detail.gseControl; - this.currentSelectedGooseDataset = event.detail.dataset; + this.selectedDataset = event.detail.dataset; this.clearIedLists(); Array.from(this.doc.querySelectorAll(':root > IED')) - .filter(ied => ied.getAttribute('name') != this.currentSelectedGooseIED) + .filter(ied => ied.getAttribute('name') != this.selectedGooseIEDName) .forEach(ied => { const inputs = ied.querySelector(`LN0 > Inputs`); @@ -98,8 +99,8 @@ export class SubscriberIEDList extends LitElement { /** * Count all the linked ExtRefs. */ - this.currentSelectedGooseDataset.querySelectorAll('FCDA').forEach(fcda => { - if(inputs.querySelector(`ExtRef[iedName=${this.currentSelectedGooseIED}]` + + this.selectedDataset.querySelectorAll('FCDA').forEach(fcda => { + if(inputs.querySelector(`ExtRef[iedName=${this.selectedGooseIEDName}]` + `${fcdaReferences.map(fcdaRef => fcda.getAttribute(fcdaRef) ? `[${fcdaRef}="${fcda.getAttribute(fcdaRef)}"]` @@ -118,7 +119,7 @@ export class SubscriberIEDList extends LitElement { return; } - if (numberOfLinkedExtRefs == this.currentSelectedGooseDataset.querySelectorAll('FCDA').length) { + if (numberOfLinkedExtRefs == this.selectedDataset.querySelectorAll('FCDA').length) { this.subscribedIeds.push({element: ied}); } else { this.availableIeds.push({element: ied, partial: true}); @@ -163,8 +164,8 @@ export class SubscriberIEDList extends LitElement { } /** Updating the ExtRefs according the dataset */ - this.currentSelectedGooseDataset.querySelectorAll('FCDA').forEach(fcda => { - if(!inputsElement!.querySelector(`ExtRef[iedName=${this.currentSelectedGooseIED}][serviceType="GOOSE"]` + + this.selectedDataset.querySelectorAll('FCDA').forEach(fcda => { + if(!inputsElement!.querySelector(`ExtRef[iedName=${this.selectedGooseIEDName}][serviceType="GOOSE"]` + `${fcdaReferences.map(fcdaRef => fcda.getAttribute(fcdaRef) ? `[${fcdaRef}="${fcda.getAttribute(fcdaRef)}"]` @@ -174,7 +175,7 @@ export class SubscriberIEDList extends LitElement { ied.ownerDocument, 'ExtRef', { - iedName: this.currentSelectedGooseIED, + iedName: this.selectedGooseIEDName, serviceType: 'GOOSE', ldInst: fcda.getAttribute('ldInst') ?? '', lnClass: fcda.getAttribute('lnClass') ?? '', @@ -205,8 +206,8 @@ export class SubscriberIEDList extends LitElement { const inputsElement = clone.querySelector('LN0 > Inputs'); - this.currentSelectedGooseDataset.querySelectorAll('FCDA').forEach(fcda => { - const extRef = inputsElement?.querySelector(`ExtRef[iedName=${this.currentSelectedGooseIED}][serviceType="GOOSE"]` + + this.selectedDataset.querySelectorAll('FCDA').forEach(fcda => { + const extRef = inputsElement?.querySelector(`ExtRef[iedName=${this.selectedGooseIEDName}][serviceType="GOOSE"]` + `${fcdaReferences.map(fcdaRef => fcda.getAttribute(fcdaRef) ? `[${fcdaRef}="${fcda.getAttribute(fcdaRef)}"]` @@ -274,7 +275,7 @@ export class SubscriberIEDList extends LitElement { return html`

    ${translate('subscription.subscriberIed.title', { - selected: gseControlName ? this.currentSelectedGooseIED + ' > ' + gseControlName : 'IED' + selected: gseControlName ? this.selectedGooseIEDName + ' > ' + gseControlName : 'IED' })}

    ${this.gseControl ? html`
    From 4d3ddff7eafecce2f9117c110c0647ee85a7fd17 Mon Sep 17 00:00:00 2001 From: Flurb Date: Wed, 2 Mar 2022 14:51:45 +0100 Subject: [PATCH 13/26] Refactoring --- .../subscription/elements/goose-message.ts | 1 - .../subscription/subscriber-ied-list.ts | 40 ++++++++++--------- src/foundation.ts | 4 +- 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/editors/subscription/elements/goose-message.ts b/src/editors/subscription/elements/goose-message.ts index 4130a4ea7a..e7b83d24a9 100644 --- a/src/editors/subscription/elements/goose-message.ts +++ b/src/editors/subscription/elements/goose-message.ts @@ -20,7 +20,6 @@ export class GOOSEMessage extends LitElement { const dataset = ln?.querySelector(`DataSet[name=${this.element.getAttribute('datSet')}]`); this.dispatchEvent( newGOOSESelectEvent( - this.element.closest('IED')?.getAttribute('name') ?? '', this.element, dataset! ) diff --git a/src/editors/subscription/subscriber-ied-list.ts b/src/editors/subscription/subscriber-ied-list.ts index 5bfc8e7108..41e15b4010 100644 --- a/src/editors/subscription/subscriber-ied-list.ts +++ b/src/editors/subscription/subscriber-ied-list.ts @@ -48,12 +48,14 @@ export class SubscriberIEDList extends LitElement { /** List holding all current avaialble IEDs which are not subscribed. */ availableIeds: IED[] = []; - selectedGooseIEDName!: string; + /** The current selected dataset */ + currentDataset!: Element; - selectedDataset!: Element; + /** Current selected GSEControl element */ + currentGseControl!: Element; - /** Current selected GSEControl element. */ - gseControl!: Element; + /** The name of the IED belonging to the current selected GOOSE */ + currentGooseIEDName!: string | undefined | null; @query('div') subscriberWrapper!: Element; @@ -75,14 +77,14 @@ export class SubscriberIEDList extends LitElement { * @param event - Incoming event. */ private async onGOOSEDataSetEvent(event: GOOSESelectEvent) { - this.selectedGooseIEDName = event.detail.iedName; - this.gseControl = event.detail.gseControl; - this.selectedDataset = event.detail.dataset; + this.currentGseControl = event.detail.gseControl; + this.currentDataset = event.detail.dataset; + this.currentGooseIEDName = this.currentGseControl.closest('IED')?.getAttribute('name'); this.clearIedLists(); Array.from(this.doc.querySelectorAll(':root > IED')) - .filter(ied => ied.getAttribute('name') != this.selectedGooseIEDName) + .filter(ied => ied.getAttribute('name') != this.currentGooseIEDName) .forEach(ied => { const inputs = ied.querySelector(`LN0 > Inputs`); @@ -99,8 +101,8 @@ export class SubscriberIEDList extends LitElement { /** * Count all the linked ExtRefs. */ - this.selectedDataset.querySelectorAll('FCDA').forEach(fcda => { - if(inputs.querySelector(`ExtRef[iedName=${this.selectedGooseIEDName}]` + + this.currentDataset.querySelectorAll('FCDA').forEach(fcda => { + if(inputs.querySelector(`ExtRef[iedName=${this.currentGooseIEDName}]` + `${fcdaReferences.map(fcdaRef => fcda.getAttribute(fcdaRef) ? `[${fcdaRef}="${fcda.getAttribute(fcdaRef)}"]` @@ -119,7 +121,7 @@ export class SubscriberIEDList extends LitElement { return; } - if (numberOfLinkedExtRefs == this.selectedDataset.querySelectorAll('FCDA').length) { + if (numberOfLinkedExtRefs == this.currentDataset.querySelectorAll('FCDA').length) { this.subscribedIeds.push({element: ied}); } else { this.availableIeds.push({element: ied, partial: true}); @@ -164,8 +166,8 @@ export class SubscriberIEDList extends LitElement { } /** Updating the ExtRefs according the dataset */ - this.selectedDataset.querySelectorAll('FCDA').forEach(fcda => { - if(!inputsElement!.querySelector(`ExtRef[iedName=${this.selectedGooseIEDName}][serviceType="GOOSE"]` + + this.currentDataset.querySelectorAll('FCDA').forEach(fcda => { + if(!inputsElement!.querySelector(`ExtRef[iedName=${this.currentGooseIEDName}]` + `${fcdaReferences.map(fcdaRef => fcda.getAttribute(fcdaRef) ? `[${fcdaRef}="${fcda.getAttribute(fcdaRef)}"]` @@ -175,7 +177,7 @@ export class SubscriberIEDList extends LitElement { ied.ownerDocument, 'ExtRef', { - iedName: this.selectedGooseIEDName, + iedName: this.currentGooseIEDName!, serviceType: 'GOOSE', ldInst: fcda.getAttribute('ldInst') ?? '', lnClass: fcda.getAttribute('lnClass') ?? '', @@ -206,8 +208,8 @@ export class SubscriberIEDList extends LitElement { const inputsElement = clone.querySelector('LN0 > Inputs'); - this.selectedDataset.querySelectorAll('FCDA').forEach(fcda => { - const extRef = inputsElement?.querySelector(`ExtRef[iedName=${this.selectedGooseIEDName}][serviceType="GOOSE"]` + + this.currentDataset.querySelectorAll('FCDA').forEach(fcda => { + const extRef = inputsElement?.querySelector(`ExtRef[iedName=${this.currentGooseIEDName}][serviceType="GOOSE"]` + `${fcdaReferences.map(fcdaRef => fcda.getAttribute(fcdaRef) ? `[${fcdaRef}="${fcda.getAttribute(fcdaRef)}"]` @@ -270,14 +272,14 @@ export class SubscriberIEDList extends LitElement { render(): TemplateResult { const partialSubscribedIeds = this.availableIeds.filter(ied => ied.partial); - const gseControlName = this.gseControl?.getAttribute('name') ?? undefined; + const gseControlName = this.currentGseControl?.getAttribute('name') ?? undefined; return html`

    ${translate('subscription.subscriberIed.title', { - selected: gseControlName ? this.selectedGooseIEDName + ' > ' + gseControlName : 'IED' + selected: gseControlName ? this.currentGooseIEDName + ' > ' + gseControlName : 'IED' })}

    - ${this.gseControl ? + ${this.currentGseControl ? html`
    diff --git a/src/foundation.ts b/src/foundation.ts index befe44a509..3f7ee9f1a9 100644 --- a/src/foundation.ts +++ b/src/foundation.ts @@ -281,13 +281,11 @@ export interface ResetDetail { } export interface GOOSESelectDetail { - iedName: string; gseControl: Element; dataset: Element; } export type GOOSESelectEvent = CustomEvent; export function newGOOSESelectEvent( - iedName: string, gseControl: Element, dataset: Element, eventInitDict?: CustomEventInit @@ -296,7 +294,7 @@ export function newGOOSESelectEvent( bubbles: true, composed: true, ...eventInitDict, - detail: { iedName, gseControl, dataset, ...eventInitDict?.detail }, + detail: { gseControl, dataset, ...eventInitDict?.detail }, }); } From b6e26fe3100ceb27107d85c870d43f4be4ccd7de Mon Sep 17 00:00:00 2001 From: Flurb Date: Wed, 2 Mar 2022 21:53:10 +0100 Subject: [PATCH 14/26] Added goose-message unit tests --- .../__snapshots__/goose-message.test.snap.js | 19 +++++++++ .../elements/goose-message.test.ts | 41 +++++++++++++++++++ .../subscription/goose-message.test.ts | 23 ----------- 3 files changed, 60 insertions(+), 23 deletions(-) create mode 100644 test/unit/editors/subscription/elements/__snapshots__/goose-message.test.snap.js create mode 100644 test/unit/editors/subscription/elements/goose-message.test.ts delete mode 100644 test/unit/editors/subscription/goose-message.test.ts diff --git a/test/unit/editors/subscription/elements/__snapshots__/goose-message.test.snap.js b/test/unit/editors/subscription/elements/__snapshots__/goose-message.test.snap.js new file mode 100644 index 0000000000..9e2e07de5d --- /dev/null +++ b/test/unit/editors/subscription/elements/__snapshots__/goose-message.test.snap.js @@ -0,0 +1,19 @@ +/* @web/test-runner snapshot v1 */ +export const snapshots = {}; + +snapshots["goose-message looks like the latest snapshot"] = +` + + GCB + + + + +`; +/* end snapshot goose-message looks like the latest snapshot */ + diff --git a/test/unit/editors/subscription/elements/goose-message.test.ts b/test/unit/editors/subscription/elements/goose-message.test.ts new file mode 100644 index 0000000000..2e4614149c --- /dev/null +++ b/test/unit/editors/subscription/elements/goose-message.test.ts @@ -0,0 +1,41 @@ +import { html, fixture, expect } from '@open-wc/testing'; +import { spy } from 'sinon'; + +import '../../../../../src/editors/subscription/elements/goose-message.js' +import { GOOSEMessage } from '../../../../../src/editors/subscription/elements/goose-message.js'; + +describe('goose-message', () => { + let element: GOOSEMessage; + let validSCL: XMLDocument; + + let gseControl: Element; + + beforeEach(async () => { + validSCL = await fetch('/test/testfiles/valid2007B4.scd') + .then(response => response.text()) + .then(str => new DOMParser().parseFromString(str, 'application/xml')); + + gseControl = validSCL.querySelector('GSEControl[name="GCB"]')!; + + element = await fixture(html``); + }); + + it('a newGOOSESelectEvent is fired when clicking the goose message element', async () => { + const newGOOSESelectEvent = spy(); + window.addEventListener('goose-dataset', newGOOSESelectEvent); + + const listItem = (element.shadowRoot!.querySelector('mwc-list-item')); + listItem.click(); + + await element.requestUpdate(); + expect(newGOOSESelectEvent).to.have.been.called; + expect(newGOOSESelectEvent.args[0][0].detail['gseControl']).to.eql(gseControl); + expect(newGOOSESelectEvent.args[0][0].detail['dataset']).to.eql(gseControl.closest('IED')?.querySelector('DataSet[name="GooseDataSet1"]')); + }); + + it('looks like the latest snapshot', async () => { + await expect(element).shadowDom.to.equalSnapshot(); + }); +}); diff --git a/test/unit/editors/subscription/goose-message.test.ts b/test/unit/editors/subscription/goose-message.test.ts deleted file mode 100644 index 68c945d354..0000000000 --- a/test/unit/editors/subscription/goose-message.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { html, fixture, expect } from '@open-wc/testing'; - -import '../../../../src/editors/subscription/elements/goose-message.js' -import { GOOSEMessage } from '../../../../src/editors/subscription/elements/goose-message.js'; - -describe('goose-message', () => { - let element: GOOSEMessage; - let validSCL: XMLDocument; - - beforeEach(async () => { - validSCL = await fetch('/test/testfiles/valid2007B4.scd') - .then(response => response.text()) - .then(str => new DOMParser().parseFromString(str, 'application/xml')); - - element = await fixture(html``); - }); - - it('looks like the latest snapshot', async () => { - await expect(element).shadowDom.to.equalSnapshot(); - }); -}); From 89b03f669b4cfbdc7a429ba8fa41a4e46565a1df Mon Sep 17 00:00:00 2001 From: Flurb Date: Wed, 2 Mar 2022 22:19:28 +0100 Subject: [PATCH 15/26] Added ied-element unit tests --- .../subscription/elements/ied-element.ts | 5 ++- .../__snapshots__/ied-element.test.snap.js | 21 ++++++++++ .../subscription/elements/ied-element.test.ts | 42 +++++++++++++++++++ 3 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 test/unit/editors/subscription/elements/__snapshots__/ied-element.test.snap.js create mode 100644 test/unit/editors/subscription/elements/ied-element.test.ts diff --git a/src/editors/subscription/elements/ied-element.ts b/src/editors/subscription/elements/ied-element.ts index ecffde6ad3..8531cbe25a 100644 --- a/src/editors/subscription/elements/ied-element.ts +++ b/src/editors/subscription/elements/ied-element.ts @@ -14,13 +14,14 @@ export class IEDElement extends LitElement { @property({ attribute: false }) element!: Element; + @property({ attribute: false }) status!: SubscribeStatus; private onIedSelect = () => { - document.querySelector('open-scd')!.dispatchEvent( + this.dispatchEvent( newIEDSubscriptionEvent( this.element, - this.status + this.status ?? SubscribeStatus.None ) ); }; diff --git a/test/unit/editors/subscription/elements/__snapshots__/ied-element.test.snap.js b/test/unit/editors/subscription/elements/__snapshots__/ied-element.test.snap.js new file mode 100644 index 0000000000..f61bc9b17a --- /dev/null +++ b/test/unit/editors/subscription/elements/__snapshots__/ied-element.test.snap.js @@ -0,0 +1,21 @@ +/* @web/test-runner snapshot v1 */ +export const snapshots = {}; + +snapshots["ied-element looks like the latest snapshot"] = +` + + IED2 + + + add + + +`; +/* end snapshot ied-element looks like the latest snapshot */ + diff --git a/test/unit/editors/subscription/elements/ied-element.test.ts b/test/unit/editors/subscription/elements/ied-element.test.ts new file mode 100644 index 0000000000..49beb2d162 --- /dev/null +++ b/test/unit/editors/subscription/elements/ied-element.test.ts @@ -0,0 +1,42 @@ +import { html, fixture, expect } from '@open-wc/testing'; +import { spy } from 'sinon'; + +import '../../../../../src/editors/subscription/elements/ied-element.js' +import { IEDElement } from '../../../../../src/editors/subscription/elements/ied-element.js'; +import { SubscribeStatus } from '../../../../../src/foundation.js'; + +describe('ied-element', () => { + let element: IEDElement; + let validSCL: XMLDocument; + + let iedElement: Element; + + beforeEach(async () => { + validSCL = await fetch('/test/testfiles/valid2007B4.scd') + .then(response => response.text()) + .then(str => new DOMParser().parseFromString(str, 'application/xml')); + + iedElement = validSCL.querySelector('IED[name="IED2"]')!; + + element = await fixture(html``); + }); + + it('a newGOOSESelectEvent is fired when clicking the goose message element', async () => { + const newIEDSubscriptionEvent = spy(); + window.addEventListener('ied-subscription', newIEDSubscriptionEvent); + + const listItem = (element.shadowRoot!.querySelector('mwc-list-item')); + listItem.click(); + + await element.requestUpdate(); + expect(newIEDSubscriptionEvent).to.have.been.called; + expect(newIEDSubscriptionEvent.args[0][0].detail['element']).to.eql(iedElement); + expect(newIEDSubscriptionEvent.args[0][0].detail['subscribeStatus']).to.eql(SubscribeStatus.None); + }); + + it('looks like the latest snapshot', async () => { + await expect(element).shadowDom.to.equalSnapshot(); + }); +}); From 2b77b1f891f60f913eb2d7a1a910b24e3ae70e40 Mon Sep 17 00:00:00 2001 From: Flurb Date: Wed, 2 Mar 2022 22:36:48 +0100 Subject: [PATCH 16/26] Added publisher goose list unit tests --- .../__snapshots__/publisher-goose-list.test.snap.js | 11 +++++++++++ .../editors/subscription/publisher-goose-list.test.ts | 6 ++++++ 2 files changed, 17 insertions(+) diff --git a/test/unit/editors/subscription/__snapshots__/publisher-goose-list.test.snap.js b/test/unit/editors/subscription/__snapshots__/publisher-goose-list.test.snap.js index ea177daf07..7bf81ed850 100644 --- a/test/unit/editors/subscription/__snapshots__/publisher-goose-list.test.snap.js +++ b/test/unit/editors/subscription/__snapshots__/publisher-goose-list.test.snap.js @@ -54,3 +54,14 @@ snapshots["publisher-goose-list looks like the latest snapshot"] = `; /* end snapshot publisher-goose-list looks like the latest snapshot */ +snapshots["publisher-goose-list looks like the latest snapshot without a doc loaded"] = +`
    +

    + [subscription.publisherGoose.title] +

    + + +
    +`; +/* end snapshot publisher-goose-list looks like the latest snapshot without a doc loaded */ + diff --git a/test/unit/editors/subscription/publisher-goose-list.test.ts b/test/unit/editors/subscription/publisher-goose-list.test.ts index 0690448f07..19d7a87254 100644 --- a/test/unit/editors/subscription/publisher-goose-list.test.ts +++ b/test/unit/editors/subscription/publisher-goose-list.test.ts @@ -20,4 +20,10 @@ describe('publisher-goose-list', () => { it('looks like the latest snapshot', async () => { await expect(element).shadowDom.to.equalSnapshot(); }); + + it('looks like the latest snapshot without a doc loaded', async () => { + element = await fixture(html``); + + await expect(element).shadowDom.to.equalSnapshot(); + }); }); From dfb04a86597caf9edd522323ade0c5d4b2273c05 Mon Sep 17 00:00:00 2001 From: Flurb Date: Thu, 3 Mar 2022 00:02:09 +0100 Subject: [PATCH 17/26] Remove console.log --- src/Editing.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Editing.ts b/src/Editing.ts index f98c645ae4..7e26faaed7 100644 --- a/src/Editing.ts +++ b/src/Editing.ts @@ -72,7 +72,6 @@ export function Editing(Base: TBase) { } private onCreate(action: Create) { - console.log('onCreate') if (!this.checkCreateValidity(action)) return false; if (action.new.reference === undefined) @@ -98,7 +97,6 @@ export function Editing(Base: TBase) { } private onDelete(action: Delete) { - console.log('onDelete') if (!action.old.reference) action.old.reference = action.old.element.nextSibling; @@ -149,7 +147,6 @@ export function Editing(Base: TBase) { } private onMove(action: Move) { - console.log('onMove') if (!this.checkMoveValidity(action)) return false; if (!action.old.reference) @@ -209,7 +206,6 @@ export function Editing(Base: TBase) { } private onUpdate(action: Update) { - console.log('onUpdate') if (!this.checkUpdateValidity(action)) return false; action.new.element.append(...Array.from(action.old.element.children)); From 2d68e4d954ff2891b770d7dbb03aad64f12a77b5 Mon Sep 17 00:00:00 2001 From: Flurb Date: Thu, 3 Mar 2022 00:22:19 +0100 Subject: [PATCH 18/26] Remove serviceType --- src/editors/subscription/subscriber-ied-list.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/editors/subscription/subscriber-ied-list.ts b/src/editors/subscription/subscriber-ied-list.ts index 41e15b4010..f9706392e1 100644 --- a/src/editors/subscription/subscriber-ied-list.ts +++ b/src/editors/subscription/subscriber-ied-list.ts @@ -11,7 +11,7 @@ import { import './elements/ied-element.js'; import { translate } from 'lit-translate'; -import { createElement, GOOSESelectEvent, IEDSubscriptionEvent, newActionEvent, SubscribeStatus } from '../../foundation.js'; +import { createElement, GOOSESelectEvent, IEDSubscriptionEvent, newActionEvent, newGOOSESelectEvent, SubscribeStatus } from '../../foundation.js'; import { styles } from '../templates/foundation.js'; /** @@ -209,7 +209,7 @@ export class SubscriberIEDList extends LitElement { const inputsElement = clone.querySelector('LN0 > Inputs'); this.currentDataset.querySelectorAll('FCDA').forEach(fcda => { - const extRef = inputsElement?.querySelector(`ExtRef[iedName=${this.currentGooseIEDName}][serviceType="GOOSE"]` + + const extRef = inputsElement?.querySelector(`ExtRef[iedName=${this.currentGooseIEDName}]` + `${fcdaReferences.map(fcdaRef => fcda.getAttribute(fcdaRef) ? `[${fcdaRef}="${fcda.getAttribute(fcdaRef)}"]` From be3e65c5027ef28139b5706b456a7243650041e1 Mon Sep 17 00:00:00 2001 From: Flurb Date: Thu, 3 Mar 2022 13:15:09 +0100 Subject: [PATCH 19/26] Remove unused import --- src/editors/subscription/subscriber-ied-list.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editors/subscription/subscriber-ied-list.ts b/src/editors/subscription/subscriber-ied-list.ts index f9706392e1..f29a16d75a 100644 --- a/src/editors/subscription/subscriber-ied-list.ts +++ b/src/editors/subscription/subscriber-ied-list.ts @@ -11,7 +11,7 @@ import { import './elements/ied-element.js'; import { translate } from 'lit-translate'; -import { createElement, GOOSESelectEvent, IEDSubscriptionEvent, newActionEvent, newGOOSESelectEvent, SubscribeStatus } from '../../foundation.js'; +import { createElement, GOOSESelectEvent, IEDSubscriptionEvent, newActionEvent, SubscribeStatus } from '../../foundation.js'; import { styles } from '../templates/foundation.js'; /** From 7918ed60a0e1d587e8a366574e7aeea4b679dde1 Mon Sep 17 00:00:00 2001 From: Flurb Date: Thu, 3 Mar 2022 13:56:21 +0100 Subject: [PATCH 20/26] Added add/clear icon toggle to ied-element --- src/editors/subscription/elements/ied-element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editors/subscription/elements/ied-element.ts b/src/editors/subscription/elements/ied-element.ts index 8531cbe25a..80e8dd305a 100644 --- a/src/editors/subscription/elements/ied-element.ts +++ b/src/editors/subscription/elements/ied-element.ts @@ -32,7 +32,7 @@ export class IEDElement extends LitElement { graphic="avatar" hasMeta> ${this.element.getAttribute('name')} - add + ${this.status == SubscribeStatus.Full ? html`clear` : html`add`}
    `; } From fd2d02ecea988917c2f2054b47ca2bca039f4ac4 Mon Sep 17 00:00:00 2001 From: Flurb Date: Thu, 3 Mar 2022 14:58:23 +0100 Subject: [PATCH 21/26] Fixing bug --- .../subscription/subscriber-ied-list.ts | 111 ++++++++++-------- 1 file changed, 62 insertions(+), 49 deletions(-) diff --git a/src/editors/subscription/subscriber-ied-list.ts b/src/editors/subscription/subscriber-ied-list.ts index f29a16d75a..5df5c7f4b3 100644 --- a/src/editors/subscription/subscriber-ied-list.ts +++ b/src/editors/subscription/subscriber-ied-list.ts @@ -11,7 +11,7 @@ import { import './elements/ied-element.js'; import { translate } from 'lit-translate'; -import { createElement, GOOSESelectEvent, IEDSubscriptionEvent, newActionEvent, SubscribeStatus } from '../../foundation.js'; +import { createElement, GOOSESelectEvent, IEDSubscriptionEvent, newActionEvent, newGOOSESelectEvent, SubscribeStatus } from '../../foundation.js'; import { styles } from '../templates/foundation.js'; /** @@ -36,26 +36,40 @@ const fcdaReferences = [ "daName", ]; -/** An sub element for subscribing and unsubscribing IEDs to GOOSE messages. */ -@customElement('subscriber-ied-list') -export class SubscriberIEDList extends LitElement { - @property() - doc!: XMLDocument; +/** + * Internal persistent state, so it's not lost when + * subscribing / unsubscribing. + */ +interface State { + /** Current selected GSEControl element */ + currentGseControl: Element | undefined; + + /** The current selected dataset */ + currentDataset: Element | undefined; + + /** The name of the IED belonging to the current selected GOOSE */ + currentGooseIEDName: string | undefined | null; /** List holding all current subscribed IEDs. */ - subscribedIeds: IED[] = []; + subscribedIeds: IED[]; /** List holding all current avaialble IEDs which are not subscribed. */ - availableIeds: IED[] = []; - - /** The current selected dataset */ - currentDataset!: Element; + availableIeds: IED[]; +} - /** Current selected GSEControl element */ - currentGseControl!: Element; +const localState : State = { + currentGseControl: undefined, + currentDataset: undefined, + currentGooseIEDName: undefined, + subscribedIeds: [], + availableIeds: [] +} - /** The name of the IED belonging to the current selected GOOSE */ - currentGooseIEDName!: string | undefined | null; +/** An sub element for subscribing and unsubscribing IEDs to GOOSE messages. */ +@customElement('subscriber-ied-list') +export class SubscriberIEDList extends LitElement { + @property() + doc!: XMLDocument; @query('div') subscriberWrapper!: Element; @@ -77,14 +91,16 @@ export class SubscriberIEDList extends LitElement { * @param event - Incoming event. */ private async onGOOSEDataSetEvent(event: GOOSESelectEvent) { - this.currentGseControl = event.detail.gseControl; - this.currentDataset = event.detail.dataset; - this.currentGooseIEDName = this.currentGseControl.closest('IED')?.getAttribute('name'); + localState.currentGseControl = event.detail.gseControl; + localState.currentDataset = event.detail.dataset; + + localState.currentGooseIEDName = localState.currentGseControl.closest('IED')?.getAttribute('name'); - this.clearIedLists(); + localState.subscribedIeds = []; + localState.availableIeds = []; Array.from(this.doc.querySelectorAll(':root > IED')) - .filter(ied => ied.getAttribute('name') != this.currentGooseIEDName) + .filter(ied => ied.getAttribute('name') != localState.currentGooseIEDName) .forEach(ied => { const inputs = ied.querySelector(`LN0 > Inputs`); @@ -94,15 +110,15 @@ export class SubscriberIEDList extends LitElement { * If no Inputs element is found, we can safely say it's not subscribed. */ if (!inputs) { - this.availableIeds.push({element: ied}); + localState.availableIeds.push({element: ied}); return; } /** * Count all the linked ExtRefs. */ - this.currentDataset.querySelectorAll('FCDA').forEach(fcda => { - if(inputs.querySelector(`ExtRef[iedName=${this.currentGooseIEDName}]` + + localState.currentDataset!.querySelectorAll('FCDA').forEach(fcda => { + if(inputs.querySelector(`ExtRef[iedName=${localState.currentGooseIEDName}]` + `${fcdaReferences.map(fcdaRef => fcda.getAttribute(fcdaRef) ? `[${fcdaRef}="${fcda.getAttribute(fcdaRef)}"]` @@ -117,14 +133,14 @@ export class SubscriberIEDList extends LitElement { * partially subscribed and fully subscribed. */ if (numberOfLinkedExtRefs == 0) { - this.availableIeds.push({element: ied}); + localState.availableIeds.push({element: ied}); return; } - if (numberOfLinkedExtRefs == this.currentDataset.querySelectorAll('FCDA').length) { - this.subscribedIeds.push({element: ied}); + if (numberOfLinkedExtRefs == localState.currentDataset!.querySelectorAll('FCDA').length) { + localState.subscribedIeds.push({element: ied}); } else { - this.availableIeds.push({element: ied, partial: true}); + localState.availableIeds.push({element: ied, partial: true}); } }) @@ -166,8 +182,8 @@ export class SubscriberIEDList extends LitElement { } /** Updating the ExtRefs according the dataset */ - this.currentDataset.querySelectorAll('FCDA').forEach(fcda => { - if(!inputsElement!.querySelector(`ExtRef[iedName=${this.currentGooseIEDName}]` + + localState.currentDataset!.querySelectorAll('FCDA').forEach(fcda => { + if(!inputsElement!.querySelector(`ExtRef[iedName=${localState.currentGooseIEDName}]` + `${fcdaReferences.map(fcdaRef => fcda.getAttribute(fcdaRef) ? `[${fcdaRef}="${fcda.getAttribute(fcdaRef)}"]` @@ -177,7 +193,7 @@ export class SubscriberIEDList extends LitElement { ied.ownerDocument, 'ExtRef', { - iedName: this.currentGooseIEDName!, + iedName: localState.currentGooseIEDName!, serviceType: 'GOOSE', ldInst: fcda.getAttribute('ldInst') ?? '', lnClass: fcda.getAttribute('lnClass') ?? '', @@ -208,8 +224,8 @@ export class SubscriberIEDList extends LitElement { const inputsElement = clone.querySelector('LN0 > Inputs'); - this.currentDataset.querySelectorAll('FCDA').forEach(fcda => { - const extRef = inputsElement?.querySelector(`ExtRef[iedName=${this.currentGooseIEDName}]` + + localState.currentDataset!.querySelectorAll('FCDA').forEach(fcda => { + const extRef = inputsElement?.querySelector(`ExtRef[iedName=${localState.currentGooseIEDName}]` + `${fcdaReferences.map(fcdaRef => fcda.getAttribute(fcdaRef) ? `[${fcdaRef}="${fcda.getAttribute(fcdaRef)}"]` @@ -243,8 +259,6 @@ export class SubscriberIEDList extends LitElement { }) ); - await this.updateComplete; - this.dispatchEvent( newActionEvent({ new: { @@ -254,14 +268,13 @@ export class SubscriberIEDList extends LitElement { } }) ); - } - /** - * Clear all the IED lists. - */ - private clearIedLists() { - this.subscribedIeds = []; - this.availableIeds = []; + this.dispatchEvent( + newGOOSESelectEvent( + localState.currentGseControl!, + localState.currentDataset! + ) + ); } protected updated(): void { @@ -271,23 +284,23 @@ export class SubscriberIEDList extends LitElement { } render(): TemplateResult { - const partialSubscribedIeds = this.availableIeds.filter(ied => ied.partial); - const gseControlName = this.currentGseControl?.getAttribute('name') ?? undefined; + const partialSubscribedIeds = localState.availableIeds.filter(ied => ied.partial); + const gseControlName = localState.currentGseControl?.getAttribute('name') ?? undefined; return html`

    ${translate('subscription.subscriberIed.title', { - selected: gseControlName ? this.currentGooseIEDName + ' > ' + gseControlName : 'IED' + selected: gseControlName ? localState.currentGooseIEDName + ' > ' + gseControlName : 'IED' })}

    - ${this.currentGseControl ? + ${localState.currentGseControl ? html`
    ${translate('subscription.subscriberIed.subscribed')}
  • - ${this.subscribedIeds.length > 0 ? - this.subscribedIeds.map(ied => html``) + ${localState.subscribedIeds.length > 0 ? + localState.subscribedIeds.map(ied => html``) : html` ${translate('subscription.none')} `} @@ -308,8 +321,8 @@ export class SubscriberIEDList extends LitElement { ${translate('subscription.subscriberIed.availableToSubscribe')}
  • - ${this.availableIeds.length > 0 ? - this.availableIeds.map(ied => html``) + ${localState.availableIeds.length > 0 ? + localState.availableIeds.map(ied => html``) : html` ${translate('subscription.none')} `} From 4c7c4321b2e5b158bf76ab542d4d4d781fe72cf7 Mon Sep 17 00:00:00 2001 From: Flurb Date: Thu, 3 Mar 2022 15:06:36 +0100 Subject: [PATCH 22/26] Moving newGOOSESelectEvent dispatch --- .../subscription/subscriber-ied-list.ts | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/editors/subscription/subscriber-ied-list.ts b/src/editors/subscription/subscriber-ied-list.ts index 5df5c7f4b3..161c0080dd 100644 --- a/src/editors/subscription/subscriber-ied-list.ts +++ b/src/editors/subscription/subscriber-ied-list.ts @@ -213,6 +213,13 @@ export class SubscriberIEDList extends LitElement { } this.replaceElement(ied, clone); + + this.dispatchEvent( + newGOOSESelectEvent( + localState.currentGseControl!, + localState.currentDataset! + ) + ); } /** @@ -239,6 +246,13 @@ export class SubscriberIEDList extends LitElement { clone.querySelector('LN0')?.appendChild(inputsElement!); this.replaceElement(ied, clone); + + this.dispatchEvent( + newGOOSESelectEvent( + localState.currentGseControl!, + localState.currentDataset! + ) + ); } /** @@ -268,13 +282,6 @@ export class SubscriberIEDList extends LitElement { } }) ); - - this.dispatchEvent( - newGOOSESelectEvent( - localState.currentGseControl!, - localState.currentDataset! - ) - ); } protected updated(): void { From f08a7cb431cf4e6c0e843275de38e02952847d5c Mon Sep 17 00:00:00 2001 From: Flurb Date: Fri, 4 Mar 2022 23:42:51 +0100 Subject: [PATCH 23/26] Subscribe/Unsubscribe now work on both LN0 and LN --- .../subscription/subscriber-ied-list.ts | 51 ++++++++++--------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/src/editors/subscription/subscriber-ied-list.ts b/src/editors/subscription/subscriber-ied-list.ts index 161c0080dd..b1de39f2de 100644 --- a/src/editors/subscription/subscriber-ied-list.ts +++ b/src/editors/subscription/subscriber-ied-list.ts @@ -102,14 +102,14 @@ export class SubscriberIEDList extends LitElement { Array.from(this.doc.querySelectorAll(':root > IED')) .filter(ied => ied.getAttribute('name') != localState.currentGooseIEDName) .forEach(ied => { - const inputs = ied.querySelector(`LN0 > Inputs`); + const inputElements = ied.querySelectorAll(`LN0 > Inputs, LN > Inputs`); let numberOfLinkedExtRefs = 0; /** * If no Inputs element is found, we can safely say it's not subscribed. */ - if (!inputs) { + if (!inputElements) { localState.availableIeds.push({element: ied}); return; } @@ -118,14 +118,16 @@ export class SubscriberIEDList extends LitElement { * Count all the linked ExtRefs. */ localState.currentDataset!.querySelectorAll('FCDA').forEach(fcda => { - if(inputs.querySelector(`ExtRef[iedName=${localState.currentGooseIEDName}]` + - `${fcdaReferences.map(fcdaRef => - fcda.getAttribute(fcdaRef) - ? `[${fcdaRef}="${fcda.getAttribute(fcdaRef)}"]` - : '').join('') - }`)) { - numberOfLinkedExtRefs++; - } + inputElements.forEach(inputs => { + if(inputs.querySelector(`ExtRef[iedName=${localState.currentGooseIEDName}]` + + `${fcdaReferences.map(fcdaRef => + fcda.getAttribute(fcdaRef) + ? `[${fcdaRef}="${fcda.getAttribute(fcdaRef)}"]` + : '').join('') + }`)) { + numberOfLinkedExtRefs++; + } + }) }) /** @@ -229,21 +231,22 @@ export class SubscriberIEDList extends LitElement { private unsubscribe(ied: Element): void { const clone: Element = ied.cloneNode(true); - const inputsElement = clone.querySelector('LN0 > Inputs'); - - localState.currentDataset!.querySelectorAll('FCDA').forEach(fcda => { - const extRef = inputsElement?.querySelector(`ExtRef[iedName=${localState.currentGooseIEDName}]` + - `${fcdaReferences.map(fcdaRef => - fcda.getAttribute(fcdaRef) - ? `[${fcdaRef}="${fcda.getAttribute(fcdaRef)}"]` - : '').join('') - }`); - - inputsElement?.removeChild(extRef!); - }); + clone.querySelectorAll('LN0 > Inputs, LN > Inputs').forEach(inputs => { + localState.currentDataset!.querySelectorAll('FCDA').forEach(fcda => { + const extRef = inputs.querySelector(`ExtRef[iedName=${localState.currentGooseIEDName}]` + + `${fcdaReferences.map(fcdaRef => + fcda.getAttribute(fcdaRef) + ? `[${fcdaRef}="${fcda.getAttribute(fcdaRef)}"]` + : '').join('') + }`); + + if (extRef) inputs.removeChild(extRef!); + }); - clone.querySelector('LN0 > Inputs')?.remove(); - clone.querySelector('LN0')?.appendChild(inputsElement!); + const lnParent = inputs.closest('LN0,LN'); + lnParent?.querySelector('Inputs')?.remove(); + lnParent?.appendChild(inputs); + }) this.replaceElement(ied, clone); From 3a2e2a41277b120fa3c6e9ec4745abd5c563b3a5 Mon Sep 17 00:00:00 2001 From: Flurb Date: Fri, 4 Mar 2022 23:56:23 +0100 Subject: [PATCH 24/26] Refactoring --- src/editors/subscription/subscriber-ied-list.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/editors/subscription/subscriber-ied-list.ts b/src/editors/subscription/subscriber-ied-list.ts index b1de39f2de..74703e125a 100644 --- a/src/editors/subscription/subscriber-ied-list.ts +++ b/src/editors/subscription/subscriber-ied-list.ts @@ -93,7 +93,6 @@ export class SubscriberIEDList extends LitElement { private async onGOOSEDataSetEvent(event: GOOSESelectEvent) { localState.currentGseControl = event.detail.gseControl; localState.currentDataset = event.detail.dataset; - localState.currentGooseIEDName = localState.currentGseControl.closest('IED')?.getAttribute('name'); localState.subscribedIeds = []; @@ -151,7 +150,7 @@ export class SubscriberIEDList extends LitElement { } /** - * When a IEDSubscriptionEvent is received, check if + * When a IEDSubscriptionEvent is received, check if * @param event - Incoming event. */ private async onIEDSubscriptionEvent(event: IEDSubscriptionEvent) { @@ -242,11 +241,7 @@ export class SubscriberIEDList extends LitElement { if (extRef) inputs.removeChild(extRef!); }); - - const lnParent = inputs.closest('LN0,LN'); - lnParent?.querySelector('Inputs')?.remove(); - lnParent?.appendChild(inputs); - }) + }); this.replaceElement(ied, clone); From ba71a55c1da6183d34d100c38374eb7c9fcdf33a Mon Sep 17 00:00:00 2001 From: Jakob Vogelsang Date: Mon, 7 Mar 2022 09:20:50 +0100 Subject: [PATCH 25/26] refactor: use complex actions to subscribe and unsubscribe --- .../subscription/elements/goose-message.ts | 22 +- .../subscription/elements/ied-element.ts | 23 +- src/editors/subscription/foundation.ts | 115 +++++ .../subscription/publisher-goose-list.ts | 64 +-- .../subscription/subscriber-ied-list.ts | 418 ++++++++++-------- src/foundation.ts | 45 -- .../subscription/elements/ied-element.test.ts | 2 +- 7 files changed, 411 insertions(+), 278 deletions(-) create mode 100644 src/editors/subscription/foundation.ts diff --git a/src/editors/subscription/elements/goose-message.ts b/src/editors/subscription/elements/goose-message.ts index e7b83d24a9..29eb73e196 100644 --- a/src/editors/subscription/elements/goose-message.ts +++ b/src/editors/subscription/elements/goose-message.ts @@ -1,13 +1,16 @@ import { - css, customElement, html, LitElement, property, TemplateResult, } from 'lit-element'; -import { newGOOSESelectEvent } from '../../../foundation.js'; + +import '@material/mwc-icon'; +import '@material/mwc-list/mwc-list-item'; + import { gooseIcon } from '../../../icons.js'; +import { newGOOSESelectEvent } from '../foundation.js'; @customElement('goose-message') export class GOOSEMessage extends LitElement { @@ -17,23 +20,16 @@ export class GOOSEMessage extends LitElement { private onGooseSelect = () => { const ln = this.element.parentElement; - const dataset = ln?.querySelector(`DataSet[name=${this.element.getAttribute('datSet')}]`); - this.dispatchEvent( - newGOOSESelectEvent( - this.element, - dataset! - ) + const dataset = ln?.querySelector( + `DataSet[name=${this.element.getAttribute('datSet')}]` ); + this.dispatchEvent(newGOOSESelectEvent(this.element, dataset!)); }; render(): TemplateResult { - return html` + return html` ${this.element.getAttribute('name')} ${gooseIcon} `; } - - static styles = css``; } diff --git a/src/editors/subscription/elements/ied-element.ts b/src/editors/subscription/elements/ied-element.ts index 80e8dd305a..ff11cbe9c8 100644 --- a/src/editors/subscription/elements/ied-element.ts +++ b/src/editors/subscription/elements/ied-element.ts @@ -1,12 +1,15 @@ import { - css, customElement, html, LitElement, property, TemplateResult, } from 'lit-element'; -import { newIEDSubscriptionEvent, SubscribeStatus } from '../../../foundation.js'; + +import '@material/mwc-icon'; +import '@material/mwc-list/mwc-list-item'; + +import { newIEDSubscriptionEvent, SubscribeStatus } from '../foundation.js'; @customElement('ied-element') export class IEDElement extends LitElement { @@ -19,10 +22,7 @@ export class IEDElement extends LitElement { private onIedSelect = () => { this.dispatchEvent( - newIEDSubscriptionEvent( - this.element, - this.status ?? SubscribeStatus.None - ) + newIEDSubscriptionEvent(this.element, this.status ?? SubscribeStatus.None) ); }; @@ -30,11 +30,14 @@ export class IEDElement extends LitElement { return html` + hasMeta + > ${this.element.getAttribute('name')} - ${this.status == SubscribeStatus.Full ? html`clear` : html`add`} + ${this.status == SubscribeStatus.Full + ? html`clear` + : html`add`} `; } - - static styles = css``; } diff --git a/src/editors/subscription/foundation.ts b/src/editors/subscription/foundation.ts new file mode 100644 index 0000000000..c1445a7115 --- /dev/null +++ b/src/editors/subscription/foundation.ts @@ -0,0 +1,115 @@ +import { css } from 'lit-element'; + +/** + * Enumeration stating the Subscribe status of a IED to a GOOSE. + */ +export enum SubscribeStatus { + Full, + Partial, + None, +} + +export interface GOOSESelectDetail { + gseControl: Element; + dataset: Element; +} +export type GOOSESelectEvent = CustomEvent; +export function newGOOSESelectEvent( + gseControl: Element, + dataset: Element, + eventInitDict?: CustomEventInit +): GOOSESelectEvent { + return new CustomEvent('goose-dataset', { + bubbles: true, + composed: true, + ...eventInitDict, + detail: { gseControl, dataset, ...eventInitDict?.detail }, + }); +} + +export interface IEDSubscriptionDetail { + element: Element; + subscribeStatus: SubscribeStatus; +} +export type IEDSubscriptionEvent = CustomEvent; +export function newIEDSubscriptionEvent( + element: Element, + subscribeStatus: SubscribeStatus +): IEDSubscriptionEvent { + return new CustomEvent('ied-subscription', { + bubbles: true, + composed: true, + detail: { element, subscribeStatus }, + }); +} + +/** Common `CSS` styles used by DataTypeTemplate subeditors */ +export const styles = css` + :host(.moving) section { + opacity: 0.3; + } + + section { + background-color: var(--mdc-theme-surface); + transition: all 200ms linear; + outline-color: var(--mdc-theme-primary); + outline-style: solid; + outline-width: 0px; + opacity: 1; + } + + section:focus { + box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), + 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2); + } + + section:focus-within { + outline-width: 2px; + transition: all 250ms linear; + } + + h1, + h2, + h3 { + color: var(--mdc-theme-on-surface); + font-family: 'Roboto', sans-serif; + font-weight: 300; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + margin: 0px; + line-height: 48px; + padding-left: 0.3em; + transition: background-color 150ms linear; + } + + section:focus-within > h1, + section:focus-within > h2, + section:focus-within > h3 { + color: var(--mdc-theme-surface); + background-color: var(--mdc-theme-primary); + transition: background-color 200ms linear; + } + + h1 > nav, + h2 > nav, + h3 > nav, + h1 > abbr > mwc-icon-button, + h2 > abbr > mwc-icon-button, + h3 > abbr > mwc-icon-button { + float: right; + } + + abbr[title] { + border-bottom: none !important; + cursor: inherit !important; + text-decoration: none !important; + } +`; + +declare global { + interface ElementEventMap { + ['goose-dataset']: GOOSESelectEvent; + ['ied-subscription']: IEDSubscriptionEvent; + } +} diff --git a/src/editors/subscription/publisher-goose-list.ts b/src/editors/subscription/publisher-goose-list.ts index de3b5b6837..146229b78e 100644 --- a/src/editors/subscription/publisher-goose-list.ts +++ b/src/editors/subscription/publisher-goose-list.ts @@ -6,22 +6,27 @@ import { property, TemplateResult, } from 'lit-element'; +import { translate } from 'lit-translate'; -import './elements/goose-message.js'; +import '@material/mwc-icon'; +import '@material/mwc-list'; +import '@material/mwc-list/mwc-list-item'; -import { translate } from 'lit-translate'; +import './elements/goose-message.js'; import { compareNames, getNameAttribute } from '../../foundation.js'; -import { styles } from '../templates/foundation.js'; +import { styles } from './foundation.js'; /** An sub element for showing all published GOOSE messages per IED. */ @customElement('publisher-goose-list') export class PublisherGOOSEList extends LitElement { - @property() + @property({ attribute: false }) doc!: XMLDocument; - private get ieds() : Element[] { - return (this.doc) - ? Array.from(this.doc.querySelectorAll(':root > IED')).sort((a,b) => compareNames(a,b)) + private get ieds(): Element[] { + return this.doc + ? Array.from(this.doc.querySelectorAll(':root > IED')).sort((a, b) => + compareNames(a, b) + ) : []; } @@ -30,37 +35,40 @@ export class PublisherGOOSEList extends LitElement { * @param ied - The IED to search through. * @returns All the published GOOSE messages of this specific IED. */ - private getGSEControls(ied: Element) : Element[] { - return Array.from(ied.querySelectorAll(':scope > AccessPoint > Server > LDevice > LN0 > GSEControl')); + private getGSEControls(ied: Element): Element[] { + return Array.from( + ied.querySelectorAll( + ':scope > AccessPoint > Server > LDevice > LN0 > GSEControl' + ) + ); } render(): TemplateResult { - return html` -
    + return html`

    ${translate('subscription.publisherGoose.title')}

    ${this.ieds.map(ied => - ied.querySelector('GSEControl') ? - html` - - ${getNameAttribute(ied)} - developer_board - -
  • - ${this.getGSEControls(ied).map(control => - html``)} - ` : `` - ) - } -
    -
    `; + ied.querySelector('GSEControl') + ? html` + + ${getNameAttribute(ied)} + developer_board + +
  • + ${this.getGSEControls(ied).map( + control => + html`` + )} + ` + : `` + )} + +
    `; } static styles = css` ${styles} - + mwc-list { height: 100vh; overflow-y: scroll; diff --git a/src/editors/subscription/subscriber-ied-list.ts b/src/editors/subscription/subscriber-ied-list.ts index 74703e125a..31fb6f44f1 100644 --- a/src/editors/subscription/subscriber-ied-list.ts +++ b/src/editors/subscription/subscriber-ied-list.ts @@ -7,12 +7,26 @@ import { query, TemplateResult, } from 'lit-element'; +import { translate } from 'lit-translate'; -import './elements/ied-element.js'; +import '@material/mwc-icon'; +import '@material/mwc-list'; +import '@material/mwc-list/mwc-list-item'; -import { translate } from 'lit-translate'; -import { createElement, GOOSESelectEvent, IEDSubscriptionEvent, newActionEvent, newGOOSESelectEvent, SubscribeStatus } from '../../foundation.js'; -import { styles } from '../templates/foundation.js'; +import './elements/ied-element.js'; +import { + Create, + createElement, + Delete, + newActionEvent, +} from '../../foundation.js'; +import { + GOOSESelectEvent, + IEDSubscriptionEvent, + newGOOSESelectEvent, + styles, + SubscribeStatus, +} from './foundation.js'; /** * An IED within this IED list has 2 properties: @@ -28,12 +42,12 @@ interface IED { * All available FCDA references that are used to link ExtRefs. */ const fcdaReferences = [ - "ldInst", - "lnClass", - "lnInst", - "prefix", - "doName", - "daName", + 'ldInst', + 'lnClass', + 'lnInst', + 'prefix', + 'doName', + 'daName', ]; /** @@ -57,31 +71,37 @@ interface State { availableIeds: IED[]; } -const localState : State = { +const localState: State = { currentGseControl: undefined, currentDataset: undefined, currentGooseIEDName: undefined, subscribedIeds: [], - availableIeds: [] -} + availableIeds: [], +}; /** An sub element for subscribing and unsubscribing IEDs to GOOSE messages. */ @customElement('subscriber-ied-list') export class SubscriberIEDList extends LitElement { - @property() + @property({ attribute: false }) doc!: XMLDocument; - + @query('div') subscriberWrapper!: Element; constructor() { super(); this.onGOOSEDataSetEvent = this.onGOOSEDataSetEvent.bind(this); this.onIEDSubscriptionEvent = this.onIEDSubscriptionEvent.bind(this); - + const openScdElement = document.querySelector('open-scd'); if (openScdElement) { - openScdElement.addEventListener('goose-dataset', this.onGOOSEDataSetEvent); - openScdElement.addEventListener('ied-subscription', this.onIEDSubscriptionEvent); + openScdElement.addEventListener( + 'goose-dataset', + this.onGOOSEDataSetEvent + ); + openScdElement.addEventListener( + 'ied-subscription', + this.onIEDSubscriptionEvent + ); } } @@ -93,58 +113,68 @@ export class SubscriberIEDList extends LitElement { private async onGOOSEDataSetEvent(event: GOOSESelectEvent) { localState.currentGseControl = event.detail.gseControl; localState.currentDataset = event.detail.dataset; - localState.currentGooseIEDName = localState.currentGseControl.closest('IED')?.getAttribute('name'); + localState.currentGooseIEDName = localState.currentGseControl + .closest('IED') + ?.getAttribute('name'); localState.subscribedIeds = []; localState.availableIeds = []; Array.from(this.doc.querySelectorAll(':root > IED')) - .filter(ied => ied.getAttribute('name') != localState.currentGooseIEDName) - .forEach(ied => { - const inputElements = ied.querySelectorAll(`LN0 > Inputs, LN > Inputs`); - - let numberOfLinkedExtRefs = 0; - - /** - * If no Inputs element is found, we can safely say it's not subscribed. - */ - if (!inputElements) { - localState.availableIeds.push({element: ied}); - return; - } + .filter(ied => ied.getAttribute('name') != localState.currentGooseIEDName) + .forEach(ied => { + const inputElements = ied.querySelectorAll(`LN0 > Inputs, LN > Inputs`); + + let numberOfLinkedExtRefs = 0; + + /** + * If no Inputs element is found, we can safely say it's not subscribed. + */ + if (!inputElements) { + localState.availableIeds.push({ element: ied }); + return; + } - /** - * Count all the linked ExtRefs. - */ - localState.currentDataset!.querySelectorAll('FCDA').forEach(fcda => { - inputElements.forEach(inputs => { - if(inputs.querySelector(`ExtRef[iedName=${localState.currentGooseIEDName}]` + - `${fcdaReferences.map(fcdaRef => - fcda.getAttribute(fcdaRef) - ? `[${fcdaRef}="${fcda.getAttribute(fcdaRef)}"]` - : '').join('') - }`)) { + /** + * Count all the linked ExtRefs. + */ + localState.currentDataset!.querySelectorAll('FCDA').forEach(fcda => { + inputElements.forEach(inputs => { + if ( + inputs.querySelector( + `ExtRef[iedName=${localState.currentGooseIEDName}]` + + `${fcdaReferences + .map(fcdaRef => + fcda.getAttribute(fcdaRef) + ? `[${fcdaRef}="${fcda.getAttribute(fcdaRef)}"]` + : '' + ) + .join('')}` + ) + ) { numberOfLinkedExtRefs++; } - }) - }) - - /** - * Make a distinction between not subscribed at all, - * partially subscribed and fully subscribed. - */ - if (numberOfLinkedExtRefs == 0) { - localState.availableIeds.push({element: ied}); - return; - } + }); + }); + + /** + * Make a distinction between not subscribed at all, + * partially subscribed and fully subscribed. + */ + if (numberOfLinkedExtRefs == 0) { + localState.availableIeds.push({ element: ied }); + return; + } - if (numberOfLinkedExtRefs == localState.currentDataset!.querySelectorAll('FCDA').length) { - localState.subscribedIeds.push({element: ied}); - } else { - localState.availableIeds.push({element: ied, partial: true}); - } - - }) + if ( + numberOfLinkedExtRefs == + localState.currentDataset!.querySelectorAll('FCDA').length + ) { + localState.subscribedIeds.push({ element: ied }); + } else { + localState.availableIeds.push({ element: ied, partial: true }); + } + }); this.requestUpdate(); } @@ -156,15 +186,15 @@ export class SubscriberIEDList extends LitElement { private async onIEDSubscriptionEvent(event: IEDSubscriptionEvent) { switch (event.detail.subscribeStatus) { case SubscribeStatus.Full: { - this.unsubscribe(event.detail.element) + this.unsubscribe(event.detail.element); break; } case SubscribeStatus.Partial: { - this.subscribe(event.detail.element) + this.subscribe(event.detail.element); break; } case SubscribeStatus.None: { - this.subscribe(event.detail.element) + this.subscribe(event.detail.element); break; } } @@ -175,46 +205,54 @@ export class SubscriberIEDList extends LitElement { * @param ied - Given IED to subscribe. */ private async subscribe(ied: Element): Promise { - const clone: Element = ied.cloneNode(true); + if (!ied.querySelector('LN0')) return; - let inputsElement = clone.querySelector('LN0 > Inputs'); - if (!inputsElement) { + let inputsElement = ied.querySelector('LN0 > Inputs'); + if (!inputsElement) inputsElement = createElement(ied.ownerDocument, 'Inputs', {}); - } - /** Updating the ExtRefs according the dataset */ + const actions: Create[] = []; localState.currentDataset!.querySelectorAll('FCDA').forEach(fcda => { - if(!inputsElement!.querySelector(`ExtRef[iedName=${localState.currentGooseIEDName}]` + - `${fcdaReferences.map(fcdaRef => - fcda.getAttribute(fcdaRef) - ? `[${fcdaRef}="${fcda.getAttribute(fcdaRef)}"]` - : '').join('') - }`)) { - const extRef = createElement( - ied.ownerDocument, - 'ExtRef', - { - iedName: localState.currentGooseIEDName!, - serviceType: 'GOOSE', - ldInst: fcda.getAttribute('ldInst') ?? '', - lnClass: fcda.getAttribute('lnClass') ?? '', - lnInst: fcda.getAttribute('lnInst') ?? '', - prefix: fcda.getAttribute('prefix') ?? '', - doName: fcda.getAttribute('doName') ?? '', - daName: fcda.getAttribute('daName') ?? '' - }); - - inputsElement?.appendChild(extRef); - } + if ( + !inputsElement!.querySelector( + `ExtRef[iedName=${localState.currentGooseIEDName}]` + + `${fcdaReferences + .map(fcdaRef => + fcda.getAttribute(fcdaRef) + ? `[${fcdaRef}="${fcda.getAttribute(fcdaRef)}"]` + : '' + ) + .join('')}` + ) + ) { + const extRef = createElement(ied.ownerDocument, 'ExtRef', { + iedName: localState.currentGooseIEDName!, + serviceType: 'GOOSE', + ldInst: fcda.getAttribute('ldInst') ?? '', + lnClass: fcda.getAttribute('lnClass') ?? '', + lnInst: fcda.getAttribute('lnInst') ?? '', + prefix: fcda.getAttribute('prefix') ?? '', + doName: fcda.getAttribute('doName') ?? '', + daName: fcda.getAttribute('daName') ?? '', + }); + + if (inputsElement?.parentElement) + actions.push({ new: { parent: inputsElement!, element: extRef } }); + else inputsElement?.appendChild(extRef); + } }); /** If the IED doesn't have a Inputs element, just append it to the first LN0 element. */ - if (!inputsElement.parentElement) { - clone.querySelector('LN0')?.append(inputsElement); + const title = 'Connect'; + if (inputsElement.parentElement) + this.dispatchEvent(newActionEvent({ title, actions })); + else { + const inputAction: Create = { + new: { parent: ied.querySelector('LN0')!, element: inputsElement }, + }; + this.dispatchEvent(newActionEvent({ title, actions: [inputAction] })); } - this.replaceElement(ied, clone); - this.dispatchEvent( newGOOSESelectEvent( localState.currentGseControl!, @@ -228,117 +266,135 @@ export class SubscriberIEDList extends LitElement { * @param ied - Given IED to unsubscribe. */ private unsubscribe(ied: Element): void { - const clone: Element = ied.cloneNode(true); - - clone.querySelectorAll('LN0 > Inputs, LN > Inputs').forEach(inputs => { + const actions: Delete[] = []; + ied.querySelectorAll('LN0 > Inputs, LN > Inputs').forEach(inputs => { localState.currentDataset!.querySelectorAll('FCDA').forEach(fcda => { - const extRef = inputs.querySelector(`ExtRef[iedName=${localState.currentGooseIEDName}]` + - `${fcdaReferences.map(fcdaRef => - fcda.getAttribute(fcdaRef) - ? `[${fcdaRef}="${fcda.getAttribute(fcdaRef)}"]` - : '').join('') - }`); - - if (extRef) inputs.removeChild(extRef!); + const extRef = inputs.querySelector( + `ExtRef[iedName=${localState.currentGooseIEDName}]` + + `${fcdaReferences + .map(fcdaRef => + fcda.getAttribute(fcdaRef) + ? `[${fcdaRef}="${fcda.getAttribute(fcdaRef)}"]` + : '' + ) + .join('')}` + ); + + if (extRef) actions.push({ old: { parent: inputs, element: extRef } }); }); }); - this.replaceElement(ied, clone); - - this.dispatchEvent( - newGOOSESelectEvent( - localState.currentGseControl!, - localState.currentDataset! - ) - ); - } - - /** - * Replacing an element in the current opened file. - * @param original - The original element. - * @param clone - The element to replace the original with. - */ - private async replaceElement(original: Element, clone: Element) { - const parent = original.parentElement; - this.dispatchEvent( newActionEvent({ - old: { - parent: parent!, - element: original, - reference: original.nextSibling - } + title: 'Disconnect', + actions, }) ); this.dispatchEvent( - newActionEvent({ - new: { - parent: parent!, - element: clone, - reference: clone.nextSibling - } - }) + newGOOSESelectEvent( + localState.currentGseControl!, + localState.currentDataset! + ) ); } protected updated(): void { if (this.subscriberWrapper) { - this.subscriberWrapper.scrollTo(0,0); + this.subscriberWrapper.scrollTo(0, 0); } } render(): TemplateResult { - const partialSubscribedIeds = localState.availableIeds.filter(ied => ied.partial); - const gseControlName = localState.currentGseControl?.getAttribute('name') ?? undefined; + const partialSubscribedIeds = localState.availableIeds.filter( + ied => ied.partial + ); + const gseControlName = + localState.currentGseControl?.getAttribute('name') ?? undefined; return html`
    -

    ${translate('subscription.subscriberIed.title', { - selected: gseControlName ? localState.currentGooseIEDName + ' > ' + gseControlName : 'IED' - })}

    - ${localState.currentGseControl ? - html`
    - - - ${translate('subscription.subscriberIed.subscribed')} - -
  • - ${localState.subscribedIeds.length > 0 ? - localState.subscribedIeds.map(ied => html``) - : html` - ${translate('subscription.none')} - `} -
    - +

    + ${translate('subscription.subscriberIed.title', { + selected: gseControlName + ? localState.currentGooseIEDName + ' > ' + gseControlName + : 'IED', + })} +

    + ${localState.currentGseControl + ? html`
    + + + ${translate('subscription.subscriberIed.subscribed')} + +
  • + ${localState.subscribedIeds.length > 0 + ? localState.subscribedIeds.map( + ied => + html`` + ) + : html` + ${translate('subscription.none')} + `} +
    + + + ${translate( + 'subscription.subscriberIed.partiallySubscribed' + )} + +
  • + ${partialSubscribedIeds.length > 0 + ? partialSubscribedIeds.map( + ied => + html`` + ) + : html` + ${translate('subscription.none')} + `} +
    + + + ${translate( + 'subscription.subscriberIed.availableToSubscribe' + )} + +
  • + ${localState.availableIeds.length > 0 + ? localState.availableIeds.map( + ied => + html`` + ) + : html` + ${translate('subscription.none')} + `} +
    +
    ` + : html` - ${translate('subscription.subscriberIed.partiallySubscribed')} - -
  • - ${partialSubscribedIeds.length > 0 ? - partialSubscribedIeds.map(ied => html``) - : html` - ${translate('subscription.none')} - `} -
    - - - ${translate('subscription.subscriberIed.availableToSubscribe')} - -
  • - ${localState.availableIeds.length > 0 ? - localState.availableIeds.map(ied => html``) - : html` - ${translate('subscription.none')} - `} -
    ` : html` - - ${translate('subscription.subscriberIed.noGooseMessageSelected')} + ${translate( + 'subscription.subscriberIed.noGooseMessageSelected' + )}
    `} -
    - `; +
    + `; } static styles = css` @@ -349,7 +405,7 @@ export class SubscriberIEDList extends LitElement { white-space: unset; text-overflow: unset; } - + .subscriberWrapper { height: 100vh; overflow-y: scroll; diff --git a/src/foundation.ts b/src/foundation.ts index 3f7ee9f1a9..460a8048b2 100644 --- a/src/foundation.ts +++ b/src/foundation.ts @@ -280,49 +280,6 @@ export interface ResetDetail { kind: 'reset'; } -export interface GOOSESelectDetail { - gseControl: Element; - dataset: Element; -} -export type GOOSESelectEvent = CustomEvent; -export function newGOOSESelectEvent( - gseControl: Element, - dataset: Element, - eventInitDict?: CustomEventInit -): GOOSESelectEvent { - return new CustomEvent('goose-dataset', { - bubbles: true, - composed: true, - ...eventInitDict, - detail: { gseControl, dataset, ...eventInitDict?.detail }, - }); -} - -/** - * Enumeration stating the Subscribe status of a IED to a GOOSE. - */ -export enum SubscribeStatus { - Full, - Partial, - None -} - -export interface IEDSubscriptionDetail { - element: Element; - subscribeStatus: SubscribeStatus; -} -export type IEDSubscriptionEvent = CustomEvent; -export function newIEDSubscriptionEvent( - element: Element, - subscribeStatus: SubscribeStatus -): IEDSubscriptionEvent { - return new CustomEvent('ied-subscription', { - bubbles: true, - composed: true, - detail: { element, subscribeStatus }, - }); -} - export type LogDetail = InfoDetail | CommitDetail | ResetDetail; export type LogEvent = CustomEvent; export function newLogEvent( @@ -2680,8 +2637,6 @@ declare global { ['open-doc']: OpenDocEvent; ['wizard']: WizardEvent; ['validate']: ValidateEvent; - ['goose-dataset']: GOOSESelectEvent; - ['ied-subscription']: IEDSubscriptionEvent; ['log']: LogEvent; ['issue']: IssueEvent; } diff --git a/test/unit/editors/subscription/elements/ied-element.test.ts b/test/unit/editors/subscription/elements/ied-element.test.ts index 49beb2d162..06a5ba6a70 100644 --- a/test/unit/editors/subscription/elements/ied-element.test.ts +++ b/test/unit/editors/subscription/elements/ied-element.test.ts @@ -3,7 +3,7 @@ import { spy } from 'sinon'; import '../../../../../src/editors/subscription/elements/ied-element.js' import { IEDElement } from '../../../../../src/editors/subscription/elements/ied-element.js'; -import { SubscribeStatus } from '../../../../../src/foundation.js'; +import { SubscribeStatus } from '../../../../../src/editors/subscription/foundation.js'; describe('ied-element', () => { let element: IEDElement; From 807e4d11ea0d00c59ad376409104ed7d868cfb89 Mon Sep 17 00:00:00 2001 From: Rob Tjalma Date: Thu, 17 Mar 2022 00:20:53 +0100 Subject: [PATCH 26/26] fix(plugins/Subscription): Empty 'Inputs' element after unsubscribing GOOSE should be deleted --- .../subscription/subscriber-ied-list.ts | 114 ++- .../editors/subscription/Subscription.test.ts | 78 ++ .../__snapshots__/Subscription.test.snap.js | 388 +++++++++ test/testfiles/valid2007B4ForSubscription.scd | 787 ++++++++++++++++++ .../subscriber-ied-list.test.snap.js | 4 +- .../subscription/subscriber-ied-list.test.ts | 2 +- 6 files changed, 1336 insertions(+), 37 deletions(-) create mode 100644 test/integration/editors/subscription/Subscription.test.ts create mode 100644 test/integration/editors/subscription/__snapshots__/Subscription.test.snap.js create mode 100644 test/testfiles/valid2007B4ForSubscription.scd diff --git a/src/editors/subscription/subscriber-ied-list.ts b/src/editors/subscription/subscriber-ied-list.ts index 31fb6f44f1..bfd2bd8874 100644 --- a/src/editors/subscription/subscriber-ied-list.ts +++ b/src/editors/subscription/subscriber-ied-list.ts @@ -18,7 +18,9 @@ import { Create, createElement, Delete, + identity, newActionEvent, + selector, } from '../../foundation.js'; import { GOOSESelectEvent, @@ -50,6 +52,21 @@ const fcdaReferences = [ 'daName', ]; +/** + * Get all the FCDA attributes containing values from a specific element. + * @param elementContainingFcdaReferences - The element to use + * @returns FCDA references + */ +function getFcdaReferences(elementContainingFcdaReferences: Element): string { + return fcdaReferences + .map(fcdaRef => + elementContainingFcdaReferences.getAttribute(fcdaRef) + ? `[${fcdaRef}="${elementContainingFcdaReferences.getAttribute(fcdaRef)}"]` + : '' + ) + .join(''); +} + /** * Internal persistent state, so it's not lost when * subscribing / unsubscribing. @@ -92,13 +109,13 @@ export class SubscriberIEDList extends LitElement { this.onGOOSEDataSetEvent = this.onGOOSEDataSetEvent.bind(this); this.onIEDSubscriptionEvent = this.onIEDSubscriptionEvent.bind(this); - const openScdElement = document.querySelector('open-scd'); - if (openScdElement) { - openScdElement.addEventListener( + const parentDiv = this.closest('div[id="containerTemplates"]'); + if (parentDiv) { + parentDiv.addEventListener( 'goose-dataset', this.onGOOSEDataSetEvent ); - openScdElement.addEventListener( + parentDiv.addEventListener( 'ied-subscription', this.onIEDSubscriptionEvent ); @@ -143,13 +160,7 @@ export class SubscriberIEDList extends LitElement { if ( inputs.querySelector( `ExtRef[iedName=${localState.currentGooseIEDName}]` + - `${fcdaReferences - .map(fcdaRef => - fcda.getAttribute(fcdaRef) - ? `[${fcdaRef}="${fcda.getAttribute(fcdaRef)}"]` - : '' - ) - .join('')}` + `${getFcdaReferences(fcda)}` ) ) { numberOfLinkedExtRefs++; @@ -167,7 +178,7 @@ export class SubscriberIEDList extends LitElement { } if ( - numberOfLinkedExtRefs == + numberOfLinkedExtRefs >= localState.currentDataset!.querySelectorAll('FCDA').length ) { localState.subscribedIeds.push({ element: ied }); @@ -216,13 +227,7 @@ export class SubscriberIEDList extends LitElement { if ( !inputsElement!.querySelector( `ExtRef[iedName=${localState.currentGooseIEDName}]` + - `${fcdaReferences - .map(fcdaRef => - fcda.getAttribute(fcdaRef) - ? `[${fcdaRef}="${fcda.getAttribute(fcdaRef)}"]` - : '' - ) - .join('')}` + `${getFcdaReferences(fcda)}` ) ) { const extRef = createElement(ied.ownerDocument, 'ExtRef', { @@ -244,9 +249,9 @@ export class SubscriberIEDList extends LitElement { /** If the IED doesn't have a Inputs element, just append it to the first LN0 element. */ const title = 'Connect'; - if (inputsElement.parentElement) + if (inputsElement.parentElement) { this.dispatchEvent(newActionEvent({ title, actions })); - else { + } else { const inputAction: Create = { new: { parent: ied.querySelector('LN0')!, element: inputsElement }, }; @@ -271,23 +276,19 @@ export class SubscriberIEDList extends LitElement { localState.currentDataset!.querySelectorAll('FCDA').forEach(fcda => { const extRef = inputs.querySelector( `ExtRef[iedName=${localState.currentGooseIEDName}]` + - `${fcdaReferences - .map(fcdaRef => - fcda.getAttribute(fcdaRef) - ? `[${fcdaRef}="${fcda.getAttribute(fcdaRef)}"]` - : '' - ) - .join('')}` + `${getFcdaReferences(fcda)}` ); if (extRef) actions.push({ old: { parent: inputs, element: extRef } }); }); + + }); this.dispatchEvent( newActionEvent({ title: 'Disconnect', - actions, + actions: this.extendDeleteActions(actions), }) ); @@ -299,6 +300,48 @@ export class SubscriberIEDList extends LitElement { ); } + /** + * Creating Delete actions in case Inputs elements are empty. + * @param extRefDeleteActions - All Delete actions for ExtRefs. + * @returns Possible delete actions for empty Inputs elements. + */ + private extendDeleteActions(extRefDeleteActions: Delete[]): Delete[] { + if (!extRefDeleteActions.length) return []; + + // Initialize with the already existing ExtRef Delete actions. + const extendedDeleteActions: Delete[] = extRefDeleteActions; + const inputsMap: Record = {}; + + for (const extRefDeleteAction of extRefDeleteActions) { + const extRef = extRefDeleteAction.old.element; + const inputsElement = extRefDeleteAction.old.parent; + + const id = identity(inputsElement); + if (!inputsMap[id]) inputsMap[id] = (inputsElement.cloneNode(true)); + + const linkedExtRef = inputsMap[id].querySelector(`ExtRef[iedName=${extRef.getAttribute('iedName')}]` + + `${getFcdaReferences(extRef)}`); + + if (linkedExtRef) inputsMap[id].removeChild(linkedExtRef); + } + + // create delete action for each empty inputs + Object.entries(inputsMap).forEach(([key, value]) => { + if (value.children.length ! == 0) { + const doc = extRefDeleteActions[0].old.parent.ownerDocument!; + const inputs = doc.querySelector(selector('Inputs', key)); + + if (inputs && inputs.parentElement) { + extendedDeleteActions.push({ + old: { parent: inputs.parentElement, element: inputs }, + }); + } + } + }); + + return extendedDeleteActions; + } + protected updated(): void { if (this.subscriberWrapper) { this.subscriberWrapper.scrollTo(0, 0); @@ -309,6 +352,9 @@ export class SubscriberIEDList extends LitElement { const partialSubscribedIeds = localState.availableIeds.filter( ied => ied.partial ); + const availableIeds = localState.availableIeds.filter( + ied => !ied.partial + ); const gseControlName = localState.currentGseControl?.getAttribute('name') ?? undefined; @@ -323,7 +369,7 @@ export class SubscriberIEDList extends LitElement { ${localState.currentGseControl ? html`
    - + ${translate('subscription.subscriberIed.subscribed')}${translate('subscription.none')} `} - + ${translate( @@ -363,7 +409,7 @@ export class SubscriberIEDList extends LitElement { ${translate('subscription.none')} `} - + ${translate( @@ -372,8 +418,8 @@ export class SubscriberIEDList extends LitElement { >
  • - ${localState.availableIeds.length > 0 - ? localState.availableIeds.map( + ${availableIeds.length > 0 + ? availableIeds.map( ied => html` { + customElements.define('subscription-plugin', Wizarding(Editing(Subscription))); + let element: Subscription; + let doc: XMLDocument; + + beforeEach(async () => { + doc = await fetch('/test/testfiles/valid2007B4ForSubscription.scd') + .then(response => response.text()) + .then(str => new DOMParser().parseFromString(str, 'application/xml')); + + element = await fixture(html``); + }); + + describe('initially', () => { + it('the GOOSE list looks like the latest snapshot', async () => { + await expect(element.shadowRoot?.querySelector('publisher-goose-list')).shadowDom.to.equalSnapshot(); + }); + + it('the IED list looks like the latest snapshot', async () => { + await expect(element.shadowRoot?.querySelector('subscriber-ied-list')).shadowDom.to.equalSnapshot(); + }); + }); + + describe('when selecting a GOOSE message', () => { + beforeEach(async () => { + const gseMsg = element.shadowRoot?.querySelector('publisher-goose-list') + ?.shadowRoot?.querySelectorAll('goose-message')[2].shadowRoot?.querySelector('mwc-list-item'); + + ((gseMsg)).click(); + }); + + it('the list on the right will initially show the subscribed / partially subscribed / not subscribed IEDs', async () => { + await element.updateComplete; + expect(element.shadowRoot?.querySelector('subscriber-ied-list')).shadowDom.to.equalSnapshot(); + }); + + describe('and you subscribe a non-subscribed IED', () => { + it('it looks like the latest snapshot', async () => { + const ied = element.shadowRoot?.querySelector('subscriber-ied-list') + ?.shadowRoot?.querySelectorAll('ied-element')[2].shadowRoot?.querySelector('mwc-list-item'); + + ((ied)).click(); + await element.updateComplete; + expect(element.shadowRoot?.querySelector('subscriber-ied-list')).shadowDom.to.equalSnapshot(); + }); + }); + + describe('and you unsubscribe a subscribed IED', () => { + it('it looks like the latest snapshot', async () => { + const ied = element.shadowRoot?.querySelector('subscriber-ied-list') + ?.shadowRoot?.querySelectorAll('ied-element')[0].shadowRoot?.querySelector('mwc-list-item'); + + ((ied)).click(); + await element.updateComplete; + expect(element.shadowRoot?.querySelector('subscriber-ied-list')).shadowDom.to.equalSnapshot(); + }); + }); + + describe('and you subscribe a partially subscribed IED', () => { + it('it looks like the latest snapshot', async () => { + const ied = element.shadowRoot?.querySelector('subscriber-ied-list') + ?.shadowRoot?.querySelectorAll('ied-element')[1].shadowRoot?.querySelector('mwc-list-item'); + + ((ied)).click(); + await element.updateComplete; + expect(element.shadowRoot?.querySelector('subscriber-ied-list')).shadowDom.to.equalSnapshot(); + }); + }); + }); +}); diff --git a/test/integration/editors/subscription/__snapshots__/Subscription.test.snap.js b/test/integration/editors/subscription/__snapshots__/Subscription.test.snap.js new file mode 100644 index 0000000000..9fdce293ca --- /dev/null +++ b/test/integration/editors/subscription/__snapshots__/Subscription.test.snap.js @@ -0,0 +1,388 @@ +/* @web/test-runner snapshot v1 */ +export const snapshots = {}; + +snapshots["Subscription Plugin initially the GOOSE list looks like the latest snapshot"] = +`
    +

    + [subscription.publisherGoose.title] +

    + + + + IED1 + + + developer_board + + +
  • +
  • + + + + + + + IED2 + + + developer_board + + +
  • +
  • + + + + + IED4 + + + developer_board + + +
  • +
  • + + + + +
    +
    +`; +/* end snapshot Subscription Plugin initially the GOOSE list looks like the latest snapshot */ + +snapshots["Subscription Plugin initially the IED list looks like the latest snapshot"] = +`
    +

    + [subscription.subscriberIed.title] +

    + + + + [subscription.subscriberIed.noGooseMessageSelected] + + + +
    +`; +/* end snapshot Subscription Plugin initially the IED list looks like the latest snapshot */ + +snapshots["Subscription Plugin when selecting a GOOSE message the list on the right will initially show the subscribed / partially subscribed / not subscribed IEDs"] = +`
    +

    + [subscription.subscriberIed.title] +

    +
    + + + + [subscription.subscriberIed.subscribed] + + +
  • +
  • + + +
    + + + + [subscription.subscriberIed.partiallySubscribed] + + +
  • +
  • + + +
    + + + + [subscription.subscriberIed.availableToSubscribe] + + +
  • +
  • + + +
    +
    +
    +`; +/* end snapshot Subscription Plugin when selecting a GOOSE message the list on the right will initially show the subscribed / partially subscribed / not subscribed IEDs */ + +snapshots["Subscription Plugin when selecting a GOOSE message and you subscribe a non-subscribed IED it looks like the latest snapshot"] = +`
    +

    + [subscription.subscriberIed.title] +

    +
    + + + + [subscription.subscriberIed.subscribed] + + +
  • +
  • + + + + +
    + + + + [subscription.subscriberIed.partiallySubscribed] + + +
  • +
  • + + +
    + + + + [subscription.subscriberIed.availableToSubscribe] + + +
  • +
  • + + + [subscription.none] + + +
    +
    +
    +`; +/* end snapshot Subscription Plugin when selecting a GOOSE message and you subscribe a non-subscribed IED it looks like the latest snapshot */ + +snapshots["Subscription Plugin when selecting a GOOSE message and you unsubscribe a subscribed IED it looks like the latest snapshot"] = +`
    +

    + [subscription.subscriberIed.title] +

    +
    + + + + [subscription.subscriberIed.subscribed] + + +
  • +
  • + + + [subscription.none] + + +
    + + + + [subscription.subscriberIed.partiallySubscribed] + + +
  • +
  • + + +
    + + + + [subscription.subscriberIed.availableToSubscribe] + + +
  • +
  • + + + + +
    +
    +
    +`; +/* end snapshot Subscription Plugin when selecting a GOOSE message and you unsubscribe a subscribed IED it looks like the latest snapshot */ + +snapshots["Subscription Plugin when selecting a GOOSE message and you subscribe a partially subscribed IED it looks like the latest snapshot"] = +`
    +

    + [subscription.subscriberIed.title] +

    +
    + + + + [subscription.subscriberIed.subscribed] + + +
  • +
  • + + + + +
    + + + + [subscription.subscriberIed.partiallySubscribed] + + +
  • +
  • + + + [subscription.none] + + +
    + + + + [subscription.subscriberIed.availableToSubscribe] + + +
  • +
  • + + +
    +
    +
    +`; +/* end snapshot Subscription Plugin when selecting a GOOSE message and you subscribe a partially subscribed IED it looks like the latest snapshot */ + diff --git a/test/testfiles/valid2007B4ForSubscription.scd b/test/testfiles/valid2007B4ForSubscription.scd new file mode 100644 index 0000000000..b9ea199caa --- /dev/null +++ b/test/testfiles/valid2007B4ForSubscription.scd @@ -0,0 +1,787 @@ + + +
    + TrainingIEC61850 + + + +
    + + + 110.0 + + + + + + + + + + + + + + + + + + + + + + + + + 20 + + + + + + + 100.0 + +
    +

    192.168.210.111

    +

    255.255.255.0

    +

    192.168.210.1

    +

    1,3,9999,23

    +

    23

    +

    00000001

    +

    0001

    +

    0001

    +
    + +
    +

    01-0C-CD-01-00-10

    +

    005

    +

    4

    +

    0010

    +
    +
    + +

    RJ45

    +
    +
    +
    + + +
    +

    192.168.0.112

    +

    255.255.255.0

    +

    192.168.210.1

    +

    1,3,9999,23

    +

    23

    +

    00000001

    +

    0001

    +

    0001

    +
    +
    + +
    +

    192.168.0.113

    +

    255.255.255.0

    +

    192.168.210.1

    +

    1,3,9999,23

    +

    23

    +

    00000001

    +

    0001

    +

    0001

    +
    + +
    +

    01-0C-CD-04-00-20

    +

    007

    +

    4

    +

    4002

    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IED2 + + + + + + + status-only + + + + + + + sbo-with-enhanced-security + + + + + + + status-only + + + + + + + + 1 + + + + sbo-with-enhanced-security + + + + + + + + + + status-only + + + + + + + + + + + + + + + + status-only + + + + + + + + + direct-with-normal-security + + + + + + + sbo-with-normal-security + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + status-only + + + + + + + + + + + + + + + + + + status-only + + + + + + + + + + + + + + + + + + + + + + status-only + + + + + + + status-only + + + + + + + + + + + status-only + + + + + + + direct-with-enhanced-security + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IED2 + + + + + + + status-only + + + + + + + sbo-with-enhanced-security + + + + + + + status-only + + + + + + + + 1 + + + + sbo-with-enhanced-security + + + + + + + + + + status-only + + + + + + + + + + + + + + status-only + + + + + + + + + direct-with-normal-security + + + + + + + sbo-with-normal-security + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + sbo-with-enhanced-security + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IEC 61850-8-1:2003 + + + + + + + + + + + + + + + + + + + + + + + + + status-only + direct-with-normal-security + sbo-with-normal-security + direct-with-enhanced-security + sbo-with-enhanced-security + + + on + blocked + test + test/blocked + off + + + Ok + Warning + Alarm + + + not-supported + bay-control + station-control + remote-control + automatic-bay + automatic-station + automatic-remote + maintenance + process + + +
    \ No newline at end of file diff --git a/test/unit/editors/subscription/__snapshots__/subscriber-ied-list.test.snap.js b/test/unit/editors/subscription/__snapshots__/subscriber-ied-list.test.snap.js index 6fa76b0703..a3105d42a7 100644 --- a/test/unit/editors/subscription/__snapshots__/subscriber-ied-list.test.snap.js +++ b/test/unit/editors/subscription/__snapshots__/subscriber-ied-list.test.snap.js @@ -1,7 +1,7 @@ /* @web/test-runner snapshot v1 */ export const snapshots = {}; -snapshots["subscriber-ied-list looks like the latest snapshot"] = +snapshots["subscriber-ied-list initially looks like the latest snapshot"] = `

    [subscription.subscriberIed.title] @@ -19,5 +19,5 @@ snapshots["subscriber-ied-list looks like the latest snapshot"] =

    `; -/* end snapshot subscriber-ied-list looks like the latest snapshot */ +/* end snapshot subscriber-ied-list initially looks like the latest snapshot */ diff --git a/test/unit/editors/subscription/subscriber-ied-list.test.ts b/test/unit/editors/subscription/subscriber-ied-list.test.ts index 9bcc8aac86..4b3732dc13 100644 --- a/test/unit/editors/subscription/subscriber-ied-list.test.ts +++ b/test/unit/editors/subscription/subscriber-ied-list.test.ts @@ -17,7 +17,7 @@ describe('subscriber-ied-list', () => { >`); }); - it('looks like the latest snapshot', async () => { + it('initially looks like the latest snapshot', async () => { await expect(element).shadowDom.to.equalSnapshot(); }); });