UNPKG

@ogs-gmbh/ngx-utils

Version:

A lightweight collection of utility functions and helpers for Angular applications

202 lines 26.5 kB
/* eslint-disable @tseslint/no-non-null-assertion */ import { computed, Directive, ElementRef, inject, input, Renderer2 } from '@angular/core'; import { toObservable } from '@angular/core/rxjs-interop'; import { animationFrameScheduler, debounceTime, filter, fromEvent, merge, throttleTime } from 'rxjs'; import { CanvasMeasurerService } from './services/canvas-measurer-service.service'; import * as i0 from "@angular/core"; /** * Rounds a number up only if its fractional part is at or above the given threshold. * * @param value - The value to round. * @param threshold - The fractional cutoff (0–1) above which to round up. * @returns The rounded integer. */ function roundWithThreshold(value, threshold) { const integerPart = Math.floor(value); const fraction = value - integerPart; return fraction >= threshold ? Math.ceil(value) : integerPart; } /** * Determines whether a word of a given width fits into the remaining line width, * taking into account the ellipsis width on the last line. * * @param wordWidth - Pixel width of the current word. * @param remainingWidth - Remaining pixel width in the current line. * @param currentLine - 1-based index of the current line. * @param maxLines - Total number of allowed lines. * @param ellipsisWidth - Pixel width of the ellipsis string. * @returns True if the word can fit, false otherwise. */ // eslint-disable-next-line @tseslint/max-params function fits(wordWidth, remainingWidth, currentLine, maxLines, ellipsisWidth) { // If last word reached take ellipsis into account if (currentLine === maxLines) return wordWidth + ellipsisWidth <= remainingWidth; return wordWidth <= remainingWidth; } /** * Clamps text to its available space and appends a custom ellipsis. * * @remarks * Limits content automatically to the available size of the parent container. * Takes Line height into account as well as preserving whole words. * Works with changing input in form of signals as well as static input. * * @example **Template (HTML)** * ```html * <p * textClamp * [text]="myText()" * [ellipsis]="'… more'"></p> * ``` * * @example **Component (TypeScript)** * ```ts * @Component({ * standalone: true, * selector: 'app-foo', * imports: [TextClampDirective] * }) * export class ArticleCardComponent { * protected myText: WritableSignal<string> = signal<string>("..."); * * protected onMyEvent(): void { * this.myText.set("something else...") * } * } * ``` */ export class TextClampDirective { _isTextInitialized = false; _renderer = inject(Renderer2); _htmlElement = inject((ElementRef)); get _nativeElement() { if (this._htmlElement?.nativeElement) return this._htmlElement.nativeElement; return undefined; } text = input.required(); ellipsis = input("..."); _textChange$ = toObservable(this.text); _canvasMeasureService = inject(CanvasMeasurerService); // eslint-disable-next-line @unicorn/consistent-function-scoping _words = computed(() => { let words = []; if (this.text()) words = this.text().split(" "); return words; }); ngAfterViewInit() { const visibility$ = fromEvent(document, "visibilitychange"); const focus$ = fromEvent(window, "focus"); const resize$ = fromEvent(window, "resize"); // Slower throttle for refocus // eslint-disable-next-line @tseslint/typedef const refocus$ = merge(visibility$, focus$).pipe(throttleTime(500)); // Fast throttle for change in text or resize // eslint-disable-next-line @tseslint/typedef const fastChange$ = merge(resize$, this._textChange$).pipe(debounceTime(10)); merge(refocus$, fastChange$) .pipe(filter(() => !document.hidden), debounceTime(0, animationFrameScheduler)) .subscribe(() => { void this.scheduleEllipsis(); }); // Setting initial text setTimeout(() => { if (this._isTextInitialized) void this.scheduleEllipsis(); }, 0); } setText(text) { if (this._nativeElement) this._renderer.setProperty(this._nativeElement, 'textContent', text); } /** * Schedules the ellipsis calculation by waiting for the font to be fully loaded so it can be measured correctly */ async scheduleEllipsis() { await document.fonts.ready; setTimeout(() => { if (this._nativeElement && this.text()) { // Setting text for the text-container to adjust to its full possible height this.setText(this.text()); if (this._nativeElement.clientHeight === this._nativeElement.scrollHeight) return; requestAnimationFrame(() => { const clampedText = this.getClampedText(); if (clampedText) this.setText(clampedText); }); } }, 0); } /** * Calculates the multiline ellipsis by measuring word widths * against the available space and line count. * * @returns clamped text or undefined if measurement went wrong */ getClampedText() { if (this._nativeElement && this.text()) { if (!this._isTextInitialized) this._isTextInitialized = true; let clampText = ""; const elementStyle = getComputedStyle(this._nativeElement); const lineHeight = Number.parseFloat(elementStyle.lineHeight); const lineCount = roundWithThreshold(this._nativeElement.clientHeight / lineHeight, 0.9); const ellipsisWidth = this._canvasMeasureService.getRenderedStringWidth(elementStyle, ` ${this.ellipsis()} `); if (!ellipsisWidth) return undefined; let usedWordsCount = 0; let linePointer = 1; let remainingLineWidth = this._nativeElement.clientWidth; if (lineCount > 0) { for (let i = 0; i < this._words().length; i++) { // eslint-disable-next-line prefer-template const word = i === 0 ? this._words()[i] : " " + this._words()[i]; const wordWidth = this._canvasMeasureService.getRenderedStringWidth(elementStyle, word); if (!wordWidth) return undefined; // Check if word fits in line if (fits(wordWidth, remainingLineWidth, linePointer, lineCount, ellipsisWidth)) { clampText += word; remainingLineWidth -= wordWidth; usedWordsCount++; // eslint-disable-next-line @stylistic/ts/brace-style } // Check if there is a next line else if (linePointer < lineCount) { linePointer++; remainingLineWidth = this._nativeElement.clientWidth; // Check if word fits in line if (fits(wordWidth, remainingLineWidth, linePointer, lineCount, ellipsisWidth)) { clampText += word; remainingLineWidth -= wordWidth; usedWordsCount++; } else break; } else break; } if (usedWordsCount > 0 && usedWordsCount < this._words().length) clampText += this.ellipsis(); } return clampText; } return undefined; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TextClampDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "18.2.14", type: TextClampDirective, isStandalone: true, selector: "[textClamp]", inputs: { text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: true, transformFunction: null }, ellipsis: { classPropertyName: "ellipsis", publicName: "ellipsis", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TextClampDirective, decorators: [{ type: Directive, args: [{ selector: '[textClamp]', standalone: true }] }] }); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGV4dC1jbGFtcC5kaXJlY3RpdmUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvZGlyZWN0aXZlcy90ZXh0LWNsYW1wL3RleHQtY2xhbXAuZGlyZWN0aXZlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLG9EQUFvRDtBQUNwRCxPQUFPLEVBQWlCLFFBQVEsRUFBRSxTQUFTLEVBQUUsVUFBVSxFQUFFLE1BQU0sRUFBRSxLQUFLLEVBQWUsU0FBUyxFQUFVLE1BQU0sZUFBZSxDQUFDO0FBQzlILE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUMxRCxPQUFPLEVBQUUsdUJBQXVCLEVBQUUsWUFBWSxFQUFFLE1BQU0sRUFBRSxTQUFTLEVBQUUsS0FBSyxFQUFjLFlBQVksRUFBRSxNQUFNLE1BQU0sQ0FBQztBQUNqSCxPQUFPLEVBQUUscUJBQXFCLEVBQUUsTUFBTSw0Q0FBNEMsQ0FBQzs7QUFFbkY7Ozs7OztHQU1HO0FBQ0gsU0FBUyxrQkFBa0IsQ0FBRSxLQUFhLEVBQUUsU0FBaUI7SUFDM0QsTUFBTSxXQUFXLEdBQVcsSUFBSSxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUM5QyxNQUFNLFFBQVEsR0FBVyxLQUFLLEdBQUcsV0FBVyxDQUFDO0lBRTdDLE9BQU8sUUFBUSxJQUFJLFNBQVM7UUFDMUIsQ0FBQyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDO1FBQ2xCLENBQUMsQ0FBQyxXQUFXLENBQUM7QUFDbEIsQ0FBQztBQUVEOzs7Ozs7Ozs7O0dBVUc7QUFDSCxnREFBZ0Q7QUFDaEQsU0FBUyxJQUFJLENBQ1gsU0FBaUIsRUFDakIsY0FBc0IsRUFDdEIsV0FBbUIsRUFDbkIsUUFBZ0IsRUFDaEIsYUFBcUI7SUFFckIsa0RBQWtEO0lBQ2xELElBQUksV0FBVyxLQUFLLFFBQVE7UUFDMUIsT0FBTyxTQUFTLEdBQUcsYUFBYSxJQUFJLGNBQWMsQ0FBQztJQUVyRCxP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QUMsY0FBYyxDQUFDLFdBQVcsQ0FBQzt3QkFFckQsNkJBQTZCO3dCQUM3QixJQUFJLElBQUksQ0FBQyxTQUFTLEVBQUUsa0JBQWtCLEVBQUUsV0FBVyxFQUFFLFNBQVMsRUFBRSxhQUFhLENBQUMsRUFBRSxDQUFDOzRCQUMvRSxTQUFTLElBQUksSUFBSSxDQUFDOzRCQUNsQixrQkFBa0IsSUFBSSxTQUFTLENBQUM7NEJBQ2hDLGNBQWMsRUFBRSxDQUFDO3dCQUNuQixDQUFDOzs0QkFDQyxNQUFNO29CQUNWLENBQUM7O3dCQUNDLE1BQU07Z0JBQ1YsQ0FBQztnQkFHRCxJQUFJLGNBQWMsR0FBRyxDQUFDLElBQUksY0FBYyxHQUFHLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQyxNQUFNO29CQUM3RCxTQUFTLElBQUksSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO1lBQ2pDLENBQUM7WUFFRCxPQUFPLFNBQVMsQ0FBQztRQUNuQixDQUFDO1FBRUQsT0FBTyxTQUFTLENBQUM7SUFDbkIsQ0FBQzt3R0F6SlUsa0JBQWtCOzRGQUFsQixrQkFBa0I7OzRGQUFsQixrQkFBa0I7a0JBSjlCLFNBQVM7bUJBQUM7b0JBQ1QsUUFBUSxFQUFFLGFBQWE7b0JBQ3ZCLFVBQVUsRUFBRSxJQUFJO2lCQUNqQiIsInNvdXJjZXNDb250ZW50IjpbIi8qIGVzbGludC1kaXNhYmxlIEB0c2VzbGludC9uby1ub24tbnVsbC1hc3NlcnRpb24gKi9cbmltcG9ydCB7IEFmdGVyVmlld0luaXQsIGNvbXB1dGVkLCBEaXJlY3RpdmUsIEVsZW1lbnRSZWYsIGluamVjdCwgaW5wdXQsIElucHV0U2lnbmFsLCBSZW5kZXJlcjIsIFNpZ25hbCB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgdG9PYnNlcnZhYmxlIH0gZnJvbSAnQGFuZ3VsYXIvY29yZS9yeGpzLWludGVyb3AnO1xuaW1wb3J0IHsgYW5pbWF0aW9uRnJhbWVTY2hlZHVsZXIsIGRlYm91bmNlVGltZSwgZmlsdGVyLCBmcm9tRXZlbnQsIG1lcmdlLCBPYnNlcnZhYmxlLCB0aHJvdHRsZVRpbWUgfSBmcm9tICdyeGpzJztcbmltcG9ydCB7IENhbnZhc01lYXN1cmVyU2VydmljZSB9IGZyb20gJy4vc2VydmljZXMvY2FudmFzLW1lYXN1cmVyLXNlcnZpY2Uuc2VydmljZSc7XG5cbi8qKlxuICogUm91bmRzIGEgbnVtYmVyIHVwIG9ubHkgaWYgaXRzIGZyYWN0aW9uYWwgcGFydCBpcyBhdCBvciBhYm92ZSB0aGUgZ2l2ZW4gdGhyZXNob2xkLlxuICpcbiAqIEBwYXJhbSB2YWx1ZSAtIFRoZSB2YWx1ZSB0byByb3VuZC5cbiAqIEBwYXJhbSB0aHJlc2hvbGQgLSBUaGUgZnJhY3Rpb25hbCBjdXRvZmYgKDDigJMxKSBhYm92ZSB3aGljaCB0byByb3VuZCB1cC5cbiAqIEByZXR1cm5zIFRoZSByb3VuZGVkIGludGVnZXIuXG4gKi9cbmZ1bmN0aW9uIHJvdW5kV2l0aFRocmVzaG9sZCAodmFsdWU6IG51bWJlciwgdGhyZXNob2xkOiBudW1iZXIpOiBudW1iZXIge1xuICBjb25zdCBpbnRlZ2VyUGFydDogbnVtYmVyID0gTWF0aC5mbG9vcih2YWx1ZSk7XG4gIGNvbnN0IGZyYWN0aW9uOiBudW1iZXIgPSB2YWx1ZSAtIGludGVnZXJQYXJ0O1xuXG4gIHJldHVybiBmcmFjdGlvbiA+PSB0aHJlc2hvbGRcbiAgICA/IE1hdGguY2VpbCh2YWx1ZSlcbiAgICA6IGludGVnZXJQYXJ0O1xufVxuXG4vKipcbiAqIERldGVybWluZXMgd2hldGhlciBhIHdvcmQgb2YgYSBnaXZlbiB3aWR0aCBmaXRzIGludG8gdGhlIHJlbWFpbmluZyBsaW5lIHdpZHRoLFxuICogdGFraW5nIGludG8gYWNjb3VudCB0aGUgZWxsaXBzaXMgd2lkdGggb24gdGhlIGxhc3QgbGluZS5cbiAqXG4gKiBAcGFyYW0gd29yZFdpZHRoIC0gUGl4ZWwgd2lkdGggb2YgdGhlIGN1cnJlbnQgd29yZC5cbiAqIEBwYXJhbSByZW1haW5pbmdXaWR0aCAtIFJlbWFpbmluZyBwaXhlbCB3aWR0aCBpbiB0aGUgY3VycmVudCBsaW5lLlxuICogQHBhcmFtIGN1cnJlbnRMaW5lIC0gMS1iYXNlZCBpbmRleCBvZiB0aGUgY3VycmVudCBsaW5lLlxuICogQHBhcmFtIG1heExpbmVzIC0gVG90YWwgbnVtYmVyIG9mIGFsbG93ZWQgbGluZXMuXG4gKiBAcGFyYW0gZWxsaXBzaXNXaWR0aCAtIFBpeGVsIHdpZHRoIG9mIHRoZSBlbGxpcHNpcyBzdHJpbmcuXG4gKiBAcmV0dXJucyBUcnVlIGlmIHRoZSB3b3JkIGNhbiBmaXQsIGZhbHNlIG90aGVyd2lzZS5cbiAqL1xuLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0c2VzbGludC9tYXgtcGFyYW1zXG5mdW5jdGlvbiBmaXRzIChcbiAgd29yZFdpZHRoOiBudW1iZXIsXG4gIHJlbWFpbmluZ1dpZHRoOiBudW1iZXIsXG4gIGN1cnJlbnRMaW5lOiBudW1iZXIsXG4gIG1heExpbmVzOiBudW1iZXIsXG4gIGVsbGlwc2lzV2lkdGg6IG51bWJlclxuKTogYm9vbGVhbiB7XG4gIC8vIElmIGxhc3Qgd29yZCByZWFjaGVkIHRha2UgZWxsaXBzaXMgaW50byBhY2NvdW50XG4gIGlmIChjdXJyZW50TGluZSA9PT0gbWF4TGluZXMpXG4gICAgcmV0dXJuIHdvcmRXaWR0aCArIGVsbGlwc2lzV2lkdGggPD0gcmVtYWluaW5nV2lkdGg7XG5cbiAgcmV0dXJuIHdvcmRXaWR0aCA8PSByZW1haW5pbmdXaWR0aDtcbn1cblxuLyoqXG4gKiBDbGFtcHMgdGV4dCB0byBpdHMgYXZhaWxhYmxlIHNwYWNlIGFuZCBhcHBlbmRzIGEgY3VzdG9tIGVsbGlwc2lzLlxuICpcbiAqIEByZW1hcmtzXG4gKiBMaW1pdHMgY29udGVudCBhdXRvbWF0aWNhbGx5IHRvIHRoZSBhdmFpbGFibGUgc2l6ZSBvZiB0aGUgcGFyZW50IGNvbnRhaW5lci5cbiAqIFRha2VzIExpbmUgaGVpZ2h0IGludG8gYWNjb3VudCBhcyB3ZWxsIGFzIHByZXNlcnZpbmcgd2hvbGUgd29yZHMuXG4gKiBXb3JrcyB3aXRoIGNoYW5naW5nIGlucHV0IGluIGZvcm0gb2Ygc2lnbmFscyBhcyB3ZWxsIGFzIHN0YXRpYyBpbnB1dC5cbiAqXG4gKiBAZXhhbXBsZSAqKlRlbXBsYXRlIChIVE1MKSoqXG4gKiBgYGBodG1sXG4gKiA8cFxuICogICB0ZXh0Q2xhbXBcbiAqICAgW3RleHRdPVwibXlUZXh0KClcIlxuICogICBbZWxsaXBzaXNdPVwiJ+KApiBtb3JlJ1wiPjwvcD5cbiAqIGBgYFxuICpcbiAqIEBleGFtcGxlICoqQ29tcG9uZW50IChUeXBlU2NyaXB0KSoqXG4gKiBgYGB0c1xuICogQENvbXBvbmVudCh7XG4gKiAgIHN0YW5kYWxvbmU6IHRydWUsXG4gKiAgIHNlbGVjdG9yOiAnYXBwLWZvbycsXG4gKiAgIGltcG9ydHM6IFtUZXh0Q2xhbXBEaXJlY3RpdmVdXG4gKiB9KVxuICogZXhwb3J0IGNsYXNzIEFydGljbGVDYXJkQ29tcG9uZW50IHtcbiAqICAgcHJvdGVjdGVkIG15VGV4dDogV3JpdGFibGVTaWduYWw8c3RyaW5nPiA9IHNpZ25hbDxzdHJpbmc+KFwiLi4uXCIpO1xuICpcbiAqICAgcHJvdGVjdGVkIG9uTXlFdmVudCgpOiB2b2lkIHtcbiAqICAgIHRoaXMubXlUZXh0LnNldChcInNvbWV0aGluZyBlbHNlLi4uXCIpXG4gKiAgIH1cbiAqIH1cbiAqIGBgYFxuICovXG5cbkBEaXJlY3RpdmUoe1xuICBzZWxlY3RvcjogJ1t0ZXh0Q2xhbXBdJyxcbiAgc3RhbmRhbG9uZTogdHJ1ZVxufSlcbmV4cG9ydCBjbGFzcyBUZXh0Q2xhbXBEaXJlY3RpdmUgaW1wbGVtZW50cyBBZnRlclZpZXdJbml0IHtcbiAgcHJpdmF0ZSBfaXNUZXh0SW5pdGlhbGl6ZWQ6IGJvb2xlYW4gPSBmYWxzZTtcblxuICBwcml2YXRlIHJlYWRvbmx5IF9yZW5kZXJlcjogUmVuZGVyZXIyID0gaW5qZWN0KFJlbmRlcmVyMik7XG5cbiAgcHJpdmF0ZSBfaHRtbEVsZW1lbnQ6IEVsZW1lbnRSZWY8SFRNTEVsZW1lbnQ+IHwgdW5kZWZpbmVkID0gaW5qZWN0PEVsZW1lbnRSZWY8SFRNTEVsZW1lbnQ+PihFbGVtZW50UmVmPEhUTUxFbGVtZW50Pik7XG5cbiAgcHJpdmF0ZSBnZXQgX25hdGl2ZUVsZW1lbnQgKCk6IEhUTUxFbGVtZW50IHwgdW5kZWZpbmVkIHtcbiAgICBpZiAodGhpcy5faHRtbEVsZW1lbnQ/Lm5hdGl2ZUVsZW1lbnQpXG4gICAgICByZXR1cm4gdGhpcy5faHRtbEVsZW1lbnQubmF0aXZlRWxlbWVudDtcblxuXG4gICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgfVxuXG4gIHB1YmxpYyB0ZXh0OiBJbnB1dFNpZ25hbDxzdHJpbmcgfCB1bmRlZmluZWQ+ID0gaW5wdXQucmVxdWlyZWQoKTtcblxuICBwdWJsaWMgZWxsaXBzaXM6IElucHV0U2lnbmFsPHN0cmluZz4gPSBpbnB1dChcIi4uLlwiKTtcblxuICBwcml2YXRlIF90ZXh0Q2hhbmdlJDogT2JzZXJ2YWJsZTxzdHJpbmcgfCB1bmRlZmluZWQ+ID0gdG9PYnNlcnZhYmxlKHRoaXMudGV4dCk7XG5cbiAgcHJpdmF0ZSBfY2FudmFzTWVhc3VyZVNlcnZpY2U6IENhbnZhc01lYXN1cmVyU2VydmljZSA9IGluamVjdChDYW52YXNNZWFzdXJlclNlcnZpY2UpO1xuXG4gIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdW5pY29ybi9jb25zaXN0ZW50LWZ1bmN0aW9uLXNjb3BpbmdcbiAgcHJpdmF0ZSBfd29yZHM6IFNpZ25hbDxzdHJpbmdbXT4gPSBjb21wdXRlZCgoKSA9PiB7XG4gICAgbGV0IHdvcmRzOiBzdHJpbmdbXSA9IFtdO1xuXG4gICAgaWYgKHRoaXMudGV4dCgpKVxuXG4gICAgICB3b3JkcyA9IHRoaXMudGV4dCgpIS5zcGxpdChcIiBcIik7XG5cbiAgICByZXR1cm4gd29yZHM7XG4gIH0pO1xuXG4gIG5nQWZ0ZXJWaWV3SW5pdCAoKTogdm9pZCB7XG4gICAgY29uc3QgdmlzaWJpbGl0eSQ6IE9ic2VydmFibGU8RXZlbnQ+ID0gZnJvbUV2ZW50KGRvY3VtZW50LCBcInZpc2liaWxpdHljaGFuZ2VcIik7XG4gICAgY29uc3QgZm9jdXMkOiBPYnNlcnZhYmxlPEV2ZW50PiA9IGZyb21FdmVudCh3aW5kb3csIFwiZm9jdXNcIik7XG4gICAgY29uc3QgcmVzaXplJDogT2JzZXJ2YWJsZTxFdmVudD4gPSBmcm9tRXZlbnQod2luZG93LCBcInJlc2l6ZVwiKTtcbiAgICAvLyBTbG93ZXIgdGhyb3R0bGUgZm9yIHJlZm9jdXNcbiAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgQHRzZXNsaW50L3R5cGVkZWZcbiAgICBjb25zdCByZWZvY3VzJCA9IG1lcmdlKHZpc2liaWxpdHkkLCBmb2N1cyQpLnBpcGUodGhyb3R0bGVUaW1lKDUwMCkpO1xuICAgIC8vIEZhc3QgdGhyb3R0bGUgZm9yIGNoYW5nZSBpbiB0ZXh0IG9yIHJlc2l6ZVxuICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHNlc2xpbnQvdHlwZWRlZlxuICAgIGNvbnN0IGZhc3RDaGFuZ2UkID0gbWVyZ2UocmVzaXplJCwgdGhpcy5fdGV4dENoYW5nZSQpLnBpcGUoZGVib3VuY2VUaW1lKDEwKSk7XG5cbiAgICBtZXJnZShyZWZvY3VzJCwgZmFzdENoYW5nZSQpXG4gICAgICAucGlwZShcbiAgICAgICAgZmlsdGVyKCgpID0+ICFkb2N1bWVudC5oaWRkZW4pLFxuICAgICAgICBkZWJvdW5jZVRpbWUoMCwgYW5pbWF0aW9uRnJhbWVTY2hlZHVsZXIpXG4gICAgICApXG4gICAgICAuc3Vic2NyaWJlKCgpID0+IHtcbiAgICAgICAgdm9pZCB0aGlzLnNjaGVkdWxlRWxsaXBzaXMoKTtcbiAgICAgIH0pO1xuICAgIC8vIFNldHRpbmcgaW5pdGlhbCB0ZXh0XG4gICAgc2V0VGltZW91dCgoKSA9PiB7XG4gICAgICBpZiAodGhpcy5faXNUZXh0SW5pdGlhbGl6ZWQpXG4gICAgICAgIHZvaWQgdGhpcy5zY2hlZHVsZUVsbGlwc2lzKCk7XG4gICAgfSwgMCk7XG4gIH1cblxuICBzZXRUZXh0ICh0ZXh0OiBzdHJpbmcpOiB2b2lkIHtcbiAgICBpZiAodGhpcy5fbmF0aXZlRWxlbWVudClcbiAgICAgIHRoaXMuX3JlbmRlcmVyLnNldFByb3BlcnR5KHRoaXMuX25hdGl2ZUVsZW1lbnQsICd0ZXh0Q29udGVudCcsIHRleHQpO1xuICB9XG5cbiAgLyoqXG4gICAqIFNjaGVkdWxlcyB0aGUgZWxsaXBzaXMgY2FsY3VsYXRpb24gYnkgd2FpdGluZyBmb3IgdGhlIGZvbnQgdG8gYmUgZnVsbHkgbG9hZGVkIHNvIGl0IGNhbiBiZSBtZWFzdXJlZCBjb3JyZWN0bHlcbiAgICovXG4gIHByaXZhdGUgYXN5bmMgc2NoZWR1bGVFbGxpcHNpcyAoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgYXdhaXQgZG9jdW1lbnQuZm9udHMucmVhZHk7XG4gICAgc2V0VGltZW91dCgoKSA9PiB7XG4gICAgICBpZiAodGhpcy5fbmF0aXZlRWxlbWVudCAmJiB0aGlzLnRleHQoKSkge1xuICAgICAgICAvLyBTZXR0aW5nIHRleHQgZm9yIHRoZSB0ZXh0LWNvbnRhaW5lciB0byBhZGp1c3QgdG8gaXRzIGZ1bGwgcG9zc2libGUgaGVpZ2h0XG4gICAgICAgIHRoaXMuc2V0VGV4dCh0aGlzLnRleHQoKSEpO1xuXG4gICAgICAgIGlmICh0aGlzLl9uYXRpdmVFbGVtZW50LmNsaWVudEhlaWdodCA9PT0gdGhpcy5fbmF0aXZlRWxlbWVudC5zY3JvbGxIZWlnaHQpIHJldHVybjtcblxuICAgICAgICByZXF1ZXN0QW5pbWF0aW9uRnJhbWUoKCkgPT4ge1xuICAgICAgICAgIGNvbnN0IGNsYW1wZWRUZXh0OiBzdHJpbmcgfCB1bmRlZmluZWQgPSB0aGlzLmdldENsYW1wZWRUZXh0KCk7XG5cbiAgICAgICAgICBpZiAoY2xhbXBlZFRleHQpXG4gICAgICAgICAgICB0aGlzLnNldFRleHQoY2xhbXBlZFRleHQpO1xuICAgICAgICB9KTtcbiAgICAgIH1cbiAgICB9LCAwKTtcbiAgfVxuXG5cbiAgLyoqXG4gICAqIENhbGN1bGF0ZXMgdGhlIG11bHRpbGluZSBlbGxpcHNpcyBieSBtZWFzdXJpbmcgd29yZCB3aWR0aHNcbiAgICogYWdhaW5zdCB0aGUgYXZhaWxhYmxlIHNwYWNlIGFuZCBsaW5lIGNvdW50LlxuICAgKlxuICAgKiBAcmV0dXJucyBjbGFtcGVkIHRleHQgb3IgdW5kZWZpbmVkIGlmIG1lYXN1cmVtZW50IHdlbnQgd3JvbmdcbiAgICovXG4gIGdldENsYW1wZWRUZXh0ICgpOiBzdHJpbmcgfCB1bmRlZmluZWQge1xuICAgIGlmICh0aGlzLl9uYXRpdmVFbGVtZW50ICYmIHRoaXMudGV4dCgpKSB7XG4gICAgICBpZiAoIXRoaXMuX2lzVGV4dEluaXRpYWxpemVkKVxuICAgICAgICB0aGlzLl9pc1RleHRJbml0aWFsaXplZCA9IHRydWU7XG5cblxuICAgICAgbGV0IGNsYW1wVGV4dDogc3RyaW5nID0gXCJcIjtcblxuICAgICAgY29uc3QgZWxlbWVudFN0eWxlOiBDU1NTdHlsZURlY2xhcmF0aW9uIHwgdW5kZWZpbmVkID0gZ2V0Q29tcHV0ZWRTdHlsZSh0aGlzLl9uYXRpdmVFbGVtZW50KTtcbiAgICAgIGNvbnN0IGxpbmVIZWlnaHQ6IG51bWJlciA9IE51bWJlci5wYXJzZUZsb2F0KGVsZW1lbnRTdHlsZS5saW5lSGVpZ2h0KTtcbiAgICAgIGNvbnN0IGxpbmVDb3VudDogbnVtYmVyID0gcm91bmRXaXRoVGhyZXNob2xkKHRoaXMuX25hdGl2ZUVsZW1lbnQuY2xpZW50SGVpZ2h0IC8gbGluZUhlaWdodCwgMC45KTtcbiAgICAgIGNvbnN0IGVsbGlwc2lzV2lkdGg6IG51bWJlciB8IHVuZGVmaW5lZCA9IHRoaXMuX2NhbnZhc01lYXN1cmVTZXJ2aWNlLmdldFJlbmRlcmVkU3RyaW5nV2lkdGgoZWxlbWVudFN0eWxlLCBgICR7IHRoaXMuZWxsaXBzaXMoKSB9IGApO1xuXG4gICAgICBpZiAoIWVsbGlwc2lzV2lkdGgpIHJldHVybiB1bmRlZmluZWQ7XG5cbiAgICAgIGxldCB1c2VkV29yZHNDb3VudDogbnVtYmVyID0gMDtcbiAgICAgIGxldCBsaW5lUG9pbnRlcjogbnVtYmVyID0gMTtcbiAgICAgIGxldCByZW1haW5pbmdMaW5lV2lkdGg6IG51bWJlciA9IHRoaXMuX25hdGl2ZUVsZW1lbnQuY2xpZW50V2lkdGg7XG5cbiAgICAgIGlmIChsaW5lQ291bnQgPiAwKSB7XG4gICAgICAgIGZvciAobGV0IGk6IG51bWJlciA9IDA7IGkgPCB0aGlzLl93b3JkcygpLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIHByZWZlci10ZW1wbGF0ZVxuICAgICAgICAgIGNvbnN0IHdvcmQ6IHN0cmluZyA9IGkgPT09IDAgPyB0aGlzLl93b3JkcygpWyBpIF0hIDogXCIgXCIgKyB0aGlzLl93b3JkcygpWyBpIF0hO1xuICAgICAgICAgIGNvbnN0IHdvcmRXaWR0aDogbnVtYmVyIHwgdW5kZWZpbmVkID0gdGhpcy5fY2FudmFzTWVhc3VyZVNlcnZpY2UuZ2V0UmVuZGVyZWRTdHJpbmdXaWR0aChlbGVtZW50U3R5bGUsIHdvcmQpO1xuXG4gICAgICAgICAgaWYgKCF3b3JkV2lkdGgpIHJldHVybiB1bmRlZmluZWQ7XG5cbiAgICAgICAgICAvLyBDaGVjayBpZiB3b3JkIGZpdHMgaW4gbGluZVxuICAgICAgICAgIGlmIChmaXRzKHdvcmRXaWR0aCwgcmVtYWluaW5nTGluZVdpZHRoLCBsaW5lUG9pbnRlciwgbGluZUNvdW50LCBlbGxpcHNpc1dpZHRoKSkge1xuICAgICAgICAgICAgY2xhbXBUZXh0ICs9IHdvcmQ7XG4gICAgICAgICAgICByZW1haW5pbmdMaW5lV2lkdGggLT0gd29yZFdpZHRoO1xuICAgICAgICAgICAgdXNlZFdvcmRzQ291bnQrKztcbiAgICAgICAgICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAc3R5bGlzdGljL3RzL2JyYWNlLXN0eWxlXG4gICAgICAgICAgfVxuICAgICAgICAgIC8vIENoZWNrIGlmIHRoZXJlIGlzIGEgbmV4dCBsaW5lXG4gICAgICAgICAgZWxzZSBpZiAobGluZVBvaW50ZXIgPCBsaW5lQ291bnQpIHtcbiAgICAgICAgICAgIGxpbmVQb2ludGVyKys7XG4gICAgICAgICAgICByZW1haW5pbmdMaW5lV2lkdGggPSB0aGlzLl9uYXRpdmVFbGVtZW50LmNsaWVudFdpZHRoO1xuXG4gICAgICAgICAgICAvLyBDaGVjayBpZiB3b3JkIGZpdHMgaW4gbGluZVxuICAgICAgICAgICAgaWYgKGZpdHMod29yZFdpZHRoLCByZW1haW5pbmdMaW5lV2lkdGgsIGxpbmVQb2ludGVyLCBsaW5lQ291bnQsIGVsbGlwc2lzV2lkdGgpKSB7XG4gICAgICAgICAgICAgIGNsYW1wVGV4dCArPSB3b3JkO1xuICAgICAgICAgICAgICByZW1haW5pbmdMaW5lV2lkdGggLT0gd29yZFdpZHRoO1xuICAgICAgICAgICAgICB1c2VkV29yZHNDb3VudCsrO1xuICAgICAgICAgICAgfSBlbHNlXG4gICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIH0gZWxzZVxuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cblxuXG4gICAgICAgIGlmICh1c2VkV29yZHNDb3VudCA+IDAgJiYgdXNlZFdvcmRzQ291bnQgPCB0aGlzLl93b3JkcygpLmxlbmd0aClcbiAgICAgICAgICBjbGFtcFRleHQgKz0gdGhpcy5lbGxpcHNpcygpO1xuICAgICAgfVxuXG4gICAgICByZXR1cm4gY2xhbXBUZXh0O1xuICAgIH1cblxuICAgIHJldHVybiB1bmRlZmluZWQ7XG4gIH1cbn1cblxuIl19