From ce13b0a36af5e550a04d34232c774a39ca267084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20Russ?= Date: Thu, 12 Dec 2024 20:15:01 +0100 Subject: [PATCH 1/7] add missing scl export --- packages/core/foundation.ts | 8 +++ packages/core/foundation/scl.ts | 89 +++++++++++++++++++++++++++++++++ packages/core/package.json | 3 +- 3 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 packages/core/foundation/scl.ts diff --git a/packages/core/foundation.ts b/packages/core/foundation.ts index c7cd164a43..60b3a30ad3 100644 --- a/packages/core/foundation.ts +++ b/packages/core/foundation.ts @@ -34,3 +34,11 @@ export type { EditCompletedEvent, EditCompletedDetail, } from './foundation/edit-completed-event.js'; + +/** @returns the cartesian product of `arrays` */ +export function crossProduct(...arrays: T[][]): T[][] { + return arrays.reduce( + (a, b) => a.flatMap(d => b.map(e => [d, e].flat())), + [[]] + ); +} diff --git a/packages/core/foundation/scl.ts b/packages/core/foundation/scl.ts new file mode 100644 index 0000000000..966d47eadd --- /dev/null +++ b/packages/core/foundation/scl.ts @@ -0,0 +1,89 @@ +import { crossProduct } from '../foundation.js'; + +function getDataModelChildren(parent: Element): Element[] { + if (['LDevice', 'Server'].includes(parent.tagName)) + return Array.from(parent.children).filter( + child => + child.tagName === 'LDevice' || + child.tagName === 'LN0' || + child.tagName === 'LN' + ); + + const id = + parent.tagName === 'LN' || parent.tagName === 'LN0' + ? parent.getAttribute('lnType') + : parent.getAttribute('type'); + + return Array.from( + parent.ownerDocument.querySelectorAll( + `LNodeType[id="${id}"] > DO, DOType[id="${id}"] > SDO, DOType[id="${id}"] > DA, DAType[id="${id}"] > BDA` + ) + ); +} + +export function existFcdaReference(fcda: Element, ied: Element): boolean { + const [ldInst, prefix, lnClass, lnInst, doName, daName, fc] = [ + 'ldInst', + 'prefix', + 'lnClass', + 'lnInst', + 'doName', + 'daName', + 'fc', + ].map(attr => fcda.getAttribute(attr)); + + const sinkLdInst = ied.querySelector(`LDevice[inst="${ldInst}"]`); + if (!sinkLdInst) return false; + + const prefixSelctors = prefix + ? [`[prefix="${prefix}"]`] + : ['[prefix=""]', ':not([prefix])']; + const lnInstSelectors = lnInst + ? [`[inst="${lnInst}"]`] + : ['[inst=""]', ':not([inst])']; + + const anyLnSelector = crossProduct( + ['LN0', 'LN'], + prefixSelctors, + [`[lnClass="${lnClass}"]`], + lnInstSelectors + ) + .map(strings => strings.join('')) + .join(','); + + const sinkAnyLn = ied.querySelector(anyLnSelector); + if (!sinkAnyLn) return false; + + const doNames = doName?.split('.'); + if (!doNames) return false; + + let parent: Element | undefined = sinkAnyLn; + for (const doNameAttr of doNames) { + parent = getDataModelChildren(parent).find( + child => child.getAttribute('name') === doNameAttr + ); + if (!parent) return false; + } + + const daNames = daName?.split('.'); + const someFcInSink = getDataModelChildren(parent).some( + da => da.getAttribute('fc') === fc + ); + if (!daNames && someFcInSink) return true; + if (!daNames) return false; + + let sinkFc = ''; + for (const daNameAttr of daNames) { + parent = getDataModelChildren(parent).find( + child => child.getAttribute('name') === daNameAttr + ); + + if (parent?.getAttribute('fc')) sinkFc = parent.getAttribute('fc')!; + + if (!parent) return false; + } + + if (sinkFc !== fc) return false; + + return true; +} diff --git a/packages/core/package.json b/packages/core/package.json index 8c5eba0ffb..71fd653958 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -13,6 +13,7 @@ ], "exports": { ".": "./dist/foundation.js", + "./foundation/scl.js": "./dist/foundation/scl.js", "./foundation/deprecated/editor.js": "./dist/foundation/deprecated/editor.js", "./foundation/deprecated/open-event.js": "./dist/foundation/deprecated/open-event.js", "./foundation/deprecated/settings.js": "./dist/foundation/deprecated/settings.js", @@ -159,4 +160,4 @@ "prettier --write" ] } -} +} \ No newline at end of file From 02fc07763bbe78711d41602ef51ef41ce6e878a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20Russ?= Date: Thu, 12 Dec 2024 20:15:58 +0100 Subject: [PATCH 2/7] reef: extract util functions --- packages/openscd/src/addons/Layout.ts | 33 +- packages/openscd/src/open-scd.ts | 455 ++++++++++++++------------ packages/openscd/src/plugin.ts | 1 + packages/openscd/src/plugins.ts | 8 + 4 files changed, 281 insertions(+), 216 deletions(-) diff --git a/packages/openscd/src/addons/Layout.ts b/packages/openscd/src/addons/Layout.ts index 41d56a8cd7..fec380051a 100644 --- a/packages/openscd/src/addons/Layout.ts +++ b/packages/openscd/src/addons/Layout.ts @@ -52,7 +52,7 @@ import '@material/mwc-dialog'; import '@material/mwc-switch'; import '@material/mwc-select'; import '@material/mwc-textfield'; -import { EditCompletedEvent } from '@openscd/core'; + @customElement('oscd-layout') export class OscdLayout extends LitElement { @@ -61,6 +61,7 @@ export class OscdLayout extends LitElement { return html`
this.pluginDownloadUI.show()} + @activate-tab=${this.handleActivatedEditorTabByEvent} > ${this.renderHeader()} ${this.renderAside()} ${this.renderContent()} @@ -322,6 +323,9 @@ export class OscdLayout extends LitElement { icon: plugin.icon || pluginIcons['menu'], name: plugin.name, action: ae => { + const targetMenuElement = (ae.target).items[ae.detail.index]; + const nextElement = targetMenuElement.nextElementSibling; + this.dispatchEvent( newPendingStateEvent( (( @@ -367,7 +371,6 @@ export class OscdLayout extends LitElement { private renderMenuItem(me: MenuItem | 'divider'): TemplateResult { if (me === 'divider') { return html`
  • `; } if (me.actionItem){ return html``; } - return html` (this.activeTab = e.detail.index)}> + ${activeEditors} ${renderEditorContent(this.editors, this.activeTab, this.doc)} @@ -491,6 +497,27 @@ export class OscdLayout extends LitElement { } } + private handleActivatedEditorTabByUser(e: CustomEvent): void { + const tabIndex = e.detail.index; + this.activateTab(tabIndex); + } + + /** + * + * + * @param e Custom event, where the detail is the index number of the tab + */ + // Note: this function is for now the same as `handleActivatedEditorTabByUser` + // I would keep it spearated for now, because I think they will be different. + private handleActivatedEditorTabByEvent(e: CustomEvent<{index:number}>): void { + const tabIndex = e.detail.index + this.activateTab(tabIndex) + } + + private activateTab(index: number){ + this.activeTab = index; + } + /** * Renders the landing buttons (open project and new project) * it no document loaded we display the menu item that are in the position diff --git a/packages/openscd/src/open-scd.ts b/packages/openscd/src/open-scd.ts index 5c47f77936..d252056dbd 100644 --- a/packages/openscd/src/open-scd.ts +++ b/packages/openscd/src/open-scd.ts @@ -1,3 +1,4 @@ +//#region import import { customElement, html, @@ -52,176 +53,45 @@ import { InstalledOfficialPlugin, MenuPosition, PluginKind, Plugin } from "./plu import { ConfigurePluginEvent, ConfigurePluginDetail, newConfigurePluginEvent } from './plugin.events.js'; import { newLogEvent } from '@openscd/core/foundation/deprecated/history'; -// HOSTING INTERFACES - -export interface MenuItem { - icon: string; - name: string; - hint?: string; - actionItem?: boolean; - action?: (event: CustomEvent) => void; - disabled?: () => boolean; - content?: TemplateResult; - kind: string; -} - -export interface Validator { - validate: () => Promise; -} - -export interface MenuPlugin { - run: () => Promise; -} - -export function newResetPluginsEvent(): CustomEvent { - return new CustomEvent('reset-plugins', { bubbles: true, composed: true }); -} - -export interface AddExternalPluginDetail { - plugin: Omit; -} - -export type AddExternalPluginEvent = CustomEvent; - -export function newAddExternalPluginEvent( - plugin: Omit -): AddExternalPluginEvent { - return new CustomEvent('add-external-plugin', { - bubbles: true, - composed: true, - detail: { plugin }, - }); -} - -export interface SetPluginsDetail { - indices: Set; -} - -export type SetPluginsEvent = CustomEvent; +//#endregion import -export function newSetPluginsEvent(indices: Set): SetPluginsEvent { - return new CustomEvent('set-plugins', { - bubbles: true, - composed: true, - detail: { indices }, - }); -} - -// PLUGGING INTERFACES -const pluginTags = new Map(); -/** - * Hashes `uri` using cyrb64 analogous to - * https://github.com/bryc/code/blob/master/jshash/experimental/cyrb53.js . - * @returns a valid customElement tagName containing the URI hash. - */ -function pluginTag(uri: string): string { - if (!pluginTags.has(uri)) { - let h1 = 0xdeadbeef, - h2 = 0x41c6ce57; - for (let i = 0, ch; i < uri.length; i++) { - ch = uri.charCodeAt(i); - h1 = Math.imul(h1 ^ ch, 2654435761); - h2 = Math.imul(h2 ^ ch, 1597334677); - } - h1 = - Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ - Math.imul(h2 ^ (h2 >>> 13), 3266489909); - h2 = - Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ - Math.imul(h1 ^ (h1 >>> 13), 3266489909); - pluginTags.set( - uri, - 'oscd-plugin' + - ((h2 >>> 0).toString(16).padStart(8, '0') + - (h1 >>> 0).toString(16).padStart(8, '0')) - ); - } - return pluginTags.get(uri)!; -} - -/** - * This is a template literal tag function. See: - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates - * - * Passes its arguments to LitElement's `html` tag after combining the first and - * last expressions with the first two and last two static strings. - * Throws unless the first and last expressions are identical strings. - * - * We need this to get around the expression location limitations documented in - * https://lit.dev/docs/templates/expressions/#expression-locations - * - * After upgrading to Lit 2 we can use their static HTML functions instead: - * https://lit.dev/docs/api/static-html/ - */ -function staticTagHtml( - oldStrings: ReadonlyArray, - ...oldArgs: unknown[] -): TemplateResult { - const args = [...oldArgs]; - const firstArg = args.shift(); - const lastArg = args.pop(); - - if (firstArg !== lastArg) - throw new Error( - `Opening tag <${firstArg}> does not match closing tag .` - ); - - const strings = [...oldStrings] as string[] & { raw: string[] }; - const firstString = strings.shift(); - const secondString = strings.shift(); - - const lastString = strings.pop(); - const penultimateString = strings.pop(); - - strings.unshift(`${firstString}${firstArg}${secondString}`); - strings.push(`${penultimateString}${lastArg}${lastString}`); - - return html(strings, ...args); -} - - -function withoutContent

    ( - plugin: P -): P { - return { ...plugin, content: undefined }; -} - -export const pluginIcons: Record = { - editor: 'tab', - menu: 'play_circle', - validator: 'rule_folder', - top: 'play_circle', - middle: 'play_circle', - bottom: 'play_circle', -}; - -const menuOrder: (PluginKind | MenuPosition)[] = [ - 'editor', - 'top', - 'validator', - 'middle', - 'bottom', -]; - -function menuCompare(a: Plugin, b: Plugin): -1 | 0 | 1 { - if (a.kind === b.kind && a.position === b.position) return 0; - const earlier = menuOrder.find(kind => - [a.kind, b.kind, a.position, b.position].includes(kind) - ); - return [a.kind, a.position].includes(earlier) ? -1 : 1; -} - -function compareNeedsDoc(a: Plugin, b: Plugin): -1 | 0 | 1 { - if (a.requireDoc === b.requireDoc) return 0; - return a.requireDoc ? 1 : -1; -} - -const loadedPlugins = new Set(); /** The `` custom element is the main entry point of the * Open Substation Configuration Designer. */ @customElement('open-scd') export class OpenSCD extends LitElement { + + render(): TemplateResult { + return html` + + + + + + + + + + + `; + } + + @property({ attribute: false }) doc: XMLDocument | null = null; /** The name of the current [[`doc`]] */ @@ -234,7 +104,7 @@ export class OpenSCD extends LitElement { editCount: -1, canRedo: false, canUndo: false, - } + } /** Object containing all *.nsdoc files and a function extracting element's label form them*/ @property({ attribute: false }) @@ -311,6 +181,7 @@ export class OpenSCD extends LitElement { connectedCallback(): void { super.connectedCallback(); + this.loadPluginsFromLocalStorage() this.addEventListener('reset-plugins', this.resetPlugins); this.addEventListener('set-plugins', (e: SetPluginsEvent) => { this.setPlugins(e.detail.indices); @@ -325,35 +196,7 @@ export class OpenSCD extends LitElement { }); } - render(): TemplateResult { - return html` - - - - - - - - - - - `; - } + private storePlugins(plugins: Array) { localStorage.setItem( @@ -462,9 +305,9 @@ export class OpenSCD extends LitElement { return allPlugnis } - private get sortedStoredPlugins(): Plugin[] { - - const mergedPlugins = this.storedPlugins.map(plugin => { + @state() private sortedStoredPlugins: Plugin[] = []; + private updateSortedStoredPlugins(newPlugins: Plugin[]) { + const mergedPlugins = newPlugins.map(plugin => { if (!plugin.official){ return plugin }; const officialPlugin = (builtinPlugins as Plugin[]) @@ -476,28 +319,35 @@ export class OpenSCD extends LitElement { ...plugin, }; }) - - - return mergedPlugins - .sort(compareNeedsDoc) - .sort(menuCompare); + this.sortedStoredPlugins = mergedPlugins; + // return mergedPlugins; + // return mergedPlugins + // .sort(compareNeedsDoc) + // .sort(menuCompare); } - private get storedPlugins(): Plugin[] { - const pluginsConfigStr = localStorage.getItem('plugins') ?? '[]' - const storedPlugins = JSON.parse(pluginsConfigStr) as Plugin[] - - const plugins = storedPlugins.map(plugin => { + @state() private storedPlugins: Plugin[] = []; + private updateStoredPlugins(newPlugins: Plugin[]) { + // const pluginsConfigStr = localStorage.getItem('plugins') ?? '[]' + // const storedPlugins = JSON.parse(pluginsConfigStr) as Plugin[] + const plugins = newPlugins.map(plugin => { const isInstalled = plugin.src && plugin.installed if(!isInstalled) { return plugin } return this.addContent(plugin) }) - return plugins + this.storedPlugins = plugins; + } + private loadPluginsFromLocalStorage() { + const pluginsConfigStr = localStorage.getItem('plugins') ?? '[]' + const pluginsConfig = JSON.parse(pluginsConfigStr) as Plugin[] + this.updateStoredPlugins(pluginsConfig) + this.updateSortedStoredPlugins(this.storedPlugins) } + protected get locale(): string { return navigator.language || 'en-US'; } @@ -519,7 +369,10 @@ export class OpenSCD extends LitElement { installed: indices.has(index) }; }); - this.storePlugins(newPlugins); + this.updateStoredPlugins(newPlugins); + + this.updateSortedStoredPlugins(this.sortedStoredPlugins); + this.storePlugins(this.sortedStoredPlugins); } private updatePlugins() { @@ -563,11 +416,13 @@ export class OpenSCD extends LitElement { } private addContent(plugin: Omit): Plugin { - const tag = pluginTag(plugin.src); + const tag = this.pluginTag(plugin.src); - if (!loadedPlugins.has(tag)) { - loadedPlugins.add(tag); - import(plugin.src).then(mod => customElements.define(tag, mod.default)); + if (!this.loadedPlugins.has(tag)) { + this.loadedPlugins.add(tag); + import(plugin.src).then((mod) => { + customElements.define(tag, mod.default) + }) } return { @@ -581,6 +436,7 @@ export class OpenSCD extends LitElement { .nsdoc=${this.nsdoc} .docs=${this.docs} .locale=${this.locale} + .plugins=${this.sortedStoredPlugins} class="${classMap({ plugin: true, menu: plugin.kind === 'menu', @@ -590,6 +446,42 @@ export class OpenSCD extends LitElement { >`, }; } + + @state() private loadedPlugins = new Set(); + + // PLUGGING INTERFACES + @state() private pluginTags = new Map(); + /** + * Hashes `uri` using cyrb64 analogous to + * https://github.com/bryc/code/blob/master/jshash/experimental/cyrb53.js . + * @returns a valid customElement tagName containing the URI hash. + */ + private pluginTag(uri: string): string { + if (!this.pluginTags.has(uri)) { + let h1 = 0xdeadbeef, + h2 = 0x41c6ce57; + for (let i = 0, ch; i < uri.length; i++) { + ch = uri.charCodeAt(i); + h1 = Math.imul(h1 ^ ch, 2654435761); + h2 = Math.imul(h2 ^ ch, 1597334677); + } + h1 = + Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ + Math.imul(h2 ^ (h2 >>> 13), 3266489909); + h2 = + Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ + Math.imul(h1 ^ (h1 >>> 13), 3266489909); + this.pluginTags.set( + uri, + 'oscd-plugin' + + ((h2 >>> 0).toString(16).padStart(8, '0') + + (h1 >>> 0).toString(16).padStart(8, '0')) + ); + } + return this.pluginTags.get(uri)!; + } + + } declare global { @@ -599,3 +491,140 @@ declare global { 'set-plugins': CustomEvent; } } + + +// HOSTING INTERFACES + +export interface MenuItem { + icon: string; + name: string; + hint?: string; + actionItem?: boolean; + action?: (event: CustomEvent) => void; + disabled?: () => boolean; + content?: TemplateResult; + kind: string; +} + +export interface Validator { + validate: () => Promise; +} + +export interface MenuPlugin { + run: () => Promise; +} + +export function newResetPluginsEvent(): CustomEvent { + return new CustomEvent('reset-plugins', { bubbles: true, composed: true }); +} + +export interface AddExternalPluginDetail { + plugin: Omit; +} + +export type AddExternalPluginEvent = CustomEvent; + +export function newAddExternalPluginEvent( + plugin: Omit +): AddExternalPluginEvent { + return new CustomEvent('add-external-plugin', { + bubbles: true, + composed: true, + detail: { plugin }, + }); +} + +export interface SetPluginsDetail { + indices: Set; +} + +export type SetPluginsEvent = CustomEvent; + +export function newSetPluginsEvent(indices: Set): SetPluginsEvent { + return new CustomEvent('set-plugins', { + bubbles: true, + composed: true, + detail: { indices }, + }); +} + + + + +/** + * This is a template literal tag function. See: + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates + * + * Passes its arguments to LitElement's `html` tag after combining the first and + * last expressions with the first two and last two static strings. + * Throws unless the first and last expressions are identical strings. + * + * We need this to get around the expression location limitations documented in + * https://lit.dev/docs/templates/expressions/#expression-locations + * + * After upgrading to Lit 2 we can use their static HTML functions instead: + * https://lit.dev/docs/api/static-html/ + */ +function staticTagHtml( + oldStrings: ReadonlyArray, + ...oldArgs: unknown[] +): TemplateResult { + const args = [...oldArgs]; + const firstArg = args.shift(); + const lastArg = args.pop(); + + if (firstArg !== lastArg) + throw new Error( + `Opening tag <${firstArg}> does not match closing tag .` + ); + + const strings = [...oldStrings] as string[] & { raw: string[] }; + const firstString = strings.shift(); + const secondString = strings.shift(); + + const lastString = strings.pop(); + const penultimateString = strings.pop(); + + strings.unshift(`${firstString}${firstArg}${secondString}`); + strings.push(`${penultimateString}${lastArg}${lastString}`); + + return html(strings, ...args); +} + + +function withoutContent

    ( + plugin: P +): P { + return { ...plugin, content: undefined }; +} + +export const pluginIcons: Record = { + editor: 'tab', + menu: 'play_circle', + validator: 'rule_folder', + top: 'play_circle', + middle: 'play_circle', + bottom: 'play_circle', +}; + +const menuOrder: (PluginKind | MenuPosition)[] = [ + 'editor', + 'top', + 'validator', + 'middle', + 'bottom', +]; + +function menuCompare(a: Plugin, b: Plugin): -1 | 0 | 1 { + if (a.kind === b.kind && a.position === b.position) return 0; + const earlier = menuOrder.find(kind => + [a.kind, b.kind, a.position, b.position].includes(kind) + ); + return [a.kind, a.position].includes(earlier) ? -1 : 1; +} + +function compareNeedsDoc(a: Plugin, b: Plugin): -1 | 0 | 1 { + if (a.requireDoc === b.requireDoc) return 0; + return a.requireDoc ? 1 : -1; +} + diff --git a/packages/openscd/src/plugin.ts b/packages/openscd/src/plugin.ts index 4b9dfba41e..cfc4ffddb7 100644 --- a/packages/openscd/src/plugin.ts +++ b/packages/openscd/src/plugin.ts @@ -23,3 +23,4 @@ export type InstalledOfficialPlugin = { export type PluginKind = 'editor' | 'menu' | 'validator'; export const menuPosition = ['top', 'middle', 'bottom'] as const; export type MenuPosition = (typeof menuPosition)[number]; + diff --git a/packages/openscd/src/plugins.ts b/packages/openscd/src/plugins.ts index 0c33dfa6c1..fb08cea0ec 100644 --- a/packages/openscd/src/plugins.ts +++ b/packages/openscd/src/plugins.ts @@ -3,6 +3,14 @@ function generatePluginPath(plugin: string): string { } export const officialPlugins = [ + { + name: 'Launcher', + src: 'http://localhost:50714/index.js', + icon: 'launch', + default: true, + kind: 'editor', + requireDoc: true, + }, { name: 'IED', src: generatePluginPath('plugins/src/editors/IED.js'), From 7ea53ed73b87349fa7df9fcb29bd04e7efe68793 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20Russ?= Date: Fri, 13 Dec 2024 17:25:13 +0100 Subject: [PATCH 3/7] feat: add event to activate editor tab --- packages/openscd/src/addons/Layout.ts | 68 +++++++---- packages/openscd/src/open-scd.ts | 170 +++++++++++++++----------- packages/openscd/src/plugin.ts | 2 +- 3 files changed, 141 insertions(+), 99 deletions(-) diff --git a/packages/openscd/src/addons/Layout.ts b/packages/openscd/src/addons/Layout.ts index fec380051a..1a8db74e9a 100644 --- a/packages/openscd/src/addons/Layout.ts +++ b/packages/openscd/src/addons/Layout.ts @@ -52,6 +52,7 @@ import '@material/mwc-dialog'; import '@material/mwc-switch'; import '@material/mwc-select'; import '@material/mwc-textfield'; +import { nothing } from 'lit'; @customElement('oscd-layout') @@ -61,7 +62,7 @@ export class OscdLayout extends LitElement { return html`

    this.pluginDownloadUI.show()} - @activate-tab=${this.handleActivatedEditorTabByEvent} + @oscd-activate-editor=${this.handleActivateEditorByEvent} > ${this.renderHeader()} ${this.renderAside()} ${this.renderContent()} @@ -156,6 +157,7 @@ export class OscdLayout extends LitElement { }, disabled: (): boolean => !this.historyState.canUndo, kind: 'static', + content: () => html``, }, { icon: 'redo', @@ -166,6 +168,7 @@ export class OscdLayout extends LitElement { }, disabled: (): boolean => !this.historyState.canRedo, kind: 'static', + content: () => html``, }, ...validators, { @@ -176,6 +179,7 @@ export class OscdLayout extends LitElement { this.dispatchEvent(newHistoryUIEvent(true, HistoryUIKind.log)); }, kind: 'static', + content: () => html``, }, { icon: 'history', @@ -185,6 +189,7 @@ export class OscdLayout extends LitElement { this.dispatchEvent(newHistoryUIEvent(true, HistoryUIKind.history)); }, kind: 'static', + content: () => html``, }, { icon: 'rule', @@ -194,6 +199,7 @@ export class OscdLayout extends LitElement { this.dispatchEvent(newHistoryUIEvent(true, HistoryUIKind.diagnostic)); }, kind: 'static', + content: () => html``, }, 'divider', ...middleMenu, @@ -204,6 +210,7 @@ export class OscdLayout extends LitElement { this.dispatchEvent(newSettingsUIEvent(true)); }, kind: 'static', + content: () => html``, }, ...bottomMenu, { @@ -211,6 +218,7 @@ export class OscdLayout extends LitElement { name: 'plugins.heading', action: (): void => this.pluginUI.show(), kind: 'static', + content: () => html``, }, ]; } @@ -337,7 +345,10 @@ export class OscdLayout extends LitElement { ); }, disabled: (): boolean => plugin.requireDoc! && this.doc === null, - content: plugin.content, + content: () => { + if(plugin.content){ return plugin.content(); } + return nothing + }, kind: kind, } }) @@ -362,15 +373,18 @@ export class OscdLayout extends LitElement { ); }, disabled: (): boolean => this.doc === null, - content: plugin.content, + content: plugin.content ?? (() => html``), kind: 'validator', } }); } private renderMenuItem(me: MenuItem | 'divider'): TemplateResult { - if (me === 'divider') { return html`
  • `; } - if (me.actionItem){ return html``; } + const isDivider = me === 'divider'; + const hasActionItem = me !== 'divider' && me.actionItem; + + if (isDivider) { return html`
  • `; } + if (hasActionItem){ return html``; } return html` ${me.hint}` : ''} - ${me.content ?? ''} + ${me.content ? me.content() : nothing} `; } @@ -459,18 +473,23 @@ export class OscdLayout extends LitElement { } + private calcActiveEditors(){ + const hasActiveDoc = Boolean(this.doc); + + return this.editors + .filter(editor => { + // this is necessary because `requireDoc` can be undefined + // and that is not the same as false + const doesNotRequireDoc = editor.requireDoc === false + return doesNotRequireDoc || hasActiveDoc + }) + } + /** Renders the enabled editor plugins and a tab bar to switch between them*/ protected renderContent(): TemplateResult { - const hasActiveDoc = Boolean(this.doc); - const activeEditors = this.editors - .filter(editor => { - // this is necessary because `requireDoc` can be undefined - // and that is not the same as false - const doesNotRequireDoc = editor.requireDoc === false - return doesNotRequireDoc || hasActiveDoc - }) - .map(this.renderEditorTab) + const activeEditors = this.calcActiveEditors() + .map(this.renderEditorTab) const hasActiveEditors = activeEditors.length > 0; if(!hasActiveEditors){ return html``; } @@ -493,7 +512,7 @@ export class OscdLayout extends LitElement { const content = editor?.content; if(!content) { return html`` } - return html`${content}`; + return html`${content()}`; } } @@ -502,16 +521,13 @@ export class OscdLayout extends LitElement { this.activateTab(tabIndex); } - /** - * - * - * @param e Custom event, where the detail is the index number of the tab - */ - // Note: this function is for now the same as `handleActivatedEditorTabByUser` - // I would keep it spearated for now, because I think they will be different. - private handleActivatedEditorTabByEvent(e: CustomEvent<{index:number}>): void { - const tabIndex = e.detail.index - this.activateTab(tabIndex) + private handleActivateEditorByEvent(e: CustomEvent<{name: string, src: string}>): void { + const {name, src} = e.detail; + const editors = this.calcActiveEditors() + const wantedEditorIndex = editors.findIndex(editor => editor.name === name || editor.src === src) + if(wantedEditorIndex < 0){ return; } // TODO: log error + + this.activateTab(wantedEditorIndex); } private activateTab(index: number){ diff --git a/packages/openscd/src/open-scd.ts b/packages/openscd/src/open-scd.ts index d252056dbd..fc3a2645e0 100644 --- a/packages/openscd/src/open-scd.ts +++ b/packages/openscd/src/open-scd.ts @@ -81,7 +81,7 @@ export class OpenSCD extends LitElement { .docName=${this.docName} .editCount=${this.historyState.editCount} .historyState=${this.historyState} - .plugins=${this.sortedStoredPlugins} + .plugins=${this.storedPlugins} > @@ -181,15 +181,13 @@ export class OpenSCD extends LitElement { connectedCallback(): void { super.connectedCallback(); - this.loadPluginsFromLocalStorage() + this.loadPlugins() + + // TODO: let Lit handle the event listeners, move to render() this.addEventListener('reset-plugins', this.resetPlugins); this.addEventListener('set-plugins', (e: SetPluginsEvent) => { this.setPlugins(e.detail.indices); }); - - this.updatePlugins(); - this.requestUpdate(); - this.addEventListener(historyStateEvent, (e: CustomEvent) => { this.historyState = e.detail; this.requestUpdate(); @@ -199,11 +197,8 @@ export class OpenSCD extends LitElement { private storePlugins(plugins: Array) { - localStorage.setItem( - 'plugins', - JSON.stringify(plugins.map(withoutContent)) - ); - this.requestUpdate(); + const pluginConfigs = JSON.stringify(plugins.map(withoutContent)) + localStorage.setItem('plugins', pluginConfigs); } /** @@ -264,9 +259,8 @@ export class OpenSCD extends LitElement { this.storePlugins( (builtinPlugins as Plugin[]).concat(this.parsedPlugins).map(plugin => { return { - src: plugin.src, + ...plugin, installed: plugin.default ?? false, - official: true, }; }) ); @@ -305,31 +299,12 @@ export class OpenSCD extends LitElement { return allPlugnis } - @state() private sortedStoredPlugins: Plugin[] = []; - private updateSortedStoredPlugins(newPlugins: Plugin[]) { - const mergedPlugins = newPlugins.map(plugin => { - if (!plugin.official){ return plugin }; - - const officialPlugin = (builtinPlugins as Plugin[]) - .concat(this.parsedPlugins) - .find(needle => needle.src === plugin.src); - - return { - ...officialPlugin, - ...plugin, - }; - }) - this.sortedStoredPlugins = mergedPlugins; - // return mergedPlugins; - // return mergedPlugins - // .sort(compareNeedsDoc) - // .sort(menuCompare); - } @state() private storedPlugins: Plugin[] = []; private updateStoredPlugins(newPlugins: Plugin[]) { - // const pluginsConfigStr = localStorage.getItem('plugins') ?? '[]' - // const storedPlugins = JSON.parse(pluginsConfigStr) as Plugin[] + // + // Generate content of each plugin + // const plugins = newPlugins.map(plugin => { const isInstalled = plugin.src && plugin.installed if(!isInstalled) { return plugin } @@ -337,14 +312,36 @@ export class OpenSCD extends LitElement { return this.addContent(plugin) }) - this.storedPlugins = plugins; + // + // Merge built-in plugins + // + const mergedPlugins = plugins.map(plugin => { + const isBuiltIn = !plugin?.official + if (!isBuiltIn){ return plugin }; + + const builtInPlugin = [...builtinPlugins, ...this.parsedPlugins] + .find(p => p.src === plugin.src); + + return { + ...builtInPlugin, + ...plugin, + }; + }) + + + this.storedPlugins = mergedPlugins; + this.storePlugins(this.storedPlugins); + } + + private getPluginConfigsFromLocalStorage(): Plugin[] { + const pluginsConfigStr = localStorage.getItem('plugins') ?? '[]' + return JSON.parse(pluginsConfigStr) as Plugin[] } private loadPluginsFromLocalStorage() { const pluginsConfigStr = localStorage.getItem('plugins') ?? '[]' const pluginsConfig = JSON.parse(pluginsConfigStr) as Plugin[] this.updateStoredPlugins(pluginsConfig) - this.updateSortedStoredPlugins(this.storedPlugins) } @@ -363,48 +360,76 @@ export class OpenSCD extends LitElement { } private setPlugins(indices: Set) { - const newPlugins = this.sortedStoredPlugins.map((plugin, index) => { + const newPlugins = this.storedPlugins.map((plugin, index) => { return { ...plugin, installed: indices.has(index) }; }); this.updateStoredPlugins(newPlugins); - - this.updateSortedStoredPlugins(this.sortedStoredPlugins); - this.storePlugins(this.sortedStoredPlugins); } - private updatePlugins() { + private loadPlugins(){ + const localPluginConfigs = this.getPluginConfigsFromLocalStorage() - const stored: Plugin[] = this.storedPlugins; - const officialStored = stored.filter(p => p.official); - const newOfficial: Array = ( - builtinPlugins as Plugin[] - ) - .concat(this.parsedPlugins) - .filter(p => !officialStored.find(o => o.src === p.src)) - .map(plugin => { - return { - src: plugin.src, - installed: plugin.default ?? false, - official: true as const, - }; - }); + const overwritesOfBultInPlugins = localPluginConfigs.filter((p) => { + return builtinPlugins.some(b => b.src === p.src) + }) - const oldOfficial = officialStored.filter( - p => - !(builtinPlugins as Plugin[]) - .concat(this.parsedPlugins) - .find(o => p.src === o.src) - ); - const newPlugins: Array = stored.filter( - p => !oldOfficial.find(o => p.src === o.src) - ); - newOfficial.map(p => newPlugins.push(p)); - this.storePlugins(newPlugins); + const userInstalledPlugins = localPluginConfigs.filter((p) => { + return !builtinPlugins.some(b => b.src === p.src) + }) + + const mergedBuiltInPlugins = builtinPlugins.map((builtInPlugin) => { + const noopOverwrite = {} + const overwrite = overwritesOfBultInPlugins + .find(p => p.src === builtInPlugin.src) + ?? noopOverwrite + + return { + ...builtInPlugin, + ...overwrite, + installed: true, // TODO: is this correct? should we decide it based on something? + } + }) + + const mergedPlugins = [...mergedBuiltInPlugins, ...userInstalledPlugins] + + // TODO: kind is string and enum, figour out later + // @ts-expect-error + this.updateStoredPlugins(mergedPlugins) } + // private updatePlugins() { + + // const stored: Plugin[] = this.storedPlugins; + // const officialStored = stored.filter(p => p.official); + // const newOfficial: Array = ( + // builtinPlugins as Plugin[] + // ) + // .concat(this.parsedPlugins) + // .filter(p => !officialStored.find(o => o.src === p.src)) + // .map(plugin => { + // return { + // src: plugin.src, + // installed: plugin.default ?? false, + // official: true as const, + // }; + // }); + + // const oldOfficial = officialStored.filter( + // p => + // !(builtinPlugins as Plugin[]) + // .concat(this.parsedPlugins) + // .find(o => p.src === o.src) + // ); + // const newPlugins: Array = stored.filter( + // p => !oldOfficial.find(o => p.src === o.src) + // ); + // newOfficial.map(p => newPlugins.push(p)); + // this.storePlugins(newPlugins); + // } + private async addExternalPlugin( plugin: Omit ): Promise { @@ -424,26 +449,27 @@ export class OpenSCD extends LitElement { customElements.define(tag, mod.default) }) } - return { ...plugin, - content: staticTagHtml`<${tag} + content: () => { + return staticTagHtml`<${tag} .doc=${this.doc} .docName=${this.docName} .editCount=${this.historyState.editCount} + .plugins=${this.storedPlugins} .docId=${this.docId} .pluginId=${plugin.src} .nsdoc=${this.nsdoc} .docs=${this.docs} .locale=${this.locale} - .plugins=${this.sortedStoredPlugins} class="${classMap({ plugin: true, menu: plugin.kind === 'menu', validator: plugin.kind === 'validator', editor: plugin.kind === 'editor', })}" - >`, + >` + }, }; } @@ -502,7 +528,7 @@ export interface MenuItem { actionItem?: boolean; action?: (event: CustomEvent) => void; disabled?: () => boolean; - content?: TemplateResult; + content: () => TemplateResult; kind: string; } diff --git a/packages/openscd/src/plugin.ts b/packages/openscd/src/plugin.ts index cfc4ffddb7..0ff10bb789 100644 --- a/packages/openscd/src/plugin.ts +++ b/packages/openscd/src/plugin.ts @@ -10,7 +10,7 @@ export type Plugin = { position?: MenuPosition; installed: boolean; official?: boolean; - content?: TemplateResult; + content?: () => TemplateResult; }; export type InstalledOfficialPlugin = { From 3891a24c3e85524a6896d1147cfcc4d6672394eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20Russ?= Date: Fri, 13 Dec 2024 18:46:10 +0100 Subject: [PATCH 4/7] feat: add api to programmatically run menu plugins --- packages/openscd/src/addons/Layout.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/openscd/src/addons/Layout.ts b/packages/openscd/src/addons/Layout.ts index 1a8db74e9a..68055f25f7 100644 --- a/packages/openscd/src/addons/Layout.ts +++ b/packages/openscd/src/addons/Layout.ts @@ -63,6 +63,7 @@ export class OscdLayout extends LitElement {
    this.pluginDownloadUI.show()} @oscd-activate-editor=${this.handleActivateEditorByEvent} + @oscd-run-menu=${this.handleRunMenuByEvent} > ${this.renderHeader()} ${this.renderAside()} ${this.renderContent()} @@ -331,9 +332,6 @@ export class OscdLayout extends LitElement { icon: plugin.icon || pluginIcons['menu'], name: plugin.name, action: ae => { - const targetMenuElement = (ae.target).items[ae.detail.index]; - const nextElement = targetMenuElement.nextElementSibling; - this.dispatchEvent( newPendingStateEvent( (( @@ -347,7 +345,7 @@ export class OscdLayout extends LitElement { disabled: (): boolean => plugin.requireDoc! && this.doc === null, content: () => { if(plugin.content){ return plugin.content(); } - return nothing + return html``; }, kind: kind, } @@ -390,6 +388,7 @@ export class OscdLayout extends LitElement { class="${me.kind}" iconid="${me.icon}" graphic="icon" + data-name="${me.name}" .disabled=${me.disabled?.() || !me.action} >${me.icon} ${get(me.name)} @@ -534,6 +533,17 @@ export class OscdLayout extends LitElement { this.activeTab = index; } + private handleRunMenuByEvent(e: CustomEvent<{name: string}>): void { + + // TODO: this is a workaround, fix it + this.menuUI.open = true; + const menuEntry = this.menuUI.querySelector(`[data-name="${e.detail.name}"]`) as HTMLElement + const menuElement = menuEntry.nextElementSibling + if(!menuElement){ return; } // TODO: log error + + (menuElement as unknown as MenuPlugin).run() + } + /** * Renders the landing buttons (open project and new project) * it no document loaded we display the menu item that are in the position From 430e749c83a8e5d35ec1ddc9d66d3c1b03c8bc3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20Russ?= Date: Sun, 15 Dec 2024 21:35:18 +0100 Subject: [PATCH 5/7] fix: tests --- package-lock.json | 2 +- packages/openscd/src/open-scd.ts | 14 +- packages/openscd/src/plugin.ts | 2 +- .../__snapshots__/open-scd.test.snap.js | 704 +++++++++++++----- packages/openscd/test/unit/Plugging.test.ts | 16 +- 5 files changed, 516 insertions(+), 222 deletions(-) diff --git a/package-lock.json b/package-lock.json index 589cc66969..a8ef36e7fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30515,7 +30515,7 @@ }, "packages/core": { "name": "@openscd/core", - "version": "0.1.2", + "version": "0.1.3", "license": "Apache-2.0", "dependencies": { "@lit/localize": "^0.11.4", diff --git a/packages/openscd/src/open-scd.ts b/packages/openscd/src/open-scd.ts index fc3a2645e0..4b153cbc0e 100644 --- a/packages/openscd/src/open-scd.ts +++ b/packages/openscd/src/open-scd.ts @@ -196,10 +196,7 @@ export class OpenSCD extends LitElement { - private storePlugins(plugins: Array) { - const pluginConfigs = JSON.stringify(plugins.map(withoutContent)) - localStorage.setItem('plugins', pluginConfigs); - } + /** * @@ -328,11 +325,14 @@ export class OpenSCD extends LitElement { }; }) - - this.storedPlugins = mergedPlugins; - this.storePlugins(this.storedPlugins); + this.storePlugins(mergedPlugins); } + private storePlugins(plugins: Plugin[]) { + this.storedPlugins = plugins + const pluginConfigs = JSON.stringify(plugins.map(withoutContent)) + localStorage.setItem('plugins', pluginConfigs); + } private getPluginConfigsFromLocalStorage(): Plugin[] { const pluginsConfigStr = localStorage.getItem('plugins') ?? '[]' return JSON.parse(pluginsConfigStr) as Plugin[] diff --git a/packages/openscd/src/plugin.ts b/packages/openscd/src/plugin.ts index 0ff10bb789..60e4fc3a02 100644 --- a/packages/openscd/src/plugin.ts +++ b/packages/openscd/src/plugin.ts @@ -13,7 +13,7 @@ export type Plugin = { content?: () => TemplateResult; }; -export type InstalledOfficialPlugin = { +export type InstalledOfficialPlugin = Plugin & { src: string; official: true; installed: boolean; diff --git a/packages/openscd/test/integration/__snapshots__/open-scd.test.snap.js b/packages/openscd/test/integration/__snapshots__/open-scd.test.snap.js index a43b37e707..a4ef978ecb 100644 --- a/packages/openscd/test/integration/__snapshots__/open-scd.test.snap.js +++ b/packages/openscd/test/integration/__snapshots__/open-scd.test.snap.js @@ -85,6 +85,7 @@ snapshots["open-scd renders menu plugins passed down as props and it looks like + + developer_board + + + Create Virtual IED + + + + + + + play_circle + + + Update desc (ABB) + + + + + + + play_circle + + + Update desc (SEL) + + + + + + + + sim_card_download + + + Export Communication Section + + + +
  • - help + history_toggle_off - Help + Show SCL History - - + + - history_toggle_off + help - Show SCL History + Help - - + +
  • + + launch + + Launcher + + @@ -417,7 +523,7 @@ snapshots["open-scd renders menu plugins passed down as props and it looks like @@ -448,11 +555,12 @@ snapshots["open-scd renders menu plugins passed down as props and it looks like @@ -463,11 +571,12 @@ snapshots["open-scd renders menu plugins passed down as props and it looks like @@ -478,7 +587,7 @@ snapshots["open-scd renders menu plugins passed down as props and it looks like @@ -509,11 +619,12 @@ snapshots["open-scd renders menu plugins passed down as props and it looks like @@ -524,7 +635,7 @@ snapshots["open-scd renders menu plugins passed down as props and it looks like @@ -571,7 +683,7 @@ snapshots["open-scd renders menu plugins passed down as props and it looks like @@ -602,11 +715,12 @@ snapshots["open-scd renders menu plugins passed down as props and it looks like @@ -640,7 +754,7 @@ snapshots["open-scd renders menu plugins passed down as props and it looks like
  • - - link - - Top Mock Plugin - - - - link - - Middle Mock Plugin - - @@ -793,7 +878,7 @@ snapshots["open-scd renders menu plugins passed down as props and it looks like @@ -824,11 +910,12 @@ snapshots["open-scd renders menu plugins passed down as props and it looks like @@ -839,7 +926,7 @@ snapshots["open-scd renders menu plugins passed down as props and it looks like @@ -908,22 +996,23 @@ snapshots["open-scd renders menu plugins passed down as props and it looks like - link + history_toggle_off - Bottom Mock Plugin + Show SCL History Help - - - history_toggle_off - - Show SCL History - + + developer_board + + + Create Virtual IED + +
    + + + + + play_circle + + + Update desc (ABB) + + + + + + + play_circle + + + Update desc (SEL) + + + + + + + + sim_card_download + + + Export Communication Section + + + +
  • - help + history_toggle_off - Help + Show SCL History - - + + - history_toggle_off + help - Show SCL History + Help - - + +
  • + + launch + + Launcher + + @@ -1514,7 +1693,7 @@ snapshots["open-scd renders editor plugins passed down as props and it looks lik @@ -1545,11 +1725,12 @@ snapshots["open-scd renders editor plugins passed down as props and it looks lik @@ -1560,11 +1741,12 @@ snapshots["open-scd renders editor plugins passed down as props and it looks lik @@ -1575,7 +1757,7 @@ snapshots["open-scd renders editor plugins passed down as props and it looks lik @@ -1606,11 +1789,12 @@ snapshots["open-scd renders editor plugins passed down as props and it looks lik @@ -1621,7 +1805,7 @@ snapshots["open-scd renders editor plugins passed down as props and it looks lik @@ -1668,7 +1853,7 @@ snapshots["open-scd renders editor plugins passed down as props and it looks lik @@ -1699,11 +1885,12 @@ snapshots["open-scd renders editor plugins passed down as props and it looks lik @@ -1712,21 +1899,6 @@ snapshots["open-scd renders editor plugins passed down as props and it looks lik Cleanup - - - link - - Mock Editor Plugin - @@ -1875,7 +2048,7 @@ snapshots["open-scd renders editor plugins passed down as props and it looks lik @@ -1906,11 +2080,12 @@ snapshots["open-scd renders editor plugins passed down as props and it looks lik @@ -1921,7 +2096,7 @@ snapshots["open-scd renders editor plugins passed down as props and it looks lik @@ -1990,35 +2166,35 @@ snapshots["open-scd renders editor plugins passed down as props and it looks lik
  • - help + history_toggle_off - Help + Show SCL History - history_toggle_off + help - Show SCL History + Help + + developer_board + + + Create Virtual IED + +
    + + + + + play_circle + + + Update desc (ABB) + + + + + + + play_circle + + + Update desc (SEL) + + + + + + + + sim_card_download + + + Export Communication Section + + + +
  • - help + history_toggle_off - Help + Show SCL History - - + + - history_toggle_off + help - Show SCL History + Help - - + +
  • + + launch + + Launcher + + @@ -2581,7 +2863,7 @@ snapshots["open-scd layout looks like its snapshot"] = @@ -2612,11 +2895,12 @@ snapshots["open-scd layout looks like its snapshot"] = @@ -2627,11 +2911,12 @@ snapshots["open-scd layout looks like its snapshot"] = @@ -2642,7 +2927,7 @@ snapshots["open-scd layout looks like its snapshot"] = @@ -2673,11 +2959,12 @@ snapshots["open-scd layout looks like its snapshot"] = @@ -2688,7 +2975,7 @@ snapshots["open-scd layout looks like its snapshot"] = @@ -2735,7 +3023,7 @@ snapshots["open-scd layout looks like its snapshot"] = @@ -2766,11 +3055,12 @@ snapshots["open-scd layout looks like its snapshot"] = @@ -2804,7 +3094,7 @@ snapshots["open-scd layout looks like its snapshot"] =
  • @@ -2927,7 +3218,7 @@ snapshots["open-scd layout looks like its snapshot"] = @@ -2958,11 +3250,12 @@ snapshots["open-scd layout looks like its snapshot"] = @@ -2973,7 +3266,7 @@ snapshots["open-scd layout looks like its snapshot"] = @@ -3042,35 +3336,35 @@ snapshots["open-scd layout looks like its snapshot"] = - help + history_toggle_off - Help + Show SCL History - history_toggle_off + help - Show SCL History + Help { }); it('stores default plugins on load', () =>{ - expect(element.layout).property('editors').to.have.lengthOf(6) + expect(element.layout).property('editors').to.have.lengthOf(15) }); it('has Locale property', async () => { @@ -66,7 +66,7 @@ describe('OpenSCD-Plugin', () => { it('disables deselected plugins', async () => { firstEditorPlugin.click(); await element.updateComplete; - expect(element.layout).property('editors').to.have.lengthOf(5); + expect(element.layout).property('editors').to.have.lengthOf(14); }); it('enables selected plugins', async () => { @@ -74,7 +74,7 @@ describe('OpenSCD-Plugin', () => { await element.updateComplete; (element.layout.pluginList.firstElementChild).click(); await element.updateComplete; - expect(element.layout).property('editors').to.have.lengthOf(6); + expect(element.layout).property('editors').to.have.lengthOf(15); }); it('resets plugins to default on reset button click', async () => { @@ -82,7 +82,7 @@ describe('OpenSCD-Plugin', () => { await element.updateComplete; resetAction.click(); await element.updateComplete; - expect(element.layout).property('editors').to.have.lengthOf(6); + expect(element.layout).property('editors').to.have.lengthOf(7); }); it('opens the custom plugin dialog on add button click', async () => { @@ -173,7 +173,7 @@ describe('OpenSCD-Plugin', () => { await name.updateComplete; primaryAction.click(); await element.updateComplete; - expect(element.layout.editors).to.have.lengthOf(7); + expect(element.layout.editors).to.have.lengthOf(16); }); it('adds a new menu kind plugin on add button click', async () => { @@ -374,7 +374,7 @@ describe('OpenSCD-Plugin', () => { name: "plugin to remove, but wrong kind", kind: "editor", src: "https://example.com/plugin-to-remove.js", - installed: false, + installed: true, }], eventDetails: { name: "plugin to remove, but wrong kind", @@ -385,7 +385,7 @@ describe('OpenSCD-Plugin', () => { name: "plugin to remove, but wrong kind", kind: "editor", src: "https://example.com/plugin-to-remove.js", - installed: false, + installed: true, }] }, ] @@ -396,7 +396,7 @@ describe('OpenSCD-Plugin', () => { it(tc.desc, async () => { // ARRANGE - // @ts-ignore: we use the private to arrange the scenario + // @ts-ignore: we use the private function to arrange the scenario element.storePlugins(tc.currentPlugins) await element.updateComplete From e005f24ceb3909ab10c009aeebe01e85f4b99188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20Russ?= Date: Mon, 16 Dec 2024 22:51:38 +0100 Subject: [PATCH 6/7] ref: remove unused code --- packages/openscd/src/open-scd.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/openscd/src/open-scd.ts b/packages/openscd/src/open-scd.ts index 4b153cbc0e..e342fdfe6d 100644 --- a/packages/openscd/src/open-scd.ts +++ b/packages/openscd/src/open-scd.ts @@ -338,12 +338,6 @@ export class OpenSCD extends LitElement { return JSON.parse(pluginsConfigStr) as Plugin[] } - private loadPluginsFromLocalStorage() { - const pluginsConfigStr = localStorage.getItem('plugins') ?? '[]' - const pluginsConfig = JSON.parse(pluginsConfigStr) as Plugin[] - this.updateStoredPlugins(pluginsConfig) - } - protected get locale(): string { return navigator.language || 'en-US'; From ed420f6b04faceff35053c9009ca1d47b268e40c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20Russ?= Date: Mon, 16 Dec 2024 23:31:14 +0100 Subject: [PATCH 7/7] ref: remove tsc from distribution --- packages/distribution/snowpack.config.mjs | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/distribution/snowpack.config.mjs b/packages/distribution/snowpack.config.mjs index 6ea30e8b56..cbe594598d 100644 --- a/packages/distribution/snowpack.config.mjs +++ b/packages/distribution/snowpack.config.mjs @@ -1,5 +1,4 @@ export default { - plugins: ['@snowpack/plugin-typescript'], packageOptions: { external: [ '@web/dev-server-core',