Skip to content

Commit 97c221d

Browse files
author
Dennis Labordus
committed
Merge branch '104-plugin' into 104-expected-value-refactor
Signed-off-by: Dennis Labordus <[email protected]>
2 parents 448edb6 + 6f1e388 commit 97c221d

File tree

14 files changed

+1263
-74
lines changed

14 files changed

+1263
-74
lines changed

src/editors/protocol104/connectedap-editor.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import '@material/mwc-fab';
1010

1111
import '../../action-icon.js';
1212
import { newWizardEvent, newActionEvent } from '../../foundation.js';
13-
import { editConnectedAp104Wizard } from './wizards/connectedap.js';
13+
import { editConnectedApWizard } from './wizards/connectedap.js';
1414

1515
/** [[`104`]] subeditor for a `ConnectedAP` element. */
1616
@customElement('connectedap-104-editor')
@@ -20,7 +20,14 @@ export class ConnectedAP104Editor extends LitElement {
2020
element!: Element;
2121

2222
private openEditWizard(): void {
23-
this.dispatchEvent(newWizardEvent(editConnectedAp104Wizard(this.element)));
23+
this.dispatchEvent(
24+
newWizardEvent(() =>
25+
editConnectedApWizard(
26+
this.element,
27+
this.element.querySelectorAll('Address > P[type^="RG"]').length > 0
28+
)
29+
)
30+
);
2431
}
2532

2633
remove(): void {

src/editors/protocol104/foundation/foundation.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1+
import { html, TemplateResult } from 'lit-element';
2+
import { ifDefined } from 'lit-html/directives/if-defined.js';
3+
import { translate } from 'lit-translate';
14
import { getInstanceAttribute, getNameAttribute } from '../../../foundation.js';
5+
import { typeMaxLength } from '../../../wizards/foundation/p-types.js';
6+
import { typeDescriptiveNameKeys, typePattern } from './p-types.js';
27

38
export const PRIVATE_TYPE_104 = 'IEC_60870_5_104';
49

@@ -315,6 +320,25 @@ export function getEnumOrds(daiElement: Element): string[] {
315320
return ords;
316321
}
317322

323+
/**
324+
* Create a wizard-textfield element for the wizards within the Network part of the 104 plugin.
325+
* @param pType - The type of P a Text Field has to be created for.
326+
* @returns - A Text Field created for a specific type for the Create wizard.
327+
*/
328+
export function createNetworkTextField(
329+
pType: string,
330+
maybeValue?: string
331+
): TemplateResult {
332+
return html`<wizard-textfield
333+
required
334+
label="${pType}"
335+
pattern="${ifDefined(typePattern[pType])}"
336+
.maybeValue=${maybeValue ?? null}
337+
maxLength="${ifDefined(typeMaxLength[pType])}"
338+
helper="${translate(typeDescriptiveNameKeys[pType])}"
339+
></wizard-textfield>`;
340+
}
341+
318342
/**
319343
* Enumeration stating the active view of the 104 plugin.
320344
*/

src/editors/protocol104/foundation/p-types.ts

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,20 @@ export const pTypes104: string[] = [
99
'TIMEOUT-3'
1010
];
1111

12+
export const pTypesRedundancyGroup104: string[] = [
13+
'W-FACTOR',
14+
'K-FACTOR',
15+
'TIMEOUT-0',
16+
'TIMEOUT-1',
17+
'TIMEOUT-2',
18+
'TIMEOUT-3'
19+
];
20+
21+
export const pTypesLogicLink104: string[] = [
22+
'IP',
23+
'IP-SUBNET'
24+
];
25+
1226
const typeBase = {
1327
IP: '([0-9]{1,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])[.]([0-9]{1,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])[.]([0-9]{1,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])[.]([0-9]{1,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])',
1428
factor: '[1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-3][0-2][0-7][0-6][0-7]',
@@ -32,15 +46,14 @@ export const stationTypeOptions: string[] = [
3246
'controlled-station'
3347
]
3448

35-
/** Max length definition for all `P` element */
3649
export const typeDescriptiveNameKeys: Record<string, string> = {
37-
'StationType': 'protocol104.network.connectedap.stationType',
38-
'IP': 'protocol104.network.connectedap.ip',
39-
'IP-SUBNET': 'protocol104.network.connectedap.ipSubnet',
40-
'W-FACTOR': 'protocol104.network.connectedap.wFactor',
41-
'K-FACTOR': 'protocol104.network.connectedap.kFactor',
42-
'TIMEOUT-0': 'protocol104.network.connectedap.timeout0',
43-
'TIMEOUT-1': 'protocol104.network.connectedap.timeout1',
44-
'TIMEOUT-2': 'protocol104.network.connectedap.timeout2',
45-
'TIMEOUT-3': 'protocol104.network.connectedap.timeout3',
50+
'StationType': 'protocol104.network.connectedAp.wizard.stationTypeHelper',
51+
'IP': 'protocol104.network.connectedAp.wizard.ipHelper',
52+
'IP-SUBNET': 'protocol104.network.connectedAp.wizard.ipSubnetHelper',
53+
'W-FACTOR': 'protocol104.network.connectedAp.wizard.wFactorHelper',
54+
'K-FACTOR': 'protocol104.network.connectedAp.wizard.kFactorHelper',
55+
'TIMEOUT-0': 'protocol104.network.connectedAp.wizard.timeout0Helper',
56+
'TIMEOUT-1': 'protocol104.network.connectedAp.wizard.timeout1Helper',
57+
'TIMEOUT-2': 'protocol104.network.connectedAp.wizard.timeout2Helper',
58+
'TIMEOUT-3': 'protocol104.network.connectedAp.wizard.timeout3Helper',
4659
};

src/editors/protocol104/wizards/connectedap.ts

Lines changed: 150 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { html } from 'lit-element';
1+
import { html, TemplateResult } from 'lit-element';
22
import { translate, get } from 'lit-translate';
33

44
import '@material/mwc-checkbox';
5+
import '@material/mwc-switch';
56
import '@material/mwc-formfield';
67
import '@material/mwc-list/mwc-list-item';
78
import '@material/mwc-list/mwc-check-list-item';
@@ -15,24 +16,32 @@ import '../../../filtered-list.js';
1516
import {
1617
pTypes104,
1718
stationTypeOptions,
18-
typeDescriptiveNameKeys
19+
typeDescriptiveNameKeys,
20+
typePattern
1921
} from '../foundation/p-types.js';
2022
import {
23+
cloneElement,
2124
compareNames,
2225
ComplexAction,
2326
createElement,
2427
EditorAction,
2528
getValue,
2629
identity,
2730
isPublic,
31+
newSubWizardEvent,
32+
newWizardEvent,
2833
Wizard,
2934
WizardActor,
30-
WizardInputElement
35+
WizardInputElement,
36+
WizardMenuActor
3137
} from '../../../foundation.js';
3238
import {
33-
createPTextField,
3439
createTypeRestrictionCheckbox
3540
} from '../../../wizards/connectedap.js';
41+
import { SingleSelectedEvent } from '@material/mwc-list/mwc-list-foundation';
42+
import { ifDefined } from 'lit-html/directives/if-defined';
43+
import { typeMaxLength } from '../../../wizards/foundation/p-types.js';
44+
import { createRedundancyGroupWizard, editRedundancyGroupWizard } from './redundancygroup.js';
3645

3746
interface AccessPointDescription {
3847
element: Element;
@@ -164,7 +173,85 @@ function createAddressElement(
164173
return element;
165174
}
166175

167-
export function updateConnectedApAction(parent: Element): WizardActor {
176+
/** @returns single page [[`Wizard`]] to edit SCL element ConnectedAP for the 104 plugin. */
177+
export function editConnectedApWizard(parent: Element, redundancy?: boolean): Wizard {
178+
const redundancyGroupNumbers = getRedundancyGroupNumbers(parent);
179+
return [
180+
{
181+
title: get('protocol104.network.connectedAp.wizard.title.edit'),
182+
element: parent,
183+
menuActions: redundancy
184+
? [{
185+
icon: 'playlist_add',
186+
label: get('protocol104.network.connectedAp.wizard.addRedundancyGroup'),
187+
action: openRedundancyGroupWizard(parent, redundancyGroupNumbers),
188+
}]
189+
: undefined,
190+
primary: {
191+
icon: 'save',
192+
label: get('save'),
193+
action: editConnectedApAction(parent, redundancy),
194+
},
195+
content: [
196+
html`<mwc-formfield label="${get('protocol104.network.connectedAp.wizard.redundancySwitchLabel')}">
197+
<mwc-switch
198+
id="redundancy"
199+
?checked=${redundancy}
200+
@change=${(event: Event) => {
201+
event.target!.dispatchEvent(newWizardEvent());
202+
event.target!.dispatchEvent(
203+
newSubWizardEvent(() =>
204+
editConnectedApWizard(
205+
parent,
206+
!redundancy
207+
)
208+
)
209+
);
210+
}}
211+
></mwc-switch>
212+
</mwc-formfield>
213+
<wizard-divider></wizard-divider>
214+
${createTypeRestrictionCheckbox(parent)}
215+
<wizard-select
216+
label="StationType"
217+
.maybeValue=${parent.querySelector(
218+
`Address > P[type="StationType"]`
219+
)?.innerHTML ?? null}
220+
required
221+
fixedMenuPosition
222+
helper="${translate(typeDescriptiveNameKeys["StationType"])}"
223+
>
224+
${stationTypeOptions.map(
225+
option => html`<mwc-list-item value="${option}">${option}</mwc-list-item>`
226+
)}
227+
</wizard-select>
228+
${redundancy
229+
? html`<h3>${get('protocol104.network.connectedAp.wizard.redundancyGroupTitle')}</h3>
230+
<mwc-list
231+
@selected=${(e: SingleSelectedEvent) => {
232+
e.target!.dispatchEvent(
233+
newSubWizardEvent(() =>
234+
editRedundancyGroupWizard(
235+
parent,
236+
redundancyGroupNumbers[e.detail.index]
237+
)
238+
)
239+
);
240+
}}>
241+
${redundancyGroupNumbers.length != 0
242+
? redundancyGroupNumbers.map(number => html`<mwc-list-item>Redundancy Group ${number}</mwc-list-item>`)
243+
: html`<p>${get('protocol104.network.connectedAp.wizard.noRedundancyGroupsAvailable')}</p>`}
244+
</mwc-list>`
245+
: html`${pTypes104.map(
246+
pType => html`${createEditTextField(parent, pType)}`
247+
)}`}
248+
`,
249+
],
250+
},
251+
];
252+
}
253+
254+
function editConnectedApAction(parent: Element, redundancy?: boolean): WizardActor {
168255
return (inputs: WizardInputElement[], wizard: Element): EditorAction[] => {
169256
const typeRestriction: boolean =
170257
(<Checkbox>wizard.shadowRoot?.querySelector('#typeRestriction'))
@@ -180,7 +267,24 @@ export function updateConnectedApAction(parent: Element): WizardActor {
180267
apName: parent.getAttribute('apName') ?? '',
181268
}),
182269
};
183-
if (oldAddress !== null && !isEqualAddress(oldAddress, newAddress)) {
270+
// When we have a redundanct ConnectedAP, we are only interested in the StationType value.
271+
// All redundancy group actions are done in those wizards itself.
272+
if (redundancy) {
273+
const stationTypeValue = getValue(inputs.find(i => i.label === 'StationType')!)!;
274+
const originalElement = oldAddress?.querySelector('P[type="StationType"]');
275+
276+
const elementClone = cloneElement(originalElement!, {});
277+
elementClone!.textContent = stationTypeValue;
278+
279+
complexAction.actions.push({
280+
old: {
281+
element: originalElement!
282+
},
283+
new: {
284+
element: elementClone
285+
}
286+
});
287+
} else if (oldAddress !== null && !isEqualAddress(oldAddress, newAddress)) {
184288
//address & child elements P are changed: cannot use replace editor action
185289
complexAction.actions.push({
186290
old: {
@@ -206,35 +310,44 @@ export function updateConnectedApAction(parent: Element): WizardActor {
206310
};
207311
}
208312

209-
/** @returns single page [[`Wizard`]] to edit SCL element ConnectedAP for the 104 plugin. */
210-
export function editConnectedAp104Wizard(element: Element): Wizard {
211-
return [
212-
{
213-
title: get('wizard.title.edit', { tagName: element.tagName }),
214-
element,
215-
primary: {
216-
icon: 'save',
217-
label: get('save'),
218-
action: updateConnectedApAction(element),
219-
},
220-
content: [
221-
html`${createTypeRestrictionCheckbox(element)}
222-
<wizard-select
223-
label="StationType"
224-
.maybeValue=${element.querySelector(
225-
`Address > P[type="StationType"]`
226-
)?.innerHTML ?? null}
227-
required
228-
helper="${translate(typeDescriptiveNameKeys["StationType"])}"
229-
>${stationTypeOptions.map(
230-
option =>
231-
html`<mwc-list-item value="${option}">${option}</mwc-list-item>`
232-
)}</wizard-select>
233-
${pTypes104.map(
234-
pType =>
235-
html`${createPTextField(element, pType)}`
236-
)}`,
237-
],
238-
},
239-
];
313+
function openRedundancyGroupWizard(element: Element, rGNumbers: number[]): WizardMenuActor {
314+
return (wizard: Element): void => {
315+
wizard.dispatchEvent(newSubWizardEvent(createRedundancyGroupWizard(element, rGNumbers)));
316+
};
240317
}
318+
319+
/**
320+
* Get all the current used Redundancy Group numbers.
321+
* @param parent - The parent element of all the P elements.
322+
* @returns An array with all the Redundancy Group numbers.
323+
*/
324+
function getRedundancyGroupNumbers(parent: Element): number[] {
325+
const groupNumbers: number[] = [];
326+
327+
parent.querySelectorAll(`Address > P[type^="RG"]`).forEach(p => {
328+
const redundancyGroupPart = p.getAttribute('type')?.split('-')[0];
329+
const number = Number(redundancyGroupPart?.substring(2));
330+
331+
if (!groupNumbers.includes(number)) groupNumbers.push(number)
332+
})
333+
334+
return groupNumbers.sort();
335+
}
336+
337+
/**
338+
* Create a wizard-textfield element for the Edit wizard.
339+
* @param parent - The parent element of the P to create.
340+
* @param pType - The type of P a Text Field has to be created for.
341+
* @returns - A Text Field created for a specific type for the Edit wizard.
342+
*/
343+
function createEditTextField(parent: Element, pType: string): TemplateResult {
344+
return html`<wizard-textfield
345+
required
346+
label="${pType}"
347+
pattern="${ifDefined(typePattern[pType])}"
348+
.maybeValue=${parent.querySelector(
349+
`Address > P[type="${pType}"]`
350+
)?.innerHTML ?? null}
351+
maxLength="${ifDefined(typeMaxLength[pType])}"
352+
></wizard-textfield>`
353+
}

0 commit comments

Comments
 (0)