Skip to content

Commit

Permalink
feat(plugging): load menu and editor plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
ca-d committed Oct 31, 2022
1 parent f39220e commit 73110da
Show file tree
Hide file tree
Showing 42 changed files with 899 additions and 248 deletions.
37 changes: 37 additions & 0 deletions demo/AddPlugins.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/* eslint-disable no-alert */
export default class AddPlugins extends HTMLElement {
/* eslint-disable-next-line class-methods-use-this */
run() {
const editor = (this.getRootNode()).host;
const kind = window.confirm(
`Add a menu type plugin?
If you choose 'Cancel', an editor type plugin will be added instead.`)
? 'menu'
: 'editor';
const requireDoc = window.confirm(
'Does the plugin require a loaded document? (OK=yes, Cancel=no)')
;
const name =
window.prompt('Plugin name', 'My plugin') ||
'Default plugin name';
const icon =
window.prompt('Plugin icon (material icon name)', 'extension') ||
'extension';
const active = true;
const src =
window.prompt(
'Plugin source URI',
'https://example.org/plugin.js'
) || 'data:text/javascript,';
const plugin = { name, src, icon, active, requireDoc };
if (
!window.confirm(
`Add ${kind} plugin ${JSON.stringify(plugin, null, ' ')}?`)

)
return;
if (!editor.plugins[kind]) editor.plugins[kind] = [];
editor.plugins[kind].push(plugin);
editor.requestUpdate('plugins');
}
}
2 changes: 1 addition & 1 deletion demo/embedded.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<title>OpenSCD Core Embedding Demo</title>
<iframe src="./index.html"></iframe>
<iframe src="./index.html?locale=de&dark"></iframe>
<style>
iframe {
width:50vw;
Expand Down
8 changes: 7 additions & 1 deletion demo/index.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
<title>OpenSCD Core Demo</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@300&family=Roboto:wght@300;400;500&display=swap">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Material+Icons&display=block">
<open-scd></open-scd>
<open-scd plugins='{"menu": [{"name": "Add plugins...", "translations": {"de": "Erweitern..."}, "icon": "extension", "active": true, "src": "/demo/AddPlugins.js"}]}'></open-scd>

<script type="module">
import '../dist/open-scd.js';

const editor = document.querySelector('open-scd');
const params = (new URL(document.location)).searchParams;
for (const [name, value] of params) {
editor.setAttribute(name, value);
}
</script>
<style>
* {
Expand Down
6 changes: 3 additions & 3 deletions mixins/Editing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,9 @@ export function Editing<TBase extends LitElementConstructor>(Base: TBase) {
docs: Record<string, XMLDocument> = {};

/** The name of the [[`doc`]] currently being edited */
@property({ type: String }) docName = '';
@property({ type: String, reflect: true }) docName = '';

protected onOpenDoc({ detail: { docName, doc } }: OpenEvent) {
protected handleOpenDoc({ detail: { docName, doc } }: OpenEvent) {
this.docName = docName;
this.docs[this.docName] = doc;
}
Expand Down Expand Up @@ -177,7 +177,7 @@ export function Editing<TBase extends LitElementConstructor>(Base: TBase) {
constructor(...args: any[]) {
super(...args);

this.addEventListener('oscd-open', this.onOpenDoc);
this.addEventListener('oscd-open', this.handleOpenDoc);
this.addEventListener('oscd-edit', event => this.handleEditEvent(event));
}
}
Expand Down
65 changes: 65 additions & 0 deletions mixins/Plugging.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { expect, fixture } from '@open-wc/testing';

import { html, LitElement } from 'lit';
import { customElement } from 'lit/decorators.js';

import { Plugging } from './Plugging.js';

namespace util {
@customElement('plugging-element')
export class PluggingElement extends Plugging(LitElement) {}
}

describe('Plugging Element', () => {
let editor: util.PluggingElement;

beforeEach(async () => {
editor = <util.PluggingElement>(
await fixture(html`<plugging-element></plugging-element>`)
);
});

it('loads menu plugins', () => {
editor.plugins = {
menu: [
{
name: 'Test Menu Plugin',
translations: { de: 'Test Menu Erweiterung' },
src: 'data:text/javascript;charset=utf-8,export%20default%20class%20TestPlugin%20extends%20HTMLElement%20%7B%0D%0A%20%20async%20run%28%29%20%7B%0D%0A%20%20%20%20return%20true%3B%0D%0A%20%20%7D%0D%0A%7D',
icon: 'margin',
active: true,
requireDoc: false,
},
{
name: 'Test Menu Plugin 2',
src: 'data:text/javascript;charset=utf-8,export%20default%20class%20TestPlugin%20extends%20HTMLElement%20%7B%0D%0A%20%20async%20run%28%29%20%7B%0D%0A%20%20%20%20return%20true%3B%0D%0A%20%20%7D%0D%0A%7D',
icon: 'margin',
active: true,
requireDoc: false,
},
],
};
expect(editor).property('plugins').property('menu').to.have.lengthOf(2);
});

it('loads editor plugins', () => {
editor.plugins = {
editor: [
{
name: 'Test Editor Plugin',
translations: { de: 'Test Editor Erweiterung' },
src: 'data:text/javascript;charset=utf-8,export%20default%20class%20TestEditorPlugin%20extends%20HTMLElement%20%7B%0D%0A%20%20constructor%20%28%29%20%7B%20super%28%29%3B%20this.innerHTML%20%3D%20%60%3Cp%3ETest%20Editor%20Plugin%3C%2Fp%3E%60%3B%20%7D%0D%0A%7D',
icon: 'coronavirus',
active: true,
},
{
name: 'Test Editor Plugin 2',
src: 'data:text/javascript;charset=utf-8,export%20default%20class%20TestEditorPlugin%20extends%20HTMLElement%20%7B%0D%0A%20%20constructor%20%28%29%20%7B%20super%28%29%3B%20this.innerHTML%20%3D%20%60%3Cp%3ETest%20Editor%20Plugin%3C%2Fp%3E%60%3B%20%7D%0D%0A%7D',
icon: 'coronavirus',
active: true,
},
],
};
expect(editor).property('plugins').property('editor').to.have.lengthOf(2);
});
});
85 changes: 85 additions & 0 deletions mixins/Plugging.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { property, state } from 'lit/decorators.js';

import { LitElementConstructor } from '../foundation.js';
import { targetLocales } from '../locales.js';

export type Plugin = {
name: string;
translations?: Record<typeof targetLocales[number], string>;
src: string;
icon: string;
requireDoc?: boolean;
active?: boolean;
};
export type PluginSet = { menu: Plugin[]; editor: Plugin[] };

const pluginTags = new Map<string, string>();
/**
* 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.
*/
export function pluginTag(uri: string): string {
if (!pluginTags.has(uri)) {
/* eslint-disable no-bitwise */
let h1 = 0xdeadbeef;
let h2 = 0x41c6ce57;
/* eslint-disable-next-line no-plusplus */
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-p${
(h2 >>> 0).toString(16).padStart(8, '0') +
(h1 >>> 0).toString(16).padStart(8, '0')
}`
);
/* eslint-enable no-bitwise */
}
return pluginTags.get(uri)!;
}

export function Plugging<TBase extends LitElementConstructor>(Base: TBase) {
class PluggingElement extends Base {
#loadedPlugins = new Map<string, Plugin>();

@state()
get loadedPlugins(): Map<string, Plugin> {
return this.#loadedPlugins;
}

#plugins: PluginSet = { menu: [], editor: [] };

@property({ type: Object })
get plugins(): PluginSet {
return this.#plugins;
}

set plugins(plugins: Partial<PluginSet>) {
Object.values(plugins).forEach(kind =>
kind.forEach(plugin => {
const tagName = pluginTag(plugin.src);
if (this.loadedPlugins.has(tagName)) return;
this.#loadedPlugins.set(tagName, plugin);
if (customElements.get(tagName)) return;
import(plugin.src).then(mod =>
customElements.define(tagName, mod.default)
);
})
);

this.#plugins = { menu: [], editor: [], ...plugins };
this.requestUpdate();
}
}
return PluggingElement;
}
Loading

0 comments on commit 73110da

Please sign in to comment.