Skip to content

Feat/extract scl wizarding #1643

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

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
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