Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Programatic Plugin Activation #1611

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions packages/core/foundation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,11 @@ export type {
EditCompletedEvent,
EditCompletedDetail,
} from './foundation/edit-completed-event.js';

/** @returns the cartesian product of `arrays` */
export function crossProduct<T>(...arrays: T[][]): T[][] {
return arrays.reduce<T[][]>(
(a, b) => <T[][]>a.flatMap(d => b.map(e => [d, e].flat())),
[[]]
);
}
89 changes: 89 additions & 0 deletions packages/core/foundation/scl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { crossProduct } from '../foundation.js';
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

file was missing.


function getDataModelChildren(parent: Element): Element[] {
if (['LDevice', 'Server'].includes(parent.tagName))
return Array.from(parent.children).filter(
child =>
Comment on lines +3 to +6
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this file related to activating plugins?

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;
}
3 changes: 2 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -159,4 +160,4 @@
"prettier --write"
]
}
}
}
1 change: 0 additions & 1 deletion packages/distribution/snowpack.config.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export default {
plugins: ['@snowpack/plugin-typescript'],
packageOptions: {
external: [
'@web/dev-server-core',
Expand Down
87 changes: 70 additions & 17 deletions packages/openscd/src/addons/Layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ import '@material/mwc-dialog';
import '@material/mwc-switch';
import '@material/mwc-select';
import '@material/mwc-textfield';
import { EditCompletedEvent } from '@openscd/core';
import { nothing } from 'lit';


@customElement('oscd-layout')
export class OscdLayout extends LitElement {
Expand All @@ -61,6 +62,8 @@ export class OscdLayout extends LitElement {
return html`
<div
@open-plugin-download=${() => this.pluginDownloadUI.show()}
@oscd-activate-editor=${this.handleActivateEditorByEvent}
@oscd-run-menu=${this.handleRunMenuByEvent}
>
<slot></slot>
${this.renderHeader()} ${this.renderAside()} ${this.renderContent()}
Expand Down Expand Up @@ -155,6 +158,7 @@ export class OscdLayout extends LitElement {
},
disabled: (): boolean => !this.historyState.canUndo,
kind: 'static',
content: () => html``,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the default for content, so we don't have to set it right?

},
{
icon: 'redo',
Expand All @@ -165,6 +169,7 @@ export class OscdLayout extends LitElement {
},
disabled: (): boolean => !this.historyState.canRedo,
kind: 'static',
content: () => html``,
},
...validators,
{
Expand All @@ -175,6 +180,7 @@ export class OscdLayout extends LitElement {
this.dispatchEvent(newHistoryUIEvent(true, HistoryUIKind.log));
},
kind: 'static',
content: () => html``,
},
{
icon: 'history',
Expand All @@ -184,6 +190,7 @@ export class OscdLayout extends LitElement {
this.dispatchEvent(newHistoryUIEvent(true, HistoryUIKind.history));
},
kind: 'static',
content: () => html``,
},
{
icon: 'rule',
Expand All @@ -193,6 +200,7 @@ export class OscdLayout extends LitElement {
this.dispatchEvent(newHistoryUIEvent(true, HistoryUIKind.diagnostic));
},
kind: 'static',
content: () => html``,
},
'divider',
...middleMenu,
Expand All @@ -203,13 +211,15 @@ export class OscdLayout extends LitElement {
this.dispatchEvent(newSettingsUIEvent(true));
},
kind: 'static',
content: () => html``,
},
...bottomMenu,
{
icon: 'extension',
name: 'plugins.heading',
action: (): void => this.pluginUI.show(),
kind: 'static',
content: () => html``,
},
];
}
Expand Down Expand Up @@ -333,7 +343,10 @@ export class OscdLayout extends LitElement {
);
},
disabled: (): boolean => plugin.requireDoc! && this.doc === null,
content: plugin.content,
content: () => {
if(plugin.content){ return plugin.content(); }
return html``;
},
kind: kind,
}
})
Expand All @@ -358,29 +371,32 @@ 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`<li divider padded role="separator"></li>`; }
if (me.actionItem){ return html``; }
const isDivider = me === 'divider';
const hasActionItem = me !== 'divider' && me.actionItem;

if (isDivider) { return html`<li divider padded role="separator"></li>`; }
if (hasActionItem){ return html``; }
return html`
<mwc-list-item
class="${me.kind}"
iconid="${me.icon}"
graphic="icon"
data-name="${me.name}"
.disabled=${me.disabled?.() || !me.action}
><mwc-icon slot="graphic">${me.icon}</mwc-icon>
<span>${get(me.name)}</span>
${me.hint
? html`<span slot="secondary"><tt>${me.hint}</tt></span>`
: ''}
</mwc-list-item>
${me.content ?? ''}
${me.content ? me.content() : nothing}
`;
}

Expand Down Expand Up @@ -456,24 +472,32 @@ 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``; }

return html`
<mwc-tab-bar @MDCTabBar:activated=${(e: CustomEvent) => (this.activeTab = e.detail.index)}>
<mwc-tab-bar
@MDCTabBar:activated=${this.handleActivatedEditorTabByUser}
activeIndex=${this.activeTab}
>
${activeEditors}
</mwc-tab-bar>
${renderEditorContent(this.editors, this.activeTab, this.doc)}
Expand All @@ -487,10 +511,39 @@ export class OscdLayout extends LitElement {
const content = editor?.content;
if(!content) { return html`` }

return html`${content}`;
return html`${content()}`;
}
}

private handleActivatedEditorTabByUser(e: CustomEvent): 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){
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
Expand Down
Loading
Loading