diff --git a/public/js/plugins.js b/public/js/plugins.js
index 13bd52793b..3bf4cfe0ff 100644
--- a/public/js/plugins.js
+++ b/public/js/plugins.js
@@ -55,6 +55,13 @@ export const officialPlugins = [
default: true,
kind: 'editor',
},
+ {
+ name: 'Publisher',
+ src: '/src/editors/Publisher.js',
+ icon: 'publish',
+ default: false,
+ kind: 'editor',
+ },
{
name: 'Open project',
src: '/src/menu/OpenProject.js',
diff --git a/src/editors/Publisher.ts b/src/editors/Publisher.ts
new file mode 100644
index 0000000000..022ae88b4c
--- /dev/null
+++ b/src/editors/Publisher.ts
@@ -0,0 +1,94 @@
+import {
+ css,
+ html,
+ LitElement,
+ property,
+ state,
+ TemplateResult,
+} from 'lit-element';
+import { classMap } from 'lit-html/directives/class-map';
+
+import '@material/mwc-formfield';
+import '@material/mwc-radio';
+
+import './publisher/report-control-editor.js';
+import './publisher/gse-control-editor.js';
+import './publisher/sampled-value-control-editor.js';
+import './publisher/data-set-editor.js';
+
+/** An editor [[`plugin`]] to configure `Report`, `GOOSE`, `SampledValue` control blocks and its `DataSet` */
+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';
+
+ render(): TemplateResult {
+ return html`
+ (this.publisherType = 'Report')}
+ > (this.publisherType = 'GOOSE')}
+ > (this.publisherType = 'SampledValue')}
+ > (this.publisherType = 'DataSet')}
+ >
+
+ `;
+ }
+
+ static styles = css`
+ .hidden {
+ display: none;
+ }
+
+ .publishertypeselector {
+ margin: 4px 8px 16px;
+ background-color: var(--mdc-theme-surface);
+ width: calc(100% - 16px);
+ justify-content: space-around;
+ }
+ `;
+}
diff --git a/src/editors/publisher/data-set-editor.ts b/src/editors/publisher/data-set-editor.ts
new file mode 100644
index 0000000000..599047ec58
--- /dev/null
+++ b/src/editors/publisher/data-set-editor.ts
@@ -0,0 +1,59 @@
+import {
+ css,
+ customElement,
+ html,
+ LitElement,
+ property,
+ TemplateResult,
+} from 'lit-element';
+import { compareNames, identity } from '../../foundation.js';
+
+@customElement('data-set-editor')
+export class DataSetEditor extends LitElement {
+ /** The document being edited as provided to plugins by [[`OpenSCD`]]. */
+ @property({ attribute: false })
+ doc!: XMLDocument;
+
+ renderList(): TemplateResult {
+ return html`${Array.from(this.doc.querySelectorAll('IED'))
+ .sort(compareNames)
+ .flatMap(ied => {
+ const ieditem = html`
+ `;
+
+ const dataSets = Array.from(ied.querySelectorAll('DataSet')).map(
+ reportCb =>
+ html`${reportCb.getAttribute('name')}${identity(reportCb)}
+ `
+ );
+
+ return [ieditem, ...dataSets];
+ })}`;
+ }
+
+ render(): TemplateResult {
+ return html`${this.renderList()}`;
+ }
+
+ static styles = css`
+ filtered-list {
+ margin: 4px 8px 16px;
+ background-color: var(--mdc-theme-surface);
+ }
+
+ .listitem.header {
+ font-weight: 500;
+ }
+ `;
+}
diff --git a/src/editors/publisher/gse-control-editor.ts b/src/editors/publisher/gse-control-editor.ts
new file mode 100644
index 0000000000..3a23798c68
--- /dev/null
+++ b/src/editors/publisher/gse-control-editor.ts
@@ -0,0 +1,71 @@
+import {
+ css,
+ customElement,
+ html,
+ LitElement,
+ property,
+ TemplateResult,
+} from 'lit-element';
+
+import '@material/mwc-list/mwc-list-item';
+import '@material/mwc-icon';
+
+import '../../filtered-list.js';
+import { compareNames, identity } from '../../foundation.js';
+import { gooseIcon } from '../../icons/icons.js';
+
+@customElement('gse-control-editor')
+export class GseControlEditor extends LitElement {
+ /** The document being edited as provided to plugins by [[`OpenSCD`]]. */
+ @property({ attribute: false })
+ doc!: XMLDocument;
+
+ renderList(): TemplateResult {
+ return html`${Array.from(this.doc.querySelectorAll('IED'))
+ .sort(compareNames)
+ .flatMap(ied => {
+ const ieditem = html`
+ `;
+
+ const gseControls = Array.from(
+ ied.querySelectorAll('GSEControl')
+ ).map(
+ reportCb =>
+ html`${reportCb.getAttribute('name')}${identity(reportCb)}
+ ${gooseIcon}
+ `
+ );
+
+ return [ieditem, ...gseControls];
+ })}`;
+ }
+
+ render(): TemplateResult {
+ return html`${this.renderList()}`;
+ }
+
+ static styles = css`
+ filtered-list {
+ margin: 4px 8px 16px;
+ background-color: var(--mdc-theme-surface);
+ }
+
+ .listitem.header {
+ font-weight: 500;
+ }
+ `;
+}
diff --git a/src/editors/publisher/report-control-editor.ts b/src/editors/publisher/report-control-editor.ts
new file mode 100644
index 0000000000..f40f9bc479
--- /dev/null
+++ b/src/editors/publisher/report-control-editor.ts
@@ -0,0 +1,69 @@
+import {
+ css,
+ customElement,
+ html,
+ LitElement,
+ property,
+ TemplateResult,
+} from 'lit-element';
+
+import '@material/mwc-list/mwc-list-item';
+import '@material/mwc-icon';
+
+import '../../filtered-list.js';
+import { compareNames, identity } from '../../foundation.js';
+import { reportIcon } from '../../icons/icons.js';
+
+@customElement('report-control-editor')
+export class ReportControlEditor extends LitElement {
+ /** The document being edited as provided to plugins by [[`OpenSCD`]]. */
+ @property({ attribute: false })
+ doc!: XMLDocument;
+
+ renderList(): TemplateResult {
+ return html`${Array.from(this.doc.querySelectorAll('IED'))
+ .sort(compareNames)
+ .flatMap(ied => {
+ const ieditem = html`
+ `;
+
+ const reports = Array.from(ied.querySelectorAll('ReportControl')).map(
+ reportCb =>
+ html`${reportCb.getAttribute('name')}${identity(reportCb)}
+ ${reportIcon}
+ `
+ );
+
+ return [ieditem, ...reports];
+ })}`;
+ }
+
+ render(): TemplateResult {
+ return html`${this.renderList()}`;
+ }
+
+ static styles = css`
+ filtered-list {
+ margin: 4px 8px 16px;
+ background-color: var(--mdc-theme-surface);
+ }
+
+ .listitem.header {
+ font-weight: 500;
+ }
+ `;
+}
diff --git a/src/editors/publisher/sampled-value-control-editor.ts b/src/editors/publisher/sampled-value-control-editor.ts
new file mode 100644
index 0000000000..e179066f2b
--- /dev/null
+++ b/src/editors/publisher/sampled-value-control-editor.ts
@@ -0,0 +1,71 @@
+import {
+ css,
+ customElement,
+ html,
+ LitElement,
+ property,
+ TemplateResult,
+} from 'lit-element';
+
+import '@material/mwc-list/mwc-list-item';
+import '@material/mwc-icon';
+
+import '../../filtered-list.js';
+import { compareNames, identity } from '../../foundation.js';
+import { smvIcon } from '../../icons/icons.js';
+
+@customElement('sampled-value-control-editor')
+export class SampledValueControlEditor extends LitElement {
+ /** The document being edited as provided to plugins by [[`OpenSCD`]]. */
+ @property({ attribute: false })
+ doc!: XMLDocument;
+
+ renderList(): TemplateResult {
+ return html`${Array.from(this.doc.querySelectorAll('IED'))
+ .sort(compareNames)
+ .flatMap(ied => {
+ const ieditem = html`
+ `;
+
+ const sampledValueControls = Array.from(
+ ied.querySelectorAll('SampledValueControl')
+ ).map(
+ reportCb =>
+ html`${reportCb.getAttribute('name')}${identity(reportCb)}
+ ${smvIcon}
+ `
+ );
+
+ return [ieditem, ...sampledValueControls];
+ })}`;
+ }
+
+ render(): TemplateResult {
+ return html`${this.renderList()}`;
+ }
+
+ static styles = css`
+ filtered-list {
+ margin: 4px 8px 16px;
+ background-color: var(--mdc-theme-surface);
+ }
+
+ .listitem.header {
+ font-weight: 500;
+ }
+ `;
+}
diff --git a/test/integration/__snapshots__/open-scd.test.snap.js b/test/integration/__snapshots__/open-scd.test.snap.js
index dcbc6a55d9..10a651d2a8 100644
--- a/test/integration/__snapshots__/open-scd.test.snap.js
+++ b/test/integration/__snapshots__/open-scd.test.snap.js
@@ -683,6 +683,21 @@ snapshots["open-scd looks like its snapshot"] =
Templates
+
+
+ publish
+
+ Publisher
+
{
+ customElements.define('publisher-plugin', Publisher);
+
+ let element: Publisher;
+
+ beforeEach(async () => {
+ element = await fixture(html``);
+ });
+
+ it('per default looks like the latest snapshot', async () =>
+ await expect(element).shadowDom.to.equalSnapshot());
+
+ it('displays report-control-editor with selected Report publisherType', async () => {
+ element
+ .shadowRoot!.querySelector('mwc-radio[value="Report"]')!
+ .click();
+
+ await element.requestUpdate();
+
+ await expect(element).shadowDom.to.equalSnapshot();
+ });
+
+ it('displays sampled-value-control-editor with selected SampledValue publisherType', async () => {
+ element
+ .shadowRoot!.querySelector(
+ 'mwc-radio[value="SampledValue"]'
+ )!
+ .click();
+
+ await element.requestUpdate();
+
+ await expect(element).shadowDom.to.equalSnapshot();
+ });
+
+ it('displays data-set-editor with selected DataSet publisherType', async () => {
+ element
+ .shadowRoot!.querySelector('mwc-radio[value="DataSet"]')!
+ .click();
+
+ await element.requestUpdate();
+
+ await expect(element).shadowDom.to.equalSnapshot();
+ });
+});
diff --git a/test/unit/editors/__snapshots__/Publisher.test.snap.js b/test/unit/editors/__snapshots__/Publisher.test.snap.js
new file mode 100644
index 0000000000..533d9cd263
--- /dev/null
+++ b/test/unit/editors/__snapshots__/Publisher.test.snap.js
@@ -0,0 +1,135 @@
+/* @web/test-runner snapshot v1 */
+export const snapshots = {};
+
+snapshots["Publisher plugin per default looks like the latest snapshot"] =
+`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+/* end snapshot Publisher plugin per default looks like the latest snapshot */
+
+snapshots["Publisher plugin displays report-control-editor with selected Report publisherType"] =
+`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+/* end snapshot Publisher plugin displays report-control-editor with selected Report publisherType */
+
+snapshots["Publisher plugin displays sampled-value-control-editor with selected SampledValue publisherType"] =
+`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+/* end snapshot Publisher plugin displays sampled-value-control-editor with selected SampledValue publisherType */
+
+snapshots["Publisher plugin displays data-set-editor with selected DataSet publisherType"] =
+`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+/* end snapshot Publisher plugin displays data-set-editor with selected DataSet publisherType */
+
diff --git a/test/unit/editors/publisher/__snapshots__/data-set-editor.test.snap.js b/test/unit/editors/publisher/__snapshots__/data-set-editor.test.snap.js
new file mode 100644
index 0000000000..04483fc37a
--- /dev/null
+++ b/test/unit/editors/publisher/__snapshots__/data-set-editor.test.snap.js
@@ -0,0 +1,136 @@
+/* @web/test-runner snapshot v1 */
+export const snapshots = {};
+
+snapshots["Editor for DataSet element looks like the latest snapshot"] =
+`
+
+
+
+
+
+ GooseDataSet1
+
+
+ IED1>>CircuitBreaker_CB1>GooseDataSet1
+
+
+
+
+
+
+
+ GooseDataSet1
+
+
+ IED2>>CBSW>GooseDataSet1
+
+
+
+
+ dataSet
+
+
+ IED2>>CBSW> XSWI 1>dataSet
+
+
+
+
+ dataSet
+
+
+ IED2>>CBSW> XSWI 2>dataSet
+
+
+
+
+
+
+
+ PhsMeas1
+
+
+ IED3>>MU01>PhsMeas1
+
+
+
+`;
+/* end snapshot Editor for DataSet element looks like the latest snapshot */
+
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
new file mode 100644
index 0000000000..5d7bbc9364
--- /dev/null
+++ b/test/unit/editors/publisher/__snapshots__/gse-control-editor.test.snap.js
@@ -0,0 +1,117 @@
+/* @web/test-runner snapshot v1 */
+export const snapshots = {};
+
+snapshots["Editor for GSEControl element looks like the latest snapshot"] =
+`
+
+
+
+
+
+ GCB
+
+
+ IED1>>CircuitBreaker_CB1>GCB
+
+
+
+
+
+
+ GCB2
+
+
+ IED1>>CircuitBreaker_CB1>GCB2
+
+
+
+
+
+
+
+
+
+ GCB
+
+
+ IED2>>CBSW>GCB
+
+
+
+
+
+
+
+
+`;
+/* end snapshot Editor for GSEControl element looks like the latest snapshot */
+
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
new file mode 100644
index 0000000000..e5d9eef0d6
--- /dev/null
+++ b/test/unit/editors/publisher/__snapshots__/report-control-editor.test.snap.js
@@ -0,0 +1,100 @@
+/* @web/test-runner snapshot v1 */
+export const snapshots = {};
+
+snapshots["Editor for ReportControl element looks like the latest snapshot"] =
+`
+
+
+
+
+
+
+
+
+ ReportCb
+
+
+ IED2>>CBSW> XSWI 1>ReportCb
+
+
+
+
+
+
+ ReportCb
+
+
+ IED2>>CBSW> XSWI 2>ReportCb
+
+
+
+
+
+
+
+
+`;
+/* end snapshot Editor for ReportControl element looks like the latest snapshot */
+
diff --git a/test/unit/editors/publisher/__snapshots__/sampled-value-control-editor.test.snap.js b/test/unit/editors/publisher/__snapshots__/sampled-value-control-editor.test.snap.js
new file mode 100644
index 0000000000..d28b686899
--- /dev/null
+++ b/test/unit/editors/publisher/__snapshots__/sampled-value-control-editor.test.snap.js
@@ -0,0 +1,83 @@
+/* @web/test-runner snapshot v1 */
+export const snapshots = {};
+
+snapshots["Editor for SampledValueControl element looks like the latest snapshot"] =
+`
+
+
+
+
+
+
+
+
+
+
+
+ MSVCB01
+
+
+ IED3>>MU01>MSVCB01
+
+
+
+
+
+`;
+/* end snapshot Editor for SampledValueControl element looks like the latest snapshot */
+
diff --git a/test/unit/editors/publisher/data-set-editor.test.ts b/test/unit/editors/publisher/data-set-editor.test.ts
new file mode 100644
index 0000000000..d7d065ac1e
--- /dev/null
+++ b/test/unit/editors/publisher/data-set-editor.test.ts
@@ -0,0 +1,22 @@
+import { expect, fixture, html } from '@open-wc/testing';
+
+import '../../../../src/editors/publisher/data-set-editor.js';
+import { DataSetEditor } from '../../../../src/editors/publisher/data-set-editor.js';
+
+describe('Editor for DataSet element', () => {
+ let doc: XMLDocument;
+ let element: DataSetEditor;
+
+ beforeEach(async () => {
+ doc = 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());
+});
diff --git a/test/unit/editors/publisher/gse-control-editor.test.ts b/test/unit/editors/publisher/gse-control-editor.test.ts
new file mode 100644
index 0000000000..d7c578f9f7
--- /dev/null
+++ b/test/unit/editors/publisher/gse-control-editor.test.ts
@@ -0,0 +1,22 @@
+import { expect, fixture, html } from '@open-wc/testing';
+
+import '../../../../src/editors/publisher/gse-control-editor.js';
+import { GseControlEditor } from '../../../../src/editors/publisher/gse-control-editor.js';
+
+describe('Editor for GSEControl element', () => {
+ let doc: XMLDocument;
+ let element: GseControlEditor;
+
+ beforeEach(async () => {
+ doc = 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());
+});
diff --git a/test/unit/editors/publisher/report-control-editor.test.ts b/test/unit/editors/publisher/report-control-editor.test.ts
new file mode 100644
index 0000000000..659f140a87
--- /dev/null
+++ b/test/unit/editors/publisher/report-control-editor.test.ts
@@ -0,0 +1,22 @@
+import { expect, fixture, html } from '@open-wc/testing';
+
+import '../../../../src/editors/publisher/report-control-editor.js';
+import { ReportControlEditor } from '../../../../src/editors/publisher/report-control-editor.js';
+
+describe('Editor for ReportControl element', () => {
+ let doc: XMLDocument;
+ let element: ReportControlEditor;
+
+ beforeEach(async () => {
+ doc = 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());
+});
diff --git a/test/unit/editors/publisher/sampled-value-control-editor.test.ts b/test/unit/editors/publisher/sampled-value-control-editor.test.ts
new file mode 100644
index 0000000000..d47f0afafe
--- /dev/null
+++ b/test/unit/editors/publisher/sampled-value-control-editor.test.ts
@@ -0,0 +1,24 @@
+import { expect, fixture, html } from '@open-wc/testing';
+
+import '../../../../src/editors/publisher/sampled-value-control-editor.js';
+import { SampledValueControlEditor } from '../../../../src/editors/publisher/sampled-value-control-editor.js';
+
+describe('Editor for SampledValueControl element', () => {
+ let doc: XMLDocument;
+ let element: SampledValueControlEditor;
+
+ beforeEach(async () => {
+ doc = 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());
+});