diff --git a/package.json b/package.json index f6289575b6c9dec3dbc552bf8df8311a09ca9a9f..acdbe236d9ed327938ac3fb443404a39f754dd45 100755 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@ngx-translate/core": "^13.0.0", "@ngx-translate/http-loader": "^6.0.0", "core-js": "^3.6.5", + "flat-to-nested": "^1.1.0", "ngx-joyride": "^2.3.1", "zone.js": "~0.10.3" }, diff --git a/src/frontend/app/administration/user/user-administration-redirect-modal.component.html b/src/frontend/app/administration/user/user-administration-redirect-modal.component.html index 7d39d237d4d4dfe5bdf9ec69d4e36458f901146e..2f7bb6e09f4576d4991ccdc8c030eec195cff742 100755 --- a/src/frontend/app/administration/user/user-administration-redirect-modal.component.html +++ b/src/frontend/app/administration/user/user-administration-redirect-modal.component.html @@ -4,10 +4,10 @@ <form #redirectConfForm="ngForm"> <div class="alert-message alert-message-info" role="alert" *ngIf="data.hasConfidentialityInstances" - [innerHTML]="this.translate.instant('lang.redirectConfidentialInfo')"> + [innerHTML]="'lang.redirectConfidentialInfo' | translate"> </div> <div class="alert-message alert-message-info" role="alert" *ngIf="data.hasListTemplates" - [innerHTML]="this.translate.instant('lang.redirectModelInfo')"> + [innerHTML]="'lang.redirectModelInfo' | translate"> </div> <div class="input-group"> <mat-radio-group id="processMode" name="processMode" @@ -22,14 +22,14 @@ {{'lang.reaffectUserRedirect' | translate}} </mat-radio-button> </mat-radio-group> - <plugin-autocomplete *ngIf="this.processMode == 'reaffect'" [labelPlaceholder]="this.translate.instant('lang.userReplacement')" - [labelList]="this.translate.instant('lang.availableUsers')" [routeDatas]="['/rest/autocomplete/users']" + <plugin-autocomplete *ngIf="this.processMode == 'reaffect'" [labelPlaceholder]="'lang.userReplacement' | translate" + [labelList]="'lang.availableUsers' | translate" [routeDatas]="['/rest/autocomplete/users']" [targetSearchKey]="'idToDisplay'" [subInfoKey]="'descriptionToDisplay'" (triggerEvent)="setRedirectUser($event)" singleMode required></plugin-autocomplete> <div class="alert-message alert-message-info" role="alert" style="max-width: inherit;"> <span *ngIf="this.processMode == ''"><em>{{'lang.doNothingRedirectInformations' | translate}}</em></span> - <span *ngIf="this.processMode == 'delete'" [innerHTML]="this.translate.instant('lang.removeUserRedirectInformations')"></span> + <span *ngIf="this.processMode == 'delete'" [innerHTML]="'lang.removeUserRedirectInformations' | translate"></span> <span *ngIf="this.processMode == 'reaffect'"><em>{{'lang.reaffectUserRedirectInformations' | translate}}</em></span> </div> diff --git a/src/frontend/app/administration/user/user-administration.component.html b/src/frontend/app/administration/user/user-administration.component.html index e2111f13f17b20d1903550920e6ffbd1e7f7442b..a9495830ec2082dfed8d748566cf500d1295a206 100755 --- a/src/frontend/app/administration/user/user-administration.component.html +++ b/src/frontend/app/administration/user/user-administration.component.html @@ -232,8 +232,8 @@ </div> <div *ngIf="user.mode === 'rest'"> <mat-form-field> - <mat-label>{{'this.lang.authorizedRoutes' | translate}}</mat-label> - <textarea matInput [(ngModel)]="user.authorizedApi" placeholder="POST/attachments\nPUT/attachments/{id}"></textarea> + <mat-label>{{'lang.authorizedRoutes' | translate}}</mat-label> + <textarea matInput name="authorizedApi" [(ngModel)]="user.authorizedApi" placeholder="POST/attachments\nPUT/attachments/{id}"></textarea> </mat-form-field> </div> </mat-expansion-panel> @@ -259,11 +259,7 @@ </mat-nav-list> </mat-tab> <mat-tab *ngIf="!creationMode" label="{{'lang.entities' | translate}}"> - <mat-form-field> - <input matInput id="jstree_search" type="text" placeholder="{{'lang.searchEntities' | translate}}"> - <mat-hint>{{'lang.linkEntityToUserInfo' | translate}}</mat-hint> - </mat-form-field> - <div id="jstree"></div> + <app-maarch-flat-tree #maarchTree (afterSelectNode)="addEntity($event)" [selectionPropagation]="false" [openState]="'all'" (afterDeselectNode)="deleteEntity($event)"></app-maarch-flat-tree> </mat-tab> <mat-tab *ngIf="!creationMode" label="{{'lang.baskets' | translate}}"> <div class="col-sm-6" style="overflow:hidden;"> diff --git a/src/frontend/app/administration/user/user-administration.component.ts b/src/frontend/app/administration/user/user-administration.component.ts index 500ac7d9b9b5a7703032ecb14866847099319615..a23ef42a6bf43d5fca07af203c5b4890602ffe43 100755 --- a/src/frontend/app/administration/user/user-administration.component.ts +++ b/src/frontend/app/administration/user/user-administration.component.ts @@ -15,6 +15,7 @@ import { SelectionModel } from '@angular/cdk/collections'; import { AccountLinkComponent } from './account-link/account-link.component'; import { AppService } from '../../../service/app.service'; import { PrivilegeService } from '../../../service/privileges.service'; +import { MaarchFlatTreeComponent } from '../../../plugins/tree/maarch-flat-tree.component'; declare var $: any; @@ -26,6 +27,8 @@ export class UserAdministrationComponent implements OnInit { @ViewChild('snav2', { static: true }) public sidenavRight: MatSidenav; @ViewChild('adminMenuTemplate', { static: true }) adminMenuTemplate: TemplateRef<any>; + @ViewChild('maarchTree', { static: false }) maarchTree: MaarchFlatTreeComponent; + lang: any = LANG; loading: boolean = false; @@ -36,8 +39,8 @@ export class UserAdministrationComponent implements OnInit { userId: string; mode: string = ''; user: any = { - mode : 'standard', - authorizedApi : '' + mode: 'standard', + authorizedApi: '' }; _search: string = ''; creationMode: boolean; @@ -94,19 +97,19 @@ export class UserAdministrationComponent implements OnInit { adminModes: any[] = [ { id: 'standard', - label : this.translate.instant('lang.standard') + label: this.translate.instant('lang.standard') }, { id: 'root_visible', - label : this.translate.instant('lang.root_visible') + label: this.translate.instant('lang.root_visible') }, { id: 'root_invisible', - label : this.translate.instant('lang.root_invisible') + label: this.translate.instant('lang.root_invisible') }, { id: 'rest', - label : this.translate.instant('lang.rest') + label: this.translate.instant('lang.rest') } ]; @@ -176,9 +179,8 @@ export class UserAdministrationComponent implements OnInit { .subscribe((data: any) => { this.user = data; - if (this.user.mode == 'rest') { + if (this.user.mode === 'rest') { this.user.authorizedApi = this.user.authorizedApi.join('\n'); - console.log(this.user.authorizedApi); } if (this.headerService.user.id === this.user.id) { @@ -311,7 +313,15 @@ export class UserAdministrationComponent implements OnInit { } initService() { - if ($('.jstree-container-ul').length === 0) { + if (this.maarchTree.rawData.length === 0) { + this.maarchTree.initData(this.user.allEntities.map((ent: any) => { + return { + ...ent, + parent_id : ent.parent, + }; + })); + } + /*if ($('.jstree-container-ul').length === 0) { $('#jstree').jstree({ 'checkbox': { 'three_state': false // no cascade selection @@ -346,7 +356,7 @@ export class UserAdministrationComponent implements OnInit { $('#jstree').jstree(true).search(v); }, 250); }); - } + }*/ } processAfterUpload(b64Content: any) { @@ -454,23 +464,25 @@ export class UserAdministrationComponent implements OnInit { }); } - addEntity(entiyId: any) { - const entity = { - 'entityId': entiyId, - 'role': '' - }; + addEntity(entities: any[]) { + entities.forEach(ent => { + const entity = { + 'entityId': ent.entity_id, + 'role': '' + }; + this.http.post('../rest/users/' + this.serialId + '/entities', entity) + .subscribe((data: any) => { + this.user.entities = data.entities; + this.user.allEntities = data.allEntities; + if (this.headerService.user.id == this.serialId) { + this.headerService.resfreshCurrentUser(); + } + this.notify.success(this.translate.instant('lang.entityAdded')); + }, (err) => { + this.notify.error(err.error.errors); + }); + }); - this.http.post('../rest/users/' + this.serialId + '/entities', entity) - .subscribe((data: any) => { - this.user.entities = data.entities; - this.user.allEntities = data.allEntities; - if (this.headerService.user.id == this.serialId) { - this.headerService.resfreshCurrentUser(); - } - this.notify.success(this.translate.instant('lang.entityAdded')); - }, (err) => { - this.notify.error(err.error.errors); - }); } updateEntity(entity: any) { @@ -492,52 +504,64 @@ export class UserAdministrationComponent implements OnInit { }); } - deleteEntity(entityId: any) { + deleteEntity(entities: any[]) { - // first check confidential state - this.http.get('../rest/users/' + this.serialId + '/entities/' + entityId) - .subscribe((data: any) => { - if (!data['hasConfidentialityInstances'] && !data['hasListTemplates']) { - this.http.delete('../rest/users/' + this.serialId + '/entities/' + entityId) - .subscribe((dataEntities: any) => { - this.user.entities = dataEntities.entities; - this.user.allEntities = dataEntities.allEntities; - if (this.headerService.user.id == this.serialId) { - this.headerService.resfreshCurrentUser(); + entities.forEach(ent => { + const entityId = ent.entity_id; + // first check confidential state + this.http.get('../rest/users/' + this.serialId + '/entities/' + entityId) + .subscribe((data: any) => { + if (!data['hasConfidentialityInstances'] && !data['hasListTemplates']) { + this.http.delete('../rest/users/' + this.serialId + '/entities/' + entityId) + .subscribe((dataEntities: any) => { + this.user.entities = dataEntities.entities; + this.user.allEntities = dataEntities.allEntities; + if (this.headerService.user.id == this.serialId) { + this.headerService.resfreshCurrentUser(); + } + this.notify.success(this.translate.instant('lang.entityDeleted')); + }, (err) => { + this.notify.error(err.error.errors); + }); + } else { + this.config = { panelClass: 'maarch-modal', data: { hasConfidentialityInstances: data['hasConfidentialityInstances'], hasListTemplates: data['hasListTemplates'] } }; + this.dialogRef = this.dialog.open(UserAdministrationRedirectModalComponent, this.config); + this.dialogRef.afterClosed().subscribe((result: any) => { + this.mode = 'delete'; + if (result) { + this.mode = result.processMode; + this.http.request('DELETE', '../rest/users/' + this.serialId + '/entities/' + entityId, { body: { 'mode': this.mode, 'newUser': result.newUser } }) + .subscribe((dataEntities: any) => { + this.user.entities = dataEntities.entities; + this.user.allEntities = dataEntities.allEntities; + if (this.headerService.user.id == this.serialId) { + this.headerService.resfreshCurrentUser(); + } + this.notify.success(this.translate.instant('lang.entityDeleted')); + }, (err) => { + this.notify.error(err.error.errors); + }); + } else { + this.maarchTree.toggleNode( + this.maarchTree.dataSource.data, + { + selected: true, + opened: true + }, + [ent.id] + ); + // $('#jstree').jstree('select_node', entityId); + this.mode = ''; } - this.notify.success(this.translate.instant('lang.entityDeleted')); - }, (err) => { - this.notify.error(err.error.errors); + this.dialogRef = null; }); - } else { - this.config = { panelClass: 'maarch-modal', data: { hasConfidentialityInstances: data['hasConfidentialityInstances'], hasListTemplates: data['hasListTemplates'] } }; - this.dialogRef = this.dialog.open(UserAdministrationRedirectModalComponent, this.config); - this.dialogRef.afterClosed().subscribe((result: any) => { - this.mode = 'delete'; - if (result) { - this.mode = result.processMode; - this.http.request('DELETE', '../rest/users/' + this.serialId + '/entities/' + entityId, { body: { 'mode': this.mode, 'newUser': result.newUser } }) - .subscribe((dataEntities: any) => { - this.user.entities = dataEntities.entities; - this.user.allEntities = dataEntities.allEntities; - if (this.headerService.user.id == this.serialId) { - this.headerService.resfreshCurrentUser(); - } - this.notify.success(this.translate.instant('lang.entityDeleted')); - }, (err) => { - this.notify.error(err.error.errors); - }); - } else { - $('#jstree').jstree('select_node', entityId); - this.mode = ''; - } - this.dialogRef = null; - }); - } + } + + }, (err) => { + this.notify.error(err.error.errors); + }); + }); - }, (err) => { - this.notify.error(err.error.errors); - }); } submitSignature() { @@ -911,11 +935,13 @@ export class UserAdministrationComponent implements OnInit { this.notify.error(err.error.errors); }); } else { - if (this.user.mode == 'rest') { - this.user.authorizedApi = this.user.authorizedApi.split('\n'); - console.log(this.user.authorizedApi); + const user = { + ...this.user + }; + if (this.user.mode === 'rest') { + user.authorizedApi = this.user.authorizedApi.split('\n')[0] !== '' ? this.user.authorizedApi.split('\n') : []; } - this.http.put('../rest/users/' + this.serialId, this.user) + this.http.put('../rest/users/' + this.serialId, user) .subscribe((data: any) => { if (this.headerService.user.id == this.serialId) { this.headerService.resfreshCurrentUser(); diff --git a/src/frontend/plugins/tree/maarch-flat-tree.component.html b/src/frontend/plugins/tree/maarch-flat-tree.component.html index 5cd3c465fe582c3440cf5e5ead4641d775983f9f..b2d9c081ffc7139bab1ff059bc5bdad7b79dc25d 100644 --- a/src/frontend/plugins/tree/maarch-flat-tree.component.html +++ b/src/frontend/plugins/tree/maarch-flat-tree.component.html @@ -1,10 +1,10 @@ <mat-form-field> <input matInput type="text" [formControl]="searchTerm" placeholder="{{'lang.searchEntities' | translate}}"> - <mat-hint align="end" [innerHTML]="'lang.hotkeyInfo' | translate"></mat-hint> + <mat-hint *ngIf="selectionPropagation" align="end" [innerHTML]="'lang.hotkeyInfo' | translate"></mat-hint> </mat-form-field> <div style="position: relative;padding-top: 20px;"> <div class="msgHotkey" *ngIf="holdShift" [title]="'lang.hotkeyTitle' | translate"> - <i class="fas fa-keyboard" [iinerHTML]=""></i> <span [innerHTML]="'lang.hotkeyMsg' | translate"></span> + <i class="fas fa-keyboard"></i> <span [innerHTML]="'lang.hotkeyMsg' | translate"></span> </div> <mat-tree [dataSource]="dataSource" [treeControl]="treeControl"> <mat-nested-tree-node *matTreeNodeDef="let node"> diff --git a/src/frontend/plugins/tree/maarch-flat-tree.component.ts b/src/frontend/plugins/tree/maarch-flat-tree.component.ts index 9ad27ef85446f4b95ac9742dcac2833fb4fd2ed1..37ff4365252f405032b77362b501a155eab0671b 100644 --- a/src/frontend/plugins/tree/maarch-flat-tree.component.ts +++ b/src/frontend/plugins/tree/maarch-flat-tree.component.ts @@ -28,11 +28,16 @@ export class MaarchFlatTreeComponent implements OnInit { @Input() rawData: any = []; + @Input() selectionPropagation: boolean = true; + @Input() openState: string = ''; + @Output() afterSelectNode = new EventEmitter<any>(); @Output() afterDeselectNode = new EventEmitter<any>(); holdShift: boolean = false; + defaultOpenedNodes: any[] = []; + treeControl = new NestedTreeControl<any>(node => node.children); dataSource = new MatTreeNestedDataSource<any>(); @@ -41,8 +46,13 @@ export class MaarchFlatTreeComponent implements OnInit { lastSelectedNodeIds: any[] = []; + pendingChildOf: any = {}; + temp: any = {}; + @HostListener('document:keydown.Shift', ['$event']) onKeydownHandler(event: KeyboardEvent) { - this.holdShift = true; + if (this.selectionPropagation) { + this.holdShift = true; + } } @HostListener('document:keyup.Shift', ['$event']) onKeyupHandler(event: KeyboardEvent) { this.holdShift = false; @@ -108,21 +118,42 @@ export class MaarchFlatTreeComponent implements OnInit { } initData(data: any = this.rawData) { + this.rawData = data; + + // Catch all nodes Ids to open tree nodes in root level + if (this.openState !== 'all') { + this.setDefaultOpened(); + } + this.rawData = data.map((item: any) => { return { ...item, - parent_id : item.parent_id === '#' ? null : item.parent_id, - state: { + parent_id: item.parent_id === '#' || item.parent_id === '' ? null : item.parent_id, + state: item.state !== undefined ? { selected: item.state.selected, - opened: item.state.opened, + opened: item.state.opened || this.defaultOpenedNodes.indexOf(item.id) > -1 || this.openState === 'all', disabled: item.state.disabled, - } + } : { + selected: false, + opened: this.defaultOpenedNodes.indexOf(item.id) > -1 || this.openState === 'all', + disabled: false, + } }; }); - this.rawData = this.sortPipe.transform(this.rawData, 'parent_id'); + let FlatToNested, flatToNested; - const nestedData = this.flatToNestedObject(this.rawData); + FlatToNested = require('flat-to-nested'); + flatToNested = new FlatToNested({ + id: 'id', + parent: 'parent_id', + children: 'children', + options : { deleteParent: false } + }); + + this.rawData = this.sortPipe.transform(this.rawData, 'text'); + let nestedData = flatToNested.convert(this.rawData); + nestedData = nestedData.children; this.dataSource.data = nestedData; this.treeControl.dataNodes = nestedData; @@ -139,6 +170,13 @@ export class MaarchFlatTreeComponent implements OnInit { ).subscribe(); } + setDefaultOpened() { + this.rawData.filter((item: any) => item.state !== undefined && item.state.opened).forEach((item: any) => { + this.defaultOpenedNodes = this.defaultOpenedNodes.concat(this.getParents([item])); + }); + this.defaultOpenedNodes = this.defaultOpenedNodes.map((node: any) => node.id); + } + getData(id: any) { return this.rawData.filter((elem: any) => elem.id === id)[0]; } @@ -147,74 +185,6 @@ export class MaarchFlatTreeComponent implements OnInit { return Array(it).fill(0).map((x, i) => i); } - flatToNestedObject(data: any) { - const nested = data.reduce((initial: any, value: any, index: any, original: any) => { - if (value.parent_id === '') { - if (initial.left.length) { - this.checkLeftOvers(initial.left, value); - } - delete value.parent_id; - value.root = true; - initial.nested.push(value); - initial.nested = this.sortPipe.transform(initial.nested, 'text'); - initial.nested = initial.nested.map((info: any, indexPar: number) => { - return { - ...info, - last: initial.nested.length - 1 === indexPar, - }; - }); - } 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; - possibleParents[i].children = this.sortPipe.transform(possibleParents[i].children, 'text'); - possibleParents[i].children = possibleParents[i].children.map((info: any, index: number) => { - return { - ...info, - last: possibleParents[i].children.length - 1 === index, - }; - }); - return true; - } else if (possibleParents[i].children) { - found = this.findParent(possibleParents[i].children, possibleChild); - } - } - return found; - } - hasChild = (_: number, node: any) => !!node.children && node.children.length > 0; selectNode(node: any) { @@ -252,7 +222,6 @@ export class MaarchFlatTreeComponent implements OnInit { // traverse throuh each node if (Array.isArray(data)) { // if data is an array data.forEach((d) => { - if (nodeIds.indexOf(d.id) > -1 || (this.holdShift && nodeIds.indexOf(d.parent_id) > -1)) { Object.keys(state).forEach(key => { if (d.state.disabled && key === 'opened') { @@ -295,6 +264,17 @@ export class MaarchFlatTreeComponent implements OnInit { } } + getParents(node: any[]) { + const res = this.rawData.filter((data: any) => data.id === node[node.length - 1].parent_id); + + if (res.length > 0) { + node.push(res[0]); + return this.getParents(node); + } else { + return node; + } + } + searchNode(data, term) { this.searchMode = term !== ''; // traverse throuh each node diff --git a/src/lang/lang-fr.json b/src/lang/lang-fr.json index 89dec32be4ad0934171abd38cbe34dc29888bb0a..f4c900a4b690a556d34676755ca3cca00a22fcd5 100644 --- a/src/lang/lang-fr.json +++ b/src/lang/lang-fr.json @@ -1885,6 +1885,7 @@ "enableField": "Activer le champ", "disableField": "Désactiver le champ", "emailSubject": "Objet du courriel", + "authorizedRoutes": "Routes autorisées", "infoImportusers": "La colonne <b>id</b> est utilisée pour identifier un utilisateur", "infoImportusers2": "Les colonnes <b>id</b> et <b>user_id</b> ne sont pas prises en compte lors de mise à jour d'utilisateurs", "hotkeyInfo": "Maintenez <b>SHIFT</b> pour propager vos selections aux sous-éléments",