Skip to content

Commit bba9b3c

Browse files
author
Dennis Labordus
authored
feat(editor/ied): Add wizard/action to remove IED including references (openscd#732)
* Add action to remove IED, including references. * Updated German translations. * Fixed small issues. * Added remove button to IED Editor. * Refactor cleanup Inputs and add logic to removing IED. * Updated comments.
1 parent 5c19822 commit bba9b3c

File tree

19 files changed

+677
-236
lines changed

19 files changed

+677
-236
lines changed

src/editors/IED.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ export default class IedPlugin extends LitElement {
4949
}
5050

5151
private get selectedIed(): Element | undefined {
52-
if (iedEditorSelectedIed === undefined) {
52+
// When there is no IED selected, or the selected IED has no parent (IED has been removed)
53+
// select the first IED from the List.
54+
if (iedEditorSelectedIed === undefined || iedEditorSelectedIed.parentElement === null) {
5355
const iedList = this.alphabeticOrderedIeds;
5456
if (iedList.length > 0) {
5557
iedEditorSelectedIed = iedList[0];

src/editors/ied/ied-container.ts

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,17 @@ import {
99
import { nothing } from 'lit-html';
1010
import { translate } from "lit-translate";
1111

12-
import {wizards} from "../../wizards/wizard-library.js";
1312
import '../../action-pane.js';
1413
import './access-point-container.js';
14+
15+
import { wizards } from "../../wizards/wizard-library.js";
1516
import { Nsdoc } from '../../foundation/nsdoc.js';
16-
import { getDescriptionAttribute, getNameAttribute, newWizardEvent } from '../../foundation.js';
17+
import {
18+
getDescriptionAttribute,
19+
getNameAttribute,
20+
newActionEvent,
21+
newWizardEvent} from '../../foundation.js';
22+
import { removeIEDWizard } from "../../wizards/ied.js";
1723

1824
/** [[`IED`]] plugin subeditor for editing `IED` element. */
1925
@customElement('ied-container')
@@ -24,12 +30,22 @@ export class IedContainer extends LitElement {
2430

2531
@property()
2632
nsdoc!: Nsdoc;
27-
33+
2834
private openEditWizard(): void {
2935
const wizard = wizards['IED'].edit(this.element);
3036
if (wizard) this.dispatchEvent(newWizardEvent(wizard));
3137
}
3238

39+
private removeIED(): void {
40+
const wizard = removeIEDWizard(this.element);
41+
if (wizard) {
42+
this.dispatchEvent(newWizardEvent(() => wizard));
43+
} else {
44+
// If no Wizard is needed, just remove the element.
45+
this.dispatchEvent(newActionEvent({ old: { parent: this.element.parentElement!, element: this.element } }));
46+
}
47+
}
48+
3349
private header(): TemplateResult {
3450
const name = getNameAttribute(this.element);
3551
const desc = getDescriptionAttribute(this.element);
@@ -38,22 +54,34 @@ export class IedContainer extends LitElement {
3854
}
3955

4056
render(): TemplateResult {
41-
return html`<action-pane .label="${this.header()}">
42-
<mwc-icon slot="icon">developer_board</mwc-icon>
43-
<abbr slot="action" title="${translate('edit')}">
44-
<mwc-icon-button
45-
icon="edit"
46-
@click=${() => this.openEditWizard()}
47-
></mwc-icon-button>
48-
</abbr>
49-
${Array.from(this.element.querySelectorAll(':scope > AccessPoint')).map(
50-
ap => html`<access-point-container
51-
.element=${ap}
52-
.nsdoc=${this.nsdoc}
53-
.ancestors=${[this.element]}
54-
></access-point-container>`)}
57+
return html`
58+
<action-pane .label="${this.header()}">
59+
<mwc-icon slot="icon">developer_board</mwc-icon>
60+
<abbr slot="action" title="${translate('remove')}">
61+
<mwc-icon-button
62+
icon="delete"
63+
@click=${() => this.removeIED()}
64+
></mwc-icon-button>
65+
</abbr>
66+
<abbr slot="action" title="${translate('edit')}">
67+
<mwc-icon-button
68+
icon="edit"
69+
@click=${() => this.openEditWizard()}
70+
></mwc-icon-button>
71+
</abbr>
72+
${Array.from(this.element.querySelectorAll(':scope > AccessPoint')).map(
73+
ap => html`<access-point-container
74+
.element=${ap}
75+
.nsdoc=${this.nsdoc}
76+
.ancestors=${[this.element]}
77+
></access-point-container>`)}
5578
</action-pane>`;
5679
}
5780

58-
static styles = css``;
81+
static styles = css`
82+
abbr {
83+
text-decoration: none;
84+
border-bottom: none;
85+
}
86+
`;
5987
}

src/editors/sampledvalues/subscriber-ied-list-smv.ts

Lines changed: 8 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,18 @@ import {
1818
Create,
1919
createElement,
2020
Delete,
21-
identity,
2221
newActionEvent,
23-
selector,
2422
} from '../../foundation.js';
2523
import {
2624
SampledValuesSelectEvent,
2725
IEDSampledValuesSubscriptionEvent,
2826
styles,
2927
SubscribeStatus,
3028
} from './foundation.js';
29+
import {
30+
emptyInputsDeleteActions,
31+
getFcdaReferences
32+
} from "../../foundation/ied.js";
3133

3234
/**
3335
* An IED within this IED list has 2 properties:
@@ -39,33 +41,6 @@ interface IED {
3941
partial?: boolean;
4042
}
4143

42-
/**
43-
* All available FCDA references that are used to link ExtRefs.
44-
*/
45-
const fcdaReferences = [
46-
'ldInst',
47-
'lnClass',
48-
'lnInst',
49-
'prefix',
50-
'doName',
51-
'daName',
52-
];
53-
54-
/**
55-
* Get all the FCDA attributes containing values from a specific element.
56-
* @param elementContainingFcdaReferences - The element to use
57-
* @returns FCDA references
58-
*/
59-
function getFcdaReferences(elementContainingFcdaReferences: Element): string {
60-
return fcdaReferences
61-
.map(fcdaRef =>
62-
elementContainingFcdaReferences.getAttribute(fcdaRef)
63-
? `[${fcdaRef}="${elementContainingFcdaReferences.getAttribute(fcdaRef)}"]`
64-
: ''
65-
)
66-
.join('');
67-
}
68-
6944
/** An sub element for subscribing and unsubscribing IEDs to Sampled Values messages. */
7045
@customElement('subscriber-ied-list-smv')
7146
export class SubscriberIEDListSmv extends LitElement {
@@ -261,56 +236,17 @@ export class SubscriberIEDListSmv extends LitElement {
261236
});
262237
});
263238

239+
// Check if empty Input Element should also be removed.
240+
actions.push(...emptyInputsDeleteActions(actions));
241+
264242
this.dispatchEvent(
265243
newActionEvent({
266244
title: 'Disconnect',
267-
actions: this.extendDeleteActions(actions),
245+
actions: actions,
268246
})
269247
);
270248
}
271249

272-
/**
273-
* Creating Delete actions in case Inputs elements are empty.
274-
* @param extRefDeleteActions - All Delete actions for ExtRefs.
275-
* @returns Possible delete actions for empty Inputs elements.
276-
*/
277-
private extendDeleteActions(extRefDeleteActions: Delete[]): Delete[] {
278-
if (!extRefDeleteActions.length) return [];
279-
280-
// Initialize with the already existing ExtRef Delete actions.
281-
const extendedDeleteActions: Delete[] = extRefDeleteActions;
282-
const inputsMap: Record<string, Element> = {};
283-
284-
for (const extRefDeleteAction of extRefDeleteActions) {
285-
const extRef = <Element>extRefDeleteAction.old.element;
286-
const inputsElement = <Element>extRefDeleteAction.old.parent;
287-
288-
const id = identity(inputsElement);
289-
if (!inputsMap[id]) inputsMap[id] = <Element>(inputsElement.cloneNode(true));
290-
291-
const linkedExtRef = inputsMap[id].querySelector(`ExtRef[iedName=${extRef.getAttribute('iedName')}]` +
292-
`${getFcdaReferences(extRef)}`);
293-
294-
if (linkedExtRef) inputsMap[id].removeChild(linkedExtRef);
295-
}
296-
297-
// create delete action for each empty inputs
298-
Object.entries(inputsMap).forEach(([key, value]) => {
299-
if (value.children.length ! == 0) {
300-
const doc = extRefDeleteActions[0].old.parent.ownerDocument!;
301-
const inputs = doc.querySelector(selector('Inputs', key));
302-
303-
if (inputs && inputs.parentElement) {
304-
extendedDeleteActions.push({
305-
old: { parent: inputs.parentElement, element: inputs },
306-
});
307-
}
308-
}
309-
});
310-
311-
return extendedDeleteActions;
312-
}
313-
314250
protected updated(): void {
315251
if (this.subscriberWrapper) {
316252
this.subscriberWrapper.scrollTo(0, 0);

src/editors/subscription/subscriber-list.ts

Lines changed: 8 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@ import {
1818
Create,
1919
createElement,
2020
Delete,
21-
identity,
2221
newActionEvent,
23-
selector,
2422
} from '../../foundation.js';
2523
import {
2624
newSubscriptionEvent,
@@ -32,6 +30,10 @@ import {
3230
View,
3331
ViewEvent,
3432
} from './foundation.js';
33+
import {
34+
emptyInputsDeleteActions,
35+
getFcdaReferences
36+
} from "../../foundation/ied.js";
3537

3638
/**
3739
* An element within this list has 2 properties:
@@ -43,35 +45,6 @@ interface ListElement {
4345
partial?: boolean;
4446
}
4547

46-
/**
47-
* All available FCDA references that are used to link ExtRefs.
48-
*/
49-
const fcdaReferences = [
50-
'ldInst',
51-
'lnClass',
52-
'lnInst',
53-
'prefix',
54-
'doName',
55-
'daName',
56-
];
57-
58-
/**
59-
* Get all the FCDA attributes containing values from a specific element.
60-
* @param elementContainingFcdaReferences - The element to use
61-
* @returns FCDA references
62-
*/
63-
function getFcdaReferences(elementContainingFcdaReferences: Element): string {
64-
return fcdaReferences
65-
.map(fcdaRef =>
66-
elementContainingFcdaReferences.getAttribute(fcdaRef)
67-
? `[${fcdaRef}="${elementContainingFcdaReferences.getAttribute(
68-
fcdaRef
69-
)}"]`
70-
: ''
71-
)
72-
.join('');
73-
}
74-
7548
/** Defining view outside the class, which makes it persistent. */
7649
let view: View = View.GOOSE_PUBLISHER;
7750

@@ -351,59 +324,17 @@ export class SubscriberList extends LitElement {
351324
});
352325
});
353326

327+
// Check if empty Input Element should also be removed.
328+
actions.push(...emptyInputsDeleteActions(actions));
329+
354330
this.dispatchEvent(
355331
newActionEvent({
356332
title: 'Disconnect',
357-
actions: this.extendDeleteActions(actions),
333+
actions: actions,
358334
})
359335
);
360336
}
361337

362-
/**
363-
* Creating Delete actions in case Inputs elements are empty.
364-
* @param extRefDeleteActions - All Delete actions for ExtRefs.
365-
* @returns Possible delete actions for empty Inputs elements.
366-
*/
367-
private extendDeleteActions(extRefDeleteActions: Delete[]): Delete[] {
368-
if (!extRefDeleteActions.length) return [];
369-
370-
// Initialize with the already existing ExtRef Delete actions.
371-
const extendedDeleteActions: Delete[] = extRefDeleteActions;
372-
const inputsMap: Record<string, Element> = {};
373-
374-
for (const extRefDeleteAction of extRefDeleteActions) {
375-
const extRef = <Element>extRefDeleteAction.old.element;
376-
const inputsElement = <Element>extRefDeleteAction.old.parent;
377-
378-
const id = identity(inputsElement);
379-
if (!inputsMap[id])
380-
inputsMap[id] = <Element>inputsElement.cloneNode(true);
381-
382-
const linkedExtRef = inputsMap[id].querySelector(
383-
`ExtRef[iedName=${extRef.getAttribute('iedName')}]` +
384-
`${getFcdaReferences(extRef)}`
385-
);
386-
387-
if (linkedExtRef) inputsMap[id].removeChild(linkedExtRef);
388-
}
389-
390-
// create delete action for each empty inputs
391-
Object.entries(inputsMap).forEach(([key, value]) => {
392-
if (value.children.length! == 0) {
393-
const doc = extRefDeleteActions[0].old.parent.ownerDocument!;
394-
const inputs = doc.querySelector(selector('Inputs', key));
395-
396-
if (inputs && inputs.parentElement) {
397-
extendedDeleteActions.push({
398-
old: { parent: inputs.parentElement, element: inputs },
399-
});
400-
}
401-
}
402-
});
403-
404-
return extendedDeleteActions;
405-
}
406-
407338
private resetElements() {
408339
this.subscribedElements = [];
409340
this.availableElements = [];

src/editors/substation/ied-editor.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ import '../../action-icon.js';
1515
import { createClientLnWizard } from '../../wizards/clientln.js';
1616
import { gooseIcon, smvIcon, reportIcon } from '../../icons/icons.js';
1717
import { wizards } from '../../wizards/wizard-library.js';
18-
import { newWizardEvent } from '../../foundation.js';
18+
import { newActionEvent, newWizardEvent } from '../../foundation.js';
1919
import { selectGseControlWizard } from '../../wizards/gsecontrol.js';
2020
import { selectSampledValueControlWizard } from '../../wizards/sampledvaluecontrol.js';
2121
import { selectReportControlWizard } from '../../wizards/reportcontrol.js';
22+
import { removeIEDWizard } from "../../wizards/ied.js";
2223

2324
/** [[`SubstationEditor`]] subeditor for a child-less `IED` element. */
2425
@customElement('ied-editor')
@@ -65,6 +66,16 @@ export class IedEditor extends LitElement {
6566
if (wizard) this.dispatchEvent(newWizardEvent(wizard));
6667
}
6768

69+
private removeIED(): void {
70+
const wizard = removeIEDWizard(this.element);
71+
if (wizard) {
72+
this.dispatchEvent(newWizardEvent(() => wizard));
73+
} else {
74+
// If no Wizard is needed, just remove the element.
75+
this.dispatchEvent(newActionEvent({ old: { parent: this.element.parentElement!, element: this.element } }));
76+
}
77+
}
78+
6879
render(): TemplateResult {
6980
return html`<action-icon label="${this.name}" icon="developer_board">
7081
<mwc-fab
@@ -76,10 +87,11 @@ export class IedEditor extends LitElement {
7687
></mwc-fab
7788
><mwc-fab
7889
slot="action"
79-
class="selectgse"
90+
class="delete"
8091
mini
81-
@click="${() => this.openGseControlSelection()}"
82-
><mwc-icon slot="icon">${gooseIcon}</mwc-icon></mwc-fab
92+
@click="${() => this.removeIED() }"
93+
icon="delete"
94+
></mwc-fab
8395
><mwc-fab
8496
slot="action"
8597
class="selectreport"
@@ -99,6 +111,12 @@ export class IedEditor extends LitElement {
99111
@click="${() => this.openCommunicationMapping()}"
100112
icon="add_link"
101113
></mwc-fab
114+
><mwc-fab
115+
slot="action"
116+
class="selectgse"
117+
mini
118+
@click="${() => this.openGseControlSelection()}"
119+
><mwc-icon slot="icon">${gooseIcon}</mwc-icon></mwc-fab
102120
></action-icon> `;
103121
}
104122
}

0 commit comments

Comments
 (0)