@ogs-gmbh/ngx-utils
Version:
A lightweight collection of utility functions and helpers for Angular applications
202 lines • 26.5 kB
JavaScript
/* 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