Skip to content

Commit f94b042

Browse files
author
Rob Tjalma
authored
feat(plugins/SampledValues): Create Sampled Values tab in Subscription plugin
1 parent c3c0166 commit f94b042

File tree

11 files changed

+803
-5
lines changed

11 files changed

+803
-5
lines changed

public/js/plugins.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ export const officialPlugins = [
2020
default: false,
2121
kind: 'editor',
2222
},
23+
{
24+
name: 'Sampled Values Subscriber',
25+
src: '/src/editors/SampledValues.js',
26+
icon: 'link',
27+
default: true,
28+
kind: 'editor',
29+
},
2330
{
2431
name: 'Communication',
2532
src: '/src/editors/Communication.js',

src/editors/SampledValues.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { LitElement, html, TemplateResult, property, css } from 'lit-element';
2+
3+
import '@material/mwc-fab';
4+
5+
import './sampledvalues/subscriber-ied-list.js';
6+
import './sampledvalues/sampled-values-list.js';
7+
8+
/** An editor [[`plugin`]] for subscribing IEDs to Sampled Values. */
9+
export default class SampledValuesPlugin extends LitElement {
10+
/** The document being edited as provided to plugins by [[`OpenSCD`]]. */
11+
@property()
12+
doc!: XMLDocument;
13+
14+
render(): TemplateResult {
15+
return html`
16+
<div id="containerTemplates">
17+
<section>
18+
<sampled-values-list .doc=${this.doc}></sampled-values-list>
19+
</section>
20+
<section>
21+
<subscriber-ied-list .doc=${this.doc}></subscriber-ied-list>
22+
</section>
23+
</div>`;
24+
}
25+
26+
static styles = css`
27+
:host {
28+
width: 100vw;
29+
}
30+
31+
section {
32+
width: 49vw;
33+
}
34+
35+
#containerTemplates {
36+
display: grid;
37+
grid-gap: 12px;
38+
padding: 8px 12px 16px;
39+
box-sizing: border-box;
40+
grid-template-columns: repeat(auto-fit, minmax(316px, auto));
41+
}
42+
43+
@media (max-width: 387px) {
44+
#containerTemplates {
45+
grid-template-columns: repeat(auto-fit, minmax(196px, auto));
46+
}
47+
}
48+
`;
49+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import {
2+
customElement,
3+
html,
4+
LitElement,
5+
property,
6+
TemplateResult,
7+
} from 'lit-element';
8+
9+
import '@material/mwc-icon';
10+
import '@material/mwc-list/mwc-list-item';
11+
12+
import { newIEDSampledValuesSubscriptionEvent, SubscribeStatus } from '../foundation.js';
13+
14+
@customElement('ied-element')
15+
export class IEDElement extends LitElement {
16+
/** Holding the IED element */
17+
@property({ attribute: false })
18+
element!: Element;
19+
20+
@property({ attribute: false })
21+
status!: SubscribeStatus;
22+
23+
private onIedSelect = () => {
24+
this.dispatchEvent(
25+
newIEDSampledValuesSubscriptionEvent(
26+
this.element,
27+
this.status ?? SubscribeStatus.None
28+
)
29+
);
30+
};
31+
32+
render(): TemplateResult {
33+
return html`<mwc-list-item
34+
@click=${this.onIedSelect}
35+
graphic="avatar"
36+
hasMeta
37+
>
38+
<span>${this.element.getAttribute('name')}</span>
39+
<mwc-icon slot="graphic"
40+
>${this.status == SubscribeStatus.Full
41+
? html`clear`
42+
: html`add`}</mwc-icon
43+
>
44+
</mwc-list-item>`;
45+
}
46+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import {
2+
customElement,
3+
html,
4+
LitElement,
5+
property,
6+
TemplateResult,
7+
} from 'lit-element';
8+
9+
import '@material/mwc-icon';
10+
import '@material/mwc-list/mwc-list-item';
11+
12+
import { newSampledValuesSelectEvent } from '../foundation.js';
13+
import { smvIcon } from '../../../icons/icons.js';
14+
15+
@customElement('sampled-values-message')
16+
export class SampledValuesMessage extends LitElement {
17+
/** Holding the SampledValueControl element */
18+
@property({ attribute: false })
19+
element!: Element;
20+
21+
private onSampledValuesSelect = () => {
22+
const ln = this.element.parentElement;
23+
const dataset = ln?.querySelector(
24+
`DataSet[name=${this.element.getAttribute('datSet')}]`
25+
);
26+
this.dispatchEvent(
27+
newSampledValuesSelectEvent(
28+
this.element,
29+
dataset!
30+
)
31+
);
32+
};
33+
34+
render(): TemplateResult {
35+
return html`<mwc-list-item @click=${this.onSampledValuesSelect} graphic="large">
36+
<span>${this.element.getAttribute('name')}</span>
37+
<mwc-icon slot="graphic">${smvIcon}</mwc-icon>
38+
</mwc-list-item>`;
39+
}
40+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import { css } from 'lit-element';
2+
3+
/**
4+
* Enumeration stating the Subscribe status of a IED to a GOOSE.
5+
*/
6+
export enum SubscribeStatus {
7+
Full,
8+
Partial,
9+
None,
10+
}
11+
12+
export interface SampledValuesSelectDetail {
13+
sampledValuesControl: Element;
14+
dataset: Element;
15+
}
16+
export type SampledValuesSelectEvent = CustomEvent<SampledValuesSelectDetail>;
17+
export function newSampledValuesSelectEvent(
18+
sampledValuesControl: Element,
19+
dataset: Element,
20+
eventInitDict?: CustomEventInit<SampledValuesSelectDetail>
21+
): SampledValuesSelectEvent {
22+
return new CustomEvent<SampledValuesSelectDetail>('sampled-values-select', {
23+
bubbles: true,
24+
composed: true,
25+
...eventInitDict,
26+
detail: { sampledValuesControl, dataset, ...eventInitDict?.detail },
27+
});
28+
}
29+
30+
export interface IEDSampledValuesSubscriptionDetail {
31+
ied: Element;
32+
subscribeStatus: SubscribeStatus;
33+
}
34+
export type IEDSampledValuesSubscriptionEvent = CustomEvent<IEDSampledValuesSubscriptionDetail>;
35+
export function newIEDSampledValuesSubscriptionEvent(
36+
ied: Element,
37+
subscribeStatus: SubscribeStatus
38+
): IEDSampledValuesSubscriptionEvent {
39+
return new CustomEvent<IEDSampledValuesSubscriptionDetail>('ied-smv-subscription', {
40+
bubbles: true,
41+
composed: true,
42+
detail: { ied, subscribeStatus },
43+
});
44+
}
45+
46+
/** Common `CSS` styles used by DataTypeTemplate subeditors */
47+
export const styles = css`
48+
:host(.moving) section {
49+
opacity: 0.3;
50+
}
51+
52+
section {
53+
background-color: var(--mdc-theme-surface);
54+
transition: all 200ms linear;
55+
outline-color: var(--mdc-theme-primary);
56+
outline-style: solid;
57+
outline-width: 0px;
58+
opacity: 1;
59+
}
60+
61+
section:focus {
62+
box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14),
63+
0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2);
64+
}
65+
66+
section:focus-within {
67+
outline-width: 2px;
68+
transition: all 250ms linear;
69+
}
70+
71+
h1,
72+
h2,
73+
h3 {
74+
color: var(--mdc-theme-on-surface);
75+
font-family: 'Roboto', sans-serif;
76+
font-weight: 300;
77+
overflow: hidden;
78+
white-space: nowrap;
79+
text-overflow: ellipsis;
80+
margin: 0px;
81+
line-height: 48px;
82+
padding-left: 0.3em;
83+
transition: background-color 150ms linear;
84+
}
85+
86+
section:focus-within > h1,
87+
section:focus-within > h2,
88+
section:focus-within > h3 {
89+
color: var(--mdc-theme-surface);
90+
background-color: var(--mdc-theme-primary);
91+
transition: background-color 200ms linear;
92+
}
93+
94+
h1 > nav,
95+
h2 > nav,
96+
h3 > nav,
97+
h1 > abbr > mwc-icon-button,
98+
h2 > abbr > mwc-icon-button,
99+
h3 > abbr > mwc-icon-button {
100+
float: right;
101+
}
102+
103+
abbr[title] {
104+
border-bottom: none !important;
105+
cursor: inherit !important;
106+
text-decoration: none !important;
107+
}
108+
`;
109+
110+
declare global {
111+
interface ElementEventMap {
112+
['sampled-values-select']: SampledValuesSelectEvent;
113+
['ied-smv-subscription']: IEDSampledValuesSubscriptionEvent;
114+
}
115+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import {
2+
css,
3+
customElement,
4+
html,
5+
LitElement,
6+
property,
7+
TemplateResult,
8+
} from 'lit-element';
9+
import { translate } from 'lit-translate';
10+
11+
import '@material/mwc-icon';
12+
import '@material/mwc-list';
13+
import '@material/mwc-list/mwc-list-item';
14+
15+
import './elements/sampled-values-message.js';
16+
import { compareNames, getNameAttribute } from '../../foundation.js';
17+
import { styles } from './foundation.js';
18+
19+
/** An sub element for showing all Sampled Values per IED. */
20+
@customElement('sampled-values-list')
21+
export class SampledValuesList extends LitElement {
22+
@property({ attribute: false })
23+
doc!: XMLDocument;
24+
25+
private get ieds(): Element[] {
26+
return this.doc
27+
? Array.from(this.doc.querySelectorAll(':root > IED')).sort((a, b) =>
28+
compareNames(a, b)
29+
)
30+
: [];
31+
}
32+
33+
/**
34+
* Get all the Sampled Values.
35+
* @param ied - The IED to search through.
36+
* @returns All the Sampled Values of this specific IED.
37+
*/
38+
private getSampledValuesControls(ied: Element): Element[] {
39+
return Array.from(
40+
ied.querySelectorAll(
41+
':scope > AccessPoint > Server > LDevice > LN0 > SampledValueControl'
42+
)
43+
);
44+
}
45+
46+
render(): TemplateResult {
47+
return html` <section>
48+
<h1>${translate('sampledvalues.sampledValuesList.title')}</h1>
49+
<mwc-list>
50+
${this.ieds.map(ied =>
51+
ied.querySelector('SampledValueControl')
52+
? html`
53+
<mwc-list-item noninteractive graphic="icon">
54+
<span class="iedListTitle">${getNameAttribute(ied)}</span>
55+
<mwc-icon slot="graphic">developer_board</mwc-icon>
56+
</mwc-list-item>
57+
<li divider role="separator"></li>
58+
${this.getSampledValuesControls(ied).map(
59+
control =>
60+
html`<sampled-values-message .element=${control}></sampled-values-message>`
61+
)}
62+
`
63+
: ``
64+
)}
65+
</mwc-list>
66+
</section>`;
67+
}
68+
69+
static styles = css`
70+
${styles}
71+
72+
mwc-list {
73+
height: 100vh;
74+
overflow-y: scroll;
75+
}
76+
77+
.iedListTitle {
78+
font-weight: bold;
79+
}
80+
`;
81+
}

0 commit comments

Comments
 (0)