ngx-scroll-position-restoration
Version:
Scroll position restoration in Angular.
177 lines • 27.5 kB
JavaScript
import { isPlatformServer } from '@angular/common';
import { Directive, Inject, PLATFORM_ID } from '@angular/core';
import { ElementRef } from '@angular/core';
import { NavigationStart } from '@angular/router';
import { NavigationEnd } from '@angular/router';
import { Router } from '@angular/router';
import { RouterOutlet } from '@angular/router';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import * as DomUtils from './dom-utils';
import { NGX_SCROLL_POSITION_RESTORATION_CONFIG_INJECTION_TOKEN } from './ngx-scroll-position-restoration-config-injection-token';
import { NgxScrollPositionRestorationService } from './ngx-scroll-position-restoration.service';
const ANGULAR_DEFAULT_ROUTER_OUTLET_NAME = 'primary';
/**
* I co-opt the <router-outlet> element selector so that I can tap into the life-cycle of the core RouterOutlet directive.
*
* REASON: When the user clicks on a link, it's quite hard to differentiate between a primary navigation, which should probably scroll the user back to the top of the viewport; and, something like a tabbed-navigation, which should probably keep the user's scroll around the offset associated with the tab. As such, we are going to rely on the inherent scroll-position of the view as the router-outlet target is pulled out of the DOM.
* PS: Keep in mind in Angular per default scroll position is maintained on navigation.
*/
export class CustomRouterOutletDirective {
constructor(elementRef, router, routerOutlet, ngxScrollPositionRestorationService, platformId, config) {
this.elementRef = elementRef;
this.router = router;
this.routerOutlet = routerOutlet;
this.ngxScrollPositionRestorationService = ngxScrollPositionRestorationService;
this.platformId = platformId;
this.config = config;
this.recordedScrollPositions = [];
this.directiveDestroyed$ = new Subject();
}
ngOnInit() {
if (isPlatformServer(this.platformId)) {
return;
}
this.routerOutlet.activateEvents.pipe(takeUntil(this.directiveDestroyed$)).subscribe(() => this.handleActivateEvent());
this.routerOutlet.deactivateEvents.pipe(takeUntil(this.directiveDestroyed$)).subscribe(() => this.handleDectivateEvent());
this.router.events.pipe(takeUntil(this.directiveDestroyed$)).subscribe((event) => this.handleNavigationEvent(event));
}
ngOnDestroy() {
this.directiveDestroyed$.next();
this.directiveDestroyed$.complete();
}
/**
* Called when a router-outlet component has been rendered.
*/
handleActivateEvent() {
var _a;
const currentRouterOutletName = this.routerOutlet.activatedRoute.outlet;
// A Check because there is no `router.getCurrentNavigation` function in Angular 6.
const currentNavigation = typeof this.router.getCurrentNavigation === 'function' ? this.router.getCurrentNavigation() : null;
if (currentRouterOutletName !== ANGULAR_DEFAULT_ROUTER_OUTLET_NAME
&& !((_a = currentNavigation === null || currentNavigation === void 0 ? void 0 : currentNavigation.extras) === null || _a === void 0 ? void 0 : _a.skipLocationChange)) {
this.ngxScrollPositionRestorationService.clearSavedWindowScrollTopInLastNavigation();
}
const isRootRouterOutlet = this.isRootRouterOutlet(this.routerOutlet.activatedRoute);
if (isRootRouterOutlet
&& this.navigationTrigger === 'imperative'
&& this.routerOutlet.activatedRoute.outlet === ANGULAR_DEFAULT_ROUTER_OUTLET_NAME) {
DomUtils.scrollTo(window, 0);
if (this.config.debug) {
console.log('Imperative navigation: scrolled to the top (scrollTop = 0) of the window.');
}
}
else {
// At this point, the View-in-question has been mounted in the DOM (Document
// Object Model). We can now walk back up the DOM and make sure that the
// previously-recorded offsets (in the last 'deactivate' event) are being applied
// to the ancestral elements. This will prevent the browser's native desire to
// auto-scroll-down a document once the view has been injected. Essentially, this
// ensures that we scroll back to the 'expected top' as the user clicks through
// the application.
if (this.config.debug) {
console.group(`router-outlet ("${this.elementRef.nativeElement.getAttribute('name') || ANGULAR_DEFAULT_ROUTER_OUTLET_NAME}") - Reapply recorded scroll positions.`);
console.log(this.recordedScrollPositions.slice());
console.groupEnd();
}
if (this.recordedScrollPositions.length === 0) {
return;
}
for (const { elementSelector, scrollPosition } of this.recordedScrollPositions) {
if (elementSelector) {
const element = DomUtils.select(elementSelector);
if (element) {
DomUtils.scrollTo(element, scrollPosition);
}
}
}
this.recordedScrollPositions = [];
}
}
/**
* Called when a router-outlet component has been destroyed from the DOM. This means, at this point, the scroll position of the scrollable element containing the router-outlet component should be `0` (@todo: (BUG) but this seems not to work in Angular@13.1.1: component is not destroyed at this point).
*/
handleDectivateEvent() {
// At this point, the View-in-question has already been removed from the
// document. Let's walk up the DOM (Document Object Model) and record the scroll
// position of all scrollable elements. This will give us a sense of what the DOM
// should look like after the next View is injected.
let node = this.elementRef.nativeElement.parentNode;
while (node && node.tagName !== 'BODY') {
// If this is an "Element" node, capture its offset.
if (node.nodeType === 1) {
const scrollTop = DomUtils.getScrollTop(node);
const elementSelector = DomUtils.getSelector(node);
this.recordedScrollPositions.push({
elementSelector,
target: node,
scrollPosition: scrollTop
});
}
node = node.parentNode;
}
if (this.config.debug) {
console.group(`router-outlet ("${this.elementRef.nativeElement.getAttribute('name') || ANGULAR_DEFAULT_ROUTER_OUTLET_NAME}") - Recorded scroll positions.`);
console.log(this.recordedScrollPositions.slice());
console.groupEnd();
}
}
/**
* I get called whenever a router event is raised.
*/
handleNavigationEvent(event) {
if (event instanceof NavigationStart) {
this.navigationTrigger = event.navigationTrigger;
}
// The 'offsets' are only meant to be used across a single navigation. As such,
// let's clear out the offsets at the end of each navigation in order to ensure
// that old offsets don't accidentally get applied to a future view mounted by
// the current router-outlet.
if (event instanceof NavigationEnd) {
this.recordedScrollPositions = [];
}
}
/**
* Is root "primary" (or any secondary) router-outet.
*/
isRootRouterOutlet(actvitedRoute) {
var _a, _b;
const currentComponent = actvitedRoute.component;
const parentChildren = (_b = (_a = actvitedRoute.parent) === null || _a === void 0 ? void 0 : _a.routeConfig) === null || _b === void 0 ? void 0 : _b.children;
if (!Array.isArray(parentChildren)) {
return true;
}
for (const route of parentChildren) {
if (route.component === currentComponent) {
return false;
}
}
return true;
// Alternative: solution 02 (but not valid for secondary router-outlet)
// if (actvitedRoute.parent?.component) {
// return false;
// } else {
// return true;
// }
}
}
CustomRouterOutletDirective.decorators = [
{ type: Directive, args: [{
selector: 'router-outlet'
},] }
];
CustomRouterOutletDirective.ctorParameters = () => [
{ type: ElementRef },
{ type: Router },
{ type: RouterOutlet },
{ type: NgxScrollPositionRestorationService },
{ type: String, decorators: [{ type: Inject, args: [PLATFORM_ID,] }] },
{ type: undefined, decorators: [{ type: Inject, args: [NGX_SCROLL_POSITION_RESTORATION_CONFIG_INJECTION_TOKEN,] }] }
];
/**
* Source:
* - https://www.bennadel.com/blog/3534-restoring-and-resetting-the-scroll-position-using-the-navigationstart-event-in-angular-7-0-4.htm
* - http://bennadel.github.io/JavaScript-Demos/demos/router-retain-scroll-polyfill-angular7/
* - https://github.com/bennadel/JavaScript-Demos/tree/master/demos/router-retain-scroll-polyfill-angular7
*/
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3VzdG9tLXJvdXRlci1vdXRsZXQuZGlyZWN0aXZlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vcHJvamVjdHMvbmd4LXNjcm9sbC1wb3NpdGlvbi1yZXN0b3JhdGlvbi9zcmMvbGliL2N1c3RvbS1yb3V0ZXItb3V0bGV0LmRpcmVjdGl2ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUNuRCxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0sRUFBYSxXQUFXLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFDMUUsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUMzQyxPQUFPLEVBQWtELGVBQWUsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQ2xHLE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUNoRCxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDekMsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQy9DLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxNQUFNLENBQUM7QUFDL0IsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQzNDLE9BQU8sS0FBSyxRQUFRLE1BQU0sYUFBYSxDQUFDO0FBRXhDLE9BQU8sRUFBRSxzREFBc0QsRUFBRSxNQUFNLDBEQUEwRCxDQUFDO0FBQ2xJLE9BQU8sRUFBRSxtQ0FBbUMsRUFBRSxNQUFNLDJDQUEyQyxDQUFDO0FBRWhHLE1BQU0sa0NBQWtDLEdBQUcsU0FBUyxDQUFDO0FBRXJEOzs7OztHQUtHO0FBSUgsTUFBTSxPQUFPLDJCQUEyQjtJQVF0QyxZQUNVLFVBQStCLEVBQy9CLE1BQWMsRUFDZCxZQUEwQixFQUMxQixtQ0FBd0UsRUFDbkQsVUFBa0IsRUFDeUIsTUFBMEM7UUFMMUcsZUFBVSxHQUFWLFVBQVUs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SUFBSSxjQUFjLEVBQUU7WUFDbEMsSUFBSSxLQUFLLENBQUMsU0FBUyxLQUFLLGdCQUFnQixFQUFFO2dCQUN4QyxPQUFPLEtBQUssQ0FBQzthQUNkO1NBQ0Y7UUFDRCxPQUFPLElBQUksQ0FBQztRQUVaLHVFQUF1RTtRQUN2RSx5Q0FBeUM7UUFDekMsa0JBQWtCO1FBQ2xCLFdBQVc7UUFDWCxpQkFBaUI7UUFDakIsSUFBSTtJQUNOLENBQUM7OztZQXZLRixTQUFTLFNBQUM7Z0JBQ1QsUUFBUSxFQUFFLGVBQWU7YUFDMUI7OztZQXRCUSxVQUFVO1lBR1YsTUFBTTtZQUNOLFlBQVk7WUFNWixtQ0FBbUM7eUNBMEJ2QyxNQUFNLFNBQUMsV0FBVzs0Q0FDbEIsTUFBTSxTQUFDLHNEQUFzRDs7QUErSmxFOzs7OztHQUtHIiwic291cmNlc0NvbnRlbnQiOlsiXG5pbXBvcnQgeyBpc1BsYXRmb3JtU2VydmVyIH0gZnJvbSAnQGFuZ3VsYXIvY29tbW9uJztcbmltcG9ydCB7IERpcmVjdGl2ZSwgSW5qZWN0LCBPbkRlc3Ryb3ksIFBMQVRGT1JNX0lEIH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyBFbGVtZW50UmVmIH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyBBY3RpdmF0ZWRSb3V0ZSwgRXZlbnQgYXMgUm91dGVyTmF2aWdhdGlvbkV2ZW50LCBOYXZpZ2F0aW9uU3RhcnQgfSBmcm9tICdAYW5ndWxhci9yb3V0ZXInO1xuaW1wb3J0IHsgTmF2aWdhdGlvbkVuZCB9IGZyb20gJ0Bhbmd1bGFyL3JvdXRlcic7XG5pbXBvcnQgeyBSb3V0ZXIgfSBmcm9tICdAYW5ndWxhci9yb3V0ZXInO1xuaW1wb3J0IHsgUm91dGVyT3V0bGV0IH0gZnJvbSAnQGFuZ3VsYXIvcm91dGVyJztcbmltcG9ydCB7IFN1YmplY3QgfSBmcm9tICdyeGpzJztcbmltcG9ydCB7IHRha2VVbnRpbCB9IGZyb20gJ3J4anMvb3BlcmF0b3JzJztcbmltcG9ydCAqIGFzIERvbVV0aWxzIGZyb20gJy4vZG9tLXV0aWxzJztcbmltcG9ydCB7IE5neFNjcm9sbFBvc2l0aW9uUmVzdG9yYXRpb25Db25maWcgfSBmcm9tICcuL25neC1zY3JvbGwtcG9zaXRpb24tcmVzdG9yYXRpb24tY29uZmlnJztcbmltcG9ydCB7IE5HWF9TQ1JPTExfUE9TSVRJT05fUkVTVE9SQVRJT05fQ09ORklHX0lOSkVDVElPTl9UT0tFTiB9IGZyb20gJy4vbmd4LXNjcm9sbC1wb3NpdGlvbi1yZXN0b3JhdGlvbi1jb25maWctaW5qZWN0aW9uLXRva2VuJztcbmltcG9ydCB7IE5neFNjcm9sbFBvc2l0aW9uUmVzdG9yYXRpb25TZXJ2aWNlIH0gZnJvbSAnLi9uZ3gtc2Nyb2xsLXBvc2l0aW9uLXJlc3RvcmF0aW9uLnNlcnZpY2UnO1xuXG5jb25zdCBBTkdVTEFSX0RFRkFVTFRfUk9VVEVSX09VVExFVF9OQU1FID0gJ3ByaW1hcnknO1xuXG4vKipcbiAqIEkgY28tb3B0IHRoZSA8cm91dGVyLW91dGxldD4gZWxlbWVudCBzZWxlY3RvciBzbyB0aGF0IEkgY2FuIHRhcCBpbnRvIHRoZSBsaWZlLWN5Y2xlIG9mIHRoZSBjb3JlIFJvdXRlck91dGxldCBkaXJlY3RpdmUuXG4gKiBcbiAqIFJFQVNPTjogV2hlbiB0aGUgdXNlciBjbGlja3Mgb24gYSBsaW5rLCBpdCdzIHF1aXRlIGhhcmQgdG8gZGlmZmVyZW50aWF0ZSBiZXR3ZWVuIGEgcHJpbWFyeSBuYXZpZ2F0aW9uLCB3aGljaCBzaG91bGQgcHJvYmFibHkgc2Nyb2xsIHRoZSB1c2VyIGJhY2sgdG8gdGhlIHRvcCBvZiB0aGUgdmlld3BvcnQ7IGFuZCwgc29tZXRoaW5nIGxpa2UgYSB0YWJiZWQtbmF2aWdhdGlvbiwgd2hpY2ggc2hvdWxkIHByb2JhYmx5IGtlZXAgdGhlIHVzZXIncyBzY3JvbGwgYXJvdW5kIHRoZSBvZmZzZXQgYXNzb2NpYXRlZCB3aXRoIHRoZSB0YWIuIEFzIHN1Y2gsIHdlIGFyZSBnb2luZyB0byByZWx5IG9uIHRoZSBpbmhlcmVudCBzY3JvbGwtcG9zaXRpb24gb2YgdGhlIHZpZXcgYXMgdGhlIHJvdXRlci1vdXRsZXQgdGFyZ2V0IGlzIHB1bGxlZCBvdXQgb2YgdGhlIERPTS5cbiAqIFBTOiBLZWVwIGluIG1pbmQgaW4gQW5ndWxhciBwZXIgZGVmYXVsdCBzY3JvbGwgcG9zaXRpb24gaXMgbWFpbnRhaW5lZCBvbiBuYXZpZ2F0aW9uLlxuICovXG5ARGlyZWN0aXZlKHtcbiAgc2VsZWN0b3I6ICdyb3V0ZXItb3V0bGV0J1xufSlcbmV4cG9ydCBjbGFzcyBDdXN0b21Sb3V0ZXJPdXRsZXREaXJlY3RpdmUgaW1wbGVtZW50cyBPbkRlc3Ryb3kge1xuXG4gIHByaXZhdGUgcmVjb3JkZWRTY3JvbGxQb3NpdGlvbnM6IFJlY29yZGVkU2Nyb2xsUG9zaXRpb25bXSA9IFtdO1xuXG4gIHByaXZhdGUgZGlyZWN0aXZlRGVzdHJveWVkJCA9IG5ldyBTdWJqZWN0PHZvaWQ+KCk7XG5cbiAgcHJpdmF0ZSBuYXZpZ2F0aW9uVHJpZ2dlcjogJ2ltcGVyYXRpdmUnIHwgJ3BvcHN0YXRlJyB8ICdoYXNoY2hhbmdlJyB8IHVuZGVmaW5lZDtcblxuICBjb25zdHJ1Y3RvcihcbiAgICBwcml2YXRlIGVsZW1lbnRSZWY6IEVsZW1lbnRSZWY8RWxlbWVudD4sXG4gICAgcHJpdmF0ZSByb3V0ZXI6IFJvdXRlcixcbiAgICBwcml2YXRlIHJvdXRlck91dGxldDogUm91dGVyT3V0bGV0LFxuICAgIHByaXZhdGUgbmd4U2Nyb2xsUG9zaXRpb25SZXN0b3JhdGlvblNlcnZpY2U6IE5neFNjcm9sbFBvc2l0aW9uUmVzdG9yYXRpb25TZXJ2aWNlLFxuICAgIEBJbmplY3QoUExBVEZPUk1fSUQpIHByaXZhdGUgcGxhdGZvcm1JZDogc3RyaW5nLFxuICAgIEBJbmplY3QoTkdYX1NDUk9MTF9QT1NJVElPTl9SRVNUT1JBVElPTl9DT05GSUdfSU5KRUNUSU9OX1RPS0VOKSBwcml2YXRlIGNvbmZpZzogTmd4U2Nyb2xsUG9zaXRpb25SZXN0b3JhdGlvbkNvbmZpZ1xuICApIHsgfVxuXG4gIG5nT25Jbml0KCk6IHZvaWQge1xuICAgIGlmIChpc1BsYXRmb3JtU2VydmVyKHRoaXMucGxhdGZvcm1JZCkpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICB0aGlzLnJvdXRlck91dGxldC5hY3RpdmF0ZUV2ZW50cy5waXBlKFxuICAgICAgdGFrZVVudGlsKHRoaXMuZGlyZWN0aXZlRGVzdHJveWVkJClcbiAgICApLnN1YnNjcmliZSgoKSA9PiB0aGlzLmhhbmRsZUFjdGl2YXRlRXZlbnQoKSk7XG5cbiAgICB0aGlzLnJvdXRlck91dGxldC5kZWFjdGl2YXRlRXZlbnRzLnBpcGUoXG4gICAgICB0YWtlVW50aWwodGhpcy5kaXJlY3RpdmVEZXN0cm95ZWQkKVxuICAgICkuc3Vic2NyaWJlKCgpID0+IHRoaXMuaGFuZGxlRGVjdGl2YXRlRXZlbnQoKSk7XG5cbiAgICB0aGlzLnJvdXRlci5ldmVudHMucGlwZShcbiAgICAgIHRha2VVbnRpbCh0aGlzLmRpcmVjdGl2ZURlc3Ryb3llZCQpXG4gICAgKS5zdWJzY3JpYmUoKGV2ZW50OiBSb3V0ZXJOYXZpZ2F0aW9uRXZlbnQpID0+IHRoaXMuaGFuZGxlTmF2aWdhdGlvbkV2ZW50KGV2ZW50KSk7XG4gIH1cblxuICBuZ09uRGVzdHJveSgpOiB2b2lkIHtcbiAgICB0aGlzLmRpcmVjdGl2ZURlc3Ryb3llZCQubmV4dCgpO1xuICAgIHRoaXMuZGlyZWN0aXZlRGVzdHJveWVkJC5jb21wbGV0ZSgpO1xuICB9XG5cbiAgLyoqXG4gICAqIENhbGxlZCB3aGVuIGEgcm91dGVyLW91dGxldCBjb21wb25lbnQgaGFzIGJlZW4gcmVuZGVyZWQuXG4gICAqL1xuICBwcml2YXRlIGhhbmRsZUFjdGl2YXRlRXZlbnQoKTogdm9pZCB7XG4gICAgY29uc3QgY3VycmVudFJvdXRlck91dGxldE5hbWUgPSB0aGlzLnJvdXRlck91dGxldC5hY3RpdmF0ZWRSb3V0ZS5vdXRsZXQ7XG4gICAgLy8gQSBDaGVjayBiZWNhdXNlIHRoZXJlIGlzIG5vIGByb3V0ZXIuZ2V0Q3VycmVudE5hdmlnYXRpb25gIGZ1bmN0aW9uIGluIEFuZ3VsYXIgNi5cbiAgICBjb25zdCBjdXJyZW50TmF2aWdhdGlvbiA9IHR5cGVvZiB0aGlzLnJvdXRlci5nZXRDdXJyZW50TmF2aWdhdGlvbiA9PT0gJ2Z1bmN0aW9uJyA/IHRoaXMucm91dGVyLmdldEN1cnJlbnROYXZpZ2F0aW9uKCkgOiBudWxsO1xuICAgIGlmIChjdXJyZW50Um91dGVyT3V0bGV0TmFtZSAhPT0gQU5HVUxBUl9ERUZBVUxUX1JPVVRFUl9PVVRMRVRfTkFNRVxuICAgICAgJiYgIShjdXJyZW50TmF2aWdhdGlvbj8uZXh0cmFzPy5za2lwTG9jYXRpb25DaGFuZ2UpKSB7XG4gICAgICB0aGlzLm5neFNjcm9sbFBvc2l0aW9uUmVzdG9yYXRpb25TZXJ2aWNlLmNsZWFyU2F2ZWRXaW5kb3dTY3JvbGxUb3BJbkxhc3ROYXZpZ2F0aW9uKCk7XG4gICAgfVxuXG4gICAgY29uc3QgaXNSb290Um91dGVyT3V0bGV0ID0gdGhpcy5pc1Jvb3RSb3V0ZXJPdXRsZXQodGhpcy5yb3V0ZXJPdXRsZXQuYWN0aXZhdGVkUm91dGUpO1xuICAgIGlmIChpc1Jvb3RSb3V0ZXJPdXRsZXRcbiAgICAgICYmIHRoaXMubmF2aWdhdGlvblRyaWdnZXIgPT09ICdpbXBlcmF0aXZlJ1xuICAgICAgJiYgdGhpcy5yb3V0ZXJPdXRsZXQuYWN0aXZhdGVkUm91dGUub3V0bGV0ID09PSBBTkdVTEFSX0RFRkFVTFRfUk9VVEVSX09VVExFVF9OQU1FKSB7XG4gICAgICBEb21VdGlscy5zY3JvbGxUbyh3aW5kb3csIDApO1xuICAgICAgaWYgKHRoaXMuY29uZmlnLmRlYnVnKSB7XG4gICAgICAgIGNvbnNvbGUubG9nKCdJbXBlcmF0aXZlIG5hdmlnYXRpb246IHNjcm9sbGVkIHRvIHRoZSB0b3AgKHNjcm9sbFRvcCA9IDApIG9mIHRoZSB3aW5kb3cuJyk7XG4gICAgICB9XG4gICAgfSBlbHNlIHtcblxuICAgICAgLy8gQXQgdGhpcyBwb2ludCwgdGhlIFZpZXctaW4tcXVlc3Rpb24gaGFzIGJlZW4gbW91bnRlZCBpbiB0aGUgRE9NIChEb2N1bWVudFxuICAgICAgLy8gT2JqZWN0IE1vZGVsKS4gV2UgY2FuIG5vdyB3YWxrIGJhY2sgdXAgdGhlIERPTSBhbmQgbWFrZSBzdXJlIHRoYXQgdGhlXG4gICAgICAvLyBwcmV2aW91c2x5LXJlY29yZGVkIG9mZnNldHMgKGluIHRoZSBsYXN0ICdkZWFjdGl2YXRlJyBldmVudCkgYXJlIGJlaW5nIGFwcGxpZWRcbiAgICAgIC8vIHRvIHRoZSBhbmNlc3RyYWwgZWxlbWVudHMuIFRoaXMgd2lsbCBwcmV2ZW50IHRoZSBicm93c2VyJ3MgbmF0aXZlIGRlc2lyZSB0byBcbiAgICAgIC8vIGF1dG8tc2Nyb2xsLWRvd24gYSBkb2N1bWVudCBvbmNlIHRoZSB2aWV3IGhhcyBiZWVuIGluamVjdGVkLiBFc3NlbnRpYWxseSwgdGhpc1xuICAgICAgLy8gZW5zdXJlcyB0aGF0IHdlIHNjcm9sbCBiYWNrIHRvIHRoZSAnZXhwZWN0ZWQgdG9wJyBhcyB0aGUgdXNlciBjbGlja3MgdGhyb3VnaFxuICAgICAgLy8gdGhlIGFwcGxpY2F0aW9uLlxuXG4gICAgICBpZiAodGhpcy5jb25maWcuZGVidWcpIHtcbiAgICAgICAgY29uc29sZS5ncm91cChgcm91dGVyLW91dGxldCAoXCIke3RoaXMuZWxlbWVudFJlZi5uYXRpdmVFbGVtZW50LmdldEF0dHJpYnV0ZSgnbmFtZScpIHx8IEFOR1VMQVJfREVGQVVMVF9ST1VURVJfT1VUTEVUX05BTUV9XCIpIC0gUmVhcHBseSByZWNvcmRlZCBzY3JvbGwgcG9zaXRpb25zLmApO1xuICAgICAgICBjb25zb2xlLmxvZyh0aGlzLnJlY29yZGVkU2Nyb2xsUG9zaXRpb25zLnNsaWNlKCkpO1xuICAgICAgICBjb25zb2xlLmdyb3VwRW5kKCk7XG4gICAgICB9XG5cbiAgICAgIGlmICh0aGlzLnJlY29yZGVkU2Nyb2xsUG9zaXRpb25zLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG5cbiAgICAgIGZvciAoY29uc3QgeyBlbGVtZW50U2VsZWN0b3IsIHNjcm9sbFBvc2l0aW9uIH0gb2YgdGhpcy5yZWNvcmRlZFNjcm9sbFBvc2l0aW9ucykge1xuICAgICAgICBpZiAoZWxlbWVudFNlbGVjdG9yKSB7XG4gICAgICAgICAgY29uc3QgZWxlbWVudCA9IERvbVV0aWxzLnNlbGVjdChlbGVtZW50U2VsZWN0b3IpO1xuICAgICAgICAgIGlmIChlbGVtZW50KSB7XG4gICAgICAgICAgICBEb21VdGlscy5zY3JvbGxUbyhlbGVtZW50LCBzY3JvbGxQb3NpdGlvbik7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIHRoaXMucmVjb3JkZWRTY3JvbGxQb3NpdGlvbnMgPSBbXTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogQ2FsbGVkIHdoZW4gYSByb3V0ZXItb3V0bGV0IGNvbXBvbmVudCBoYXMgYmVlbiBkZXN0cm95ZWQgZnJvbSB0aGUgRE9NLiBUaGlzIG1lYW5zLCBhdCB0aGlzIHBvaW50LCB0aGUgc2Nyb2xsIHBvc2l0aW9uIG9mIHRoZSBzY3JvbGxhYmxlIGVsZW1lbnQgY29udGFpbmluZyB0aGUgcm91dGVyLW91dGxldCBjb21wb25lbnQgc2hvdWxkIGJlIGAwYCAoQHRvZG86IChCVUcpIGJ1dCB0aGlzIHNlZW1zIG5vdCB0byB3b3JrIGluIEFuZ3VsYXJAMTMuMS4xOiBjb21wb25lbnQgaXMgbm90IGRlc3Ryb3llZCBhdCB0aGlzIHBvaW50KS5cbiAgICovXG4gIHByaXZhdGUgaGFuZGxlRGVjdGl2YXRlRXZlbnQoKTogdm9pZCB7XG5cbiAgICAvLyBBdCB0aGlzIHBvaW50LCB0aGUgVmlldy1pbi1xdWVzdGlvbiBoYXMgYWxyZWFkeSBiZWVuIHJlbW92ZWQgZnJvbSB0aGUgXG4gICAgLy8gZG9jdW1lbnQuIExldCdzIHdhbGsgdXAgdGhlIERPTSAoRG9jdW1lbnQgT2JqZWN0IE1vZGVsKSBhbmQgcmVjb3JkIHRoZSBzY3JvbGxcbiAgICAvLyBwb3NpdGlvbiBvZiBhbGwgc2Nyb2xsYWJsZSBlbGVtZW50cy4gVGhpcyB3aWxsIGdpdmUgdXMgYSBzZW5zZSBvZiB3aGF0IHRoZSBET01cbiAgICAvLyBzaG91bGQgbG9vayBsaWtlIGFmdGVyIHRoZSBuZXh0IFZpZXcgaXMgaW5qZWN0ZWQuXG4gICAgbGV0IG5vZGUgPSB0aGlzLmVsZW1lbnRSZWYubmF0aXZlRWxlbWVudC5wYXJlbnROb2RlIGFzIEVsZW1lbnQ7XG4gICAgd2hpbGUgKG5vZGUgJiYgbm9kZS50YWdOYW1lICE9PSAnQk9EWScpIHtcbiAgICAgIC8vIElmIHRoaXMgaXMgYW4gXCJFbGVtZW50XCIgbm9kZSwgY2FwdHVyZSBpdHMgb2Zmc2V0LlxuICAgICAgaWYgKG5vZGUubm9kZVR5cGUgPT09IDEpIHtcbiAgICAgICAgY29uc3Qgc2Nyb2xsVG9wID0gRG9tVXRpbHMuZ2V0U2Nyb2xsVG9wKG5vZGUpO1xuICAgICAgICBjb25zdCBlbGVtZW50U2VsZWN0b3IgPSBEb21VdGlscy5nZXRTZWxlY3Rvcihub2RlKTtcbiAgICAgICAgdGhpcy5yZWNvcmRlZFNjcm9sbFBvc2l0aW9ucy5wdXNoKHtcbiAgICAgICAgICBlbGVtZW50U2VsZWN0b3IsXG4gICAgICAgICAgdGFyZ2V0OiBub2RlLFxuICAgICAgICAgIHNjcm9sbFBvc2l0aW9uOiBzY3JvbGxUb3BcbiAgICAgICAgfSk7XG4gICAgICB9XG4gICAgICBub2RlID0gbm9kZS5wYXJlbnROb2RlIGFzIEVsZW1lbnQ7XG4gICAgfVxuXG4gICAgaWYgKHRoaXMuY29uZmlnLmRlYnVnKSB7XG4gICAgICBjb25zb2xlLmdyb3VwKGByb3V0ZXItb3V0bGV0IChcIiR7dGhpcy5lbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnQuZ2V0QXR0cmlidXRlKCduYW1lJykgfHwgQU5HVUxBUl9ERUZBVUxUX1JPVVRFUl9PVVRMRVRfTkFNRX1cIikgLSBSZWNvcmRlZCBzY3JvbGwgcG9zaXRpb25zLmApO1xuICAgICAgY29uc29sZS5sb2codGhpcy5yZWNvcmRlZFNjcm9sbFBvc2l0aW9ucy5zbGljZSgpKTtcbiAgICAgIGNvbnNvbGUuZ3JvdXBFbmQoKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogSSBnZXQgY2FsbGVkIHdoZW5ldmVyIGEgcm91dGVyIGV2ZW50IGlzIHJhaXNlZC5cbiAgICovXG4gIHByaXZhdGUgaGFuZGxlTmF2aWdhdGlvbkV2ZW50KGV2ZW50OiBSb3V0ZXJOYXZpZ2F0aW9uRXZlbnQpOiB2b2lkIHtcbiAgICBpZiAoZXZlbnQgaW5zdGFuY2VvZiBOYXZpZ2F0aW9uU3RhcnQpIHtcbiAgICAgIHRoaXMubmF2aWdhdGlvblRyaWdnZXIgPSBldmVudC5uYXZpZ2F0aW9uVHJpZ2dlcjtcbiAgICB9XG5cbiAgICAvLyBUaGUgJ29mZnNldHMnIGFyZSBvbmx5IG1lYW50IHRvIGJlIHVzZWQgYWNyb3NzIGEgc2luZ2xlIG5hdmlnYXRpb24uIEFzIHN1Y2gsXG4gICAgLy8gbGV0J3MgY2xlYXIgb3V0IHRoZSBvZmZzZXRzIGF0IHRoZSBlbmQgb2YgZWFjaCBuYXZpZ2F0aW9uIGluIG9yZGVyIHRvIGVuc3VyZVxuICAgIC8vIHRoYXQgb2xkIG9mZnNldHMgZG9uJ3QgYWNjaWRlbnRhbGx5IGdldCBhcHBsaWVkIHRvIGEgZnV0dXJlIHZpZXcgbW91bnRlZCBieVxuICAgIC8vIHRoZSBjdXJyZW50IHJvdXRlci1vdXRsZXQuXG4gICAgaWYgKGV2ZW50IGluc3RhbmNlb2YgTmF2aWdhdGlvbkVuZCkge1xuICAgICAgdGhpcy5yZWNvcmRlZFNjcm9sbFBvc2l0aW9ucyA9IFtdO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBJcyByb290IFwicHJpbWFyeVwiIChvciBhbnkgc2Vjb25kYXJ5KSByb3V0ZXItb3V0ZXQuXG4gICAqL1xuICBwcml2YXRlIGlzUm9vdFJvdXRlck91dGxldChhY3R2aXRlZFJvdXRlOiBBY3RpdmF0ZWRSb3V0ZSk6IGJvb2xlYW4ge1xuICAgIGNvbnN0IGN1cnJlbnRDb21wb25lbnQgPSBhY3R2aXRlZFJvdXRlLmNvbXBvbmVudDtcbiAgICBjb25zdCBwYXJlbnRDaGlsZHJlbiA9IGFjdHZpdGVkUm91dGUucGFyZW50Py5yb3V0ZUNvbmZpZz8uY2hpbGRyZW47XG4gICAgaWYgKCFBcnJheS5pc0FycmF5KHBhcmVudENoaWxkcmVuKSkge1xuICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxuXG4gICAgZm9yIChjb25zdCByb3V0ZSBvZiBwYXJlbnRDaGlsZHJlbikge1xuICAgICAgaWYgKHJvdXRlLmNvbXBvbmVudCA9PT0gY3VycmVudENvbXBvbmVudCkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiB0cnVlO1xuXG4gICAgLy8gQWx0ZXJuYXRpdmU6IHNvbHV0aW9uIDAyIChidXQgbm90IHZhbGlkIGZvciBzZWNvbmRhcnkgcm91dGVyLW91dGxldClcbiAgICAvLyBpZiAoYWN0dml0ZWRSb3V0ZS5wYXJlbnQ/LmNvbXBvbmVudCkge1xuICAgIC8vICAgcmV0dXJuIGZhbHNlO1xuICAgIC8vIH0gZWxzZSB7XG4gICAgLy8gICByZXR1cm4gdHJ1ZTtcbiAgICAvLyB9XG4gIH1cbn1cblxuaW50ZXJmYWNlIFJlY29yZGVkU2Nyb2xsUG9zaXRpb24ge1xuICBlbGVtZW50U2VsZWN0b3I6IHN0cmluZyB8IG51bGw7XG4gIHNjcm9sbFBvc2l0aW9uOiBudW1iZXI7XG4gIHRhcmdldDogYW55XG59XG5cbi8qKlxuICogU291cmNlOlxuICogLSBodHRwczovL3d3dy5iZW5uYWRlbC5jb20vYmxvZy8zNTM0LXJlc3RvcmluZy1hbmQtcmVzZXR0aW5nLXRoZS1zY3JvbGwtcG9zaXRpb24tdXNpbmctdGhlLW5hdmlnYXRpb25zdGFydC1ldmVudC1pbi1hbmd1bGFyLTctMC00Lmh0bVxuICogLSBodHRwOi8vYmVubmFkZWwuZ2l0aHViLmlvL0phdmFTY3JpcHQtRGVtb3MvZGVtb3Mvcm91dGVyLXJldGFpbi1zY3JvbGwtcG9seWZpbGwtYW5ndWxhcjcvXG4gKiAtIGh0dHBzOi8vZ2l0aHViLmNvbS9iZW5uYWRlbC9KYXZhU2NyaXB0LURlbW9zL3RyZWUvbWFzdGVyL2RlbW9zL3JvdXRlci1yZXRhaW4tc2Nyb2xsLXBvbHlmaWxsLWFuZ3VsYXI3XG4gKi8iXX0=