UNPKG

ngx-tree-dnd

Version:

Angular 7 support tree with drag-and-drop sortable data tree. It`s fast and smart.

1,314 lines (1,302 loc) 115 kB
import { Injectable, NgModule, Component, Input, Directive, ElementRef, HostListener, HostBinding, Output, EventEmitter, defineInjectable } from '@angular/core'; import { Subject, BehaviorSubject, Observable } from 'rxjs'; import { FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms'; import { CommonModule } from '@angular/common'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { library } from '@fortawesome/fontawesome-svg-core'; import { faCoffee, faPlus, faEdit, faMinus, faTimes, faCheck, faArrowDown } from '@fortawesome/free-solid-svg-icons'; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class NgxTreeService { constructor() { this.treeStorage = []; this.onDragStart = new Subject(); this.onDragEnter = new Subject(); this.onDragLeave = new Subject(); this.onDrop = new Subject(); this.onDrag = new Subject(); this.onAllowDrop = new Subject(); this.onDragEnd = new Subject(); this.onAddItem = new Subject(); this.onRenameItem = new Subject(); this.onStartRenameItem = new Subject(); this.onFinishRenameItem = new Subject(); this.onStartDeleteItem = new Subject(); this.onFinishDeleteItem = new Subject(); this.onCancelDeleteItem = new Subject(); this.config = new BehaviorSubject(null); // set default config this.defaulConfig = { showActionButtons: true, showAddButtons: true, showRenameButtons: true, showDeleteButtons: true, showRootActionButtons: true, enableExpandButtons: true, enableDragging: true, rootTitle: 'Root', validationText: 'Enter valid name', minCharacterLength: 1, setItemsAsLinks: false, setFontSize: 16, setIconSize: 14 }; } /* get data and set it on observable. if data = null set empty data array */ /** * @param {?} item * @return {?} */ getLocalData(item) { /** @type {?} */ const data = new Observable(observer => { this.treeStorage = item; if (this.treeStorage && this.treeStorage !== null) { observer.next(this.treeStorage); } else { this.treeStorage = JSON.parse('[]'); observer.next(this.treeStorage); } }); return data; } /* Element finder, it`s find element by id in tree. Returns: finded element, parent array. Watch out, this is recursive method. */ /** * @private * @param {?} list * @param {?} id * @param {?=} parent * @return {?} */ elementFinder(list, id, parent) { for (const item of list) { if (item.id === id) { this.findingResults = { foundItem: item, itemsList: list }; if (parent) { this.findingResults.parentItem = parent; } break; } else { if (item.childrens.length > 0) { this.elementFinder(item.childrens, id, item); } } } } /* Add new item to tree. Its accepts 'type' for detect add root element or children. Emit onAddItem Subject. */ /** * @param {?} id * @param {?} name * @param {?=} parent * @return {?} */ addNewItem(id, name, parent) { /** @type {?} */ let pos = 1; if (parent && parent.childrens.length !== 0) { /** @type {?} */ const parentPrevChildren = parent.childrens.length - 1; /** @type {?} */ const newItemPosition = parent.childrens[parentPrevChildren].options.position + 1; pos = newItemPosition; } /** @type {?} */ const createObj = { id, name, options: { position: pos, edit: true }, childrens: [] }; if (parent != null) { this.elementFinder(this.treeStorage, parent ? parent.id : null); this.findingResults && this.findingResults.foundItem.childrens.push(createObj); } else { this.treeStorage.push(createObj); } /** @type {?} */ const eventEmit = { element: createObj, parent: parent ? this.findingResults.foundItem : 'root' }; this.onAddItem.next(eventEmit); this.clearAction(); } /* Delete element. It`s accepts 'id' for find item on tree. Emit onStartDeleteItem Subject before delete. Emit onFinishDeleteItem Subject after submit delete. Emit onCancelDeleteItem Subject after on cancel delete. */ /** * @param {?} id * @return {?} */ deleteItem(id) { this.elementFinder(this.treeStorage, id); /** @type {?} */ const eventEmit = { element: this.findingResults.foundItem, parent: this.findingResults.parentItem || 'root' }; this.onStartDeleteItem.next(eventEmit); /** @type {?} */ let text; if (this.findingResults.foundItem.name) { text = `Do you really want to delete '${this.findingResults.foundItem.name}'?`; } else { text = `Cancel creating a new item?`; } if (confirm(text)) { this.onFinishDeleteItem.next(eventEmit); /** @type {?} */ const i = this.findingResults.itemsList.indexOf(this.findingResults.foundItem); this.findingResults.itemsList.splice(i, 1); } else { this.onCancelDeleteItem.next(eventEmit); } this.clearAction(); } /* Trigger start rename element. It`s accepts 'name' and 'id' for find item on tree and set the name. Emit onRenameItem Subject. */ /** * @param {?} element * @return {?} */ startRenameItem(element) { this.elementFinder(this.treeStorage, element.id); // event emit /** @type {?} */ const eventEmit = { element: this.findingResults.foundItem, parent: this.findingResults.parentItem || 'root' }; this.onStartRenameItem.next(eventEmit); } /* Rename element. It`s accepts 'name' and 'id' for find item on tree and set the name. Emit onRenameItem Subject. */ /** * @param {?} name * @param {?} id * @return {?} */ finishRenameItem(name, id) { this.elementFinder(this.treeStorage, id); // code this.findingResults.foundItem.name = name; this.findingResults.foundItem.options.edit = false; // event emit /** @type {?} */ const eventEmit = { element: this.findingResults.foundItem, parent: this.findingResults.parentItem || 'root' }; this.onFinishRenameItem.next(eventEmit); this.clearAction(); } /* Event: ondragstart; On start dragging find element my id and set option currentlyDragging true. */ /** * @param {?} eventObj * @return {?} */ startDragging(eventObj) { this.switchDropButton(true, this.treeStorage); this.onDragStart.next(eventObj); } /* Event: ondrag; Trigger dragging element */ /** * @param {?} eventObj * @return {?} */ onDragProcess(eventObj) { this.onDrag.next(eventObj); } /* Event: ondragend; detect end of drag action */ /** * @param {?} eventObj * @return {?} */ dragEndAction(eventObj) { this.removeDestenationBorders(this.treeStorage); this.switchDropButton(false, this.treeStorage); this.onDragEnd.next(eventObj); } /* Event: enterdropzone; Entering drop zone for styling items. */ /** * @param {?} eventObj * @return {?} */ enterDropZone(eventObj) { this.onDragEnter.next(eventObj); } /* Event: dragover; Detect hover on dropable elements */ /** * @param {?} eventObj * @return {?} */ onDragOver(eventObj) { /** @type {?} */ const el = ((/** @type {?} */ (eventObj.target))); if (el && el.id !== this.isDragging.id) { /** @type {?} */ const elementHalfHeight = eventObj.event.toElement.offsetHeight / 2; if (eventObj.event.offsetY < elementHalfHeight) { el.options.destenationBottom = false; el.options.destenationTop = true; } else { el.options.destenationBottom = true; el.options.destenationTop = false; } this.onAllowDrop.next(eventObj); } } /* Event: leavedropzone; Leave drop zone for restyling items. */ /** * @param {?} eventObj * @return {?} */ leaveDropZone(eventObj) { this.removeDestenationBorders(this.treeStorage); this.onDragLeave.next(eventObj); } /* Event: ondrop; Its use where draggable item drop not on allowed for drop zone: set item option currentlyDragging false. return false. */ /** * @param {?} eventObj * @return {?} */ onDropItem(eventObj) { if (eventObj.target) { /** @type {?} */ const elementHalfHeight = eventObj.event.toElement.offsetHeight / 2; if (eventObj.event.offsetY < elementHalfHeight) { this.changeItemPosition(eventObj.target, 'up'); } else { this.changeItemPosition(eventObj.target, 'down'); } this.onDrop.next(eventObj); } else { /** @type {?} */ const dropZoneId = parseInt(eventObj.event.target.getAttribute('data-id'), null); this.elementFinder(this.treeStorage, this.isDragging.id); /** @type {?} */ const i = this.findingResults.itemsList.indexOf(this.findingResults.foundItem); /** @type {?} */ const copyItem = this.findingResults.itemsList.splice(i, 1)[0]; this.elementFinder(this.treeStorage, dropZoneId); this.findingResults.foundItem.childrens.push(copyItem); // this.sortTree(); eventObj.target = this.findingResults.foundItem; this.onDrop.next(eventObj); } this.removeDestenationBorders(this.treeStorage); this.switchDropButton(false, this.treeStorage); this.clearAction(); } /* change position of items need set direction before use */ /** * @private * @param {?} el * @param {?} direction * @return {?} */ changeItemPosition(el, direction) { setTimeout(() => { this.elementFinder(this.treeStorage, this.isDragging.id); /** @type {?} */ const i = this.findingResults.itemsList.indexOf(this.findingResults.foundItem); /** @type {?} */ const copyItem = this.findingResults.itemsList.splice(i, 1)[0]; // end test /** @type {?} */ const positionTarget = el.options.position; this.elementFinder(this.treeStorage, el.id); if (direction === 'up') { for (const items of this.findingResults.itemsList) { if (items.options.position >= positionTarget) { items.options.position = items.options.position + 1; copyItem.options.position = positionTarget; } } } else { for (const items of this.findingResults.itemsList) { if (items.options.position <= positionTarget) { items.options.position = items.options.position - 1; } } } copyItem.options.position = positionTarget; this.findingResults.itemsList.push(copyItem); this.sortTree(); }); } // get position of item /** * @param {?} item * @return {?} */ getItemPosition(item) { this.elementFinder(this.treeStorage, item.id); /** @type {?} */ let position = this.findingResults.itemsList.indexOf(this.findingResults.foundItem); return ++position; } // sort tree byposition /** * @return {?} */ sortTree() { this.sortElements(this.treeStorage); } // part of sortTree() /** * @private * @param {?} tree * @return {?} */ sortElements(tree) { tree.sort(this.compate); for (const item of tree) { if (item.childrens.length > 0) { this.sortElements(item.childrens); } } } // part of sortTree() /** * @private * @param {?} a * @param {?} b * @return {?} */ compate(a, b) { if (a.options.position < b.options.position) { return -1; } if (a.options.position > b.options.position) { return 1; } return 0; } // clear selectedElement && isDragging from element finder. /** * @return {?} */ clearAction() { this.findingResults = null; } /** * @private * @param {?} data * @return {?} */ removeDestenationBorders(data) { for (const item of data) { item.options.destenationBottom = false; item.options.destenationTop = false; if (item.childrens.length > 0) { this.removeDestenationBorders(item.childrens); } } } /** * @private * @param {?} bool * @param {?} data * @return {?} */ switchDropButton(bool, data) { for (const el of data) { el.options.showActionButtons = !bool; if (el.id !== this.isDragging.id) { el.options.showDropChildZone = bool; } if (el.childrens.length > 0) { this.switchDropButton(bool, el.childrens); } } } } NgxTreeService.decorators = [ { type: Injectable, args: [{ providedIn: 'root' },] }, ]; /** @nocollapse */ NgxTreeService.ctorParameters = () => []; /** @nocollapse */ NgxTreeService.ngInjectableDef = defineInjectable({ factory: function NgxTreeService_Factory() { return new NgxTreeService(); }, token: NgxTreeService, providedIn: "root" }); /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class NgxTreeParentComponent { /** * @param {?} treeService * @param {?} fb */ constructor(treeService, fb) { this.treeService = treeService; this.fb = fb; this.userConfig = { showActionButtons: true, showAddButtons: true, showRenameButtons: true, showDeleteButtons: true, showRootActionButtons: true, enableExpandButtons: true, enableDragging: true, rootTitle: 'Root', options: { edit: false }, validationText: 'Enter valid name', minCharacterLength: 1, setItemsAsLinks: false, setFontSize: 16, setIconSize: 14 }; this.ondragstart = new EventEmitter(); this.ondragenter = new EventEmitter(); this.ondragleave = new EventEmitter(); this.ondrop = new EventEmitter(); this.onallowdrop = new EventEmitter(); this.ondragend = new EventEmitter(); this.onadditem = new EventEmitter(); this.onStartRenameItem = new EventEmitter(); this.onFinishRenameItem = new EventEmitter(); this.onStartDeleteItem = new EventEmitter(); this.onFinishDeleteItem = new EventEmitter(); this.onCancelDeleteItem = new EventEmitter(); this.enableSubscribers(); this.createForm(); } /** * @param {?} config * @return {?} */ set config(config) { // seal config Object.seal(this.userConfig); try { // if config it`s pass this.setConfig(config); this.treeService.config.next(this.userConfig); } catch (error) { // if config invalid console.log('Config is invalid! Default configuragion will be appeared'); this.treeService.config.next(this.treeService.defaulConfig); } } /** * @param {?} item * @return {?} */ set treeData(item) { // get user tree data this.getTreeData(item); } // set user config /** * @param {?} config * @return {?} */ setConfig(config) { for (const key of Object.keys(config)) { this.setValue(key, config); } this.renameForm.patchValue({ name: this.userConfig.rootTitle }); } // set value to keys of config /** * @param {?} item * @param {?} config * @return {?} */ setValue(item, config) { this.userConfig[item] = config[item]; } // subscribe to all events and emit them to user. /** * @return {?} */ enableSubscribers() { this.treeService.onDrop.subscribe((event) => { this.ondrop.emit(event); }); this.treeService.onDragStart.subscribe((event) => { this.ondragstart.emit(event); }); this.treeService.onAllowDrop.subscribe((event) => { this.onallowdrop.emit(event); }); this.treeService.onDragEnd.subscribe((event) => { this.ondragend.emit(event); }); this.treeService.onAddItem.subscribe((event) => { this.onadditem.emit(event); }); this.treeService.onStartRenameItem.subscribe((event) => { this.onStartRenameItem.emit(event); }); this.treeService.onFinishRenameItem.subscribe((event) => { this.onFinishRenameItem.emit(event); }); this.treeService.onStartDeleteItem.subscribe((event) => { this.onStartDeleteItem.emit(event); }); this.treeService.onFinishDeleteItem.subscribe((event) => { this.onFinishDeleteItem.emit(event); }); this.treeService.onCancelDeleteItem.subscribe((event) => { this.onCancelDeleteItem.emit(event); }); this.treeService.onDragEnter.subscribe((event) => { this.ondragenter.emit(event); }); this.treeService.onDragLeave.subscribe((event) => { this.ondragleave.emit(event); }); } // get tree data from treeService. /** * @param {?} userTree * @return {?} */ getTreeData(userTree) { this.treeService.getLocalData(userTree).subscribe((tree) => { this.treeView = tree; setTimeout(() => { this.treeService.sortTree(); }); }, (error) => { console.log(error); }); } // create edit form /** * @return {?} */ createForm() { this.renameForm = this.fb.group({ name: [this.userConfig.rootTitle || '', [ Validators.required, Validators.minLength(this.userConfig.minCharacterLength) ]], }); } /** * @return {?} */ enableRootRenameMode() { this.userConfig.options.edit = true; } /** * @param {?} name * @return {?} */ submitAdd(name) { /** @type {?} */ const d = `${new Date().getFullYear()}${new Date().getDay()}${new Date().getTime()}`; /** @type {?} */ const elemId = parseInt(d, null); this.treeService.addNewItem(elemId, name, null); } /** * @return {?} */ submitRootRename() { if (this.renameForm.valid) { this.showError = false; this.userConfig.rootTitle = this.renameForm.value.name; this.userConfig.options.edit = false; } else { this.showError = true; } } /** * @return {?} */ ngAfterViewInit() { } } NgxTreeParentComponent.decorators = [ { type: Component, args: [{ selector: 'lib-ngx-tree-component', template: `<div id='threeWrapper' *ngIf="treeView" [style.font-size.px]='userConfig.setFontSize'> <div class='root-title d-inline-flex pos-relative' *ngIf="!userConfig.options.edit;else onEdit"> <div class='root-text'> {{userConfig.rootTitle}} </div> <div class='d-flex buttons-bar' *ngIf="userConfig.showActionButtons && userConfig.showRootActionButtons"> <div class='d-flex'> <button class="tree-btn add-btn" *ngIf="userConfig.showAddButtons" (click)="submitAdd(null)"> <fa-icon icon="plus" [style.font-size.px]='userConfig.setIconSize'></fa-icon> </button> </div> <div class='d-flex'> <button class="tree-btn edit-btn" *ngIf="userConfig.showRenameButtons" (click)="enableRootRenameMode()"> <fa-icon icon="edit" [style.font-size.px]='userConfig.setIconSize'></fa-icon> </button> </div> </div> </div> <ng-template #onEdit> <div class='d-inline-flex'> <form [formGroup]="renameForm" class='d-flex' (submit)='submitRootRename()'> <input type="text" class='input-rename' formControlName="name" libAutoFocus="true" [style.font-size.px]='userConfig.setFontSize'> </form> <div class='d-flex'> <button class='tree-btn submit-btn' (click)='submitRootRename()'> <fa-icon icon="check" [style.font-size.px]='userConfig.setIconSize'></fa-icon> </button> <div class='error-edit-wrap' *ngIf="showError"> {{userConfig.validationText}} </div> </div> </div> </ng-template> <div class='tree-child'> <div class="tree-content-main"> <lib-ngx-tree-children [setItem]="clild" *ngFor='let clild of treeView'></lib-ngx-tree-children> </div> </div> </div>` },] }, ]; /** @nocollapse */ NgxTreeParentComponent.ctorParameters = () => [ { type: NgxTreeService }, { type: FormBuilder } ]; NgxTreeParentComponent.propDecorators = { ondragstart: [{ type: Output }], ondragenter: [{ type: Output }], ondragleave: [{ type: Output }], ondrop: [{ type: Output }], onallowdrop: [{ type: Output }], ondragend: [{ type: Output }], onadditem: [{ type: Output }], onStartRenameItem: [{ type: Output }], onFinishRenameItem: [{ type: Output }], onStartDeleteItem: [{ type: Output }], onFinishDeleteItem: [{ type: Output }], onCancelDeleteItem: [{ type: Output }], config: [{ type: Input }], treeData: [{ type: Input }] }; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class NgxTreeChildrenComponent { /** * @param {?} treeService * @param {?} fb */ constructor(treeService, fb) { this.treeService = treeService; this.fb = fb; } // get item from parent component /** * @param {?} data * @return {?} */ set setItem(data) { this.element = data; this.itemOptions = { href: '#', hidden: false, hideChildrens: false, position: this.treeService.getItemPosition(this.element), draggable: true, edit: false, showActionButtons: true, currentlyDragging: false, destenationTop: false, destenationBottom: false, disabled: false, showExpandButton: true, showDeleteButton: true }; if (this.element.options) { this.setOptions(this.element.options); this.element.options = this.itemOptions; } else { this.element.options = this.itemOptions; } // enable subscribers this.enableSubscribers(); // create form this.createForm(); } // enable subscribe to config /** * @return {?} */ enableSubscribers() { this.treeService.config.subscribe((config) => { if (config !== null) { this.config = config; } else { this.config = this.treeService.defaulConfig; } if (this.element.options.draggable) { this.element.options.draggable = this.config.enableDragging; } }); } // set options to item /** * @param {?} options * @return {?} */ setOptions(options) { for (const key of Object.keys(options)) { this.setValue(key, options); } } // set value to options keys /** * @param {?} item * @param {?} options * @return {?} */ setValue(item, options) { this.itemOptions[item] = options[item]; } // create edit form /** * @return {?} */ createForm() { this.renameForm = this.fb.group({ name: [this.element.name || '', [ Validators.required, Validators.minLength(this.config.minCharacterLength) ]], }); } /* Event: onStartRenameItem; Enable rename mode in element Call onStartRenameItem() from tree service. */ /** * @param {?} element * @return {?} */ enableRenameMode(element) { element.options.edit = true; this.treeService.startRenameItem(element); } /* Event: onadditem; Generate id by new Date() by 'full year + day + time'. Call addNewItem() from tree service. */ /** * @param {?} name * @param {?} item * @return {?} */ submitAdd(name, item) { /** @type {?} */ const d = `${new Date().getFullYear()}${new Date().getDay()}${new Date().getTime()}`; /** @type {?} */ const elemId = parseInt(d, null); this.treeService.addNewItem(elemId, name, item); this.element.options.hideChildrens = false; } /* Event: onFinishRenameItem; Check is form valid. Call addNewItem() from tree service. */ /** * @param {?} item * @return {?} */ submitRename(item) { if (this.renameForm.valid) { this.showError = false; this.treeService.finishRenameItem(this.renameForm.value.name, item.id); this.element.options.edit = false; } else { this.showError = true; } } /* Events: onStartDeleteItem, onFinishDeleteItem, onCancelDeleteItem. Check is item edit, then if name empty delete item. Call deleteItem() from tree service. */ /** * @param {?} item * @return {?} */ onSubmitDelete(item) { if (!this.element.options.edit) { this.treeService.deleteItem(item.id); } else { if (item.name === null) { this.treeService.deleteItem(item.id); } else { this.element.options.edit = false; } } } // after view init /** * @return {?} */ ngAfterViewInit() { } } NgxTreeChildrenComponent.decorators = [ { type: Component, args: [{ selector: 'lib-ngx-tree-children', template: `<div class='tree-child' id={{element.id}} libDragElement [draggableValue]='element.options.draggable' [item]='element' [ngClass]="{disabled : element.options.disabled}"> <div *ngIf="element && element.options" class='d-flex'> <div *ngIf='config' [ngClass]="{hidden : element.options.hidden}"> <div class='tree-title d-inline-flex pos-relative' [ngClass]="{destenationTop : element.options.destenationTop, destenationBottom: element.options.destenationBottom}" *ngIf="!element.options.edit;else onEdit"> <div *ngIf="!config.setItemsAsLinks; else link" [ngClass]="{addOpacity : element.options.currentlyDragging}" libDropElement [item]='element' class='draggable-item'> {{element.name}} </div> <ng-template #link> <div [ngClass]="{addOpacity : element.options.currentlyDragging}" libDropElement [item]='element' class='draggable-item'> <a [href]="element.options.href" class='tree-link'>{{element.name}}</a> </div> </ng-template> <div class='d-flex buttons-bar' *ngIf="config.showActionButtons && element.options.showActionButtons && !element.options.disabled"> <div class='d-flex'> <button class="tree-btn add-btn" *ngIf="config.showAddButtons" (click)="submitAdd(null, element)"> <fa-icon icon="plus" [style.font-size.px]='config.setIconSize'></fa-icon> </button> </div> <div class='d-flex'> <button class="tree-btn edit-btn" *ngIf="config.showRenameButtons" (click)="enableRenameMode(element)"> <fa-icon icon="edit" [style.font-size.px]='config.setIconSize'></fa-icon> </button> </div> <div class='d-flex'> <button class="tree-btn delete-btn" *ngIf="config.showDeleteButtons && element.options.showDeleteButton" (click)="onSubmitDelete( element )"> <fa-icon icon="times" [style.font-size.px]='config.setIconSize'></fa-icon> </button> </div> </div> <div class='child-drop-place' [attr.data-id]='element.id' libDropElement *ngIf='element.options.showDropChildZone && !element.options.disabled'> <fa-icon icon="arrow-down" [style.font-size.px]='config.setIconSize'></fa-icon> </div> <div class='show-hide-switch' *ngIf="config.enableExpandButtons && element.options.showExpandButton && element.childrens.length > 0 && !element.options.disabled"> <div *ngIf="element.options.hideChildrens; else visible"> <button class='tree-btn show-btn' (click)='element.options.hideChildrens = false'> <fa-icon icon="plus" [style.font-size.px]='config.setIconSize'></fa-icon> </button> </div> <ng-template #visible> <button class='tree-btn hide-btn' (click)='element.options.hideChildrens = true'> <fa-icon icon="minus" [style.font-size.px]='config.setIconSize'></fa-icon> </button> </ng-template> </div> </div> <ng-template #onEdit> <div class='tree-title d-inline-flex'> <form [formGroup]="renameForm" class='d-flex' (submit)='submitRename(element)'> <input type="text" class='input-rename' formControlName="name" libAutoFocus="true" [style.font-size.px]='config.setFontSize'> </form> <div class='d-flex'> <button class='tree-btn submit-btn' (click)='submitRename(element)'> <fa-icon icon="check" [style.font-size.px]='config.setIconSize'></fa-icon> </button> <button class='tree-btn delete-btn' (click)='onSubmitDelete(element)'> <fa-icon icon="times" [style.font-size.px]='config.setIconSize'></fa-icon> </button> <div class='error-edit-wrap' *ngIf="showError"> {{config.validationText}} </div> </div> </div> </ng-template> <div class="tree-content" *ngIf="element.childrens && !element.options.hideChildrens"> <lib-ngx-tree-children [setItem]="child" *ngFor='let child of element.childrens'></lib-ngx-tree-children> </div> </div> </div> </div> ` },] }, ]; /** @nocollapse */ NgxTreeChildrenComponent.ctorParameters = () => [ { type: NgxTreeService }, { type: FormBuilder } ]; NgxTreeChildrenComponent.propDecorators = { setItem: [{ type: Input }] }; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class AutoFocusDirective { /** * @param {?} el */ constructor(el) { this.el = el; this.focus = true; } /** * @return {?} */ ngOnInit() { if (this.focus) { window.setTimeout(() => { this.el.nativeElement.focus(); }); } } /** * @param {?} condition * @return {?} */ set autofocus(condition) { this.focus = condition !== false; } } AutoFocusDirective.decorators = [ { type: Directive, args: [{ selector: '[libAutoFocus]' },] }, ]; /** @nocollapse */ AutoFocusDirective.ctorParameters = () => [ { type: ElementRef } ]; AutoFocusDirective.propDecorators = { autofocus: [{ type: Input }] }; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class DragElementsDirective { /** * @param {?} el * @param {?} treeService */ constructor(el, treeService) { this.el = el; this.treeService = treeService; } /** * @return {?} */ get draggable() { return this.draggableValue; } /* Event: ondragstart; Set item as dragging and call startDragging() from tree service. Emit OnDragStart on tree service. */ /** * @param {?} event * @return {?} */ onDragStart(event) { /** @type {?} */ const eventObj = { event, target: this.item }; this.treeService.isDragging = this.item; this.treeService.lastExpandState = this.item.options.hideChildrens; this.item.options.hideChildrens = true; this.item.options.currentlyDragging = true; // call service func this.treeService.startDragging(eventObj); event.stopPropagation(); } /* Event: onDrag; trigger drag items and call onDragProcess() from tree service. Emit OnDrag on tree service. */ /** * @param {?} event * @return {?} */ onDrag(event) { /** @type {?} */ const eventObj = { event, target: this.item }; this.treeService.onDragProcess(eventObj); } /* Event: ondragend; Call dragEndAction() from tree service. Emit OnDragEnd on tree service. */ /** * @param {?} event * @return {?} */ onDragEnd(event) { /** @type {?} */ const eventObj = { event, target: this.item }; this.item.options.hideChildrens = this.treeService.lastExpandState; this.item.options.currentlyDragging = false; this.treeService.dragEndAction(eventObj); event.stopPropagation(); } } DragElementsDirective.decorators = [ { type: Directive, args: [{ selector: '[libDragElement]' },] }, ]; /** @nocollapse */ DragElementsDirective.ctorParameters = () => [ { type: ElementRef }, { type: NgxTreeService } ]; DragElementsDirective.propDecorators = { item: [{ type: Input }], draggableValue: [{ type: Input }], draggable: [{ type: HostBinding, args: ['draggable',] }], onDragStart: [{ type: HostListener, args: ['dragstart', ['$event'],] }], onDrag: [{ type: HostListener, args: ['drag', ['$event'],] }], onDragEnd: [{ type: HostListener, args: ['dragend', ['$event'],] }] }; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class DropElementsDirective { /** * @param {?} el * @param {?} treeService */ constructor(el, treeService) { this.el = el; this.treeService = treeService; this.drop = new EventEmitter(); } /* Event: onallowdrop; Call onDragOver() from tree service. Emit onAllowDrop on tree service. */ /** * @param {?} event * @return {?} */ onDragOver(event) { /** @type {?} */ const eventObj = { event, target: this.item }; this.treeService.onDragOver(eventObj); event.preventDefault(); } /* Event: ondrop; Call onDropItem() from tree service. Emit OnDrop on tree service. */ /** * @param {?} event * @return {?} */ onDrop(event) { /** @type {?} */ const dragItem = this.treeService.isDragging; /** @type {?} */ const eventObj = { event, target: this.item }; dragItem.options.hideChildrens = this.treeService.lastExpandState; dragItem.options.currentlyDragging = false; if (dragItem !== eventObj.target) { this.treeService.onDropItem(eventObj); } event.preventDefault(); } /*s Event: ondragenter; Detect event where draggable element enter in drop zone. Call enterDropZone() from tree service. Emit onDragEnter. */ /** * @param {?} event * @return {?} */ onDragEnter(event) { /** @type {?} */ const eventObj = { event, target: this.item }; this.treeService.enterDropZone(eventObj); } /* Event: ondragleave; Detect event where draggable element leave drop zone. Call leaveDropZone() from tree service. Emit onDragLeave. */ /** * @param {?} event * @return {?} */ onDragLeave(event) { // emit events /** @type {?} */ const eventObj = { event, target: this.item }; // code this.treeService.leaveDropZone(eventObj); } } DropElementsDirective.decorators = [ { type: Directive, args: [{ selector: '[libDropElement]' },] }, ]; /** @nocollapse */ DropElementsDirective.ctorParameters = () => [ { type: ElementRef }, { type: NgxTreeService } ]; DropElementsDirective.propDecorators = { item: [{ type: Input }], drop: [{ type: Output }], onDragOver: [{ type: HostListener, args: ['dragover', ['$event'],] }], onDrop: [{ type: HostListener, args: ['drop', ['$event'],] }], onDragEnter: [{ type: HostListener, args: ['dragenter', ['$event'],] }], onDragLeave: [{ type: HostListener, args: ['dragleave', ['$event'],] }] }; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ library.add(faCoffee, faPlus, faEdit, faMinus, faTimes, faCheck, faArrowDown); class NgxTreeDndModule { } NgxTreeDndModule.decorators = [ { type: NgModule, args: [{ imports: [ CommonModule, ReactiveFormsModule, FontAwesomeModule ], declarations: [ AutoFocusDirective, DragElementsDirective, DropElementsDirective, NgxTreeParentComponent, NgxTreeChildrenComponent ], exports: [ AutoFocusDirective, DragElementsDirective, DropElementsDirective, NgxTreeParentComponent, NgxTreeChildrenComponent ] },] }, ]; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ export { NgxTreeService, NgxTreeParentComponent, NgxTreeChildrenComponent, AutoFocusDirective, DragElementsDirective, DropElementsDirective, NgxTreeDndModule }; //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmd4LXRyZWUtZG5kLmpzLm1hcCIsInNvdXJjZXMiOlsibmc6Ly9uZ3gtdHJlZS1kbmQvbGliL25neC10cmVlLWRuZC5zZXJ2aWNlLnRzIiwibmc6Ly9uZ3gtdHJlZS1kbmQvbGliL25neC10cmVlLWRuZC1wYXJlbnQvbmd4LXRyZWUtZG5kLXBhcmVudC5jb21wb25lbnQudHMiLCJuZzovL25neC10cmVlLWRuZC9saWIvbmd4LXRyZWUtZG5kLWNoaWxkcmVuL25neC10cmVlLWRuZC1jaGlsZHJlbi5jb21wb25lbnQudHMiLCJuZzovL25neC10cmVlLWRuZC9saWIvZGlyZWN0aXZlcy9uZ3gtdHJlZS1kbmQtYXV0b2ZvY3VzLmRpcmVjdGl2ZS50cyIsIm5nOi8vbmd4LXRyZWUtZG5kL2xpYi9kaXJlY3RpdmVzL25neC10cmVlLWRuZC1kcmFnLmRpcmVjdGl2ZS50cyIsIm5nOi8vbmd4LXRyZWUtZG5kL2xpYi9kaXJlY3RpdmVzL25neC10cmVlLWRuZC1kcm9wLmRpcmVjdGl2ZS50cyIsIm5nOi8vbmd4LXRyZWUtZG5kL2xpYi9uZ3gtdHJlZS1kbmQubW9kdWxlLnRzIl0sInNvdXJjZXNDb250ZW50IjpbIi8qXG4gQ29weXJpZ2h0IChDKSAyMDE4IFlhcm9zbGF2IEtpa290XG4gVGhpcyBwcm9qZWN0IGlzIGxpY2Vuc2VkIHVuZGVyIHRoZSB0ZXJtcyBvZiB0aGUgTUlUIGxpY2Vuc2UuXG4gaHR0cHM6Ly9naXRodWIuY29tL1ppY3JhZWwvbmd4LXRyZWUtZG5kXG4gKi9cbmltcG9ydCB7IEluamVjdGFibGUgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IFN1YmplY3QsIEJlaGF2aW9yU3ViamVjdCwgT2JzZXJ2YWJsZSB9IGZyb20gJ3J4anMnO1xuaW1wb3J0IHsgVHJlZU1vZGVsLCBUcmVlQ29uZmlnLCBGaW5kaW5nUmVzdWx0cyB9IGZyb20gJy4vbW9kZWxzL3RyZWUtdmlldy5tb2RlbCc7XG5cbkBJbmplY3RhYmxlKHtcbiAgcHJvdmlkZWRJbjogJ3Jvb3QnXG59KVxuZXhwb3J0IGNsYXNzIE5neFRyZWVTZXJ2aWNlIHtcbiAgdHJlZVN0b3JhZ2U6IFRyZWVNb2RlbFtdID0gW107XG4gIHByaXZhdGUgZmluZGluZ1Jlc3VsdHM6IEZpbmRpbmdSZXN1bHRzO1xuICAvLyBsaXN0T2ZTZWxlY3RlZEVsZW1lbnQ6IFRyZWVNb2RlbFtdO1xuICAvLyBwYXJlbnRPZlNlbGVjdGVkOiBUcmVlTW9kZWw7XG4gIC8vIHByaXZhdGUgc2VsZWN0ZWRFbGVtZW50OiBUcmVlTW9kZWw7XG4gIGlzRHJhZ2dpbmc6IFRyZWVNb2RlbDtcbiAgZHJhZ0V2ZW50OiB7fTtcbiAgZGlyZWN0aW9uOiBzdHJpbmc7XG4gIGxhc3RFeHBhbmRTdGF0ZTogYm9vbGVhbjtcbiAgb25EcmFnU3RhcnQgPSBuZXcgU3ViamVjdDxhbnk+KCk7XG4gIG9uRHJhZ0VudGVyID0gbmV3IFN1YmplY3Q8YW55PigpO1xuICBvbkRyYWdMZWF2ZSA9IG5ldyBTdWJqZWN0PGFueT4oKTtcbiAgb25Ecm9wID0gbmV3IFN1YmplY3Q8YW55PigpO1xuICBvbkRyYWcgPSBuZXcgU3ViamVjdDxhbnk+KCk7XG4gIG9uQWxsb3dEcm9wID0gbmV3IFN1YmplY3Q8YW55PigpO1xuICBvbkRyYWdFbmQgPSBuZXcgU3ViamVjdDxhbnk+KCk7XG4gIG9uQWRkSXRlbSA9IG5ldyBTdWJqZWN0PGFueT4oKTtcbiAgb25SZW5hbWVJdGVtID0gbmV3IFN1YmplY3Q8YW55PigpO1xuICBvblN0YXJ0UmVuYW1lSXRlbSA9IG5ldyBTdWJqZWN0PGFueT4oKTtcbiAgb25GaW5pc2hSZW5hbWVJdGVtID0gbmV3IFN1YmplY3Q8YW55PigpO1xuICBvblN0YXJ0RGVsZXRlSXRlbSA9IG5ldyBTdWJqZWN0PGFueT4oKTtcbiAgb25GaW5pc2hEZWxldGVJdGVtID0gbmV3IFN1YmplY3Q8YW55PigpO1xuICBvbkNhbmNlbERlbGV0ZUl0ZW0gPSBuZXcgU3ViamVjdDxhbnk+KCk7XG4gIGNvbmZpZyA9IG5ldyBCZWhhdmlvclN1YmplY3Q8YW55PihudWxsKTtcbiAgZGVmYXVsQ29uZmlnOiBUcmVlQ29uZmlnO1xuXG4gIGNvbnN0cnVjdG9yKCkge1xuICAgIC8vIHNldCBkZWZhdWx0IGNvbmZpZ1xuICAgIHRoaXMuZGVmYXVsQ29uZmlnID0ge1xuICAgICAgc2hvd0FjdGlvbkJ1dHRvbnM6IHRydWUsXG4gICAgICBzaG93QWRkQnV0dG9uczogdHJ1ZSxcbiAgICAgIHNob3dSZW5hbWVCdXR0b25zOiB0cnVlLFxuICAgICAgc2hvd0RlbGV0ZUJ1dHRvbnM6IHRydWUsXG4gICAgICBzaG93Um9vdEFjdGlvbkJ1dHRvbnM6IHRydWUsXG4gICAgICBlbmFibGVFeHBhbmRCdXR0b25zOiB0cnVlLFxuICAgICAgZW5hYmxlRHJhZ2dpbmc6IHRydWUsXG4gICAgICByb290VGl0bGU6ICdSb290JyxcbiAgICAgIHZhbGlkYXRpb25UZXh0OiAnRW50ZXIgdmFsaWQgbmFtZScsXG4gICAgICBtaW5DaGFyYWN0ZXJMZW5ndGg6IDEsXG4gICAgICBzZXRJdGVtc0FzTGlua3M6IGZhbHNlLFxuICAgICAgc2V0Rm9udFNpemU6IDE2LFxuICAgICAgc2V0SWNvblNpemU6IDE0XG4gICAgfTtcbiAgfVxuXG4gIC8qXG4gICAgZ2V0IGRhdGEgYW5kIHNldCBpdCBvbiBvYnNlcnZhYmxlLlxuICAgIGlmIGRhdGEgPSBudWxsIHNldCBlbXB0eSBkYXRhIGFycmF5XG4gICovXG4gIHB1YmxpYyBnZXRMb2NhbERhdGEoaXRlbSkge1xuICAgIGNvbnN0IGRhdGEgPSBuZXcgT2JzZXJ2YWJsZShvYnNlcnZlciA9PiB7XG4gICAgdGhpcy50cmVlU3RvcmFnZSA9IGl0ZW07XG4gICAgICBpZiAoIHRoaXMudHJlZVN0b3JhZ2UgJiYgdGhpcy50cmVlU3RvcmFnZSAhPT0gbnVsbCApIHtcbiAgICAgICAgb2JzZXJ2ZXIubmV4dCh0aGlzLnRyZWVTdG9yYWdlKTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHRoaXMudHJlZVN0b3JhZ2UgID0gSlNPTi5wYXJzZSgnW10nKTtcbiAgICAgICAgb2JzZXJ2ZXIubmV4dCh0aGlzLnRyZWVTdG9yYWdlKTtcbiAgICAgIH1cbiAgICB9KTtcbiAgICByZXR1cm4gZGF0YTtcbiAgfVxuXG4gIC8qXG4gICBFbGVtZW50IGZpbmRlciwgaXRgcyBmaW5kIGVsZW1lbnQgYnkgaWQgaW4gdHJlZS5cbiAgIFJldHVybnM6IGZpbmRlZCBlbGVtZW50LCBwYXJlbnQgYXJyYXkuXG4gICBXYXRjaCBvdXQsIHRoaXMgaXMgcmVjdXJzaXZlIG1ldGhvZC5cbiAgKi9cbiAgIHByaXZhdGUgZWxlbWVudEZpbmRlcihsaXN0LCBpZCwgcGFyZW50Pykge1xuICAgICBmb3IgKGNvbnN0IGl0ZW0gb2YgbGlzdCkge1xuICAgICAgIGlmIChpdGVtLmlkID09PSBpZCkge1xuICAgICAgICAgdGhpcy5maW5kaW5nUmVzdWx0cyA9IHtcbiAgICAgICAgICAgZm91bmRJdGVtOiBpdGVtLFxuICAgICAgICAgICBpdGVtc0xpc3Q6IGxpc3RcbiAgICAgICAgIH1cbiAgICAgICAgIGlmIChwYXJlbnQpIHtcbiAgICAgICAgICAgdGhpcy5maW5kaW5nUmVzdWx0cy5wYXJlbnRJdGVtID0gcGFyZW50O1xuICAgICAgICAgfVxuICAgICAgICAgYnJlYWs7XG4gICAgICAgfSBlbHNlIHtcbiAgICAgICAgIGlmIChpdGVtLmNoaWxkcmVucy5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgIHRoaXMuZWxlbWVudEZpbmRlcihpdGVtLmNoaWxkcmVucywgaWQsIGl0ZW0pO1xuICAgICAgICAgfVxuICAgICAgIH1cbiAgICAgfVxuXG4gIH1cblxuXG4gICAvKlxuICAgQWRkIG5ldyBpdGVtIHRvIHRyZWUuXG4gICBJdHMgYWNjZXB0cyAndHlwZScgZm9yIGRldGVjdCBhZGQgcm9vdCBlbGVtZW50IG9yIGNoaWxkcmVuLlxuICAgRW1pdCBvbkFkZEl0ZW0gU3ViamVjdC5cbiAgKi9cbiAgcHVibGljIGFkZE5ld0l0ZW0oaWQsIG5hbWUsIHBhcmVudD8pIHtcbiAgICBsZXQgcG9zID0gMTtcbiAgICBpZiAocGFyZW50ICYmIHBhcmVudC5jaGlsZHJlbnMubGVuZ3RoICE9PSAwKSB7XG4gICAgICBjb25zdCBwYXJlbnRQcmV2Q2hpbGRyZW4gPSBwYXJlbnQuY2hpbGRyZW5zLmxlbmd0aCAtIDE7XG4gICAgICBjb25zdCBuZXdJdGVtUG9zaXRpb24gPSBwYXJlbnQuY2hpbGRyZW5zW3BhcmVudFByZXZDaGlsZHJlbl0ub3B0aW9ucy5wb3NpdGlvbiArIDE7XG4gICAgICBwb3MgPSBuZXdJdGVtUG9zaXRpb247XG4gICAgfVxuICAgIGNvbnN0IGNyZWF0ZU9iajogVHJlZU1vZGVsID0ge1xuICAgICAgaWQsXG4gICAgICBuYW1lLFxuICAgICAgb3B0aW9uczogIHtcbiAgICAgICAgcG9zaXRpb246IHBvcyxcbiAgICAgICAgZWRpdDogdHJ1ZVxuICAgICAgfSxcbiAgICAgIGNoaWxkcmVuczogW11cbiAgICB9O1xuICAgIFxuICAgIGlmKHBhcmVudCAhPSBudWxsKSB7XG4gICAgICB0aGlzLmVsZW1lbnRGaW5kZXIodGhpcy50cmVlU3RvcmFnZSwgcGFyZW50ID8gcGFyZW50LmlkIDogbnVsbCk7XG4gICAgICB0aGlzLmZpbmRpbmdSZXN1bHRzICYmIHRoaXMuZmluZGluZ1Jlc3VsdHMuZm91bmRJdGVtLmNoaWxkcmVucy5wdXNoKGNyZWF0ZU9iaik7XG4gICAgfVxuICAgIGVsc2V7XG4gICAgICB0aGlzLnRyZWVTdG9yYWdlLnB1c2goY3JlYXRlT2JqKTtcbiAgICB9XG4gICAgXG4gICAgY29uc3QgZXZlbnRFbWl0ID0ge1xuICAgICAgZWxlbWVudDogY3JlYXRlT2JqLFxuICAgICAgcGFyZW50OiBwYXJlbnQgPyB0aGlzLmZpbmRpbmdSZXN1bHRzLmZvdW5kSXRlbSA6ICdyb290J1xuICAgIH07XG5cbiAgICB0aGlzLm9uQWRkSXRlbS5uZXh0KGV2ZW50RW1pdCk7XG4gICAgdGhpcy5jbGVhckFjdGlvbigpO1xuICB9XG5cbiAgLypcbiAgIERlbGV0ZSBlbGVtZW50LlxuICAgSXRgcyBhY2NlcHRzICdpZCcgZm9yIGZpbmQgaXRlbSBvbiB0cmVlLlxuICAgRW1pdCBvblN0YXJ0RGVsZXRlSXRlbSBTdWJqZWN0IGJlZm9yZSBkZWxldGUuXG4gICBFbWl0IG9uRmluaXNoRGVsZXRlSXRlbSBTdWJqZWN0IGFmdGVyIHN1Ym1pdCBkZWxldGUuXG4gICBFbWl0IG9uQ2FuY2VsRGVsZXRlSXRlbSBTdWJqZWN0IGFmdGVyIG9uIGNhbmNlbCBkZWxldGUuXG4gICovXG4gIHB1YmxpYyBkZWxldGVJdGVtKGlkKSB7XG4gICAgdGhpcy5lbGVtZW50RmluZGVyKHRoaXMudHJlZVN0b3JhZ2UsIGlkKTtcbiAgICBjb25zdCBldmVudEVtaXQgPSB7XG4gICAgICBlbGVtZW50OiB0aGlzLmZpbmRpbmdSZXN1bHRzLmZvdW5kSXRlbSxcbiAgICAgIHBhcmVudDogdGhpcy5maW5kaW5nUmVzdWx0cy5wYXJlbnRJdGVtIHx8ICdyb290J1xuICAgIH07XG4gICAgdGhpcy5vblN0YXJ0RGVsZXRlSXRlbS5uZXh0KGV2ZW50RW1pdCk7XG4gICAgbGV0IHRleHQ6IHN0cmluZztcbiAgICBpZiggdGhpcy5maW5kaW5nUmVzdWx0cy5mb3VuZEl0ZW0ubmFtZSApIHtcbiAgICAgIHRleHQgPSBgRG8geW91IHJlYWxseSB3YW50IHRvIGRlbGV0ZSAnJHt0aGlzLmZpbmRpbmdSZXN1bHRzLmZvdW5kSXRlbS5uYW1lfSc/YDtcbiAgICB9IGVsc2Uge1xuICAgICAgdGV4dCA9IGBDYW5jZWwgY3JlYXRpbmcgYSBuZXcgaXRlbT9gO1xuICAgIH1cbiAgICBpZihjb25maXJtKHRleHQpKSB7XG4gICAgICB0aGlzLm9uRmluaXNoRGVsZXRlSXRlbS5uZXh0KGV2ZW50RW1pdCk7XG4gICAgICBjb25zdCBpID0gdGhpcy5maW5kaW5nUmVzdWx0cy5pdGVtc0xpc3QuaW5kZXhPZih0aGlzLmZpbmRpbmdSZXN1bHRzLmZvdW5kSXRlbSk7XG4gICAgICB0aGlzLmZpbmRpbmdSZXN1bHRzLml0ZW1zTGlzdC5zcGxpY2UoaSwgMSk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHRoaXMub25DYW5jZWxEZWxldGVJdGVtLm5leHQoZXZlbnRFbWl0KTtcbiAgICB9XG4gICAgdGhpcy5jbGVhckFjdGlvbigpO1xuICB9XG5cbiAgLypcbiAgIFRyaWdnZXIgc3RhcnQgcmVuYW1lIGVsZW1lbnQuXG4gICBJdGBzIGFjY2VwdHMgJ25hbWUnIGFuZCAnaWQnIGZvciBmaW5kIGl0ZW0gb24gdHJlZSBhbmQgc2V0IHRoZSBuYW1lLlxuICAgRW1pdCBvblJlbmFtZUl0ZW0gU3ViamVjdC5cbiAgKi9cbiBwdWJsaWMgc3RhcnRSZW5hbWVJdGVtKGVsZW1lbnQpIHtcbiAgICB0aGlzLmVsZW1lbnRGaW5kZXIodGhpcy50cmVlU3RvcmFnZSwgZWxlbWVudC5pZCk7XG4gICAgLy8gZXZlbnQgZW1pdFxuICAgIGNvbnN0IGV2ZW50RW1pdCA9IHtcbiAgICAgIGVsZW1lbnQ6IHRoaXMuZmluZGluZ1Jlc3VsdHMuZm91bmRJdGVtLFxuICAgICAgcGFyZW50OiB0aGlzLmZpbmRpbmdSZXN1bHRzLnBhcmVudEl0ZW0gfHwgJ3Jvb3QnXG4gICAgfTtcbiAgICB0aGlzLm9uU3RhcnRSZW5hbWVJdGVtLm5leHQoZXZlbnRFbWl0KTtcbiAgfVxuXG4gIC8qXG4gICBSZW5hbWUgZWxlbWVudC5cbiAgIEl0YHMgYWNjZXB0cyAnbmFtZScgYW5kICdpZCcgZm9yIGZpbmQgaXRlbSBvbiB0cmVlIGFuZCBzZXQgdGhlIG5hbWUuXG4gICBFbWl0IG9uUmVuYW1lSXRlbSBTdWJqZWN0LlxuICAqL1xuICBwdWJsaWMgZmluaXNoUmVuYW1lSXRlbShuYW1lLCBpZCkge1xuICAgIHRoaXMuZWxlbWVudEZpbmRlcih0aGlzLnRyZWVTdG9yYWdlLCBpZCk7XG4gICAgLy8gY29kZVxuICAgIHRoaXMuZmluZGluZ1Jlc3VsdHMuZm91bmRJdGVtLm5hbWUgPSBuYW1lO1xuICAgIHRoaXMuZmluZGluZ1Jlc3VsdHMuZm91bmRJdGVtLm9wdGlvbnMuZWRpdCA9IGZhbHNlO1xuICAgIC8vIGV2ZW50IGVtaXRcbiAgICBjb25zdCBldmVudEVtaXQgPSB7XG4gICAgICBlbGVtZW50OiB0aGlzLmZpbmRpbmdSZXN1bHRzLmZvdW5kSXRlbSxcbiAgICAgIHBhcmVudDogdGhpcy5maW5kaW5nUmVzdWx0cy5wYXJlbnRJdGVtIHx8ICdyb290J1xuICAgIH07XG4gICAgdGhpcy5vbkZpbmlzaFJlbmFtZUl0ZW0ubmV4dChldmVudEVtaXQpO1xuICAgIHRoaXMuY2xlYXJBY3Rpb24oKTtcbiAgfVxuXG4gIC8qXG4gICBFdmVudDogb25kcmFnc3RhcnQ7XG4gICBPbiBzdGFy