-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(plugging): load menu and editor plugins
- Loading branch information
Showing
42 changed files
with
899 additions
and
248 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
Oops, something went wrong.