Skip to content

Commit

Permalink
feat(editors/publisher): add read only gse-control-element-editor (op…
Browse files Browse the repository at this point in the history
  • Loading branch information
JakobVogelsang authored Aug 3, 2022
1 parent 21732c9 commit 2aee3cc
Show file tree
Hide file tree
Showing 5 changed files with 471 additions and 7 deletions.
59 changes: 52 additions & 7 deletions src/editors/publisher/gse-control-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import {
customElement,
html,
LitElement,
property as state,
property,
state,
query,
TemplateResult,
} from 'lit-element';
Expand All @@ -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';

Expand All @@ -25,21 +27,39 @@ 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;

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');
Expand All @@ -51,8 +71,12 @@ export class GseControlEditor extends LitElement {
if (this.selectedGseControl !== undefined)
return html`<div class="elementeditorcontainer">
<data-set-element-editor
.element=${this.selectedGseControl}
.element=${this.selectedDataSet!}
></data-set-element-editor>
<gse-control-element-editor
.doc=${this.doc}
.element=${this.selectedGseControl}
></gse-control-element-editor>
</div>`;

return html``;
Expand Down Expand Up @@ -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;
}
}
`;
}
224 changes: 224 additions & 0 deletions src/editors/publisher/gse-control-element-editor.ts
Original file line number Diff line number Diff line change
@@ -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`<div class="content">
<h3>
<div>Communication Settings (GSE)</div>
<div class="headersubtitle">No connection to SubNetwork</div>
</h3>
</div>`;

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<string, string | null> = {};

['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`<div class="content">
<h3>Communication Settings (GSE)</h3>
<mwc-formfield
label="${translate('connectedap.wizard.addschemainsttype')}"
><mwc-checkbox
id="instType"
?checked="${hasInstType}"
></mwc-checkbox></mwc-formfield
>${Object.entries(attributes).map(
([key, value]) =>
html`<wizard-textfield
label="${key}"
?nullable=${typeNullable[key]}
.maybeValue=${value}
pattern="${ifDefined(typePattern[key])}"
required
></wizard-textfield>`
)}<wizard-textfield
label="MinTime"
.maybeValue=${minTime}
nullable
suffix="ms"
type="number"
></wizard-textfield
><wizard-textfield
label="MaxTime"
.maybeValue=${maxTime}
nullable
suffix="ms"
type="number"
></wizard-textfield>
</div>`;
}

private renderGseControlContent(): TemplateResult {
const [name, desc, type, appID, fixedOffs, securityEnabled] = [
'name',
'desc',
'type',
'appID',
'fixedOffs',
'securityEnabled',
].map(attr => this.element?.getAttribute(attr));

return html`<div class="content">
<wizard-textfield
label="name"
.maybeValue=${name}
helper="${translate('scl.name')}"
required
validationMessage="${translate('textfield.required')}"
pattern="${patterns.asciName}"
maxLength="${maxLength.cbName}"
dialogInitialFocus
></wizard-textfield>
<wizard-textfield
label="desc"
.maybeValue=${desc}
nullable
helper="${translate('scl.desc')}"
></wizard-textfield>
<wizard-select
label="type"
.maybeValue=${type}
helper="${translate('scl.type')}"
nullable
required
>${['GOOSE', 'GSSE'].map(
type => html`<mwc-list-item value="${type}">${type}</mwc-list-item>`
)}</wizard-select
>
<wizard-textfield
label="appID"
.maybeValue=${appID}
helper="${translate('scl.id')}"
required
validationMessage="${translate('textfield.nonempty')}"
></wizard-textfield>
<wizard-checkbox
label="fixedOffs"
.maybeValue=${fixedOffs}
nullable
helper="${translate('scl.fixedOffs')}"
></wizard-checkbox>
<wizard-select
label="securityEnabled"
.maybeValue=${securityEnabled}
nullable
required
helper="${translate('scl.securityEnable')}"
>${['None', 'Signature', 'SignatureAndEncryption'].map(
type => html`<mwc-list-item value="${type}">${type}</mwc-list-item>`
)}</wizard-select
>
</div>`;
}

render(): TemplateResult {
return html`<h2 style="display: flex;">
<div style="flex:auto">
<div>GSEControl</div>
<div class="headersubtitle">${identity(this.element)}</div>
</div>
</h2>
<div class="parentcontent">
${this.renderGseControlContent()}${this.renderGseContent()}
</div>`;
}

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);
}
}
`;
}
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,8 @@ snapshots["Editor for GSEControl element with a selected GSEControl looks like t
<div class="elementeditorcontainer">
<data-set-element-editor>
</data-set-element-editor>
<gse-control-element-editor>
</gse-control-element-editor>
</div>
</div>
`;
Expand Down
Loading

0 comments on commit 2aee3cc

Please sign in to comment.