]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/eg2/src/app/staff/admin/server/perm-group-map-dialog.component.ts
LP 2061136 follow-up: ng lint --fix
[Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / admin / server / perm-group-map-dialog.component.ts
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';
10
11 interface PermEntry { id: number; label: string; }
12
13 @Component({
14     selector: 'eg-perm-group-map-dialog',
15     templateUrl: './perm-group-map-dialog.component.html'
16 })
17
18 /**
19  * Ask the user which part is the lead part then merge others parts in.
20  */
21 export class PermGroupMapDialogComponent
22     extends DialogComponent implements OnInit, OnDestroy {
23
24     @Input() permGroup: IdlObject;
25
26     @Input() permissions: IdlObject[];
27
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[];
31
32     @Input() orgDepths: number[];
33
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[] = [];
39
40     // Permissions the user may apply to the current group.
41     trimmedPerms: IdlObject[] = [];
42
43     permMapsForm = this.fb.group({ newPermMaps: this.fb.array([]) });
44     get newPermMaps() {
45         return this.permMapsForm.controls.newPermMaps as FormArray;
46     }
47
48     onCreate = new Subject<void>();
49     onDestroy = new Subject<void>();
50
51     constructor(
52         private idl: IdlService,
53         private pcrud: PcrudService,
54         private modal: NgbModal,
55         private renderer: Renderer2,
56         private fb: FormBuilder) {
57         super(modal);
58     }
59
60     ngOnInit() {
61
62         this.permissions = this.permissions
63             .sort((a, b) => a.code() < b.code() ? -1 : 1);
64
65         this.onOpen$.pipe(
66             tap(() => this.reset()),
67             takeUntil(this.onDestroy)
68         ).subscribe(() => this.focusPermSelector());
69
70         this.onCreate.pipe(
71             exhaustMap(() => this.create()),
72             takeUntil(this.onDestroy)
73         ).subscribe(success => this.close(success));
74
75     }
76
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
81             debounceTime(300),
82             map(term => (term ?? '').toLowerCase()),
83             distinctUntilChanged(),
84             map(term => this.permEntryResults(term))
85         );
86     }
87
88     private permEntryResults(term: string): PermEntry[] {
89         if (/^\s*$/.test(term)) {return [];}
90
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() });}
96             return entries;
97         }, []);
98     }
99
100     private reset() {
101         this.permMapsForm = this.fb.group({
102             newPermMaps: this.fb.array([])
103         });
104         this.selectedPermEntries = [];
105         this.trimmedPerms = [];
106
107         this.permissions.forEach(p => {
108
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()) {
114                     return;
115                 }
116             }
117
118             this.trimmedPerms.push(p);
119         });
120     }
121
122     private focusPermSelector(): void {
123         const el = this.renderer.selectRootElement(
124             '#select-perms'
125         );
126         if (el) {el.focus();}
127     }
128
129     select(event: NgbTypeaheadSelectItemEvent<PermEntry>): void {
130         event.preventDefault();
131         this.newPermMaps.push(this.fb.group({
132             ...event.item, depth: 0, grantable: false
133         }));
134         this.selectedPermEntries.push({ ...event.item });
135     }
136
137     remove(index: number): void {
138         this.newPermMaps.removeAt(index);
139         this.selectedPermEntries.splice(index, 1);
140         if (!this.selectedPermEntries.length) {this.focusPermSelector();}
141     }
142
143     create(): Observable<boolean> {
144         const maps: IdlObject[] = this.newPermMaps.getRawValue().map(
145             ({ id, depth, grantable }) => {
146                 const map = this.idl.create('pgpm');
147
148                 map.grp(this.permGroup.id());
149                 map.perm(id);
150                 map.grantable(grantable ? 't' : 'f');
151                 map.depth(depth);
152
153                 return map;
154             });
155
156         return this.pcrud.create(maps).pipe(
157             catchError(() => of(false)),
158             toArray(),
159             map(newMaps => !newMaps.includes(false))
160         );
161     }
162
163     ngOnDestroy(): void {
164         this.onDestroy.next();
165     }
166 }