Something went wrong on our end
-
Alex ORLUC authoredAlex ORLUC authored
folder-tree.component.ts 16.11 KiB
import { Component, OnInit, ViewChild, Input, Renderer2, Output, EventEmitter, OnDestroy } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { LANG } from '../translate.component';
import { tap, catchError, filter, exhaustMap } from 'rxjs/operators';
import { FlatTreeControl } from '@angular/cdk/tree';
import { trigger, transition, style, animate } from '@angular/animations';
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { MatInput } from '@angular/material/input';
import { MatTreeFlatDataSource, MatTreeFlattener } from '@angular/material/tree';
import { NotificationService } from '../notification.service';
import { ConfirmComponent } from '../../plugins/modal/confirm.component';
import { Router } from '@angular/router';
import { FolderUpdateComponent } from './folder-update/folder-update.component';
import { FoldersService } from './folders.service';
import { FormControl } from '@angular/forms';
import { PluginAutocomplete } from '../../plugins/autocomplete/autocomplete.component';
import { HeaderService } from '../../service/header.service';
import { FolderCreateModalComponent } from './folder-create-modal/folder-create-modal.component';
import { FunctionsService } from '../../service/functions.service';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { Subscription } from 'rxjs/internal/Subscription';
import { of } from 'rxjs/internal/observable/of';
/**
* Node for to-do item
*/
export class ItemNode {
id: number;
children: ItemNode[];
label: string;
parent_id: number;
public: boolean;
pinned: boolean;
countResources: number;
}
/** Flat to-do item node with expandable and level information */
export class ItemFlatNode {
id: number;
label: string;
parent_id: number;
countResources: number;
level: number;
public: boolean;
pinned: boolean;
expandable: boolean;
}
@Component({
selector: 'folder-tree',
templateUrl: 'folder-tree.component.html',
styleUrls: ['folder-tree.component.scss'],
animations: [
trigger('hideShow', [
transition(
':enter', [
style({ height: '0px' }),
animate('200ms', style({ 'height': '30px' }))
]
),
transition(
':leave', [
style({ height: '30px' }),
animate('200ms', style({ 'height': '0px' }))
]
)
]),
],
})
export class FolderTreeComponent implements OnInit, OnDestroy {
lang: any = LANG;
loading: boolean = true;
searchTerm: FormControl = new FormControl();
TREE_DATA: any[] = [];
dialogRef: MatDialogRef<any>;
createRootNode: boolean = false;
createItemNode: boolean = false;
dataChange = new BehaviorSubject<ItemNode[]>([]);
@Input() selectedId: number;
@ViewChild('itemValue', { static: true }) itemValue: MatInput;
@ViewChild('autocomplete', { static: false }) autocomplete: PluginAutocomplete;
subscription: Subscription;
@ViewChild('tree', { static: true }) tree: any;
@Output() refreshDocList = new EventEmitter<string>();
@Output() refreshFolderList = new EventEmitter<string>();
get data(): ItemNode[] { return this.dataChange.value; }
/** Map from flat node to nested node. This helps us finding the nested node to be modified */
flatNodeMap = new Map<ItemFlatNode, ItemNode>();
/** Map from nested node to flattened node. This helps us to keep the same object for selection */
nestedNodeMap = new Map<ItemNode, ItemFlatNode>();
private transformer = (node: ItemNode, level: number) => {
const existingNode = this.nestedNodeMap.get(node);
const flatNode = existingNode && existingNode.label === node.label
? existingNode
: new ItemFlatNode();
flatNode.label = node.label;
flatNode.countResources = node.countResources;
flatNode.public = node.public;
flatNode.pinned = node.pinned;
flatNode.parent_id = node.parent_id;
flatNode.id = node.id;
flatNode.level = level;
flatNode.expandable = !!node.children;
this.flatNodeMap.set(flatNode, node);
this.nestedNodeMap.set(node, flatNode);
return flatNode;
};
treeControl = new FlatTreeControl<any>(
node => node.level, node => node.expandable);
treeFlattener = new MatTreeFlattener(
this.transformer, node => node.level, node => node.expandable, node => node.children);
dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
constructor(
public http: HttpClient,
private notify: NotificationService,
private dialog: MatDialog,
private router: Router,
private renderer: Renderer2,
private headerService: HeaderService,
public foldersService: FoldersService,
private functions: FunctionsService,
) {
// Event after process action
this.subscription = this.foldersService.catchEvent().subscribe((result: any) => {
if (result.type === 'initTree') {
const folders = this.flatToNestedObject(this.foldersService.getList());
if (folders.length > 0) {
this.initTree(folders);
this.openTree(this.foldersService.getCurrentFolder().id);
}
this.loading = false;
} else if (result.type === 'refreshFolderCount') {
this.treeControl.dataNodes.filter(folder => folder.id === result.content.id)[0].countResources = result.content.countResources;
} else if (result.type === 'refreshFolderPinned') {
this.treeControl.dataNodes.filter(folder => folder.id === result.content.id)[0].pinned = result.content.pinned;
} else {
if (this.treeControl.dataNodes !== undefined) {
this.openTree(this.foldersService.getCurrentFolder().id);
}
}
});
}
ngOnInit(): void {
this.getFolders();
}
getFolders() {
this.loading = true;
this.foldersService.getFolders();
}
initTree(data: any) {
this.dataChange.next(data);
this.dataChange.subscribe(info => {
this.dataSource.data = info;
});
}
openTree(id: any) {
let indexSelectedFolder = this.treeControl.dataNodes.map((folder: any) => folder.id).indexOf(parseInt(id));
while (indexSelectedFolder !== -1) {
indexSelectedFolder = this.treeControl.dataNodes.map((folder: any) => folder.id).indexOf(this.treeControl.dataNodes[indexSelectedFolder].parent_id);
if (indexSelectedFolder !== -1) {
this.treeControl.expand(this.treeControl.dataNodes[indexSelectedFolder]);
}
}
}
hasChild = (_: number, node: any) => node.expandable;
hasNoContent = (_: number, _nodeData: any) => _nodeData.label === '';
showAction(node: any) {
this.treeControl.dataNodes.forEach(element => {
element.showAction = false;
});
node.showAction = true;
}
hideAction(node: any) {
node.showAction = false;
}
flatToNestedObject(data: any) {
const nested = data.reduce((initial: any, value: any, index: any, original: any) => {
if (value.parent_id === null) {
if (initial.left.length) {
this.checkLeftOvers(initial.left, value);
}
delete value.parent_id;
value.root = true;
initial.nested.push(value);
} else {
const parentFound = this.findParent(initial.nested, value);
if (parentFound) {
this.checkLeftOvers(initial.left, value);
} else {
initial.left.push(value);
}
}
return index < original.length - 1 ? initial : initial.nested;
}, { nested: [], left: [] });
return nested;
}
checkLeftOvers(leftOvers: any, possibleParent: any) {
for (let i = 0; i < leftOvers.length; i++) {
if (leftOvers[i].parent_id === possibleParent.id) {
// delete leftOvers[i].parent_id;
possibleParent.children ? possibleParent.children.push(leftOvers[i]) : possibleParent.children = [leftOvers[i]];
possibleParent.count = possibleParent.children.length;
const addedObj = leftOvers.splice(i, 1);
this.checkLeftOvers(leftOvers, addedObj[0]);
}
}
}
findParent(possibleParents: any, possibleChild: any): any {
let found = false;
for (let i = 0; i < possibleParents.length; i++) {
if (possibleParents[i].id === possibleChild.parent_id) {
found = true;
// delete possibleChild.parent_id;
if (possibleParents[i].children) {
possibleParents[i].children.push(possibleChild);
} else {
possibleParents[i].children = [possibleChild];
}
possibleParents[i].count = possibleParents[i].children.length;
return true;
} else if (possibleParents[i].children) {
found = this.findParent(possibleParents[i].children, possibleChild);
}
}
return found;
}
addNewItem(node: any) {
this.createItemNode = true;
const currentNode = this.flatNodeMap.get(node);
if (currentNode.children === undefined) {
currentNode['children'] = [];
}
currentNode.children.push({ label: '', parent_id: currentNode.id, public: currentNode.public } as ItemNode);
this.dataChange.next(this.data);
this.treeControl.expand(node);
this.renderer.selectRootElement('#itemValue').focus();
}
saveNode(node: any, value: any) {
this.http.post('../../rest/folders', { label: value, parent_id: node.parent_id }).pipe(
tap((data: any) => {
const nestedNode = this.flatNodeMap.get(node);
nestedNode.label = value;
nestedNode.id = data.folder;
nestedNode.countResources = 0;
this.dataChange.next(this.data);
this.treeControl.collapseAll();
this.openTree(nestedNode.id);
this.createItemNode = false;
this.foldersService.getPinnedFolders();
}),
tap(() => this.notify.success(this.lang.folderAdded)),
catchError((err) => {
this.notify.handleErrors(err);
return of(false);
})
).subscribe();
}
removeTemporaryNode(node: any) {
const parentNode = this.getParentNode(node);
const index = parentNode.children.map(nodeItem => nodeItem.id).indexOf(node.id);
if (index !== -1) {
parentNode.children.splice(index, 1);
}
this.flatNodeMap.delete(node);
this.dataChange.next(this.data);
this.createItemNode = false;
}
openCreateFolderModal() {
this.dialogRef = this.dialog.open(FolderCreateModalComponent, { panelClass: 'maarch-modal', data: { folderName: this.autocomplete.getValue() } });
this.dialogRef.afterClosed().pipe(
filter((folderId: number) => !this.functions.empty(folderId)),
tap(() => {
this.autocomplete.resetValue();
this.getFolders();
this.foldersService.getPinnedFolders();
}),
catchError((err) => {
this.notify.handleSoftErrors(err);
return of(false);
})
).subscribe();
}
deleteNode(node: any) {
this.dialogRef = this.dialog.open(ConfirmComponent, { panelClass: 'maarch-modal', autoFocus: false, disableClose: true, data: { title: this.lang.delete, msg: this.lang.confirmAction } });
this.dialogRef.afterClosed().pipe(
filter((data: string) => data === 'ok'),
exhaustMap(() => this.http.delete('../../rest/folders/' + node.id)),
tap(() => {
const parentNode = this.getParentNode(node);
if (parentNode !== null) {
const index = parentNode.children.map(nodeItem => nodeItem.id).indexOf(node.id);
if (index !== -1) {
parentNode.children.splice(index, 1);
}
} else {
const index = this.data.map(nodeItem => nodeItem.id).indexOf(node.id);
if (index !== -1) {
this.data.splice(index, 1);
}
}
this.flatNodeMap.delete(node);
this.dataChange.next(this.data);
}),
tap(() => {
this.foldersService.getPinnedFolders();
this.notify.success(this.lang.folderDeleted);
}),
catchError((err) => {
this.notify.handleErrors(err);
return of(false);
})
).subscribe();
}
private getParentNode(node: any) {
const currentLevel = node.level;
if (currentLevel < 1) {
return null;
}
const startIndex = this.treeControl.dataNodes.indexOf(node) - 1;
for (let i = startIndex; i >= 0; i--) {
const currentNode = this.treeControl.dataNodes[i];
if (currentNode.level < currentLevel) {
return this.flatNodeMap.get(currentNode);
}
}
return null;
}
drop(ev: any, node: any) {
this.foldersService.classifyDocument(ev, node);
}
dragEnter(node: any) {
node.drag = true;
}
openFolderAdmin(node: any) {
this.dialogRef = this.dialog.open(FolderUpdateComponent, { panelClass: 'maarch-modal', autoFocus: false, data: { folderId: node.id } });
this.dialogRef.afterClosed().pipe(
tap((data) => {
if (data !== undefined) {
this.getFolders();
this.foldersService.getPinnedFolders();
this.foldersService.setEvent({ type: 'refreshFolderInformations', content: node });
}
})
).subscribe();
}
checkRights(node: any) {
let userEntities: any[] = [];
let currentUserId: number = 0;
this.http.get(`../../rest/folders/${node.id}`).pipe(
tap((data: any) => {
userEntities = this.headerService.user.entities.map((info: any) => info.id);
currentUserId = this.headerService.user.id;
let canAdmin = false;
let canDelete = false;
let canAdd = true;
const compare = data.folder.sharing.entities.filter((item: any) => userEntities.indexOf(item) > -1);
const entitiesCompare = data.folder.sharing.entities.filter((item: any) => compare.indexOf(item.id));
entitiesCompare.forEach((element: any) => {
if (element.edition === true) {
canAdmin = true;
}
if (element.canDelete === true) {
canDelete = true;
}
});
if (data.folder.user_id !== currentUserId && node.public) {
canAdd = false;
}
node.edition = (canAdmin || data.folder.user_id === currentUserId) ? true : false;
node.canAdd = node.edition;
node.canDelete = canDelete || data.folder.user_id === currentUserId;
}),
).subscribe();
}
goTo(folder: any) {
this.selectedId = folder.id;
this.getFolders();
this.router.navigate(['/folders/' + folder.id]);
}
togglePinFolder(folder: any) {
if (folder.pinned) {
this.foldersService.unpinFolder(folder);
} else {
this.foldersService.pinFolder(folder);
}
}
ngOnDestroy() {
// unsubscribe to ensure no memory leaks
this.subscription.unsubscribe();
}
}