UNPKG

ngx-nested-data-table

Version:
285 lines (274 loc) 27.8 kB
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">&#9650;</span> <ng-template #plusIcon><span class="expand-icon">&#9660;</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" [@detailExpand]="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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