From 79c14d67a2ead7c9e11070b9a270da40caa69dd3 Mon Sep 17 00:00:00 2001 From: Ben Rohlfs Date: Mon, 3 Aug 2020 09:52:08 +0200 Subject: [PATCH 1/2] Rename files from js to ts to preserve history Test and Eslint failures are expected. Change-Id: I3c3d5da13ed522feb4291831af80c251889b1bea --- .../{gr-attribute-helper.js => gr-attribute-helper.ts} | 0 .../gr-event-helper/{gr-event-helper.js => gr-event-helper.ts} | 0 .../{gr-plugin-endpoints.js => gr-plugin-endpoints.ts} | 0 polygerrit-ui/app/scripts/{import-href.js => import-href.ts} | 0 .../gr-reporting/{gr-reporting_mock.js => gr-reporting_mock.ts} | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename polygerrit-ui/app/elements/plugins/gr-attribute-helper/{gr-attribute-helper.js => gr-attribute-helper.ts} (100%) rename polygerrit-ui/app/elements/plugins/gr-event-helper/{gr-event-helper.js => gr-event-helper.ts} (100%) rename polygerrit-ui/app/elements/shared/gr-js-api-interface/{gr-plugin-endpoints.js => gr-plugin-endpoints.ts} (100%) rename polygerrit-ui/app/scripts/{import-href.js => import-href.ts} (100%) rename polygerrit-ui/app/services/gr-reporting/{gr-reporting_mock.js => gr-reporting_mock.ts} (100%) diff --git a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.js b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts similarity index 100% rename from polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.js rename to polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts diff --git a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.js b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.ts similarity index 100% rename from polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.js rename to polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.ts diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.ts similarity index 100% rename from polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.js rename to polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.ts diff --git a/polygerrit-ui/app/scripts/import-href.js b/polygerrit-ui/app/scripts/import-href.ts similarity index 100% rename from polygerrit-ui/app/scripts/import-href.js rename to polygerrit-ui/app/scripts/import-href.ts diff --git a/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.js b/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts similarity index 100% rename from polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.js rename to polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts From cfe354dcfad86faca7d58f9d4276df72f8bd8b1b Mon Sep 17 00:00:00 2001 From: Ben Rohlfs Date: Mon, 3 Aug 2020 12:27:47 +0200 Subject: [PATCH 2/2] Convert some helper classes to TypeScript Change-Id: Ife0bef08bee7fce14c03fa9b019b57d9f8bba633 --- .../gr-attribute-helper.ts | 160 +++++++------ .../gr-event-helper/gr-event-helper.ts | 151 ++++++------ .../gr-plugin-endpoints.ts | 223 +++++++++--------- polygerrit-ui/app/scripts/import-href.ts | 45 ++-- .../app/test/test-app-context-init.js | 2 +- polygerrit-ui/app/types/globals.ts | 1 + 6 files changed, 292 insertions(+), 290 deletions(-) diff --git a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts index 3f8aa44c2e..6c6321b31c 100644 --- a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts +++ b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts @@ -15,85 +15,83 @@ * limitations under the License. */ -/** @constructor */ -export function GrAttributeHelper(element) { - this.element = element; - this._promises = {}; +export class GrAttributeHelper { + private readonly _promises = new Map>(); + + constructor(public element: any) {} + + _getChangedEventName(name: string): string { + return name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase() + '-changed'; + } + + /** + * Returns true if the property is defined on wrapped element. + */ + _elementHasProperty(name: string) { + return this.element[name] !== undefined; + } + + _reportValue(callback: (value: any) => void, value: any) { + try { + callback(value); + } catch (e) { + console.info(e); + } + } + + /** + * Binds callback to property updates. + * + * @param {string} name Property name. + * @param {function(?)} callback + * @return {function()} Unbind function. + */ + bind(name: string, callback: (value: any) => void) { + const attributeChangedEventName = this._getChangedEventName(name); + const changedHandler = (e: CustomEvent) => + this._reportValue(callback, e.detail.value); + const unbind = () => + this.element.removeEventListener( + attributeChangedEventName, + changedHandler + ); + this.element.addEventListener(attributeChangedEventName, changedHandler); + if (this._elementHasProperty(name)) { + this._reportValue(callback, this.element[name]); + } + return unbind; + } + + /** + * Get value of the property from wrapped object. Waits for the property + * to be initialized if it isn't defined. + * + * @param {string} name Property name. + * @return {!Promise} + */ + get(name: string) { + if (this._elementHasProperty(name)) { + return Promise.resolve(this.element[name]); + } + if (!this._promises.has(name)) { + let resolve: (value: any) => void; + const promise = new Promise(r => (resolve = r)); + const unbind = this.bind(name, value => { + resolve(value); + unbind(); + }); + this._promises.set(name, promise); + } + return this._promises.get(name); + } + + /** + * Sets value and dispatches event to force notify. + */ + set(name: string, value: any) { + this.element[name] = value; + this.element.dispatchEvent( + new CustomEvent(this._getChangedEventName(name), {detail: {value}}) + ); + } } - -GrAttributeHelper.prototype._getChangedEventName = function(name) { - return name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase() + '-changed'; -}; - -/** - * Returns true if the property is defined on wrapped element. - * - * @param {string} name - * @return {boolean} - */ -GrAttributeHelper.prototype._elementHasProperty = function(name) { - return this.element[name] !== undefined; -}; - -GrAttributeHelper.prototype._reportValue = function(callback, value) { - try { - callback(value); - } catch (e) { - console.info(e); - } -}; - -/** - * Binds callback to property updates. - * - * @param {string} name Property name. - * @param {function(?)} callback - * @return {function()} Unbind function. - */ -GrAttributeHelper.prototype.bind = function(name, callback) { - const attributeChangedEventName = this._getChangedEventName(name); - const changedHandler = e => this._reportValue(callback, e.detail.value); - const unbind = () => this.element.removeEventListener( - attributeChangedEventName, changedHandler); - this.element.addEventListener( - attributeChangedEventName, changedHandler); - if (this._elementHasProperty(name)) { - this._reportValue(callback, this.element[name]); - } - return unbind; -}; - -/** - * Get value of the property from wrapped object. Waits for the property - * to be initialized if it isn't defined. - * - * @param {string} name Property name. - * @return {!Promise} - */ -GrAttributeHelper.prototype.get = function(name) { - if (this._elementHasProperty(name)) { - return Promise.resolve(this.element[name]); - } - if (!this._promises[name]) { - let resolve; - const promise = new Promise(r => resolve = r); - const unbind = this.bind(name, value => { - resolve(value); - unbind(); - }); - this._promises[name] = promise; - } - return this._promises[name]; -}; - -/** - * Sets value and dispatches event to force notify. - * - * @param {string} name Property name. - * @param {?} value - */ -GrAttributeHelper.prototype.set = function(name, value) { - this.element[name] = value; - this.element.dispatchEvent( - new CustomEvent(this._getChangedEventName(name), {detail: {value}})); -}; diff --git a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.ts b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.ts index 466f84aec0..bf08b8e7bd 100644 --- a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.ts +++ b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.ts @@ -15,88 +15,85 @@ * limitations under the License. */ -/** @constructor */ -export function GrEventHelper(element) { - this.element = element; - this._unsubscribers = []; +interface EventWithPath extends Event { + path?: HTMLElement[]; } -/** - * Add a callback to arbitrary event. - * The callback may return false to prevent event bubbling. - * - * @param {string} event Event name - * @param {function(Event):boolean} callback - * @return {function()} Unsubscribe function. - */ -GrEventHelper.prototype.on = function(event, callback) { - return this._listen(this.element, callback, {event}); -}; +export interface ListenOptions { + event?: string; + capture?: boolean; +} -/** - * Alias of onClick - * - * @see onClick - */ -GrEventHelper.prototype.onTap = function(callback) { - return this._listen(this.element, callback); -}; +export class GrEventHelper { + constructor(readonly element: HTMLElement) {} -/** - * Add a callback to element click or touch. - * The callback may return false to prevent event bubbling. - * - * @param {function(Event):boolean} callback - * @return {function()} Unsubscribe function. - */ -GrEventHelper.prototype.onClick = function(callback) { - return this._listen(this.element, callback); -}; + /** + * Add a callback to arbitrary event. + * The callback may return false to prevent event bubbling. + */ + on(event: string, callback: (event: Event) => boolean) { + return this._listen(this.element, callback, {event}); + } -/** - * Alias of captureClick - * - * @see captureClick - */ -GrEventHelper.prototype.captureTap = function(callback) { - return this._listen(this.element.parentElement, callback, {capture: true}); -}; + /** + * Alias for @see onClick + */ + onTap(callback: (event: Event) => boolean) { + return this.onClick(callback); + } -/** - * Add a callback to element click or touch ahead of normal flow. - * Callback is installed on parent during capture phase. - * https://www.w3.org/TR/DOM-Level-3-Events/#event-flow - * The callback may return false to cancel regular event listeners. - * - * @param {function(Event):boolean} callback - * @return {function()} Unsubscribe function. - */ -GrEventHelper.prototype.captureClick = function(callback) { - return this._listen(this.element.parentElement, callback, {capture: true}); -}; + /** + * Add a callback to element click or touch. + * The callback may return false to prevent event bubbling. + */ + onClick(callback: (event: Event) => boolean) { + return this._listen(this.element, callback); + } -GrEventHelper.prototype._listen = function(container, callback, opt_options) { - const capture = opt_options && opt_options.capture; - const event = opt_options && opt_options.event || 'click'; - const handler = e => { - if (e.path.indexOf(this.element) !== -1) { - let mayContinue = true; - try { - mayContinue = callback(e); - } catch (e) { - console.warn(`Plugin error handing event: ${e}`); + /** + * Alias for @see captureClick + */ + captureTap(callback: (event: Event) => boolean) { + this.captureClick(callback); + } + + /** + * Add a callback to element click or touch ahead of normal flow. + * Callback is installed on parent during capture phase. + * https://www.w3.org/TR/DOM-Level-3-Events/#event-flow + * The callback may return false to cancel regular event listeners. + */ + captureClick(callback: (event: Event) => boolean) { + const parent = this.element.parentElement!; + return this._listen(parent, callback, {capture: true}); + } + + _listen( + container: HTMLElement, + callback: (event: Event) => boolean, + opt_options?: ListenOptions | null + ) { + const capture = opt_options?.capture; + const event = opt_options?.event || 'click'; + const handler = (e: EventWithPath) => { + if (!e.path) return; + if (e.path.indexOf(this.element) !== -1) { + let mayContinue = true; + try { + mayContinue = callback(e); + } catch (exception) { + console.warn(`Plugin error handing event: ${exception}`); + } + if (mayContinue === false) { + e.stopImmediatePropagation(); + e.stopPropagation(); + e.preventDefault(); + } } - if (mayContinue === false) { - e.stopImmediatePropagation(); - e.stopPropagation(); - e.preventDefault(); - } - } - }; - container.addEventListener(event, handler, capture); - const unsubscribe = () => - container.removeEventListener(event, handler, capture); - this._unsubscribers.push(unsubscribe); - return unsubscribe; -}; - + }; + container.addEventListener(event, handler, capture); + const unsubscribe = () => + container.removeEventListener(event, handler, capture); + return unsubscribe; + } +} diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.ts index dae8d3e5a8..6a3f957625 100644 --- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.ts +++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.ts @@ -15,58 +15,85 @@ * limitations under the License. */ -import {importHref} from '../../../scripts/import-href.js'; +import {importHref} from '../../../scripts/import-href'; + +type Callback = (value: any) => void; + +interface ModuleInfo { + moduleName: string; + // TODO(TS): Convert type to GrPlugin. + plugin: any; + pluginUrl: URL; + type?: string; + // TODO(TS): Convert type to GrDomHook. + domHook: any; + slot?: string; +} + +interface Options { + endpoint: string; + dynamicEndpoint?: string; + slot?: string; + type?: string; + moduleName?: string; + // TODO(TS): Convert type to GrDomHook. + domHook?: any; +} -/** @constructor */ export class GrPluginEndpoints { - constructor() { - this._endpoints = {}; - this._callbacks = {}; - this._dynamicPlugins = {}; - this._importedUrls = new Set(); - this._pluginLoaded = false; - } + private readonly _endpoints = new Map(); + + private readonly _callbacks = new Map void)[]>(); + + private readonly _dynamicPlugins = new Map>(); + + private readonly _importedUrls = new Set(); + + private _pluginLoaded = false; setPluginsReady() { this._pluginLoaded = true; } - onNewEndpoint(endpoint, callback) { - if (!this._callbacks[endpoint]) { - this._callbacks[endpoint] = []; + onNewEndpoint(endpoint: string, callback: Callback) { + if (!this._callbacks.has(endpoint)) { + this._callbacks.set(endpoint, []); } - this._callbacks[endpoint].push(callback); + this._callbacks.get(endpoint)!.push(callback); } - onDetachedEndpoint(endpoint, callback) { - if (this._callbacks[endpoint]) { - this._callbacks[endpoint] = this._callbacks[endpoint].filter( - cb => cb !== callback - ); + onDetachedEndpoint(endpoint: string, callback: Callback) { + if (this._callbacks.has(endpoint)) { + const filteredCallbacks = this._callbacks + .get(endpoint)! + .filter((cb: Callback) => cb !== callback); + this._callbacks.set(endpoint, filteredCallbacks); } } - _getOrCreateModuleInfo(plugin, opts) { + _getOrCreateModuleInfo(plugin: any, opts: Options): ModuleInfo { const {endpoint, slot, type, moduleName, domHook} = opts; - const existingModule = this._endpoints[endpoint].find( - info => + const existingModule = this._endpoints + .get(endpoint!)! + .find( + (info: ModuleInfo) => info.plugin === plugin && - info.moduleName === moduleName && - info.domHook === domHook && - info.slot === slot - ); + info.moduleName === moduleName && + info.domHook === domHook && + info.slot === slot + ); if (existingModule) { return existingModule; } else { - const newModule = { - moduleName, + const newModule: ModuleInfo = { + moduleName: moduleName!, plugin, pluginUrl: plugin._url, type, domHook, slot, }; - this._endpoints[endpoint].push(newModule); + this._endpoints.get(endpoint!)!.push(newModule); return newModule; } } @@ -81,16 +108,17 @@ export class GrPluginEndpoints { * @param {Object} plugin * @param {Object} opts */ - registerModule(plugin, opts) { - const {endpoint, dynamicEndpoint} = opts; + registerModule(plugin: any, opts: Options) { + const endpoint = opts.endpoint!; + const dynamicEndpoint = opts.dynamicEndpoint; if (dynamicEndpoint) { - if (!this._dynamicPlugins[dynamicEndpoint]) { - this._dynamicPlugins[dynamicEndpoint] = new Set(); + if (!this._dynamicPlugins.has(dynamicEndpoint)) { + this._dynamicPlugins.set(dynamicEndpoint, new Set()); } - this._dynamicPlugins[dynamicEndpoint].add(endpoint); + this._dynamicPlugins.get(dynamicEndpoint)!.add(endpoint); } - if (!this._endpoints[endpoint]) { - this._endpoints[endpoint] = []; + if (!this._endpoints.has(endpoint)) { + this._endpoints.set(endpoint, []); } const moduleInfo = this._getOrCreateModuleInfo(plugin, opts); // TODO: the logic below seems wrong when: @@ -98,13 +126,13 @@ export class GrPluginEndpoints { // one register before plugins ready // the other done after, then only the later one will have the callbacks // invoked. - if (this._pluginLoaded && this._callbacks[endpoint]) { - this._callbacks[endpoint].forEach(callback => callback(moduleInfo)); + if (this._pluginLoaded && this._callbacks.has(endpoint)) { + this._callbacks.get(endpoint)!.forEach(callback => callback(moduleInfo)); } } - getDynamicEndpoints(dynamicEndpoint) { - const plugins = this._dynamicPlugins[dynamicEndpoint]; + getDynamicEndpoints(dynamicEndpoint: string): string[] { + const plugins = this._dynamicPlugins.get(dynamicEndpoint); if (!plugins) return []; return Array.from(plugins); } @@ -112,45 +140,28 @@ export class GrPluginEndpoints { /** * Get detailed information about modules registered with an extension * endpoint. - * - * @param {string} name Endpoint name. - * @param {?{ - * type: (string|undefined), - * moduleName: (string|undefined) - * }} opt_options - * @return {!Array<{ - * moduleName: string, - * plugin: Plugin, - * pluginUrl: String, - * type: EndpointType, - * domHook: !Object - * }>} */ - getDetails(name, opt_options) { - const type = opt_options && opt_options.type; - const moduleName = opt_options && opt_options.moduleName; - if (!this._endpoints[name]) { + getDetails(name: string, options?: Options): ModuleInfo[] { + const type = options && options.type; + const moduleName = options && options.moduleName; + if (!this._endpoints.has(name)) { return []; + } else { + return this._endpoints + .get(name)! + .filter( + (item: ModuleInfo) => + (!type || item.type === type) && + (!moduleName || moduleName === item.moduleName) + ); } - return this._endpoints[name].filter( - item => - (!type || item.type === type) && - (!moduleName || moduleName == item.moduleName) - ); } /** * Get detailed module names for instantiating at the endpoint. - * - * @param {string} name Endpoint name. - * @param {?{ - * type: (string|undefined), - * moduleName: (string|undefined) - * }} opt_options - * @return {!Array} */ - getModules(name, opt_options) { - const modulesData = this.getDetails(name, opt_options); + getModules(name: string, options?: Options): string[] { + const modulesData = this.getDetails(name, options); if (!modulesData.length) { return []; } @@ -159,64 +170,48 @@ export class GrPluginEndpoints { /** * Get plugin URLs with element and module definitions. - * - * @param {string} name Endpoint name. - * @param {?{ - * type: (string|undefined), - * moduleName: (string|undefined) - * }} opt_options - * @return {!Array} */ - getPlugins(name, opt_options) { - const modulesData = this.getDetails(name, opt_options); + getPlugins(name: string, options?: Options): URL[] { + const modulesData = this.getDetails(name, options); if (!modulesData.length) { return []; } return Array.from(new Set(modulesData.map(m => m.pluginUrl))); } - importUrl(pluginUrl) { - let timerId; - return Promise - .race([ - new Promise((resolve, reject) => { - this._importedUrls.add(pluginUrl.href); - importHref(pluginUrl, resolve, reject); - }), - // Timeout after 3s - new Promise(r => timerId = setTimeout(r, 3000)), - ]) - .finally(() => { - if (timerId) clearTimeout(timerId); - }); + importUrl(pluginUrl: URL) { + let timerId: any; + return Promise.race([ + new Promise((resolve, reject) => { + this._importedUrls.add(pluginUrl.href); + importHref(pluginUrl.href, resolve, reject); + }), + // Timeout after 3s + new Promise(r => (timerId = setTimeout(r, 3000))), + ]).finally(() => { + if (timerId) clearTimeout(timerId); + }); } /** * Get plugin URLs with element and module definitions. - * - * @param {string} name Endpoint name. - * @param {?{ - * type: (string|undefined), - * moduleName: (string|undefined) - * }} opt_options - * @return {!Array>} */ - getAndImportPlugins(name, opt_options) { + getAndImportPlugins(name: string, options?: Options) { return Promise.all( - this.getPlugins(name, opt_options).map(pluginUrl => { - if (this._importedUrls.has(pluginUrl.href)) { - return Promise.resolve(); - } + this.getPlugins(name, options).map(pluginUrl => { + if (this._importedUrls.has(pluginUrl.href)) { + return Promise.resolve(); + } - // TODO: we will deprecate html plugins entirely - // for now, keep the original behavior and import - // only for html ones - if (pluginUrl && pluginUrl.pathname.endsWith('.html')) { - return this.importUrl(pluginUrl); - } else { - return Promise.resolve(); - } - }) + // TODO: we will deprecate html plugins entirely + // for now, keep the original behavior and import + // only for html ones + if (pluginUrl && pluginUrl.pathname.endsWith('.html')) { + return this.importUrl(pluginUrl); + } else { + return Promise.resolve(); + } + }) ); } } diff --git a/polygerrit-ui/app/scripts/import-href.ts b/polygerrit-ui/app/scripts/import-href.ts index 6ff40a5d96..bd1b678292 100644 --- a/polygerrit-ui/app/scripts/import-href.ts +++ b/polygerrit-ui/app/scripts/import-href.ts @@ -20,11 +20,16 @@ // file contains code inside and can't be imported // in es6 modules. +interface ImportHrefElement extends HTMLLinkElement { + __dynamicImportLoaded?: boolean; +} + // run a callback when HTMLImports are ready or immediately if // this api is not available. -function whenImportsReady(cb) { - if (window.HTMLImports) { - HTMLImports.whenReady(cb); +function whenImportsReady(cb: () => void) { + const win = window as Window; + if (win.HTMLImports) { + win.HTMLImports.whenReady(cb); } else { cb(); } @@ -39,37 +44,43 @@ function whenImportsReady(cb) { * element will contain the imported document contents. * * @memberof Polymer - * @param {string} href URL to document to load. - * @param {?function(!Event):void=} onload Callback to notify when an import successfully + * @param href URL to document to load. + * @param onload Callback to notify when an import successfully * loaded. - * @param {?function(!ErrorEvent):void=} onerror Callback to notify when an import + * @param onerror Callback to notify when an import * unsuccessfully loaded. - * @param {boolean=} optAsync True if the import should be loaded `async`. + * @param async True if the import should be loaded `async`. * Defaults to `false`. - * @return {!HTMLLinkElement} The link element for the URL to be loaded. + * @return The link element for the URL to be loaded. */ -export function importHref(href, onload, onerror, optAsync) { - let link = /** @type {HTMLLinkElement} */ - (document.head.querySelector('link[href="' + href + '"][import-href]')); +export function importHref( + href: string, + onload: (e: Event) => void, + onerror: (e: Event) => void, + async = false +): HTMLLinkElement { + let link = document.head.querySelector( + 'link[href="' + href + '"][import-href]' + ) as ImportHrefElement; if (!link) { - link = /** @type {HTMLLinkElement} */ (document.createElement('link')); + link = document.createElement('link') as ImportHrefElement; link.rel = 'import'; link.href = href; link.setAttribute('import-href', ''); } // always ensure link has `async` attribute if user specified one, // even if it was previously not async. This is considered less confusing. - if (optAsync) { + if (async) { link.setAttribute('async', ''); } // NOTE: the link may now be in 3 states: (1) pending insertion, // (2) inflight, (3) already loaded. In each case, we need to add // event listeners to process callbacks. - const cleanup = function() { + const cleanup = function () { link.removeEventListener('load', loadListener); link.removeEventListener('error', errorListener); }; - const loadListener = function(event) { + const loadListener = function (event: Event) { cleanup(); // In case of a successful load, cache the load event on the link so // that it can be used to short-circuit this method in the future when @@ -81,7 +92,7 @@ export function importHref(href, onload, onerror, optAsync) { }); } }; - const errorListener = function(event) { + const errorListener = function (event: Event) { cleanup(); // In case of an error, remove the link from the document so that it // will be automatically created again the next time `importHref` is @@ -97,7 +108,7 @@ export function importHref(href, onload, onerror, optAsync) { }; link.addEventListener('load', loadListener); link.addEventListener('error', errorListener); - if (link.parentNode == null) { + if (link.parentNode === null) { document.head.appendChild(link); // if the link already loaded, dispatch a fake load event // so that listeners are called and get a proper event argument. diff --git a/polygerrit-ui/app/test/test-app-context-init.js b/polygerrit-ui/app/test/test-app-context-init.js index 6fffdba792..88232cd384 100644 --- a/polygerrit-ui/app/test/test-app-context-init.js +++ b/polygerrit-ui/app/test/test-app-context-init.js @@ -17,7 +17,7 @@ // Init app context before any other imports import {initAppContext} from '../services/app-context-init.js'; -import {grReportingMock} from '../services/gr-reporting/gr-reporting_mock'; +import {grReportingMock} from '../services/gr-reporting/gr-reporting_mock.js'; import {appContext} from '../services/app-context.js'; initAppContext(); diff --git a/polygerrit-ui/app/types/globals.ts b/polygerrit-ui/app/types/globals.ts index 167ccda367..a09e651a92 100644 --- a/polygerrit-ui/app/types/globals.ts +++ b/polygerrit-ui/app/types/globals.ts @@ -22,6 +22,7 @@ declare global { ShadyCSS?: { getComputedStyleValue(el: Element, name: string): string; }; + HTMLImports?: {whenReady: (cb: () => void) => void}; } interface Performance {