@cleavelandprice/ngx-lib 3.1.9 → 3.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/active-directory/gui/components/user-chooser/user-chooser.component.d.ts +54 -0
  2. package/active-directory/gui/components/user-form-field/user-form-field.component.d.ts +27 -0
  3. package/active-directory/gui/gui.module.d.ts +20 -0
  4. package/active-directory/gui/index.d.ts +5 -0
  5. package/active-directory/gui/model/default-user-chooser-config.d.ts +2 -0
  6. package/active-directory/gui/model/default-user-chooser-filter-options-config.d.ts +2 -0
  7. package/active-directory/gui/model/default-user-chooser-pre-processing-group-config.d.ts +2 -0
  8. package/active-directory/gui/model/selectable-user.d.ts +5 -0
  9. package/active-directory/gui/model/user-chooser-data.d.ts +21 -0
  10. package/active-directory/gui/model/user-chooser-filter-options.d.ts +7 -0
  11. package/active-directory/gui/model/user-chooser-mode.d.ts +4 -0
  12. package/active-directory/gui/model/user-chooser-pre-processing-group.d.ts +6 -0
  13. package/active-directory/gui/public_api.d.ts +10 -0
  14. package/authentication/token/gui/token-gui.module.d.ts +6 -6
  15. package/esm2020/active-directory/active-directory.module.mjs +3 -2
  16. package/esm2020/active-directory/gui/cleavelandprice-ngx-lib-active-directory-gui.mjs +5 -0
  17. package/esm2020/active-directory/gui/components/user-chooser/user-chooser.component.mjs +352 -0
  18. package/esm2020/active-directory/gui/components/user-form-field/user-form-field.component.mjs +118 -0
  19. package/esm2020/active-directory/gui/gui.module.mjs +73 -0
  20. package/esm2020/active-directory/gui/model/default-user-chooser-config.mjs +18 -0
  21. package/esm2020/active-directory/gui/model/default-user-chooser-filter-options-config.mjs +8 -0
  22. package/esm2020/active-directory/gui/model/default-user-chooser-pre-processing-group-config.mjs +7 -0
  23. package/esm2020/active-directory/gui/model/selectable-user.mjs +2 -0
  24. package/esm2020/active-directory/gui/model/user-chooser-data.mjs +2 -0
  25. package/esm2020/active-directory/gui/model/user-chooser-filter-options.mjs +2 -0
  26. package/esm2020/active-directory/gui/model/user-chooser-mode.mjs +6 -0
  27. package/esm2020/active-directory/gui/model/user-chooser-pre-processing-group.mjs +2 -0
  28. package/esm2020/active-directory/gui/public_api.mjs +11 -0
  29. package/esm2020/authentication/token/gui/token-gui.module.mjs +9 -9
  30. package/fesm2015/cleavelandprice-ngx-lib-active-directory-gui.mjs +574 -0
  31. package/fesm2015/cleavelandprice-ngx-lib-active-directory-gui.mjs.map +1 -0
  32. package/fesm2015/cleavelandprice-ngx-lib-active-directory.mjs +2 -1
  33. package/fesm2015/cleavelandprice-ngx-lib-active-directory.mjs.map +1 -1
  34. package/fesm2015/cleavelandprice-ngx-lib-authentication-token-gui.mjs +9 -9
  35. package/fesm2015/cleavelandprice-ngx-lib-authentication-token-gui.mjs.map +1 -1
  36. package/fesm2020/cleavelandprice-ngx-lib-active-directory-gui.mjs +564 -0
  37. package/fesm2020/cleavelandprice-ngx-lib-active-directory-gui.mjs.map +1 -0
  38. package/fesm2020/cleavelandprice-ngx-lib-active-directory.mjs +2 -1
  39. package/fesm2020/cleavelandprice-ngx-lib-active-directory.mjs.map +1 -1
  40. package/fesm2020/cleavelandprice-ngx-lib-authentication-token-gui.mjs +9 -9
  41. package/fesm2020/cleavelandprice-ngx-lib-authentication-token-gui.mjs.map +1 -1
  42. package/package.json +9 -1
