From 73d8a892c20ae861737ffc4bc047123eb0bdf0f6 Mon Sep 17 00:00:00 2001 From: Illia Solovei Date: Thu, 21 Nov 2024 16:32:51 +0100 Subject: [PATCH 1/8] bugfix: make desc field required --- packages/plugins/src/menu/VirtualTemplateIED.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/plugins/src/menu/VirtualTemplateIED.ts b/packages/plugins/src/menu/VirtualTemplateIED.ts index 077e567f48..785fd00f7e 100644 --- a/packages/plugins/src/menu/VirtualTemplateIED.ts +++ b/packages/plugins/src/menu/VirtualTemplateIED.ts @@ -271,6 +271,8 @@ export default class VirtualTemplateIED extends LitElement { Date: Mon, 25 Nov 2024 10:52:37 +0100 Subject: [PATCH 2/8] bugfix: remove toggle --- .../plugins/src/menu/VirtualTemplateIED.ts | 480 +++++++++--------- 1 file changed, 239 insertions(+), 241 deletions(-) diff --git a/packages/plugins/src/menu/VirtualTemplateIED.ts b/packages/plugins/src/menu/VirtualTemplateIED.ts index 785fd00f7e..c7e557923f 100644 --- a/packages/plugins/src/menu/VirtualTemplateIED.ts +++ b/packages/plugins/src/menu/VirtualTemplateIED.ts @@ -1,328 +1,326 @@ 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[] = []; + + 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; } /** 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()} > 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 c3307a71cc536d68762df486f8a8051518f19aa0 Mon Sep 17 00:00:00 2001 From: Illia Solovei Date: Mon, 25 Nov 2024 11:45:34 +0100 Subject: [PATCH 3/8] bugfix: visually show no description warning - add banner if there are S-IEDs with no desc attribute present - more descriptive labels --- packages/openscd/src/translations/de.ts | 1 + packages/openscd/src/translations/en.ts | 1 + packages/plugins/src/editors/IED.ts | 12 ++++++++++++ packages/plugins/src/menu/VirtualTemplateIED.ts | 4 ++-- 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/openscd/src/translations/de.ts b/packages/openscd/src/translations/de.ts index 67e314ede6..7bcb1698d5 100644 --- a/packages/openscd/src/translations/de.ts +++ b/packages/openscd/src/translations/de.ts @@ -252,6 +252,7 @@ export const de: Translations = { wizard: { nameHelper: 'Name des IED', descHelper: 'Beschreibung des IED', + noDescWarning: "Bei einigen S-IEDs fehlt das Attribut desc.", title: { edit: 'IED bearbeiten', delete: 'IED mit Abhängigkeiten entfernen', diff --git a/packages/openscd/src/translations/en.ts b/packages/openscd/src/translations/en.ts index ca983fd40d..544690747b 100644 --- a/packages/openscd/src/translations/en.ts +++ b/packages/openscd/src/translations/en.ts @@ -249,6 +249,7 @@ export const en = { wizard: { nameHelper: 'IED name', descHelper: 'IED description', + noDescWarning: "Some S-IEDs lack description.", title: { edit: 'Edit IED', delete: 'Remove IED with references', diff --git a/packages/plugins/src/editors/IED.ts b/packages/plugins/src/editors/IED.ts index 052a1309c0..e93b780c11 100644 --- a/packages/plugins/src/editors/IED.ts +++ b/packages/plugins/src/editors/IED.ts @@ -99,6 +99,11 @@ export default class IedPlugin extends LitElement { return undefined; } + @state() + private get hasIedWithoutDesc(): boolean { + return this.iedList.some(ied => !ied.hasAttribute('desc')); + } + lNClassListOpenedOnce = false; protected updated(_changedProperties: PropertyValues): void { @@ -151,6 +156,13 @@ export default class IedPlugin extends LitElement { const iedList = this.iedList; if (iedList.length > 0) { return html`
    + ${ + this.hasIedWithoutDesc + ? html` + ${get('ied.wizard.noDescWarning')} + ` + : nothing + }

    ${get('filters')}:

    diff --git a/packages/plugins/src/menu/VirtualTemplateIED.ts b/packages/plugins/src/menu/VirtualTemplateIED.ts index c7e557923f..d6e5be2ff3 100644 --- a/packages/plugins/src/menu/VirtualTemplateIED.ts +++ b/packages/plugins/src/menu/VirtualTemplateIED.ts @@ -263,13 +263,13 @@ export default class VirtualTemplateIED extends LitElement { @closed=${this.onClosed} >
    this.requestUpdate()} > From c36863d071c85f8f5dfbd3660edc8dff71d6c09d Mon Sep 17 00:00:00 2001 From: Illia Solovei Date: Mon, 25 Nov 2024 11:48:33 +0100 Subject: [PATCH 4/8] bugfix: restore formatting --- .../plugins/src/menu/VirtualTemplateIED.ts | 482 +++++++++--------- 1 file changed, 241 insertions(+), 241 deletions(-) diff --git a/packages/plugins/src/menu/VirtualTemplateIED.ts b/packages/plugins/src/menu/VirtualTemplateIED.ts index d6e5be2ff3..68ce0b7865 100644 --- a/packages/plugins/src/menu/VirtualTemplateIED.ts +++ b/packages/plugins/src/menu/VirtualTemplateIED.ts @@ -1,326 +1,326 @@ 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[] = []; + + 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; } /** 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()} > 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 d6743606e268cf561d35c09e50da88418cf04d8a Mon Sep 17 00:00:00 2001 From: Illia Solovei Date: Mon, 25 Nov 2024 11:54:11 +0100 Subject: [PATCH 5/8] bugfix: improve translation --- packages/openscd/src/translations/de.ts | 2 +- packages/openscd/src/translations/en.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/openscd/src/translations/de.ts b/packages/openscd/src/translations/de.ts index 7bcb1698d5..8b2c18a013 100644 --- a/packages/openscd/src/translations/de.ts +++ b/packages/openscd/src/translations/de.ts @@ -252,7 +252,7 @@ export const de: Translations = { wizard: { nameHelper: 'Name des IED', descHelper: 'Beschreibung des IED', - noDescWarning: "Bei einigen S-IEDs fehlt das Attribut desc.", + noDescWarning: "Einige S-IEDs können nicht angezeigt werden, da sie kein 'desc'-Attribut haben. Klicken Sie hier und fügen Sie es manuell hinzu.", title: { edit: 'IED bearbeiten', delete: 'IED mit Abhängigkeiten entfernen', diff --git a/packages/openscd/src/translations/en.ts b/packages/openscd/src/translations/en.ts index 544690747b..68da8beb3b 100644 --- a/packages/openscd/src/translations/en.ts +++ b/packages/openscd/src/translations/en.ts @@ -249,7 +249,7 @@ export const en = { wizard: { nameHelper: 'IED name', descHelper: 'IED description', - noDescWarning: "Some S-IEDs lack description.", + noDescWarning: "Impossible to apply filter for some S-IEDs - missing 'desc' attribute. Manually select and add it here.", title: { edit: 'Edit IED', delete: 'Remove IED with references', From ea22c9ab0fee90bac33324319ea870383008c2d5 Mon Sep 17 00:00:00 2001 From: Illia Solovei Date: Mon, 25 Nov 2024 12:28:53 +0100 Subject: [PATCH 6/8] bugfix: open editor wizard if clicked --- packages/plugins/src/editors/IED.ts | 37 +++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/packages/plugins/src/editors/IED.ts b/packages/plugins/src/editors/IED.ts index e93b780c11..b96cd9ead9 100644 --- a/packages/plugins/src/editors/IED.ts +++ b/packages/plugins/src/editors/IED.ts @@ -23,9 +23,11 @@ import { compareNames, getDescriptionAttribute, getNameAttribute, + newWizardEvent, } from '@openscd/open-scd/src/foundation.js'; import { Nsdoc } from '@openscd/open-scd/src/foundation/nsdoc.js'; import { getIcon } from '@openscd/open-scd/src/icons/icons.js'; +import { wizards } from '../wizards/wizard-library.js'; /** An editor [[`plugin`]] for editing the `IED` section. */ export default class IedPlugin extends LitElement { @@ -100,8 +102,8 @@ export default class IedPlugin extends LitElement { } @state() - private get hasIedWithoutDesc(): boolean { - return this.iedList.some(ied => !ied.hasAttribute('desc')); + private get iedsWithoutDesc(): Element[] { + return this.iedList.filter(ied => !ied.hasAttribute('desc')); } lNClassListOpenedOnce = false; @@ -152,14 +154,28 @@ export default class IedPlugin extends LitElement { return selectedLNClasses; } + private openEditWizard(element: Element): void { + const wizard = wizards['IED'].edit(element); + if (wizard) this.dispatchEvent(newWizardEvent(wizard)); + } + render(): TemplateResult { const iedList = this.iedList; if (iedList.length > 0) { return html`
    ${ - this.hasIedWithoutDesc + this.iedsWithoutDesc.length > 0 ? html` - ${get('ied.wizard.noDescWarning')} +
    + ${get('ied.wizard.noDescWarning')} + edit +
    ` : nothing } @@ -264,6 +280,19 @@ export default class IedPlugin extends LitElement { display: flex; } + .missing-desc-banner { + margin-bottom: 16px; + background-color: var(--mdc-theme-error); + color: var(--mdc-theme-on-error); + padding: 8px 16px; + display: flex; + align-items: center; + justify-content: space-between; + cursor: pointer; + border-radius: 4px; + transition: opacity 0.2s ease-in-out; + } + h1 { color: var(--mdc-theme-on-surface); font-family: 'Roboto', sans-serif; From 9ecf075b268bbea7cf175ad2f6402b2336b13fac Mon Sep 17 00:00:00 2001 From: Illia Solovei Date: Mon, 25 Nov 2024 12:30:20 +0100 Subject: [PATCH 7/8] bugfix: make desc required in edit wizard --- packages/plugins/src/wizards/ied.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/plugins/src/wizards/ied.ts b/packages/plugins/src/wizards/ied.ts index e4fbb4693e..0fb20165e8 100644 --- a/packages/plugins/src/wizards/ied.ts +++ b/packages/plugins/src/wizards/ied.ts @@ -15,7 +15,7 @@ import { WizardInputElement, WizardMenuActor, } from '@openscd/open-scd/src/foundation.js'; -import { +import { ComplexAction, Delete, EditorAction, @@ -60,7 +60,7 @@ export function renderIEDWizard( html``, From fd510576d15541d951cd44283cbdcd91877dc8f7 Mon Sep 17 00:00:00 2001 From: Illia Solovei Date: Fri, 29 Nov 2024 16:11:56 +0100 Subject: [PATCH 8/8] bugfix: translation and ui changes --- packages/openscd/src/translations/de.ts | 2 +- packages/openscd/src/translations/en.ts | 2 +- packages/plugins/src/editors/IED.ts | 10 ++++++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/openscd/src/translations/de.ts b/packages/openscd/src/translations/de.ts index 8b2c18a013..701a766945 100644 --- a/packages/openscd/src/translations/de.ts +++ b/packages/openscd/src/translations/de.ts @@ -252,7 +252,7 @@ export const de: Translations = { wizard: { nameHelper: 'Name des IED', descHelper: 'Beschreibung des IED', - noDescWarning: "Einige S-IEDs können nicht angezeigt werden, da sie kein 'desc'-Attribut haben. Klicken Sie hier und fügen Sie es manuell hinzu.", + noDescWarning: "Warnung! Einige S-IEDs können nicht angezeigt werden, da sie kein „des“-Attribut haben. Klicke hier, um das Attribut manuell hinzuzufügen.", title: { edit: 'IED bearbeiten', delete: 'IED mit Abhängigkeiten entfernen', diff --git a/packages/openscd/src/translations/en.ts b/packages/openscd/src/translations/en.ts index 68da8beb3b..2cef227375 100644 --- a/packages/openscd/src/translations/en.ts +++ b/packages/openscd/src/translations/en.ts @@ -249,7 +249,7 @@ export const en = { wizard: { nameHelper: 'IED name', descHelper: 'IED description', - noDescWarning: "Impossible to apply filter for some S-IEDs - missing 'desc' attribute. Manually select and add it here.", + noDescWarning: "Warning! Some S-IEDs cannot be displayed because they do not have a “desc” attribute. Click here to add the attribute manually.", title: { edit: 'Edit IED', delete: 'Remove IED with references', diff --git a/packages/plugins/src/editors/IED.ts b/packages/plugins/src/editors/IED.ts index b96cd9ead9..af59c90de6 100644 --- a/packages/plugins/src/editors/IED.ts +++ b/packages/plugins/src/editors/IED.ts @@ -203,6 +203,13 @@ export default class IedPlugin extends LitElement { return; } + // const selectedIedNames = e.detail.selectedItems.map(item => { + // const [name] = item.split('__'); + // return name; + // }); + + // console.log("HERE: ", selectedIedNames) + this.lNClassListOpenedOnce = false; this.selectedIEDs = e.detail.selectedItems; this.selectedLNClasses = []; @@ -282,8 +289,7 @@ export default class IedPlugin extends LitElement { .missing-desc-banner { margin-bottom: 16px; - background-color: var(--mdc-theme-error); - color: var(--mdc-theme-on-error); + background-color: #ffcc00; padding: 8px 16px; display: flex; align-items: center;