diff --git a/src/Plugging.ts b/src/Plugging.ts index 608c5b8f00..e10d6215ca 100644 --- a/src/Plugging.ts +++ b/src/Plugging.ts @@ -23,7 +23,7 @@ import { Select } from '@material/mwc-select'; import { Switch } from '@material/mwc-switch'; import { TextField } from '@material/mwc-textfield'; -import { ifImplemented, LitElementConstructor, Mixin } from './foundation.js'; +import { ifImplemented, initializeNsdoc, LitElementConstructor, Mixin } from './foundation.js'; import { EditingElement } from './Editing.js'; import { officialPlugins } from '../public/js/plugins.js'; @@ -208,6 +208,7 @@ export function Plugging EditingElement>( .docName=${this.docName} .docId=${this.docId} .pluginId=${plugin.src} + .nsdoc=${initializeNsdoc()} >`; }, }; diff --git a/src/Setting.ts b/src/Setting.ts index f686892194..a8983d7df8 100644 --- a/src/Setting.ts +++ b/src/Setting.ts @@ -16,16 +16,9 @@ import { ifImplemented, LitElementConstructor, Mixin } from './foundation.js'; import { Language, languages, loader } from './translations/loader.js'; import './WizardDivider.js'; +import { WizardDialog } from './wizard-dialog.js'; -function NsdocSettings() { - return { - - } -} - -export const nsdocSettings = NsdocSettings(); - -export type SettingsRecord = { +export type Settings = { language: Language; theme: 'light' | 'dark'; mode: 'safe' | 'pro'; @@ -35,11 +28,26 @@ export type SettingsRecord = { 'IEC 61850-7-4': string | undefined; 'IEC 61850-8-1': string | undefined; }; +export const defaults: Settings = { + language: 'en', + theme: 'light', + mode: 'safe', + showieds: 'off', + 'IEC 61850-7-2': undefined, + 'IEC 61850-7-3': undefined, + 'IEC 61850-7-4': undefined, + 'IEC 61850-8-1': undefined +}; -export function Settings() { - return { - /** Current [[`CompasSettings`]] in `localStorage`, default to [[`defaults`]]. */ - get settings(): SettingsRecord { +/** Mixin that saves [[`Settings`]] to `localStorage`, reflecting them in the + * `settings` property, setting them through `setSetting(setting, value)`. */ +export type SettingElement = Mixin; + +export function Setting(Base: TBase) { + class SettingElement extends Base { + /** Current [[`Settings`]] in `localStorage`, default to [[`defaults`]]. */ + @property() + get settings(): Settings { return { language: this.getSetting('language'), theme: this.getSetting('theme'), @@ -50,49 +58,6 @@ export function Settings() { 'IEC 61850-7-4': this.getSetting('IEC 61850-7-4'), 'IEC 61850-8-1': this.getSetting('IEC 61850-8-1') }; - }, - - get defaultSettings(): SettingsRecord { - return { - language: 'en', - theme: 'light', - mode: 'safe', - showieds: 'off', - 'IEC 61850-7-2': undefined, - 'IEC 61850-7-3': undefined, - 'IEC 61850-7-4': undefined, - 'IEC 61850-8-1': undefined - } - }, - - /** Update the `value` of `setting`, storing to `localStorage`. */ - setSetting(setting: T, value: SettingsRecord[T]): void { - localStorage.setItem(setting, (value)); - }, - - /** Update the `value` of `setting`, storing to `localStorage`. */ - removeSetting(setting: T): void { - localStorage.removeItem(setting); - }, - - getSetting(setting: T): SettingsRecord[T] { - return ( - localStorage.getItem(setting) ?? this.defaultSettings[setting] - ); - } - } -} - -/** Mixin that saves [[`Settings`]] to `localStorage`, reflecting them in the - * `settings` property, setting them through `setSetting(setting, value)`. */ -export type SettingElement = Mixin; - -export function Setting(Base: TBase) { - class SettingElement extends Base { - /** Current [[`Settings`]] in `localStorage`, default to [[`defaults`]]. */ - @property() - get settings(): SettingsRecord { - return Settings().settings; } @query('#settings') @@ -109,6 +74,30 @@ export function Setting(Base: TBase) { @query('#nsdoc-file') private nsdocFileUI!: HTMLInputElement; + private getSetting(setting: T): Settings[T] { + return ( + localStorage.getItem(setting) ?? defaults[setting] + ); + } + + /** Update the `value` of `setting`, storing to `localStorage`. */ + setSetting(setting: T, value: Settings[T]): void { + localStorage.setItem(setting, (value)); + this.shadowRoot + ?.querySelector('wizard-dialog') + ?.requestUpdate(); + this.requestUpdate(); + } + + /** Remove the `setting` in `localStorage`. */ + removeSetting(setting: T): void { + localStorage.removeItem(setting); + this.shadowRoot + ?.querySelector('wizard-dialog') + ?.requestUpdate(); + this.requestUpdate(); + } + private onClosing(ae: CustomEvent<{ action: string } | null>): void { if (ae.detail?.action === 'reset') { Object.keys(this.settings).forEach(item => @@ -116,10 +105,10 @@ export function Setting(Base: TBase) { ); this.requestUpdate('settings'); } else if (ae.detail?.action === 'save') { - Settings().setSetting('language', this.languageUI.value); - Settings().setSetting('theme', this.darkThemeUI.checked ? 'dark' : 'light'); - Settings().setSetting('mode', this.modeUI.checked ? 'pro' : 'safe'); - Settings().setSetting('showieds', this.showiedsUI.checked ? 'on' : 'off'); + this.setSetting('language', this.languageUI.value); + this.setSetting('theme', this.darkThemeUI.checked ? 'dark' : 'light'); + this.setSetting('mode', this.modeUI.checked ? 'pro' : 'safe'); + this.setSetting('showieds', this.showiedsUI.checked ? 'on' : 'off'); this.requestUpdate('settings'); } } @@ -154,7 +143,7 @@ export function Setting(Base: TBase) { const id = this.parseToXmlObject(text).querySelector('NSDoc')?.getAttribute('id'); if (!id) return; - Settings().setSetting(id as keyof SettingsRecord, text); + this.setSetting(id as keyof Settings, text); }) this.nsdocFileUI.value = ''; @@ -166,7 +155,7 @@ export function Setting(Base: TBase) { * @param key - The key of the nsdoc file in the settings. * @returns a .nsdoc item for the Settings wizard */ - private renderNsdocItem(key: T): TemplateResult { + private renderNsdocItem(key: T): TemplateResult { const nsdSetting = this.settings[key]; let nsdVersion: string | undefined | null; let nsdRevision: string | undefined | null; @@ -185,10 +174,7 @@ export function Setting(Base: TBase) { html``} ${nsdSetting ? html`done` : html`close`} - ${nsdSetting ? html` { - Settings().removeSetting(key); - this.requestUpdate(); - }}>delete` : + ${nsdSetting ? html` {this.removeSetting(key)}}>delete` : html``} `; } diff --git a/src/editors/IED.ts b/src/editors/IED.ts index 927f662105..256aaf2c2e 100644 --- a/src/editors/IED.ts +++ b/src/editors/IED.ts @@ -10,7 +10,7 @@ import './ied/ied-container.js' import { translate } from 'lit-translate'; import { Select } from '@material/mwc-select'; import { SingleSelectedEvent } from '@material/mwc-list/mwc-list-foundation'; -import { compareNames, getDescriptionAttribute, getNameAttribute } from '../foundation.js'; +import { compareNames, getDescriptionAttribute, getNameAttribute, Nsdoc } from '../foundation.js'; /** An editor [[`plugin`]] for editing the `IED` section. */ export default class IedPlugin extends LitElement { @@ -18,6 +18,10 @@ export default class IedPlugin extends LitElement { @property() doc!: XMLDocument; + /** All the nsdoc files that are being uploaded via the settings. */ + @property() + nsdoc!: Nsdoc; + /** Query holding the current selected IEDs. */ @state() currentSelectedIEDs = ':root > IED'; @@ -60,6 +64,7 @@ export default class IedPlugin extends LitElement { ${Array.from(this.doc?.querySelectorAll(this.currentSelectedIEDs)).map( ied => html`` )}` : html`

diff --git a/src/editors/ied/access-point-container.ts b/src/editors/ied/access-point-container.ts index cf4c323820..d45d547943 100644 --- a/src/editors/ied/access-point-container.ts +++ b/src/editors/ied/access-point-container.ts @@ -10,7 +10,7 @@ import { import '../../action-pane.js'; import './server-container.js' import { nothing } from 'lit-html'; -import { getDescriptionAttribute, getNameAttribute } from '../../foundation.js'; +import { getDescriptionAttribute, getNameAttribute, Nsdoc } from '../../foundation.js'; /** [[`IED`]] plugin subeditor for editing `AccessPoint` element. */ @customElement('access-point-container') @@ -18,6 +18,9 @@ export class AccessPointContainer extends LitElement { @property({ attribute: false }) element!: Element; + @property() + nsdoc!: Nsdoc; + private header(): TemplateResult { const name = getNameAttribute(this.element); const desc = getDescriptionAttribute(this.element); @@ -30,6 +33,7 @@ export class AccessPointContainer extends LitElement { ${Array.from(this.element.querySelectorAll(':scope > Server')).map( server => html``)} `; } diff --git a/src/editors/ied/ied-container.ts b/src/editors/ied/ied-container.ts index eb2e24f857..2cedd8e7b6 100644 --- a/src/editors/ied/ied-container.ts +++ b/src/editors/ied/ied-container.ts @@ -9,7 +9,7 @@ import { import { nothing } from 'lit-html'; import '../../action-pane.js'; -import { getDescriptionAttribute, getNameAttribute } from '../../foundation.js'; +import { getDescriptionAttribute, getNameAttribute, Nsdoc } from '../../foundation.js'; import './access-point-container.js'; /** [[`IED`]] plugin subeditor for editing `IED` element. */ @@ -19,6 +19,9 @@ export class IedContainer extends LitElement { @property({ attribute: false }) element!: Element; + @property() + nsdoc!: Nsdoc; + private header(): TemplateResult { const name = getNameAttribute(this.element); const desc = getDescriptionAttribute(this.element); @@ -31,6 +34,7 @@ export class IedContainer extends LitElement { ${Array.from(this.element.querySelectorAll(':scope > AccessPoint')).map( ap => html``)} `; } diff --git a/src/editors/ied/ldevice-container.ts b/src/editors/ied/ldevice-container.ts index e3207ee139..d2b4e984ec 100644 --- a/src/editors/ied/ldevice-container.ts +++ b/src/editors/ied/ldevice-container.ts @@ -11,7 +11,7 @@ import { import '../../action-pane.js'; import './ln-container.js' import { nothing } from 'lit-html'; -import { getDescriptionAttribute, getInstanceAttribute, getNameAttribute } from '../../foundation.js'; +import { getDescriptionAttribute, getInstanceAttribute, getNameAttribute, Nsdoc } from '../../foundation.js'; import { IconButtonToggle } from '@material/mwc-icon-button-toggle'; import { translate } from 'lit-translate'; @@ -20,6 +20,9 @@ import { translate } from 'lit-translate'; export class LDeviceContainer extends LitElement { @property({ attribute: false }) element!: Element; + + @property() + nsdoc!: Nsdoc; @query('#toggleButton') toggleButton!: IconButtonToggle | undefined; @@ -48,8 +51,9 @@ export class LDeviceContainer extends LitElement { > ` : nothing}
- ${this.toggleButton?.on ? lnElements.map(server => html` html` `) : nothing}
diff --git a/src/editors/ied/ln-container.ts b/src/editors/ied/ln-container.ts index 67e5c1832a..6290adc207 100644 --- a/src/editors/ied/ln-container.ts +++ b/src/editors/ied/ln-container.ts @@ -11,25 +11,30 @@ import { nothing } from 'lit-html'; import '../../action-pane.js'; import './do-container.js'; -import { getInstanceAttribute, getNameAttribute } from '../../foundation.js'; +import { getInstanceAttribute, getNameAttribute, Nsdoc } from '../../foundation.js'; import { translate } from 'lit-translate'; import { IconButtonToggle } from '@material/mwc-icon-button-toggle'; +import { until } from 'lit-html/directives/until'; /** [[`IED`]] plugin subeditor for editing `LN` and `LN0` element. */ @customElement('ln-container') export class LNContainer extends LitElement { @property({ attribute: false }) element!: Element; + + @property() + nsdoc!: Nsdoc; @query('#toggleButton') toggleButton!: IconButtonToggle | undefined; - private header(): TemplateResult { + private async header(): Promise { const prefix = this.element.getAttribute('prefix'); - const lnClass = this.element.getAttribute('lnClass'); const inst = getInstanceAttribute(this.element); + const data = await this.nsdoc.getDataDescription(this.element); + return html`${prefix != null ? html`${prefix} — ` : nothing} - ${lnClass} + ${data.label} ${inst ? html` — ${inst}` : nothing}`; } @@ -59,7 +64,7 @@ export class LNContainer extends LitElement { render(): TemplateResult { const doElements = this.getDOElements(); - return html` + return html` ${doElements.length > 0 ? html` LDevice')).map( server => html``)} `; } diff --git a/src/foundation.ts b/src/foundation.ts index ed639b97a4..0b7314ed90 100644 --- a/src/foundation.ts +++ b/src/foundation.ts @@ -8,6 +8,7 @@ import AceEditor from 'ace-custom-element'; import { WizardTextField } from './wizard-textfield.js'; import { WizardSelect } from './wizard-select.js'; +import { iec6185074 } from './validators/templates/foundation.js'; export type SimpleAction = Create | Update | Delete | Move; export type ComplexAction = { @@ -360,6 +361,45 @@ export function newOpenDocEvent( }); } +export interface Nsdoc { + nsdoc72?: XMLDocument; + nsdoc73?: XMLDocument; + nsdoc74?: XMLDocument; + nsdoc81?: XMLDocument; + getDataDescription: (element: Element, attribute?: string) => Promise<{ label: string; description: string; }> +} + +export function initializeNsdoc(): Nsdoc { + const nsdoc72 = localStorage.getItem('IEC 61850-7-2') ? new DOMParser().parseFromString(localStorage.getItem('IEC 61850-7-2')!, 'application/xml') : undefined; + const nsdoc73 = localStorage.getItem('IEC 61850-7-3') ? new DOMParser().parseFromString(localStorage.getItem('IEC 61850-7-3')!, 'application/xml') : undefined; + const nsdoc74 = localStorage.getItem('IEC 61850-7-4') ? new DOMParser().parseFromString(localStorage.getItem('IEC 61850-7-4')!, 'application/xml') : undefined; + const nsdoc81 = localStorage.getItem('IEC 61850-8-1') ? new DOMParser().parseFromString(localStorage.getItem('IEC 61850-8-1')!, 'application/xml') : undefined; + + async function getDataDescription(element: Element, attribute?: string): Promise<{ label: string; description: string; }> { + const nsd74 = await iec6185074; + + if (element.tagName == 'LN' || element.tagName == 'LN0') { + const lnClass = nsd74.querySelector(`NS > LNClasses > LNClass[name="${element.getAttribute('lnClass')}"]`); + const titleId = lnClass?.getAttribute('titleID'); + return { + label: nsdoc74?.querySelector(`NSDoc > Doc[id="${titleId}"]`)?.textContent ?? element.getAttribute('lnClass')!, + description: '...' + }; + } + + return {label: '...', description: '...'}; + } + + return { + nsdoc72: nsdoc72, + nsdoc73: nsdoc73, + nsdoc74: nsdoc74, + nsdoc81: nsdoc81, + getDataDescription: getDataDescription + + } +} + /** @returns a reference to `element` with segments delimited by '/'. */ // TODO(c-dinkel): replace with identity (FIXME) export function referencePath(element: Element): string { diff --git a/src/themes.ts b/src/themes.ts index ce214bbea6..74b8127118 100644 --- a/src/themes.ts +++ b/src/themes.ts @@ -1,7 +1,7 @@ import { html, TemplateResult } from 'lit-element'; -import { SettingsRecord } from './Setting.js'; +import { Settings } from './Setting.js'; -export function getTheme(theme: SettingsRecord['theme']): TemplateResult { +export function getTheme(theme: Settings['theme']): TemplateResult { document.body.style.cssText = bodyStyles[theme]; return html` ${themes[theme]} @@ -61,12 +61,12 @@ export function getTheme(theme: SettingsRecord['theme']): TemplateResult { `; } -const bodyStyles: Record = { +const bodyStyles: Record = { dark: 'background: #073642', light: 'background: #eee8d5', }; -const themes: Record = { +const themes: Record = { light: html`