Skip to content

Commit

Permalink
feat(wizards/lnode): add edit wizard (#778)
Browse files Browse the repository at this point in the history
* feat(wizards/lnode): add edit wizard

* test(wizards/lnode): add editing integration

* test(wizards/lnode): fix snapshots

* refactor(translation/de): better localization
  • Loading branch information
JakobVogelsang committed May 30, 2022
1 parent 8ee23fc commit 965f10a
Show file tree
Hide file tree
Showing 14 changed files with 505 additions and 10 deletions.
2 changes: 1 addition & 1 deletion src/editors/substation/bay-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export class BayEditor extends LitElement {

/** Opens a [[`WizardDialog`]] for editing `LNode` connections. */
openLNodeWizard(): void {
const wizard = wizards['LNode'].edit(this.element);
const wizard = wizards['LNode'].create(this.element);
if (wizard) this.dispatchEvent(newWizardEvent(wizard));
}

Expand Down
2 changes: 1 addition & 1 deletion src/editors/substation/conducting-equipment-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export class ConductingEquipmentEditor extends LitElement {
}

private openLNodeWizard(): void {
const wizard = wizards['LNode'].edit(this.element);
const wizard = wizards['LNode'].create(this.element);
if (wizard) this.dispatchEvent(newWizardEvent(wizard));
}

Expand Down
14 changes: 13 additions & 1 deletion src/editors/substation/l-node-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from 'lit-element';

import '../../action-icon.js';
import { identity, newActionEvent } from '../../foundation.js';
import { identity, newActionEvent, newWizardEvent } from '../../foundation.js';
import {
automationLogicalNode,
controlLogicalNode,
Expand All @@ -27,6 +27,7 @@ import {
systemLogicalNode,
transformerLogicalNode,
} from '../../icons/lnode.js';
import { wizards } from '../../wizards/wizard-library.js';

export function getLNodeIcon(lNode: Element): TemplateResult {
const lnClassGroup = lNode.getAttribute('lnClass')?.charAt(0) ?? '';
Expand Down Expand Up @@ -75,6 +76,11 @@ export class LNodeEditor extends LitElement {
return this.element.getAttribute('iedName') === 'None' ?? false;
}

private openEditWizard(): void {
const wizard = wizards['LNode'].edit(this.element);
if (wizard) this.dispatchEvent(newWizardEvent(wizard));
}

remove(): void {
if (this.element)
this.dispatchEvent(
Expand All @@ -93,6 +99,12 @@ export class LNodeEditor extends LitElement {
?secondary=${this.missingIedReference}
?highlighted=${this.missingIedReference}
><mwc-icon slot="icon">${getLNodeIcon(this.element)}</mwc-icon
><mwc-fab
slot="action"
mini
icon="edit"
@click="${() => this.openEditWizard()}}"
></mwc-fab
><mwc-fab
slot="action"
mini
Expand Down
2 changes: 1 addition & 1 deletion src/editors/substation/powertransformer-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export class PowerTransformerEditor extends LitElement {
}

private openLNodeWizard(): void {
const wizard = wizards['LNode'].edit(this.element);
const wizard = wizards['LNode'].create(this.element);
if (wizard) this.dispatchEvent(newWizardEvent(wizard));
}

Expand Down
2 changes: 1 addition & 1 deletion src/editors/substation/substation-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export class SubstationEditor extends LitElement {

/** Opens a [[`WizardDialog`]] for editing `LNode` connections. */
openLNodeWizard(): void {
const wizard = wizards['LNode'].edit(this.element);
const wizard = wizards['LNode'].create(this.element);
if (wizard) this.dispatchEvent(newWizardEvent(wizard));
}

Expand Down
2 changes: 1 addition & 1 deletion src/editors/substation/voltage-level-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export class VoltageLevelEditor extends LitElement {

/** Opens a [[`WizardDialog`]] for editing `LNode` connections. */
openLNodeWizard(): void {
const wizard = wizards['LNode'].edit(this.element);
const wizard = wizards['LNode'].create(this.element);
if (wizard) this.dispatchEvent(newWizardEvent(wizard));
}

Expand Down
4 changes: 4 additions & 0 deletions src/translations/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ export const de: Translations = {
sampleRate: 'Abtastrate zu Telegram hinzufügen',
security: 'Potentiel in Zukunft für z.B. digitale Signature',
synchSourceId: 'Identität der Zeitquelle zu Telegram hinzufügen',
iedName: 'Referenziertes IED',
ldInst: 'Referenziertes logisches Gerät',
prefix: 'Präfix des logischen Knotens',
lnInst: 'Instanz des logischen Knotens',
},
settings: {
title: 'Einstellungen',
Expand Down
4 changes: 4 additions & 0 deletions src/translations/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ export const en = {
sampleRate: 'Add sample rate to SMV packet',
security: 'Potential future use. e.g. digital signature',
synchSourceId: 'Add sync source id to SMV packet',
iedName: 'Referenced IED',
ldInst: 'Referenced Logical Device',
prefix: 'Prefix of the Logical Node',
lnInst: 'Instance of the Logical Node',
},
settings: {
title: 'Settings',
Expand Down
130 changes: 130 additions & 0 deletions src/wizards/lnode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import { MultiSelectedEvent } from '@material/mwc-list/mwc-list-foundation';
import '../filtered-list.js';
import {
Create,
cloneElement,
createElement,
EditorAction,
getChildElementsByTagName,
getValue,
identity,
isPublic,
newLogEvent,
Expand All @@ -26,6 +28,7 @@ import {
WizardInputElement,
WizardMenuActor,
} from '../foundation.js';
import { patterns } from './foundation/limits.js';

const maxLnInst = 99;
const lnInstRange = Array(maxLnInst)
Expand Down Expand Up @@ -489,3 +492,130 @@ export function lNodeWizard(parent: Element): Wizard {

return lNodeReferenceWizard(parent);
}

interface ContentOptions {
iedName: string | null;
ldInst: string | null;
prefix: string | null;
lnClass: string | null;
lnInst: string | null;
reservedLnInst: string[];
}

function contentLNodeWizard(options: ContentOptions): TemplateResult[] {
const isIedRef = options.iedName !== 'None';

return [
html`<wizard-textfield
label="iedName"
.maybeValue=${options.iedName}
helper="${translate('scl.iedName')}"
helperPersistent
disabled
></wizard-textfield>`,
html`<wizard-textfield
label="ldInst"
.maybeValue=${options.ldInst}
helper="${translate('scl.ldInst')}"
helperPersistent
nullable
disabled
></wizard-textfield>`,
html`<wizard-textfield
label="prefix"
.maybeValue=${options.prefix}
helper="${translate('scl.prefix')}"
pattern="${patterns.asciName}"
maxLength="11"
helperPersistent
nullable
?disabled=${isIedRef}
></wizard-textfield>`,
html`<wizard-textfield
label="lnClass"
.maybeValue=${options.lnClass}
helper="${translate('scl.lnClass')}"
helperPersistent
disabled
></wizard-textfield>`,
html`<wizard-textfield
label="lnInst"
.maybeValue=${options.lnInst}
helper="${translate('scl.lnInst')}"
helperPersistent
type="number"
min="1"
max="99"
.reservedValues=${options.reservedLnInst}
?disabled=${isIedRef}
></wizard-textfield>`,
];
}

function updateLNodeAction(element: Element): WizardActor {
return (inputs: WizardInputElement[]): EditorAction[] => {
const attributes: Record<string, string | null> = {};
const attributeKeys = ['iedName', 'ldInst', 'prefix', 'lnClass', 'lnInst'];

attributeKeys.forEach(key => {
attributes[key] = getValue(inputs.find(i => i.label === key)!);
});

let lNodeAction: EditorAction | null = null;
if (
attributeKeys.some(key => attributes[key] !== element.getAttribute(key))
) {
const newElement = cloneElement(element, attributes);
lNodeAction = {
old: { element },
new: { element: newElement },
};
return [lNodeAction];
}

return [];
};
}

export function editLNodeWizard(element: Element): Wizard {
const [iedName, ldInst, prefix, lnClass, lnInst] = [
'iedName',
'ldInst',
'prefix',
'lnClass',
'lnInst',
].map(attr => element.getAttribute(attr));

const reservedLnInst = getChildElementsByTagName(
element.parentElement,
'LNode'
)
.filter(
sibling =>
sibling !== element &&
sibling.getAttribute('lnClass') === element.getAttribute('lnClass')
)
.map(sibling => sibling.getAttribute('lnInst')!);

return [
{
title: get('wizard.title.edit', { tagName: 'LNode' }),
element,
primary: {
label: get('save'),
icon: 'save',
action: updateLNodeAction(element),
},
content: [
...contentLNodeWizard({
iedName,
ldInst,
prefix,
lnClass,
lnInst,
reservedLnInst,
}),
],
},
];
}
4 changes: 2 additions & 2 deletions src/wizards/wizard-library.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from './conductingequipment.js';
import { editConnectivityNodeWizard } from './connectivitynode.js';
import { createFCDAsWizard } from './fcda.js';
import { lNodeWizard } from './lnode.js';
import { editLNodeWizard, lNodeWizard } from './lnode.js';
import { editOptFieldsWizard } from './optfields.js';
import { createSubstationWizard, substationEditWizard } from './substation.js';
import { editTerminalWizard } from './terminal.js';
Expand Down Expand Up @@ -313,7 +313,7 @@ export const wizards: Record<
create: emptyWizard,
},
LNode: {
edit: lNodeWizard,
edit: editLNodeWizard,
create: lNodeWizard,
},
LNodeType: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ import { MockWizardEditor } from '../../../mock-wizard-editor.js';

import '../../../../src/editors/substation/l-node-editor.js';
import { LNodeEditor } from '../../../../src/editors/substation/l-node-editor.js';
import { WizardTextField } from '../../../../src/wizard-textfield.js';

describe('l-node-editor wizarding editing integration', () => {
let doc: XMLDocument;
let parent: MockWizardEditor;
let element: LNodeEditor | null;

let primaryAction: HTMLElement;

beforeEach(async () => {
doc = await fetch('/test/testfiles/zeroline/functions.scd')
.then(response => response.text())
Expand Down Expand Up @@ -48,4 +51,66 @@ describe('l-node-editor wizarding editing integration', () => {
.exist;
});
});

describe('has a edit icon button that', () => {
let prefixField: WizardTextField;
let lnInstField: WizardTextField;

beforeEach(async () => {
element!.element = doc.querySelector(
'SubFunction[name="myBaySubFunc"] > LNode[lnClass="XSWI"]'
)!;

(<HTMLElement>(
element?.shadowRoot?.querySelector('mwc-fab[icon="edit"]')
)).click();
await parent.updateComplete;

prefixField = <WizardTextField>(
parent.wizardUI.dialog?.querySelector(
'wizard-textfield[label="prefix"]'
)
);

lnInstField = <WizardTextField>(
parent.wizardUI.dialog?.querySelector(
'wizard-textfield[label="lnInst"]'
)
);

primaryAction = <HTMLElement>(
parent.wizardUI.dialog?.querySelector(
'mwc-button[slot="primaryAction"]'
)
);

await parent.updateComplete;
});

it('changes prefix attribute on primary action', async () => {
prefixField.value = 'newPref';

primaryAction.click();
await parent.updateComplete;

expect(
doc.querySelector(
'SubFunction[name="myBaySubFunc"] > LNode[lnClass="XSWI"]'
)
).to.have.attribute('prefix', 'newPref');
});

it('changes lnInst attribute on primary action', async () => {
lnInstField.value = '31';

primaryAction.click();
await parent.updateComplete;

expect(
doc.querySelector(
'SubFunction[name="myBaySubFunc"] > LNode[lnClass="XSWI"]'
)
).to.have.attribute('lnInst', '31');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ snapshots["web component rendering LNode element as reference to a LN/LN0 within
>
<mwc-icon slot="icon">
</mwc-icon>
<mwc-fab
icon="edit"
mini=""
slot="action"
>
</mwc-fab>
<mwc-fab
icon="delete"
mini=""
Expand All @@ -27,6 +33,12 @@ snapshots["web component rendering LNode element as instance of a LNodeType only
>
<mwc-icon slot="icon">
</mwc-icon>
<mwc-fab
icon="edit"
mini=""
slot="action"
>
</mwc-fab>
<mwc-fab
icon="delete"
mini=""
Expand Down
Loading

0 comments on commit 965f10a

Please sign in to comment.