@rb-mwindh/ngx-theme-manager
Version:
Angular component to switch between different theming stylesheets
186 lines • 20.2 kB
JavaScript
import { Inject, Injectable } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { filter, map, timer } from 'rxjs';
import * as i0 from "@angular/core";
import * as i1 from "@angular/cdk/observers";
import * as i2 from "./theme-registry.service";
/**
* A service that manages the activation and deactivation of themes.
*
* The service uses the `ContentObserver` service to observe changes in the `<head>` element
* and updates the {@link ThemeRegistryService internal theme registry}
* when new `<style>` elements are added to the DOM.
*
* Themes are identified by the `data-theme` attribute on the `<style>` element.
*
* The service provides a method `use` to activate a theme with a given ID
* and deactivate all other themes.
*
* @internal
* @group Services
*/
export class ThemeStyleManagerService {
/**
* Creates a new instance.
*
* Subscribes to the `ContentObserver` to listen for new `<style>` elements
* added to the document head. If a new `<style>` element is added, the
* `#updateRegistry()` method is called.
*
* @param {ContentObserver} observer - The Angular ContentObserver service
* @param {ThemeRegistryService} themeRegistry - A service to register new themes
* @param {Document} document - A reference to the current document
*/
constructor(observer, themeRegistry, document) {
this.observer = observer;
this.themeRegistry = themeRegistry;
this.document = document;
this.observer
.observe(document.head)
.pipe(map((mutations) => mutations.some((mutation) => Array.from(mutation.addedNodes).some((node) => node.nodeName === 'STYLE'))), filter((newStyles) => !!newStyles))
.subscribe(() => this.#updateRegistry());
}
/**
* Activates the theme with the given ID and deactivates all other themes.
*
* @param {string} theme - The theme to activate
* @see turnOn
* @see turnOff
* @remarks A theme may consist of 1 or more `<style>` elements.
*/
use(theme) {
// INFO: [author: NWD8FE, since: 2023/01/26]
// running asynchronously, to give #updateRegistries
// the chance to run first.
timer(0).subscribe(() => {
const styles = this.#getAllThemeStyles();
styles.forEach((el) => {
const id = el.getAttribute('data-theme');
(theme === id ? turnOn : turnOff)(el);
});
});
}
/**
* Get all theme `<style>` elements in the document head.
*
* @private
*/
#getAllThemeStyles() {
return Array.from(this.document.head.querySelectorAll('style[data-theme]'));
}
/**
* Updates the internal theme registry.
*
* Identifies all `<style>` elements without the `data-no-theme` and `data-theme` attributes.
* Extracts the theme annotations from the elements' text content and applies the
* `data-theme` or `data-no-theme` attribute depending on the discovered theme id.
*
* @private
* @see extractThemeAnnotations
* @see applyThemeIdentifier
*/
#updateRegistry() {
Array.from(this.document.head.querySelectorAll('style:not([data-no-theme]):not([data-theme])'))
.map((el) => ({ el, meta: extractThemeAnnotations(el.textContent) }))
.forEach(({ el, meta }) => {
if (applyThemeIdentifier(el, meta?.id)) {
turnOff(el);
this.themeRegistry.register(meta);
}
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.1", ngImport: i0, type: ThemeStyleManagerService, deps: [{ token: i1.ContentObserver }, { token: i2.ThemeRegistryService }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.1", ngImport: i0, type: ThemeStyleManagerService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.1", ngImport: i0, type: ThemeStyleManagerService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: () => [{ type: i1.ContentObserver }, { type: i2.ThemeRegistryService }, { type: Document, decorators: [{
type: Inject,
args: [DOCUMENT]
}] }] });
/**
* Applies a theme identifier to a `<style>` element.
*
* If the provided `id` is truthy, the element receives the attribute
* `data-theme` set to the given `id`. Otherwise, the element
* will get the attribute `data-no-theme` without any value.
*
* @param {HTMLStyleElement} el - The `<style>` element to apply the identifier to
* @param {string | undefined} id - The theme identifier
* @returns {boolean} true, if the style element belongs to a theme
* @group Functions
* @internal
*/
export function applyThemeIdentifier(el, id) {
if (!!id) {
el.setAttribute('data-theme', id);
return true;
}
else {
el.toggleAttribute('data-no-theme', true);
return false;
}
}
/**
* Turn off a style element by setting its `media` attribute to `none`.
*
* @param {HTMLStyleElement} el - The style element to turn off.
* @group Functions
* @internal
*/
export function turnOff(el) {
el.media = 'none';
}
/**
* Turn on a style element by removing its `media` attribute.
*
* @param {HTMLStyleElement} el - The style element to turn on.
* @group Functions
* @internal
*/
export function turnOn(el) {
el.removeAttribute('media');
}
/**
* Extracts theme annotations from a given string.
*
* **Format:** `@@<annotationName> value` (until end of line)
*
* Possible annotation names:
* - `id`: a unique identifier for the theme
* - `displayName`: a human-readable name for the theme
* - `description`: a short description of the theme
* - `defaultTheme`: a boolean flag indicating if this is the default theme
*
* @param {string} s The string to extract annotations from.
* @returns {Theme | null} An object containing the extracted annotations, or null if no annotations were found.
* @group Functions
* @internal
*/
export function extractThemeAnnotations(s) {
if (!s) {
return null;
}
const id = unwrap(/@@id\s+([^\r\n]+)$/m.exec(s));
if (!id) {
return null;
}
const displayName = unwrap(/@@displayName\s+([^\r\n]+)$/m.exec(s)) || id;
const description = unwrap(/@@description\s+([^\r\n]+)$/m.exec(s));
const defaultTheme = /@@default/m.test(s);
return { id, displayName, description, defaultTheme };
}
/**
* Unwrap a match from a regular expression, returning the first captured group as a string.
*
* @param {RegExpExecArray} match - The match to unwrap.
* @group Functions
* @internal
*/
export function unwrap(match) {
return (match && match[1] && match[1].trim()) || undefined;
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGhlbWUtc3R5bGUtbWFuYWdlci5zZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vcHJvamVjdHMvbmd4LXRoZW1lLW1hbmFnZXIvc3JjL2xpYi9pbnRlcm5hbC90aGVtZS1zdHlsZS1tYW5hZ2VyLnNlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLE1BQU0sRUFBRSxVQUFVLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFDbkQsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBRTNDLE9BQU8sRUFBRSxNQUFNLEVBQUUsR0FBRyxFQUFFLEtBQUssRUFBRSxNQUFNLE1BQU0sQ0FBQzs7OztBQUkxQzs7Ozs7Ozs7Ozs7Ozs7R0FjRztBQUlILE1BQU0sT0FBTyx3QkFBd0I7SUFDbkM7Ozs7Ozs7Ozs7T0FVRztJQUNILFlBQ21CLFFBQXlCLEVBQ3pCLGFBQW1DLEVBQ2pCLFFBQWtCO1FBRnBDLGFBQVEsR0FBUixRQUFRLENBQWlCO1FBQ3pCLGtCQUFhLEdBQWIsYUFBYSxDQUFzQjtRQUNqQixhQUFRLEdBQVIsUUFBUSxDQUFVO1FBRXJELElBQUksQ0FBQyxRQUFRO2FBQ1YsT0FBTyxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUM7YUFDdEIsSUFBSSxDQUNILEdBQUcsQ0FBQyxDQUFDLFNBQVMsRUFBRSxFQUFFLENBQ2hCLFNBQVMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxRQUFRLEVBQUUsRUFBRSxDQUMxQixLQUFLLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxVQUFVLENBQUMsQ0FBQyxJQUFJLENBQ2xDLENBQUMsSUFBSSxFQUFFLEVBQUUsQ0FBQyxJQUFJLENBQUMsUUFBUSxLQUFLLE9BQU8sQ0FDcEMsQ0FDRixDQUNGLEVBQ0QsTUFBTSxDQUFDLENBQUMsU0FBUyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQ25DO2FBQ0EsU0FBUyxDQUFDLEdB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QUNkLENBQUM7SUFDRCxNQUFNLEVBQUUsR0FBRyxNQUFNLENBQUMscUJBQXFCLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDakQsSUFBSSxDQUFDLEVBQUUsRUFBRSxDQUFDO1FBQ1IsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0lBQ0QsTUFBTSxXQUFXLEdBQUcsTUFBTSxDQUFDLDhCQUE4QixDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUN6RSxNQUFNLFdBQVcsR0FBRyxNQUFNLENBQUMsOEJBQThCLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDbkUsTUFBTSxZQUFZLEdBQUcsWUFBWSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUMxQyxPQUFPLEVBQUUsRUFBRSxFQUFFLFdBQVcsRUFBRSxXQUFXLEVBQUUsWUFBWSxFQUFFLENBQUM7QUFDeEQsQ0FBQztBQUVEOzs7Ozs7R0FNRztBQUNILE1BQU0sVUFBVSxNQUFNLENBQUMsS0FBNkI7SUFDbEQsT0FBTyxDQUFDLEtBQUssSUFBSSxLQUFLLENBQUMsQ0FBQyxDQUFDLElBQUksS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDLElBQUksU0FBUyxDQUFDO0FBQzdELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBJbmplY3QsIEluamVjdGFibGUgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IERPQ1VNRU5UIH0gZnJvbSAnQGFuZ3VsYXIvY29tbW9uJztcbmltcG9ydCB7IENvbnRlbnRPYnNlcnZlciB9IGZyb20gJ0Bhbmd1bGFyL2Nkay9vYnNlcnZlcnMnO1xuaW1wb3J0IHsgZmlsdGVyLCBtYXAsIHRpbWVyIH0gZnJvbSAncnhqcyc7XG5pbXBvcnQgeyBUaGVtZSB9IGZyb20gJy4uL3RoZW1lJztcbmltcG9ydCB7IFRoZW1lUmVnaXN0cnlTZXJ2aWNlIH0gZnJvbSAnLi90aGVtZS1yZWdpc3RyeS5zZXJ2aWNlJztcblxuLyoqXG4gKiBBIHNlcnZpY2UgdGhhdCBtYW5hZ2VzIHRoZSBhY3RpdmF0aW9uIGFuZCBkZWFjdGl2YXRpb24gb2YgdGhlbWVzLlxuICpcbiAqIFRoZSBzZXJ2aWNlIHVzZXMgdGhlIGBDb250ZW50T2JzZXJ2ZXJgIHNlcnZpY2UgdG8gb2JzZXJ2ZSBjaGFuZ2VzIGluIHRoZSBgPGhlYWQ+YCBlbGVtZW50XG4gKiBhbmQgdXBkYXRlcyB0aGUge0BsaW5rIFRoZW1lUmVnaXN0cnlTZXJ2aWNlIGludGVybmFsIHRoZW1lIHJlZ2lzdHJ5fVxuICogd2hlbiBuZXcgYDxzdHlsZT5gIGVsZW1lbnRzIGFyZSBhZGRlZCB0byB0aGUgRE9NLlxuICpcbiAqIFRoZW1lcyBhcmUgaWRlbnRpZmllZCBieSB0aGUgYGRhdGEtdGhlbWVgIGF0dHJpYnV0ZSBvbiB0aGUgYDxzdHlsZT5gIGVsZW1lbnQuXG4gKlxuICogVGhlIHNlcnZpY2UgcHJvdmlkZXMgYSBtZXRob2QgYHVzZWAgdG8gYWN0aXZhdGUgYSB0aGVtZSB3aXRoIGEgZ2l2ZW4gSURcbiAqIGFuZCBkZWFjdGl2YXRlIGFsbCBvdGhlciB0aGVtZXMuXG4gKlxuICogQGludGVybmFsXG4gKiBAZ3JvdXAgU2VydmljZXNcbiAqL1xuQEluamVjdGFibGUoe1xuICBwcm92aWRlZEluOiAncm9vdCcsXG59KVxuZXhwb3J0IGNsYXNzIFRoZW1lU3R5bGVNYW5hZ2VyU2VydmljZSB7XG4gIC8qKlxuICAgKiBDcmVhdGVzIGEgbmV3IGluc3RhbmNlLlxuICAgKlxuICAgKiBTdWJzY3JpYmVzIHRvIHRoZSBgQ29udGVudE9ic2VydmVyYCB0byBsaXN0ZW4gZm9yIG5ldyBgPHN0eWxlPmAgZWxlbWVudHNcbiAgICogYWRkZWQgdG8gdGhlIGRvY3VtZW50IGhlYWQuIElmIGEgbmV3IGA8c3R5bGU+YCBlbGVtZW50IGlzIGFkZGVkLCB0aGVcbiAgICogYCN1cGRhdGVSZWdpc3RyeSgpYCBtZXRob2QgaXMgY2FsbGVkLlxuICAgKlxuICAgKiBAcGFyYW0ge0NvbnRlbnRPYnNlcnZlcn0gb2JzZXJ2ZXIgLSBUaGUgQW5ndWxhciBDb250ZW50T2JzZXJ2ZXIgc2VydmljZVxuICAgKiBAcGFyYW0ge1RoZW1lUmVnaXN0cnlTZXJ2aWNlfSB0aGVtZVJlZ2lzdHJ5IC0gQSBzZXJ2aWNlIHRvIHJlZ2lzdGVyIG5ldyB0aGVtZXNcbiAgICogQHBhcmFtIHtEb2N1bWVudH0gZG9jdW1lbnQgLSBBIHJlZmVyZW5jZSB0byB0aGUgY3VycmVudCBkb2N1bWVudFxuICAgKi9cbiAgY29uc3RydWN0b3IoXG4gICAgcHJpdmF0ZSByZWFkb25seSBvYnNlcnZlcjogQ29udGVudE9ic2VydmVyLFxuICAgIHByaXZhdGUgcmVhZG9ubHkgdGhlbWVSZWdpc3RyeTogVGhlbWVSZWdpc3RyeVNlcnZpY2UsXG4gICAgQEluamVjdChET0NVTUVOVCkgcHJpdmF0ZSByZWFkb25seSBkb2N1bWVudDogRG9jdW1lbnQsXG4gICkge1xuICAgIHRoaXMub2JzZXJ2ZXJcbiAgICAgIC5vYnNlcnZlKGRvY3VtZW50LmhlYWQpXG4gICAgICAucGlwZShcbiAgICAgICAgbWFwKChtdXRhdGlvbnMpID0+XG4gICAgICAgICAgbXV0YXRpb25zLnNvbWUoKG11dGF0aW9uKSA9PlxuICAgICAgICAgICAgQXJyYXkuZnJvbShtdXRhdGlvbi5hZGRlZE5vZGVzKS5zb21lKFxuICAgICAgICAgICAgICAobm9kZSkgPT4gbm9kZS5ub2RlTmFtZSA9PT0gJ1NUWUxFJyxcbiAgICAgICAgICAgICksXG4gICAgICAgICAgKSxcbiAgICAgICAgKSxcbiAgICAgICAgZmlsdGVyKChuZXdTdHlsZXMpID0+ICEhbmV3U3R5bGVzKSxcbiAgICAgIClcbiAgICAgIC5zdWJzY3JpYmUoKCkgPT4gdGhpcy4jdXBkYXRlUmVnaXN0cnkoKSk7XG4gIH1cblxuICAvKipcbiAgICogQWN0aXZhdGVzIHRoZSB0aGVtZSB3aXRoIHRoZSBnaXZlbiBJRCBhbmQgZGVhY3RpdmF0ZXMgYWxsIG90aGVyIHRoZW1lcy5cbiAgICpcbiAgICogQHBhcmFtIHtzdHJpbmd9IHRoZW1lIC0gVGhlIHRoZW1lIHRvIGFjdGl2YXRlXG4gICAqIEBzZWUgdHVybk9uXG4gICAqIEBzZWUgdHVybk9mZlxuICAgKiBAcmVtYXJrcyBBIHRoZW1lIG1heSBjb25zaXN0IG9mIDEgb3IgbW9yZSBgPHN0eWxlPmAgZWxlbWVudHMuXG4gICAqL1xuICB1c2UodGhlbWU6IHN0cmluZyk6IHZvaWQge1xuICAgIC8vIElORk86IFthdXRob3I6IE5XRDhGRSwgc2luY2U6IDIwMjMvMDEvMjZdXG4gICAgLy8gIHJ1bm5pbmcgYXN5bmNocm9ub3VzbHksIHRvIGdpdmUgI3VwZGF0ZVJlZ2lzdHJpZXNcbiAgICAvLyAgdGhlIGNoYW5jZSB0byBydW4gZmlyc3QuXG4gICAgdGltZXIoMCkuc3Vic2NyaWJlKCgpID0+IHtcbiAgICAgIGNvbnN0IHN0eWxlcyA9IHRoaXMuI2dldEFsbFRoZW1lU3R5bGVzKCk7XG4gICAgICBzdHlsZXMuZm9yRWFjaCgoZWwpID0+IHtcbiAgICAgICAgY29uc3QgaWQgPSBlbC5nZXRBdHRyaWJ1dGUoJ2RhdGEtdGhlbWUnKTtcbiAgICAgICAgKHRoZW1lID09PSBpZCA/IHR1cm5PbiA6IHR1cm5PZmYpKGVsKTtcbiAgICAgIH0pO1xuICAgIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIEdldCBhbGwgdGhlbWUgYDxzdHlsZT5gIGVsZW1lbnRzIGluIHRoZSBkb2N1bWVudCBoZWFkLlxuICAgKlxuICAgKiBAcHJpdmF0ZVxuICAgKi9cbiAgI2dldEFsbFRoZW1lU3R5bGVzKCk6IEhUTUxTdHlsZUVsZW1lbnRbXSB7XG4gICAgcmV0dXJuIEFycmF5LmZyb20oXG4gICAgICB0aGlzLmRvY3VtZW50LmhlYWQucXVlcnlTZWxlY3RvckFsbCgnc3R5bGVbZGF0YS10aGVtZV0nKSxcbiAgICApO1xuICB9XG5cbiAgLyoqXG4gICAqIFVwZGF0ZXMgdGhlIGludGVybmFsIHRoZW1lIHJlZ2lzdHJ5LlxuICAgKlxuICAgKiBJZGVudGlmaWVzIGFsbCBgPHN0eWxlPmAgZWxlbWVudHMgd2l0aG91dCB0aGUgYGRhdGEtbm8tdGhlbWVgIGFuZCBgZGF0YS10aGVtZWAgYXR0cmlidXRlcy5cbiAgICogRXh0cmFjdHMgdGhlIHRoZW1lIGFubm90YXRpb25zIGZyb20gdGhlIGVsZW1lbnRzJyB0ZXh0IGNvbnRlbnQgYW5kIGFwcGxpZXMgdGhlXG4gICAqIGBkYXRhLXRoZW1lYCBvciBgZGF0YS1uby10aGVtZWAgYXR0cmlidXRlIGRlcGVuZGluZyBvbiB0aGUgZGlzY292ZXJlZCB0aGVtZSBpZC5cbiAgICpcbiAgICogQHByaXZhdGVcbiAgICogQHNlZSBleHRyYWN0VGhlbWVBbm5vdGF0aW9uc1xuICAgKiBAc2VlIGFwcGx5VGhlbWVJZGVudGlmaWVyXG4gICAqL1xuICAjdXBkYXRlUmVnaXN0cnkoKSB7XG4gICAgQXJyYXkuZnJvbShcbiAgICAgIHRoaXMuZG9jdW1lbnQuaGVhZC5xdWVyeVNlbGVjdG9yQWxsPEhUTUxTdHlsZUVsZW1lbnQ+KFxuICAgICAgICAnc3R5bGU6bm90KFtkYXRhLW5vLXRoZW1lXSk6bm90KFtkYXRhLXRoZW1lXSknLFxuICAgICAgKSxcbiAgICApXG4gICAgICAubWFwKChlbCkgPT4gKHsgZWwsIG1ldGE6IGV4dHJhY3RUaGVtZUFubm90YXRpb25zKGVsLnRleHRDb250ZW50KSB9KSlcbiAgICAgIC5mb3JFYWNoKCh7IGVsLCBtZXRhIH0pID0+IHtcbiAgICAgICAgaWYgKGFwcGx5VGhlbWVJZGVudGlmaWVyKGVsLCBtZXRhPy5pZCkpIHtcbiAgICAgICAgICB0dXJuT2ZmKGVsKTtcbiAgICAgICAgICB0aGlzLnRoZW1lUmVnaXN0cnkucmVnaXN0ZXIobWV0YSk7XG4gICAgICAgIH1cbiAgICAgIH0pO1xuICB9XG59XG5cbi8qKlxuICogQXBwbGllcyBhIHRoZW1lIGlkZW50aWZpZXIgdG8gYSBgPHN0eWxlPmAgZWxlbWVudC5cbiAqXG4gKiBJZiB0aGUgcHJvdmlkZWQgYGlkYCBpcyB0cnV0aHksIHRoZSBlbGVtZW50IHJlY2VpdmVzIHRoZSBhdHRyaWJ1dGVcbiAqIGBkYXRhLXRoZW1lYCBzZXQgdG8gdGhlIGdpdmVuIGBpZGAuIE90aGVyd2lzZSwgdGhlIGVsZW1lbnRcbiAqIHdpbGwgZ2V0IHRoZSBhdHRyaWJ1dGUgYGRhdGEtbm8tdGhlbWVgIHdpdGhvdXQgYW55IHZhbHVlLlxuICpcbiAqIEBwYXJhbSB7SFRNTFN0eWxlRWxlbWVudH0gZWwgLSBUaGUgYDxzdHlsZT5gIGVsZW1lbnQgdG8gYXBwbHkgdGhlIGlkZW50aWZpZXIgdG9cbiAqIEBwYXJhbSB7c3RyaW5nIHwgdW5kZWZpbmVkfSBpZCAtIFRoZSB0aGVtZSBpZGVudGlmaWVyXG4gKiBAcmV0dXJucyB7Ym9vbGVhbn0gdHJ1ZSwgaWYgdGhlIHN0eWxlIGVsZW1lbnQgYmVsb25ncyB0byBhIHRoZW1lXG4gKiBAZ3JvdXAgRnVuY3Rpb25zXG4gKiBAaW50ZXJuYWxcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGFwcGx5VGhlbWVJZGVudGlmaWVyKFxuICBlbDogSFRNTFN0eWxlRWxlbWVudCxcbiAgaWQ/OiBzdHJpbmcsXG4pOiBib29sZWFuIHtcbiAgaWYgKCEhaWQpIHtcbiAgICBlbC5zZXRBdHRyaWJ1dGUoJ2RhdGEtdGhlbWUnLCBpZCk7XG4gICAgcmV0dXJuIHRydWU7XG4gIH0gZWxzZSB7XG4gICAgZWwudG9nZ2xlQXR0cmlidXRlKCdkYXRhLW5vLXRoZW1lJywgdHJ1ZSk7XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG59XG5cbi8qKlxuICogVHVybiBvZmYgYSBzdHlsZSBlbGVtZW50IGJ5IHNldHRpbmcgaXRzIGBtZWRpYWAgYXR0cmlidXRlIHRvIGBub25lYC5cbiAqXG4gKiBAcGFyYW0ge0hUTUxTdHlsZUVsZW1lbnR9IGVsIC0gVGhlIHN0eWxlIGVsZW1lbnQgdG8gdHVybiBvZmYuXG4gKiBAZ3JvdXAgRnVuY3Rpb25zXG4gKiBAaW50ZXJuYWxcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHR1cm5PZmYoZWw6IEhUTUxTdHlsZUVsZW1lbnQpOiB2b2lkIHtcbiAgZWwubWVkaWEgPSAnbm9uZSc7XG59XG5cbi8qKlxuICogVHVybiBvbiBhIHN0eWxlIGVsZW1lbnQgYnkgcmVtb3ZpbmcgaXRzIGBtZWRpYWAgYXR0cmlidXRlLlxuICpcbiAqIEBwYXJhbSB7SFRNTFN0eWxlRWxlbWVudH0gZWwgLSBUaGUgc3R5bGUgZWxlbWVudCB0byB0dXJuIG9uLlxuICogQGdyb3VwIEZ1bmN0aW9uc1xuICogQGludGVybmFsXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiB0dXJuT24oZWw6IEhUTUxTdHlsZUVsZW1lbnQpOiB2b2lkIHtcbiAgZWwucmVtb3ZlQXR0cmlidXRlKCdtZWRpYScpO1xufVxuXG4vKipcbiAqIEV4dHJhY3RzIHRoZW1lIGFubm90YXRpb25zIGZyb20gYSBnaXZlbiBzdHJpbmcuXG4gKlxuICogKipGb3JtYXQ6KiogYEBAPGFubm90YXRpb25OYW1lPiB2YWx1ZWAgKHVudGlsIGVuZCBvZiBsaW5lKVxuICpcbiAqIFBvc3NpYmxlIGFubm90YXRpb24gbmFtZXM6XG4gKiAtIGBpZGA6IGEgdW5pcXVlIGlkZW50aWZpZXIgZm9yIHRoZSB0aGVtZVxuICogLSBgZGlzcGxheU5hbWVgOiBhIGh1bWFuLXJlYWRhYmxlIG5hbWUgZm9yIHRoZSB0aGVtZVxuICogLSBgZGVzY3JpcHRpb25gOiBhIHNob3J0IGRlc2NyaXB0aW9uIG9mIHRoZSB0aGVtZVxuICogLSBgZGVmYXVsdFRoZW1lYDogYSBib29sZWFuIGZsYWcgaW5kaWNhdGluZyBpZiB0aGlzIGlzIHRoZSBkZWZhdWx0IHRoZW1lXG4gKlxuICogQHBhcmFtIHtzdHJpbmd9IHMgVGhlIHN0cmluZyB0byBleHRyYWN0IGFubm90YXRpb25zIGZyb20uXG4gKiBAcmV0dXJucyB7VGhlbWUgfCBudWxsfSBBbiBvYmplY3QgY29udGFpbmluZyB0aGUgZXh0cmFjdGVkIGFubm90YXRpb25zLCBvciBudWxsIGlmIG5vIGFubm90YXRpb25zIHdlcmUgZm91bmQuXG4gKiBAZ3JvdXAgRnVuY3Rpb25zXG4gKiBAaW50ZXJuYWxcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGV4dHJhY3RUaGVtZUFubm90YXRpb25zKFxuICBzOiB1bmRlZmluZWQgfCBudWxsIHwgc3RyaW5nLFxuKTogVGhlbWUgfCBudWxsIHtcbiAgaWYgKCFzKSB7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cbiAgY29uc3QgaWQgPSB1bndyYXAoL0BAaWRcXHMrKFteXFxyXFxuXSspJC9tLmV4ZWMocykpO1xuICBpZiAoIWlkKSB7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cbiAgY29uc3QgZGlzcGxheU5hbWUgPSB1bndyYXAoL0BAZGlzcGxheU5hbWVcXHMrKFteXFxyXFxuXSspJC9tLmV4ZWMocykpIHx8IGlkO1xuICBjb25zdCBkZXNjcmlwdGlvbiA9IHVud3JhcCgvQEBkZXNjcmlwdGlvblxccysoW15cXHJcXG5dKykkL20uZXhlYyhzKSk7XG4gIGNvbnN0IGRlZmF1bHRUaGVtZSA9IC9AQGRlZmF1bHQvbS50ZXN0KHMpO1xuICByZXR1cm4geyBpZCwgZGlzcGxheU5hbWUsIGRlc2NyaXB0aW9uLCBkZWZhdWx0VGhlbWUgfTtcbn1cblxuLyoqXG4gKiBVbndyYXAgYSBtYXRjaCBmcm9tIGEgcmVndWxhciBleHByZXNzaW9uLCByZXR1cm5pbmcgdGhlIGZpcnN0IGNhcHR1cmVkIGdyb3VwIGFzIGEgc3RyaW5nLlxuICpcbiAqIEBwYXJhbSB7UmVnRXhwRXhlY0FycmF5fSBtYXRjaCAtIFRoZSBtYXRjaCB0byB1bndyYXAuXG4gKiBAZ3JvdXAgRnVuY3Rpb25zXG4gKiBAaW50ZXJuYWxcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHVud3JhcChtYXRjaDogUmVnRXhwRXhlY0FycmF5IHwgbnVsbCk6IHN0cmluZyB8IHVuZGVmaW5lZCB7XG4gIHJldHVybiAobWF0Y2ggJiYgbWF0Y2hbMV0gJiYgbWF0Y2hbMV0udHJpbSgpKSB8fCB1bmRlZmluZWQ7XG59XG4iXX0=