listine
Version:
Angular virtual scroll component supporting variable item heights.
207 lines • 28.1 kB
JavaScript
import { Component, Input, ViewChild, ElementRef, ChangeDetectionStrategy, ViewChildren, EventEmitter, Output, } from "@angular/core";
import { CommonModule } from "@angular/common";
import * as i0 from "@angular/core";
import * as i1 from "@angular/common";
/**
* `s73-variable-virtual-scroll`
*
* A lightweight, customizable virtual scroll component that supports variable item heights.
* Efficiently renders only visible items to improve performance for large lists.
*
* ## Inputs:
* - `items`: List of data items to display
* - `viewportHeight`: Height of the scrollable container (default: 400px)
* - `buffer`: Number of extra items rendered above and below the viewport for smooth scrolling (default: 5)
* - `itemTemplate`: Angular template for rendering each item
* - `initialItemHeight`: Default item height before measurement (default: 50px)
*
* ## Outputs:
* - `scrollEmitter`: Emits scroll position on every scroll event
*/
export class ListineVirtualScrollModule {
cdr;
ngZone;
/** List of all items to render */
items = [];
/** Height of the scrollable viewport in pixels */
viewportHeight = 400;
/** Number of extra items to render above and below the viewport */
buffer = 5;
/** Template reference for rendering each item */
itemTemplate;
/** Flag to track if panel is open */
panelOpen = false;
/** Initial estimated height of each item before actual measurement */
initialItemHeight = 50;
/** Emits scroll position whenever user scrolls */
scrollEmitter = new EventEmitter();
/** Emits when user scrolls to the end of the list */
scrollToEnd = new EventEmitter();
/** Reference to the scrolling container */
scrollerRef;
/** QueryList of rendered item elements (used for height measurement) */
itemElements;
/** Stores measured heights of items */
itemHeights = [];
/** Stores calculated top offset for each item */
itemTops = [];
/** Total height of all items (used to simulate full scrollable area) */
totalContentHeight = 0;
/** Index of the first visible item (including buffer) */
visibleStart = 0;
/** Index of the last visible item (including buffer) */
visibleEnd = 0;
/** Items currently visible in the viewport */
visibleItems = [];
/** ResizeObserver to watch for item height changes */
resizeObserver = new ResizeObserver(() => {
this.ngZone.run(() => {
this.measureItemHeights();
});
});
/** Previous scroll position to detect actual scrolling */
previousScrollTop = 0;
constructor(cdr, ngZone) {
this.cdr = cdr;
this.ngZone = ngZone;
}
/** Initialize item heights on component initialization */
ngOnInit() {
this.initializeHeights();
}
/**
* Respond to changes in input bindings (mainly the `items` array)
* @param changes - input property changes
*/
ngOnChanges(changes) {
if (changes["items"]) {
this.items = changes["items"].currentValue;
this.initializeHeights();
setTimeout(() => {
this.calculateHeights();
this.onScroll();
});
}
if (changes["panelOpen"] && changes["panelOpen"]?.currentValue) {
this.panelOpen = changes["panelOpen"]?.currentValue;
this.onScroll();
}
}
/**
* Called after view initialization. Triggers initial height calculation.
*/
ngAfterViewInit() {
setTimeout(() => {
this.calculateHeights();
this.onScroll();
});
}
/**
* Disconnects ResizeObserver on component destroy
*/
ngOnDestroy() {
this.resizeObserver.disconnect();
}
/** Initializes item height and position tracking arrays */
initializeHeights() {
this.itemHeights = this.items.map(() => this.initialItemHeight);
this.updateItemTops();
}
/** Recalculates item top positions and content height */
calculateHeights() {
this.updateItemTops();
}
/** Updates top positions of all items based on their current heights */
updateItemTops() {
this.itemTops = [];
let top = 0;
for (const height of this.itemHeights) {
this.itemTops.push(top);
top += height;
}
this.totalContentHeight = top;
}
/**
* Returns the top position of an item by index
* @param index - index of the item
*/
getItemTop(index) {
return this.itemTops[index] || 0;
}
/** Scroll event handler: calculates which items should be visible */
onScroll() {
const scrollTop = this.scrollerRef.nativeElement.scrollTop;
const viewportBottom = scrollTop + this.viewportHeight;
let start = 0;
while (start < this.items.length &&
this.getItemTop(start + 1) < scrollTop) {
start++;
}
let end = start;
while (end < this.items.length && this.getItemTop(end) < viewportBottom) {
end++;
}
this.visibleStart = Math.max(0, start - this.buffer);
this.visibleEnd = Math.min(this.items.length, end + this.buffer);
this.updateVisibleItems();
this.scrollEmitter.emit(scrollTop);
// Check if scrolled to the end
const scrollHeight = this.scrollerRef.nativeElement.scrollHeight;
const clientHeight = this.scrollerRef.nativeElement.clientHeight;
const isAtEnd = scrollTop + clientHeight >= scrollHeight - 1;
if (isAtEnd && scrollTop > this.previousScrollTop && scrollTop > 0) {
this.scrollToEnd.emit();
}
this.previousScrollTop = scrollTop;
}
/** Updates the list of items to be rendered based on scroll position */
updateVisibleItems() {
this.visibleItems = this.items.slice(this.visibleStart, this.visibleEnd);
// Wait for DOM render before measuring heights
setTimeout(() => {
this.measureItemHeights();
});
}
/** Measures actual heights of rendered DOM elements and updates tracking */
measureItemHeights() {
this.itemElements.forEach((el, idx) => {
const index = this.visibleStart + idx;
const height = el.nativeElement.offsetHeight;
if (this.itemHeights[index] !== height) {
this.itemHeights[index] = height;
this.resizeObserver.observe(el.nativeElement);
}
});
this.updateItemTops();
this.cdr.markForCheck();
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ListineVirtualScrollModule, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: ListineVirtualScrollModule, isStandalone: true, selector: "listine-variable-virtual-scroll", inputs: { items: "items", viewportHeight: "viewportHeight", buffer: "buffer", itemTemplate: "itemTemplate", panelOpen: "panelOpen", initialItemHeight: "initialItemHeight" }, outputs: { scrollEmitter: "scrollEmitter", scrollToEnd: "scrollToEnd" }, viewQueries: [{ propertyName: "scrollerRef", first: true, predicate: ["scroller"], descendants: true, static: true }, { propertyName: "itemElements", predicate: ["itemElement"], descendants: true, read: ElementRef }], usesOnChanges: true, ngImport: i0, template: "<div\n #scroller\n class=\"scroll-container\"\n (scroll)=\"onScroll()\"\n [style.height.px]=\"viewportHeight\"\n>\n <div\n class=\"total-height\"\n [style.height.px]=\"totalContentHeight - viewportHeight\"\n >\n <ng-container *ngFor=\"let item of visibleItems; let i = index\">\n <div\n #itemElement\n class=\"item\"\n [style.transform]=\"'translateY(' + getItemTop(visibleStart + i) + 'px)'\"\n >\n <ng-container\n *ngTemplateOutlet=\"itemTemplate; context: { $implicit: item }\"\n ></ng-container>\n </div>\n </ng-container>\n </div>\n</div>\n", styles: [".scroll-container{overflow-y:auto;position:relative;width:100%;background:#fff}.total-height{position:relative;width:100%}.item{position:absolute;width:100%;box-sizing:border-box;will-change:transform}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ListineVirtualScrollModule, decorators: [{
type: Component,
args: [{ selector: "listine-variable-virtual-scroll", standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n #scroller\n class=\"scroll-container\"\n (scroll)=\"onScroll()\"\n [style.height.px]=\"viewportHeight\"\n>\n <div\n class=\"total-height\"\n [style.height.px]=\"totalContentHeight - viewportHeight\"\n >\n <ng-container *ngFor=\"let item of visibleItems; let i = index\">\n <div\n #itemElement\n class=\"item\"\n [style.transform]=\"'translateY(' + getItemTop(visibleStart + i) + 'px)'\"\n >\n <ng-container\n *ngTemplateOutlet=\"itemTemplate; context: { $implicit: item }\"\n ></ng-container>\n </div>\n </ng-container>\n </div>\n</div>\n", styles: [".scroll-container{overflow-y:auto;position:relative;width:100%;background:#fff}.total-height{position:relative;width:100%}.item{position:absolute;width:100%;box-sizing:border-box;will-change:transform}\n"] }]
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i0.NgZone }], propDecorators: { items: [{
type: Input
}], viewportHeight: [{
type: Input
}], buffer: [{
type: Input
}], itemTemplate: [{
type: Input
}], panelOpen: [{
type: Input
}], initialItemHeight: [{
type: Input
}], scrollEmitter: [{
type: Output
}], scrollToEnd: [{
type: Output
}], scrollerRef: [{
type: ViewChild,
args: ["scroller", { static: true }]
}], itemElements: [{
type: ViewChildren,
args: ["itemElement", { read: ElementRef }]
}] } });
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibGlzdGluZS12aXJ0dWFsLXNjcm9sbC5jb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9wcm9qZWN0cy9saXN0aW5lL3NyYy9saWIvbGlzdHRpbmUtdmlydHVhbC92YXJpYWJsZS12aXJ0dWFsLXNjcm9sbC9saXN0aW5lLXZpcnR1YWwtc2Nyb2xsLmNvbXBvbmVudC50cyIsIi4uLy4uLy4uLy4uLy4uLy4uL3Byb2plY3RzL2xpc3RpbmUvc3JjL2xpYi9saXN0dGluZS12aXJ0dWFsL3ZhcmlhYmxlLXZpcnR1YWwtc2Nyb2xsL2xpc3RpbmUtdmlydHVhbC1zY3JvbGwuY29tcG9uZW50Lmh0bWwiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUNMLFNBQVMsRUFDVCxLQUFLLEVBRUwsU0FBUyxFQUNULFVBQVUsRUFFVix1QkFBdUIsRUFFdkIsWUFBWSxFQUVaLFlBQVksRUFDWixNQUFNLEdBTVAsTUFBTSxlQUFlLENBQUM7QUFDdkIsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLGlCQUFpQixDQUFDOzs7QUFFL0M7Ozs7Ozs7Ozs7Ozs7OztHQWVHO0FBU0gsTUFBTSxPQUFPLDBCQUEwQjtJQThEakI7SUFBZ0M7SUEzRHBELGtDQUFrQztJQUN6QixLQUFLLEdBQVUsRUFBRSxDQUFDO0lBRTNCLGtEQUFrRDtJQUN6QyxjQUFjLEdBQUcsR0FBRyxDQUFDO0lBRTlCLG1FQUFtRTtJQUMxRCxNQUFNLEdBQUcsQ0FBQyxDQUFDO0lBRXBCLGlEQUFpRDtJQUN4QyxZQUFZLENBQW1CO0lBRXhDLHFDQUFxQztJQUM1QixTQUFTLEdBQVksS0FBSyxDQUFDO0lBRXBDLHNFQUFzRTtJQUM3RCxpQkFBaUIsR0FBRyxFQUFFLENBQUM7SUFFaEMsa0RBQWtEO0lBQ3hDLGFBQWEsR0FBRyxJQUFJLFlBQVksRUFBTyxDQUFDO0lBRWxELHFEQUFxRDtJQUMzQyxXQUFXLEdBQUcsSUFBSSxZQUFZLEVBQVEsQ0FBQztJQUVqRCwyQ0FBMkM7SUFDRixXQUFXLENBQWM7SUFFbEUsd0VBQXdFO0lBRXhFLFlBQVksQ0FBeUI7SUFFckMsdUNBQXVDO0lBQ3ZDLFdBQVcsR0FBYSxFQUFFLENBQUM7SUFFM0IsaURBQWlEO0lBQ2pELFFBQVEsR0FBYSxFQUFFLENBQUM7SUFFeEIsd0VBQXdFO0lBQ3hFLGtCQUFrQixHQUFHLENBQUMsQ0FBQztJQUV2Qix5REFBeUQ7SUFDekQsWUFBWSxHQUFHLENBQUMsQ0FBQztJQUVqQix3REFBd0Q7SUFDeEQsVUFBVSxHQUFHLENBQUMsQ0FBQztJQUVmLDhDQUE4QztJQUM5QyxZQUFZLEdBQVUsRUFBRSxDQUFDO0lBRXpCLHNEQUFzRDtJQUM5QyxjQUFjLEdBQUcsSUFBSSxjQUFjLENBQUMsR0FBRyxFQUFFO1FBQy9DLElBQUksQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLEdBQUcsRUFBRTtZQUNuQixJQUFJLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztRQUM1QixDQUFDLENBQUMsQ0FBQztJQUNMLENBQUMsQ0FBQyxDQUFDO0lBRUgsMERBQTBEO0lBQ2xELGlCQUFpQixHQUFHLENBQUMsQ0FBQztJQUU5QixZQUFvQixHQUFzQixFQUFVLE1BQWM7UUFBOUMsUUFBRyxHQUFILEdBQUcsQ0FBbUI7UUFBVSxXQUFNLEdBQU4sTUFBTSxDQUFRO0lBQUcsQ0FBQztJQUV0RSwwREFBMEQ7SUFDMUQsUUFBUTtRQUNOLElBQUksQ0FBQyxpQkFBaUIsRUFBRSxDQUFDO0lBQzNCLENBQUM7SUFFRDs7O09BR0c7SUFDSCxXQUFXLENBQUMsT0FBc0I7UUFDaEMsSUFBSSxPQUFPLENBQUMsT0FBTyxDQUFDLEVBQUU7WUFDcEIsSUFBSSxDQUFDLEtBQUssR0FBRyxPQUFPLENBQUMsT0FBTyxDQUFDLENBQUMsWUFBWSxDQUFDO1lBQzNDLElBQUksQ0FBQyxpQkFBaUIsRUFBRSxDQUFDO1lBRXpCLFVBQVUsQ0FBQyxHQUFHLEVBQUU7Z0JBQ2QsSUFBSSxDQUFDLGdCQUFnQixFQUFFLENBQUM7Z0JBQ3hCLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQztZQUNsQixDQUFDLENBQUMsQ0FBQztTQUNKO1FBQ0QsSUFBSSxPQUFPLENBQUMsV0FBVyxDQUFDLElBQUksT0FBTyxDQUFDLFdBQVcsQ0FBQyxFQUFFLFlBQVksRUFBRTtZQUM5RCxJQUFJLENBQUMsU0FBUyxHQUFHLE9BQU8sQ0FBQyxXQUFXLENBQUMsRUFBRSxZQUFZLENBQUM7WUFDcEQsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO1NBQ2pCO0lBQ0gsQ0FBQztJQUVEOztPQUVHO0lBQ0gsZUFBZTtRQUNiLFVBQVUsQ0FBQyxHQUFHLEVBQUU7WUFDZCxJQUFJLENBQUMsZ0JBQWdCLEVBQUUsQ0FBQztZQUN4QixJQUFJLENBQUMsUUFBUSxFQUFFLENBQUM7UUFDbEIsQ0FBQyxDQUFDLENBQUM7SUFDTCxDQUFDO0lBRUQ7O09BRUc7SUFDSCxXQUFXO1FBQ1QsSUFBSSxDQUFDLGNBQWMsQ0FBQyxVQUFVLEVBQUUsQ0FBQztJQUNuQyxDQUFDO0lBRUQsMkRBQTJEO0lBQzNELGlCQUFpQjtRQUNmLElBQUksQ0FBQyxXQUFXLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsR0FBRyxFQUFFLENBQUMsSUFBSSxDQUFDLGlCQUFpQixDQUFDLENBQUM7UUFDaEUsSUFBSSxDQUFDLGNBQWMsRUFBRSxDQUFDO0lBQ3hCLENBQUM7SUFFRCx5REFBeUQ7SUFDekQsZ0JBQWdCO1FBQ2QsSUFBSSxDQUFDLGNBQWMsRUFBRSxDQUFDO0lBQ3hCLENBQUM7SUFFRCx3RUFBd0U7SUFDeEUsY0FBYztRQUNaLElBQUksQ0FBQyxRQUFRLEdBQUcsRUFBRSxDQUFDO1FBQ25CLElBQUksR0FBRyxHQUFHLENBQUMsQ0FBQztRQUNaLEtBQUssTUFBTSxNQUFNLElBQUksSUFBSSxDQUFDLFdBQVcsRUFBRTtZQUNyQyxJQUFJLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQztZQUN4QixHQUFHLElBQUksTUFBTSxDQUFDO1NBQ2Y7UUFDRCxJQUFJLENBQUMsa0JBQWtCLEdBQUcsR0FBRyxDQUFDO0lBQ2hDLENBQUM7SUFFRDs7O09BR0c7SUFDSCxVQUFVLENBQUMsS0FBYTtRQUN0QixPQUFPLElBQUksQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ25DLENBQUM7SUFFRCxxRUFBcUU7SUFDckUsUUFBUTtRQUNOLE1BQU0sU0FBUyxHQUFHLElBQUksQ0FBQyxXQUFXLENBQUMsYUFBYSxDQUFDLFNBQVMsQ0FBQztRQUMzRCxNQUFNLGNBQWMsR0FBRyxTQUFTLEdBQUcsSUFBSSxDQUFDLGNBQWMsQ0FBQztRQUV2RCxJQUFJLEtBQUssR0FBRyxDQUFDLENBQUM7UUFDZCxPQUNFLEtBQUssR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLE1BQU07WUFDekIsSUFBSSxDQUFDLFVBQVUsQ0FBQyxLQUFLLEdBQUcsQ0FBQyxDQUFDLEdBQUcsU0FBUyxFQUN0QztZQUNBLEtBQUssRUFBRSxDQUFDO1NBQ1Q7UUFFRCxJQUFJLEdBQUcsR0FBRyxLQUFLLENBQUM7UUFDaEIsT0FBTyxHQUFHLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxNQUFNLElBQUksSUFBSSxDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUMsR0FBRyxjQUFjLEVBQUU7WUFDdkUsR0FBRyxFQUFFLENBQUM7U0FDUDtRQUNELElBQUksQ0FBQyxZQUFZLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEVBQUUsS0FBSyxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNyRCxJQUFJLENBQUMsVUFBVSxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxNQUFNLEVBQUUsR0FBRyxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNqRSxJQUFJLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztRQUMxQixJQUFJLENBQUMsYUFBYSxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUVuQywrQkFBK0I7UUFDL0IsTUFBTSxZQUFZLEdBQUcsSUFBSSxDQUFDLFdBQVcsQ0FBQyxhQUFhLENBQUMsWUFBWSxDQUFDO1FBQ2pFLE1BQU0sWUFBWSxHQUFHLElBQUksQ0FBQyxXQUFXLENBQUMsYUFBYSxDQUFDLFlBQVksQ0FBQztRQUNqRSxNQUFNLE9BQU8sR0FBRyxTQUFTLEdBQUcsWUFBWSxJQUFJLFlBQVksR0FBRyxDQUFDLENBQUM7UUFFN0QsSUFBSSxPQUFPLElBQUksU0FBUyxHQUFHLElBQUksQ0FBQyxpQkFBaUIsSUFBSSxTQUFTLEdBQUcsQ0FBQyxFQUFFO1lBQ2xFLElBQUksQ0FBQyxXQUFXLENBQUMsSUFBSSxFQUFFLENBQUM7U0FDekI7UUFFRCxJQUFJLENBQUMsaUJBQWlCLEdBQUcsU0FBUyxDQUFDO0lBQ3JDLENBQUM7SUFFRCx3RUFBd0U7SUFDeEUsa0JBQWtCO1FBQ2hCLElBQUksQ0FBQyxZQUFZLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLFlBQVksRUFBRSxJQUFJLENBQUMsVUFBVSxDQUFDLENBQUM7UUFFekUsK0NBQStDO1FBQy9DLFVBQVUsQ0FBQyxHQUFHLEVBQUU7WUFDZCxJQUFJLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztRQUM1QixDQUFDLENBQUMsQ0FBQztJQUNMLENBQUM7SUFFRCw0RUFBNEU7SUFDNUUsa0JBQWtCO1FBQ2hCLElBQUksQ0FBQyxZQUFZLENBQUMsT0FBTyxDQUFDLENBQUMsRUFBRSxFQUFFLEdBQUcsRUFBRSxFQUFFO1lBQ3BDLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxZQUFZLEdBQUcsR0FBRyxDQUFDO1lBQ3RDLE1BQU0sTUFBTSxHQUFHLEVBQUUsQ0FBQyxhQUFhLENBQUMsWUFBWSxDQUFDO1lBQzdDLElBQUksSUFBSSxDQUFDLFdBQVcsQ0FBQyxLQUFLLENBQUMsS0FBSyxNQUFNLEVBQUU7Z0JBQ3RDLElBQUksQ0FBQyxXQUFXLENBQUMsS0FBSyxDQUFDLEdBQUcsTUFBTSxDQUFDO2dCQUNqQyxJQUFJLENBQUMsY0FBYyxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUMsYUFBYSxDQUFDLENBQUM7YUFDL0M7UUFDSCxDQUFDLENBQUMsQ0FBQztRQUNILElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQztRQUN0QixJQUFJLENBQUMsR0FBRyxDQUFDLFlBQVksRUFBRSxDQUFDO0lBQzFCLENBQUM7d0dBaE1VLDBCQUEwQjs0RkFBMUIsMEJBQTBCLHFnQkErQkEsVUFBVSxrREM1RWpELGluQkF1QkEsb1FEaUJZLFlBQVk7OzRGQUtYLDBCQUEwQjtrQkFSdEMsU0FBUzsrQkFDRSxpQ0FBaUMsY0FDL0IsSUFBSSxXQUNQLENBQUMsWUFBWSxDQUFDLG1CQUdOLHVCQUF1QixDQUFDLE1BQU07MkdBTXRDLEtBQUs7c0JBQWIsS0FBSztnQkFHRyxjQUFjO3NCQUF0QixLQUFLO2dCQUdHLE1BQU07c0JBQWQsS0FBSztnQkFHRyxZQUFZO3NCQUFwQixLQUFLO2dCQUdHLFNBQVM7c0JBQWpCLEtBQUs7Z0JBR0csaUJBQWlCO3NCQUF6QixLQUFLO2dCQUdJLGFBQWE7c0JBQXRCLE1BQU07Z0JBR0csV0FBVztzQkFBcEIsTUFBTTtnQkFHa0MsV0FBVztzQkFBbkQsU0FBUzt1QkFBQyxVQUFVLEVBQUUsRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFO2dCQUl2QyxZQUFZO3NCQURYLFlBQVk7dUJBQUMsYUFBYSxFQUFFLEVBQUUsSUFBSSxFQUFFLFVBQVUsRUFBRSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7XG4gIENvbXBvbmVudCxcbiAgSW5wdXQsXG4gIFRlbXBsYXRlUmVmLFxuICBWaWV3Q2hpbGQsXG4gIEVsZW1lbnRSZWYsXG4gIEFmdGVyVmlld0luaXQsXG4gIENoYW5nZURldGVjdGlvblN0cmF0ZWd5LFxuICBDaGFuZ2VEZXRlY3RvclJlZixcbiAgVmlld0NoaWxkcmVuLFxuICBRdWVyeUxpc3QsXG4gIEV2ZW50RW1pdHRlcixcbiAgT3V0cHV0LFxuICBPbkNoYW5nZXMsXG4gIFNpbXBsZUNoYW5nZXMsXG4gIE9uSW5pdCxcbiAgT25EZXN0cm95LFxuICBOZ1pvbmUsXG59IGZyb20gXCJAYW5ndWxhci9jb3JlXCI7XG5pbXBvcnQgeyBDb21tb25Nb2R1bGUgfSBmcm9tIFwiQGFuZ3VsYXIvY29tbW9uXCI7XG5cbi8qKlxuICogYHM3My12YXJpYWJsZS12aXJ0dWFsLXNjcm9sbGBcbiAqXG4gKiBBIGxpZ2h0d2VpZ2h0LCBjdXN0b21pemFibGUgdmlydHVhbCBzY3JvbGwgY29tcG9uZW50IHRoYXQgc3VwcG9ydHMgdmFyaWFibGUgaXRlbSBoZWlnaHRzLlxuICogRWZmaWNpZW50bHkgcmVuZGVycyBvbmx5IHZpc2libGUgaXRlbXMgdG8gaW1wcm92ZSBwZXJmb3JtYW5jZSBmb3IgbGFyZ2UgbGlzdHMuXG4gKlxuICogIyMgSW5wdXRzOlxuICogLSBgaXRlbXNgOiBMaXN0IG9mIGRhdGEgaXRlbXMgdG8gZGlzcGxheVxuICogLSBgdmlld3BvcnRIZWlnaHRgOiBIZWlnaHQgb2YgdGhlIHNjcm9sbGFibGUgY29udGFpbmVyIChkZWZhdWx0OiA0MDBweClcbiAqIC0gYGJ1ZmZlcmA6IE51bWJlciBvZiBleHRyYSBpdGVtcyByZW5kZXJlZCBhYm92ZSBhbmQgYmVsb3cgdGhlIHZpZXdwb3J0IGZvciBzbW9vdGggc2Nyb2xsaW5nIChkZWZhdWx0OiA1KVxuICogLSBgaXRlbVRlbXBsYXRlYDogQW5ndWxhciB0ZW1wbGF0ZSBmb3IgcmVuZGVyaW5nIGVhY2ggaXRlbVxuICogLSBgaW5pdGlhbEl0ZW1IZWlnaHRgOiBEZWZhdWx0IGl0ZW0gaGVpZ2h0IGJlZm9yZSBtZWFzdXJlbWVudCAoZGVmYXVsdDogNTBweClcbiAqXG4gKiAjIyBPdXRwdXRzOlxuICogLSBgc2Nyb2xsRW1pdHRlcmA6IEVtaXRzIHNjcm9sbCBwb3NpdGlvbiBvbiBldmVyeSBzY3JvbGwgZXZlbnRcbiAqL1xuQENvbXBvbmVudCh7XG4gIHNlbGVjdG9yOiBcImxpc3RpbmUtdmFyaWFibGUtdmlydHVhbC1zY3JvbGxcIixcbiAgc3RhbmRhbG9uZTogdHJ1ZSxcbiAgaW1wb3J0czogW0NvbW1vbk1vZHVsZV0sXG4gIHRlbXBsYXRlVXJsOiBcIi4vbGlzdGluZS12aXJ0dWFsLXNjcm9sbC5jb21wb25lbnQuaHRtbFwiLFxuICBzdHlsZVVybHM6IFtcIi4vbGlzdGluZS12aXJ0dWFsLXNjcm9sbC5jb21wb25lbnQuc2Nzc1wiXSxcbiAgY2hhbmdlRGV0ZWN0aW9uOiBDaGFuZ2VEZXRlY3Rpb25TdHJhdGVneS5PblB1c2gsXG59KVxuZXhwb3J0IGNsYXNzIExpc3RpbmVWaXJ0dWFsU2Nyb2xsTW9kdWxlXG4gIGltcGxlbWVudHMgT25Jbml0LCBBZnRlclZpZXdJbml0LCBPbkNoYW5nZXMsIE9uRGVzdHJveVxue1xuICAvKiogTGlzdCBvZiBhbGwgaXRlbXMgdG8gcmVuZGVyICovXG4gIEBJbnB1dCgpIGl0ZW1zOiBhbnlbXSA9IFtdO1xuXG4gIC8qKiBIZWlnaHQgb2YgdGhlIHNjcm9sbGFibGUgdmlld3BvcnQgaW4gcGl4ZWxzICovXG4gIEBJbnB1dCgpIHZpZXdwb3J0SGVpZ2h0ID0gNDAwO1xuXG4gIC8qKiBOdW1iZXIgb2YgZXh0cmEgaXRlbXMgdG8gcmVuZGVyIGFib3ZlIGFuZCBiZWxvdyB0aGUgdmlld3BvcnQgKi9cbiAgQElucHV0KCkgYnVmZmVyID0gNTtcblxuICAvKiogVGVtcGxhdGUgcmVmZXJlbmNlIGZvciByZW5kZXJpbmcgZWFjaCBpdGVtICovXG4gIEBJbnB1dCgpIGl0ZW1UZW1wbGF0ZTogVGVtcGxhdGVSZWY8YW55PjtcblxuICAvKiogRmxhZyB0byB0cmFjayBpZiBwYW5lbCBpcyBvcGVuICovXG4gIEBJbnB1dCgpIHBhbmVsT3BlbjogYm9vbGVhbiA9IGZhbHNlO1xuXG4gIC8qKiBJbml0aWFsIGVzdGltYXRlZCBoZWlnaHQgb2YgZWFjaCBpdGVtIGJlZm9yZSBhY3R1YWwgbWVhc3VyZW1lbnQgKi9cbiAgQElucHV0KCkgaW5pdGlhbEl0ZW1IZWlnaHQgPSA1MDtcblxuICAvKiogRW1pdHMgc2Nyb2xsIHBvc2l0aW9uIHdoZW5ldmVyIHVzZXIgc2Nyb2xscyAqL1xuICBAT3V0cHV0KCkgc2Nyb2xsRW1pdHRlciA9IG5ldyBFdmVudEVtaXR0ZXI8YW55PigpO1xuXG4gIC8qKiBFbWl0cyB3aGVuIHVzZXIgc2Nyb2xscyB0byB0aGUgZW5kIG9mIHRoZSBsaXN0ICovXG4gIEBPdXRwdXQoKSBzY3JvbGxUb0VuZCA9IG5ldyBFdmVudEVtaXR0ZXI8dm9pZD4oKTtcblxuICAvKiogUmVmZXJlbmNlIHRvIHRoZSBzY3JvbGxpbmcgY29udGFpbmVyICovXG4gIEBWaWV3Q2hpbGQoXCJzY3JvbGxlclwiLCB7IHN0YXRpYzogdHJ1ZSB9KSBzY3JvbGxlclJlZiE6IEVsZW1lbnRSZWY7XG5cbiAgLyoqIFF1ZXJ5TGlzdCBvZiByZW5kZXJlZCBpdGVtIGVsZW1lbnRzICh1c2VkIGZvciBoZWlnaHQgbWVhc3VyZW1lbnQpICovXG4gIEBWaWV3Q2hpbGRyZW4oXCJpdGVtRWxlbWVudFwiLCB7IHJlYWQ6IEVsZW1lbnRSZWYgfSlcbiAgaXRlbUVsZW1lbnRzITogUXVlcnlMaXN0PEVsZW1lbnRSZWY+O1xuXG4gIC8qKiBTdG9yZXMgbWVhc3VyZWQgaGVpZ2h0cyBvZiBpdGVtcyAqL1xuICBpdGVtSGVpZ2h0czogbnVtYmVyW10gPSBbXTtcblxuICAvKiogU3RvcmVzIGNhbGN1bGF0ZWQgdG9wIG9mZnNldCBmb3IgZWFjaCBpdGVtICovXG4gIGl0ZW1Ub3BzOiBudW1iZXJbXSA9IFtdO1xuXG4gIC8qKiBUb3RhbCBoZWlnaHQgb2YgYWxsIGl0ZW1zICh1c2VkIHRvIHNpbXVsYXRlIGZ1bGwgc2Nyb2xsYWJsZSBhcmVhKSAqL1xuICB0b3RhbENvbnRlbnRIZWlnaHQgPSAwO1xuXG4gIC8qKiBJbmRleCBvZiB0aGUgZmlyc3QgdmlzaWJsZSBpdGVtIChpbmNsdWRpbmcgYnVmZmVyKSAqL1xuICB2aXNpYmxlU3RhcnQgPSAwO1xuXG4gIC8qKiBJbmRleCBvZiB0aGUgbGFzdCB2aXNpYmxlIGl0ZW0gKGluY2x1ZGluZyBidWZmZXIpICovXG4gIHZpc2libGVFbmQgPSAwO1xuXG4gIC8qKiBJdGVtcyBjdXJyZW50bHkgdmlzaWJsZSBpbiB0aGUgdmlld3BvcnQgKi9cbiAgdmlzaWJsZUl0ZW1zOiBhbnlbXSA9IFtdO1xuXG4gIC8qKiBSZXNpemVPYnNlcnZlciB0byB3YXRjaCBmb3IgaXRlbSBoZWlnaHQgY2hhbmdlcyAqL1xuICBwcml2YXRlIHJlc2l6ZU9ic2VydmVyID0gbmV3IFJlc2l6ZU9ic2VydmVyKCgpID0+IHtcbiAgICB0aGlzLm5nWm9uZS5ydW4oKCkgPT4ge1xuICAgICAgdGhpcy5tZWFzdXJlSXRlbUhlaWdodHMoKTtcbiAgICB9KTtcbiAgfSk7XG5cbiAgLyoqIFByZXZpb3VzIHNjcm9sbCBwb3NpdGlvbiB0byBkZXRlY3QgYWN0dWFsIHNjcm9sbGluZyAqL1xuICBwcml2YXRlIHByZXZpb3VzU2Nyb2xsVG9wID0gMDtcblxuICBjb25zdHJ1Y3Rvcihwcml2YXRlIGNkcjogQ2hhbmdlRGV0ZWN0b3JSZWYsIHByaXZhdGUgbmdab25lOiBOZ1pvbmUpIHt9XG5cbiAgLyoqIEluaXRpYWxpemUgaXRlbSBoZWlnaHRzIG9uIGNvbXBvbmVudCBpbml0aWFsaXphdGlvbiAqL1xuICBuZ09uSW5pdCgpOiB2b2lkIHtcbiAgICB0aGlzLmluaXRpYWxpemVIZWlnaHRzKCk7XG4gIH1cblxuICAvKipcbiAgICogUmVzcG9uZCB0byBjaGFuZ2VzIGluIGlucHV0IGJpbmRpbmdzIChtYWlubHkgdGhlIGBpdGVtc2AgYXJyYXkpXG4gICAqIEBwYXJhbSBjaGFuZ2VzIC0gaW5wdXQgcHJvcGVydHkgY2hhbmdlc1xuICAgKi9cbiAgbmdPbkNoYW5nZXMoY2hhbmdlczogU2ltcGxlQ2hhbmdlcyk6IHZvaWQge1xuICAgIGlmIChjaGFuZ2VzW1wiaXRlbXNcIl0pIHtcbiAgICAgIHRoaXMuaXRlbXMgPSBjaGFuZ2VzW1wiaXRlbXNcIl0uY3VycmVudFZhbHVlO1xuICAgICAgdGhpcy5pbml0aWFsaXplSGVpZ2h0cygpO1xuXG4gICAgICBzZXRUaW1lb3V0KCgpID0+IHtcbiAgICAgICAgdGhpcy5jYWxjdWxhdGVIZWlnaHRzKCk7XG4gICAgICAgIHRoaXMub25TY3JvbGwoKTtcbiAgICAgIH0pO1xuICAgIH1cbiAgICBpZiAoY2hhbmdlc1tcInBhbmVsT3BlblwiXSAmJiBjaGFuZ2VzW1wicGFuZWxPcGVuXCJdPy5jdXJyZW50VmFsdWUpIHtcbiAgICAgIHRoaXMucGFuZWxPcGVuID0gY2hhbmdlc1tcInBhbmVsT3BlblwiXT8uY3VycmVudFZhbHVlO1xuICAgICAgdGhpcy5vblNjcm9sbCgpO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBDYWxsZWQgYWZ0ZXIgdmlldyBpbml0aWFsaXphdGlvbi4gVHJpZ2dlcnMgaW5pdGlhbCBoZWlnaHQgY2FsY3VsYXRpb24uXG4gICAqL1xuICBuZ0FmdGVyVmlld0luaXQoKSB7XG4gICAgc2V0VGltZW91dCgoKSA9PiB7XG4gICAgICB0aGlzLmNhbGN1bGF0ZUhlaWdodHMoKTtcbiAgICAgIHRoaXMub25TY3JvbGwoKTtcbiAgICB9KTtcbiAgfVxuXG4gIC8qKlxuICAgKiBEaXNjb25uZWN0cyBSZXNpemVPYnNlcnZlciBvbiBjb21wb25lbnQgZGVzdHJveVxuICAgKi9cbiAgbmdPbkRlc3Ryb3koKTogdm9pZCB7XG4gICAgdGhpcy5yZXNpemVPYnNlcnZlci5kaXNjb25uZWN0KCk7XG4gIH1cblxuICAvKiogSW5pdGlhbGl6ZXMgaXRlbSBoZWlnaHQgYW5kIHBvc2l0aW9uIHRyYWNraW5nIGFycmF5cyAqL1xuICBpbml0aWFsaXplSGVpZ2h0cygpIHtcbiAgICB0aGlzLml0ZW1IZWlnaHRzID0gdGhpcy5pdGVtcy5tYXAoKCkgPT4gdGhpcy5pbml0aWFsSXRlbUhlaWdodCk7XG4gICAgdGhpcy51cGRhdGVJdGVtVG9wcygpO1xuICB9XG5cbiAgLyoqIFJlY2FsY3VsYXRlcyBpdGVtIHRvcCBwb3NpdGlvbnMgYW5kIGNvbnRlbnQgaGVpZ2h0ICovXG4gIGNhbGN1bGF0ZUhlaWdodHMoKSB7XG4gICAgdGhpcy51cGRhdGVJdGVtVG9wcygpO1xuICB9XG5cbiAgLyoqIFVwZGF0ZXMgdG9wIHBvc2l0aW9ucyBvZiBhbGwgaXRlbXMgYmFzZWQgb24gdGhlaXIgY3VycmVudCBoZWlnaHRzICovXG4gIHVwZGF0ZUl0ZW1Ub3BzKCkge1xuICAgIHRoaXMuaXRlbVRvcHMgPSBbXTtcbiAgICBsZXQgdG9wID0gMDtcbiAgICBmb3IgKGNvbnN0IGhlaWdodCBvZiB0aGlzLml0ZW1IZWlnaHRzKSB7XG4gICAgICB0aGlzLml0ZW1Ub3BzLnB1c2godG9wKTtcbiAgICAgIHRvcCArPSBoZWlnaHQ7XG4gICAgfVxuICAgIHRoaXMudG90YWxDb250ZW50SGVpZ2h0ID0gdG9wO1xuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIHRvcCBwb3NpdGlvbiBvZiBhbiBpdGVtIGJ5IGluZGV4XG4gICAqIEBwYXJhbSBpbmRleCAtIGluZGV4IG9mIHRoZSBpdGVtXG4gICAqL1xuICBnZXRJdGVtVG9wKGluZGV4OiBudW1iZXIpOiBudW1iZXIge1xuICAgIHJldHVybiB0aGlzLml0ZW1Ub3BzW2luZGV4XSB8fCAwO1xuICB9XG5cbiAgLyoqIFNjcm9sbCBldmVudCBoYW5kbGVyOiBjYWxjdWxhdGVzIHdoaWNoIGl0ZW1zIHNob3VsZCBiZSB2aXNpYmxlICovXG4gIG9uU2Nyb2xsKCkge1xuICAgIGNvbnN0IHNjcm9sbFRvcCA9IHRoaXMuc2Nyb2xsZXJSZWYubmF0aXZlRWxlbWVudC5zY3JvbGxUb3A7XG4gICAgY29uc3Qgdmlld3BvcnRCb3R0b20gPSBzY3JvbGxUb3AgKyB0aGlzLnZpZXdwb3J0SGVpZ2h0O1xuXG4gICAgbGV0IHN0YXJ0ID0gMDtcbiAgICB3aGlsZSAoXG4gICAgICBzdGFydCA8IHRoaXMuaXRlbXMubGVuZ3RoICYmXG4gICAgICB0aGlzLmdldEl0ZW1Ub3Aoc3RhcnQgKyAxKSA8IHNjcm9sbFRvcFxuICAgICkge1xuICAgICAgc3RhcnQrKztcbiAgICB9XG5cbiAgICBsZXQgZW5kID0gc3RhcnQ7XG4gICAgd2hpbGUgKGVuZCA8IHRoaXMuaXRlbXMubGVuZ3RoICYmIHRoaXMuZ2V0SXRlbVRvcChlbmQpIDwgdmlld3BvcnRCb3R0b20pIHtcbiAgICAgIGVuZCsrO1xuICAgIH1cbiAgICB0aGlzLnZpc2libGVTdGFydCA9IE1hdGgubWF4KDAsIHN0YXJ0IC0gdGhpcy5idWZmZXIpO1xuICAgIHRoaXMudmlzaWJsZUVuZCA9IE1hdGgubWluKHRoaXMuaXRlbXMubGVuZ3RoLCBlbmQgKyB0aGlzLmJ1ZmZlcik7XG4gICAgdGhpcy51cGRhdGVWaXNpYmxlSXRlbXMoKTtcbiAgICB0aGlzLnNjcm9sbEVtaXR0ZXIuZW1pdChzY3JvbGxUb3ApO1xuXG4gICAgLy8gQ2hlY2sgaWYgc2Nyb2xsZWQgdG8gdGhlIGVuZFxuICAgIGNvbnN0IHNjcm9sbEhlaWdodCA9IHRoaXMuc2Nyb2xsZXJSZWYubmF0aXZlRWxlbWVudC5zY3JvbGxIZWlnaHQ7XG4gICAgY29uc3QgY2xpZW50SGVpZ2h0ID0gdGhpcy5zY3JvbGxlclJlZi5uYXRpdmVFbGVtZW50LmNsaWVudEhlaWdodDtcbiAgICBjb25zdCBpc0F0RW5kID0gc2Nyb2xsVG9wICsgY2xpZW50SGVpZ2h0ID49IHNjcm9sbEhlaWdodCAtIDE7XG5cbiAgICBpZiAoaXNBdEVuZCAmJiBzY3JvbGxUb3AgPiB0aGlzLnByZXZpb3VzU2Nyb2xsVG9wICYmIHNjcm9sbFRvcCA+IDApIHtcbiAgICAgIHRoaXMuc2Nyb2xsVG9FbmQuZW1pdCgpO1xuICAgIH1cblxuICAgIHRoaXMucHJldmlvdXNTY3JvbGxUb3AgPSBzY3JvbGxUb3A7XG4gIH1cblxuICAvKiogVXBkYXRlcyB0aGUgbGlzdCBvZiBpdGVtcyB0byBiZSByZW5kZXJlZCBiYXNlZCBvbiBzY3JvbGwgcG9zaXRpb24gKi9cbiAgdXBkYXRlVmlzaWJsZUl0ZW1zKCkge1xuICAgIHRoaXMudmlzaWJsZUl0ZW1zID0gdGhpcy5pdGVtcy5zbGljZSh0aGlzLnZpc2libGVTdGFydCwgdGhpcy52aXNpYmxlRW5kKTtcblxuICAgIC8vIFdhaXQgZm9yIERPTSByZW5kZXIgYmVmb3JlIG1lYXN1cmluZyBoZWlnaHRzXG4gICAgc2V0VGltZW91dCgoKSA9PiB7XG4gICAgICB0aGlzLm1lYXN1cmVJdGVtSGVpZ2h0cygpO1xuICAgIH0pO1xuICB9XG5cbiAgLyoqIE1lYXN1cmVzIGFjdHVhbCBoZWlnaHRzIG9mIHJlbmRlcmVkIERPTSBlbGVtZW50cyBhbmQgdXBkYXRlcyB0cmFja2luZyAqL1xuICBtZWFzdXJlSXRlbUhlaWdodHMoKSB7XG4gICAgdGhpcy5pdGVtRWxlbWVudHMuZm9yRWFjaCgoZWwsIGlkeCkgPT4ge1xuICAgICAgY29uc3QgaW5kZXggPSB0aGlzLnZpc2libGVTdGFydCArIGlkeDtcbiAgICAgIGNvbnN0IGhlaWdodCA9IGVsLm5hdGl2ZUVsZW1lbnQub2Zmc2V0SGVpZ2h0O1xuICAgICAgaWYgKHRoaXMuaXRlbUhlaWdodHNbaW5kZXhdICE9PSBoZWlnaHQpIHtcbiAgICAgICAgdGhpcy5pdGVtSGVpZ2h0c1tpbmRleF0gPSBoZWlnaHQ7XG4gICAgICAgIHRoaXMucmVzaXplT2JzZXJ2ZXIub2JzZXJ2ZShlbC5uYXRpdmVFbGVtZW50KTtcbiAgICAgIH1cbiAgICB9KTtcbiAgICB0aGlzLnVwZGF0ZUl0ZW1Ub3BzKCk7XG4gICAgdGhpcy5jZHIubWFya0ZvckNoZWNrKCk7XG4gIH1cbn1cbiIsIjxkaXZcbiAgI3Njcm9sbGVyXG4gIGNsYXNzPVwic2Nyb2xsLWNvbnRhaW5lclwiXG4gIChzY3JvbGwpPVwib25TY3JvbGwoKVwiXG4gIFtzdHlsZS5oZWlnaHQucHhdPVwidmlld3BvcnRIZWlnaHRcIlxuPlxuICA8ZGl2XG4gICAgY2xhc3M9XCJ0b3RhbC1oZWlnaHRcIlxuICAgIFtzdHlsZS5oZWlnaHQucHhdPVwidG90YWxDb250ZW50SGVpZ2h0IC0gdmlld3BvcnRIZWlnaHRcIlxuICA+XG4gICAgPG5nLWNvbnRhaW5lciAqbmdGb3I9XCJsZXQgaXRlbSBvZiB2aXNpYmxlSXRlbXM7IGxldCBpID0gaW5kZXhcIj5cbiAgICAgIDxkaXZcbiAgICAgICAgI2l0ZW1FbGVtZW50XG4gICAgICAgIGNsYXNzPVwiaXRlbVwiXG4gICAgICAgIFtzdHlsZS50cmFuc2Zvcm1dPVwiJ3RyYW5zbGF0ZVkoJyArIGdldEl0ZW1Ub3AodmlzaWJsZVN0YXJ0ICsgaSkgKyAncHgpJ1wiXG4gICAgICA+XG4gICAgICAgIDxuZy1jb250YWluZXJcbiAgICAgICAgICAqbmdUZW1wbGF0ZU91dGxldD1cIml0ZW1UZW1wbGF0ZTsgY29udGV4dDogeyAkaW1wbGljaXQ6IGl0ZW0gfVwiXG4gICAgICAgID48L25nLWNvbnRhaW5lcj5cbiAgICAgIDwvZGl2PlxuICAgIDwvbmctY29udGFpbmVyPlxuICA8L2Rpdj5cbjwvZGl2PlxuIl19