From 45471454a3a284fd6aa788173fbc4347d630293a Mon Sep 17 00:00:00 2001 From: Illia Solovei Date: Fri, 15 Nov 2024 15:36:50 +0100 Subject: [PATCH 1/8] bugfix: fix getDescription logic to include all lnodes --- .../plugins/src/menu/VirtualTemplateIED.ts | 486 +++++++++--------- 1 file changed, 247 insertions(+), 239 deletions(-) diff --git a/packages/plugins/src/menu/VirtualTemplateIED.ts b/packages/plugins/src/menu/VirtualTemplateIED.ts index 077e567f48..2e09e4db6e 100644 --- a/packages/plugins/src/menu/VirtualTemplateIED.ts +++ b/packages/plugins/src/menu/VirtualTemplateIED.ts @@ -1,270 +1,278 @@ import { - css, - html, - LitElement, - property, - query, - queryAll, - state, - TemplateResult, -} from 'lit-element'; -import { get } from 'lit-translate'; - -import '@material/mwc-dialog'; -import '@material/mwc-list'; -import '@material/mwc-list/mwc-list-item'; -import '@material/mwc-list/mwc-check-list-item'; -import '@material/mwc-list/mwc-radio-list-item'; -import { Dialog } from '@material/mwc-dialog'; -import { CheckListItem } from '@material/mwc-list/mwc-check-list-item'; -import { Select } from '@material/mwc-select'; - -import '@openscd/open-scd/src/filtered-list.js'; -import { find, identity } from '@openscd/open-scd/src/foundation.js'; -import { getChildElementsByTagName } from '@openscd/xml'; - -import { newActionEvent } from '@openscd/core/foundation/deprecated/editor.js'; -import { WizardTextField } from '@openscd/open-scd/src/wizard-textfield.js'; + css, + html, + LitElement, + property, + query, + queryAll, + state, + TemplateResult, +} from "lit-element"; +import { get } from "lit-translate"; + +import "@material/mwc-dialog"; +import "@material/mwc-list"; +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-list/mwc-check-list-item"; +import "@material/mwc-list/mwc-radio-list-item"; +import { Dialog } from "@material/mwc-dialog"; +import { CheckListItem } from "@material/mwc-list/mwc-check-list-item"; +import { Select } from "@material/mwc-select"; + +import "@openscd/open-scd/src/filtered-list.js"; +import { find, identity } from "@openscd/open-scd/src/foundation.js"; +import { getChildElementsByTagName } from "@openscd/xml"; + +import { newActionEvent } from "@openscd/core/foundation/deprecated/editor.js"; +import { WizardTextField } from "@openscd/open-scd/src/wizard-textfield.js"; import { - getFunctionNamingPrefix, - getNonLeafParent, - getSpecificationIED, - getUniqueFunctionName, - LDeviceDescription, -} from './virtualtemplateied/foundation.js'; + getFunctionNamingPrefix, + getNonLeafParent, + getSpecificationIED, + getUniqueFunctionName, + LDeviceDescription, +} from "./virtualtemplateied/foundation.js"; export type FunctionElementDescription = { - uniqueName: string; - lNodes: Element[]; - lln0?: Element; + uniqueName: string; + lNodes: Element[]; + lln0?: Element; }; /** converts FunctionElementDescription's to LDeviceDescription's */ function getLDeviceDescriptions( - functions: Record, - selectedLNodes: Element[], - selectedLLN0s: string[] + functions: Record, + selectedLNodes: Element[], + selectedLLN0s: string[], ): LDeviceDescription[] { - const lDeviceDescriptions: LDeviceDescription[] = []; - - Object.values(functions).forEach(functionDescription => { - if ( - functionDescription.lNodes.some(lNode => selectedLNodes.includes(lNode)) - ) { - const lLN0 = selectedLLN0s.find(selectedLLN0 => - selectedLLN0.includes(functionDescription.uniqueName) - )!; - const lnType = lLN0?.split(': ')[1]; - - lDeviceDescriptions.push({ - validLdInst: functionDescription.uniqueName, - anyLNs: [ - { prefix: null, lnClass: 'LLN0', inst: '', lnType }, - ...functionDescription.lNodes - .filter(lNode => selectedLNodes.includes(lNode)) - .map(lNode => { - return { - prefix: getFunctionNamingPrefix(lNode), - lnClass: lNode.getAttribute('lnClass')!, - inst: lNode.getAttribute('lnInst')!, - lnType: lNode.getAttribute('lnType')!, - }; - }), - ], - }); - } - }); - - return lDeviceDescriptions; + const lDeviceDescriptions: LDeviceDescription[] = []; + + console.log("LDeviceDescriptions input - selectedLNodes: ", selectedLNodes); + + Object.values(functions).forEach((functionDescription) => { + if ( + functionDescription.lNodes.some((lNode) => selectedLNodes.includes(lNode)) + ) { + const lLN0 = selectedLLN0s.find((selectedLLN0) => + selectedLLN0.includes(functionDescription.uniqueName), + )!; + const lnType = lLN0?.split(": ")[1]; + + const anyLNs = [ + { prefix: null, lnClass: "LLN0", inst: "", lnType }, + ...selectedLNodes.map((lNode, index) => ({ + prefix: `CSWI-${index + 1}`, // Assigns a unique prefix based on the index + lnClass: lNode.getAttribute("lnClass")!, + inst: lNode.getAttribute("lnInst")!, + lnType: lNode.getAttribute("lnType")!, + })), + ]; + + lDeviceDescriptions.push({ + validLdInst: functionDescription.uniqueName, + anyLNs, + }); + } + }); + + console.log( + "AFTER LDeviceDescriptions input - selectedLNodes: ", + lDeviceDescriptions, + ); + + return lDeviceDescriptions; } /** Groups all incomming LNode's with non-leaf parent function type elements */ function groupLNodesToFunctions( - lNodes: Element[] + lNodes: Element[], ): Record { - const functionElements: Record = {}; - - lNodes.forEach(lNode => { - const parentFunction = getNonLeafParent(lNode); - if (!parentFunction) return; - - if (functionElements[identity(parentFunction)]) - functionElements[identity(parentFunction)].lNodes.push(lNode); - else { - functionElements[identity(parentFunction)] = { - uniqueName: getUniqueFunctionName(parentFunction), - lNodes: [lNode], - lln0: getChildElementsByTagName(parentFunction, 'LNode').find( - lNode => lNode.getAttribute('lnClass') === 'LLN0' - ), - }; - } - }); - - return functionElements; + const functionElements: Record = {}; + + lNodes.forEach((lNode) => { + const parentFunction = getNonLeafParent(lNode); + if (!parentFunction) return; + + if (functionElements[identity(parentFunction)]) + functionElements[identity(parentFunction)].lNodes.push(lNode); + else { + functionElements[identity(parentFunction)] = { + uniqueName: getUniqueFunctionName(parentFunction), + lNodes: [lNode], + lln0: getChildElementsByTagName(parentFunction, "LNode").find( + (lNode) => lNode.getAttribute("lnClass") === "LLN0", + ), + }; + } + }); + + return functionElements; } export default class VirtualTemplateIED extends LitElement { - @property({ attribute: false }) - doc!: XMLDocument; - @property({ type: Number }) - editCount = -1; - @state() - get isValidManufacturer(): boolean { - const manufacturer = this.dialog?.querySelector( - 'wizard-textfield[label="manufacturer"]' - )!.value; - - return (manufacturer && manufacturer !== '') || false; - } - @state() - get isValidApName(): boolean { - const apName = this.dialog?.querySelector( - 'wizard-textfield[label="AccessPoint name"]' - )!.value; - - return (apName && apName !== '') || false; - } - @state() - get someItemsSelected(): boolean { - if (!this.selectedLNodeItems) return false; - return !!this.selectedLNodeItems.length; - } - @state() - get validPriparyAction(): boolean { - return ( - this.someItemsSelected && this.isValidManufacturer && this.isValidApName - ); - } - - get unreferencedLNodes(): Element[] { - return Array.from( - this.doc.querySelectorAll('LNode[iedName="None"]') - ).filter(lNode => lNode.getAttribute('lnClass') !== 'LLN0'); - } - - get lLN0s(): Element[] { - return Array.from(this.doc.querySelectorAll('LNodeType[lnClass="LLN0"]')); - } - - @query('mwc-dialog') dialog!: Dialog; - @queryAll('mwc-check-list-item[selected]') - selectedLNodeItems?: CheckListItem[]; - - async run(): Promise { - this.dialog.open = true; - } - - private onPrimaryAction( - functions: Record - ): void { - const selectedLNode = Array.from( - this.dialog.querySelectorAll( - 'mwc-check-list-item[selected]:not([disabled])' - ) ?? [] - ).map(selectedItem => find(this.doc, 'LNode', selectedItem.value)!); - if (!selectedLNode.length) return; - - const selectedLLN0s = Array.from( - this.dialog.querySelectorAll("mwc-select") ?? [], + ).map((selectedItem) => selectedItem.value); + + const manufacturer = this.dialog.querySelector( + 'wizard-textfield[label="manufacturer"]', + )!.value; + const desc = this.dialog.querySelector( + 'wizard-textfield[label="desc"]', + )!.maybeValue; + const apName = this.dialog.querySelector( + 'wizard-textfield[label="AccessPoint name"]', + )!.value; + + const ied = getSpecificationIED(this.doc, { + manufacturer, + desc, + apName, + lDevices: getLDeviceDescriptions(functions, selectedLNode, selectedLLN0s), + }); + + // checkValidity: () => true disables name check as is the same here: SPECIFICATION + this.dispatchEvent( + newActionEvent({ + new: { parent: this.doc.documentElement, element: ied }, + checkValidity: () => true, + }), + ); + this.dialog.close(); + } + + private onClosed(ae: CustomEvent<{ action: string } | null>): void { + if (!(ae.target instanceof Dialog && ae.detail?.action)) return; + } + + private renderLLN0s( + functionID: string, + lLN0Types: Element[], + lNode?: Element, + ): TemplateResult { + if (!lNode && !lLN0Types.length) return html``; + + if (lNode) + return html`${html`${lNode.getAttribute('lnType')} + value="${functionID + ": " + lNode.getAttribute("lnType")}" + >${lNode.getAttribute("lnType")} `}`; - return html`${lLN0Types.map(lLN0Type => { - return html`${lLN0Type.getAttribute('id')}${lLN0Types.map((lLN0Type) => { + return html`${lLN0Type.getAttribute("id")}`; - })}`; - } + } - private renderLNodes(lNodes: Element[], disabled: boolean): TemplateResult[] { - return lNodes.map(lNode => { - const prefix = getFunctionNamingPrefix(lNode); - const lnClass = lNode.getAttribute('lnClass')!; - const lnInst = lNode.getAttribute('lnInst')!; + private renderLNodes(lNodes: Element[], disabled: boolean): TemplateResult[] { + return lNodes.map((lNode) => { + const prefix = getFunctionNamingPrefix(lNode); + const lnClass = lNode.getAttribute("lnClass")!; + const lnInst = lNode.getAttribute("lnInst")!; - const label = prefix + ' ' + lnClass + ' ' + lnInst; - return html`${label}`; - }); - } + }); + } - render(): TemplateResult { - if (!this.doc) return html``; + render(): TemplateResult { + if (!this.doc) return html``; - const existValidLLN0 = this.lLN0s.length !== 0; + const existValidLLN0 = this.lLN0s.length !== 0; - const functionElementDescriptions = groupLNodesToFunctions( - this.unreferencedLNodes - ); + const functionElementDescriptions = groupLNodesToFunctions( + this.unreferencedLNodes, + ); - return html`
this.requestUpdate()} > @@ -275,52 +283,52 @@ export default class VirtualTemplateIED extends LitElement { > this.requestUpdate()} > this.requestUpdate()} >${Object.entries(functionElementDescriptions).flatMap( - ([id, functionDescription]) => [ - html` [ + html`${functionDescription.uniqueName}${existValidLLN0 ? id : 'Invalid LD: Missing LLN0'}${existValidLLN0 ? id : "Invalid LD: Missing LLN0"}`, - this.renderLLN0s( - functionDescription.uniqueName, - this.lLN0s, - functionDescription.lln0 - ), - ...this.renderLNodes(functionDescription.lNodes, !existValidLLN0), - html`
  • `, - ] - )}
    `, + ], + )}
    this.onPrimaryAction(functionElementDescriptions)} >
    `; - } + } - static styles = css` + static styles = css` mwc-dialog { --mdc-dialog-max-width: 92vw; } From 1ea983253c260e89900e515e83b135152303aeea Mon Sep 17 00:00:00 2001 From: Illia Solovei Date: Mon, 18 Nov 2024 14:18:00 +0100 Subject: [PATCH 2/8] bugfix: render prefix logic rework --- .../plugins/src/menu/VirtualTemplateIED.ts | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/plugins/src/menu/VirtualTemplateIED.ts b/packages/plugins/src/menu/VirtualTemplateIED.ts index 2e09e4db6e..0d942af82d 100644 --- a/packages/plugins/src/menu/VirtualTemplateIED.ts +++ b/packages/plugins/src/menu/VirtualTemplateIED.ts @@ -58,14 +58,25 @@ function getLDeviceDescriptions( )!; const lnType = lLN0?.split(": ")[1]; + // Map to ensure unique prefixes based on node attributes const anyLNs = [ { prefix: null, lnClass: "LLN0", inst: "", lnType }, - ...selectedLNodes.map((lNode, index) => ({ - prefix: `CSWI-${index + 1}`, // Assigns a unique prefix based on the index - lnClass: lNode.getAttribute("lnClass")!, - inst: lNode.getAttribute("lnInst")!, - lnType: lNode.getAttribute("lnType")!, - })), + ...selectedLNodes.map((lNode, index) => { + const lnClass = lNode.getAttribute("lnClass")!; + const inst = lNode.getAttribute("lnInst")!; + const lnType = lNode.getAttribute("lnType")!; + const existingPrefix = lNode.getAttribute("prefix") || ""; + + // Generate unique prefix if not already provided + const uniquePrefix = existingPrefix || `${lnClass}-${index + 1}`; // Prefix includes class and unique index + + return { + prefix: uniquePrefix, + lnClass, + inst, + lnType, + }; + }), ]; lDeviceDescriptions.push({ From a0f6ddd8348ea224d8b3ad334187ec419c2884a0 Mon Sep 17 00:00:00 2001 From: Illia Solovei Date: Mon, 18 Nov 2024 14:27:54 +0100 Subject: [PATCH 3/8] bugfix: simplify the logic --- packages/plugins/src/menu/VirtualTemplateIED.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/plugins/src/menu/VirtualTemplateIED.ts b/packages/plugins/src/menu/VirtualTemplateIED.ts index 0d942af82d..46b6c0fe64 100644 --- a/packages/plugins/src/menu/VirtualTemplateIED.ts +++ b/packages/plugins/src/menu/VirtualTemplateIED.ts @@ -58,20 +58,16 @@ function getLDeviceDescriptions( )!; const lnType = lLN0?.split(": ")[1]; - // Map to ensure unique prefixes based on node attributes const anyLNs = [ { prefix: null, lnClass: "LLN0", inst: "", lnType }, ...selectedLNodes.map((lNode, index) => { const lnClass = lNode.getAttribute("lnClass")!; const inst = lNode.getAttribute("lnInst")!; const lnType = lNode.getAttribute("lnType")!; - const existingPrefix = lNode.getAttribute("prefix") || ""; - - // Generate unique prefix if not already provided - const uniquePrefix = existingPrefix || `${lnClass}-${index + 1}`; // Prefix includes class and unique index + const prefix = lNode.getAttribute("prefix") || ""; return { - prefix: uniquePrefix, + prefix: prefix || `CSWI-${index}`, lnClass, inst, lnType, From 217e86b1b56a1ee9ff8358edb9b62970872aee66 Mon Sep 17 00:00:00 2001 From: Illia Solovei Date: Mon, 18 Nov 2024 14:39:43 +0100 Subject: [PATCH 4/8] bugfix: bring back the use of getFunctionNamingPrefix function --- packages/plugins/src/menu/VirtualTemplateIED.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/packages/plugins/src/menu/VirtualTemplateIED.ts b/packages/plugins/src/menu/VirtualTemplateIED.ts index 46b6c0fe64..9506630d75 100644 --- a/packages/plugins/src/menu/VirtualTemplateIED.ts +++ b/packages/plugins/src/menu/VirtualTemplateIED.ts @@ -60,17 +60,13 @@ function getLDeviceDescriptions( const anyLNs = [ { prefix: null, lnClass: "LLN0", inst: "", lnType }, - ...selectedLNodes.map((lNode, index) => { - const lnClass = lNode.getAttribute("lnClass")!; - const inst = lNode.getAttribute("lnInst")!; - const lnType = lNode.getAttribute("lnType")!; - const prefix = lNode.getAttribute("prefix") || ""; - + ...selectedLNodes.map((lNode) => { + console.log("here: ", getFunctionNamingPrefix(lNode)); return { - prefix: prefix || `CSWI-${index}`, - lnClass, - inst, - lnType, + prefix: getFunctionNamingPrefix(lNode), + lnClass: lNode.getAttribute("lnClass")!, + inst: lNode.getAttribute("lnInst")!, + lnType: lNode.getAttribute("lnType")!, }; }), ]; From c71cc40ee3aaa05ea2fe1f5f76e1421b0dd170cf Mon Sep 17 00:00:00 2001 From: Illia Solovei Date: Mon, 18 Nov 2024 14:42:43 +0100 Subject: [PATCH 5/8] bugfix: re-format the file --- .../plugins/src/menu/VirtualTemplateIED.ts | 498 +++++++++--------- 1 file changed, 248 insertions(+), 250 deletions(-) diff --git a/packages/plugins/src/menu/VirtualTemplateIED.ts b/packages/plugins/src/menu/VirtualTemplateIED.ts index 9506630d75..449de7defb 100644 --- a/packages/plugins/src/menu/VirtualTemplateIED.ts +++ b/packages/plugins/src/menu/VirtualTemplateIED.ts @@ -1,281 +1,279 @@ import { - css, - html, - LitElement, - property, - query, - queryAll, - state, - TemplateResult, -} from "lit-element"; -import { get } from "lit-translate"; - -import "@material/mwc-dialog"; -import "@material/mwc-list"; -import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-list/mwc-check-list-item"; -import "@material/mwc-list/mwc-radio-list-item"; -import { Dialog } from "@material/mwc-dialog"; -import { CheckListItem } from "@material/mwc-list/mwc-check-list-item"; -import { Select } from "@material/mwc-select"; - -import "@openscd/open-scd/src/filtered-list.js"; -import { find, identity } from "@openscd/open-scd/src/foundation.js"; -import { getChildElementsByTagName } from "@openscd/xml"; - -import { newActionEvent } from "@openscd/core/foundation/deprecated/editor.js"; -import { WizardTextField } from "@openscd/open-scd/src/wizard-textfield.js"; + css, + html, + LitElement, + property, + query, + queryAll, + state, + TemplateResult, +} from 'lit-element'; +import { get } from 'lit-translate'; + +import '@material/mwc-dialog'; +import '@material/mwc-list'; +import '@material/mwc-list/mwc-list-item'; +import '@material/mwc-list/mwc-check-list-item'; +import '@material/mwc-list/mwc-radio-list-item'; +import { Dialog } from '@material/mwc-dialog'; +import { CheckListItem } from '@material/mwc-list/mwc-check-list-item'; +import { Select } from '@material/mwc-select'; + +import '@openscd/open-scd/src/filtered-list.js'; +import { find, identity } from '@openscd/open-scd/src/foundation.js'; +import { getChildElementsByTagName } from '@openscd/xml'; + +import { newActionEvent } from '@openscd/core/foundation/deprecated/editor.js'; +import { WizardTextField } from '@openscd/open-scd/src/wizard-textfield.js'; import { - getFunctionNamingPrefix, - getNonLeafParent, - getSpecificationIED, - getUniqueFunctionName, - LDeviceDescription, -} from "./virtualtemplateied/foundation.js"; + getFunctionNamingPrefix, + getNonLeafParent, + getSpecificationIED, + getUniqueFunctionName, + LDeviceDescription, +} from './virtualtemplateied/foundation.js'; export type FunctionElementDescription = { - uniqueName: string; - lNodes: Element[]; - lln0?: Element; + uniqueName: string; + lNodes: Element[]; + lln0?: Element; }; /** converts FunctionElementDescription's to LDeviceDescription's */ function getLDeviceDescriptions( - functions: Record, - selectedLNodes: Element[], - selectedLLN0s: string[], + functions: Record, + selectedLNodes: Element[], + selectedLLN0s: string[], ): LDeviceDescription[] { - const lDeviceDescriptions: LDeviceDescription[] = []; - - console.log("LDeviceDescriptions input - selectedLNodes: ", selectedLNodes); - - Object.values(functions).forEach((functionDescription) => { - if ( - functionDescription.lNodes.some((lNode) => selectedLNodes.includes(lNode)) - ) { - const lLN0 = selectedLLN0s.find((selectedLLN0) => - selectedLLN0.includes(functionDescription.uniqueName), - )!; - const lnType = lLN0?.split(": ")[1]; - - const anyLNs = [ - { prefix: null, lnClass: "LLN0", inst: "", lnType }, - ...selectedLNodes.map((lNode) => { - console.log("here: ", getFunctionNamingPrefix(lNode)); - return { - prefix: getFunctionNamingPrefix(lNode), - lnClass: lNode.getAttribute("lnClass")!, - inst: lNode.getAttribute("lnInst")!, - lnType: lNode.getAttribute("lnType")!, - }; - }), - ]; - - lDeviceDescriptions.push({ - validLdInst: functionDescription.uniqueName, - anyLNs, - }); - } - }); - - console.log( - "AFTER LDeviceDescriptions input - selectedLNodes: ", - lDeviceDescriptions, - ); - - return lDeviceDescriptions; + const lDeviceDescriptions: LDeviceDescription[] = []; + + console.log("LDeviceDescriptions input - selectedLNodes: ", selectedLNodes); + + Object.values(functions).forEach((functionDescription) => { + if ( + functionDescription.lNodes.some((lNode) => selectedLNodes.includes(lNode)) + ) { + const lLN0 = selectedLLN0s.find((selectedLLN0) => + selectedLLN0.includes(functionDescription.uniqueName), + )!; + const lnType = lLN0?.split(": ")[1]; + + const anyLNs = [ + { prefix: null, lnClass: "LLN0", inst: "", lnType }, + ...selectedLNodes.map((lNode) => { + console.log("here: ", getFunctionNamingPrefix(lNode)); + return { + prefix: getFunctionNamingPrefix(lNode), + lnClass: lNode.getAttribute("lnClass")!, + inst: lNode.getAttribute("lnInst")!, + lnType: lNode.getAttribute("lnType")!, + }; + }), + ]; + + lDeviceDescriptions.push({ + validLdInst: functionDescription.uniqueName, + anyLNs, + }); + } + }); + + console.log( + "AFTER LDeviceDescriptions input - selectedLNodes: ", + lDeviceDescriptions, + ); + + return lDeviceDescriptions; } + /** Groups all incomming LNode's with non-leaf parent function type elements */ function groupLNodesToFunctions( - lNodes: Element[], + lNodes: Element[] ): Record { - const functionElements: Record = {}; - - lNodes.forEach((lNode) => { - const parentFunction = getNonLeafParent(lNode); - if (!parentFunction) return; - - if (functionElements[identity(parentFunction)]) - functionElements[identity(parentFunction)].lNodes.push(lNode); - else { - functionElements[identity(parentFunction)] = { - uniqueName: getUniqueFunctionName(parentFunction), - lNodes: [lNode], - lln0: getChildElementsByTagName(parentFunction, "LNode").find( - (lNode) => lNode.getAttribute("lnClass") === "LLN0", - ), - }; - } - }); - - return functionElements; + const functionElements: Record = {}; + + lNodes.forEach(lNode => { + const parentFunction = getNonLeafParent(lNode); + if (!parentFunction) return; + + if (functionElements[identity(parentFunction)]) + functionElements[identity(parentFunction)].lNodes.push(lNode); + else { + functionElements[identity(parentFunction)] = { + uniqueName: getUniqueFunctionName(parentFunction), + lNodes: [lNode], + lln0: getChildElementsByTagName(parentFunction, 'LNode').find( + lNode => lNode.getAttribute('lnClass') === 'LLN0' + ), + }; + } + }); + + return functionElements; } export default class VirtualTemplateIED extends LitElement { - @property({ attribute: false }) - doc!: XMLDocument; - @property({ type: Number }) - editCount = -1; - @state() - get isValidManufacturer(): boolean { - const manufacturer = this.dialog?.querySelector( - 'wizard-textfield[label="manufacturer"]', - )!.value; - - return (manufacturer && manufacturer !== "") || false; - } - @state() - get isValidApName(): boolean { - const apName = this.dialog?.querySelector( - 'wizard-textfield[label="AccessPoint name"]', - )!.value; - - return (apName && apName !== "") || false; - } - @state() - get someItemsSelected(): boolean { - if (!this.selectedLNodeItems) return false; - return !!this.selectedLNodeItems.length; - } - @state() - get validPriparyAction(): boolean { - return ( - this.someItemsSelected && this.isValidManufacturer && this.isValidApName - ); - } - - get unreferencedLNodes(): Element[] { - return Array.from( - this.doc.querySelectorAll('LNode[iedName="None"]'), - ).filter((lNode) => lNode.getAttribute("lnClass") !== "LLN0"); - } - - get lLN0s(): Element[] { - return Array.from(this.doc.querySelectorAll('LNodeType[lnClass="LLN0"]')); - } - - @query("mwc-dialog") dialog!: Dialog; - @queryAll("mwc-check-list-item[selected]") - selectedLNodeItems?: CheckListItem[]; - - async run(): Promise { - this.dialog.open = true; - } - - private onPrimaryAction( - functions: Record, - ): void { - const selectedLNode = Array.from( - this.dialog.querySelectorAll( - "mwc-check-list-item[selected]:not([disabled])", - ) ?? [], - ).map((selectedItem) => find(this.doc, "LNode", selectedItem.value)!); - selectedLNode.forEach((lNode, index) => { - lNode.setAttribute("prefix", `CSWI-${index}`); - }); - if (!selectedLNode.length) return; - - const selectedLLN0s = Array.from( - this.dialog.querySelectorAll('mwc-select') ?? [] + ).map(selectedItem => selectedItem.value); + + const manufacturer = this.dialog.querySelector( + 'wizard-textfield[label="manufacturer"]' + )!.value; + const desc = this.dialog.querySelector( + 'wizard-textfield[label="desc"]' + )!.maybeValue; + const apName = this.dialog.querySelector( + 'wizard-textfield[label="AccessPoint name"]' + )!.value; + + const ied = getSpecificationIED(this.doc, { + manufacturer, + desc, + apName, + lDevices: getLDeviceDescriptions(functions, selectedLNode, selectedLLN0s), + }); + + // checkValidity: () => true disables name check as is the same here: SPECIFICATION + this.dispatchEvent( + newActionEvent({ + new: { parent: this.doc.documentElement, element: ied }, + checkValidity: () => true, + }) + ); + this.dialog.close(); + } + + private onClosed(ae: CustomEvent<{ action: string } | null>): void { + if (!(ae.target instanceof Dialog && ae.detail?.action)) return; + } + + private renderLLN0s( + functionID: string, + lLN0Types: Element[], + lNode?: Element + ): TemplateResult { + if (!lNode && !lLN0Types.length) return html``; + + if (lNode) + return html`${html`${lNode.getAttribute("lnType")} + value="${functionID + ': ' + lNode.getAttribute('lnType')}" + >${lNode.getAttribute('lnType')} `}`; - return html`${lLN0Types.map((lLN0Type) => { - return html`${lLN0Type.getAttribute("id")}${lLN0Types.map(lLN0Type => { + return html`${lLN0Type.getAttribute('id')}`; - })}`; - } + } - private renderLNodes(lNodes: Element[], disabled: boolean): TemplateResult[] { - return lNodes.map((lNode) => { - const prefix = getFunctionNamingPrefix(lNode); - const lnClass = lNode.getAttribute("lnClass")!; - const lnInst = lNode.getAttribute("lnInst")!; + private renderLNodes(lNodes: Element[], disabled: boolean): TemplateResult[] { + return lNodes.map(lNode => { + const prefix = getFunctionNamingPrefix(lNode); + const lnClass = lNode.getAttribute('lnClass')!; + const lnInst = lNode.getAttribute('lnInst')!; - const label = prefix + " " + lnClass + " " + lnInst; - return html`${label}`; - }); - } + }); + } - render(): TemplateResult { - if (!this.doc) return html``; + render(): TemplateResult { + if (!this.doc) return html``; - const existValidLLN0 = this.lLN0s.length !== 0; + const existValidLLN0 = this.lLN0s.length !== 0; - const functionElementDescriptions = groupLNodesToFunctions( - this.unreferencedLNodes, - ); + const functionElementDescriptions = groupLNodesToFunctions( + this.unreferencedLNodes + ); - return html`
    this.requestUpdate()} > @@ -286,52 +284,52 @@ export default class VirtualTemplateIED extends LitElement { > this.requestUpdate()} > this.requestUpdate()} >${Object.entries(functionElementDescriptions).flatMap( - ([id, functionDescription]) => [ - html` [ + html`${functionDescription.uniqueName}${existValidLLN0 ? id : "Invalid LD: Missing LLN0"}${existValidLLN0 ? id : 'Invalid LD: Missing LLN0'}`, - this.renderLLN0s( - functionDescription.uniqueName, - this.lLN0s, - functionDescription.lln0, - ), - ...this.renderLNodes(functionDescription.lNodes, !existValidLLN0), - html`
  • `, - ], - )}
    `, + ] + )}
    this.onPrimaryAction(functionElementDescriptions)} >
    `; - } + } - static styles = css` + static styles = css` mwc-dialog { --mdc-dialog-max-width: 92vw; } From 53172ee657dc4adc9f7dabd4b61d30c7c21d01c8 Mon Sep 17 00:00:00 2001 From: Illia Solovei Date: Mon, 18 Nov 2024 16:50:09 +0100 Subject: [PATCH 6/8] bugfix: include lnInst in selector function --- packages/openscd/src/foundation.ts | 6 +-- .../plugins/src/menu/VirtualTemplateIED.ts | 47 ++++++++----------- 2 files changed, 22 insertions(+), 31 deletions(-) diff --git a/packages/openscd/src/foundation.ts b/packages/openscd/src/foundation.ts index b30182842f..d1f92fdc0f 100644 --- a/packages/openscd/src/foundation.ts +++ b/packages/openscd/src/foundation.ts @@ -338,7 +338,7 @@ function lNodeIdentity(e: Element): string { 'lnType', ].map(name => e.getAttribute(name)); if (iedName === 'None') - return `${identity(e.parentElement)}>(${lnClass} ${lnType})`; + return `${identity(e.parentElement)}>(${lnClass} ${lnType} ${lnInst})`; return `${iedName} ${ldInst || '(Client)'}/${prefix ?? ''} ${lnClass} ${ lnInst ?? '' }`; @@ -347,7 +347,7 @@ function lNodeIdentity(e: Element): string { function lNodeSelector(tagName: SCLTag, identity: string): string { if (identity.endsWith(')')) { const [parentIdentity, childIdentity] = pathParts(identity); - const [lnClass, lnType] = childIdentity + const [lnClass, lnType, lnInst] = childIdentity .substring(1, childIdentity.length - 1) .split(' '); @@ -360,7 +360,7 @@ function lNodeSelector(tagName: SCLTag, identity: string): string { return crossProduct( parentSelectors, ['>'], - [`${tagName}[iedName="None"][lnClass="${lnClass}"][lnType="${lnType}"]`] + [`${tagName}[iedName="None"][lnClass="${lnClass}"][lnType="${lnType}"][lnInst="${lnInst}"]`] ) .map(strings => strings.join('')) .join(','); diff --git a/packages/plugins/src/menu/VirtualTemplateIED.ts b/packages/plugins/src/menu/VirtualTemplateIED.ts index 449de7defb..077e567f48 100644 --- a/packages/plugins/src/menu/VirtualTemplateIED.ts +++ b/packages/plugins/src/menu/VirtualTemplateIED.ts @@ -43,50 +43,41 @@ export type FunctionElementDescription = { function getLDeviceDescriptions( functions: Record, selectedLNodes: Element[], - selectedLLN0s: string[], + selectedLLN0s: string[] ): LDeviceDescription[] { const lDeviceDescriptions: LDeviceDescription[] = []; - console.log("LDeviceDescriptions input - selectedLNodes: ", selectedLNodes); - - Object.values(functions).forEach((functionDescription) => { + Object.values(functions).forEach(functionDescription => { if ( - functionDescription.lNodes.some((lNode) => selectedLNodes.includes(lNode)) + functionDescription.lNodes.some(lNode => selectedLNodes.includes(lNode)) ) { - const lLN0 = selectedLLN0s.find((selectedLLN0) => - selectedLLN0.includes(functionDescription.uniqueName), + const lLN0 = selectedLLN0s.find(selectedLLN0 => + selectedLLN0.includes(functionDescription.uniqueName) )!; - const lnType = lLN0?.split(": ")[1]; - - const anyLNs = [ - { prefix: null, lnClass: "LLN0", inst: "", lnType }, - ...selectedLNodes.map((lNode) => { - console.log("here: ", getFunctionNamingPrefix(lNode)); - return { - prefix: getFunctionNamingPrefix(lNode), - lnClass: lNode.getAttribute("lnClass")!, - inst: lNode.getAttribute("lnInst")!, - lnType: lNode.getAttribute("lnType")!, - }; - }), - ]; + const lnType = lLN0?.split(': ')[1]; lDeviceDescriptions.push({ validLdInst: functionDescription.uniqueName, - anyLNs, + anyLNs: [ + { prefix: null, lnClass: 'LLN0', inst: '', lnType }, + ...functionDescription.lNodes + .filter(lNode => selectedLNodes.includes(lNode)) + .map(lNode => { + return { + prefix: getFunctionNamingPrefix(lNode), + lnClass: lNode.getAttribute('lnClass')!, + inst: lNode.getAttribute('lnInst')!, + lnType: lNode.getAttribute('lnType')!, + }; + }), + ], }); } }); - console.log( - "AFTER LDeviceDescriptions input - selectedLNodes: ", - lDeviceDescriptions, - ); - return lDeviceDescriptions; } - /** Groups all incomming LNode's with non-leaf parent function type elements */ function groupLNodesToFunctions( lNodes: Element[] From 1db6a093fde46478a524a4dd248b4bf271803937 Mon Sep 17 00:00:00 2001 From: Illia Solovei Date: Mon, 18 Nov 2024 17:07:26 +0100 Subject: [PATCH 7/8] bugfix: adjust test cases to properly handle lnInst --- .../VirtualTemplateIED.test.snap.js | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/plugins/test/unit/menu/__snapshots__/VirtualTemplateIED.test.snap.js b/packages/plugins/test/unit/menu/__snapshots__/VirtualTemplateIED.test.snap.js index 9f308faba4..c2216f291e 100644 --- a/packages/plugins/test/unit/menu/__snapshots__/VirtualTemplateIED.test.snap.js +++ b/packages/plugins/test/unit/menu/__snapshots__/VirtualTemplateIED.test.snap.js @@ -1,7 +1,7 @@ /* @web/test-runner snapshot v1 */ export const snapshots = {}; -snapshots["Plugin that creates with some user input a virtual template IED - SPECIFICATION looks like the latest snapshot"] = +snapshots["Plugin that creates with some user input a virtual template IED - SPECIFICATION looks like the latest snapshot"] = ` CSWI 1 @@ -81,7 +81,7 @@ snapshots["Plugin that creates with some user input a virtual template IED - SPE graphic="control" mwc-list-item="" tabindex="-1" - value="AA1>E1>Q01>QC9>Earth_Switch>(CILO OpenSCD_CILO)" + value="AA1>E1>Q01>QC9>Earth_Switch>(CILO OpenSCD_CILO 1)" > CILO 1 @@ -90,7 +90,7 @@ snapshots["Plugin that creates with some user input a virtual template IED - SPE graphic="control" mwc-list-item="" tabindex="-1" - value="AA1>E1>Q01>QC9>Earth_Switch>(XSWI OpenSCD_XSWI_EarthSwitch)" + value="AA1>E1>Q01>QC9>Earth_Switch>(XSWI OpenSCD_XSWI_EarthSwitch 1)" > XSWI 1 @@ -148,7 +148,7 @@ snapshots["Plugin that creates with some user input a virtual template IED - SPE graphic="control" mwc-list-item="" tabindex="-1" - value="AA1>E1>Q01>QB1>Disconnector>(CSWI OpenSCD_CSWI)" + value="AA1>E1>Q01>QB1>Disconnector>(CSWI OpenSCD_CSWI 1)" > CSWI 1 @@ -157,7 +157,7 @@ snapshots["Plugin that creates with some user input a virtual template IED - SPE graphic="control" mwc-list-item="" tabindex="-1" - value="AA1>E1>Q01>QB1>Disconnector>(XSWI OpenSCD_XSWI_DIS)" + value="AA1>E1>Q01>QB1>Disconnector>(XSWI OpenSCD_XSWI_DIS 1)" > XSWI 1 @@ -166,7 +166,7 @@ snapshots["Plugin that creates with some user input a virtual template IED - SPE graphic="control" mwc-list-item="" tabindex="-1" - value="AA1>E1>Q01>QB1>Disconnector>(CILO OpenSCD_CILO)" + value="AA1>E1>Q01>QB1>Disconnector>(CILO OpenSCD_CILO 1)" > CILO 1 @@ -224,7 +224,7 @@ snapshots["Plugin that creates with some user input a virtual template IED - SPE graphic="control" mwc-list-item="" tabindex="-1" - value="AA1>E1>Q01>QA1>Circuit_Breaker>(CSWI OpenSCD_CSWI)" + value="AA1>E1>Q01>QA1>Circuit_Breaker>(CSWI OpenSCD_CSWI 1)" > CSWI 1 @@ -233,7 +233,7 @@ snapshots["Plugin that creates with some user input a virtual template IED - SPE graphic="control" mwc-list-item="" tabindex="-1" - value="AA1>E1>Q01>QA1>Circuit_Breaker>(CILO OpenSCD_CILO)" + value="AA1>E1>Q01>QA1>Circuit_Breaker>(CILO OpenSCD_CILO 1)" > CILO 1 @@ -242,7 +242,7 @@ snapshots["Plugin that creates with some user input a virtual template IED - SPE graphic="control" mwc-list-item="" tabindex="-1" - value="AA1>E1>Q01>QA1>Circuit_Breaker>(XCBR OpenSCD_XCBR)" + value="AA1>E1>Q01>QA1>Circuit_Breaker>(XCBR OpenSCD_XCBR 1)" > XCBR 1 @@ -292,7 +292,7 @@ snapshots["Plugin that creates with some user input a virtual template IED - SPE graphic="control" mwc-list-item="" tabindex="-1" - value="AA1>E1>Q01>Timed_Overcurrent>(PTOC OpenSCD_PTOC)" + value="AA1>E1>Q01>Timed_Overcurrent>(PTOC OpenSCD_PTOC 2)" > ID_ PTOC 2 @@ -301,7 +301,7 @@ snapshots["Plugin that creates with some user input a virtual template IED - SPE graphic="control" mwc-list-item="" tabindex="-1" - value="AA1>E1>Q01>Timed_Overcurrent>(PTOC OpenSCD_PTOC)" + value="AA1>E1>Q01>Timed_Overcurrent>(PTOC OpenSCD_PTOC 1)" > IDD_ PTOC 1 @@ -359,7 +359,7 @@ snapshots["Plugin that creates with some user input a virtual template IED - SPE graphic="control" mwc-list-item="" tabindex="-1" - value="AA1>E1>Q01>Distance_Protection>Zone4>(PDIS OpenSCD_PDIS)" + value="AA1>E1>Q01>Distance_Protection>Zone4>(PDIS OpenSCD_PDIS 1)" > Zone4 PDIS 1 @@ -368,7 +368,7 @@ snapshots["Plugin that creates with some user input a virtual template IED - SPE graphic="control" mwc-list-item="" tabindex="-1" - value="AA1>E1>Q01>Distance_Protection>Zon3>(PDIS OpenSCD_PDIS)" + value="AA1>E1>Q01>Distance_Protection>Zon3>(PDIS OpenSCD_PDIS 1)" > Zon3 PDIS 1 @@ -377,7 +377,7 @@ snapshots["Plugin that creates with some user input a virtual template IED - SPE graphic="control" mwc-list-item="" tabindex="-1" - value="AA1>E1>Q01>Distance_Protection>Zone2>(PDIS OpenSCD_PDIS)" + value="AA1>E1>Q01>Distance_Protection>Zone2>(PDIS OpenSCD_PDIS 1)" > Zone2 PDIS 1 @@ -386,7 +386,7 @@ snapshots["Plugin that creates with some user input a virtual template IED - SPE graphic="control" mwc-list-item="" tabindex="-1" - value="AA1>E1>Q01>Distance_Protection>Zone1>(PDIS OpenSCD_PDIS)" + value="AA1>E1>Q01>Distance_Protection>Zone1>(PDIS OpenSCD_PDIS 1)" > Zone1 PDIS 1 @@ -444,7 +444,7 @@ snapshots["Plugin that creates with some user input a virtual template IED - SPE graphic="control" mwc-list-item="" tabindex="-1" - value="AA1>E1>Q02>QB1>Disconnector>(CSWI OpenSCD_CSWI)" + value="AA1>E1>Q02>QB1>Disconnector>(CSWI OpenSCD_CSWI 1)" > CSWI 1 @@ -453,7 +453,7 @@ snapshots["Plugin that creates with some user input a virtual template IED - SPE graphic="control" mwc-list-item="" tabindex="-1" - value="AA1>E1>Q02>QB1>Disconnector>(XSWI OpenSCD_XSWI_DIS)" + value="AA1>E1>Q02>QB1>Disconnector>(XSWI OpenSCD_XSWI_DIS 1)" > XSWI 1 @@ -462,7 +462,7 @@ snapshots["Plugin that creates with some user input a virtual template IED - SPE graphic="control" mwc-list-item="" tabindex="-1" - value="AA1>E1>Q02>QB1>Disconnector>(CILO OpenSCD_CILO)" + value="AA1>E1>Q02>QB1>Disconnector>(CILO OpenSCD_CILO 1)" > CILO 1 @@ -520,7 +520,7 @@ snapshots["Plugin that creates with some user input a virtual template IED - SPE graphic="control" mwc-list-item="" tabindex="-1" - value="AA1>J1>Q01>QC9>Earth_Switch>(CSWI OpenSCD_CSWI)" + value="AA1>J1>Q01>QC9>Earth_Switch>(CSWI OpenSCD_CSWI 1)" > CSWI 1 @@ -529,7 +529,7 @@ snapshots["Plugin that creates with some user input a virtual template IED - SPE graphic="control" mwc-list-item="" tabindex="-1" - value="AA1>J1>Q01>QC9>Earth_Switch>(CILO OpenSCD_CILO)" + value="AA1>J1>Q01>QC9>Earth_Switch>(CILO OpenSCD_CILO 1)" > CILO 1 @@ -538,7 +538,7 @@ snapshots["Plugin that creates with some user input a virtual template IED - SPE graphic="control" mwc-list-item="" tabindex="-1" - value="AA1>J1>Q01>QC9>Earth_Switch>(XSWI OpenSCD_XSWI_EarthSwitch)" + value="AA1>J1>Q01>QC9>Earth_Switch>(XSWI OpenSCD_XSWI_EarthSwitch 1)" > XSWI 1 @@ -569,7 +569,7 @@ snapshots["Plugin that creates with some user input a virtual template IED - SPE `; /* end snapshot Plugin that creates with some user input a virtual template IED - SPECIFICATION looks like the latest snapshot */ -snapshots["Plugin that creates with some user input a virtual template IED - SPECIFICATION IEDs data model show selected logical nodes and its structure"] = +snapshots["Plugin that creates with some user input a virtual template IED - SPECIFICATION IEDs data model show selected logical nodes and its structure"] = ` Date: Tue, 19 Nov 2024 13:58:33 +0100 Subject: [PATCH 8/8] bugfix(tests): temporarily adjust test --- .../unit/menu/__snapshots__/VirtualTemplateIED.test.snap.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/plugins/test/unit/menu/__snapshots__/VirtualTemplateIED.test.snap.js b/packages/plugins/test/unit/menu/__snapshots__/VirtualTemplateIED.test.snap.js index c2216f291e..b753457b04 100644 --- a/packages/plugins/test/unit/menu/__snapshots__/VirtualTemplateIED.test.snap.js +++ b/packages/plugins/test/unit/menu/__snapshots__/VirtualTemplateIED.test.snap.js @@ -601,10 +601,10 @@ snapshots["Plugin that creates with some user input a virtual template IED - SPE >