<div class="col-lg-7">{{permGroup.name()}}</div>
</div>
<div class="row mt-1 pt-1">
- <div class="col-lg-5" i18n>New Permission</div>
- <div class="col-lg-7">
- <eg-combobox [asyncDataSource]="permEntries"
- (onChange)="perm = $event ? $event.id : null">
- </eg-combobox>
+ <div class="col-lg-5">
+ <label for="select-perms" i18n>New Permission</label>
</div>
- </div>
- <div class="row mt-1 pt-1">
- <div class="col-lg-5" i18n>Depth</div>
- <div class="col-lg-7">
- <select [(ngModel)]="depth" class="p-1">
- <option *ngFor="let d of orgDepths" value="{{d}}">{{d}}</option>
- </select>
- </div>
- </div>
- <div class="row mt-1 pt-1">
- <div class="col-lg-5" i18n>Grantable</div>
<div class="col-lg-7">
- <input type="checkbox" [(ngModel)]="grantable"/>
+ <input type="text" id="select-perms" #selectPerms
+ [ngbTypeahead]="permEntries"
+ [inputFormatter]="permEntriesFormatter"
+ [resultFormatter]="permEntriesFormatter"
+ [editable]="false"
+ (selectItem)="select($event); selectPerms.value=''">
</div>
</div>
+ <ng-container *ngFor="let map of newPermMaps.controls; let i = index">
+ <ng-container [formGroup]="map">
+ <div class="row mt-1 pt-1">
+ <div class="col-lg-12">
+ <hr>
+ <h5 i18n>{{map.controls.label.value}}</h5>
+ </div>
+ </div>
+ <div class="row row-cols-4 mt-1 pt-1">
+ <div class="col">
+ <label [attr.for]="'depth-'+map.controls.id.value"
+ i18n>Depth
+ </label>
+ </div>
+ <div class="col">
+ <select formControlName="depth" class="p-1"
+ id="depth-{{map.controls.id.value}}">
+ <option *ngFor="let d of orgDepths" value="{{d}}">{{d}}</option>
+ </select>
+ </div>
+ <div class="col">
+ <label [attr.for]="'grantable-'+map.controls.id.value"
+ i18n>Grantable
+ </label>
+ </div>
+ <div class="col">
+ <input type="checkbox" formControlName="grantable"
+ id="grantable-{{map.controls.id.value}}">
+ </div>
+ </div>
+ <div class="row mt-1 pt-1">
+ <div class="col-lg-12">
+ <button type="button" class="btn btn-danger"
+ (click)="remove(i)"
+ i18n>Remove
+ </button>
+ </div>
+ </div>
+ </ng-container>
+ </ng-container>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success"
- (click)="create()" i18n>Create</button>
+ [disabled]="!selectedPermEntries.length"
+ (click)="onCreate.next()" i18n>Create</button>
<button type="button" class="btn btn-warning"
(click)="close()" i18n>Cancel</button>
</div>
-import {Component, Input, ViewChild, TemplateRef, OnInit} from '@angular/core';
-import {Observable, from, EMPTY, throwError} from 'rxjs';
+import {Component, Input, OnDestroy, OnInit, Renderer2} from '@angular/core';
+import {Observable, Subject, of, OperatorFunction} from 'rxjs';
import {DialogComponent} from '@eg/share/dialog/dialog.component';
import {IdlService, IdlObject} from '@eg/core/idl.service';
import {PcrudService} from '@eg/core/pcrud.service';
-import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
-import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
+import {NgbModal, NgbTypeaheadSelectItemEvent} from '@ng-bootstrap/ng-bootstrap';
+import {FormArray, FormBuilder} from '@angular/forms';
+import {catchError, debounceTime, distinctUntilChanged, exhaustMap, map, takeUntil, tap, toArray} from 'rxjs/operators';
+
+interface PermEntry { id: number; label: string; }
@Component({
selector: 'eg-perm-group-map-dialog',
* Ask the user which part is the lead part then merge others parts in.
*/
export class PermGroupMapDialogComponent
- extends DialogComponent implements OnInit {
+ extends DialogComponent implements OnInit, OnDestroy {
@Input() permGroup: IdlObject;
// Note we have all of the permissions on hand, but rendering the
// full list of permissions can caus sluggishness. Render async instead.
- permEntries: (term: string) => Observable<ComboboxEntry>;
+ permEntries = this.permEntriesOperator();
+ permEntriesFormatter = (entry: PermEntry): string => entry.label;
+ selectedPermEntries: PermEntry[] = [];
// Permissions the user may apply to the current group.
- trimmedPerms: IdlObject[];
+ trimmedPerms: IdlObject[] = [];
+
+ permMapsForm = this.fb.group({ newPermMaps: this.fb.array([]) });
+ get newPermMaps() {
+ return this.permMapsForm.controls.newPermMaps as FormArray;
+ }
- depth: number;
- grantable: boolean;
- perm: number;
+ onCreate = new Subject<void>();
+ onDestroy = new Subject<void>();
constructor(
private idl: IdlService,
private pcrud: PcrudService,
- private modal: NgbModal) {
+ private modal: NgbModal,
+ private renderer: Renderer2,
+ private fb: FormBuilder) {
super(modal);
}
ngOnInit() {
- this.depth = 0;
- this.grantable = false;
this.permissions = this.permissions
.sort((a, b) => a.code() < b.code() ? -1 : 1);
- this.onOpen$.subscribe(() => this.trimPermissions());
+ this.onOpen$.pipe(
+ tap(() => this.reset()),
+ takeUntil(this.onDestroy)
+ ).subscribe(() => this.focusPermSelector());
+ this.onCreate.pipe(
+ exhaustMap(() => this.create()),
+ takeUntil(this.onDestroy)
+ ).subscribe(success => this.close(success));
- this.permEntries = (term: string) => {
- if (term === null || term === undefined) { return EMPTY; }
- term = ('' + term).toLowerCase();
+ }
- // Find entries whose code or description match the search term
+ // Find entries whose code or description match the search term
+ private permEntriesOperator(): OperatorFunction<string, PermEntry[]> {
+ return term$ => term$.pipe(
+ debounceTime(300),
+ map(term => (term ?? '').toLowerCase()),
+ distinctUntilChanged(),
+ map(term => this.permEntryResults(term))
+ );
+ }
- const entries: ComboboxEntry[] = [];
- this.trimmedPerms.forEach(p => {
- if (p.code().toLowerCase().includes(term) ||
- (p.description() || '').toLowerCase().includes(term)) {
- entries.push({id: p.id(), label: p.code()});
- }
- });
+ private permEntryResults(term: string): PermEntry[] {
+ if (/^\s*$/.test(term)) return [];
- return from(entries);
- };
+ return this.trimmedPerms.reduce<PermEntry[]>((entries, p) => {
+ if ((p.code().toLowerCase().includes(term) ||
+ (p.description() || '').toLowerCase().includes(term)) &&
+ !this.selectedPermEntries.find(s => s.id === p.id())
+ ) entries.push({ id: p.id(), label: p.code() });
+ return entries;
+ }, []);
}
- trimPermissions() {
+ private reset() {
+ this.permMapsForm = this.fb.group({
+ newPermMaps: this.fb.array([])
+ });
+ this.selectedPermEntries = [];
this.trimmedPerms = [];
this.permissions.forEach(p => {
});
}
- create() {
- const map = this.idl.create('pgpm');
+ private focusPermSelector(): void {
+ const el = this.renderer.selectRootElement(
+ '#select-perms'
+ );
+ if (el) el.focus();
+ }
+
+ select(event: NgbTypeaheadSelectItemEvent<PermEntry>): void {
+ event.preventDefault();
+ this.newPermMaps.push(this.fb.group({
+ ...event.item, depth: 0, grantable: false
+ }));
+ this.selectedPermEntries.push({ ...event.item });
+ }
+
+ remove(index: number): void {
+ this.newPermMaps.removeAt(index);
+ this.selectedPermEntries.splice(index, 1);
+ if (!this.selectedPermEntries.length)
+ this.focusPermSelector();
+ }
+
+ create(): Observable<boolean> {
+ const maps: IdlObject[] = this.newPermMaps.getRawValue().map(
+ ({ id, depth, grantable }) => {
+ const map = this.idl.create('pgpm');
+
+ map.grp(this.permGroup.id());
+ map.perm(id);
+ map.grantable(grantable ? 't' : 'f');
+ map.depth(depth);
- map.grp(this.permGroup.id());
- map.perm(this.perm);
- map.grantable(this.grantable ? 't' : 'f');
- map.depth(this.depth);
+ return map;
+ });
- this.pcrud.create(map).subscribe(
- newMap => this.close(newMap),
- err => throwError(err)
+ return this.pcrud.create(maps).pipe(
+ catchError(() => of(false)),
+ toArray(),
+ map(newMaps => !newMaps.includes(false))
);
}
-}
+
+ ngOnDestroy(): void {
+ this.onDestroy.next();
+ }
+}