From 68eb26e1bd8a1bd129d8a805a2ec7c99dbee8db3 Mon Sep 17 00:00:00 2001 From: Alex ORLUC <alex.orluc@maarch.org> Date: Fri, 24 Apr 2020 20:38:13 +0200 Subject: [PATCH] FEAT #13664 TIME 4:30 add ajax tree + admin alfresco + fix alfresco action --- src/frontend/app/actions/actions.service.ts | 1 + .../send-alfresco-action.component.html | 91 ++++++------ .../send-alfresco-action.component.scss | 18 +-- .../send-alfresco-action.component.ts | 138 ++++++------------ .../alfresco-administration.component.html | 7 +- ...lfresco-list-administration.component.html | 12 +- ...lfresco-list-administration.component.scss | 6 + .../alfresco-list-administration.component.ts | 49 +++++-- src/frontend/assets/32px.png | Bin 0 -> 3333 bytes .../plugins/tree/maarch-tree.component.html | 19 ++- .../plugins/tree/maarch-tree.component.scss | 78 ++++++++++ .../plugins/tree/maarch-tree.component.ts | 131 ++++++++++++----- 12 files changed, 333 insertions(+), 217 deletions(-) create mode 100644 src/frontend/app/administration/alfresco/alfresco-list-administration.component.scss create mode 100644 src/frontend/assets/32px.png diff --git a/src/frontend/app/actions/actions.service.ts b/src/frontend/app/actions/actions.service.ts index e22014e5623..1d98b9bdad5 100644 --- a/src/frontend/app/actions/actions.service.ts +++ b/src/frontend/app/actions/actions.service.ts @@ -938,6 +938,7 @@ export class ActionsService { sendAlfrescoAction(options: any = null) { const dialogRef = this.dialog.open(SendAlfrescoActionComponent, { + panelClass: 'maarch-modal', autoFocus: false, disableClose: true, data: this.setDatasActionToSend() diff --git a/src/frontend/app/actions/send-alfresco-action/send-alfresco-action.component.html b/src/frontend/app/actions/send-alfresco-action/send-alfresco-action.component.html index 9a97032a255..fa58b23ef08 100644 --- a/src/frontend/app/actions/send-alfresco-action/send-alfresco-action.component.html +++ b/src/frontend/app/actions/send-alfresco-action/send-alfresco-action.component.html @@ -1,48 +1,49 @@ -<h1 mat-dialog-title>{{data.action.label}}</h1> -<div mat-dialog-content> - <div *ngIf="loading" class="loading" style="display:flex;height:100%;"> - <mat-spinner style="margin:auto;"></mat-spinner> - </div> - <mat-sidenav-container autosize style="height:100%;"> - <mat-sidenav-content style="background: white;"> - <div class="row" style="margin: 0;"> - <div class="col-md-12"> - {{lang.makeActionOn}} - <b *ngIf="data.resIds.length === 0" color="primary" - class="highlight">{{lang.currentIndexingMail}}</b> - <b *ngIf="data.resIds.length == 1" color="primary" class="highlight">{{data.resource.chrono}}</b> - <b *ngIf="data.resIds.length > 1" color="primary" class="highlight">{{data.resIds.length}} - {{lang.elements}}</b> ? - </div> - <br/> - <div *ngIf="resourcesErrors.length > 0" class="alert-message alert-message-danger mailList" role="alert"> - <p> - {{lang.canNotMakeAction}} : - </p> - <ul> - <li *ngFor="let ressource of resourcesErrors"> - <b>{{ressource.alt_identifier}}</b> : {{lang[ressource.reason]}} - </li> - </ul> - </div> - <div style="clear: both;"> - <mat-form-field class="smallInput" appearance="outline" floatLabel="never"> - <mat-icon color="primary" class="fa fa-search" matPrefix></mat-icon> - <input type="text" id="autoCompleteInput" #autoCompleteInput - [placeholder]="'Recherche un dossier'" matInput [formControl]="searchFolder"> - </mat-form-field> - <div id="jstreeAlfresco"></div> - </div> +<div class="mat-dialog-content-container"> + <h1 mat-dialog-title>{{data.action.label}}</h1> + <div mat-dialog-content> + <div *ngIf="loading" class="loading" style="display:flex;height:100%;"> + <mat-spinner style="margin:auto;"></mat-spinner> + </div> + <div class="row" style="margin: 0;"> + <div class="col-md-12"> + {{lang.makeActionOn}} + <b *ngIf="data.resIds.length === 0" color="primary" class="highlight">{{lang.currentIndexingMail}}</b> + <b *ngIf="data.resIds.length == 1" color="primary" class="highlight">{{data.resource.chrono}}</b> + <b *ngIf="data.resIds.length > 1" color="primary" class="highlight">{{data.resIds.length}} + {{lang.elements}}</b> ? + </div> + <br /> + <div *ngIf="resourcesErrors.length > 0" class="alert-message alert-message-danger mailList" role="alert"> + <p> + {{lang.canNotMakeAction}} : + </p> + <ul> + <li *ngFor="let ressource of resourcesErrors"> + <b>{{ressource.alt_identifier}}</b> : {{lang[ressource.reason]}} + </li> + </ul> + </div> + <div style="clear: both;"> + <mat-form-field class="smallInput" appearance="outline" floatLabel="never"> + <mat-icon color="primary" class="fa fa-search" matPrefix></mat-icon> + <input type="text" id="autoCompleteInput" #autoCompleteInput [placeholder]="'Recherche un dossier'" + matInput [formControl]="searchFolder"> + </mat-form-field> + <app-maaarch-tree #maarchTree *ngIf="alfrescoFolders.length > 0" + [childrenRoute]="'../rest/alfresco/folders/__node/children'" [rawData]="alfrescoFolders" + (afterSelectNode)="selectFolder($event)"></app-maaarch-tree> + <div id="jstreeAlfresco"></div> + </div> - <div class="col-md-12" style="padding-top: 10px;"> - <app-note-editor #noteEditor [resIds]="data.resIds"></app-note-editor> - </div> + <div class="col-md-12" style="padding-top: 10px;"> + <app-note-editor #noteEditor [resIds]="data.resIds"></app-note-editor> </div> - </mat-sidenav-content> - </mat-sidenav-container> -</div> -<div mat-dialog-actions class="actions"> - <button mat-raised-button mat-button color="primary" [disabled]="loading || !isValidAction()" - (click)="onSubmit()">{{lang.validate}}</button> + </div> + </div> + <span class="divider-modal"></span> + <div mat-dialog-actions class="actions"> + <button mat-raised-button mat-button color="primary" [disabled]="loading || !isValidAction()" + (click)="onSubmit()">{{lang.validate}}</button> <button mat-raised-button mat-button [disabled]="loading" [mat-dialog-close]="">{{lang.cancel}}</button> -</div> + </div> +</div> \ No newline at end of file diff --git a/src/frontend/app/actions/send-alfresco-action/send-alfresco-action.component.scss b/src/frontend/app/actions/send-alfresco-action/send-alfresco-action.component.scss index 12615b072b9..05b77d31b1f 100644 --- a/src/frontend/app/actions/send-alfresco-action/send-alfresco-action.component.scss +++ b/src/frontend/app/actions/send-alfresco-action/send-alfresco-action.component.scss @@ -25,20 +25,4 @@ .mat-icon { height: auto; } -} - -#jstreeAlfresco{ - max-height: 300px; - overflow: auto; - - ::ng-deep.jstree-anchor { - .jstree-icon { - color: $primary !important; - } - } - ::ng-deep.jstree-hovered, ::ng-deep.jstree-clicked { - .jstree-icon { - color: white !important; - } - } -} +} \ No newline at end of file diff --git a/src/frontend/app/actions/send-alfresco-action/send-alfresco-action.component.ts b/src/frontend/app/actions/send-alfresco-action/send-alfresco-action.component.ts index 749f880c0ef..7936f4d9eeb 100644 --- a/src/frontend/app/actions/send-alfresco-action/send-alfresco-action.component.ts +++ b/src/frontend/app/actions/send-alfresco-action/send-alfresco-action.component.ts @@ -4,15 +4,16 @@ import { NotificationService } from '../../notification.service'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { HttpClient } from '@angular/common/http'; import { NoteEditorComponent } from '../../notes/note-editor.component'; -import { tap, finalize, catchError, debounceTime, filter } from 'rxjs/operators'; -import { of } from 'rxjs'; +import { tap, finalize, catchError, debounceTime, filter, switchMap } from 'rxjs/operators'; import { FormControl } from '@angular/forms'; import { FunctionsService } from '../../../service/functions.service'; +import { MaarchTreeComponent } from '../../../plugins/tree/maarch-tree.component'; +import { of } from 'rxjs/internal/observable/of'; declare var $: any; @Component({ - templateUrl: "send-alfresco-action.component.html", + templateUrl: 'send-alfresco-action.component.html', styleUrls: ['send-alfresco-action.component.scss'], }) export class SendAlfrescoActionComponent implements OnInit { @@ -33,6 +34,7 @@ export class SendAlfrescoActionComponent implements OnInit { noResourceToProcess: boolean = null; @ViewChild('noteEditor', { static: true }) noteEditor: NoteEditorComponent; + @ViewChild('maarchTree', { static: false }) maarchTree: MaarchTreeComponent; constructor( public http: HttpClient, @@ -44,44 +46,26 @@ export class SendAlfrescoActionComponent implements OnInit { async ngOnInit(): Promise<void> { this.loading = true; - await this.checkAlfresco(); + // await this.checkAlfresco(); this.loading = false; - this.initTree(); + this.getRootFolders(); this.searchFolder.valueChanges .pipe( debounceTime(300), - tap((value: any) => { + tap(async (value: any) => { this.selectedFolder = null; this.selectedFolderName = null; if (value.length === 0) { - $('#jstreeAlfresco').jstree(true).settings.core.data = - { - 'url': (node: any) => { - return node.id === '#' ? - '../rest/alfresco/rootFolders' : `../rest/alfresco/folders/${node.id}/children`; - }, - 'data': (node: any) => { - return { 'id': node.id }; - } - }; - $('#jstreeAlfresco').jstree("refresh"); + await this.getRootFolders(); + this.refreshTree(); } }), filter(value => value.length > 2), + switchMap(data => this.http.get('../rest/alfresco/autocomplete/folders', { params: { 'search': data } })), tap((data: any) => { - - $('#jstreeAlfresco').jstree(true).settings.core.data = - { - 'url': (node: any) => { - return node.id === '#' ? - `../rest/alfresco/autocomplete/folders?search=${data}` : `../rest/alfresco/folders/${node.id}/children`; - }, - 'data': (node: any) => { - return { 'id': node.id }; - } - }; - $('#jstreeAlfresco').jstree("refresh"); + this.alfrescoFolders = data; + this.refreshTree(); }) ).subscribe(); } @@ -92,10 +76,10 @@ export class SendAlfrescoActionComponent implements OnInit { return new Promise((resolve, reject) => { this.http.post('../rest/resourcesList/users/' + this.data.userId + '/groups/' + this.data.groupId + '/baskets/' + this.data.basketId + '/actions/' + this.data.action.id + '/checkSendAlfresco', { resources: this.data.resIds }) .subscribe((data: any) => { - if(!this.functions.empty(data.fatalError)) { + if (!this.functions.empty(data.fatalError)) { this.notify.error(this.lang[data.reason]); this.dialogRef.close(); - } else if(!this.functions.empty(data.resourcesInformations.error)) { + } else if (!this.functions.empty(data.resourcesInformations.error)) { this.resourcesErrors = data.resourcesInformations.error; this.noResourceToProcess = this.resourcesErrors.length === this.data.resIds.length; } @@ -107,66 +91,33 @@ export class SendAlfrescoActionComponent implements OnInit { }); } - initTree() { + refreshTree() { + const tmpData = this.alfrescoFolders; + this.alfrescoFolders = []; + setTimeout(() => { - $('#jstreeAlfresco').jstree({ - "checkbox": { - 'deselect_all': true, - "three_state": false //no cascade selection - }, - 'core': { - force_text: true, - 'themes': { - 'name': 'proton', - 'responsive': true - }, - 'multiple': false, - 'data': { - 'url': (node: any) => { - return node.id === '#' ? - '../rest/alfresco/rootFolders' : `../rest/alfresco/folders/${node.id}/children`; - }, - 'data': (node: any) => { - return { 'id': node.id }; - }, - /*"dataFilter": (data: any) => { - - let objFold = JSON.parse(data); - objFold = objFold.folders; - - return JSON.stringify(objFold); - },*/ - /*"success": (data: any) => { - data.folders = data.folders.map((folder: any) => { - return { - ...folder, - id: folder.id, - icon: 'fa fa-folder', - text: folder.name, - parent: '#', - children: true - } - }); - console.log(data.folders); - return data.folders; - }*/ - }, - - //'data': this.alfrescoFolders, - }, - "plugins": ["checkbox"] - }); - $('#jstreeAlfresco') - // listen for event - .on('select_node.jstree', (e: any, data: any) => { - this.selectedFolder = data.node.id; - this.selectedFolderName = this.getNameWithParents(data.node.text, data.node.parent); - }).on('deselect_node.jstree', (e: any, data: any) => { - this.selectedFolder = null; - this.selectedFolderName = null; - }) - .jstree(); - }, 0); + this.alfrescoFolders = tmpData; + }, 200); + } + + getRootFolders() { + return new Promise((resolve, reject) => { + this.http.get('../rest/alfresco/rootFolders').pipe( + tap((data: any) => { + this.alfrescoFolders = data; + resolve(true); + }), + catchError((err: any) => { + this.notify.handleSoftErrors(err); + return of(false); + }) + ).subscribe(); + }); + } + + selectFolder(folder: any) { + this.selectedFolder = folder.id; + this.selectedFolderName = this.getNameWithParents(folder.text, folder.parent); } onSubmit() { @@ -206,10 +157,11 @@ export class SendAlfrescoActionComponent implements OnInit { if (parentId === '#') { return name; } - $('#jstreeAlfresco').jstree(true).get_json('#', {flat:true}).forEach((folder: any) => { + + this.maarchTree.getTreeData().forEach((folder: any) => { if (folder.id == parentId) { - name = folder.text + "/" + name; - if (folder.parent != '#') { + name = folder.text + '/' + name; + if (folder.parent !== '#') { name = this.getNameWithParents(name, folder.parent); } } diff --git a/src/frontend/app/administration/alfresco/alfresco-administration.component.html b/src/frontend/app/administration/alfresco/alfresco-administration.component.html index f9b420f2904..3ab2ba461b1 100644 --- a/src/frontend/app/administration/alfresco/alfresco-administration.component.html +++ b/src/frontend/app/administration/alfresco/alfresco-administration.component.html @@ -56,14 +56,15 @@ style="text-align: center;font-weight: bold;opacity: 0.5;"> <div>{{lang.mustFillAccountMsg}}</div> </div> - <div *ngIf="alfrescoTreeLoaded && functionsService.empty(alfresco.rootFolder)"> - <div id="jstreeAlfresco"></div> + <div *ngIf="alfrescoTreeLoaded && functionsService.empty(alfresco.rootFolder)" style="width: 100%;"> + <app-maaarch-tree *ngIf="entities.length > 0" (afterSelectNode)="alfresco.rootFolder=$event.id" [rawData]="entities"></app-maaarch-tree> + <!--<div id="jstreeAlfresco"></div>--> </div> <div *ngIf="!functionsService.empty(alfresco.rootFolder)" style="width: 100%;"> <mat-form-field> <input matInput [(ngModel)]="alfresco.rootFolder" required name="rootFolder" id="rootFolder" title="Dépôt racine" type="text" placeholder="Dépôt racine" - disabled> + > <button mat-icon-button matSuffix color="warn" (click)="alfresco.rootFolder=null"> <mat-icon class="fa fa-trash"></mat-icon> diff --git a/src/frontend/app/administration/alfresco/alfresco-list-administration.component.html b/src/frontend/app/administration/alfresco/alfresco-list-administration.component.html index 535e69a3097..c2c267e4bfb 100644 --- a/src/frontend/app/administration/alfresco/alfresco-list-administration.component.html +++ b/src/frontend/app/administration/alfresco/alfresco-list-administration.component.html @@ -1,5 +1,14 @@ <mat-sidenav-container autosize class="maarch-container"> <ng-template #adminMenuTemplate> + <mat-list> + <a mat-list-item> + <mat-form-field style="padding-top: 10px;"> + <mat-label>Url API Alfresco</mat-label> + <input matInput required name="accountId" [(ngModel)]="alfrescoUrl" + id="accountId" [title]="alfrescoUrl" type="text" placeholder="https://alfresco/api/" (blur)="saveUrl()"> + </mat-form-field> + </a> + </mat-list> <mat-nav-list> <h3 mat-subheader>{{lang.actions}}</h3> <a mat-list-item routerLink="/administration/alfresco/new"> @@ -51,7 +60,8 @@ <ng-container matColumnDef="entitiesLabel"> <mat-header-cell *matHeaderCellDef mat-sort-header>{{lang.linkedEntities}}</mat-header-cell> <mat-cell *matCellDef="let element"> - {{element.entitiesLabel}} </mat-cell> + <span class="label" *ngFor="let entity of element.entitiesLabel">{{entity}}</span> + </mat-cell> </ng-container> <ng-container matColumnDef="actions"> <mat-header-cell *matHeaderCellDef></mat-header-cell> diff --git a/src/frontend/app/administration/alfresco/alfresco-list-administration.component.scss b/src/frontend/app/administration/alfresco/alfresco-list-administration.component.scss new file mode 100644 index 00000000000..09e356bf6ca --- /dev/null +++ b/src/frontend/app/administration/alfresco/alfresco-list-administration.component.scss @@ -0,0 +1,6 @@ +@import '../../../css/vars.scss'; + +.label { + background: $primary; + margin: 5px; +} \ No newline at end of file diff --git a/src/frontend/app/administration/alfresco/alfresco-list-administration.component.ts b/src/frontend/app/administration/alfresco/alfresco-list-administration.component.ts index 60681840308..3ca5ae63c08 100644 --- a/src/frontend/app/administration/alfresco/alfresco-list-administration.component.ts +++ b/src/frontend/app/administration/alfresco/alfresco-list-administration.component.ts @@ -19,6 +19,7 @@ import { MatDialog } from '@angular/material/dialog'; @Component({ templateUrl: 'alfresco-list-administration.component.html', + styleUrls: ['./alfresco-list-administration.component.scss'], providers: [AppService] }) export class AlfrescoListAdministrationComponent implements OnInit { @@ -27,6 +28,8 @@ export class AlfrescoListAdministrationComponent implements OnInit { lang: any = LANG; + alfrescoUrl: string = ''; + accounts: any[] = []; loading: boolean = false; @@ -65,6 +68,13 @@ export class AlfrescoListAdministrationComponent implements OnInit { this.loading = true; + this.http.get('../rest/alfresco/configuration').pipe( + filter((data: any) => !this.functions.empty(data.configuration)), + tap((data: any) => { + this.alfrescoUrl = data.configuration.uri; + }) + ).subscribe(); + this.http.get('../rest/alfresco/accounts') .subscribe((data: any) => { this.accounts = data.accounts; @@ -82,20 +92,29 @@ export class AlfrescoListAdministrationComponent implements OnInit { 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/alfresco/accounts/' + id)), - tap(() => { - this.accounts = this.accounts.filter((account: any) => account.id !== id); - this.dataSource = new MatTableDataSource(this.accounts); - this.dataSource.paginator = this.paginator; - this.dataSource.sort = this.sort; - this.notify.success(this.lang.accountDeleted); - }), - catchError((err: any) => { - this.notify.handleSoftErrors(err); - return of(false); - }) - ).subscribe(); + this.dialogRef.afterClosed().pipe( + filter((data: string) => data === 'ok'), + exhaustMap(() => this.http.delete('../rest/alfresco/accounts/' + id)), + tap(() => { + this.accounts = this.accounts.filter((account: any) => account.id !== id); + this.dataSource = new MatTableDataSource(this.accounts); + this.dataSource.paginator = this.paginator; + this.dataSource.sort = this.sort; + this.notify.success(this.lang.accountDeleted); + }), + catchError((err: any) => { + this.notify.handleSoftErrors(err); + return of(false); + }) + ).subscribe(); + } + + saveUrl() { + this.http.put('../rest/alfresco/configuration', { uri: this.alfrescoUrl }).pipe( + catchError((err: any) => { + this.notify.handleSoftErrors(err); + return of(false); + }) + ).subscribe(); } } diff --git a/src/frontend/assets/32px.png b/src/frontend/assets/32px.png new file mode 100644 index 0000000000000000000000000000000000000000..eb1db46fb25a553a9628e9be1bc8cb86470c369a GIT binary patch literal 3333 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 literal 0 HcmV?d00001 diff --git a/src/frontend/plugins/tree/maarch-tree.component.html b/src/frontend/plugins/tree/maarch-tree.component.html index 4fe265840ef..fdd3238e0e2 100644 --- a/src/frontend/plugins/tree/maarch-tree.component.html +++ b/src/frontend/plugins/tree/maarch-tree.component.html @@ -1,13 +1,18 @@ <mat-tree [dataSource]="dataSource" [treeControl]="treeControl"> - <mat-tree-node *matTreeNodeDef="let node" matTreeNodePadding> - <button mat-icon-button disabled></button> - {{getData(node.item).label}} + <mat-tree-node *matTreeNodeDef="let node" matTreeNodePadding matTreeNodePaddingIndent="25px" style="background-size: 320px 96px;background-image: url(32px.png); background-position: -288px -5px;background-repeat: repeat-y;"> + <button mat-icon-button disabled style="width: 32px;height: 32px;background-size: 320px 96px;background-image: url('32px.png');background-position: -288px -5px;background-repeat: repeat-y;"> + <mat-icon class="maarch-tree-node"></mat-icon> + </button> + <span class="node-content" [class.node-selected]="getData(node.item).selected" (click)="toggleNode(getData(node.item))"><i [class]="getData(node.item).icon"></i> {{getData(node.item).text}}</span> </mat-tree-node> - <mat-tree-node *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding> - <button type="button" mat-icon-button [attr.aria-label]="'toggle ' + node.filename" matTreeNodeToggle> - <mat-icon class="mat-icon-rtl-mirror fa fa-{{treeControl.isExpanded(node) ? 'chevron-down' : 'chevron-right'}}"></mat-icon> + <mat-tree-node *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding matTreeNodePaddingIndent="25px" style="background-size: 320px 96px;background-image: url(32px.png); background-position: -288px -5px;background-repeat: repeat-y;"> + <i *ngFor="let it of getIteration(node.level)" [style.left.px]="25*(it)" style="background-size: 320px 96px;background-image: url(32px.png); background-position: -288px -5px;background-repeat: repeat-y;width: 32px;height: 32px;position: absolute;"></i> + <button type="button" mat-icon-button [attr.aria-label]="'toggle ' + node.filename" matTreeNodeToggle style="width: 32px;height: 32px;background-size: 320px 96px;background-image: url('32px.png');background-position: -288px -5px;background-repeat: repeat-y;"> + <mat-icon class="mat-icon-rtl-mirror {{treeControl.isExpanded(node) ? 'maarch-tree-collapse' : 'maarch-tree-expand'}}"></mat-icon> </button> - {{getData(node.item).label}} + <span class="node-content" [class.node-selected]="getData(node.item).selected" (click)="toggleNode(getData(node.item))"> + <i [class]="getData(node.item).icon"></i> {{getData(node.item).text}} + </span> <mat-progress-bar *ngIf="node.isLoading" mode="indeterminate" class="example-tree-progress-bar"> </mat-progress-bar> </mat-tree-node> diff --git a/src/frontend/plugins/tree/maarch-tree.component.scss b/src/frontend/plugins/tree/maarch-tree.component.scss index db449840977..1731be2bb21 100644 --- a/src/frontend/plugins/tree/maarch-tree.component.scss +++ b/src/frontend/plugins/tree/maarch-tree.component.scss @@ -1,3 +1,81 @@ +@import '../../css/vars.scss'; + .example-tree-progress-bar { margin-left: 30px; +} + +.maarch-tree-expand { + background-size: 320px 96px; + background-image: url(../../assets/32px.png); + width: 22px; + height: 22px; + line-height: 22px; + display: inline-block; + background-position: -101px -8px; +} + +.maarch-tree-collapse { + background-size: 320px 96px; + background-image: url(../../assets/32px.png); + width: 22px; + height: 22px; + line-height: 22px; + display: inline-block; + background-position: -133px -8px; +} + +.maarch-tree-node { + background-size: 320px 96px; + background-image: url(../../assets/32px.png); + width: 22px; + height: 22px; + line-height: 22px; + display: inline-block; + background-position: -69px -8px; +} + +.mat-tree { + position: relative; +} + +.mat-tree-node { + font-family: 'Titillium Web',sans-serif,Arial,sans-serif; + min-height: 32px; + line-height: 32px; + //margin-left: 0px; + min-width: 32px; + height: 32px; + button[disabled] { + opacity: 1; + } +} + +.node-content { + padding-left: 5px; + padding-right: 5px; + cursor: pointer; + transition: all 0.3s; + border-radius: 5px; + height: 25px; + display: flex; + align-items: center; + white-space: pre; + + i { + color: $primary; + } +} +.node-content:not(.node-selected):hover { + background-color: #6666661c; + transition: all 0.3s; +} + +.node-selected { + background-color: $primary; + color:white; + transition: all 0.3s; + + i { + color: white; + } } \ No newline at end of file diff --git a/src/frontend/plugins/tree/maarch-tree.component.ts b/src/frontend/plugins/tree/maarch-tree.component.ts index 4f0db38c6b5..a469b7742f1 100644 --- a/src/frontend/plugins/tree/maarch-tree.component.ts +++ b/src/frontend/plugins/tree/maarch-tree.component.ts @@ -4,10 +4,11 @@ import { DataSource } from '@angular/cdk/collections'; import { FlatTreeControl } from '@angular/cdk/tree'; -import { Component, Injectable, Input, OnInit } from '@angular/core'; +import { Component, Injectable, Input, OnInit, EventEmitter, Output, OnDestroy } from '@angular/core'; import { BehaviorSubject, merge, Observable } from 'rxjs'; -import { map, tap } from 'rxjs/operators'; +import { map, tap, filter, finalize } from 'rxjs/operators'; import { HttpClient } from '@angular/common/http'; +import { trigger, transition, style, animate, state } from '@angular/animations'; /** Flat node with expandable and level information */ export class DynamicFlatNode { @@ -23,7 +24,7 @@ export class DynamicFlatNode { * Database for dynamic data. When expanding a node in the tree, the data source will need to fetch * the descendants data from the database. */ -@Injectable({ providedIn: 'root' }) +@Injectable() export class DynamicDatabase { dataMap = new Map<string, string[]>([]); @@ -35,7 +36,6 @@ export class DynamicDatabase { } setData(node: any) { - console.log(node.childrens); return this.dataMap.set(node.id, node.childrens); } @@ -73,6 +73,7 @@ export class DynamicDataSource implements DataSource<DynamicFlatNode> { private _treeControl: FlatTreeControl<DynamicFlatNode>, private _database: DynamicDatabase, private rawData: any, + private childrenRoute: string, private httpClient: HttpClient ) { } @@ -112,7 +113,7 @@ export class DynamicDataSource implements DataSource<DynamicFlatNode> { toggleNode(node: DynamicFlatNode, expand: boolean) { let children = this._database.getChildren(node.item); let index = this.data.indexOf(node); - if (!this.rawData[index].hasChildren) { + if (!this.rawData[index].children) { // If no children, or cannot find the node, no op return; } @@ -122,16 +123,28 @@ export class DynamicDataSource implements DataSource<DynamicFlatNode> { if (expand) { if (children === undefined) { this.httpClient - .get('../rest/entities') + .get(this.childrenRoute.replace('__node', node.item)) .pipe( - tap(() => { - console.log(node); - this.rawData.push({ - id: 34, + filter((data: any) => data.length > 0), + tap((data: any) => { + data.forEach((element: any) => { + this.rawData.push(element); + }); + // TO DO : SAMPLE + /*this.rawData.push({ + id: '432', label: 'cool', - parent_id: node.item, - hasChildren: false + parent: node.item, + icon: 'fa fa-building', + children: true }); + this.rawData.push({ + id: '3465', + label: 'good', + parent: node.item, + icon: 'fa fa-building', + children: false + });*/ const folders = this.rawData.map((elem: any) => elem.id); @@ -139,14 +152,15 @@ export class DynamicDataSource implements DataSource<DynamicFlatNode> { const node = { id: element.id, childrens: this.rawData - .filter((elem: any) => elem.parent_id === element.id) + .filter((elem: any) => elem.parent === element.id) .map((elem: any) => elem.id) }; if ( - this.rawData.filter((elem: any) => elem.parent_id === element.id).length > 0 + this.rawData.filter((elem: any) => elem.parent === element.id).length > 0 ) { this._database.setData(node); } + }); children = this._database.getChildren(node.item); @@ -160,14 +174,12 @@ export class DynamicDataSource implements DataSource<DynamicFlatNode> { this._database.isExpandable(name) ) ); - console.log(nodes); this.data.splice(index + 1, 0, ...nodes); // notify the change this.dataChange.next(this.data); - node.isLoading = false; - }) - ) - .subscribe(); + }), + finalize(() => node.isLoading = false), + ).subscribe(); } else { const nodes = children.map( name => @@ -177,7 +189,6 @@ export class DynamicDataSource implements DataSource<DynamicFlatNode> { this._database.isExpandable(name) ) ); - console.log(nodes); this.data.splice(index + 1, 0, ...nodes); // notify the change this.dataChange.next(this.data); @@ -204,20 +215,22 @@ export class DynamicDataSource implements DataSource<DynamicFlatNode> { @Component({ selector: 'app-maaarch-tree', templateUrl: 'maarch-tree.component.html', - styleUrls: ['maarch-tree.component.scss'] + styleUrls: ['maarch-tree.component.scss'], + providers: [DynamicDatabase], }) export class MaarchTreeComponent implements OnInit { @Input() rawData: any = []; + @Input() childrenRoute: string = ''; + @Input() multiple: boolean = false; + + @Output() afterSelectNode = new EventEmitter<any>(); + @Output() afterDeselectNode = new EventEmitter<any>(); constructor( private database: DynamicDatabase, private httpClient: HttpClient ) { - this.treeControl = new FlatTreeControl<DynamicFlatNode>( - this.getLevel, - this.isExpandable - ); } treeControl: FlatTreeControl<DynamicFlatNode>; @@ -229,29 +242,46 @@ export class MaarchTreeComponent implements OnInit { isExpandable = (node: DynamicFlatNode) => node.expandable; hasChild = (_: number, _nodeData: DynamicFlatNode) => - this.getData(_nodeData.item).hasChildren + this.getData(_nodeData.item).children ngOnInit(): void { - console.log('init!'); + this.treeControl = new FlatTreeControl<DynamicFlatNode>( + this.getLevel, + this.isExpandable + ); // SAMPLE - this.rawData = [ + /*this.rawData = [ { id: '46', label: 'bonjour', - parent_id: null, - hasChildren: true + parent: null, + icon: 'fa fa-building', + children: true, + selected : true, }, { id: '1', label: 'Compétences fonctionnelles', - parent_id: null, - hasChildren: false + parent: null, + icon: 'fa fa-building', + children: false, + selected : true, + }, + { + id: '232', + label: 'Compétences technique', + parent: null, + icon: 'fa fa-building', + children: false, + selected : true, } - ]; + ];*/ + this.dataSource = new DynamicDataSource( this.treeControl, this.database, this.rawData, + this.childrenRoute, this.httpClient ); this.initTree(); @@ -262,18 +292,18 @@ export class MaarchTreeComponent implements OnInit { const node = { id: element.id, childrens: this.rawData - .filter((elem: any) => elem.parent_id === element.id) + .filter((elem: any) => elem.parent === element.id) .map((elem: any) => elem.id) }; if ( - this.rawData.filter((elem: any) => elem.parent_id === element.id).length > 0 + this.rawData.filter((elem: any) => elem.parent === element.id).length > 0 ) { this.database.setData(node); } }); this.database.setRootNode( - this.rawData.filter((elem: any) => elem.parent_id === null).map((elem: any) => elem.id) + this.rawData.filter((elem: any) => elem.parent === '#').map((elem: any) => elem.id) ); this.dataSource.data = this.database.initialData(); } @@ -281,4 +311,33 @@ export class MaarchTreeComponent implements OnInit { getData(id: any) { return this.rawData.filter((elem: any) => elem.id === id)[0]; } + + toggleNode(item: any) { + if (item.selected) { + this.afterDeselectNode.emit(item); + } else { + this.afterSelectNode.emit(item); + } + + if (!this.multiple) { + this.rawData.forEach((elem: any) => { + if (elem.id === item.id) { + elem.selected = !item.selected; + } else { + elem.selected = false; + } + }); + } else { + item.selected = !item.selected; + } + + } + + getIteration(it: number) { + return Array(it).fill(0).map((x, i) => i); + } + + getTreeData() { + return this.rawData; + } } -- GitLab