@@ -0,0 +1,564 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Component, Inject, ViewChild, EventEmitter, Output, Input, NgModule } from '@angular/core';
3
+ import * as i5 from '@angular/forms';
4
+ import { FormGroup, FormControl, ReactiveFormsModule } from '@angular/forms';
5
+ import * as i1 from '@angular/material/dialog';
6
+ import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
7
+ import * as i10 from '@angular/material/list';
8
+ import { MatSelectionList, MatListModule } from '@angular/material/list';
9
+ import { MatTableDataSource, MatTableModule } from '@angular/material/table';
10
+ import { of, forkJoin, debounceTime } from 'rxjs';
11
+ import * as i2 from '@cleavelandprice/ngx-lib/active-directory';
12
+ import * as i3 from '@cleavelandprice/ngx-lib/sharepoint';
13
+ import * as i4 from '@angular/common';
14
+ import { CommonModule } from '@angular/common';
15
+ import * as i6 from '@angular/material/button';
16
+ import { MatButtonModule } from '@angular/material/button';
17
+ import * as i7 from '@angular/material/form-field';
18
+ import { MatFormFieldModule } from '@angular/material/form-field';
19
+ import * as i8 from '@angular/material/icon';
20
+ import { MatIconModule } from '@angular/material/icon';
21
+ import * as i9 from '@angular/material/input';
22
+ import { MatInputModule } from '@angular/material/input';
23
+ import * as i11 from '@angular/material/progress-spinner';
24
+ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
25
+ import * as i12 from '@angular/material/slide-toggle';
26
+ import { MatSlideToggleModule } from '@angular/material/slide-toggle';
27
+ import * as i13 from '@angular/material/tooltip';
28
+ import { MatTooltipModule } from '@angular/material/tooltip';
29
+
30
+ const defaultUserChooserFilterOptionsConfig = {
31
+ accountName: true,
32
+ department: true,
33
+ groupMembership: false,
34
+ phoneNumber: true,
35
+ title: true
36
+ };
37
+
38
+ const defaultUserChooserPreProcessingGroupConfig = {
39
+ accountNames: [],
40
+ departments: [],
41
+ groups: [],
42
+ titles: []
43
+ };
44
+
45
+ var UserChooserMode;
46
+ (function (UserChooserMode) {
47
+ UserChooserMode[UserChooserMode["Single"] = 0] = "Single";
48
+ UserChooserMode[UserChooserMode["Multiple"] = 1] = "Multiple";
49
+ })(UserChooserMode || (UserChooserMode = {}));
50
+
51
+ const defaultUserChooserConfig = {
52
+ closeWhenSelectionMade: false,
53
+ includeDisabledUsers: false,
54
+ parentOu: undefined,
55
+ showGroupMembershipTooltips: false,
56
+ showPhone: true,
57
+ title: 'Select User(s)',
58
+ mode: UserChooserMode.Single,
59
+ returnUserObjects: false,
60
+ filterOptions: defaultUserChooserFilterOptionsConfig,
61
+ exclusions: defaultUserChooserPreProcessingGroupConfig,
62
+ inclusions: defaultUserChooserPreProcessingGroupConfig,
63
+ preSelections: defaultUserChooserPreProcessingGroupConfig
64
+ };
65
+
66
+ class UserChooserComponent {
67
+ //#endregion
68
+ //#region Lifecycle
69
+ constructor(data, dialogRef, activeDirectoryService, sharePointService) {
70
+ this.data = data;
71
+ this.dialogRef = dialogRef;
72
+ this.activeDirectoryService = activeDirectoryService;
73
+ this.sharePointService = sharePointService;
74
+ //#region Fields
75
+ this.selectionList = null;
76
+ this.filterForm = new FormGroup({
77
+ filter: new FormControl()
78
+ });
79
+ this.dataSource = null;
80
+ this.photos = null;
81
+ this.groups = null; // May or may not get populated, depending on config data sent in
82
+ this.data = {
83
+ ...defaultUserChooserConfig,
84
+ ...data
85
+ };
86
+ }
87
+ //#endregion
88
+ //#region Properties
89
+ get isMultiMode() {
90
+ return this.data.mode === UserChooserMode.Multiple;
91
+ }
92
+ get selectedOptions() {
93
+ return this.selectionList?.options.filter(o => o.selected) ?? [];
94
+ }
95
+ get filterValue() {
96
+ return this.filterForm.value.filter;
97
+ }
98
+ get adGroupsNeeded() {
99
+ // If any are these are true, then we need to pull AD groups for use later
100
+ return this.data.showGroupMembershipTooltips ||
101
+ this.data.exclusions.groups.length > 0 ||
102
+ this.data.inclusions.groups.length > 0 ||
103
+ this.data.preSelections.groups.length > 0 ||
104
+ this.data.filterOptions.groupMembership;
105
+ }
106
+ ngOnInit() {
107
+ this.getData();
108
+ this.listenForFilterChanges();
109
+ }
110
+ //#endregion
111
+ //#region Events
112
+ listSelectionChanged(options) {
113
+ if (this.data.mode === UserChooserMode.Single &&
114
+ this.selectedOptions.length &&
115
+ this.data.closeWhenSelectionMade) {
116
+ this.close();
117
+ }
118
+ }
119
+ optionSelectionChanged(option) {
120
+ const user = this.dataSource?.data
121
+ .find(u => u.sAMAccountName.toLocaleLowerCase() === option.value.sAMAccountName.toLowerCase());
122
+ if (user) {
123
+ user.selected = option.selected;
124
+ }
125
+ }
126
+ hideUnselectedChanged(selected) {
127
+ this.filterForm.get('filter')?.setValue(selected ? '{selected:true}' : null);
128
+ }
129
+ selectAll() {
130
+ this.selectionList?.selectAll();
131
+ if (this.selectionList?.options) {
132
+ this.listSelectionChanged(this.selectionList?.options.toArray());
133
+ }
134
+ }
135
+ deselectAll() {
136
+ this.selectionList?.deselectAll();
137
+ }
138
+ //#endregion
139
+ //#region Utilities
140
+ getData() {
141
+ const dataObservables = [
142
+ this.data.users ?
143
+ // Users were passed in
144
+ of(this.data.users) :
145
+ // Users need to be retrieved
146
+ this.activeDirectoryService.getUsers(false, this.data.includeDisabledUsers, this.data.parentOu),
147
+ this.data.photos ?
148
+ // Photos were passed in
149
+ of(this.data.photos) :
150
+ // Photos need to be retrieved
151
+ this.sharePointService.getPhotos()
152
+ ];
153
+ // Do we need AD groups?
154
+ if (this.adGroupsNeeded) {
155
+ dataObservables.push(this.activeDirectoryService.getGroups());
156
+ }
157
+ forkJoin(dataObservables)
158
+ //.subscribe(([users, photos]) => {
159
+ .subscribe(data => {
160
+ // Need groups in place before we handle exclusions/inclusions/pre-selections
161
+ this.groups = this.adGroupsNeeded ? data[2] : null;
162
+ let users = data[0];
163
+ users = this.includeUsersByAccountName(users);
164
+ users = this.includeUsersByGroupName(users);
165
+ users = this.includeUsersByDepartment(users);
166
+ users = this.includeUsersByTitle(users);
167
+ users = this.excludeUsersByAccountName(users);
168
+ users = this.excludeUsersByGroupName(users);
169
+ users = this.excludeUsersByDepartment(users);
170
+ users = this.excludeUsersByTitle(users);
171
+ users = this.preSelectUsersByAccountName(users);
172
+ users = this.preSelectUsersByGroupName(users);
173
+ users = this.preSelectUsersByDepartment(users);
174
+ users = this.preSelectUsersByTitle(users);
175
+ this.dataSource = new MatTableDataSource(users);
176
+ this.photos = data[1];
177
+ this.setFilterPredicate();
178
+ });
179
+ }
180
+ close() {
181
+ this.dialogRef.close(this.selectedOptions
182
+ .map(o => o.value)
183
+ .map(u => this.data.returnUserObjects ? u : u.sAMAccountName));
184
+ }
185
+ getPhoto(user) {
186
+ return this.photos?.find(p => (p.employeeNumber && p.employeeNumber.toString() === user.employeeNumber) ||
187
+ p.accountName?.toLowerCase() === user.sAMAccountName.toLowerCase() ||
188
+ p.displayName?.toLowerCase() === user.displayName.toLowerCase() ||
189
+ p.title?.toLowerCase() === user.displayName.toLowerCase())?.encodedAbsUrl;
190
+ }
191
+ //#region Exclusions - won't be included in the dataSource at all
192
+ excludeUsersByAccountName(users) {
193
+ this.data.exclusions.accountNames
194
+ .forEach(accountName => {
195
+ const index = users.findIndex(u => u.sAMAccountName.toLowerCase() === accountName.toLowerCase());
196
+ if (index === -1) {
197
+ return;
198
+ }
199
+ users.splice(index, 1);
200
+ });
201
+ return users;
202
+ }
203
+ excludeUsersByGroupName(users) {
204
+ this.data.exclusions.groups
205
+ .forEach(groupName => {
206
+ const group = this.groups?.find(g => g.name.toLowerCase() === groupName.toLowerCase());
207
+ if (!group) {
208
+ return;
209
+ }
210
+ group.member.forEach(groupMemberDn => {
211
+ const index = users.findIndex(u => u.distinguishedName === groupMemberDn);
212
+ if (index === -1) {
213
+ return;
214
+ }
215
+ users.splice(index, 1);
216
+ });
217
+ });
218
+ return users;
219
+ }
220
+ excludeUsersByDepartment(users) {
221
+ this.data.exclusions.departments
222
+ .forEach(department => {
223
+ const excludedUsers = users.filter(u => u.department?.toLowerCase() === department.toLowerCase());
224
+ excludedUsers.forEach(user => {
225
+ const index = users.indexOf(user);
226
+ users.splice(index, 1);
227
+ });
228
+ });
229
+ return users;
230
+ }
231
+ excludeUsersByTitle(users) {
232
+ this.data.exclusions.titles
233
+ .forEach(title => {
234
+ const excludedUsers = users.filter(u => u.title?.toLowerCase() === title.toLowerCase());
235
+ excludedUsers.forEach(user => {
236
+ const index = users.indexOf(user);
237
+ users.splice(index, 1);
238
+ });
239
+ });
240
+ return users;
241
+ }
242
+ //#endregion
243
+ //#region Inclusions - only these will be included in the dataSource
244
+ includeUsersByAccountName(users) {
245
+ return users.filter(user => this.data.inclusions.accountNames.length === 0 || // No account inclusions specified
246
+ this.data.inclusions.accountNames.find(accountName => accountName.toLowerCase() === user.sAMAccountName.toLowerCase()));
247
+ }
248
+ includeUsersByDepartment(users) {
249
+ return users.filter(user => this.data.inclusions.departments.length === 0 || // No department inclusions specified
250
+ this.data.inclusions.departments.find(department => department.toLowerCase() === user.department?.toLowerCase()));
251
+ }
252
+ includeUsersByTitle(users) {
253
+ return users.filter(user => this.data.inclusions.titles.length === 0 || // No title inclusions specified
254
+ this.data.inclusions.titles.find(title => title.toLowerCase() === user.title?.toLowerCase()));
255
+ }
256
+ includeUsersByGroupName(users) {
257
+ if (!this.data.inclusions.groups.length) {
258
+ return users; // No group inclusions specified
259
+ }
260
+ // Cheating a bit and leveraging the 'selected' property to keep track of who should be included
261
+ // Since inclusions happen befor pre-selections, we can do that, as long as we set 'selected' back to false when we're done
262
+ this.data.inclusions.groups
263
+ .forEach(groupName => {
264
+ const group = this.groups?.find(g => g.name.toLowerCase() === groupName.toLowerCase());
265
+ if (!group) {
266
+ return;
267
+ }
268
+ users
269
+ .filter(user => group.member.find(groupMemberDn => groupMemberDn === user.distinguishedName))
270
+ .forEach(user => user.selected = true);
271
+ });
272
+ // At this point, all of the appropriate users should have 'selected' set to true
273
+ const selectedUsers = users.filter(user => user.selected); // Subset of the original list
274
+ // Reset 'selected' to false, and return
275
+ selectedUsers.forEach(user => user.selected = false);
276
+ return selectedUsers;
277
+ }
278
+ //#endregion
279
+ //#region Pre-Selections
280
+ preSelectUsersByAccountName(users) {
281
+ this.data.preSelections.accountNames
282
+ .forEach(accountName => {
283
+ const user = users.find(u => u.sAMAccountName.toLowerCase() === accountName.toLowerCase());
284
+ if (!user) {
285
+ return;
286
+ }
287
+ user.selected = true;
288
+ });
289
+ return users;
290
+ }
291
+ preSelectUsersByDepartment(users) {
292
+ this.data.preSelections.departments
293
+ .forEach(department => users
294
+ .filter(u => u.department?.toLowerCase() === department.toLowerCase())
295
+ .forEach(user => user.selected = true));
296
+ return users;
297
+ }
298
+ preSelectUsersByTitle(users) {
299
+ this.data.preSelections.titles
300
+ .forEach(title => users
301
+ .filter(u => u.title?.toLowerCase() === title.toLowerCase())
302
+ .forEach(user => user.selected = true));
303
+ return users;
304
+ }
305
+ preSelectUsersByGroupName(users) {
306
+ this.data.preSelections.groups
307
+ .forEach(groupName => {
308
+ const group = this.groups?.find(g => g.name.toLowerCase() === groupName.toLowerCase());
309
+ if (!group) {
310
+ return;
311
+ }
312
+ group.member.forEach(groupMemberDn => {
313
+ const user = users.find(u => u.distinguishedName === groupMemberDn);
314
+ if (!user) {
315
+ return;
316
+ }
317
+ user.selected = true;
318
+ });
319
+ });
320
+ return users;
321
+ }
322
+ //#endregion
323
+ setFilterPredicate() {
324
+ if (!this.dataSource) {
325
+ return;
326
+ }
327
+ this.dataSource.filterPredicate = (user, filter) => {
328
+ if (filter === '{selected:true}') {
329
+ return user.selected;
330
+ }
331
+ else {
332
+ const regex = new RegExp(filter, 'i'); // Case-insensitive
333
+ let match = false; // The end result - whether or not a employee matches the filter
334
+ const filterProperties = ['displayName'];
335
+ if (this.data.filterOptions.accountName) {
336
+ filterProperties.push('sAMAccountName');
337
+ }
338
+ if (this.data.filterOptions.title) {
339
+ filterProperties.push('title');
340
+ }
341
+ if (this.data.filterOptions.department) {
342
+ filterProperties.push('department');
343
+ }
344
+ if (this.data.filterOptions.phoneNumber) {
345
+ filterProperties.push('telephoneNumber');
346
+ }
347
+ // Match on the properties configured above
348
+ filterProperties
349
+ .forEach(field => {
350
+ // @ts-ignore
351
+ if (user[field] && user[field].match(regex)) {
352
+ match = true;
353
+ }
354
+ });
355
+ // Match on group membership (group name)
356
+ if (this.data.filterOptions.groupMembership) {
357
+ user.memberOf.forEach(groupDn => {
358
+ const group = this.groups?.find(group => group.distinguishedName === groupDn && group.name.match(regex));
359
+ if (group) {
360
+ match = true;
361
+ }
362
+ });
363
+ }
364
+ return match;
365
+ }
366
+ };
367
+ }
368
+ listenForFilterChanges() {
369
+ this.filterForm.get('filter')?.valueChanges
370
+ .pipe(debounceTime(500))
371
+ .subscribe(() => this.dataSource.filter = this.filterForm.value.filter?.trim().toLowerCase());
372
+ }
373
+ tooltip(user) {
374
+ return this.data.showGroupMembershipTooltips ?
375
+ user.memberOf
376
+ .map(groupDn => this.groups
377
+ ?.find(group => group.distinguishedName === groupDn)?.name)
378
+ .sort((a, b) => a > b ? 1 : -1)
379
+ .join('\n') :
380
+ '';
381
+ }
382
+ }
383
+ UserChooserComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.1", ngImport: i0, type: UserChooserComponent, deps: [{ token: MAT_DIALOG_DATA }, { token: i1.MatDialogRef }, { token: i2.ActiveDirectoryService }, { token: i3.SharePointService }], target: i0.ɵɵFactoryTarget.Component });
384
+ UserChooserComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.2.1", type: UserChooserComponent, selector: "user-chooser", viewQueries: [{ propertyName: "selectionList", first: true, predicate: MatSelectionList, descendants: true }], ngImport: i0, template: "<div id=\"dialog-title\">\n <h2 mat-dialog-title>{{ data.title }}</h2>\n\n <div>\n {{ selectedOptions.length }} selected\n </div>\n</div>\n\n<mat-dialog-content>\n <div *ngIf=\"!(dataSource && photos)\">\n <mat-spinner [diameter]=\"50\"></mat-spinner>\n <h2 id=\"spinner-message\">Getting users and photos...</h2>\n </div>\n\n <ng-container *ngIf=\"dataSource && photos\">\n <form [formGroup]=\"filterForm\">\n <mat-form-field *ngIf=\"dataSource\">\n <input matInput placeholder=\"Search\" formControlName=\"filter\">\n\n <button *ngIf=\"filterForm.value.filter\" matSuffix mat-icon-button aria-label=\"Clear\" (click)=\"filterForm.get('filter')?.setValue('')\">\n <mat-icon fontIcon=\"close\"></mat-icon>\n </button>\n </mat-form-field>\n </form>\n\n <mat-selection-list [multiple]=\"isMultiMode\"\n (selectionChange)=\"listSelectionChanged($event.options)\">\n\n <mat-list-option *ngFor=\"let user of dataSource?.filteredData\" [value]=\"user\" [selected]=\"user.selected\"\n (selectedChange)=\"optionSelectionChanged(option)\" #option>\n\n <img *ngIf=\"getPhoto(user)\" matListAvatar alt=\"\" [src]=\"getPhoto(user)\">\n\n <mat-icon *ngIf=\"!getPhoto(user)\" matListAvatar class=\"no-photo\"\n fontIcon=\"account_circle\">\n </mat-icon>\n \n <div class=\"user\">\n <div [matTooltip]=\"tooltip(user)\" class=\"user-name\">\n {{ user.displayName }}\n <div *ngIf=\"data.showPhone && user.telephoneNumber\">{{ user.telephoneNumber }}</div>\n </div>\n \n <div>{{ user.department }}</div>\n \n <div class=\"title\">{{ user.title }}</div>\n </div>\n </mat-list-option>\n </mat-selection-list>\n </ng-container>\n</mat-dialog-content>\n\n<mat-dialog-actions>\n <ng-container *ngIf=\"dataSource && photos\">\n <mat-slide-toggle *ngIf=\"selectedOptions.length\" (change)=\"hideUnselectedChanged($event.checked)\">\n Hide Unselected\n </mat-slide-toggle>\n\n <button mat-raised-button color=\"primary\" (click)=\"selectAll()\"\n *ngIf=\"isMultiMode\">\n <mat-icon fontIcon=\"select_all\"></mat-icon>\n <span class=\"hide-mobile\">Select All</span>\n </button>\n \n <button mat-raised-button color=\"primary\" (click)=\"deselectAll()\"\n *ngIf=\"isMultiMode && selectedOptions.length\">\n <mat-icon fontIcon=\"clear_all\"></mat-icon>\n <span class=\"hide-mobile\">Clear All</span>\n </button>\n\n <button mat-raised-button color=\"primary\" (click)=\"close()\" matTooltip=\"Accept your selection\"\n [disabled]=\"!selectedOptions.length\">\n <mat-icon fontIcon=\"check\"></mat-icon>\n <span class=\"hide-mobile\">Ok</span>\n </button>\n </ng-container>\n\n <button mat-raised-button color=\"warn\" mat-dialog-close matTooltip=\"Close this window\">\n <mat-icon fontIcon=\"close\"></mat-icon>\n <span class=\"hide-mobile\">Cancel</span>\n </button>\n</mat-dialog-actions>", styles: ["::ng-deep .mat-tooltip{font-size:1em!important;white-space:pre-line}mat-dialog-content{min-width:50vw}#dialog-title{display:flex;justify-content:space-between}.mat-spinner{margin:0 auto}#spinner-message{text-align:center;padding:.5em}mat-form-field{width:100%}.title{font-style:italic;color:#757575}img,.no-photo{width:2rem;height:2rem;font-size:2.3rem!important;border-radius:100%}.hide-mobile{display:none}mat-dialog-actions{display:grid;grid-auto-flow:column;grid-gap:1em;justify-content:center}.user-name{display:flex;justify-content:space-between}.user-name>div:last-child{margin-right:10rem;font-weight:400;font-style:italic;color:#757575}@media (min-width:801px){.user{display:grid;grid-template-columns:1fr 1fr 1fr}.user>div:first-child{font-weight:700}.hide-mobile{display:initial}}\n", ".material-icons{font-family:Material Icons;font-weight:400;font-style:normal;font-size:24px;display:inline-block;line-height:1;text-transform:none;letter-spacing:normal;word-wrap:normal;white-space:nowrap;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;-moz-osx-font-smoothing:grayscale;font-feature-settings:\"liga\"}\n"], dependencies: [{ kind: "directive", type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i5.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i5.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i5.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i5.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i5.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i5.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i6.MatButton, selector: "button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }, { kind: "directive", type: i1.MatDialogClose, selector: "[mat-dialog-close], [matDialogClose]", inputs: ["aria-label", "type", "mat-dialog-close", "matDialogClose"], exportAs: ["matDialogClose"] }, { kind: "directive", type: i1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: i1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "component", type: i7.MatFormField, selector: "mat-form-field", inputs: ["color", "appearance", "hideRequiredMarker", "hintLabel", "floatLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i7.MatSuffix, selector: "[matSuffix]" }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i9.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "directive", type: i10.MatListAvatarCssMatStyler, selector: "[mat-list-avatar], [matListAvatar]" }, { kind: "component", type: i10.MatSelectionList, selector: "mat-selection-list", inputs: ["disableRipple", "color", "compareWith", "disabled", "multiple"], outputs: ["selectionChange"], exportAs: ["matSelectionList"] }, { kind: "component", type: i10.MatListOption, selector: "mat-list-option", inputs: ["disableRipple", "checkboxPosition", "color", "value", "disabled", "selected"], outputs: ["selectedChange"], exportAs: ["matListOption"] }, { kind: "component", type: i11.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "diameter", "strokeWidth", "mode", "value"], exportAs: ["matProgressSpinner"] }, { kind: "component", type: i12.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["disabled", "disableRipple", "color", "tabIndex"], exportAs: ["matSlideToggle"] }, { kind: "directive", type: i13.MatTooltip, selector: "[matTooltip]", exportAs: ["matTooltip"] }] });
385
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.1", ngImport: i0, type: UserChooserComponent, decorators: [{
386
+ type: Component,
387
+ args: [{ selector: 'user-chooser', template: "<div id=\"dialog-title\">\n <h2 mat-dialog-title>{{ data.title }}</h2>\n\n <div>\n {{ selectedOptions.length }} selected\n </div>\n</div>\n\n<mat-dialog-content>\n <div *ngIf=\"!(dataSource && photos)\">\n <mat-spinner [diameter]=\"50\"></mat-spinner>\n <h2 id=\"spinner-message\">Getting users and photos...</h2>\n </div>\n\n <ng-container *ngIf=\"dataSource && photos\">\n <form [formGroup]=\"filterForm\">\n <mat-form-field *ngIf=\"dataSource\">\n <input matInput placeholder=\"Search\" formControlName=\"filter\">\n\n <button *ngIf=\"filterForm.value.filter\" matSuffix mat-icon-button aria-label=\"Clear\" (click)=\"filterForm.get('filter')?.setValue('')\">\n <mat-icon fontIcon=\"close\"></mat-icon>\n </button>\n </mat-form-field>\n </form>\n\n <mat-selection-list [multiple]=\"isMultiMode\"\n (selectionChange)=\"listSelectionChanged($event.options)\">\n\n <mat-list-option *ngFor=\"let user of dataSource?.filteredData\" [value]=\"user\" [selected]=\"user.selected\"\n (selectedChange)=\"optionSelectionChanged(option)\" #option>\n\n <img *ngIf=\"getPhoto(user)\" matListAvatar alt=\"\" [src]=\"getPhoto(user)\">\n\n <mat-icon *ngIf=\"!getPhoto(user)\" matListAvatar class=\"no-photo\"\n fontIcon=\"account_circle\">\n </mat-icon>\n \n <div class=\"user\">\n <div [matTooltip]=\"tooltip(user)\" class=\"user-name\">\n {{ user.displayName }}\n <div *ngIf=\"data.showPhone && user.telephoneNumber\">{{ user.telephoneNumber }}</div>\n </div>\n \n <div>{{ user.department }}</div>\n \n <div class=\"title\">{{ user.title }}</div>\n </div>\n </mat-list-option>\n </mat-selection-list>\n </ng-container>\n</mat-dialog-content>\n\n<mat-dialog-actions>\n <ng-container *ngIf=\"dataSource && photos\">\n <mat-slide-toggle *ngIf=\"selectedOptions.length\" (change)=\"hideUnselectedChanged($event.checked)\">\n Hide Unselected\n </mat-slide-toggle>\n\n <button mat-raised-button color=\"primary\" (click)=\"selectAll()\"\n *ngIf=\"isMultiMode\">\n <mat-icon fontIcon=\"select_all\"></mat-icon>\n <span class=\"hide-mobile\">Select All</span>\n </button>\n \n <button mat-raised-button color=\"primary\" (click)=\"deselectAll()\"\n *ngIf=\"isMultiMode && selectedOptions.length\">\n <mat-icon fontIcon=\"clear_all\"></mat-icon>\n <span class=\"hide-mobile\">Clear All</span>\n </button>\n\n <button mat-raised-button color=\"primary\" (click)=\"close()\" matTooltip=\"Accept your selection\"\n [disabled]=\"!selectedOptions.length\">\n <mat-icon fontIcon=\"check\"></mat-icon>\n <span class=\"hide-mobile\">Ok</span>\n </button>\n </ng-container>\n\n <button mat-raised-button color=\"warn\" mat-dialog-close matTooltip=\"Close this window\">\n <mat-icon fontIcon=\"close\"></mat-icon>\n <span class=\"hide-mobile\">Cancel</span>\n </button>\n</mat-dialog-actions>", styles: ["::ng-deep .mat-tooltip{font-size:1em!important;white-space:pre-line}mat-dialog-content{min-width:50vw}#dialog-title{display:flex;justify-content:space-between}.mat-spinner{margin:0 auto}#spinner-message{text-align:center;padding:.5em}mat-form-field{width:100%}.title{font-style:italic;color:#757575}img,.no-photo{width:2rem;height:2rem;font-size:2.3rem!important;border-radius:100%}.hide-mobile{display:none}mat-dialog-actions{display:grid;grid-auto-flow:column;grid-gap:1em;justify-content:center}.user-name{display:flex;justify-content:space-between}.user-name>div:last-child{margin-right:10rem;font-weight:400;font-style:italic;color:#757575}@media (min-width:801px){.user{display:grid;grid-template-columns:1fr 1fr 1fr}.user>div:first-child{font-weight:700}.hide-mobile{display:initial}}\n", ".material-icons{font-family:Material Icons;font-weight:400;font-style:normal;font-size:24px;display:inline-block;line-height:1;text-transform:none;letter-spacing:normal;word-wrap:normal;white-space:nowrap;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;-moz-osx-font-smoothing:grayscale;font-feature-settings:\"liga\"}\n"] }]
388
+ }], ctorParameters: function () { return [{ type: undefined, decorators: [{
389
+ type: Inject,
390
+ args: [MAT_DIALOG_DATA]
391
+ }] }, { type: i1.MatDialogRef }, { type: i2.ActiveDirectoryService }, { type: i3.SharePointService }]; }, propDecorators: { selectionList: [{
392
+ type: ViewChild,
393
+ args: [MatSelectionList]
394
+ }] } });
395
+
396
+ class UserFormFieldComponent {
397
+ //#endregion
398
+ //#region Lifecycle
399
+ constructor(dialog, activeDirectoryService, sharePointService) {
400
+ this.dialog = dialog;
401
+ this.activeDirectoryService = activeDirectoryService;
402
+ this.sharePointService = sharePointService;
403
+ //#region Fields
404
+ this.usersSelected = new EventEmitter();
405
+ this.placeholder = 'Select User(s)';
406
+ this.clickable = true;
407
+ /*
408
+ @Input() mode = UserChooserMode.Multiple;
409
+ @Input() preSelectAccountNames = true;
410
+ @Input() includeDisabledUsers = false;
411
+ @Input() parentOu = undefined;*/
412
+ this.accountNames = [];
413
+ this.users = undefined;
414
+ this.photos = undefined;
415
+ }
416
+ //#endregion
417
+ //#region Properties
418
+ get isEdge() {
419
+ return window.navigator.userAgent.indexOf('Edge') > -1;
420
+ }
421
+ ngOnInit() {
422
+ this.userChooserData = {
423
+ ...defaultUserChooserConfig,
424
+ ...this.userChooserData
425
+ };
426
+ // If users have been pre-selected in the config, store those in accountNames, which drives the UI of this component
427
+ this.accountNames = this.userChooserData.preSelections.accountNames;
428
+ this.getData();
429
+ }
430
+ //#endregion
431
+ //#region Events
432
+ selectUser() {
433
+ if (!this.clickable) {
434
+ return;
435
+ }
436
+ const data = {
437
+ ...this.userChooserData,
438
+ users: this.users,
439
+ photos: this.photos,
440
+ };
441
+ this.dialog.open(UserChooserComponent, {
442
+ data: data
443
+ })
444
+ .afterClosed()
445
+ .subscribe(accountNames => {
446
+ if (!accountNames) {
447
+ return;
448
+ }
449
+ this.accountNames = accountNames;
450
+ this.usersSelected.emit(accountNames);
451
+ });
452
+ }
453
+ //#endregion
454
+ //#region Utilities
455
+ getData() {
456
+ forkJoin([
457
+ this.userChooserData.users ?
458
+ // Users were passed in
459
+ of(this.userChooserData.users) :
460
+ // Users need to be retrieved
461
+ this.activeDirectoryService.getUsers(false, this.userChooserData.includeDisabledUsers, this.userChooserData.parentOu),
462
+ this.userChooserData.photos ?
463
+ // Photos were passed in
464
+ of(this.userChooserData.photos) :
465
+ // Photos need to be retrieved
466
+ this.sharePointService.getPhotos()
467
+ ])
468
+ .subscribe(([users, photos]) => {
469
+ this.users = users;
470
+ this.photos = photos;
471
+ });
472
+ }
473
+ getUser(accountName) {
474
+ return this.users?.find(u => u.sAMAccountName.toLowerCase() === accountName.toLowerCase());
475
+ }
476
+ getPhoto(accountName) {
477
+ const user = this.getUser(accountName);
478
+ if (!user) {
479
+ return undefined;
480
+ }
481
+ return this.photos?.find(p => (p.employeeNumber && p.employeeNumber.toString() === user.employeeNumber) ||
482
+ p.accountName?.toLowerCase() === user.sAMAccountName.toLowerCase() ||
483
+ p.displayName?.toLowerCase() === user.displayName.toLowerCase() ||
484
+ p.title?.toLowerCase() === user.displayName.toLowerCase())?.encodedAbsUrl;
485
+ }
486
+ }
487
+ UserFormFieldComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.1", ngImport: i0, type: UserFormFieldComponent, deps: [{ token: i1.MatDialog }, { token: i2.ActiveDirectoryService }, { token: i3.SharePointService }], target: i0.ɵɵFactoryTarget.Component });
488
+ UserFormFieldComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.2.1", type: UserFormFieldComponent, selector: "user-form-field", inputs: { placeholder: "placeholder", clickable: "clickable", userChooserData: "userChooserData" }, outputs: { usersSelected: "usersSelected" }, ngImport: i0, template: "<!-- In Microsoft Edge, selectEmployee() never gets called if the input fields are disabled\n So... I'm dynamically setting the disabled property based on whether or not the browser is Edge.\n It's definitely a better experience if the field IS disabled.\n Otherwise, it's confusing - especially because Edge is SO SLOW to display the resulting dialog window.\n-->\n\n<div (click)=\"selectUser()\" [class.clickable]=\"clickable\">\n <mat-form-field *ngIf=\"!accountNames?.length\">\n <input matInput [disabled]=\"!isEdge\" [placeholder]=\"placeholder\">\n </mat-form-field>\n \n <div *ngIf=\"accountNames?.length\"\n [class.grid]=\"accountNames.length > 1\"\n [class.three]=\"accountNames.length > 2\"\n [class.four]=\"accountNames.length > 3\"\n [class.five]=\"accountNames.length > 4\">\n\n <div *ngFor=\"let accountName of accountNames; let i=index\" class=\"user\">\n <!--<img *ngIf=\"accountName && empUtils.getEmployee(accountName)\" alt=\"\" [src]=\"empUtils.getEmployee(accountName) | employeePhotoUrl:cacheService.photos\">-->\n <img *ngIf=\"getPhoto(accountName)\" matListAvatar alt=\"\" [src]=\"getPhoto(accountName)\">\n \n <mat-form-field>\n <input matInput [disabled]=\"!isEdge\" [placeholder]=\"i === 0 ? placeholder : ''\"\n [value]=\"accountName ? getUser(accountName)?.displayName : accountName\">\n </mat-form-field>\n </div>\n </div>\n </div>", styles: [".user{display:grid;grid-template-columns:auto 1fr;grid-gap:.5em;align-items:center}.clickable,.clickable input{cursor:pointer!important}img{width:2em;height:2em;border-radius:100%}@media (min-width:801px){.grid{display:grid;grid-template-columns:repeat(2,1fr);grid-gap:1em}.three{grid-template-columns:repeat(3,1fr)}.four{grid-template-columns:repeat(4,1fr)}.five{grid-template-columns:repeat(5,1fr)}::ng-deep .grid mat-form-field{width:15vw}}\n"], dependencies: [{ kind: "directive", type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i7.MatFormField, selector: "mat-form-field", inputs: ["color", "appearance", "hideRequiredMarker", "hintLabel", "floatLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i9.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "directive", type: i10.MatListAvatarCssMatStyler, selector: "[mat-list-avatar], [matListAvatar]" }] });
489
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.1", ngImport: i0, type: UserFormFieldComponent, decorators: [{
490
+ type: Component,
491
+ args: [{ selector: 'user-form-field', template: "<!-- In Microsoft Edge, selectEmployee() never gets called if the input fields are disabled\n So... I'm dynamically setting the disabled property based on whether or not the browser is Edge.\n It's definitely a better experience if the field IS disabled.\n Otherwise, it's confusing - especially because Edge is SO SLOW to display the resulting dialog window.\n-->\n\n<div (click)=\"selectUser()\" [class.clickable]=\"clickable\">\n <mat-form-field *ngIf=\"!accountNames?.length\">\n <input matInput [disabled]=\"!isEdge\" [placeholder]=\"placeholder\">\n </mat-form-field>\n \n <div *ngIf=\"accountNames?.length\"\n [class.grid]=\"accountNames.length > 1\"\n [class.three]=\"accountNames.length > 2\"\n [class.four]=\"accountNames.length > 3\"\n [class.five]=\"accountNames.length > 4\">\n\n <div *ngFor=\"let accountName of accountNames; let i=index\" class=\"user\">\n <!--<img *ngIf=\"accountName && empUtils.getEmployee(accountName)\" alt=\"\" [src]=\"empUtils.getEmployee(accountName) | employeePhotoUrl:cacheService.photos\">-->\n <img *ngIf=\"getPhoto(accountName)\" matListAvatar alt=\"\" [src]=\"getPhoto(accountName)\">\n \n <mat-form-field>\n <input matInput [disabled]=\"!isEdge\" [placeholder]=\"i === 0 ? placeholder : ''\"\n [value]=\"accountName ? getUser(accountName)?.displayName : accountName\">\n </mat-form-field>\n </div>\n </div>\n </div>", styles: [".user{display:grid;grid-template-columns:auto 1fr;grid-gap:.5em;align-items:center}.clickable,.clickable input{cursor:pointer!important}img{width:2em;height:2em;border-radius:100%}@media (min-width:801px){.grid{display:grid;grid-template-columns:repeat(2,1fr);grid-gap:1em}.three{grid-template-columns:repeat(3,1fr)}.four{grid-template-columns:repeat(4,1fr)}.five{grid-template-columns:repeat(5,1fr)}::ng-deep .grid mat-form-field{width:15vw}}\n"] }]
492
+ }], ctorParameters: function () { return [{ type: i1.MatDialog }, { type: i2.ActiveDirectoryService }, { type: i3.SharePointService }]; }, propDecorators: { usersSelected: [{
493
+ type: Output
494
+ }], placeholder: [{
495
+ type: Input
496
+ }], clickable: [{
497
+ type: Input
498
+ }], userChooserData: [{
499
+ type: Input
500
+ }] } });
501
+
502
+ class GuiModule {
503
+ }
504
+ GuiModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.1", ngImport: i0, type: GuiModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
505
+ GuiModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.2.1", ngImport: i0, type: GuiModule, declarations: [UserChooserComponent,
506
+ UserFormFieldComponent], imports: [CommonModule,
507
+ ReactiveFormsModule,
508
+ MatButtonModule,
509
+ MatDialogModule,
510
+ MatFormFieldModule,
511
+ MatIconModule,
512
+ MatInputModule,
513
+ MatListModule,
514
+ MatProgressSpinnerModule,
515
+ MatSlideToggleModule,
516
+ MatTableModule,
517
+ MatTooltipModule], exports: [UserChooserComponent,
518
+ UserFormFieldComponent] });
519
+ GuiModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.2.1", ngImport: i0, type: GuiModule, imports: [CommonModule,
520
+ ReactiveFormsModule,
521
+ MatButtonModule,
522
+ MatDialogModule,
523
+ MatFormFieldModule,
524
+ MatIconModule,
525
+ MatInputModule,
526
+ MatListModule,
527
+ MatProgressSpinnerModule,
528
+ MatSlideToggleModule,
529
+ MatTableModule,
530
+ MatTooltipModule] });
531
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.1", ngImport: i0, type: GuiModule, decorators: [{
532
+ type: NgModule,
533
+ args: [{
534
+ declarations: [
535
+ UserChooserComponent,
536
+ UserFormFieldComponent
537
+ ],
538
+ imports: [
539
+ CommonModule,
540
+ ReactiveFormsModule,
541
+ MatButtonModule,
542
+ MatDialogModule,
543
+ MatFormFieldModule,
544
+ MatIconModule,
545
+ MatInputModule,
546
+ MatListModule,
547
+ MatProgressSpinnerModule,
548
+ MatSlideToggleModule,
549
+ MatTableModule,
550
+ MatTooltipModule
551
+ ],
552
+ exports: [
553
+ UserChooserComponent,
554
+ UserFormFieldComponent
555
+ ]
556
+ }]
557
+ }] });
558
+
559
+ /**
560
+ * Generated bundle index. Do not edit.
561
+ */
562
+
563
+ export { GuiModule, UserChooserComponent, UserChooserMode, UserFormFieldComponent, defaultUserChooserConfig, defaultUserChooserFilterOptionsConfig, defaultUserChooserPreProcessingGroupConfig };
564
+ //# sourceMappingURL=cleavelandprice-ngx-lib-active-directory-gui.mjs.map