dom-inject.ts
100 lines
| 2.7 KiB
| video/mp2t
|
TypeScriptLexer
|
|
r65 | import { id as mid } from "module"; | ||
| import { TraceSource } from "@implab/core-amd/log/TraceSource"; | ||||
| import { MapOf } from "@implab/core-amd/interfaces"; | ||||
| import { mixin } from "@implab/core-amd/safe"; | ||||
| const trace = TraceSource.get(mid); | ||||
|
|
r109 | function on<T extends keyof HTMLElementEventMap>(node: HTMLElement, eventName: T, handler: (this: HTMLElement, ev: HTMLElementEventMap[T]) => unknown): () => void { | ||
|
|
r65 | // Add an event listener to a DOM node | ||
| node.addEventListener(eventName, handler, false); | ||||
| return () => node.removeEventListener(eventName, handler, false); | ||||
| } | ||||
| interface NodeLoadResult { | ||||
| node: HTMLElement; | ||||
| } | ||||
| class DomInject { | ||||
| injectionPoint?: HTMLElement; | ||||
| injectAfter?: HTMLElement; | ||||
| _map: MapOf<Promise<NodeLoadResult>> = {}; | ||||
| _inject<T extends keyof HTMLElementTagNameMap>(name: T, attr: Partial<HTMLElementTagNameMap[T]>) { | ||||
| const node = document.createElement(name); | ||||
| return new Promise<NodeLoadResult>((ok, fail) => { | ||||
| const cleanup = () => { | ||||
| noerr(); | ||||
| noload(); | ||||
| }; | ||||
| const noload = on(node, "load", () => { | ||||
| ok({ node }); | ||||
| cleanup(); | ||||
| }); | ||||
| const noerr = on(node, "error", e => { | ||||
| fail({ | ||||
| error: e, | ||||
| node | ||||
| }); | ||||
| cleanup(); | ||||
| }); | ||||
| mixin(node, attr); | ||||
| const _injectionPoint = this.injectionPoint || document.head; | ||||
| const _injectBefore = this.injectAfter ? this.injectAfter.nextSibling : null; | ||||
| _injectionPoint.insertBefore(node, _injectBefore); | ||||
| }); | ||||
| } | ||||
| async injectScript(url: string) { | ||||
| let d = this._map[url]; | ||||
|
|
r109 | if (d === undefined) { | ||
|
|
r65 | trace.log("js {0}", url); | ||
| d = this._inject("script", { | ||||
| type: "text/javascript", | ||||
| charset: "utf-8", | ||||
| src: url | ||||
| }); | ||||
| this._map[url] = d; | ||||
| } | ||||
| try { | ||||
| await d; | ||||
| trace.log("done {0}", url); | ||||
| } catch (e) { | ||||
| trace.error("failed {0}: {1}", url, e); | ||||
| throw e; | ||||
| } | ||||
| } | ||||
| async injectStylesheet(url: string) { | ||||
| let d = this._map[url]; | ||||
|
|
r109 | if (d === undefined) { | ||
|
|
r65 | trace.log("js {0}", url); | ||
| d = this._inject("link", { | ||||
| type: "text/css", | ||||
| rel: "stylesheet", | ||||
| href: url | ||||
| }); | ||||
| this._map[url] = d; | ||||
| } | ||||
| try { | ||||
| await d; | ||||
| trace.log("done {0}", url); | ||||
| } catch (e) { | ||||
| trace.error("failed {0}: {1}", url, e); | ||||
| throw e; | ||||
| } | ||||
| } | ||||
|
|
r79 | } | ||
|
|
r65 | |||
| const instance = new DomInject(); | ||||
| export default instance; | ||||
