1 /* eslint-disable no-shadow, @typescript-eslint/member-ordering */
2 import {Component, Input, OnDestroy, OnInit, Renderer2} from '@angular/core';
3 import {Observable, Subject, of, OperatorFunction} from 'rxjs';
4 import {DialogComponent} from '@eg/share/dialog/dialog.component';
5 import {IdlService, IdlObject} from '@eg/core/idl.service';
6 import {PcrudService} from '@eg/core/pcrud.service';
7 import {NgbModal, NgbTypeaheadSelectItemEvent} from '@ng-bootstrap/ng-bootstrap';
8 import {FormArray, FormBuilder} from '@angular/forms';
9 import {catchError, debounceTime, distinctUntilChanged, exhaustMap, map, takeUntil, tap, toArray} from 'rxjs/operators';
11 interface PermEntry { id: number; label: string; }
14 selector: 'eg-perm-group-map-dialog',
15 templateUrl: './perm-group-map-dialog.component.html'
19 * Ask the user which part is the lead part then merge others parts in.
21 export class PermGroupMapDialogComponent
22 extends DialogComponent implements OnInit, OnDestroy {
24 @Input() permGroup: IdlObject;
26 @Input() permissions: IdlObject[];
28 // List of grp-perm-map objects that relate to the selected permission
29 // group or are linked to a parent group.
30 @Input() permMaps: IdlObject[];
32 @Input() orgDepths: number[];
34 // Note we have all of the permissions on hand, but rendering the
35 // full list of permissions can caus sluggishness. Render async instead.
36 permEntries = this.permEntriesOperator();
37 permEntriesFormatter = (entry: PermEntry): string => entry.label;
38 selectedPermEntries: PermEntry[] = [];
40 // Permissions the user may apply to the current group.
41 trimmedPerms: IdlObject[] = [];
43 permMapsForm = this.fb.group({ newPermMaps: this.fb.array([]) });
45 return this.permMapsForm.controls.newPermMaps as FormArray;
48 onCreate = new Subject<void>();
49 onDestroy = new Subject<void>();
52 private idl: IdlService,
53 private pcrud: PcrudService,
54 private modal: NgbModal,
55 private renderer: Renderer2,
56 private fb: FormBuilder) {
62 this.permissions = this.permissions
63 .sort((a, b) => a.code() < b.code() ? -1 : 1);
66 tap(() => this.reset()),
67 takeUntil(this.onDestroy)
68 ).subscribe(() => this.focusPermSelector());
71 exhaustMap(() => this.create()),
72 takeUntil(this.onDestroy)
73 ).subscribe(success => this.close(success));
77 // Find entries whose code or description match the search term
78 private permEntriesOperator(): OperatorFunction<string, PermEntry[]> {
79 return term$ => term$.pipe(
80 // eslint-disable-next-line no-magic-numbers
82 map(term => (term ?? '').toLowerCase()),
83 distinctUntilChanged(),
84 map(term => this.permEntryResults(term))
88 private permEntryResults(term: string): PermEntry[] {
89 if (/^\s*$/.test(term)) {return [];}
91 return this.trimmedPerms.reduce<PermEntry[]>((entries, p) => {
92 if ((p.code().toLowerCase().includes(term) ||
93 (p.description() || '').toLowerCase().includes(term)) &&
94 !this.selectedPermEntries.find(s => s.id === p.id())
95 ) {entries.push({ id: p.id(), label: p.code() });}
101 this.permMapsForm = this.fb.group({
102 newPermMaps: this.fb.array([])
104 this.selectedPermEntries = [];
105 this.trimmedPerms = [];
107 this.permissions.forEach(p => {
109 // Prevent duplicate permissions, for-loop for early exit.
110 for (let idx = 0; idx < this.permMaps.length; idx++) {
111 const map = this.permMaps[idx];
112 if (map.perm().id() === p.id() &&
113 map.grp().id() === this.permGroup.id()) {
118 this.trimmedPerms.push(p);
122 private focusPermSelector(): void {
123 const el = this.renderer.selectRootElement(
126 if (el) {el.focus();}
129 select(event: NgbTypeaheadSelectItemEvent<PermEntry>): void {
130 event.preventDefault();
131 this.newPermMaps.push(this.fb.group({
132 ...event.item, depth: 0, grantable: false
134 this.selectedPermEntries.push({ ...event.item });
137 remove(index: number): void {
138 this.newPermMaps.removeAt(index);
139 this.selectedPermEntries.splice(index, 1);
140 if (!this.selectedPermEntries.length) {this.focusPermSelector();}
143 create(): Observable<boolean> {
144 const maps: IdlObject[] = this.newPermMaps.getRawValue().map(
145 ({ id, depth, grantable }) => {
146 const map = this.idl.create('pgpm');
148 map.grp(this.permGroup.id());
150 map.grantable(grantable ? 't' : 'f');
156 return this.pcrud.create(maps).pipe(
157 catchError(() => of(false)),
159 map(newMaps => !newMaps.includes(false))
163 ngOnDestroy(): void {
164 this.onDestroy.next();