|
| 1 | +import { |
| 2 | + css, |
| 3 | + customElement, |
| 4 | + html, |
| 5 | + LitElement, |
| 6 | + property, |
| 7 | + state, |
| 8 | + TemplateResult, |
| 9 | +} from 'lit-element'; |
| 10 | +import { nothing } from 'lit-html'; |
| 11 | +import { translate } from 'lit-translate'; |
| 12 | + |
| 13 | +import { |
| 14 | + compareNames, |
| 15 | + getDescriptionAttribute, |
| 16 | + getNameAttribute, |
| 17 | + identity, |
| 18 | +} from '../../../foundation.js'; |
| 19 | + |
| 20 | +import { styles } from '../foundation.js'; |
| 21 | +import { FcdaSelectEvent, getFcdaTitleValue } from './foundation.js'; |
| 22 | + |
| 23 | +/** |
| 24 | + * A sub element for showing all Ext Refs from a FCDA Element. |
| 25 | + * The List reacts on a custom event to know which FCDA Element was selected and updated the view. |
| 26 | + */ |
| 27 | +@customElement('extref-later-binding-list') |
| 28 | +export class ExtRefLaterBindingList extends LitElement { |
| 29 | + @property({ attribute: false }) |
| 30 | + doc!: XMLDocument; |
| 31 | + |
| 32 | + @state() |
| 33 | + currentSelectedSvcElement: Element | undefined; |
| 34 | + @state() |
| 35 | + currentSelectedFcdaElement: Element | undefined; |
| 36 | + @state() |
| 37 | + currentIedElement: Element | undefined; |
| 38 | + |
| 39 | + constructor() { |
| 40 | + super(); |
| 41 | + this.onFcdaSelectEvent = this.onFcdaSelectEvent.bind(this); |
| 42 | + |
| 43 | + const parentDiv = this.closest('.container'); |
| 44 | + if (parentDiv) { |
| 45 | + parentDiv.addEventListener('fcda-select', this.onFcdaSelectEvent); |
| 46 | + } |
| 47 | + } |
| 48 | + |
| 49 | + private getExtRefElements(): Element[] { |
| 50 | + if (this.doc) { |
| 51 | + return Array.from(this.doc.querySelectorAll('ExtRef')) |
| 52 | + .filter(element => element.hasAttribute('intAddr')) |
| 53 | + .filter(element => element.closest('IED') !== this.currentIedElement) |
| 54 | + .sort((a, b) => |
| 55 | + compareNames( |
| 56 | + `${a.getAttribute('intAddr')}`, |
| 57 | + `${b.getAttribute('intAddr')}` |
| 58 | + ) |
| 59 | + ); |
| 60 | + } |
| 61 | + return []; |
| 62 | + } |
| 63 | + |
| 64 | + private getSubscribedExtRefElements(): Element[] { |
| 65 | + return this.getExtRefElements().filter(element => |
| 66 | + this.isSubscribedTo(element) |
| 67 | + ); |
| 68 | + } |
| 69 | + |
| 70 | + private getAvailableExtRefElements(): Element[] { |
| 71 | + return this.getExtRefElements().filter( |
| 72 | + element => !this.isSubscribed(element) |
| 73 | + ); |
| 74 | + } |
| 75 | + |
| 76 | + private async onFcdaSelectEvent(event: FcdaSelectEvent) { |
| 77 | + this.currentSelectedSvcElement = event.detail.svc; |
| 78 | + this.currentSelectedFcdaElement = event.detail.fcda; |
| 79 | + |
| 80 | + // Retrieve the IED Element to which the FCDA belongs. |
| 81 | + // These ExtRef Elements will be excluded. |
| 82 | + this.currentIedElement = this.currentSelectedFcdaElement |
| 83 | + ? this.currentSelectedFcdaElement.closest('IED') ?? undefined |
| 84 | + : undefined; |
| 85 | + } |
| 86 | + |
| 87 | + private sameAttributeValue( |
| 88 | + extRefElement: Element, |
| 89 | + attributeName: string |
| 90 | + ): boolean { |
| 91 | + return ( |
| 92 | + extRefElement.getAttribute(attributeName) === |
| 93 | + this.currentSelectedFcdaElement?.getAttribute(attributeName) |
| 94 | + ); |
| 95 | + } |
| 96 | + |
| 97 | + /** |
| 98 | + * Check if specific attributes from the ExtRef Element are the same as the ones from the FCDA Element |
| 99 | + * and also if the IED Name is the same. If that is the case this ExtRef subscribes to the selected FCDA |
| 100 | + * Element. |
| 101 | + * |
| 102 | + * @param extRefElement - The Ext Ref Element to check. |
| 103 | + */ |
| 104 | + private isSubscribedTo(extRefElement: Element): boolean { |
| 105 | + return ( |
| 106 | + extRefElement.getAttribute('iedName') === |
| 107 | + this.currentIedElement?.getAttribute('name') && |
| 108 | + this.sameAttributeValue(extRefElement, 'ldInst') && |
| 109 | + this.sameAttributeValue(extRefElement, 'prefix') && |
| 110 | + this.sameAttributeValue(extRefElement, 'lnClass') && |
| 111 | + this.sameAttributeValue(extRefElement, 'lnInst') && |
| 112 | + this.sameAttributeValue(extRefElement, 'doName') && |
| 113 | + this.sameAttributeValue(extRefElement, 'daName') |
| 114 | + ); |
| 115 | + } |
| 116 | + |
| 117 | + /** |
| 118 | + * Check if the ExtRef is already subscribed to a FCDA Element. |
| 119 | + * |
| 120 | + * @param extRefElement - The Ext Ref Element to check. |
| 121 | + */ |
| 122 | + private isSubscribed(extRefElement: Element): boolean { |
| 123 | + return ( |
| 124 | + extRefElement.hasAttribute('iedName') && |
| 125 | + extRefElement.hasAttribute('ldInst') && |
| 126 | + extRefElement.hasAttribute('prefix') && |
| 127 | + extRefElement.hasAttribute('lnClass') && |
| 128 | + extRefElement.hasAttribute('lnInst') && |
| 129 | + extRefElement.hasAttribute('doName') && |
| 130 | + extRefElement.hasAttribute('daName') |
| 131 | + ); |
| 132 | + } |
| 133 | + |
| 134 | + /** |
| 135 | + * The data attribute check using attributes pLN, pDO, pDA and pServT is not supported yet by this plugin. |
| 136 | + * To make sure the user does not do anything prohibited, this type of ExtRef cannot be manipulated for the time being. |
| 137 | + * (Will be updated in the future). |
| 138 | + * |
| 139 | + * @param extRefElement - The Ext Ref Element to check. |
| 140 | + */ |
| 141 | + private unsupportedExtRefElement(extRefElement: Element): boolean { |
| 142 | + return ( |
| 143 | + extRefElement.hasAttribute('pLN') || |
| 144 | + extRefElement.hasAttribute('pDO') || |
| 145 | + extRefElement.hasAttribute('pDA') || |
| 146 | + extRefElement.hasAttribute('pServT') |
| 147 | + ); |
| 148 | + } |
| 149 | + |
| 150 | + private renderTitle(): TemplateResult { |
| 151 | + const svcName = this.currentSelectedSvcElement |
| 152 | + ? getNameAttribute(this.currentSelectedSvcElement) |
| 153 | + : undefined; |
| 154 | + const fcdaName = this.currentSelectedFcdaElement |
| 155 | + ? getFcdaTitleValue(this.currentSelectedFcdaElement) |
| 156 | + : undefined; |
| 157 | + |
| 158 | + return html`<h1> |
| 159 | + ${translate('subscription.smvLaterBinding.extRefList.title', { |
| 160 | + svcName: svcName ?? '-', |
| 161 | + fcdaName: fcdaName ?? '-', |
| 162 | + })} |
| 163 | + </h1>`; |
| 164 | + } |
| 165 | + |
| 166 | + private renderSubscribedExtRefs(): TemplateResult { |
| 167 | + const subscribedExtRefs = this.getSubscribedExtRefElements(); |
| 168 | + return html` |
| 169 | + <mwc-list-item |
| 170 | + noninteractive |
| 171 | + value="${subscribedExtRefs |
| 172 | + .map( |
| 173 | + extRefElement => |
| 174 | + getDescriptionAttribute(extRefElement) + |
| 175 | + ' ' + |
| 176 | + identity(extRefElement) |
| 177 | + ) |
| 178 | + .join(' ')}" |
| 179 | + > |
| 180 | + <span>${translate('subscription.subscriber.subscribed')}</span> |
| 181 | + </mwc-list-item> |
| 182 | + <li divider role="separator"></li> |
| 183 | + ${subscribedExtRefs.length > 0 |
| 184 | + ? html`${subscribedExtRefs.map( |
| 185 | + extRefElement => html` <mwc-list-item |
| 186 | + graphic="large" |
| 187 | + twoline |
| 188 | + value="${identity(extRefElement)}" |
| 189 | + > |
| 190 | + <span> |
| 191 | + ${extRefElement.getAttribute('intAddr')} |
| 192 | + ${getDescriptionAttribute(extRefElement) |
| 193 | + ? html` (${getDescriptionAttribute(extRefElement)})` |
| 194 | + : nothing} |
| 195 | + </span> |
| 196 | + <span slot="secondary">${identity(extRefElement)}</span> |
| 197 | + <mwc-icon slot="graphic">swap_horiz</mwc-icon> |
| 198 | + </mwc-list-item>` |
| 199 | + )}` |
| 200 | + : html`<mwc-list-item graphic="large" noninteractive> |
| 201 | + ${translate( |
| 202 | + 'subscription.smvLaterBinding.extRefList.noSubscribedExtRefs' |
| 203 | + )} |
| 204 | + </mwc-list-item>`} |
| 205 | + `; |
| 206 | + } |
| 207 | + |
| 208 | + private renderAvailableExtRefs(): TemplateResult { |
| 209 | + const availableExtRefs = this.getAvailableExtRefElements(); |
| 210 | + return html` |
| 211 | + <mwc-list-item |
| 212 | + noninteractive |
| 213 | + value="${availableExtRefs |
| 214 | + .map( |
| 215 | + extRefElement => |
| 216 | + getDescriptionAttribute(extRefElement) + |
| 217 | + ' ' + |
| 218 | + identity(extRefElement) |
| 219 | + ) |
| 220 | + .join(' ')}" |
| 221 | + > |
| 222 | + <span> |
| 223 | + ${translate('subscription.subscriber.availableToSubscribe')} |
| 224 | + </span> |
| 225 | + </mwc-list-item> |
| 226 | + <li divider role="separator"></li> |
| 227 | + ${availableExtRefs.length > 0 |
| 228 | + ? html`${availableExtRefs.map( |
| 229 | + extRefElement => html` <mwc-list-item |
| 230 | + graphic="large" |
| 231 | + ?disabled=${this.unsupportedExtRefElement(extRefElement)} |
| 232 | + twoline |
| 233 | + value="${identity(extRefElement)}" |
| 234 | + > |
| 235 | + <span> |
| 236 | + ${extRefElement.getAttribute('intAddr')} |
| 237 | + ${getDescriptionAttribute(extRefElement) |
| 238 | + ? html` (${getDescriptionAttribute(extRefElement)})` |
| 239 | + : nothing} |
| 240 | + </span> |
| 241 | + <span slot="secondary">${identity(extRefElement)}</span> |
| 242 | + <mwc-icon slot="graphic">arrow_back</mwc-icon> |
| 243 | + </mwc-list-item>` |
| 244 | + )}` |
| 245 | + : html`<mwc-list-item graphic="large" noninteractive> |
| 246 | + ${translate( |
| 247 | + 'subscription.smvLaterBinding.extRefList.noAvailableExtRefs' |
| 248 | + )} |
| 249 | + </mwc-list-item>`} |
| 250 | + `; |
| 251 | + } |
| 252 | + |
| 253 | + render(): TemplateResult { |
| 254 | + return html` <section tabindex="0"> |
| 255 | + ${this.currentSelectedSvcElement && this.currentSelectedFcdaElement |
| 256 | + ? html` |
| 257 | + ${this.renderTitle()} |
| 258 | + <filtered-list> |
| 259 | + ${this.renderSubscribedExtRefs()} ${this.renderAvailableExtRefs()} |
| 260 | + </filtered-list> |
| 261 | + ` |
| 262 | + : html` |
| 263 | + <h1> |
| 264 | + ${translate( |
| 265 | + 'subscription.smvLaterBinding.extRefList.noSelection' |
| 266 | + )} |
| 267 | + </h1> |
| 268 | + `} |
| 269 | + </section>`; |
| 270 | + } |
| 271 | + |
| 272 | + static styles = css` |
| 273 | + ${styles} |
| 274 | +
|
| 275 | + mwc-list-item.hidden[noninteractive] + li[divider] { |
| 276 | + display: none; |
| 277 | + } |
| 278 | + `; |
| 279 | +} |
0 commit comments