diff --git a/src/editors/ied/da-container.ts b/src/editors/ied/da-container.ts new file mode 100644 index 000000000..092952205 --- /dev/null +++ b/src/editors/ied/da-container.ts @@ -0,0 +1,116 @@ +import { + css, + customElement, + html, + LitElement, + property, + query, + TemplateResult, +} from 'lit-element'; +import { nothing } from 'lit-html'; +import { translate } from 'lit-translate'; + +import '@material/mwc-icon-button-toggle'; +import { IconButtonToggle } from '@material/mwc-icon-button-toggle'; + +import '../../action-pane.js'; +import { getNameAttribute } from '../../foundation.js'; + +/** [[`IED`]] plugin subeditor for editing `(B)DA` element. */ +@customElement('da-container') +export class DAContainer extends LitElement { + /** + * The DA itself. + */ + @property({ attribute: false }) + element!: Element; + + /** + * The optional DAI of this (B)DA. + */ + @property({ attribute: false }) + instanceElement!: Element; + + @query('#toggleButton') toggleButton: IconButtonToggle | undefined; + + private header(): TemplateResult { + const name = getNameAttribute(this.element); + const bType = this.element!.getAttribute('bType') ?? nothing; + + if (this.instanceElement) { + return html`${name} — ${bType}`; + } else { + return html`${name} — ${bType}`; + } + } + + /** + * Rendering an optional value of this (B)DA container. + * If there is a DAI, it get's priority on top of (B)DA values. + * @returns TemplateResult containing the value of the instance, element or nothing. + */ + private renderValue(): TemplateResult { + if (this.instanceElement) { + return html`${this.getValueElement(this.instanceElement)?.textContent}` + } + + return html`${this.getValueElement(this.element)?.textContent}`; + } + + /** + * Get the 'Val' element of another element. + * @param element - The element to search for an 'Val' element. + * @returns the 'Val' element, or null if not found. + */ + private getValueElement(element: Element): Element | null { + return element.querySelector('Val') ?? null; + } + + /** + * Get the nested (B)DA element(s) if available. + * @returns The nested (B)DA element(s) of this (B)DA container. + */ + private getBDAElements(): Element[] { + const type = this.element!.getAttribute('type') ?? undefined; + const doType = this.element!.closest('SCL')!.querySelector(`:root > DataTypeTemplates > DAType[id="${type}"]`); + if (doType != null) { + return Array.from(doType!.querySelectorAll(':scope > BDA')) + } + return []; + } + + render(): TemplateResult { + const bType = this.element!.getAttribute('bType'); + + return html` + ${bType == 'Struct' ? html` + this.requestUpdate()} + > + ` : nothing} +
${this.renderValue()}
+ ${this.toggleButton?.on && bType == 'Struct' ? this.getBDAElements().map(element => + html` + `) : nothing} +
+ `; + } + + static styles = css` + h6 { + color: var(--mdc-theme-on-surface); + font-family: 'Roboto', sans-serif; + font-weight: 500; + font-size: 0.8em; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + margin: 0px; + padding-left: 0.3em; + } + `; +} \ No newline at end of file diff --git a/src/editors/ied/do-container.ts b/src/editors/ied/do-container.ts index ef49c9e42..de1660819 100644 --- a/src/editors/ied/do-container.ts +++ b/src/editors/ied/do-container.ts @@ -1,15 +1,20 @@ import { - css, customElement, html, LitElement, property, + query, TemplateResult, } from 'lit-element'; import { nothing } from 'lit-html'; +import '@material/mwc-icon-button-toggle'; +import { IconButtonToggle } from '@material/mwc-icon-button-toggle'; + import '../../action-pane.js'; +import './da-container.js'; import { getDescriptionAttribute, getNameAttribute } from '../../foundation.js'; +import { translate } from 'lit-translate'; /** [[`IED`]] plugin subeditor for editing `DO` element. */ @customElement('do-container') @@ -25,6 +30,8 @@ export class DOContainer extends LitElement { */ @property({ attribute: false }) instanceElement!: Element; + + @query('#toggleButton') toggleButton: IconButtonToggle | undefined; private header(): TemplateResult { const name = getNameAttribute(this.element); @@ -51,25 +58,68 @@ export class DOContainer extends LitElement { } /** - * Get the instance element (SDI) of a SDO element (if available) - * @param sdo - The SDO object to search with. + * Get the nested (B)DA element(s). + * @returns The nested (B)DA element(s) of this DO container. + */ + private getDAElements(): Element[] { + const type = this.element.getAttribute('type') ?? undefined; + const doType = this.element.closest('SCL')!.querySelector(`:root > DataTypeTemplates > DOType[id="${type}"]`); + if (doType != null) { + return Array.from(doType!.querySelectorAll(':scope > DA')) + } + return []; + } + + /** + * Get the instance element (SDI) of a (S)DO element (if available) + * @param dO - The (S)DO object to search with. * @returns The optional SDI element. */ - private getInstanceElement(sdo: Element): Element | null { - const sdoName = getNameAttribute(sdo); + private getInstanceDOElement(dO: Element): Element | null { + const sdoName = getNameAttribute(dO); if (this.instanceElement) { return this.instanceElement.querySelector(`:scope > SDI[name="${sdoName}"]`) } return null; } + /** + * Get the instance element (DAI) of a DA element (if available) + * @param da - The (B)DA object to search with. + * @returns The optional DAI element. + */ + private getInstanceDAElement(da: Element): Element | null { + const daName = getNameAttribute(da); + if (this.instanceElement) { + return this.instanceElement.querySelector(`:scope > DAI[name="${daName}"]`) + } + return null; + } + render(): TemplateResult { + const daElements = this.getDAElements(); + const doElements = this.getDOElements(); + return html` - ${this.getDOElements().map(dO => + ${daElements.length > 0 || doElements.length > 0 ? + html` + this.requestUpdate()} + > + ` : nothing} + ${this.toggleButton?.on ? daElements.map(da => + html` + `) : nothing} + ${this.toggleButton?.on ? doElements.map(dO => html` - `)} + .instanceElement=${this.getInstanceDOElement(dO)}> + `) : nothing} `; } diff --git a/src/editors/ied/ln-container.ts b/src/editors/ied/ln-container.ts index 34854e8fd..67e5c1832 100644 --- a/src/editors/ied/ln-container.ts +++ b/src/editors/ied/ln-container.ts @@ -4,6 +4,7 @@ import { html, LitElement, property, + query, TemplateResult, } from 'lit-element'; import { nothing } from 'lit-html'; @@ -11,12 +12,16 @@ import { nothing } from 'lit-html'; import '../../action-pane.js'; import './do-container.js'; import { getInstanceAttribute, getNameAttribute } from '../../foundation.js'; +import { translate } from 'lit-translate'; +import { IconButtonToggle } from '@material/mwc-icon-button-toggle'; /** [[`IED`]] plugin subeditor for editing `LN` and `LN0` element. */ @customElement('ln-container') export class LNContainer extends LitElement { @property({ attribute: false }) element!: Element; + + @query('#toggleButton') toggleButton!: IconButtonToggle | undefined; private header(): TemplateResult { const prefix = this.element.getAttribute('prefix'); @@ -43,7 +48,7 @@ export class LNContainer extends LitElement { /** * Get the instance element (DOI) of a DO element (if available) - * @param dO - The DOI object to use. + * @param dO - The DO object to use. * @returns The optional DOI object. */ private getInstanceElement(dO: Element): Element | null { @@ -52,12 +57,22 @@ export class LNContainer extends LitElement { } render(): TemplateResult { + const doElements = this.getDOElements(); + return html` - ${this.getDOElements().map(dO => html` 0 ? html` + this.requestUpdate()} + > + ` : nothing} + ${this.toggleButton?.on ? this.getDOElements().map(dO => html` - `)} + `) : nothing} `; } diff --git a/src/translations/de.ts b/src/translations/de.ts index 34ab4b1d2..94413bc37 100644 --- a/src/translations/de.ts +++ b/src/translations/de.ts @@ -162,6 +162,7 @@ export const de: Translations = { searchHelper: 'IED auswählen', searchHelperDesc: '({{description}})', missing: 'Kein IED vorhanden', + toggleChildElements: "???" }, voltagelevel: { name: 'Spannungsebene', diff --git a/src/translations/en.ts b/src/translations/en.ts index 0e4cf58e4..857c60754 100644 --- a/src/translations/en.ts +++ b/src/translations/en.ts @@ -158,7 +158,8 @@ export const en = { iededitor: { searchHelper: "Select IED", searchHelperDesc: "({{description}})", - missing: 'No IED' + missing: 'No IED', + toggleChildElements: "Toggle child elements" }, voltagelevel: { name: 'Voltage level', diff --git a/test/unit/editors/ied/__snapshots__/da-container.test.snap.js b/test/unit/editors/ied/__snapshots__/da-container.test.snap.js new file mode 100644 index 000000000..c69d5ce88 --- /dev/null +++ b/test/unit/editors/ied/__snapshots__/da-container.test.snap.js @@ -0,0 +1,61 @@ +/* @web/test-runner snapshot v1 */ +export const snapshots = {}; + +snapshots["da-container looks like the latest snapshot with a DA element"] = +` +
+
+
+`; +/* end snapshot da-container looks like the latest snapshot with a DA element */ + +snapshots["da-container looks like the latest snapshot with a DA element and child elements are toggled."] = +` + + + + +
+
+ + + + + + + + + + + + +
+`; +/* end snapshot da-container looks like the latest snapshot with a DA element and child elements are toggled. */ + +snapshots["da-container looks like the latest snapshot with a DA element containing and a DAI."] = +` +
+ status-only +
+
+`; +/* end snapshot da-container looks like the latest snapshot with a DA element containing and a DAI. */ + diff --git a/test/unit/editors/ied/__snapshots__/do-container.test.snap.js b/test/unit/editors/ied/__snapshots__/do-container.test.snap.js index 54435eba5..ded699c08 100644 --- a/test/unit/editors/ied/__snapshots__/do-container.test.snap.js +++ b/test/unit/editors/ied/__snapshots__/do-container.test.snap.js @@ -1,23 +1,121 @@ /* @web/test-runner snapshot v1 */ export const snapshots = {}; +snapshots["do-container looks like the latest snapshot with a DO element."] = +` + + + + + +`; +/* end snapshot do-container looks like the latest snapshot with a DO element. */ + + + +snapshots["do-container looks like the latest snapshot with a DO element and child elements are toggled."] = +` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; +/* end snapshot do-container looks like the latest snapshot with a DO element and child elements are toggled. */ + snapshots["do-container looks like the latest snapshot with a SDO element."] = ` - - + + + + `; /* end snapshot do-container looks like the latest snapshot with a SDO element. */ -snapshots["do-container looks like the latest snapshot with a DO element."] = +snapshots["do-container looks like the latest snapshot with a SDO element and child elements are toggled."] = ` + + + + + + + + + + `; -/* end snapshot do-container looks like the latest snapshot with a DO element. */ +/* end snapshot do-container looks like the latest snapshot with a SDO element and child elements are toggled. */ diff --git a/test/unit/editors/ied/__snapshots__/ln-container.test.snap.js b/test/unit/editors/ied/__snapshots__/ln-container.test.snap.js index 9aa4546e4..875757d8b 100644 --- a/test/unit/editors/ied/__snapshots__/ln-container.test.snap.js +++ b/test/unit/editors/ied/__snapshots__/ln-container.test.snap.js @@ -3,6 +3,35 @@ export const snapshots = {}; snapshots["ln-container looks like the latest snapshot with a LN0 element."] = ` + + + + + +`; +/* end snapshot ln-container looks like the latest snapshot with a LN0 element. */ + +snapshots["ln-container looks like the latest snapshot with a LN0 element and child elements are toggled."] = +` + + + + @@ -17,10 +46,22 @@ snapshots["ln-container looks like the latest snapshot with a LN0 element."] = `; -/* end snapshot ln-container looks like the latest snapshot with a LN0 element. */ +/* end snapshot ln-container looks like the latest snapshot with a LN0 element and child elements are toggled. */ -snapshots["ln-container looks like the latest snapshot with a LN element."] = +snapshots["ln-container looks like the latest snapshot with a LN element and child elements are toggled."] = ` + + + + @@ -37,5 +78,22 @@ snapshots["ln-container looks like the latest snapshot with a LN element."] = `; +/* end snapshot ln-container looks like the latest snapshot with a LN element and child elements are toggled. */ + +snapshots["ln-container looks like the latest snapshot with a LN element."] = +` + + + + + +`; /* end snapshot ln-container looks like the latest snapshot with a LN element. */ diff --git a/test/unit/editors/ied/da-container.test.ts b/test/unit/editors/ied/da-container.test.ts new file mode 100644 index 000000000..36e13c9cf --- /dev/null +++ b/test/unit/editors/ied/da-container.test.ts @@ -0,0 +1,77 @@ +import { html, fixture, expect } from '@open-wc/testing'; + +import '../../../../src/editors/ied/da-container.js'; +import { DAContainer } from '../../../../src/editors/ied/da-container.js'; + +describe('da-container', () => { + let element: DAContainer; + let validSCL: XMLDocument; + + beforeEach(async () => { + validSCL = await fetch('/test/testfiles/valid2007B4withIEDModifications.scd') + .then(response => response.text()) + .then(str => new DOMParser().parseFromString(str, 'application/xml')); + }); + + it('looks like the latest snapshot with a DA element', async () => { + element = await fixture(html` DOType[id="Dummy.XCBR1.Pos"] > DA[name="ctlModel"]')} + >`); + expect(element).shadowDom.to.equalSnapshot(); + }); + + it('looks like the latest snapshot with a DA element and child elements are toggled.', async () => { + element = await fixture(html` DOType[id="Dummy.LPHD1.Sim"] > DA[name="SBOw"]')} + >`); + + (( + element.shadowRoot!.querySelector('mwc-icon-button-toggle') + )).click(); + await element.requestUpdate(); + await element.updateComplete; + expect(element).shadowDom.to.equalSnapshot(); + + (( + element.shadowRoot!.querySelector('mwc-icon-button-toggle') + )).click(); + await element.requestUpdate(); + await element.updateComplete; + expect(element.shadowRoot!.querySelectorAll('do-container').length).to.eql(0); + }); + + it('looks like the latest snapshot with a DA element containing and a DAI.', async () => { + element = await fixture(html` DOType[id="Dummy.XCBR1.Pos"] > DA[name="ctlModel"]')} + .instanceElement=${validSCL.querySelector( + ':root > IED[name="IED2"] > AccessPoint[name="P1"] > Server > LDevice[inst="CircuitBreaker_CB1"] > LN[lnType="Dummy.XCBR1"] > DOI[name="Pos"]> DAI[name="ctlModel"]')} + >`); + expect(element).shadowDom.to.equalSnapshot(); + }); + + describe('has a getBDAElements function ', () => { + it('which returns BDA elements if available', async () => { + element = await fixture(html` DOType[id="Dummy.LPHD1.Sim"] > DA[name="SBOw"]')} + >`); + + const bdaElements = element['getBDAElements'](); + expect(bdaElements.length).to.eql(6); + expect(bdaElements[4].getAttribute('name')).to.eql('Test'); + }); + + it('which returns no BDA elements if they are not available', async () => { + element = await fixture(html` DOType[id="Dummy.LPHD1.Sim"] > DA[name="SBO"]')} + >`); + + const bdaElements = element['getBDAElements'](); + expect(bdaElements).to.be.empty; + }); + }); +}); diff --git a/test/unit/editors/ied/do-container.test.ts b/test/unit/editors/ied/do-container.test.ts index e477aa514..b4c3b4980 100644 --- a/test/unit/editors/ied/do-container.test.ts +++ b/test/unit/editors/ied/do-container.test.ts @@ -21,6 +21,27 @@ describe('do-container', () => { expect(element).shadowDom.to.equalSnapshot(); }); + it('looks like the latest snapshot with a DO element and child elements are toggled.', async () => { + element = await fixture(html` LNodeType[id="Dummy.LLN0"] > DO[name="Mod"]')} + >`); + + (( + element.shadowRoot!.querySelector('mwc-icon-button-toggle') + )).click(); + await element.requestUpdate(); + await element.updateComplete; + expect(element).shadowDom.to.equalSnapshot(); + + (( + element.shadowRoot!.querySelector('mwc-icon-button-toggle') + )).click(); + await element.requestUpdate(); + await element.updateComplete; + expect(element.shadowRoot!.querySelectorAll('do-container').length).to.eql(0); + }); + it('looks like the latest snapshot with a SDO element.', async () => { element = await fixture(html` { expect(element).shadowDom.to.equalSnapshot(); }); + it('looks like the latest snapshot with a SDO element and child elements are toggled.', async () => { + element = await fixture(html` DOType[id="Dummy.LLN0.ExtendedMod"] > SDO[name="someSdo"]')} + >`); + + (( + element.shadowRoot!.querySelector('mwc-icon-button-toggle') + )).click(); + await element.requestUpdate(); + await element.updateComplete; + expect(element).shadowDom.to.equalSnapshot(); + + (( + element.shadowRoot!.querySelector('mwc-icon-button-toggle') + )).click(); + await element.requestUpdate(); + await element.updateComplete; + expect(element.shadowRoot!.querySelectorAll('do-container').length).to.eql(0); + }); + describe('has a getDOElements function ', () => { it('which return the (S)DO containers underneath a given DO.', async () => { element = await fixture(html` { expect(element).shadowDom.to.equalSnapshot(); }); + it('looks like the latest snapshot with a LN0 element and child elements are toggled.', async () => { + element = await fixture(html` AccessPoint[name="P1"] > Server > LDevice[inst="CircuitBreaker_CB1"] > LN0[lnClass="LLN0"]')} + >`); + + (( + element.shadowRoot!.querySelector('mwc-icon-button-toggle') + )).click(); + await element.requestUpdate(); + await element.updateComplete; + expect(element).shadowDom.to.equalSnapshot(); + + (( + element.shadowRoot!.querySelector('mwc-icon-button-toggle') + )).click(); + await element.requestUpdate(); + await element.updateComplete; + expect(element.shadowRoot!.querySelectorAll('do-container').length).to.eql(0); + }); + it('looks like the latest snapshot with a LN element.', async () => { element = await fixture(html` { expect(element).shadowDom.to.equalSnapshot(); }); + it('looks like the latest snapshot with a LN element and child elements are toggled.', async () => { + element = await fixture(html` AccessPoint[name="P1"] > Server > LDevice[inst="CircuitBreaker_CB1"] > LN[lnClass="XCBR"]')} + >`); + + (( + element.shadowRoot!.querySelector('mwc-icon-button-toggle') + )).click(); + await element.requestUpdate(); + await element.updateComplete; + expect(element).shadowDom.to.equalSnapshot(); + + (( + element.shadowRoot!.querySelector('mwc-icon-button-toggle') + )).click(); + await element.requestUpdate(); + await element.updateComplete; + expect(element.shadowRoot!.querySelectorAll('do-container').length).to.eql(0); + }); + describe('has a getDOElements function ', () => { it('which return the DO containers underneath a given LN.', async () => { element = await fixture(html`