# HG changeset patch # User cin # Date 2020-02-04 16:40:48 # Node ID 5cc84d1fc7c092648584dba82076193a6444c7b3 # Parent e5727b40aba068797a78031e78f372599649487b added 'dom-inject' and 'css' modules diff --git a/build.gradle b/build.gradle --- a/build.gradle +++ b/build.gradle @@ -45,7 +45,7 @@ npmPackMeta { task npmPackTypings(type: Copy) { dependsOn sources.main.output - npmPack.dependsOn it + npmPackContents.dependsOn it from sources.main.output.typingsDir into npm.packageDir diff --git a/src/main/ts/css.ts b/src/main/ts/css.ts new file mode 100644 --- /dev/null +++ b/src/main/ts/css.ts @@ -0,0 +1,17 @@ +import inject = require("./inject"); +import { id as mid} from "module"; +import { TraceSource } from "@implab/core-amd/log/TraceSource"; +const log = TraceSource.get(mid); + +const plugin = { + load: async (id: string, require: Require, cb: (param: any) => void) => { + const url = require.toUrl(id); + try { + await inject.injectStylesheet(url); + cb({ url }); + } catch (e) { + log.error("CSS plugin failed to load {0} ({1}): {2}", id, url, e); + } + } +}; +export = plugin; diff --git a/src/main/ts/djx.ts b/src/main/ts/djx.ts deleted file mode 100644 --- a/src/main/ts/djx.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Constructor } from "@implab/core-amd/interfaces"; -import { HtmlElementContext } from "./djx/HtmlElementContext"; -import { WidgetContext } from "./djx/WidgetContext"; -import { isWidgetConstructor, BuildContext } from "./djx/traits"; - -export function createElement(elementType: string | T, ...args: any[]): BuildContext { - if (typeof elementType === "string") { - const ctx = new HtmlElementContext(elementType); - if (args) - args.forEach(x => ctx.visitNext(x)); - - return ctx; - } else if (isWidgetConstructor(elementType)) { - const ctx = new WidgetContext(elementType); - if (args) - args.forEach(x => ctx.visitNext(x)); - - return ctx; - } else { - throw new Error(`The element type '${elementType}' is unsupported`); - } -} - -export interface EventDetails { - detail: T; -} - -export interface EventSelector { - selectorTarget: HTMLElement; - target: HTMLElement; -} - -export type DojoMouseEvent = MouseEvent & EventSelector & EventDetails; diff --git a/src/main/ts/dom-inject.ts b/src/main/ts/dom-inject.ts new file mode 100644 --- /dev/null +++ b/src/main/ts/dom-inject.ts @@ -0,0 +1,95 @@ +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); + + +function on(node: HTMLElement, eventName: T, handler: (this: HTMLElement, ev: HTMLElementEventMap[T]) => any): () => void { + // 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 = document.head; + injectBefore = document.head.firstChild; + + _map: MapOf> = {}; + + _inject(name: T, attr: Partial) { + const node = document.createElement(name); + + return new Promise((ok, fail) => { + + const cleanup = () => { + noerr(); + noload(); + }; + + const noload = on(node, "load", () => { + ok({ node }); + cleanup(); + }); + + const noerr = on(node, "error", e => { + fail({ + erorr: e, + node + }); + cleanup(); + }); + + mixin(node, attr); + + this.injectionPoint.insertBefore(node, this.injectBefore); + }); + } + + async injectScript(url: string) { + let d = this._map[url]; + if (!d) { + 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); + } + } + + async injectStylesheet(url: string) { + let d = this._map[url]; + if (!d) { + 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); + } + } +}; + +const instance = new DomInject(); +export = instance; \ No newline at end of file diff --git a/src/main/ts/tsx.ts b/src/main/ts/tsx.ts new file mode 100644 --- /dev/null +++ b/src/main/ts/tsx.ts @@ -0,0 +1,33 @@ +import { Constructor } from "@implab/core-amd/interfaces"; +import { HtmlElementContext } from "./djx/HtmlElementContext"; +import { WidgetContext } from "./djx/WidgetContext"; +import { isWidgetConstructor, BuildContext } from "./djx/traits"; + +export function createElement(elementType: string | T, ...args: any[]): BuildContext { + if (typeof elementType === "string") { + const ctx = new HtmlElementContext(elementType); + if (args) + args.forEach(x => ctx.visitNext(x)); + + return ctx; + } else if (isWidgetConstructor(elementType)) { + const ctx = new WidgetContext(elementType); + if (args) + args.forEach(x => ctx.visitNext(x)); + + return ctx; + } else { + throw new Error(`The element type '${elementType}' is unsupported`); + } +} + +export interface EventDetails { + detail: T; +} + +export interface EventSelector { + selectorTarget: HTMLElement; + target: HTMLElement; +} + +export type DojoMouseEvent = MouseEvent & EventSelector & EventDetails;