ngx-nested-data-table
Version:
angular master-detail table angular material style
285 lines (274 loc) • 27.8 kB
JavaScript
import { DataSource } from '@angular/cdk/collections';
import { map } from 'rxjs/operators';
import { of, merge, BehaviorSubject } from 'rxjs';
import { Component, ViewChild, Input, NgModule } from '@angular/core';
import { MatPaginator, MatSort, MatTableModule, MatPaginatorModule, MatSortModule, MatInputModule } from '@angular/material';
import { trigger, state, style, transition, animate } from '@angular/animations';
import { CommonModule } from '@angular/common';
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* Data source for the NgxDataTable view. This class should
* encapsulate all logic for fetching and manipulating the displayed data
* (including sorting, pagination, and filtering).
*/
class NgxDataTableDataSource extends DataSource {
/**
* @param {?} paginator
* @param {?} _data
* @param {?} sort
*/
constructor(paginator, _data, sort) {
super();
this.paginator = paginator;
this._data = _data;
this.sort = sort;
this._filterChange = new BehaviorSubject('');
this.data = _data;
// Master detail table
const /** @type {?} */ rows = [];
_data.forEach(element => element.details
? rows.push(element, { detailRow: true, element })
: rows.push(element));
this.data = rows;
}
/**
* @return {?}
*/
get filter() {
return this._filterChange.value;
}
/**
* @param {?} filter
* @return {?}
*/
set filter(filter) {
this._filterChange.next(filter);
}
/**
* Connect this data source to the table. The table will only update when
* the returned stream emits new items.
* @return {?} A stream of the items to be rendered.
*/
connect() {
// Combine everything that affects the rendered data into one update
// stream for the data-table to consume.
const /** @type {?} */ dataMutations = [
of(this.data),
this.paginator.page,
this.sort.sortChange,
this._filterChange,
];
// Set the paginator length
this.paginator.length = this.data.length;
return merge(...dataMutations).pipe(map(() => {
const /** @type {?} */ filtered = this._filterData(this._data);
this.data = this._enrichData(filtered);
return this.getPagedData(this.getSortedData([...this.data]));
}));
}
/**
* @param {?} filtered
* @return {?}
*/
_enrichData(filtered) {
const /** @type {?} */ rows = [];
filtered.forEach(element => element.details
? rows.push(element, { detailRow: true, element })
: rows.push(element));
return rows;
}
/**
* @param {?} data
* @return {?}
*/
_filterData(data) {
this.filteredData =
!this.filter ? data : data.filter(obj => {
// Transform the data into a lowercase string of all property values.
const /** @type {?} */ accumulator = (currentTerm, key) => currentTerm + obj[key];
const /** @type {?} */ dataStr = Object.keys(obj).reduce(accumulator, '').toLowerCase();
// Transform the filter by converting it to lowercase and removing whitespace.
const /** @type {?} */ transformedFilter = this.filter.trim().toLowerCase();
return dataStr.indexOf(transformedFilter) !== -1;
});
return this.filteredData;
}
/**
* Called when the table is being destroyed. Use this function, to clean up
* any open connections or free any held resources that were set up during connect.
* @return {?}
*/
disconnect() { }
/**
* Paginate the data (client-side). If you're using server-side pagination,
* this would be replaced by requesting the appropriate data from the server.
* @param {?} data
* @return {?}
*/
getPagedData(data) {
const /** @type {?} */ startIndex = this.paginator.pageIndex * this.paginator.pageSize;
return data.splice(startIndex, this.paginator.pageSize);
}
/**
* Sort the data (client-side). If you're using server-side sorting,
* this would be replaced by requesting the appropriate data from the server.
* @param {?} data
* @return {?}
*/
getSortedData(data) {
if (!this.sort.active || this.sort.direction === '') {
return data;
}
return data.sort((a, b) => {
const /** @type {?} */ isAsc = this.sort.direction === 'asc';
if (isNaN(a[this.sort.active])) {
return compare(a[this.sort.active], b[this.sort.active], isAsc);
}
else {
return compare(+a[this.sort.active], +b[this.sort.active], isAsc);
}
});
}
}
/**
* Simple sort comparator for example ID/Name columns (for client-side sorting).
* @param {?} a
* @param {?} b
* @param {?} isAsc
* @return {?}
*/
function compare(a, b, isAsc) {
return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
class NgxDataTableComponent {
constructor() {
this.isExpansionDetailRow = (i, row) => row.hasOwnProperty('detailRow');
}
/**
* @param {?} _data
* @return {?}
*/
set data(_data) {
if (_data) {
this.dataSource = new NgxDataTableDataSource(this.paginator, _data, this.sort);
this.displayedColumns = Object.keys(_data[0]).filter(key => key !== 'details');
}
}
/**
* expand collapse a row
* @param {?} row
* @return {?}
*/
toggleRow(row) {
if (this.expandedElement === row) {
this.expandedElement = null;
}
else {
this.expandedElement = row;
}
}
/**
* @param {?} filterValue
* @return {?}
*/
applyFilter(filterValue) {
this.dataSource.filter = filterValue;
}
}
NgxDataTableComponent.decorators = [
{ type: Component, args: [{
selector: 'ngx-data-table',
template: `<div class="mat-elevation-z8">
<mat-form-field>
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter" class="filter">
</mat-form-field>
<mat-table #table [dataSource]="dataSource" matSort aria-label="Elements">
<ng-container [matColumnDef]="cols" *ngFor="let cols of displayedColumns; let i = index">
<mat-header-cell *matHeaderCellDef mat-sort-header>{{cols}}</mat-header-cell>
<mat-cell *matCellDef="let element" [ngClass]="{'col-value': element.details === undefined}">
<!-- expand caret -->
<span *ngIf="i === 0 && element.details">
<span *ngIf="expandedElement && expandedElement[cols] === element[cols]; else plusIcon" class="expand-icon">▲</span>
<ng-template #plusIcon><span class="expand-icon">▼</span></ng-template>
</span>
<span>{{element[cols]}}</span>
</mat-cell>
</ng-container>
<!-- Expanded Content Column - The detail row is made up of this one column -->
<ng-container matColumnDef="expandedDetail">
<mat-cell *matCellDef="let detail">
<ngx-data-table [data]="detail.element.details" class="detail-table"></ngx-data-table>
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns;"
[class.expanded]="expandedElement == row"
(click)="toggleRow(row)"></mat-row>
<mat-row *matRowDef="let row; columns: ['expandedDetail']; when: isExpansionDetailRow"
[]="row.element == expandedElement ? 'expanded' : 'collapsed'"
style="overflow: hidden"></mat-row>
</mat-table>
<mat-paginator #paginator
[length]="dataSource?.data?.length"
[pageIndex]="0"
[pageSize]="25"
[pageSizeOptions]="[25, 50, 100, 250]">
</mat-paginator>
</div>
`,
styles: [
`.detail-table {display: flex;flex-direction: column;display: block;margin: 10px;width: 100%;}
.expand-icon {color: rgba(0,0,0,.44);font-size: 12px; margin-right: 5px; cursor: pointer;}
.col-value:first-child span{margin-left: 15px;}
.mat-form-field {padding: 10px 10px 0 10px;width: calc(100% - 20px);}`
],
animations: [
trigger('detailExpand', [
state('collapsed', style({ height: '0px', minHeight: '0', visibility: 'hidden' })),
state('expanded', style({ height: '*', visibility: 'visible' })),
transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),
]),
],
},] },
];
NgxDataTableComponent.propDecorators = {
data: [{ type: Input }],
paginator: [{ type: ViewChild, args: [MatPaginator,] }],
sort: [{ type: ViewChild, args: [MatSort,] }]
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
class NgxDataTableModule {
}
NgxDataTableModule.decorators = [
{ type: NgModule, args: [{
imports: [
CommonModule,
MatTableModule,
MatPaginatorModule,
MatSortModule,
MatInputModule,
],
declarations: [NgxDataTableComponent],
exports: [NgxDataTableComponent],
},] },
];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
export { NgxDataTableDataSource, NgxDataTableComponent, NgxDataTableModule };
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmd4LW5lc3RlZC1kYXRhLXRhYmxlLmpzLm1hcCIsInNvdXJjZXMiOlsibmc6Ly9uZ3gtbmVzdGVkLWRhdGEtdGFibGUvbGliL25neC1kYXRhLXRhYmxlL25neC1kYXRhLXRhYmxlLWRhdGFzb3VyY2UudHMiLCJuZzovL25neC1uZXN0ZWQtZGF0YS10YWJsZS9saWIvbmd4LWRhdGEtdGFibGUvbmd4LWRhdGEtdGFibGUuY29tcG9uZW50LnRzIiwibmc6Ly9uZ3gtbmVzdGVkLWRhdGEtdGFibGUvbGliL25neC1kYXRhLXRhYmxlL25neC1kYXRhLXRhYmxlLm1vZHVsZS50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBEYXRhU291cmNlIH0gZnJvbSAnQGFuZ3VsYXIvY2RrL2NvbGxlY3Rpb25zJztcbmltcG9ydCB7IE1hdFBhZ2luYXRvciwgTWF0U29ydCB9IGZyb20gJ0Bhbmd1bGFyL21hdGVyaWFsJztcbmltcG9ydCB7IG1hcCB9IGZyb20gJ3J4anMvb3BlcmF0b3JzJztcbmltcG9ydCB7T2JzZXJ2YWJsZSwgb2YgYXMgb2JzZXJ2YWJsZU9mLCBtZXJnZSwgQmVoYXZpb3JTdWJqZWN0LCBjb21iaW5lTGF0ZXN0fSBmcm9tICdyeGpzJztcblxuLyoqXG4gKiBEYXRhIHNvdXJjZSBmb3IgdGhlIE5neERhdGFUYWJsZSB2aWV3LiBUaGlzIGNsYXNzIHNob3VsZFxuICogZW5jYXBzdWxhdGUgYWxsIGxvZ2ljIGZvciBmZXRjaGluZyBhbmQgbWFuaXB1bGF0aW5nIHRoZSBkaXNwbGF5ZWQgZGF0YVxuICogKGluY2x1ZGluZyBzb3J0aW5nLCBwYWdpbmF0aW9uLCBhbmQgZmlsdGVyaW5nKS5cbiAqL1xuZXhwb3J0IGNsYXNzIE5neERhdGFUYWJsZURhdGFTb3VyY2UgZXh0ZW5kcyBEYXRhU291cmNlPGFueT4ge1xuICBwdWJsaWMgZGF0YTogYW55W107XG4gIF9maWx0ZXJDaGFuZ2UgPSBuZXcgQmVoYXZpb3JTdWJqZWN0PHN0cmluZz4oJycpO1xuICBmaWx0ZXJlZERhdGE6IGFueVtdO1xuXG4gIGdldCBmaWx0ZXIoKTogc3RyaW5nIHtcbiAgICByZXR1cm4gdGhpcy5fZmlsdGVyQ2hhbmdlLnZhbHVlO1xuICB9XG4gIHNldCBmaWx0ZXIoZmlsdGVyOiBzdHJpbmcpIHtcbiAgICB0aGlzLl9maWx0ZXJDaGFuZ2UubmV4dChmaWx0ZXIpO1xuICB9XG5cbiAgY29uc3RydWN0b3IoXG4gICAgcHJpdmF0ZSBwYWdpbmF0b3I6IE1hdFBhZ2luYXRvcixcbiAgICBwcml2YXRlIF9kYXRhOiBhbnlbXSxcbiAgICBwcml2YXRlIHNvcnQ6IE1hdFNvcnRcbiAgKSB7XG4gICAgc3VwZXIoKTtcbiAgICB0aGlzLmRhdGEgPSBfZGF0YTtcbiAgICAvLyBNYXN0ZXIgZGV0YWlsIHRhYmxlXG4gICAgY29uc3Qgcm93cyA9IFtdO1xuICAgIF9kYXRhLmZvckVhY2goXG4gICAgICBlbGVtZW50ID0+XG4gICAgICAgIGVsZW1lbnQuZGV0YWlsc1xuICAgICAgICAgID8gcm93cy5wdXNoKGVsZW1lbnQsIHsgZGV0YWlsUm93OiB0cnVlLCBlbGVtZW50IH0pXG4gICAgICAgICAgOiByb3dzLnB1c2goZWxlbWVudClcbiAgICApO1xuICAgIHRoaXMuZGF0YSA9IHJvd3M7XG4gIH1cblxuICAvKipcbiAgICogQ29ubmVjdCB0aGlzIGRhdGEgc291cmNlIHRvIHRoZSB0YWJsZS4gVGhlIHRhYmxlIHdpbGwgb25seSB1cGRhdGUgd2hlblxuICAgKiB0aGUgcmV0dXJuZWQgc3RyZWFtIGVtaXRzIG5ldyBpdGVtcy5cbiAgICogQHJldHVybnMgQSBzdHJlYW0gb2YgdGhlIGl0ZW1zIHRvIGJlIHJlbmRlcmVkLlxuICAgKi9cbiAgY29ubmVjdCgpOiBPYnNlcnZhYmxlPGFueVtdPiB7XG4gICAgLy8gQ29tYmluZSBldmVyeXRoaW5nIHRoYXQgYWZmZWN0cyB0aGUgcmVuZGVyZWQgZGF0YSBpbnRvIG9uZSB1cGRhdGVcbiAgICAvLyBzdHJlYW0gZm9yIHRoZSBkYXRhLXRhYmxlIHRvIGNvbnN1bWUuXG4gICAgY29uc3QgZGF0YU11dGF0aW9ucyA9IFtcbiAgICAgIG9ic2VydmFibGVPZih0aGlzLmRhdGEpLFxuICAgICAgdGhpcy5wYWdpbmF0b3IucGFnZSxcbiAgICAgIHRoaXMuc29ydC5zb3J0Q2hhbmdlLFxuICAgICAgdGhpcy5fZmlsdGVyQ2hhbmdlLFxuICAgIF07XG5cbiAgICAvLyBTZXQgdGhlIHBhZ2luYXRvciBsZW5ndGhcbiAgICB0aGlzLnBhZ2luYXRvci5sZW5ndGggPSB0aGlzLmRhdGEubGVuZ3RoO1xuXG4gICAgcmV0dXJuIG1lcmdlKC4uLmRhdGFNdXRhdGlvbnMpLnBpcGUoXG4gICAgICBtYXAoKCkgPT4ge1xuICAgICAgICBjb25zdCBmaWx0ZXJlZCA9IHRoaXMuX2ZpbHRlckRhdGEodGhpcy5fZGF0YSk7XG4gICAgICAgIHRoaXMuZGF0YSA9IHRoaXMuX2VucmljaERhdGEoZmlsdGVyZWQpO1xuICAgICAgICByZXR1cm4gdGhpcy5nZXRQYWdlZERhdGEodGhpcy5nZXRTb3J0ZWREYXRhKFsuLi50aGlzLmRhdGFdKSk7XG4gICAgICB9KVxuICAgICk7XG4gIH1cblxuICBwcml2YXRlIF9lbnJpY2hEYXRhKGZpbHRlcmVkKSB7XG4gICAgY29uc3Qgcm93cyA9IFtdO1xuICAgIGZpbHRlcmVkLmZvckVhY2goXG4gICAgICBlbGVtZW50ID0+XG4gICAgICAgIGVsZW1lbnQuZGV0YWlsc1xuICAgICAgICAgID8gcm93cy5wdXNoKGVsZW1lbnQsIHtkZXRhaWxSb3c6IHRydWUsIGVsZW1lbnR9KVxuICAgICAgICAgIDogcm93cy5wdXNoKGVsZW1lbnQpXG4gICAgKTtcbiAgICByZXR1cm4gcm93cztcbiAgfVxuXG4gIHByaXZhdGUgX2ZpbHRlckRhdGEoZGF0YTogYW55W10pIHtcbiAgICB0aGlzLmZpbHRlcmVkRGF0YSA9XG4gICAgICAhdGhpcy5maWx0ZXIgPyBkYXRhIDogZGF0YS5maWx0ZXIob2JqID0+IHtcbiAgICAgIC8vIFRyYW5zZm9ybSB0aGUgZGF0YSBpbnRvIGEgbG93ZXJjYXNlIHN0cmluZyBvZiBhbGwgcHJvcGVydHkgdmFsdWVzLlxuICAgICAgY29uc3QgYWNjdW11bGF0b3IgPSAoY3VycmVudFRlcm0sIGtleSkgPT4gY3VycmVudFRlcm0gKyBvYmpba2V5XTtcbiAgICAgIGNvbnN0IGRhdGFTdHIgPSBPYmplY3Qua2V5cyhvYmopLnJlZHVjZShhY2N1bXVsYXRvciwgJycpLnRvTG93ZXJDYXNlKCk7XG5cbiAgICAgIC8vIFRyYW5zZm9ybSB0aGUgZmlsdGVyIGJ5IGNvbnZlcnRpbmcgaXQgdG8gbG93ZXJjYXNlIGFuZCByZW1vdmluZyB3aGl0ZXNwYWNlLlxuICAgICAgY29uc3QgdHJhbnNmb3JtZWRGaWx0ZXIgPSB0aGlzLmZpbHRlci50cmltKCkudG9Mb3dlckNhc2UoKTtcblxuICAgICAgcmV0dXJuIGRhdGFTdHIuaW5kZXhPZih0cmFuc2Zvcm1lZEZpbHRlcikgIT09IC0xO1xuICAgIH0pO1xuICAgIHJldHVybiB0aGlzLmZpbHRlcmVkRGF0YTtcbiAgfVxuXG4gIC8qKlxuICAgKiAgQ2FsbGVkIHdoZW4gdGhlIHRhYmxlIGlzIGJlaW5nIGRlc3Ryb3llZC4gVXNlIHRoaXMgZnVuY3Rpb24sIHRvIGNsZWFuIHVwXG4gICAqIGFueSBvcGVuIGNvbm5lY3Rpb25zIG9yIGZyZWUgYW55IGhlbGQgcmVzb3VyY2VzIHRoYXQgd2VyZSBzZXQgdXAgZHVyaW5nIGNvbm5lY3QuXG4gICAqL1xuICBkaXNjb25uZWN0KCkge31cblxuICAvKipcbiAgICogUGFnaW5hdGUgdGhlIGRhdGEgKGNsaWVudC1zaWRlKS4gSWYgeW91J3JlIHVzaW5nIHNlcnZlci1zaWRlIHBhZ2luYXRpb24sXG4gICAqIHRoaXMgd291bGQgYmUgcmVwbGFjZWQgYnkgcmVxdWVzdGluZyB0aGUgYXBwcm9wcmlhdGUgZGF0YSBmcm9tIHRoZSBzZXJ2ZXIuXG4gICAqL1xuICBwcml2YXRlIGdldFBhZ2VkRGF0YShkYXRhOiBhbnlbXSkge1xuICAgIGNvbnN0IHN0YXJ0SW5kZXggPSB0aGlzLnBhZ2luYXRvci5wYWdlSW5kZXggKiB0aGlzLnBhZ2luYXRvci5wYWdlU2l6ZTtcbiAgICByZXR1cm4gZGF0YS5zcGxpY2Uoc3RhcnRJbmRleCwgdGhpcy5wYWdpbmF0b3IucGFnZVNpemUpO1xuICB9XG5cbiAgLyoqXG4gICAqIFNvcnQgdGhlIGRhdGEgKGNsaWVudC1zaWRlKS4gSWYgeW91J3JlIHVzaW5nIHNlcnZlci1zaWRlIHNvcnRpbmcsXG4gICAqIHRoaXMgd291bGQgYmUgcmVwbGFjZWQgYnkgcmVxdWVzdGluZyB0aGUgYXBwcm9wcmlhdGUgZGF0YSBmcm9tIHRoZSBzZXJ2ZXIuXG4gICAqL1xuICBwcml2YXRlIGdldFNvcnRlZERhdGEoZGF0YTogYW55W10pIHtcbiAgICBpZiAoIXRoaXMuc29ydC5hY3RpdmUgfHwgdGhpcy5zb3J0LmRpcmVjdGlvbiA9PT0gJycpIHtcbiAgICAgIHJldHVybiBkYXRhO1xuICAgIH1cblxuICAgIHJldHVybiBkYXRhLnNvcnQoKGEsIGIpID0+IHtcbiAgICAgIGNvbnN0IGlzQXNjID0gdGhpcy5zb3J0LmRpcmVjdGlvbiA9PT0gJ2FzYyc7XG4gICAgICBpZiAoaXNOYU4oYVt0aGlzLnNvcnQuYWN0aXZlXSkpIHtcbiAgICAgICAgcmV0dXJuIGNvbXBhcmUoYVt0aGlzLnNvcnQuYWN0aXZlXSwgYlt0aGlzLnNvcnQuYWN0aXZlXSwgaXNBc2MpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgcmV0dXJuIGNvbXBhcmUoK2FbdGhpcy5zb3J0LmFjdGl2ZV0sICtiW3RoaXMuc29ydC5hY3RpdmVdLCBpc0FzYyk7XG4gICAgICB9XG4gICAgfSk7XG4gIH1cbn1cblxuLyoqIFNpbXBsZSBzb3J0IGNvbXBhcmF0b3IgZm9yIGV4YW1wbGUgSUQvTmFtZSBjb2x1bW5zIChmb3IgY2xpZW50LXNpZGUgc29ydGluZykuICovXG5mdW5jdGlvbiBjb21wYXJlKGEsIGIsIGlzQXNjKSB7XG4gIHJldHVybiAoYSA8IGIgPyAtMSA6IDEpICogKGlzQXNjID8gMSA6IC0xKTtcbn1cbiIsImltcG9ydCB7IENvbXBvbmVudCwgT25Jbml0LCBWaWV3Q2hpbGQsIElucHV0IH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyBNYXRQYWdpbmF0b3IsIE1hdFNvcnQgfSBmcm9tICdAYW5ndWxhci9tYXRlcmlhbCc7XG5pbXBvcnQgeyBOZ3hEYXRhVGFibGVEYXRhU291cmNlIH0gZnJvbSAnLi9uZ3gtZGF0YS10YWJsZS1kYXRhc291cmNlJztcbmltcG9ydCB7XG4gIHRyaWdnZXIsXG4gIHN0YXRlLFxuICBzdHlsZSxcbiAgdHJhbnNpdGlvbixcbiAgYW5pbWF0ZSxcbn0gZnJvbSAnQGFuZ3VsYXIvYW5pbWF0aW9ucyc7XG5cbkBDb21wb25lbnQoe1xuICBzZWxlY3RvcjogJ25neC1kYXRhLXRhYmxlJyxcbiAgdGVtcGxhdGU6IGA8ZGl2IGNsYXNzPVwibWF0LWVsZXZhdGlvbi16OFwiPlxuICAgIDxtYXQtZm9ybS1maWVsZD5cbiAgICAgIDxpbnB1dCBtYXRJbnB1dCAoa2V5dXApPVwiYXBwbHlGaWx0ZXIoJGV2ZW50LnRhcmdldC52YWx1ZSlcIiBwbGFjZWhvbGRlcj1cIkZpbHRlclwiIGNsYXNzPVwiZmlsdGVyXCI+XG4gICAgPC9tYXQtZm9ybS1maWVsZD5cbiAgPG1hdC10YWJsZSAjdGFibGUgW2RhdGFTb3VyY2VdPVwiZGF0YVNvdXJjZVwiIG1hdFNvcnQgYXJpYS1sYWJlbD1cIkVsZW1lbnRzXCI+XG4gICAgPG5nLWNvbnRhaW5lciBbbWF0Q29sdW1uRGVmXT1cImNvbHNcIiAqbmdGb3I9XCJsZXQgY29scyBvZiBkaXNwbGF5ZWRDb2x1bW5zOyBsZXQgaSA9IGluZGV4XCI+XG4gICAgICA8bWF0LWhlYWRlci1jZWxsICptYXRIZWFkZXJDZWxsRGVmIG1hdC1zb3J0LWhlYWRlcj57e2NvbHN9fTwvbWF0LWhlYWRlci1jZWxsPlxuICAgICAgPG1hdC1jZWxsICptYXRDZWxsRGVmPVwibGV0IGVsZW1lbnRcIiBbbmdDbGFzc109XCJ7J2NvbC12YWx1ZSc6IGVsZW1lbnQuZGV0YWlscyA9PT0gdW5kZWZpbmVkfVwiPlxuICAgICAgICA8IS0tIGV4cGFuZCBjYXJldCAtLT5cbiAgICAgICAgPHNwYW4gKm5nSWY9XCJpID09PSAwICYmIGVsZW1lbnQuZGV0YWlsc1wiPlxuICAgICAgICAgIDxzcGFuICpuZ0lmPVwiZXhwYW5kZWRFbGVtZW50ICYmIGV4cGFuZGVkRWxlbWVudFtjb2xzXSA9PT0gZWxlbWVudFtjb2xzXTsgZWxzZSBwbHVzSWNvblwiIGNsYXNzPVwiZXhwYW5kLWljb25cIj4mIzk2NTA7PC9zcGFuPlxuICAgICAgICAgIDxuZy10ZW1wbGF0ZSAjcGx1c0ljb24+PHNwYW4gY2xhc3M9XCJleHBhbmQtaWNvblwiPiYjOTY2MDs8L3NwYW4+PC9uZy10ZW1wbGF0ZT5cbiAgICAgICAgPC9zcGFuPlxuICAgICAgICA8c3Bhbj57e2VsZW1lbnRbY29sc119fTwvc3Bhbj5cbiAgICAgIDwvbWF0LWNlbGw+XG4gICAgPC9uZy1jb250YWluZXI+XG5cbiAgICA8IS0tIEV4cGFuZGVkIENvbnRlbnQgQ29sdW1uIC0gVGhlIGRldGFpbCByb3cgaXMgbWFkZSB1cCBvZiB0aGlzIG9uZSBjb2x1bW4gLS0+XG4gICAgPG5nLWNvbnRhaW5lciBtYXRDb2x1bW5EZWY9XCJleHBhbmRlZERldGFpbFwiPlxuICAgICAgPG1hdC1jZWxsICptYXRDZWxsRGVmPVwibGV0IGRldGFpbFwiPlxuICAgICAgICA8bmd4LWRhdGEtdGFibGUgW2RhdGFdPVwiZGV0YWlsLmVsZW1lbnQuZGV0YWlsc1wiIGNsYXNzPVwiZGV0YWlsLXRhYmxlXCI+PC9uZ3gtZGF0YS10YWJsZT5cbiAgICAgIDwvbWF0LWNlbGw+XG4gICAgPC9uZy1jb250YWluZXI+XG5cbiAgICA8bWF0LWhlYWRlci1yb3cgKm1hdEhlYWRlclJvd0RlZj1cImRpc3BsYXllZENvbHVtbnNcIj48L21hdC1oZWFkZXItcm93PlxuICAgIDxtYXQtcm93ICptYXRSb3dEZWY9XCJsZXQgcm93OyBjb2x1bW5zOiBkaXNwbGF5ZWRDb2x1bW5zO1wiXG4gICAgICAgICAgICAgIFtjbGFzcy5leHBhbmRlZF09XCJleHBhbmRlZEVsZW1lbnQgPT0gcm93XCJcbiAgICAgICAgICAgICAgKGNsaWNrKT1cInRvZ2dsZVJvdyhyb3cpXCI+PC9tYXQtcm93PlxuICAgIDxtYXQtcm93ICptYXRSb3dEZWY9XCJsZXQgcm93OyBjb2x1bW5zOiBbJ2V4cGFuZGVkRGV0YWlsJ107IHdoZW46IGlzRXhwYW5zaW9uRGV0YWlsUm93XCJcbiAgICBbQGRldGFpbEV4cGFuZF09XCJyb3cuZWxlbWVudCA9PSBleHBhbmRlZEVsZW1lbnQgPyAnZXhwYW5kZWQnIDogJ2NvbGxhcHNlZCdcIlxuICAgIHN0eWxlPVwib3ZlcmZsb3c6IGhpZGRlblwiPjwvbWF0LXJvdz5cbiAgPC9tYXQtdGFibGU+XG5cbiAgPG1hdC1wYWdpbmF0b3IgI3BhZ2luYXRvclxuXG4gICAgW2xlbmd0aF09XCJkYXRhU291cmNlPy5kYXRhPy5sZW5ndGhcIlxuICAgIFtwYWdlSW5kZXhdPVwiMFwiXG4gICAgW3BhZ2VTaXplXT1cIjI1XCJcbiAgICBbcGFnZVNpemVPcHRpb25zXT1cIlsyNSwgNTAsIDEwMCwgMjUwXVwiPlxuICA8L21hdC1wYWdpbmF0b3I+XG48L2Rpdj5cbmAsXG4gIHN0eWxlczogW1xuICAgIGAuZGV0YWlsLXRhYmxlIHtkaXNwbGF5OiBmbGV4O2ZsZXgtZGlyZWN0aW9uOiBjb2x1bW47ZGlzcGxheTogYmxvY2s7bWFyZ2luOiAxMHB4O3dpZHRoOiAxMDAlO31cbiAgICAuZXhwYW5kLWljb24ge2NvbG9yOiByZ2JhKDAsMCwwLC40NCk7Zm9udC1zaXplOiAxMnB4OyBtYXJnaW4tcmlnaHQ6IDVweDsgY3Vyc29yOiBwb2ludGVyO31cbiAgICAuY29sLXZhbHVlOmZpcnN0LWNoaWxkIHNwYW57bWFyZ2luLWxlZnQ6IDE1cHg7fVxuICAgIC5tYXQtZm9ybS1maWVsZCB7cGFkZGluZzogMTBweCAxMHB4IDAgMTBweDt3aWR0aDogY2FsYygxMDAlIC0gMjBweCk7fWBcbiAgXSxcbiAgYW5pbWF0aW9uczogW1xuICAgIHRyaWdnZXIoJ2RldGFpbEV4cGFuZCcsIFtcbiAgICAgIHN0YXRlKFxuICAgICAgICAnY29sbGFwc2VkJyxcbiAgICAgICAgc3R5bGUoeyBoZWlnaHQ6ICcwcHgnLCBtaW5IZWlnaHQ6ICcwJywgdmlzaWJpbGl0eTogJ2hpZGRlbicgfSlcbiAgICAgICksXG4gICAgICBzdGF0ZSgnZXhwYW5kZWQnLCBzdHlsZSh7IGhlaWdodDogJyonLCB2aXNpYmlsaXR5OiAndmlzaWJsZScgfSkpLFxuICAgICAgdHJhbnNpdGlvbihcbiAgICAgICAgJ2V4cGFuZGVkIDw9PiBjb2xsYXBzZWQnLFxuICAgICAgICBhbmltYXRlKCcyMjVtcyBjdWJpYy1iZXppZXIoMC40LCAwLjAsIDAuMiwgMSknKVxuICAgICAgKSxcbiAgICBdKSxcbiAgXSxcbn0pXG5leHBvcnQgY2xhc3MgTmd4RGF0YVRhYmxlQ29tcG9uZW50IHtcbiAgQElucHV0KClcbiAgc2V0IGRhdGEoX2RhdGE6IGFueVtdKSB7XG4gICAgaWYgKF9kYXRhKSB7XG4gICAgICB0aGlzLmRhdGFTb3VyY2UgPSBuZXcgTmd4RGF0YVRhYmxlRGF0YVNvdXJjZShcbiAgICAgICAgdGhpcy5wYWdpbmF0b3IsXG4gICAgICAgIF9kYXRhLFxuICAgICAgICB0aGlzLnNvcnRcbiAgICAgICk7XG4gICAgICB0aGlzLmRpc3BsYXllZENvbHVtbnMgPSBPYmplY3Qua2V5cyhfZGF0YVswXSkuZmlsdGVyKFxuICAgICAgICBrZXkgPT4ga2V5ICE9PSAnZGV0YWlscydcbiAgICAgICk7XG4gICAgfVxuICB9XG4gIEBWaWV3Q2hpbGQoTWF0UGFnaW5hdG9yKSBwYWdpbmF0b3I6IE1hdFBhZ2luYXRvcjtcbiAgQFZpZXdDaGlsZChNYXRTb3J0KSBzb3J0OiBNYXRTb3J0O1xuICBkYXRhU291cmNlOiBOZ3hEYXRhVGFibGVEYXRhU291cmNlO1xuXG4gIC8qKiBDb2x1bW5zIGRpc3BsYXllZCBpbiB0aGUgdGFibGUuIENvbHVtbnMgSURzIGNhbiBiZSBhZGRlZCwgcmVtb3ZlZCwgb3IgcmVvcmRlcmVkLiAqL1xuICBkaXNwbGF5ZWRDb2x1bW5zOiBBcnJheTxzdHJpbmc+O1xuICBleHBhbmRlZEVsZW1lbnQ6IEFycmF5PHN0cmluZz47XG4gIGlzRXhwYW5zaW9uRGV0YWlsUm93ID0gKGk6IG51bWJlciwgcm93OiBPYmplY3QpID0+XG4gICAgcm93Lmhhc093blByb3BlcnR5KCdkZXRhaWxSb3cnKTtcblxuICAvKipcbiAgICogZXhwYW5kIGNvbGxhcHNlIGEgcm93XG4gICAqIEBwYXJhbSByb3dcbiAgICovXG4gIHRvZ2dsZVJvdyhyb3cpIHtcbiAgICBpZiAodGhpcy5leHBhbmRlZEVsZW1lbnQgPT09IHJvdykge1xuICAgICAgdGhpcy5leHBhbmRlZEVsZW1lbnQgPSBudWxsO1xuICAgIH0gZWxzZSB7XG4gICAgICB0aGlzLmV4cGFuZGVkRWxlbWVudCA9IHJvdztcbiAgICB9XG4gIH1cblxuICBhcHBseUZpbHRlcihmaWx0ZXJWYWx1ZTogc3RyaW5nKSB7XG4gICAgdGhpcy5kYXRhU291cmNlLmZpbHRlciA9IGZpbHRlclZhbHVlO1xuICB9XG59XG4iLCJpbXBvcnQgeyBOZ01vZHVsZSB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgQ29tbW9uTW9kdWxlIH0gZnJvbSAnQGFuZ3VsYXIvY29tbW9uJztcbmltcG9ydCB7IE5neERhdGFUYWJsZUNvbXBvbmVudCB9IGZyb20gJy4vbmd4LWRhdGEtdGFibGUuY29tcG9uZW50JztcbmltcG9ydCB7XG4gIE1hdFRhYmxlTW9kdWxlLFxuICBNYXRQYWdpbmF0b3JNb2R1bGUsXG4gIE1hdFNvcnRNb2R1bGUsXG4gIE1hdElucHV0TW9kdWxlLFxufSBmcm9tICdAYW5ndWxhci9tYXRlcmlhbCc7XG5cbkBOZ01vZHVsZSh7XG4gIGltcG9ydHM6IFtcbiAgICBDb21tb25Nb2R1bGUsXG4gICAgTWF0VGFibGVNb2R1bGUsXG4gICAgTWF0UGFnaW5hdG9yTW9kdWxlLFxuICAgIE1hdFNvcnRNb2R1bGUsXG4gICAgTWF0SW5wdXRNb2R1bGUsXG4gIF0sXG4gIGRlY2xhcmF0aW9uczogW05neERhdGFUYWJsZUNvbXBvbmVudF0sXG4gIGV4cG9ydHM6IFtOZ3hEYXRhVGFibGVDb21wb25lbnRdLFxufSlcbmV4cG9ydCBjbGFzcyBOZ3hEYXRhVGFibGVNb2R1bGUge31cbiJdLCJuYW1lcyI6WyJvYnNlcnZhYmxlT2YiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7OztBQUFBOzs7OztBQVVBLDRCQUFvQyxTQUFRLFVBQWU7Ozs7OztJQVl6RCxZQUNVLFdBQ0EsT0FDQTtRQUVSLEtBQUssRUFBRSxDQUFDO1FBSkEsY0FBUyxHQUFULFNBQVM7UUFDVCxVQUFLLEdBQUwsS0FBSztRQUNMLFNBQUksR0FBSixJQUFJOzZCQWJFLElBQUksZUFBZSxDQUFTLEVBQUUsQ0FBQztRQWdCN0MsSUFBSSxDQUFDLElBQUksR0FBRyxLQUFLLENBQUM7O1FBRWxCLHVCQUFNLElBQUksR0FBRyxFQUFFLENBQUM7UUFDaEIsS0FBSyxDQUFDLE9BQU8sQ0FDWCxPQUFPLElBQ0wsT0FBTyxDQUFDLE9BQU87Y0FDWCxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxFQUFFLFNBQVMsRUFBRSxJQUFJLEVBQUUsT0FBTyxFQUFFLENBQUM7Y0FDaEQsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FDekIsQ0FBQztRQUNGLElBQUksQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFDO0tBQ2xCOzs7O0lBdkJELElBQUksTUFBTTtRQUNSLE9BQU8sSUFBSSxDQUFDLGFBQWEsQ0FBQyxLQUFLLENBQUM7S0FDakM7Ozs7O0lBQ0QsSUFBSSxNQUFNLENBQUMsTUFBYztRQUN2QixJQUFJLENBQUMsYUFBYSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQztLQUNqQzs7Ozs7O0lBeUJELE9BQU87OztRQUdMLHVCQUFNLGFBQWEsR0FBRztZQUNwQkEsRUFBWSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUM7WUFDdkIsSUFBSSxDQUFDLFNBQVMs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