Skip to content
12,911 changes: 5,650 additions & 7,261 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion packages/openscd/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@
"lit": "^2.2.7",
"lit-translate": "^1.2.1",
"marked": "^4.0.10",
"panzoom": "^9.4.2"
"panzoom": "^9.4.2",
"scl-substation-editor": "github:senzacionale/scl-substation-editor#main"
},
"scripts": {
"clean": "rimraf build",
Expand Down
107 changes: 87 additions & 20 deletions packages/openscd/src/Wizarding.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,121 @@
import { html, state, TemplateResult, query } from 'lit-element';
import { html, query, state, TemplateResult } from 'lit-element';
import {
ifImplemented,
LitElementConstructor,
Mixin,
WizardEvent,
WizardFactory,
} from './foundation.js';

import './wizard-dialog.js';
import { WizardDialog } from './wizard-dialog.js';
import { CreateWizardRequest, EditWizardRequest } from './scl-wizarding.js';

function adaptV3Wizard(v3Def: unknown): WizardFactory | null {
const v3 = v3Def as {
steps: Array<{
title: string;
render: () => TemplateResult;
}>;
};
if (!v3?.steps?.length) return null;

return () =>
v3.steps.map(step => ({
title: step.title,
content: [step.render()],
actions: [
{
label: 'Close',
icon: 'close',
action: () => [],
},
],
}));
}

/** `LitElement` mixin that adds a `workflow` property which [[`Wizard`]]s are
* queued onto on incoming [[`WizardEvent`]]s, first come first displayed. */
export type WizardingElement = Mixin<typeof Wizarding>;

export function Wizarding<TBase extends LitElementConstructor>(Base: TBase) {
class WizardingElement extends Base {
/** FIFO queue of [[`Wizard`]]s to display. */
/** FIFO queue of Wizard to display. */
@state()
workflow: WizardFactory[] = [];

@query('wizard-dialog') wizardUI!: WizardDialog;
@query('wizard-dialog')
wizardUI!: WizardDialog;

private onWizard(e: WizardEvent) {
const { wizard, subwizard, v3Wizard } = e.detail;
if (v3Wizard) {
const adapted = adaptV3Wizard(v3Wizard);
if (adapted === null) {
this.workflow.shift();
} else if (subwizard) {
this.workflow.unshift(adapted);
} else {
this.workflow.push(adapted);
}
} else if (wizard === null) {
this.workflow.shift();
} else if (wizard) {
subwizard ? this.workflow.unshift(wizard) : this.workflow.push(wizard);
}

this.updateWizards();
}

private onWizardRequest(
e: CustomEvent<EditWizardRequest | CreateWizardRequest>
) {
const detail = e.detail as (EditWizardRequest | CreateWizardRequest) & {
wizard?: WizardFactory;
};

private onWizard(we: WizardEvent) {
const wizard = we.detail.wizard;
if (wizard === null) this.workflow.shift();
else if (we.detail.subwizard) this.workflow.unshift(wizard);
else this.workflow.push(wizard);
if ('wizard' in detail && detail.wizard) {
const wf = detail.wizard as WizardFactory;
detail.subWizard ? this.workflow.unshift(wf) : this.workflow.push(wf);
} else {
console.warn('[WizardingElement] No wizard provided, skipping...');
}
this.updateWizards();
}

private onCloseWizard() {
this.workflow.shift();
this.updateWizards();
}

private updateWizards() {
this.requestUpdate('workflow');
this.updateComplete.then(() =>
this.wizardUI.updateComplete.then(() =>
this.wizardUI.dialog?.updateComplete.then(() =>
this.wizardUI.dialog?.focus()
)
)
);
this.updateComplete
.then(() => this.wizardUI.updateComplete)
.then(() => this.wizardUI.dialog?.updateComplete)
.then(() => this.wizardUI.dialog?.focus());
}

constructor(...args: any[]) {
super(...args);

this.addEventListener('wizard', this.onWizard);
this.addEventListener('wizard', this.onWizard as EventListener);
this.addEventListener('oscd-edit-wizard-request', (e: Event) =>
this.onWizardRequest(e as CustomEvent<EditWizardRequest>)
);
this.addEventListener('oscd-create-wizard-request', (e: Event) =>
this.onWizardRequest(e as CustomEvent<CreateWizardRequest>)
);
this.addEventListener('oscd-close-wizard', () => this.onCloseWizard());
this.addEventListener('editor-action', () =>
this.wizardUI.requestUpdate()
this.wizardUI?.requestUpdate()
);
}

render(): TemplateResult {
return html`${ifImplemented(super.render())}
<wizard-dialog .wizard=${this.workflow[0]?.() ?? []}></wizard-dialog>`;
return html`
${ifImplemented(super.render())}
<wizard-dialog .wizard=${this.workflow[0]?.() ?? []}></wizard-dialog>
`;
}
}

Expand Down
113 changes: 89 additions & 24 deletions packages/openscd/src/addons/Wizards.ts
Original file line number Diff line number Diff line change
@@ -1,58 +1,123 @@
import {
html,
state,
TemplateResult,
query,
customElement,
html,
LitElement,
property,
query,
state,
TemplateResult,
} from 'lit-element';
import { WizardEvent, WizardFactory } from '../foundation.js';

import '../wizard-dialog.js';
import { WizardDialog } from '../wizard-dialog.js';
import { CreateWizardRequest, EditWizardRequest } from '../scl-wizarding.js';

function adaptV3Wizard(v3Def: unknown): WizardFactory | null {
const v3 = v3Def as {
steps: Array<{
title: string;
render: () => TemplateResult;
}>;
};
if (!v3?.steps?.length) return null;

return () =>
v3.steps.map(step => ({
title: step.title,
content: [step.render()],
actions: [
{
label: 'Close',
icon: 'close',
action: () => [],
},
],
}));
}

/** `LitElement` mixin that adds a `workflow` property which [[`Wizard`]]s are
* queued onto on incoming [[`WizardEvent`]]s, first come first displayed. */
@customElement('oscd-wizards')
export class OscdWizards extends LitElement {
@property({
type: Object,
})
@property({ type: Object })
host!: HTMLElement;

/** FIFO queue of [[`Wizard`]]s to display. */
/** FIFO queue of WizardFactories to display */
@state()
workflow: WizardFactory[] = [];

@query('wizard-dialog') wizardUI!: WizardDialog;
@query('wizard-dialog')
wizardUI!: WizardDialog;

private onWizard(we: WizardEvent) {
const wizard = we.detail.wizard;
if (wizard === null) this.workflow.shift();
else if (we.detail.subwizard) this.workflow.unshift(wizard);
else this.workflow.push(wizard);
private onWizard(event: WizardEvent) {
const { wizard, subwizard, v3Wizard } = event.detail;
if (v3Wizard) {
const adapted = adaptV3Wizard(v3Wizard);
if (adapted === null) {
this.workflow.shift();
} else if (subwizard) {
this.workflow.unshift(adapted);
} else {
this.workflow.push(adapted);
}
} else if (wizard === null) {
this.workflow.shift();
} else if (wizard) {
subwizard ? this.workflow.unshift(wizard) : this.workflow.push(wizard);
}

this.updateWizards();
}

private onWizardRequest(
e: CustomEvent<EditWizardRequest | CreateWizardRequest>
) {
const detail = e.detail as (EditWizardRequest | CreateWizardRequest) & {
wizard?: WizardFactory;
};
if ('wizard' in detail && detail.wizard) {
const wf = detail.wizard as WizardFactory;
detail.subWizard ? this.workflow.unshift(wf) : this.workflow.push(wf);
} else {
console.log('[oscd-wizards] No wizard provided, skipping...');
}

this.updateWizards();
}

private onCloseWizard() {
this.workflow.shift();
this.updateWizards();
}

private updateWizards() {
this.requestUpdate('workflow');
this.updateComplete.then(() =>
this.wizardUI.updateComplete.then(() =>
this.wizardUI.dialog?.updateComplete.then(() =>
this.wizardUI.dialog?.focus()
)
)
);
this.updateComplete
.then(() => this.wizardUI.updateComplete)
.then(() => this.wizardUI.dialog?.updateComplete)
.then(() => this.wizardUI.dialog?.focus());
}

connectedCallback() {
super.connectedCallback();

this.host.addEventListener('wizard', this.onWizard.bind(this));
this.host.addEventListener('oscd-edit-wizard-request', (e: Event) =>
this.onWizardRequest(e as CustomEvent<EditWizardRequest>)
);
this.host.addEventListener('oscd-create-wizard-request', (e: Event) =>
this.onWizardRequest(e as CustomEvent<CreateWizardRequest>)
);
this.host.addEventListener('oscd-close-wizard', () => this.onCloseWizard());
this.host.addEventListener('editor-action', () =>
this.wizardUI.requestUpdate()
);
}

render(): TemplateResult {
return html`<slot></slot>
<wizard-dialog .wizard=${this.workflow[0]?.() ?? []}></wizard-dialog>`;
return html`
<slot></slot>
<wizard-dialog .wizard=${this.workflow[0]?.() ?? []}></wizard-dialog>
`;
}
}
1 change: 1 addition & 0 deletions packages/openscd/src/foundation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ export type WizardFactory = () => Wizard;
export interface WizardDetail {
wizard: WizardFactory | null;
subwizard?: boolean;
v3Wizard?: unknown;
}
export type WizardEvent = CustomEvent<WizardDetail>;
export function newWizardEvent(
Expand Down
34 changes: 29 additions & 5 deletions packages/openscd/src/plugins.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export function generatePluginPath(plugin: string): string {
return location.origin+location.pathname+plugin;
return location.origin + location.pathname + plugin;
}

export const officialPlugins = [
Expand All @@ -11,6 +11,22 @@ export const officialPlugins = [
kind: 'editor',
requireDoc: true,
},
{
name: 'V3 Test Plugin',
src: generatePluginPath('plugins/src/editors/V3TestPlugin.js'),
icon: 'extension',
default: false,
kind: 'editor',
requireDoc: true,
},
{
name: 'Test SCL Substation Editor',
src: generatePluginPath('plugins/src/editors/TestSclSubstationEditor.js'),
icon: 'extension',
activeByDefault: false,
kind: 'editor',
requireDoc: true,
},
{
name: 'Substation',
src: generatePluginPath('plugins/src/editors/Substation.js'),
Expand All @@ -29,31 +45,39 @@ export const officialPlugins = [
},
{
name: 'Subscriber Message Binding (GOOSE)',
src: generatePluginPath('plugins/src/editors/GooseSubscriberMessageBinding.js'),
src: generatePluginPath(
'plugins/src/editors/GooseSubscriberMessageBinding.js'
),
icon: 'link',
default: false,
kind: 'editor',
requireDoc: true,
},
{
name: 'Subscriber Data Binding (GOOSE)',
src: generatePluginPath('plugins/src/editors/GooseSubscriberDataBinding.js'),
src: generatePluginPath(
'plugins/src/editors/GooseSubscriberDataBinding.js'
),
icon: 'link',
default: false,
kind: 'editor',
requireDoc: true,
},
{
name: 'Subscriber Later Binding (GOOSE)',
src: generatePluginPath('plugins/src/editors/GooseSubscriberLaterBinding.js'),
src: generatePluginPath(
'plugins/src/editors/GooseSubscriberLaterBinding.js'
),
icon: 'link',
default: true,
kind: 'editor',
requireDoc: true,
},
{
name: 'Subscriber Message Binding (SMV)',
src: generatePluginPath('plugins/src/editors/SMVSubscriberMessageBinding.js'),
src: generatePluginPath(
'plugins/src/editors/SMVSubscriberMessageBinding.js'
),
icon: 'link',
default: false,
kind: 'editor',
Expand Down
Loading
Loading