Skip to content

Commit 629cbcc

Browse files
author
Dennis Labordus
authored
feat(editor/ied): Filter for logical nodes (openscd#990)
* First version of new filters in IED Editor. * First working version of LN Classes filter with new filter button component. * Updated some of test cases. * Fixed some IED Editor tests. * Updated test cases for IED Editor * Added test for component. * Fixed test for component. * Reverted modification, not needed anymore * Fixed review comments. * Adding Filtering on LDevices without LN Classes. * Fixed test.
1 parent e399e7e commit 629cbcc

36 files changed

+2820
-385
lines changed

src/editors/IED.ts

Lines changed: 122 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -3,39 +3,29 @@ import {
33
html,
44
LitElement,
55
property,
6+
PropertyValues,
67
state,
78
TemplateResult,
89
} from 'lit-element';
10+
import { translate } from 'lit-translate';
11+
import { nothing } from 'lit-html';
12+
13+
import '@material/mwc-list/mwc-check-list-item';
14+
import '@material/mwc-list/mwc-radio-list-item';
915

10-
import '@material/mwc-fab';
11-
import '@material/mwc-select';
12-
import '@material/mwc-list/mwc-list-item';
16+
import '../oscd-filter-button.js';
17+
import { SelectedItemsChangedEvent } from '../oscd-filter-button.js';
1318

1419
import './ied/ied-container.js';
1520
import './ied/element-path.js';
1621

17-
import { translate } from 'lit-translate';
18-
import { SingleSelectedEvent } from '@material/mwc-list/mwc-list-foundation';
1922
import {
2023
compareNames,
2124
getDescriptionAttribute,
2225
getNameAttribute,
2326
} from '../foundation.js';
2427
import { Nsdoc } from '../foundation/nsdoc.js';
25-
26-
/*
27-
* We need a variable outside the plugin to save the selected IED, because the Plugin is created
28-
* more than once during working with the IED Editor, for instance when opening a Wizard to edit the IED.
29-
*/
30-
let iedEditorSelectedIed: Element | undefined;
31-
/*
32-
* We will also add an Event Listener when a new document is opened. We then want to reset the selection
33-
* so setting it to undefined will set the selected IED again on the first in the list.
34-
*/
35-
function onOpenDocResetSelectedIed() {
36-
iedEditorSelectedIed = undefined;
37-
}
38-
addEventListener('open-doc', onOpenDocResetSelectedIed);
28+
import { getIcon } from '../icons/icons.js';
3929

4030
/** An editor [[`plugin`]] for editing the `IED` section. */
4131
export default class IedPlugin extends LitElement {
@@ -47,7 +37,14 @@ export default class IedPlugin extends LitElement {
4737
@property()
4838
nsdoc!: Nsdoc;
4939

50-
private get alphabeticOrderedIeds(): Element[] {
40+
@state()
41+
selectedIEDs: string[] = [];
42+
43+
@state()
44+
selectedLNClasses: string[] = [];
45+
46+
@state()
47+
private get iedList(): Element[] {
5148
return this.doc
5249
? Array.from(this.doc.querySelectorAll(':root > IED')).sort((a, b) =>
5350
compareNames(a, b)
@@ -56,60 +53,131 @@ export default class IedPlugin extends LitElement {
5653
}
5754

5855
@state()
59-
private set selectedIed(element: Element | undefined) {
60-
iedEditorSelectedIed = element;
56+
private get lnClassList(): string[][] {
57+
const currentIed = this.selectedIed;
58+
const uniqueLNClassList: string[] = [];
59+
if (currentIed) {
60+
return Array.from(currentIed.querySelectorAll('LN0, LN'))
61+
.filter(element => element.hasAttribute('lnClass'))
62+
.filter(element => {
63+
const lnClass = element.getAttribute('lnClass') ?? '';
64+
if (uniqueLNClassList.includes(lnClass)) {
65+
return false;
66+
}
67+
uniqueLNClassList.push(lnClass);
68+
return true;
69+
})
70+
.map(element => {
71+
const lnClass = element.getAttribute('lnClass');
72+
const label = this.nsdoc.getDataDescription(element).label;
73+
return [lnClass, label];
74+
}) as string[][];
75+
}
76+
return [];
6177
}
6278

79+
@state()
6380
private get selectedIed(): Element | undefined {
6481
// When there is no IED selected, or the selected IED has no parent (IED has been removed)
6582
// select the first IED from the List.
66-
if (
67-
iedEditorSelectedIed === undefined ||
68-
iedEditorSelectedIed.parentElement === null
69-
) {
70-
const iedList = this.alphabeticOrderedIeds;
71-
if (iedList.length > 0) {
72-
iedEditorSelectedIed = iedList[0];
73-
}
83+
if (this.selectedIEDs.length >= 1) {
84+
const iedList = this.iedList;
85+
return iedList.find(element => {
86+
const iedName = getNameAttribute(element);
87+
return this.selectedIEDs[0] === iedName;
88+
});
7489
}
75-
return iedEditorSelectedIed;
90+
return undefined;
7691
}
7792

78-
private onSelect(event: SingleSelectedEvent): void {
79-
this.selectedIed = this.alphabeticOrderedIeds[event.detail.index];
80-
this.requestUpdate('selectedIed');
93+
protected updated(_changedProperties: PropertyValues): void {
94+
super.updated(_changedProperties);
95+
96+
// When the document is updated, we reset the selected IED.
97+
if (_changedProperties.has('doc') || _changedProperties.has('nsdoc')) {
98+
this.selectedIEDs = [];
99+
this.selectedLNClasses = [];
100+
101+
const iedList = this.iedList;
102+
if (iedList.length > 0) {
103+
const iedName = getNameAttribute(iedList[0]);
104+
if (iedName) {
105+
this.selectedIEDs = [iedName];
106+
}
107+
}
108+
this.selectedLNClasses = this.lnClassList.map(
109+
lnClassInfo => lnClassInfo[0]
110+
);
111+
}
81112
}
82113

83114
render(): TemplateResult {
84-
const iedList = this.alphabeticOrderedIeds;
115+
const iedList = this.iedList;
85116
if (iedList.length > 0) {
86117
return html`<section>
87118
<div class="header">
88-
<mwc-select
89-
class="iedSelect"
90-
label="${translate('iededitor.searchHelper')}"
91-
@selected=${this.onSelect}
119+
<h1>${translate('filters')}:</h1>
120+
121+
<oscd-filter-button
122+
id="iedFilter"
123+
icon="developer_board"
124+
.header=${translate('iededitor.iedSelector')}
125+
@selected-items-changed="${(e: SelectedItemsChangedEvent) => {
126+
this.selectedIEDs = e.detail.selectedItems;
127+
this.selectedLNClasses = this.lnClassList.map(
128+
lnClassInfo => lnClassInfo[0]
129+
);
130+
this.requestUpdate('selectedIed');
131+
}}"
92132
>
93133
${iedList.map(ied => {
94134
const name = getNameAttribute(ied);
95135
const descr = getDescriptionAttribute(ied);
96-
return html` <mwc-list-item
97-
?selected=${ied == this.selectedIed}
136+
const type = ied.getAttribute('type');
137+
const manufacturer = ied.getAttribute('manufacturer');
138+
return html` <mwc-radio-list-item
98139
value="${name}"
99-
>${name}
100-
${descr
101-
? translate('iededitor.searchHelperDesc', {
102-
description: descr,
103-
})
104-
: ''}
105-
</mwc-list-item>`;
140+
?twoline="${type && manufacturer}"
141+
?selected="${this.selectedIEDs.includes(name ?? '')}"
142+
>
143+
${name} ${descr ? html` (${descr})` : html``}
144+
<span slot="secondary">
145+
${type} ${type && manufacturer ? html`&mdash;` : nothing}
146+
${manufacturer}
147+
</span>
148+
</mwc-radio-list-item>`;
149+
})}
150+
</oscd-filter-button>
151+
152+
<oscd-filter-button
153+
id="lnClassesFilter"
154+
multi="true"
155+
.header="${translate('iededitor.lnFilter')}"
156+
@selected-items-changed="${(e: SelectedItemsChangedEvent) => {
157+
this.selectedLNClasses = e.detail.selectedItems;
158+
this.requestUpdate('selectedIed');
159+
}}"
160+
>
161+
<span slot="icon">${getIcon('lNIcon')}</span>
162+
${this.lnClassList.map(lnClassInfo => {
163+
const value = lnClassInfo[0];
164+
const label = lnClassInfo[1];
165+
return html`<mwc-check-list-item
166+
value="${value}"
167+
?selected="${this.selectedLNClasses.includes(value)}"
168+
>
169+
${label}
170+
</mwc-check-list-item>`;
106171
})}
107-
</mwc-select>
172+
</oscd-filter-button>
173+
108174
<element-path class="elementPath"></element-path>
109175
</div>
176+
110177
<ied-container
111178
.doc=${this.doc}
112179
.element=${this.selectedIed}
180+
.selectedLNClasses=${this.selectedLNClasses}
113181
.nsdoc=${this.nsdoc}
114182
></ied-container>
115183
</section>`;
@@ -128,20 +196,10 @@ export default class IedPlugin extends LitElement {
128196
padding: 8px 12px 16px;
129197
}
130198
131-
.iedSelect {
132-
width: 35vw;
133-
padding-bottom: 20px;
134-
}
135-
136199
.header {
137200
display: flex;
138201
}
139202
140-
.elementPath {
141-
margin-left: auto;
142-
padding-right: 12px;
143-
}
144-
145203
h1 {
146204
color: var(--mdc-theme-on-surface);
147205
font-family: 'Roboto', sans-serif;
@@ -153,5 +211,10 @@ export default class IedPlugin extends LitElement {
153211
line-height: 48px;
154212
padding-left: 0.3em;
155213
}
214+
215+
.elementPath {
216+
margin-left: auto;
217+
padding-right: 12px;
218+
}
156219
`;
157220
}

src/editors/ied/access-point-container.ts

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
import { css, customElement, html, TemplateResult } from 'lit-element';
1+
import {
2+
css,
3+
customElement,
4+
html,
5+
property,
6+
PropertyValues,
7+
state,
8+
TemplateResult,
9+
} from 'lit-element';
210
import { nothing } from 'lit-html';
311

412
import { getDescriptionAttribute, getNameAttribute } from '../../foundation.js';
@@ -12,14 +20,38 @@ import { Container } from './foundation.js';
1220
/** [[`IED`]] plugin subeditor for editing `AccessPoint` element. */
1321
@customElement('access-point-container')
1422
export class AccessPointContainer extends Container {
23+
@property()
24+
selectedLNClasses: string[] = [];
25+
1526
private header(): TemplateResult {
1627
const name = getNameAttribute(this.element);
1728
const desc = getDescriptionAttribute(this.element);
1829

1930
return html`${name}${desc ? html` &mdash; ${desc}` : nothing}`;
2031
}
2132

33+
protected updated(_changedProperties: PropertyValues): void {
34+
super.updated(_changedProperties);
35+
36+
// When the LN Classes filter is updated, we also want to trigger rendering for the LN Elements.
37+
if (_changedProperties.has('selectedLNClasses')) {
38+
this.requestUpdate('lnElements');
39+
}
40+
}
41+
42+
@state()
43+
private get lnElements(): Element[] {
44+
return Array.from(this.element.querySelectorAll(':scope > LN')).filter(
45+
element => {
46+
const lnClass = element.getAttribute('lnClass') ?? '';
47+
return this.selectedLNClasses.includes(lnClass);
48+
}
49+
);
50+
}
51+
2252
render(): TemplateResult {
53+
const lnElements = this.lnElements;
54+
2355
return html`<action-pane .label="${this.header()}">
2456
<mwc-icon slot="icon">${accessPointIcon}</mwc-icon>
2557
${Array.from(this.element.querySelectorAll(':scope > Server')).map(
@@ -28,11 +60,35 @@ export class AccessPointContainer extends Container {
2860
.doc=${this.doc}
2961
.element=${server}
3062
.nsdoc=${this.nsdoc}
63+
.selectedLNClasses=${this.selectedLNClasses}
3164
.ancestors=${[...this.ancestors, this.element]}
3265
></server-container>`
3366
)}
67+
<div id="lnContainer">
68+
${lnElements.map(
69+
ln => html`<ln-container
70+
.doc=${this.doc}
71+
.element=${ln}
72+
.nsdoc=${this.nsdoc}
73+
.ancestors=${[...this.ancestors, this.element]}
74+
></ln-container> `
75+
)}
76+
</div>
3477
</action-pane>`;
3578
}
3679

37-
static styles = css``;
80+
static styles = css`
81+
#lnContainer {
82+
display: grid;
83+
grid-gap: 12px;
84+
box-sizing: border-box;
85+
grid-template-columns: repeat(auto-fit, minmax(316px, auto));
86+
}
87+
88+
@media (max-width: 387px) {
89+
#lnContainer {
90+
grid-template-columns: repeat(auto-fit, minmax(196px, auto));
91+
}
92+
}
93+
`;
3894
}

src/editors/ied/ied-container.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { css, customElement, html, TemplateResult } from 'lit-element';
1+
import {
2+
css,
3+
customElement,
4+
html,
5+
property,
6+
TemplateResult,
7+
} from 'lit-element';
28
import { nothing } from 'lit-html';
39
import { translate } from 'lit-translate';
410

@@ -18,6 +24,9 @@ import { removeIEDWizard } from '../../wizards/ied.js';
1824
/** [[`IED`]] plugin subeditor for editing `IED` element. */
1925
@customElement('ied-container')
2026
export class IedContainer extends Container {
27+
@property()
28+
selectedLNClasses: string[] = [];
29+
2130
private openEditWizard(): void {
2231
const wizard = wizards['IED'].edit(this.element);
2332
if (wizard) this.dispatchEvent(newWizardEvent(wizard));
@@ -64,6 +73,7 @@ export class IedContainer extends Container {
6473
.doc=${this.doc}
6574
.element=${ap}
6675
.nsdoc=${this.nsdoc}
76+
.selectedLNClasses=${this.selectedLNClasses}
6777
.ancestors=${[this.element]}
6878
></access-point-container>`
6979
)}

0 commit comments

Comments
 (0)