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
JavaScript
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