From 21732c984c5af0e1a31424f3485cb98ccaa70f6a Mon Sep 17 00:00:00 2001 From: Jakob Vogelsang Date: Tue, 2 Aug 2022 10:47:42 +0200 Subject: [PATCH 1/5] feat(plugin): add read-only report-control-element-editor (#913) * feat(plugin): add read-only report-control-element-editor * refactor(editors/plugin/report-control): review requests * fix(editors/publisher/data-set-element-editor): review comments * test(reportcontrol): on test files update --- src/editors/Publisher.ts | 1 + src/editors/publisher/data-set-editor.ts | 4 + .../publisher/data-set-element-editor.ts | 86 +- src/editors/publisher/foundation.ts | 14 +- src/editors/publisher/gse-control-editor.ts | 4 + .../publisher/report-control-editor.ts | 64 +- .../report-control-element-editor.ts | 240 ++++++ .../publisher/sampled-value-control-editor.ts | 4 + .../reportcontrol-wizarding-editing.test.ts | 2 +- test/testfiles/wizards/reportcontrol.scd | 734 +++++++++--------- .../data-set-element-editor.test.snap.js | 48 +- .../report-control-editor.test.snap.js | 2 + ...report-control-element-editor.test.snap.js | 170 ++++ .../publisher/data-set-element-editor.test.ts | 2 +- .../report-control-element-editor.test.ts | 30 + 15 files changed, 960 insertions(+), 445 deletions(-) create mode 100644 src/editors/publisher/report-control-element-editor.ts create mode 100644 test/unit/editors/publisher/__snapshots__/report-control-element-editor.test.snap.js create mode 100644 test/unit/editors/publisher/report-control-element-editor.test.ts diff --git a/src/editors/Publisher.ts b/src/editors/Publisher.ts index d28388be71..552cea2c47 100644 --- a/src/editors/Publisher.ts +++ b/src/editors/Publisher.ts @@ -21,6 +21,7 @@ export default class PublisherPlugin extends LitElement { /** The document being edited as provided to plugins by [[`OpenSCD`]]. */ @property({ attribute: false }) doc!: XMLDocument; + @state() private publisherType: 'Report' | 'GOOSE' | 'SampledValue' | 'DataSet' = 'GOOSE'; diff --git a/src/editors/publisher/data-set-editor.ts b/src/editors/publisher/data-set-editor.ts index 5edc41d5a1..73149abd2d 100644 --- a/src/editors/publisher/data-set-editor.ts +++ b/src/editors/publisher/data-set-editor.ts @@ -113,5 +113,9 @@ export class DataSetEditor extends LitElement { static styles = css` ${styles} + + data-set-element-editor { + flex: auto; + } `; } diff --git a/src/editors/publisher/data-set-element-editor.ts b/src/editors/publisher/data-set-element-editor.ts index d0e2cd73bb..d1554ce9a8 100644 --- a/src/editors/publisher/data-set-element-editor.ts +++ b/src/editors/publisher/data-set-element-editor.ts @@ -18,6 +18,10 @@ import { identity } from '../../foundation.js'; @customElement('data-set-element-editor') export class DataSetElementEditor extends LitElement { + /** The document being edited as provided to plugins by [[`OpenSCD`]]. */ + @property({ attribute: false }) + doc!: XMLDocument; + /** The element being edited as provided to plugins by [[`OpenSCD`]]. */ @property({ attribute: false }) element!: Element | null; @@ -30,6 +34,46 @@ export class DataSetElementEditor extends LitElement { return this.element ? this.element.getAttribute('desc') : 'UNDEFINED'; } + private renderContent(): TemplateResult { + return html` + + + + ${Array.from(this.element!.querySelectorAll('FCDA')).map(fcda => { + const [ldInst, prefix, lnClass, lnInst, doName, daName, fc] = [ + 'ldInst', + 'prefix', + 'lnClass', + 'lnInst', + 'doName', + 'daName', + 'fc', + ].map(attributeName => fcda.getAttribute(attributeName) ?? ''); + + return html`${doName}${daName + ? '.' + daName + ' ' + '[' + fc + ']' + : ' ' + '[' + fc + ']'}${ldInst + '/' + prefix + lnClass + lnInst} + `; + })}`; + } + render(): TemplateResult { if (this.element) return html`
@@ -37,46 +81,14 @@ export class DataSetElementEditor extends LitElement {
DataSet
${identity(this.element)}
- - - - - ${Array.from(this.element.querySelectorAll('FCDA')).map(fcda => { - const [ldInst, prefix, lnClass, lnInst, doName, daName] = [ - 'ldInst', - 'prefix', - 'lnClass', - 'lnInst', - 'doName', - 'daName', - ].map(attributeName => fcda.getAttribute(attributeName) ?? ''); - - return html`${doName + '.' + daName}${ldInst + '/' + prefix + lnClass + lnInst} - `; - })} + ${this.renderContent()}
`; return html`
-

${translate('publisher.nodataset')}

+

+
DataSet
+
${translate('publisher.nodataset')}
+

`; } diff --git a/src/editors/publisher/foundation.ts b/src/editors/publisher/foundation.ts index 37d69e2116..4cbe1fba09 100644 --- a/src/editors/publisher/foundation.ts +++ b/src/editors/publisher/foundation.ts @@ -21,10 +21,6 @@ export const styles = css` display: flex; } - data-set-element-editor { - width: calc(100% - 6px); - } - .listitem.header { font-weight: 500; } @@ -37,6 +33,12 @@ export const styles = css` display: none; } + @media (max-width: 950px) { + .elementeditorcontainer { + display: block; + } + } + @media (max-width: 599px) { .content { height: 100%; @@ -54,6 +56,10 @@ export const styles = css` 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2); } + .elementeditorcontainer { + display: block; + } + data-set-element-editor { width: calc(100% - 16px); } diff --git a/src/editors/publisher/gse-control-editor.ts b/src/editors/publisher/gse-control-editor.ts index 9718725654..5fb4c27acc 100644 --- a/src/editors/publisher/gse-control-editor.ts +++ b/src/editors/publisher/gse-control-editor.ts @@ -121,5 +121,9 @@ export class GseControlEditor extends LitElement { static styles = css` ${styles} + + data-set-element-editor { + flex: auto; + } `; } diff --git a/src/editors/publisher/report-control-editor.ts b/src/editors/publisher/report-control-editor.ts index d6b4719987..aa6a05905b 100644 --- a/src/editors/publisher/report-control-editor.ts +++ b/src/editors/publisher/report-control-editor.ts @@ -16,6 +16,7 @@ import { Button } from '@material/mwc-button'; import { ListItem } from '@material/mwc-list/mwc-list-item'; import './data-set-element-editor.js'; +import './report-control-element-editor.js'; import '../../filtered-list.js'; import { FilteredList } from '../../filtered-list.js'; @@ -27,10 +28,25 @@ import { styles } from './foundation.js'; export class ReportControlEditor extends LitElement { /** The document being edited as provided to plugins by [[`OpenSCD`]]. */ @property({ attribute: false }) - doc!: XMLDocument; + set doc(newDoc: XMLDocument) { + if (this._doc === newDoc) return; + + this.selectedDataSet = undefined; + this.selectedReportControl = undefined; + + this._doc = newDoc; + + this.requestUpdate(); + } + get doc(): XMLDocument { + return this._doc; + } + private _doc!: XMLDocument; @state() - selectedReportControl?: Element | null; + selectedReportControl?: Element; + @state() + selectedDataSet?: Element | null; @query('.selectionlist') selectionList!: FilteredList; @query('mwc-button') selectReportControlButton!: Button; @@ -38,11 +54,15 @@ export class ReportControlEditor extends LitElement { private selectReportControl(evt: Event): void { const id = ((evt.target as FilteredList).selected as ListItem).value; const reportControl = this.doc.querySelector(selector('ReportControl', id)); + if (!reportControl) return; + + this.selectedReportControl = reportControl; - if (reportControl) { - this.selectedReportControl = reportControl.parentElement?.querySelector( - `DataSet[name="${reportControl.getAttribute('datSet')}"]` - ); + if (this.selectedReportControl) { + this.selectedDataSet = + this.selectedReportControl.parentElement?.querySelector( + `DataSet[name="${this.selectedReportControl.getAttribute('datSet')}"]` + ); (evt.target as FilteredList).classList.add('hidden'); this.selectReportControlButton.classList.remove('hidden'); } @@ -52,8 +72,13 @@ export class ReportControlEditor extends LitElement { if (this.selectedReportControl !== undefined) return html`
+
`; return html``; @@ -120,5 +145,30 @@ export class ReportControlEditor extends LitElement { static styles = css` ${styles} + + .elementeditorcontainer { + flex: 65%; + margin: 4px 8px 4px 4px; + background-color: var(--mdc-theme-surface); + overflow-y: scroll; + display: grid; + grid-gap: 12px; + padding: 8px 12px 16px; + grid-template-columns: repeat(3, 1fr); + } + + data-set-element-editor { + grid-column: 1 / 2; + } + + report-control-element-editor { + grid-column: 2 / 4; + } + + @media (max-width: 950px) { + .elementeditorcontainer { + display: block; + } + } `; } diff --git a/src/editors/publisher/report-control-element-editor.ts b/src/editors/publisher/report-control-element-editor.ts new file mode 100644 index 0000000000..334b2e9bb6 --- /dev/null +++ b/src/editors/publisher/report-control-element-editor.ts @@ -0,0 +1,240 @@ +import { + css, + customElement, + html, + LitElement, + property, + TemplateResult, +} from 'lit-element'; +import { translate } from 'lit-translate'; + +import '../../wizard-textfield.js'; +import '../../wizard-checkbox.js'; + +import { identity } from '../../foundation.js'; +import { maxLength, patterns } from '../../wizards/foundation/limits.js'; + +@customElement('report-control-element-editor') +export class ReportControlElementEditor extends LitElement { + /** The document being edited as provided to plugins by [[`OpenSCD`]]. */ + @property({ attribute: false }) + doc!: XMLDocument; + /** The element being edited as provided to plugins by [[`OpenSCD`]]. */ + @property({ attribute: false }) + element!: Element; + + private renderOptFieldsContent(): TemplateResult { + const [ + seqNum, + timeStamp, + dataSet, + reasonCode, + dataRef, + entryID, + configRef, + bufOvfl, + ] = [ + 'seqNum', + 'timeStamp', + 'dataSet', + 'reasonCode', + 'dataRef', + 'entryID', + 'configRef', + 'bufOvfl', + ].map( + attr => + this.element.querySelector('OptFields')?.getAttribute(attr) ?? null + ); + + return html`

Optional Fields

+ ${Object.entries({ + seqNum, + timeStamp, + dataSet, + reasonCode, + dataRef, + entryID, + configRef, + bufOvfl, + }).map( + ([key, value]) => + html`` + )}`; + } + + private renderTrgOpsContent(): TemplateResult { + const [dchg, qchg, dupd, period, gi] = [ + 'dchg', + 'qchg', + 'dupd', + 'period', + 'gi', + ].map( + attr => this.element.querySelector('TrgOps')?.getAttribute(attr) ?? null + ); + + return html`

Trigger Options

+ ${Object.entries({ dchg, qchg, dupd, period, gi }).map( + ([key, value]) => + html`` + )}`; + } + + private renderChildElements(): TemplateResult { + return html`
+ ${this.renderTrgOpsContent()}${this.renderOptFieldsContent()} +
`; + } + + private renderReportControlContent(): TemplateResult { + const [name, desc, buffered, rptID, indexed, bufTime, intgPd] = [ + 'name', + 'desc', + 'buffered', + 'rptID', + 'indexed', + 'bufTime', + 'intgPd', + ].map(attr => this.element?.getAttribute(attr)); + const max = + this.element.querySelector('RptEnabled')?.getAttribute('max') ?? null; + + return html`
+ +
`; + } + + render(): TemplateResult { + if (this.element) + return html`

+
+
ReportControl
+
${identity(this.element)}
+
+

+
+ ${this.renderReportControlContent()}${this.renderChildElements()} +
`; + + return html`
+

${translate('publisher.nodataset')}

+
`; + } + + static styles = css` + .parentcontent { + display: grid; + grid-gap: 12px; + box-sizing: border-box; + grid-template-columns: repeat(auto-fit, minmax(316px, auto)); + } + + .content { + border-left: thick solid var(--mdc-theme-on-primary); + } + + .content > * { + display: block; + margin: 4px 8px 16px; + } + + h2, + h3 { + color: var(--mdc-theme-on-surface); + font-family: 'Roboto', sans-serif; + font-weight: 300; + margin: 4px 8px 16px; + padding-left: 0.3em; + } + + .headersubtitle { + font-size: 16px; + font-weight: 200; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + *[iconTrailing='search'] { + --mdc-shape-small: 28px; + } + + @media (max-width: 950px) { + .content { + border-left: 0px solid var(--mdc-theme-on-primary); + } + } + `; +} diff --git a/src/editors/publisher/sampled-value-control-editor.ts b/src/editors/publisher/sampled-value-control-editor.ts index 361461890f..7343af4f10 100644 --- a/src/editors/publisher/sampled-value-control-editor.ts +++ b/src/editors/publisher/sampled-value-control-editor.ts @@ -125,5 +125,9 @@ export class SampledValueControlEditor extends LitElement { static styles = css` ${styles} + + data-set-element-editor { + flex: auto; + } `; } diff --git a/test/integration/wizards/reportcontrol-wizarding-editing.test.ts b/test/integration/wizards/reportcontrol-wizarding-editing.test.ts index 8be891645e..a647025621 100644 --- a/test/integration/wizards/reportcontrol-wizarding-editing.test.ts +++ b/test/integration/wizards/reportcontrol-wizarding-editing.test.ts @@ -487,7 +487,7 @@ describe('Wizards for SCL element ReportControl', () => { `IED[name="IED5"] DataSet[name="${rpControl.getAttribute('datSet')}"]` ); expect(dataSet).to.exist; - expect(dataSet!.children).to.have.lengthOf(2); + expect(dataSet!.children).to.have.lengthOf(3); }); }); diff --git a/test/testfiles/wizards/reportcontrol.scd b/test/testfiles/wizards/reportcontrol.scd index e00607e101..2444be87dc 100644 --- a/test/testfiles/wizards/reportcontrol.scd +++ b/test/testfiles/wizards/reportcontrol.scd @@ -37,378 +37,379 @@ - - - - - - - - - - - - - - - - - - - status-only - - - - - + + + + + + + + + + + + + + + + + + + + status-only + + + + + - - - - - - - status-only - - - - - - - - - - - - - - - - - - - status-only - - - - - - - status-only - - - - - - - - - - - status-only - - - - - - - direct-with-enhanced-security - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - status-only - - - - - + + + + + + + status-only + + + + + + + + + + + + + + + + + + + status-only + + + + + + + status-only + + + + + + + + + + + status-only + + + + + + + direct-with-enhanced-security + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + status-only + + + + + - - - - - - - status-only - - - - - - - - - - - status-only - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - status-only - - - - - - - status-only - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - status-only - - - - - - - status-only - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - status-only - - - - - - - status-only - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - status-only - - - - - - - status-only - - - - - - - - - + + + + + + + status-only + + + + + + + + + + + status-only + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + status-only + + + + + + + status-only + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + status-only + + + + + + + status-only + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + status-only + + + + + + + status-only + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + status-only + + + + + + + status-only + + + + + + + + + @@ -662,4 +663,5 @@ process + diff --git a/test/unit/editors/publisher/__snapshots__/data-set-element-editor.test.snap.js b/test/unit/editors/publisher/__snapshots__/data-set-element-editor.test.snap.js index 60358ba8cd..e04cfd9df7 100644 --- a/test/unit/editors/publisher/__snapshots__/data-set-element-editor.test.snap.js +++ b/test/unit/editors/publisher/__snapshots__/data-set-element-editor.test.snap.js @@ -8,7 +8,7 @@ snapshots["Editor for DataSet element with valid DataSet looks like the latest s DataSet
- IED1>>CircuitBreaker_CB1>GooseDataSet1 + IED2>>CBSW>GooseDataSet1
- Pos.stVal + Pos.stVal [ST] - CircuitBreaker_CB1/XCBR1 + CBSW/XSWI2 - Pos.q + Pos.q [ST] - CircuitBreaker_CB1/XCBR1 + CBSW/XSWI2 - Pos.stVal + OpSlc.dsd.sasd.ads.asd [ST] - CircuitBreaker_CB1/CSWI1 + CBSW/XSWI2 - Pos.stVal + Pos [ST] - Disconnectors/DCXSWI1 - - - - - Pos.q - - - Disconnectors/DCXSWI1 + CBSW/XSWI2 @@ -109,7 +94,12 @@ snapshots["Editor for DataSet element with valid DataSet looks like the latest s snapshots["Editor for DataSet element with nulled DataSet looks like the latest snapshot"] = `

- [publisher.nodataset] +
+ DataSet +
+
+ [publisher.nodataset] +

`; diff --git a/test/unit/editors/publisher/__snapshots__/report-control-editor.test.snap.js b/test/unit/editors/publisher/__snapshots__/report-control-editor.test.snap.js index 4c3fdf8473..f7ed44d331 100644 --- a/test/unit/editors/publisher/__snapshots__/report-control-editor.test.snap.js +++ b/test/unit/editors/publisher/__snapshots__/report-control-editor.test.snap.js @@ -223,6 +223,8 @@ snapshots["Editor for ReportControl element with a selected ReportControl looks
+ +
`; diff --git a/test/unit/editors/publisher/__snapshots__/report-control-element-editor.test.snap.js b/test/unit/editors/publisher/__snapshots__/report-control-element-editor.test.snap.js new file mode 100644 index 0000000000..eebb23da31 --- /dev/null +++ b/test/unit/editors/publisher/__snapshots__/report-control-element-editor.test.snap.js @@ -0,0 +1,170 @@ +/* @web/test-runner snapshot v1 */ +export const snapshots = {}; + +snapshots["Editor for ReportControl element and its direct children with valid ReportControl looks like the latest snapshot"] = +`

+
+
+ ReportControl +
+
+ IED2>>CBSW> XSWI 1>ReportCb +
+
+

+
+
+ + + + + + + + + + + + + + + + +
+
+

+ Trigger Options +

+ + + + + + + + + + +

+ Optional Fields +

+ + + + + + + + + + + + + + + + +
+
+`; +/* end snapshot Editor for ReportControl element and its direct children with valid ReportControl looks like the latest snapshot */ + diff --git a/test/unit/editors/publisher/data-set-element-editor.test.ts b/test/unit/editors/publisher/data-set-element-editor.test.ts index b74d255d85..2800e5fad1 100644 --- a/test/unit/editors/publisher/data-set-element-editor.test.ts +++ b/test/unit/editors/publisher/data-set-element-editor.test.ts @@ -8,7 +8,7 @@ describe('Editor for DataSet element', () => { let element: DataSetElementEditor; beforeEach(async () => { - doc = await fetch('/test/testfiles/valid2007B4.scd') + doc = await fetch('/test/testfiles/wizards/reportcontrol.scd') .then(response => response.text()) .then(str => new DOMParser().parseFromString(str, 'application/xml')); }); diff --git a/test/unit/editors/publisher/report-control-element-editor.test.ts b/test/unit/editors/publisher/report-control-element-editor.test.ts new file mode 100644 index 0000000000..0d77acbb25 --- /dev/null +++ b/test/unit/editors/publisher/report-control-element-editor.test.ts @@ -0,0 +1,30 @@ +import { expect, fixture, html } from '@open-wc/testing'; + +import '../../../../src/editors/publisher/report-control-element-editor.js'; +import { ReportControlElementEditor } from '../../../../src/editors/publisher/report-control-element-editor.js'; + +describe('Editor for ReportControl element and its direct children', () => { + let doc: XMLDocument; + let element: ReportControlElementEditor; + + beforeEach(async () => { + doc = await fetch('/test/testfiles/valid2007B4.scd') + .then(response => response.text()) + .then(str => new DOMParser().parseFromString(str, 'application/xml')); + }); + + describe('with valid ReportControl', () => { + beforeEach(async () => { + const reportControl = doc.querySelector('ReportControl')!; + + element = await fixture( + html`` + ); + }); + + it('looks like the latest snapshot', async () => + await expect(element).shadowDom.to.equalSnapshot()); + }); +}); From 2aee3ccb78cbe1c18a2b55769b130e196bc45869 Mon Sep 17 00:00:00 2001 From: Jakob Vogelsang Date: Wed, 3 Aug 2022 12:34:38 +0200 Subject: [PATCH 2/5] feat(editors/publisher): add read only gse-control-element-editor (#917) --- src/editors/publisher/gse-control-editor.ts | 59 ++++- .../publisher/gse-control-element-editor.ts | 224 ++++++++++++++++++ .../gse-control-editor.test.snap.js | 2 + .../gse-control-element-editor.test.snap.js | 163 +++++++++++++ .../gse-control-element-editor.test.ts | 30 +++ 5 files changed, 471 insertions(+), 7 deletions(-) create mode 100644 src/editors/publisher/gse-control-element-editor.ts create mode 100644 test/unit/editors/publisher/__snapshots__/gse-control-element-editor.test.snap.js create mode 100644 test/unit/editors/publisher/gse-control-element-editor.test.ts diff --git a/src/editors/publisher/gse-control-editor.ts b/src/editors/publisher/gse-control-editor.ts index 5fb4c27acc..00db841002 100644 --- a/src/editors/publisher/gse-control-editor.ts +++ b/src/editors/publisher/gse-control-editor.ts @@ -3,7 +3,8 @@ import { customElement, html, LitElement, - property as state, + property, + state, query, TemplateResult, } from 'lit-element'; @@ -15,6 +16,7 @@ import { Button } from '@material/mwc-button'; import { ListItem } from '@material/mwc-list/mwc-list-item'; import './data-set-element-editor.js'; +import './gse-control-element-editor.js'; import '../../filtered-list.js'; import { FilteredList } from '../../filtered-list.js'; @@ -25,11 +27,26 @@ import { styles } from './foundation.js'; @customElement('gse-control-editor') export class GseControlEditor extends LitElement { /** The document being edited as provided to plugins by [[`OpenSCD`]]. */ - @state({ attribute: false }) - doc!: XMLDocument; + @property({ attribute: false }) + set doc(newDoc: XMLDocument) { + if (this._doc === newDoc) return; + this.selectedDataSet = undefined; + this.selectedGseControl = undefined; + + this._doc = newDoc; + + this.requestUpdate(); + } + get doc(): XMLDocument { + return this._doc; + } + private _doc!: XMLDocument; + + @state() + selectedGseControl?: Element; @state() - selectedGseControl?: Element | null; + selectedDataSet?: Element | null; @query('.selectionlist') selectionList!: FilteredList; @query('mwc-button') selectGSEControlButton!: Button; @@ -37,9 +54,12 @@ export class GseControlEditor extends LitElement { private selectGSEControl(evt: Event): void { const id = ((evt.target as FilteredList).selected as ListItem).value; const gseControl = this.doc.querySelector(selector('GSEControl', id)); + if (!gseControl) return; + + this.selectedGseControl = gseControl; if (gseControl) { - this.selectedGseControl = gseControl.parentElement?.querySelector( + this.selectedDataSet = gseControl.parentElement?.querySelector( `DataSet[name="${gseControl.getAttribute('datSet')}"]` ); (evt.target as FilteredList).classList.add('hidden'); @@ -51,8 +71,12 @@ export class GseControlEditor extends LitElement { if (this.selectedGseControl !== undefined) return html`
+
`; return html``; @@ -122,8 +146,29 @@ export class GseControlEditor extends LitElement { static styles = css` ${styles} + .elementeditorcontainer { + flex: 65%; + margin: 4px 8px 4px 4px; + background-color: var(--mdc-theme-surface); + overflow-y: scroll; + display: grid; + grid-gap: 12px; + padding: 8px 12px 16px; + grid-template-columns: repeat(3, 1fr); + } + data-set-element-editor { - flex: auto; + grid-column: 1 / 2; + } + + gse-control-element-editor { + grid-column: 2 / 4; + } + + @media (max-width: 950px) { + .elementeditorcontainer { + display: block; + } } `; } diff --git a/src/editors/publisher/gse-control-element-editor.ts b/src/editors/publisher/gse-control-element-editor.ts new file mode 100644 index 0000000000..a4b9f79f45 --- /dev/null +++ b/src/editors/publisher/gse-control-element-editor.ts @@ -0,0 +1,224 @@ +import { + css, + customElement, + html, + LitElement, + property, + TemplateResult, +} from 'lit-element'; +import { translate } from 'lit-translate'; + +import '@material/mwc-formfield'; +import '@material/mwc-checkbox'; + +import '../../wizard-checkbox.js'; +import '../../wizard-select.js'; +import '../../wizard-textfield.js'; + +import { identity } from '../../foundation.js'; +import { maxLength, patterns } from '../../wizards/foundation/limits.js'; +import { typeNullable, typePattern } from '../../wizards/foundation/p-types.js'; +import { ifDefined } from 'lit-html/directives/if-defined.js'; + +@customElement('gse-control-element-editor') +export class GseControlElementEditor extends LitElement { + /** The document being edited as provided to plugins by [[`OpenSCD`]]. */ + @property({ attribute: false }) + doc!: XMLDocument; + /** The element being edited as provided to plugins by [[`OpenSCD`]]. */ + @property({ attribute: false }) + element!: Element; + @property({ attribute: false }) + get gSE(): Element | null | undefined { + const cbName = this.element.getAttribute('name'); + const iedName = this.element.closest('IED')?.getAttribute('name'); + const apName = this.element.closest('AccessPoint')?.getAttribute('name'); + const ldInst = this.element.closest('LDevice')?.getAttribute('inst'); + + return this.element.ownerDocument.querySelector( + `:root > Communication > SubNetwork > ` + + `ConnectedAP[iedName="${iedName}"][apName="${apName}"] > ` + + `GSE[ldInst="${ldInst}"][cbName="${cbName}"]` + ); + } + + private renderGseContent(): TemplateResult { + const gSE = this.gSE; + if (!gSE) + return html`
+

+
Communication Settings (GSE)
+
No connection to SubNetwork
+

+
`; + + const minTime = gSE.querySelector('MinTime')?.innerHTML.trim() ?? null; + const maxTime = gSE.querySelector('MaxTime')?.innerHTML.trim() ?? null; + + const hasInstType = Array.from(gSE.querySelectorAll('Address > P')).some( + pType => pType.getAttribute('xsi:type') + ); + + const attributes: Record = {}; + + ['MAC-Address', 'APPID', 'VLAN-ID', 'VLAN-PRIORITY'].forEach(key => { + if (!attributes[key]) + attributes[key] = + gSE.querySelector(`Address > P[type="${key}"]`)?.innerHTML.trim() ?? + null; + }); + + return html`
+

Communication Settings (GSE)

+ ${Object.entries(attributes).map( + ([key, value]) => + html`` + )} +
`; + } + + private renderGseControlContent(): TemplateResult { + const [name, desc, type, appID, fixedOffs, securityEnabled] = [ + 'name', + 'desc', + 'type', + 'appID', + 'fixedOffs', + 'securityEnabled', + ].map(attr => this.element?.getAttribute(attr)); + + return html`
+ + + ${['GOOSE', 'GSSE'].map( + type => html`${type}` + )} + + + ${['None', 'Signature', 'SignatureAndEncryption'].map( + type => html`${type}` + )} +
`; + } + + render(): TemplateResult { + return html`

+
+
GSEControl
+
${identity(this.element)}
+
+

+
+ ${this.renderGseControlContent()}${this.renderGseContent()} +
`; + } + + static styles = css` + .parentcontent { + display: grid; + grid-gap: 12px; + box-sizing: border-box; + grid-template-columns: repeat(auto-fit, minmax(316px, auto)); + } + + .content { + border-left: thick solid var(--mdc-theme-on-primary); + } + + .content > * { + display: block; + margin: 4px 8px 16px; + } + + h2, + h3 { + color: var(--mdc-theme-on-surface); + font-family: 'Roboto', sans-serif; + font-weight: 300; + margin: 4px 8px 16px; + padding-left: 0.3em; + } + + .headersubtitle { + font-size: 16px; + font-weight: 200; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + *[iconTrailing='search'] { + --mdc-shape-small: 28px; + } + + @media (max-width: 950px) { + .content { + border-left: 0px solid var(--mdc-theme-on-primary); + } + } + `; +} diff --git a/test/unit/editors/publisher/__snapshots__/gse-control-editor.test.snap.js b/test/unit/editors/publisher/__snapshots__/gse-control-editor.test.snap.js index d11d9805e7..ef3616d498 100644 --- a/test/unit/editors/publisher/__snapshots__/gse-control-editor.test.snap.js +++ b/test/unit/editors/publisher/__snapshots__/gse-control-editor.test.snap.js @@ -257,6 +257,8 @@ snapshots["Editor for GSEControl element with a selected GSEControl looks like t
+ +
`; diff --git a/test/unit/editors/publisher/__snapshots__/gse-control-element-editor.test.snap.js b/test/unit/editors/publisher/__snapshots__/gse-control-element-editor.test.snap.js new file mode 100644 index 0000000000..e33e72b769 --- /dev/null +++ b/test/unit/editors/publisher/__snapshots__/gse-control-element-editor.test.snap.js @@ -0,0 +1,163 @@ +/* @web/test-runner snapshot v1 */ +export const snapshots = {}; + +snapshots["Editor for GSEControl element and its direct children with valid GSEControl looks like the latest snapshot"] = +`

+
+
+ GSEControl +
+
+ IED1>>CircuitBreaker_CB1>GCB +
+
+

+
+
+ + + + + + + GOOSE + + + GSSE + + + + + + + + + None + + + Signature + + + SignatureAndEncryption + + +
+
+

+ Communication Settings (GSE) +

+ + + + + + + + + + + + + + + + +
+
+`; +/* end snapshot Editor for GSEControl element and its direct children with valid GSEControl looks like the latest snapshot */ + diff --git a/test/unit/editors/publisher/gse-control-element-editor.test.ts b/test/unit/editors/publisher/gse-control-element-editor.test.ts new file mode 100644 index 0000000000..f81ddad991 --- /dev/null +++ b/test/unit/editors/publisher/gse-control-element-editor.test.ts @@ -0,0 +1,30 @@ +import { expect, fixture, html } from '@open-wc/testing'; + +import '../../../../src/editors/publisher/gse-control-element-editor.js'; +import { GseControlElementEditor } from '../../../../src/editors/publisher/gse-control-element-editor.js'; + +describe('Editor for GSEControl element and its direct children', () => { + let doc: XMLDocument; + let element: GseControlElementEditor; + + beforeEach(async () => { + doc = await fetch('/test/testfiles/wizards/gsecontrol.scd') + .then(response => response.text()) + .then(str => new DOMParser().parseFromString(str, 'application/xml')); + }); + + describe('with valid GSEControl', () => { + beforeEach(async () => { + const gseControl = doc.querySelector('GSEControl')!; + + element = await fixture( + html`` + ); + }); + + it('looks like the latest snapshot', async () => + await expect(element).shadowDom.to.equalSnapshot()); + }); +}); From 92e7390a9cfb42d25858578b71bf935304cc4691 Mon Sep 17 00:00:00 2001 From: Jakob Vogelsang Date: Fri, 5 Aug 2022 11:54:24 +0200 Subject: [PATCH 3/5] fix(editors/cleanup): Fix filter issue with in cleanup plugin (#910) * refactor(editors/cleanup): make linter happy * fix(editors/cleanup): make type and string filter work together * fix(editors/cleanup/control-clock-list): make sure check all works --- .../cleanup/control-blocks-container.ts | 41 ++++++++++--------- src/editors/cleanup/datasets-container.ts | 12 +++--- .../control-blocks-container.test.snap.js | 5 ++- 3 files changed, 31 insertions(+), 27 deletions(-) diff --git a/src/editors/cleanup/control-blocks-container.ts b/src/editors/cleanup/control-blocks-container.ts index d93797e349..119681aa89 100644 --- a/src/editors/cleanup/control-blocks-container.ts +++ b/src/editors/cleanup/control-blocks-container.ts @@ -10,6 +10,7 @@ import { query, queryAll, } from 'lit-element'; +import { classMap } from 'lit-html/directives/class-map'; import { translate } from 'lit-translate'; import '@material/mwc-button'; @@ -76,7 +77,7 @@ function getCommAddress(controlBlock: Element): Element | null | undefined { @customElement('cleanup-control-blocks') export class CleanupControlBlocks extends LitElement { /** The document being edited as provided to plugins by [[`OpenSCD`]]. */ - @property() + @property({ attribute: false }) doc!: XMLDocument; @property({ type: Boolean }) @@ -85,7 +86,7 @@ export class CleanupControlBlocks extends LitElement { @property({ type: Array }) unreferencedControls: Element[] = []; - @property() + @property({ attribute: false }) selectedControlItems: MWCListIndex | [] = []; @query('.deleteButton') @@ -118,7 +119,9 @@ export class CleanupControlBlocks extends LitElement { */ private toggleHiddenClass(selectorType: string) { this.cleanupList!.querySelectorAll(`.${selectorType}`).forEach(element => { - element.classList.toggle('hidden'); + element.classList.toggle('hiddenontypefilter'); + if (element.hasAttribute('disabled')) element.removeAttribute('disabled'); + else element.setAttribute('disabled', 'true'); }); } @@ -164,7 +167,13 @@ export class CleanupControlBlocks extends LitElement { private renderListItem(controlBlock: Element): TemplateResult { return html`>this.selectedControlItems).size; + return html`>this.selectedControlItems).size === 0 || (Array.isArray(this.selectedControlItems) && !this.selectedControlItems.length)} @@ -399,22 +410,12 @@ export class CleanupControlBlocks extends LitElement { opacity: 1; } - /* items are disabled if the filter is deselected */ - .tGSEControl, - .tSampledValueControl, - .tLogControl, - .tReportControl { + /* Make sure to type filter here + .hidden is set on string filter in filtered-list and must always filter*/ + .cleanupListItem.hiddenontypefilter:not(.hidden) { display: none; } - /* items enabled if filter is selected */ - .tGSEControlFilter[on] ~ .cleanupList > .tGSEControl, - .tSampledValueControlFilter[on] ~ .cleanupList > .tSampledValueControl, - .tLogControlFilter[on] ~ .cleanupList > .tLogControl, - .tReportControlFilter[on] ~ .cleanupList > .tReportControl { - display: flex; - } - /* filter disabled, Material Design guidelines for opacity */ .tGSEControlFilter, .tSampledValueControlFilter, diff --git a/src/editors/cleanup/datasets-container.ts b/src/editors/cleanup/datasets-container.ts index adebceaff9..dd374d6bb1 100644 --- a/src/editors/cleanup/datasets-container.ts +++ b/src/editors/cleanup/datasets-container.ts @@ -38,7 +38,7 @@ import { cleanSCLItems, identitySort } from './foundation.js'; @customElement('cleanup-datasets') export class CleanupDatasets extends LitElement { /** The document being edited as provided to plugins by [[`OpenSCD`]]. */ - @property() + @property({ attribute: false }) doc!: XMLDocument; @property({ type: Boolean }) @@ -47,7 +47,7 @@ export class CleanupDatasets extends LitElement { @property({ type: Array }) unreferencedDataSets: Element[] = []; - @property() + @property({ attribute: false }) selectedDatasetItems: MWCListIndex | [] = []; @query('.deleteButton') @@ -107,13 +107,15 @@ export class CleanupDatasets extends LitElement { * @returns html for the Delete Button of this container. */ private renderDeleteButton(): TemplateResult { + const sizeSelectedItems = (>this.selectedDatasetItems).size; + return html` >this.selectedDatasetItems).size === 0 || (Array.isArray(this.selectedDatasetItems) && !this.selectedDatasetItems.length)} diff --git a/test/unit/editors/cleanup/__snapshots__/control-blocks-container.test.snap.js b/test/unit/editors/cleanup/__snapshots__/control-blocks-container.test.snap.js index 9d09bc7364..296e043f80 100644 --- a/test/unit/editors/cleanup/__snapshots__/control-blocks-container.test.snap.js +++ b/test/unit/editors/cleanup/__snapshots__/control-blocks-container.test.snap.js @@ -241,8 +241,9 @@ snapshots["Cleanup: Control Blocks Container With a test file loaded looks like