Skip to content

Commit a510664

Browse files
authored
feat: Handle Config Plugin Events
1 parent 8b06a37 commit a510664

File tree

7 files changed

+392
-38
lines changed

7 files changed

+392
-38
lines changed

packages/openscd/src/addons/History.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import {
4242

4343
import { getFilterIcon, iconColors } from '../icons/icons.js';
4444

45-
import { Plugin } from '../open-scd.js';
45+
import { Plugin } from '../plugin.js';
4646

4747
const icons = {
4848
info: 'info',

packages/openscd/src/addons/Layout.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,21 @@ import { newPendingStateEvent } from '@openscd/core/foundation/deprecated/waiter
1313
import { newSettingsUIEvent } from '@openscd/core/foundation/deprecated/settings.js';
1414
import {
1515
MenuItem,
16-
Plugin,
1716
Validator,
18-
PluginKind,
19-
MenuPosition,
2017
MenuPlugin,
21-
menuPosition,
2218
pluginIcons,
2319
newResetPluginsEvent,
2420
newAddExternalPluginEvent,
2521
newSetPluginsEvent,
2622
} from '../open-scd.js';
23+
24+
import {
25+
MenuPosition,
26+
Plugin,
27+
menuPosition,
28+
PluginKind,
29+
} from "../plugin.js"
30+
2731
import {
2832
HistoryUIKind,
2933
newEmptyIssuesEvent,

packages/openscd/src/open-scd.ts

Lines changed: 105 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ import type {
4545
Plugin as CorePlugin,
4646
EditCompletedEvent,
4747
} from '@openscd/core';
48+
import { InstalledOfficialPlugin, MenuPosition, PluginKind, Plugin } from "./plugin.js"
49+
import { ConfigurePluginEvent, ConfigurePluginDetail, newConfigurePluginEvent } from './plugin.events.js';
50+
import { newLogEvent } from '@openscd/core/foundation/deprecated/history';
51+
4852

4953
// HOSTING INTERFACES
5054

@@ -173,28 +177,6 @@ function staticTagHtml(
173177
return html(<TemplateStringsArray>strings, ...args);
174178
}
175179

176-
export type PluginKind = 'editor' | 'menu' | 'validator';
177-
export const menuPosition = ['top', 'middle', 'bottom'] as const;
178-
export type MenuPosition = (typeof menuPosition)[number];
179-
180-
export type Plugin = {
181-
name: string;
182-
src: string;
183-
icon?: string;
184-
default?: boolean;
185-
kind: PluginKind;
186-
requireDoc?: boolean;
187-
position?: MenuPosition;
188-
installed: boolean;
189-
official?: boolean;
190-
content?: TemplateResult;
191-
};
192-
193-
type InstalledOfficialPlugin = {
194-
src: string;
195-
official: true;
196-
installed: boolean;
197-
};
198180

199181
function withoutContent<P extends Plugin | InstalledOfficialPlugin>(
200182
plugin: P
@@ -278,15 +260,53 @@ export class OpenSCD extends LitElement {
278260
if (src.startsWith('blob:')) URL.revokeObjectURL(src);
279261
}
280262

263+
/**
264+
*
265+
* @deprecated Use `handleConfigurationPluginEvent` instead
266+
*/
267+
public handleAddExternalPlugin(e: AddExternalPluginEvent){
268+
this.addExternalPlugin(e.detail.plugin);
269+
const {name, kind} = e.detail.plugin
270+
271+
const event = newConfigurePluginEvent(name,kind, e.detail.plugin)
272+
273+
this.handleConfigurationPluginEvent(event)
274+
}
275+
276+
277+
public handleConfigurationPluginEvent(e: ConfigurePluginEvent){
278+
const { name, kind, config } = e.detail;
279+
280+
const hasPlugin = this.hasPlugin(name, kind);
281+
const hasConfig = config !== null;
282+
const isChangeEvent = hasPlugin && hasConfig;
283+
const isRemoveEvent = hasPlugin && !hasConfig;
284+
const isAddEvent = !hasPlugin && hasConfig;
285+
286+
// the `&& config`is only because typescript
287+
// cannot infer that `isChangeEvent` and `isAddEvent` implies `config !== null`
288+
if(isChangeEvent && config){
289+
this.changePlugin(config);
290+
291+
}else if(isRemoveEvent){
292+
this.removePlugin(name, kind);
293+
294+
}else if(isAddEvent && config){
295+
this.addPlugin(config);
296+
297+
}else{
298+
const event = newLogEvent({
299+
kind: "error",
300+
title: "Invalid plugin configuration event",
301+
message: JSON.stringify({name, kind, config}),
302+
});
303+
this.dispatchEvent(event);
304+
}
305+
}
306+
281307
connectedCallback(): void {
282308
super.connectedCallback();
283309
this.addEventListener('reset-plugins', this.resetPlugins);
284-
this.addEventListener(
285-
'add-external-plugin',
286-
(e: AddExternalPluginEvent) => {
287-
this.addExternalPlugin(e.detail.plugin);
288-
}
289-
);
290310
this.addEventListener('set-plugins', (e: SetPluginsEvent) => {
291311
this.setPlugins(e.detail.indices);
292312
});
@@ -320,6 +340,8 @@ export class OpenSCD extends LitElement {
320340
.editCount=${this.editCount}
321341
>
322342
<oscd-layout
343+
@add-external-plugin=${this.handleAddExternalPlugin}
344+
@oscd-configure-plugin=${this.handleConfigurationPluginEvent}
323345
.host=${this}
324346
.doc=${this.doc}
325347
.docName=${this.docName}
@@ -341,6 +363,61 @@ export class OpenSCD extends LitElement {
341363
);
342364
this.requestUpdate();
343365
}
366+
367+
/**
368+
*
369+
* @param name
370+
* @param kind
371+
* @returns the index of the plugin in the stored plugin list
372+
*/
373+
private findPluginIndex(name: string, kind: PluginKind): number {
374+
return this.storedPlugins.findIndex(p => p.name === name && p.kind === kind);
375+
}
376+
377+
private hasPlugin(name: string, kind: PluginKind): boolean {
378+
return this.findPluginIndex(name, kind) > -1;
379+
}
380+
381+
private removePlugin(name: string, kind: PluginKind) {
382+
const newPlugins = this.storedPlugins.filter(
383+
p => p.name !== name || p.kind !== kind
384+
);
385+
this.storePlugins(newPlugins);
386+
}
387+
388+
private addPlugin(plugin: Plugin) {
389+
const newPlugins = [...this.storedPlugins, plugin];
390+
this.storePlugins(newPlugins);
391+
}
392+
393+
/**
394+
*
395+
* @param plugin
396+
* @throws if the plugin is not found
397+
*/
398+
private changePlugin(plugin: Plugin) {
399+
const storedPlugins = this.storedPlugins;
400+
const {name, kind} = plugin;
401+
const pluginIndex = this.findPluginIndex(name, kind);
402+
403+
if(pluginIndex < 0) {
404+
const event = newLogEvent({
405+
kind: "error",
406+
title: "Plugin not found, stopping change process",
407+
message: JSON.stringify({name, kind}),
408+
})
409+
this.dispatchEvent(event);
410+
return;
411+
}
412+
413+
const pluginToChange = storedPlugins[pluginIndex]
414+
const changedPlugin = {...pluginToChange, ...plugin}
415+
const newPlugins = [...storedPlugins]
416+
newPlugins.splice(pluginIndex, 1, changedPlugin)
417+
418+
this.storePlugins(newPlugins);
419+
}
420+
344421
private resetPlugins(): void {
345422
this.storePlugins(
346423
(builtinPlugins as Plugin[]).concat(this.parsedPlugins).map(plugin => {
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Plugin, PluginKind } from './plugin.js';
2+
3+
/**
4+
* The configure plugin event allows the plugin to request that OpenSCD core add, remove, or reconfigure a plugin.
5+
*/
6+
export type ConfigurePluginDetail = {
7+
name: string;
8+
// The API describes only 'menu' and 'editor' kinds b
9+
// but we still use the 'validator' too, so I just use the type PluginKind
10+
kind: PluginKind;
11+
config: Plugin | null;
12+
};
13+
14+
export type ConfigurePluginEvent = CustomEvent<ConfigurePluginDetail>;
15+
16+
/**
17+
* The combination of name and kind uniquely identifies the plugin to be configured.
18+
* If config is null, the plugin is removed. Otherwise, the plugin is added or reconfigured.
19+
*/
20+
export function newConfigurePluginEvent(name: string, kind: PluginKind, config: Plugin | null): ConfigurePluginEvent {
21+
return new CustomEvent<ConfigurePluginDetail>('oscd-configure-plugin', {
22+
bubbles: true,
23+
composed: true,
24+
detail: { name, kind, config },
25+
});
26+
}

packages/openscd/src/plugin.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { TemplateResult } from 'lit-element';
2+
3+
export type Plugin = {
4+
name: string;
5+
src: string;
6+
icon?: string;
7+
default?: boolean;
8+
kind: PluginKind;
9+
requireDoc?: boolean;
10+
position?: MenuPosition;
11+
installed: boolean;
12+
official?: boolean;
13+
content?: TemplateResult;
14+
};
15+
16+
export type InstalledOfficialPlugin = {
17+
src: string;
18+
official: true;
19+
installed: boolean;
20+
};
21+
22+
23+
export type PluginKind = 'editor' | 'menu' | 'validator';
24+
export const menuPosition = ['top', 'middle', 'bottom'] as const;
25+
export type MenuPosition = (typeof menuPosition)[number];

0 commit comments

Comments
 (0)