diff --git a/src/frontend/app/actions/actions.service.ts b/src/frontend/app/actions/actions.service.ts index e22014e56234883482f237ef3748aacff5bef6a4..1d98b9bdad5f5f179b4682960b9ec97b3846fed2 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 9a97032a255f6b462e5e65cdb516b1b024692f1d..fa58b23ef083765f62af96d1eb8408b3b92a78ba 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 12615b072b98b9aa83f873460b1d13ff3b0045fd..05b77d31b1fe99bcdd3d44f9337177598b91a866 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 749f880c0efcf7061a10669ecb01ac98f75735f5..7936f4d9eebf2042437d42ba69abe003ffc53c6d 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 f9b420f29043f971215f4921478ab24246242a25..3ab2ba461b14fec72c1727044dceb8b0cdce667f 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 535e69a3097c33ebb3b51a7348be9e916bd7e20f..c2c267e4bfb3b8399b7eda7d9e74a149f8500206 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 0000000000000000000000000000000000000000..09e356bf6ca11e0667611f024807f67df16c48a3 --- /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 60681840308ab25407bd79d6976465daaa650a57..3ca5ae63c08c3ee11cb0bc920b08a37624bece85 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 Binary files /dev/null and b/src/frontend/assets/32px.png differ diff --git a/src/frontend/plugins/tree/maarch-tree.component.html b/src/frontend/plugins/tree/maarch-tree.component.html index 4fe265840ef289f22a07b7a30e3179415e422692..fdd3238e0e2a628efdc7ac0192bdbfe996d06ade 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 db44984097748fabd6c313bc24639a63b09e3ad2..1731be2bb21aaf1a6320b64ff7532257f720d761 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 4f0db38c6b516aec1fad43d0e24993cac6682347..a469b7742f1e21cf8177a63abc93a79f5952b50f 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; + } }