ichec-angular-core 0.1.1 → 0.1.2

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.
@@ -1,8 +1,7 @@
1
- import { map, catchError, tap, mergeMap, throwError, BehaviorSubject, mergeAll, merge, debounceTime, distinctUntilChanged, Subscription, of, finalize } from 'rxjs';
1
+ import { map, catchError, tap, mergeMap, throwError, BehaviorSubject, mergeAll, of, merge, debounceTime, distinctUntilChanged, Subscription, finalize } from 'rxjs';
2
2
  import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http';
3
3
  import * as i0 from '@angular/core';
4
4
  import { InjectionToken, inject, Injectable, input, signal, Component, viewChild, computed, output } from '@angular/core';
5
- import { NgIf, Location, NgTemplateOutlet, TitleCasePipe } from '@angular/common';
6
5
  import * as i1 from '@angular/router';
7
6
  import { RouterModule, RouterOutlet, Router, ActivatedRoute } from '@angular/router';
8
7
  import * as i2 from '@angular/material/toolbar';
@@ -17,6 +16,7 @@ import * as i1$1 from '@angular/material/sidenav';
17
16
  import { MatSidenavContent, MatSidenavModule } from '@angular/material/sidenav';
18
17
  import * as i2$2 from '@angular/material/list';
19
18
  import { MatListModule } from '@angular/material/list';
19
+ import { NgIf, Location, NgTemplateOutlet, TitleCasePipe } from '@angular/common';
20
20
  import * as i1$2 from '@angular/forms';
21
21
  import { FormsModule, FormControl, ReactiveFormsModule, FormBuilder } from '@angular/forms';
22
22
  import * as i3$2 from '@angular/material/input';
@@ -78,6 +78,21 @@ class PortalMember {
78
78
  constructor(params = {}) {
79
79
  Object.assign(this, params);
80
80
  }
81
+ static getInitials(member) {
82
+ let first_initial = "";
83
+ let second_initial = "";
84
+ if (member.first_name) {
85
+ first_initial = member.first_name[0];
86
+ }
87
+ else {
88
+ first_initial = member.username[0];
89
+ }
90
+ if (member.first_name) {
91
+ second_initial = member.last_name[0];
92
+ }
93
+ const combined = first_initial + second_initial;
94
+ return combined.toUpperCase();
95
+ }
81
96
  }
82
97
  class Group {
83
98
  static typename = "group";
@@ -96,15 +111,14 @@ class Address {
96
111
  id = 0;
97
112
  url = "";
98
113
  line1 = "";
99
- line2 = "";
100
- line3 = "";
101
- city = "";
114
+ line2 = null;
115
+ line3 = null;
116
+ city = null;
102
117
  region = "";
103
- postcode = "";
118
+ postcode = null;
104
119
  country = "";
105
120
  country_name = "";
106
121
  country_flag = "";
107
- members = [];
108
122
  constructor(params = {}) {
109
123
  Object.assign(this, params);
110
124
  }
@@ -261,7 +275,8 @@ class RestService {
261
275
  }
262
276
 
263
277
  class ItemService extends RestService {
264
- itemType = "";
278
+ typenamePlural = "";
279
+ typename = "";
265
280
  canEdit() {
266
281
  return false;
267
282
  }
@@ -274,6 +289,9 @@ class ItemService extends RestService {
274
289
  canView() {
275
290
  return false;
276
291
  }
292
+ typePlural() {
293
+ return this.typenamePlural ? this.typenamePlural : this.typename + "s";
294
+ }
277
295
  instantiateType(_) {
278
296
  throw new Error('Not Implemented');
279
297
  }
@@ -294,7 +312,7 @@ class UserService extends ItemService {
294
312
  */
295
313
  loggedInUser = new BehaviorSubject(null);
296
314
  _url = PortalMember.plural;
297
- itemType = PortalMember.plural;
315
+ typename = PortalMember.typename;
298
316
  permissions = new Map();
299
317
  instantiateType(item) {
300
318
  return new PortalMember(item);
@@ -392,9 +410,51 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.1", ngImpor
392
410
  }]
393
411
  }] });
394
412
 
395
- class AddressService extends ItemService {
413
+ class ItemWithUserService extends ItemService {
414
+ userService = inject(UserService);
415
+ userItems = new BehaviorSubject([]);
416
+ canCreate() {
417
+ return this.userService.hasAddPermission(this.permissionName());
418
+ }
419
+ canView() {
420
+ return this.userService.hasViewPermission(this.permissionName());
421
+ }
422
+ canEdit() {
423
+ return this.userService.hasEditPermission(this.permissionName());
424
+ }
425
+ canDelete() {
426
+ return this.userService.hasDeletePermission(this.permissionName());
427
+ }
428
+ permissionName() {
429
+ return this.typename.replace("_", "");
430
+ }
431
+ getUserItems() {
432
+ if (this.userService.loggedInUser.value) {
433
+ return this.getForUser(this.userService.loggedInUser.value);
434
+ }
435
+ return of(new Paginated());
436
+ }
437
+ refreshUserItems(user) {
438
+ if (user) {
439
+ this.getUserItems().subscribe(items => this.userItems.next(items.results));
440
+ }
441
+ else {
442
+ this.userItems.next([]);
443
+ }
444
+ }
445
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.1", ngImport: i0, type: ItemWithUserService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
446
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.1", ngImport: i0, type: ItemWithUserService, providedIn: 'root' });
447
+ }
448
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.1", ngImport: i0, type: ItemWithUserService, decorators: [{
449
+ type: Injectable,
450
+ args: [{
451
+ providedIn: 'root'
452
+ }]
453
+ }] });
454
+
455
+ class AddressService extends ItemWithUserService {
396
456
  _url = Address.plural;
397
- itemType = Address.plural;
457
+ typename = Address.typename;
398
458
  instantiateType(item) {
399
459
  return new Address(item);
400
460
  }
@@ -408,23 +468,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.1", ngImpor
408
468
  }]
409
469
  }] });
410
470
 
411
- class GroupService extends ItemService {
412
- userService = inject(UserService);
413
- userItems = new BehaviorSubject([]);
471
+ class GroupService extends ItemWithUserService {
414
472
  _url = Group.plural;
415
- itemType = Group.plural;
473
+ typename = Group.typename;
416
474
  constructor() {
417
475
  super();
418
476
  this.userService.loggedInUser.subscribe(user => this.refreshUserItems(user));
419
477
  }
420
- refreshUserItems(user) {
421
- if (user) {
422
- this.getForUser(user).subscribe(items => this.userItems.next(items.results));
423
- }
424
- else {
425
- this.userItems.next([]);
426
- }
427
- }
428
478
  instantiateType(item) {
429
479
  return new Group(item);
430
480
  }
@@ -438,25 +488,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.1", ngImpor
438
488
  }]
439
489
  }], ctorParameters: () => [] });
440
490
 
441
- class OrganizationService extends ItemService {
491
+ class OrganizationService extends ItemWithUserService {
442
492
  _url = Organization.plural;
443
- itemType = Organization.plural;
444
- userService = inject(UserService);
493
+ typename = Organization.typename;
445
494
  instantiateType(item) {
446
495
  return new Organization(item);
447
496
  }
448
- canCreate() {
449
- return this.userService.hasAddPermission(Organization.typename);
450
- }
451
- canView() {
452
- return this.userService.hasViewPermission(Organization.typename);
453
- }
454
- canEdit() {
455
- return this.userService.hasEditPermission(Organization.typename);
456
- }
457
- canDelete() {
458
- return this.userService.hasDeletePermission(Organization.typename);
459
- }
460
497
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.1", ngImport: i0, type: OrganizationService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
461
498
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.1", ngImport: i0, type: OrganizationService, providedIn: 'root' });
462
499
  }
@@ -504,6 +541,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.1", ngImpor
504
541
  }]
505
542
  }], ctorParameters: () => [] });
506
543
 
544
+ class MockItemService {
545
+ canEdit() {
546
+ return false;
547
+ }
548
+ canDelete() {
549
+ return false;
550
+ }
551
+ canCreate() {
552
+ return false;
553
+ }
554
+ typePlural() {
555
+ return "mock_plurals";
556
+ }
557
+ }
558
+
507
559
  class TopBarComponent {
508
560
  title = input(...(ngDevMode ? [undefined, { debugName: "title" }] : []));
509
561
  user = signal(null, ...(ngDevMode ? [{ debugName: "user" }] : []));
@@ -517,12 +569,23 @@ class TopBarComponent {
517
569
  onLogout() {
518
570
  this.userService.logout();
519
571
  }
572
+ getInitials() {
573
+ const user = this.user();
574
+ if (user) {
575
+ return PortalMember.getInitials(user);
576
+ }
577
+ return "";
578
+ }
520
579
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.1", ngImport: i0, type: TopBarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
521
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.1", type: TopBarComponent, isStandalone: true, selector: "lib-top-bar", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "\n@if(user(); as user){\n<mat-toolbar style=\"height:60px\">\n <a mat-icon-button aria-label=\"Home\" [routerLink]=\"['home']\">\n <mat-icon>home</mat-icon>\n </a>\n <span>{{title()}}</span>\n <span class=\"topbar-spacer\"></span>\n <span>{{user.username}}</span>\n \n <button mat-icon-button aria-label=\"User Profile\" [matMenuTriggerFor]=\"profile_menu\">\n <mat-icon>person</mat-icon>\n </button>\n\n <mat-menu #profile_menu=\"matMenu\">\n <button mat-menu-item (click)=\"onLogout()\">Log Out</button>\n </mat-menu>\n</mat-toolbar>\n}\n", styles: [".topbar-spacer{flex:1 1 auto}\n"], dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: MatToolbarModule }, { kind: "component", type: i2.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i5.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i5.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i5.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }] });
580
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.1", type: TopBarComponent, isStandalone: true, selector: "lib-top-bar", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "\n@if(user(); as user){\n<mat-toolbar style=\"height:60px\">\n <a mat-icon-button aria-label=\"Home\" [routerLink]=\"['home']\">\n <mat-icon>home</mat-icon>\n </a>\n <span>{{title()}}</span>\n <span class=\"topbar-spacer\"></span>\n <span>{{user.username}}</span>\n \n <button mat-icon-button style=\"margin: 5px\" aria-label=\"User Profile\" [matMenuTriggerFor]=\"profile_menu\">\n <mat-icon>person</mat-icon>\n </button>\n\n <mat-menu #profile_menu=\"matMenu\">\n <a mat-menu-item aria-label=\"My Profile\" [routerLink]=\"['/members/detail', user.id]\">\n <mat-icon>manage_accounts</mat-icon>\n <span>My Profile</span>\n </a> \n <a mat-menu-item aria-label=\"Get Support\" [routerLink]=\"['/feedback']\">\n <mat-icon>contact_support</mat-icon>\n <span>Get Support</span>\n </a> \n <button mat-menu-item (click)=\"onLogout()\">\n <mat-icon>logout</mat-icon>\n <span>Log Out</span>\n </button>\n\n </mat-menu>\n</mat-toolbar>\n}\n", styles: [".topbar-spacer{flex:1 1 auto}\n"], dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: MatToolbarModule }, { kind: "component", type: i2.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i5.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i5.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i5.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }] });
522
581
  }
523
582
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.1", ngImport: i0, type: TopBarComponent, decorators: [{
524
583
  type: Component,
525
- args: [{ selector: 'lib-top-bar', imports: [NgIf, RouterModule, MatToolbarModule, MatIconModule, MatButtonModule, MatMenuModule], template: "\n@if(user(); as user){\n<mat-toolbar style=\"height:60px\">\n <a mat-icon-button aria-label=\"Home\" [routerLink]=\"['home']\">\n <mat-icon>home</mat-icon>\n </a>\n <span>{{title()}}</span>\n <span class=\"topbar-spacer\"></span>\n <span>{{user.username}}</span>\n \n <button mat-icon-button aria-label=\"User Profile\" [matMenuTriggerFor]=\"profile_menu\">\n <mat-icon>person</mat-icon>\n </button>\n\n <mat-menu #profile_menu=\"matMenu\">\n <button mat-menu-item (click)=\"onLogout()\">Log Out</button>\n </mat-menu>\n</mat-toolbar>\n}\n", styles: [".topbar-spacer{flex:1 1 auto}\n"] }]
584
+ args: [{ selector: 'lib-top-bar', imports: [RouterModule,
585
+ MatToolbarModule,
586
+ MatIconModule,
587
+ MatButtonModule,
588
+ MatMenuModule], template: "\n@if(user(); as user){\n<mat-toolbar style=\"height:60px\">\n <a mat-icon-button aria-label=\"Home\" [routerLink]=\"['home']\">\n <mat-icon>home</mat-icon>\n </a>\n <span>{{title()}}</span>\n <span class=\"topbar-spacer\"></span>\n <span>{{user.username}}</span>\n \n <button mat-icon-button style=\"margin: 5px\" aria-label=\"User Profile\" [matMenuTriggerFor]=\"profile_menu\">\n <mat-icon>person</mat-icon>\n </button>\n\n <mat-menu #profile_menu=\"matMenu\">\n <a mat-menu-item aria-label=\"My Profile\" [routerLink]=\"['/members/detail', user.id]\">\n <mat-icon>manage_accounts</mat-icon>\n <span>My Profile</span>\n </a> \n <a mat-menu-item aria-label=\"Get Support\" [routerLink]=\"['/feedback']\">\n <mat-icon>contact_support</mat-icon>\n <span>Get Support</span>\n </a> \n <button mat-menu-item (click)=\"onLogout()\">\n <mat-icon>logout</mat-icon>\n <span>Log Out</span>\n </button>\n\n </mat-menu>\n</mat-toolbar>\n}\n", styles: [".topbar-spacer{flex:1 1 auto}\n"] }]
526
589
  }] });
527
590
 
528
591
  class LeftNavComponent {
@@ -582,6 +645,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.1", ngImpor
582
645
  args: [{ selector: 'lib-landing', imports: [FormsModule, MatCardModule, MatFormFieldModule, MatInputModule, MatButtonModule, MatIconModule, NgIf], template: "\n @if(message(); as message){\n <p>{{message}}</p>\n }\n \n <mat-card style=\"text-align:center;\">\n <form class=\"base-form\" #loginForm=\"ngForm\" (ngSubmit)=\"login()\">\n <mat-card-content>\n <mat-form-field class=\"form-field\">\n <input matInput\n placeholder=\"Username\"\n type=\"text\"\n [(ngModel)]=\"loginUser.name\"\n name=\"username\"\n required>\n </mat-form-field>\n <mat-form-field class=\"form-field\">\n <input matInput\n placeholder=\"Password\"\n type=\"password\"\n [(ngModel)]=\"loginUser.password\"\n name=\"password\"\n required>\n </mat-form-field>\n </mat-card-content>\n <div *ngIf=\"loginError\" class=\"error\">\n <mat-icon>error</mat-icon><p>{{loginError()}}</p>\n </div>\n <button mat-flat-button\n type=\"submit\"\n [disabled]=\"!loginForm.form.valid\">Log In</button>\n </form>\n </mat-card>\n\n\n \n", styles: [":host{display:flex;align-items:center;flex-grow:1;background-color:#a5b3c9;flex-direction:column}.base-form{padding:10px}.form-field{display:flex}\n"] }]
583
646
  }] });
584
647
 
648
+ class FeedbackComponent {
649
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.1", ngImport: i0, type: FeedbackComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
650
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.1", type: FeedbackComponent, isStandalone: true, selector: "lib-feedback", ngImport: i0, template: "<p>feedback works!</p>\n", styles: [""] });
651
+ }
652
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.1", ngImport: i0, type: FeedbackComponent, decorators: [{
653
+ type: Component,
654
+ args: [{ selector: 'lib-feedback', imports: [], template: "<p>feedback works!</p>\n" }]
655
+ }] });
656
+
657
+ class AvatarComponent {
658
+ text = input("", ...(ngDevMode ? [{ debugName: "text" }] : []));
659
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.1", ngImport: i0, type: AvatarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
660
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.1.1", type: AvatarComponent, isStandalone: true, selector: "lib-avatar", inputs: { text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"container\"> <span>{{text()}}</span> </div>", styles: [".container{width:36px;height:36px;border-radius:100%;text-align:center;justify-content:center;align-content:center;background-color:#d3d3d3}.container span{line-height:8px;color:#000}\n"] });
661
+ }
662
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.1", ngImport: i0, type: AvatarComponent, decorators: [{
663
+ type: Component,
664
+ args: [{ selector: 'lib-avatar', imports: [], template: "<div class=\"container\"> <span>{{text()}}</span> </div>", styles: [".container{width:36px;height:36px;border-radius:100%;text-align:center;justify-content:center;align-content:center;background-color:#d3d3d3}.container span{line-height:8px;color:#000}\n"] }]
665
+ }] });
666
+
585
667
  class DetailView {
586
668
  item = signal(null, ...(ngDevMode ? [{ debugName: "item" }] : []));
587
669
  route = inject(ActivatedRoute);
@@ -593,6 +675,9 @@ class DetailView {
593
675
  onInit() {
594
676
  this.getItem();
595
677
  }
678
+ title() {
679
+ return this.itemService.typename.replace("_", " ");
680
+ }
596
681
  onItemAvailable(item) {
597
682
  this.item.set(item);
598
683
  this.userService.loggedInUser.subscribe(user => { if (user) {
@@ -621,16 +706,15 @@ class DetailView {
621
706
  }
622
707
 
623
708
  class EditView {
624
- typename = "";
625
709
  createMode = signal(false, ...(ngDevMode ? [{ debugName: "createMode" }] : []));
626
- heading = computed(() => { const prefix = this.createMode() ? "New " : "Edit "; return prefix + this.typename; }, ...(ngDevMode ? [{ debugName: "heading" }] : []));
710
+ heading = computed(() => { const prefix = this.createMode() ? "New " : "Edit "; return prefix + this.getTitle(); }, ...(ngDevMode ? [{ debugName: "heading" }] : []));
627
711
  item = signal(null, ...(ngDevMode ? [{ debugName: "item" }] : []));
628
712
  _route = inject(ActivatedRoute);
629
713
  _location = inject(Location);
630
714
  _userService = inject(UserService);
631
- _restService;
632
- constructor(_restService) {
633
- this._restService = _restService;
715
+ itemService;
716
+ constructor(itemService) {
717
+ this.itemService = itemService;
634
718
  }
635
719
  onInit() {
636
720
  this.getItem();
@@ -646,6 +730,7 @@ class EditView {
646
730
  this._location.back();
647
731
  }
648
732
  submit() {
733
+ console.log("submit called");
649
734
  if (this.item()) {
650
735
  this.save();
651
736
  }
@@ -661,8 +746,14 @@ class EditView {
661
746
  cancel() {
662
747
  this.goBack();
663
748
  }
749
+ getTitle() {
750
+ if (this.itemService && this.itemService.typename) {
751
+ return this.itemService.typename.replace("_", " ");
752
+ }
753
+ return "";
754
+ }
664
755
  fetchRestOptions() {
665
- this._restService.getOptions().subscribe(options => this.onOptions(options));
756
+ this.itemService.getOptions().subscribe(options => this.onOptions(options));
666
757
  }
667
758
  onUploadedFileReady(_file) {
668
759
  }
@@ -671,13 +762,13 @@ class EditView {
671
762
  createItem() {
672
763
  const item = this.item();
673
764
  if (item) {
674
- this._restService.postItem(item).subscribe(item => this.saveFiles(item));
765
+ this.itemService.postItem(item).subscribe(item => this.saveFiles(item));
675
766
  }
676
767
  }
677
768
  updateItem() {
678
769
  const item = this.item();
679
770
  if (item) {
680
- this._restService.putItem(item).subscribe(item => this.saveFiles(item));
771
+ this.itemService.putItem(item).subscribe(item => this.saveFiles(item));
681
772
  }
682
773
  }
683
774
  saveFiles(item) {
@@ -703,7 +794,7 @@ class EditView {
703
794
  }
704
795
  else {
705
796
  const id_str = this._route.snapshot.paramMap.get('id');
706
- this._restService.getItem(Number(id_str))
797
+ this.itemService.getItem(Number(id_str))
707
798
  .subscribe(item => {
708
799
  this.item.set(item);
709
800
  this.onItemAvailable();
@@ -726,23 +817,6 @@ class EditView {
726
817
  }
727
818
  }
728
819
 
729
- class ListView {
730
- _route = inject(ActivatedRoute);
731
- userService = inject(UserService);
732
- user = signal(null, ...(ngDevMode ? [{ debugName: "user" }] : []));
733
- getUser() {
734
- this.userService.loggedInUser.subscribe(user => {
735
- if (user) {
736
- this.user.set(user);
737
- }
738
- });
739
- }
740
- isSelfList() {
741
- const url_segments = this._route.snapshot.url;
742
- return (url_segments.length == 2) && (url_segments[1].path == "self");
743
- }
744
- }
745
-
746
820
  class SelectTableComponent {
747
821
  itemType = input("", ...(ngDevMode ? [{ debugName: "itemType" }] : []));
748
822
  selected = input([], ...(ngDevMode ? [{ debugName: "selected" }] : []));
@@ -751,7 +825,7 @@ class SelectTableComponent {
751
825
  itemAdded = output();
752
826
  itemRemoved = output();
753
827
  searchChanged = output();
754
- columnNames = computed(() => this.columns().map(c => c.name), ...(ngDevMode ? [{ debugName: "columnNames" }] : []));
828
+ columnNames = computed(() => { const names = this.columns().map(c => c.name); names.push("remove"); return names; }, ...(ngDevMode ? [{ debugName: "columnNames" }] : []));
755
829
  searchControl = new FormControl('');
756
830
  table = viewChild(MatTable, ...(ngDevMode ? [{ debugName: "table" }] : []));
757
831
  ngOnInit() {
@@ -765,6 +839,7 @@ class SelectTableComponent {
765
839
  if (this.searchControl.value) {
766
840
  this.itemAdded.emit(this.searchControl.value);
767
841
  }
842
+ this.searchControl.reset();
768
843
  this.table()?.renderRows();
769
844
  }
770
845
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.1", ngImport: i0, type: SelectTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
@@ -793,12 +868,6 @@ class ListTableViewComponent {
793
868
  columnNames = computed(() => this.columns().map(c => c.name), ...(ngDevMode ? [{ debugName: "columnNames" }] : []));
794
869
  sort = viewChild(MatSort, ...(ngDevMode ? [{ debugName: "sort" }] : []));
795
870
  paginator = viewChild(MatPaginator, ...(ngDevMode ? [{ debugName: "paginator" }] : []));
796
- ngOnInit() {
797
- const data_source = this.dataSource();
798
- if (data_source) {
799
- data_source.fetch(new ItemQuery({ page_size: this.initialPageSize }));
800
- }
801
- }
802
871
  ngAfterViewInit() {
803
872
  const paginator = this.paginator();
804
873
  const sort = this.sort();
@@ -855,9 +924,6 @@ class ListScrollViewComponent {
855
924
  pageSize = input(20, ...(ngDevMode ? [{ debugName: "pageSize" }] : []));
856
925
  dataSource = input(...(ngDevMode ? [undefined, { debugName: "dataSource" }] : []));
857
926
  listItemTemplate = input(null, ...(ngDevMode ? [{ debugName: "listItemTemplate" }] : []));
858
- ngOnInit() {
859
- this.reset();
860
- }
861
927
  reset() {
862
928
  const data_source = this.dataSource();
863
929
  if (!data_source) {
@@ -926,6 +992,8 @@ class ListDataSource extends DataSource {
926
992
  loading = signal(true, ...(ngDevMode ? [{ debugName: "loading" }] : []));
927
993
  searchTerm = signal("", ...(ngDevMode ? [{ debugName: "searchTerm" }] : []));
928
994
  pageSize = signal(20, ...(ngDevMode ? [{ debugName: "pageSize" }] : []));
995
+ hasFetched = signal(false, ...(ngDevMode ? [{ debugName: "hasFetched" }] : []));
996
+ sourceIsEmpty = computed(() => this.length() == 0 && this.hasFetched() && !this.searchTerm(), ...(ngDevMode ? [{ debugName: "sourceIsEmpty" }] : []));
929
997
  consumerType;
930
998
  items = new BehaviorSubject([]);
931
999
  itemService;
@@ -933,6 +1001,7 @@ class ListDataSource extends DataSource {
933
1001
  fetchedPages = new Set();
934
1002
  constructor(itemService, consumerType = "table") {
935
1003
  super();
1004
+ console.log("created");
936
1005
  this.itemService = itemService;
937
1006
  this.consumerType = consumerType;
938
1007
  }
@@ -976,6 +1045,7 @@ class ListDataSource extends DataSource {
976
1045
  }
977
1046
  onResponse(response) {
978
1047
  this.length.set(response.count);
1048
+ this.hasFetched.set(true);
979
1049
  if (this.hasTableConsumer()) {
980
1050
  this.items.next(response.results);
981
1051
  }
@@ -1032,10 +1102,13 @@ class ListViewComponent {
1032
1102
  itemType() {
1033
1103
  const item_service = this.itemService();
1034
1104
  if (item_service) {
1035
- return item_service.itemType;
1105
+ return item_service.typePlural();
1036
1106
  }
1037
1107
  return "";
1038
1108
  }
1109
+ title() {
1110
+ return this.itemType().replace("_", " ");
1111
+ }
1039
1112
  isTableView() {
1040
1113
  return this.selectedViewType() === "table";
1041
1114
  }
@@ -1061,7 +1134,7 @@ class ListViewComponent {
1061
1134
  return (url_segments.length == 2) && (url_segments[1].path == "self");
1062
1135
  }
1063
1136
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.1", ngImport: i0, type: ListViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1064
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.1", type: ListViewComponent, isStandalone: true, selector: "lib-list-view", inputs: { viewType: { classPropertyName: "viewType", publicName: "viewType", isSignal: true, isRequired: false, transformFunction: null }, itemService: { classPropertyName: "itemService", publicName: "itemService", isSignal: true, isRequired: false, transformFunction: null }, itemDetailTemplate: { classPropertyName: "itemDetailTemplate", publicName: "itemDetailTemplate", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, sortFields: { classPropertyName: "sortFields", publicName: "sortFields", isSignal: true, isRequired: false, transformFunction: null }, noSelfItemsMessage: { classPropertyName: "noSelfItemsMessage", publicName: "noSelfItemsMessage", isSignal: true, isRequired: false, transformFunction: null }, noItemsCanCreateMessage: { classPropertyName: "noItemsCanCreateMessage", publicName: "noItemsCanCreateMessage", isSignal: true, isRequired: false, transformFunction: null }, noItemsMessage: { classPropertyName: "noItemsMessage", publicName: "noItemsMessage", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "sort", first: true, predicate: MatSort, descendants: true, isSignal: true }], ngImport: i0, template: "<lib-back-button></lib-back-button>\n\n<div class=\"container\">\n <div class=\"header\">\n <h1 class=\"title\">{{ itemType() | titlecase }}</h1>\n\n @if (canCreate()) {\n <div class=\"button-container\">\n <a mat-fab class=\"padded-button\" aria-label=\"Add new item\" matTooltip=\"Add new item\" [routerLink]=\"['create']\">\n <mat-icon>add</mat-icon></a>\n </div>\n }\n </div>\n\n <div class=\"controls\">\n <lib-search-bar [itemType]=\"itemType()\" [sortFields]=\"sortFields()\"\n (searchChanged)=\"onSearchChange($event)\"></lib-search-bar>\n\n @if(itemDetailTemplate()){\n <div style=\"margin: 10px\">\n <mat-button-toggle-group name=\"viewType\" aria-label=\"View Type\" [value]=\"selectedViewType()\"\n (change)=\"viewChanged($event)\">\n\n <mat-button-toggle value=\"table\" matTooltip=\"Show as table\">\n <mat-icon>table_view</mat-icon>\n </mat-button-toggle>\n\n <mat-button-toggle value=\"scroll\" matTooltip=\"Show as list\">\n <mat-icon>list</mat-icon>\n </mat-button-toggle>\n\n </mat-button-toggle-group>\n </div>\n }\n </div>\n\n @if(dataSource)\n {\n @if (dataSource.loading()) {\n <div class=\"spinner-container\">\n <mat-spinner></mat-spinner>\n </div>\n }\n @else{\n @if(dataSource.length() === 0)\n {\n @if(isSelfList())\n {\n @if(noSelfItemsMessage())\n {\n <p>{{noSelfItemsMessage()}}</p>\n }\n @else {\n <p>You do not have any {{itemType()}}.</p> \n }\n\n }\n @else{\n @if(canCreate()){\n @if(noItemsCanCreateMessage())\n {\n <p>{{noItemsCanCreateMessage()}}</p>\n }\n @else{\n <p>There are currently no {{itemType()}}, click above to add one.</p>\n }\n }\n @else{\n @if(noItemsMessage())\n {\n <p>{{noItemsMessage()}}</p>\n }\n @else\n {\n <p>There are currently no {{itemType()}}.</p>\n }\n }\n }\n }\n @else{\n @if(isTableView()) {\n <lib-list-table-view [itemType]=\"itemType()\" [columns]=\"columns()\" [dataSource]=\"dataSource\">\n </lib-list-table-view>\n } \n @else {\n <lib-list-scroll-view [listItemTemplate]=\"itemDetailTemplate()\" [dataSource]=\"dataSource\"></lib-list-scroll-view>\n }\n }\n }\n }\n</div>", styles: [":host{flex-grow:1}.container{display:flex;padding:10px;flex-direction:column;justify-content:center;text-align:center;align-items:center}.header{display:flex;flex-direction:row;align-items:center;justify-content:center}.title{text-align:center;padding:10px}.controls{display:flex;align-items:center;justify-content:center;flex-wrap:wrap}\n"], dependencies: [{ kind: "component", type: ListTableViewComponent, selector: "lib-list-table-view", inputs: ["itemType", "columns", "dataSource", "searchFilter"] }, { kind: "component", type: ListScrollViewComponent, selector: "lib-list-scroll-view", inputs: ["searchTerm", "pageSize", "dataSource", "listItemTemplate"] }, { kind: "component", type: BackButtonComponent, selector: "lib-back-button" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatButtonToggleModule }, { kind: "directive", type: i1$5.MatButtonToggleGroup, selector: "mat-button-toggle-group", inputs: ["appearance", "name", "vertical", "value", "multiple", "disabled", "disabledInteractive", "hideSingleSelectionIndicator", "hideMultipleSelectionIndicator"], outputs: ["valueChange", "change"], exportAs: ["matButtonToggleGroup"] }, { kind: "component", type: i1$5.MatButtonToggle, selector: "mat-button-toggle", inputs: ["aria-label", "aria-labelledby", "id", "name", "value", "tabIndex", "disableRipple", "appearance", "checked", "disabled", "disabledInteractive"], outputs: ["change"], exportAs: ["matButtonToggle"] }, { kind: "ngmodule", type: MatProgressSpinnerModule }, { kind: "component", type: i2$5.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2$1.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: SearchBarComponent, selector: "lib-search-bar", inputs: ["itemType", "sortFields"], outputs: ["searchChanged"] }, { kind: "pipe", type: TitleCasePipe, name: "titlecase" }] });
1137
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.1", type: ListViewComponent, isStandalone: true, selector: "lib-list-view", inputs: { viewType: { classPropertyName: "viewType", publicName: "viewType", isSignal: true, isRequired: false, transformFunction: null }, itemService: { classPropertyName: "itemService", publicName: "itemService", isSignal: true, isRequired: false, transformFunction: null }, itemDetailTemplate: { classPropertyName: "itemDetailTemplate", publicName: "itemDetailTemplate", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, sortFields: { classPropertyName: "sortFields", publicName: "sortFields", isSignal: true, isRequired: false, transformFunction: null }, noSelfItemsMessage: { classPropertyName: "noSelfItemsMessage", publicName: "noSelfItemsMessage", isSignal: true, isRequired: false, transformFunction: null }, noItemsCanCreateMessage: { classPropertyName: "noItemsCanCreateMessage", publicName: "noItemsCanCreateMessage", isSignal: true, isRequired: false, transformFunction: null }, noItemsMessage: { classPropertyName: "noItemsMessage", publicName: "noItemsMessage", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "sort", first: true, predicate: MatSort, descendants: true, isSignal: true }], ngImport: i0, template: "<lib-back-button></lib-back-button>\n\n<div class=\"container\">\n <div class=\"header\">\n <h1 class=\"title\">{{ title() | titlecase }}</h1>\n\n @if (canCreate()) {\n <div class=\"button-container\">\n <a mat-fab class=\"padded-button\" aria-label=\"Add new item\" matTooltip=\"Add new item\" [routerLink]=\"['create']\">\n <mat-icon>add</mat-icon></a>\n </div>\n }\n </div>\n\n @if(dataSource)\n {\n <div class=\"controls\" [hidden]=\"dataSource.sourceIsEmpty()\">\n <lib-search-bar [itemType]=\"title()\" [sortFields]=\"sortFields()\"\n (searchChanged)=\"onSearchChange($event)\"></lib-search-bar>\n\n @if(itemDetailTemplate()){\n <div style=\"margin: 10px\">\n <mat-button-toggle-group name=\"viewType\" aria-label=\"View Type\" [value]=\"selectedViewType()\"\n (change)=\"viewChanged($event)\">\n\n <mat-button-toggle value=\"table\" matTooltip=\"Show as table\">\n <mat-icon>table_view</mat-icon>\n </mat-button-toggle>\n\n <mat-button-toggle value=\"scroll\" matTooltip=\"Show as list\">\n <mat-icon>list</mat-icon>\n </mat-button-toggle>\n\n </mat-button-toggle-group>\n </div>\n }\n </div>\n\n @if (dataSource.loading()) {\n <div class=\"spinner-container\">\n <mat-spinner></mat-spinner>\n </div>\n }\n\n <div [hidden]=\"!dataSource.sourceIsEmpty()\">\n @if(isSelfList())\n {\n @if(noSelfItemsMessage())\n {\n <p>{{noSelfItemsMessage()}}</p>\n }\n @else {\n <p>You do not have any {{itemType()}}.</p> \n }\n }\n @else{\n @if(canCreate()){\n @if(noItemsCanCreateMessage())\n {\n <p>{{noItemsCanCreateMessage()}}</p>\n }\n @else{\n <p>There are currently no {{itemType()}}, click above to add one.</p>\n }\n }\n @else{\n @if(noItemsMessage())\n {\n <p>{{noItemsMessage()}}</p>\n }\n @else\n {\n <p>There are currently no {{itemType()}}.</p>\n }\n }\n }\n </div>\n\n <div [hidden]=\"dataSource.sourceIsEmpty()\">\n @if(isTableView()) {\n <lib-list-table-view [itemType]=\"itemType()\" [columns]=\"columns()\" [dataSource]=\"dataSource\">\n </lib-list-table-view>\n } \n @else {\n <lib-list-scroll-view [listItemTemplate]=\"itemDetailTemplate()\" [dataSource]=\"dataSource\"></lib-list-scroll-view>\n }\n\n </div>\n }\n</div>", styles: [":host{flex-grow:1}.container{display:flex;padding:10px;flex-direction:column;justify-content:center;text-align:center;align-items:center}.header{display:flex;flex-direction:row;align-items:center;justify-content:center}.title{text-align:center;padding:10px}.controls{display:flex;align-items:center;justify-content:center;flex-wrap:wrap}.hide-element{display:none}\n"], dependencies: [{ kind: "component", type: ListTableViewComponent, selector: "lib-list-table-view", inputs: ["itemType", "columns", "dataSource", "searchFilter"] }, { kind: "component", type: ListScrollViewComponent, selector: "lib-list-scroll-view", inputs: ["searchTerm", "pageSize", "dataSource", "listItemTemplate"] }, { kind: "component", type: BackButtonComponent, selector: "lib-back-button" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatButtonToggleModule }, { kind: "directive", type: i1$5.MatButtonToggleGroup, selector: "mat-button-toggle-group", inputs: ["appearance", "name", "vertical", "value", "multiple", "disabled", "disabledInteractive", "hideSingleSelectionIndicator", "hideMultipleSelectionIndicator"], outputs: ["valueChange", "change"], exportAs: ["matButtonToggleGroup"] }, { kind: "component", type: i1$5.MatButtonToggle, selector: "mat-button-toggle", inputs: ["aria-label", "aria-labelledby", "id", "name", "value", "tabIndex", "disableRipple", "appearance", "checked", "disabled", "disabledInteractive"], outputs: ["change"], exportAs: ["matButtonToggle"] }, { kind: "ngmodule", type: MatProgressSpinnerModule }, { kind: "component", type: i2$5.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2$1.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: SearchBarComponent, selector: "lib-search-bar", inputs: ["itemType", "sortFields"], outputs: ["searchChanged"] }, { kind: "pipe", type: TitleCasePipe, name: "titlecase" }] });
1065
1138
  }
1066
1139
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.1", ngImport: i0, type: ListViewComponent, decorators: [{
1067
1140
  type: Component,
@@ -1078,7 +1151,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.1", ngImpor
1078
1151
  MatInputModule,
1079
1152
  MatTooltipModule,
1080
1153
  MatButtonModule,
1081
- SearchBarComponent], template: "<lib-back-button></lib-back-button>\n\n<div class=\"container\">\n <div class=\"header\">\n <h1 class=\"title\">{{ itemType() | titlecase }}</h1>\n\n @if (canCreate()) {\n <div class=\"button-container\">\n <a mat-fab class=\"padded-button\" aria-label=\"Add new item\" matTooltip=\"Add new item\" [routerLink]=\"['create']\">\n <mat-icon>add</mat-icon></a>\n </div>\n }\n </div>\n\n <div class=\"controls\">\n <lib-search-bar [itemType]=\"itemType()\" [sortFields]=\"sortFields()\"\n (searchChanged)=\"onSearchChange($event)\"></lib-search-bar>\n\n @if(itemDetailTemplate()){\n <div style=\"margin: 10px\">\n <mat-button-toggle-group name=\"viewType\" aria-label=\"View Type\" [value]=\"selectedViewType()\"\n (change)=\"viewChanged($event)\">\n\n <mat-button-toggle value=\"table\" matTooltip=\"Show as table\">\n <mat-icon>table_view</mat-icon>\n </mat-button-toggle>\n\n <mat-button-toggle value=\"scroll\" matTooltip=\"Show as list\">\n <mat-icon>list</mat-icon>\n </mat-button-toggle>\n\n </mat-button-toggle-group>\n </div>\n }\n </div>\n\n @if(dataSource)\n {\n @if (dataSource.loading()) {\n <div class=\"spinner-container\">\n <mat-spinner></mat-spinner>\n </div>\n }\n @else{\n @if(dataSource.length() === 0)\n {\n @if(isSelfList())\n {\n @if(noSelfItemsMessage())\n {\n <p>{{noSelfItemsMessage()}}</p>\n }\n @else {\n <p>You do not have any {{itemType()}}.</p> \n }\n\n }\n @else{\n @if(canCreate()){\n @if(noItemsCanCreateMessage())\n {\n <p>{{noItemsCanCreateMessage()}}</p>\n }\n @else{\n <p>There are currently no {{itemType()}}, click above to add one.</p>\n }\n }\n @else{\n @if(noItemsMessage())\n {\n <p>{{noItemsMessage()}}</p>\n }\n @else\n {\n <p>There are currently no {{itemType()}}.</p>\n }\n }\n }\n }\n @else{\n @if(isTableView()) {\n <lib-list-table-view [itemType]=\"itemType()\" [columns]=\"columns()\" [dataSource]=\"dataSource\">\n </lib-list-table-view>\n } \n @else {\n <lib-list-scroll-view [listItemTemplate]=\"itemDetailTemplate()\" [dataSource]=\"dataSource\"></lib-list-scroll-view>\n }\n }\n }\n }\n</div>", styles: [":host{flex-grow:1}.container{display:flex;padding:10px;flex-direction:column;justify-content:center;text-align:center;align-items:center}.header{display:flex;flex-direction:row;align-items:center;justify-content:center}.title{text-align:center;padding:10px}.controls{display:flex;align-items:center;justify-content:center;flex-wrap:wrap}\n"] }]
1154
+ SearchBarComponent], template: "<lib-back-button></lib-back-button>\n\n<div class=\"container\">\n <div class=\"header\">\n <h1 class=\"title\">{{ title() | titlecase }}</h1>\n\n @if (canCreate()) {\n <div class=\"button-container\">\n <a mat-fab class=\"padded-button\" aria-label=\"Add new item\" matTooltip=\"Add new item\" [routerLink]=\"['create']\">\n <mat-icon>add</mat-icon></a>\n </div>\n }\n </div>\n\n @if(dataSource)\n {\n <div class=\"controls\" [hidden]=\"dataSource.sourceIsEmpty()\">\n <lib-search-bar [itemType]=\"title()\" [sortFields]=\"sortFields()\"\n (searchChanged)=\"onSearchChange($event)\"></lib-search-bar>\n\n @if(itemDetailTemplate()){\n <div style=\"margin: 10px\">\n <mat-button-toggle-group name=\"viewType\" aria-label=\"View Type\" [value]=\"selectedViewType()\"\n (change)=\"viewChanged($event)\">\n\n <mat-button-toggle value=\"table\" matTooltip=\"Show as table\">\n <mat-icon>table_view</mat-icon>\n </mat-button-toggle>\n\n <mat-button-toggle value=\"scroll\" matTooltip=\"Show as list\">\n <mat-icon>list</mat-icon>\n </mat-button-toggle>\n\n </mat-button-toggle-group>\n </div>\n }\n </div>\n\n @if (dataSource.loading()) {\n <div class=\"spinner-container\">\n <mat-spinner></mat-spinner>\n </div>\n }\n\n <div [hidden]=\"!dataSource.sourceIsEmpty()\">\n @if(isSelfList())\n {\n @if(noSelfItemsMessage())\n {\n <p>{{noSelfItemsMessage()}}</p>\n }\n @else {\n <p>You do not have any {{itemType()}}.</p> \n }\n }\n @else{\n @if(canCreate()){\n @if(noItemsCanCreateMessage())\n {\n <p>{{noItemsCanCreateMessage()}}</p>\n }\n @else{\n <p>There are currently no {{itemType()}}, click above to add one.</p>\n }\n }\n @else{\n @if(noItemsMessage())\n {\n <p>{{noItemsMessage()}}</p>\n }\n @else\n {\n <p>There are currently no {{itemType()}}.</p>\n }\n }\n }\n </div>\n\n <div [hidden]=\"dataSource.sourceIsEmpty()\">\n @if(isTableView()) {\n <lib-list-table-view [itemType]=\"itemType()\" [columns]=\"columns()\" [dataSource]=\"dataSource\">\n </lib-list-table-view>\n } \n @else {\n <lib-list-scroll-view [listItemTemplate]=\"itemDetailTemplate()\" [dataSource]=\"dataSource\"></lib-list-scroll-view>\n }\n\n </div>\n }\n</div>", styles: [":host{flex-grow:1}.container{display:flex;padding:10px;flex-direction:column;justify-content:center;text-align:center;align-items:center}.header{display:flex;flex-direction:row;align-items:center;justify-content:center}.title{text-align:center;padding:10px}.controls{display:flex;align-items:center;justify-content:center;flex-wrap:wrap}.hide-element{display:none}\n"] }]
1082
1155
  }] });
1083
1156
 
1084
1157
  class DetailHeaderComponent {
@@ -1088,7 +1161,7 @@ class DetailHeaderComponent {
1088
1161
  canEdit = input(false, ...(ngDevMode ? [{ debugName: "canEdit" }] : []));
1089
1162
  canDelete = input(false, ...(ngDevMode ? [{ debugName: "canDelete" }] : []));
1090
1163
  deleteClicked = output();
1091
- fullRoute = computed(() => "/" + this.route() + "s/edit/", ...(ngDevMode ? [{ debugName: "fullRoute" }] : []));
1164
+ fullRoute = computed(() => "/" + this.route() + "/edit/", ...(ngDevMode ? [{ debugName: "fullRoute" }] : []));
1092
1165
  deleteClick() {
1093
1166
  this.deleteClicked.emit();
1094
1167
  }
@@ -1106,6 +1179,7 @@ class FileUploadComponent {
1106
1179
  files = input([], ...(ngDevMode ? [{ debugName: "files" }] : []));
1107
1180
  previewLoaded = output();
1108
1181
  fileUploaded = output();
1182
+ clearLocal = output();
1109
1183
  onPreviewRead(name, content) {
1110
1184
  this.previewLoaded.emit({ name: name, content: content });
1111
1185
  }
@@ -1133,12 +1207,15 @@ class FileUploadComponent {
1133
1207
  ;
1134
1208
  };
1135
1209
  }
1210
+ onClearLocal() {
1211
+ this.clearLocal.emit();
1212
+ }
1136
1213
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.1", ngImport: i0, type: FileUploadComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1137
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.1", type: FileUploadComponent, isStandalone: true, selector: "lib-file-upload", inputs: { files: { classPropertyName: "files", publicName: "files", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { previewLoaded: "previewLoaded", fileUploaded: "fileUploaded" }, ngImport: i0, template: "<div class=\"container\">\n @for( file of files(); track file.name)\n {\n @if (file.local)\n {\n <div class=\"image_holder\">\n <p>Update {{file.display_name}}</p>\n <img alt=\"{{file.display_name}}\" src=\"{{file.local}}\" style=\"height:120px;\">\n </div> \n }\n @else\n {\n @if (file.remote){\n <div class=\"image_holder\">\n <p>Update {{file.display_name}}</p>\n <img alt=\"{{file.display_name}}\" src=\"{{file.remote}}\" style=\"height:120px;\">\n </div>\n }\n @else {\n <div>\n <span>Upload a {{file.display_name}}</span>\n </div> \n }\n \n }\n\n <div class=\"button-row\">\n <button mat-mini-fab (click)=\"fileInput.click()\">\n <mat-icon>attachment</mat-icon>\n </button>\n <input hidden type=\"file\" #fileInput (change)=\"onFileUpload(file.name, $event)\"/>\n </div> \n } \n</div>\n", styles: [".container{padding:10px;display:flex;justify-content:center;text-align:center;flex-direction:column;max-width:300px}\n"], dependencies: [{ kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2$1.MatMiniFabButton, selector: "button[mat-mini-fab], a[mat-mini-fab], button[matMiniFab], a[matMiniFab]", exportAs: ["matButton", "matAnchor"] }] });
1214
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.1", type: FileUploadComponent, isStandalone: true, selector: "lib-file-upload", inputs: { files: { classPropertyName: "files", publicName: "files", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { previewLoaded: "previewLoaded", fileUploaded: "fileUploaded", clearLocal: "clearLocal" }, ngImport: i0, template: "<div class=\"container\">\n @for( file of files(); track file.name)\n {\n @if (file.local)\n {\n <div class=\"image_holder\">\n <p>Update {{file.display_name}}</p>\n <img alt=\"{{file.display_name}}\" src=\"{{file.local}}\" style=\"height:120px;\">\n <button mat-mini-fab (click)=\"onClearLocal()\">\n <mat-icon>clear</mat-icon>\n </button>\n </div> \n }\n @else\n {\n @if (file.remote){\n <div class=\"image_holder\">\n <p>Update {{file.display_name}}</p>\n <img alt=\"{{file.display_name}}\" src=\"{{file.remote}}\" style=\"height:120px;\">\n </div>\n }\n @else {\n <div>\n <span>Upload a {{file.display_name}}</span>\n </div> \n }\n \n }\n\n <div class=\"button-row\">\n <button mat-mini-fab (click)=\"fileInput.click()\">\n <mat-icon>attachment</mat-icon>\n </button>\n <input hidden type=\"file\" #fileInput (change)=\"onFileUpload(file.name, $event)\"/>\n </div> \n } \n</div>\n", styles: [".container{padding:10px;display:flex;justify-content:center;text-align:center;flex-direction:column;max-width:300px}\n"], dependencies: [{ kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2$1.MatMiniFabButton, selector: "button[mat-mini-fab], a[mat-mini-fab], button[matMiniFab], a[matMiniFab]", exportAs: ["matButton", "matAnchor"] }] });
1138
1215
  }
1139
1216
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.1", ngImport: i0, type: FileUploadComponent, decorators: [{
1140
1217
  type: Component,
1141
- args: [{ selector: 'lib-file-upload', imports: [MatIconModule, MatButtonModule], template: "<div class=\"container\">\n @for( file of files(); track file.name)\n {\n @if (file.local)\n {\n <div class=\"image_holder\">\n <p>Update {{file.display_name}}</p>\n <img alt=\"{{file.display_name}}\" src=\"{{file.local}}\" style=\"height:120px;\">\n </div> \n }\n @else\n {\n @if (file.remote){\n <div class=\"image_holder\">\n <p>Update {{file.display_name}}</p>\n <img alt=\"{{file.display_name}}\" src=\"{{file.remote}}\" style=\"height:120px;\">\n </div>\n }\n @else {\n <div>\n <span>Upload a {{file.display_name}}</span>\n </div> \n }\n \n }\n\n <div class=\"button-row\">\n <button mat-mini-fab (click)=\"fileInput.click()\">\n <mat-icon>attachment</mat-icon>\n </button>\n <input hidden type=\"file\" #fileInput (change)=\"onFileUpload(file.name, $event)\"/>\n </div> \n } \n</div>\n", styles: [".container{padding:10px;display:flex;justify-content:center;text-align:center;flex-direction:column;max-width:300px}\n"] }]
1218
+ args: [{ selector: 'lib-file-upload', imports: [MatIconModule, MatButtonModule], template: "<div class=\"container\">\n @for( file of files(); track file.name)\n {\n @if (file.local)\n {\n <div class=\"image_holder\">\n <p>Update {{file.display_name}}</p>\n <img alt=\"{{file.display_name}}\" src=\"{{file.local}}\" style=\"height:120px;\">\n <button mat-mini-fab (click)=\"onClearLocal()\">\n <mat-icon>clear</mat-icon>\n </button>\n </div> \n }\n @else\n {\n @if (file.remote){\n <div class=\"image_holder\">\n <p>Update {{file.display_name}}</p>\n <img alt=\"{{file.display_name}}\" src=\"{{file.remote}}\" style=\"height:120px;\">\n </div>\n }\n @else {\n <div>\n <span>Upload a {{file.display_name}}</span>\n </div> \n }\n \n }\n\n <div class=\"button-row\">\n <button mat-mini-fab (click)=\"fileInput.click()\">\n <mat-icon>attachment</mat-icon>\n </button>\n <input hidden type=\"file\" #fileInput (change)=\"onFileUpload(file.name, $event)\"/>\n </div> \n } \n</div>\n", styles: [".container{padding:10px;display:flex;justify-content:center;text-align:center;flex-direction:column;max-width:300px}\n"] }]
1142
1219
  }] });
1143
1220
 
1144
1221
  class FileRecord {
@@ -1154,7 +1231,6 @@ class FileRecord {
1154
1231
  }
1155
1232
 
1156
1233
  class UserDetailComponent extends DetailView {
1157
- typename = "user";
1158
1234
  constructor() {
1159
1235
  super(inject(UserService));
1160
1236
  }
@@ -1162,20 +1238,20 @@ class UserDetailComponent extends DetailView {
1162
1238
  this.onInit();
1163
1239
  }
1164
1240
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.1", ngImport: i0, type: UserDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1165
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.1", type: UserDetailComponent, isStandalone: true, selector: "lib-user-detail", usesInheritance: true, ngImport: i0, template: "<lib-back-button></lib-back-button>\n\n<div class=\"content-container\">\n @if(item(); as item){\n <div class=\"item-detail-container\">\n\n <lib-detail-header\n [text]=\"item.username\"\n [id]=\"item.id\"\n [route]=\"typename\"\n [canEdit]=\"canEdit()\"\n [canDelete]=\"canDelete()\"\n (onDelete)=\"onDelete\"></lib-detail-header>\n\n @if(item.profile_url){\n <div class=\"image-holder\">\n <img alt=\"User Profile Image\" src=\"{{item.url}}{{item.profile_url}}\" style=\"height:120px;\">\n </div> \n }\n \n <div class=\"item-field\">\n <span><b>Email: </b></span>\n {{item.email}}\n </div>\n \n <div class=\"item-field\">\n <span><b>First Name: </b></span>\n {{item.first_name}}\n </div>\n \n <div class=\"item-field\">\n <span><b>Last Name: </b></span>\n {{item.last_name}}\n </div>\n \n </div>\n }\n</div>\n", styles: [":host{flex-grow:1}\n"], dependencies: [{ kind: "component", type: BackButtonComponent, selector: "lib-back-button" }, { kind: "component", type: DetailHeaderComponent, selector: "lib-detail-header", inputs: ["id", "text", "route", "canEdit", "canDelete"], outputs: ["deleteClicked"] }] });
1241
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.1", type: UserDetailComponent, isStandalone: true, selector: "lib-user-detail", usesInheritance: true, ngImport: i0, template: "<lib-back-button></lib-back-button>\n\n<div class=\"content-container\">\n @if(item(); as item){\n <div class=\"item-detail-container\">\n\n <lib-detail-header\n [text]=\"item.username\"\n [id]=\"item.id\"\n [route]=\"itemService.typePlural()\"\n [canEdit]=\"canEdit()\"\n [canDelete]=\"canDelete()\"\n (onDelete)=\"onDelete()\"></lib-detail-header>\n\n @if(item.profile_url){\n <div class=\"image-holder\">\n <img alt=\"User Profile Image\" src=\"{{item.url}}{{item.profile_url}}\" style=\"height:120px;\">\n </div> \n }\n \n <div class=\"item-field\">\n <span><b>Email: </b></span>\n {{item.email}}\n </div>\n \n <div class=\"item-field\">\n <span><b>First Name: </b></span>\n {{item.first_name}}\n </div>\n \n <div class=\"item-field\">\n <span><b>Last Name: </b></span>\n {{item.last_name}}\n </div>\n \n </div>\n }\n</div>\n", styles: [":host{flex-grow:1}\n"], dependencies: [{ kind: "component", type: BackButtonComponent, selector: "lib-back-button" }, { kind: "component", type: DetailHeaderComponent, selector: "lib-detail-header", inputs: ["id", "text", "route", "canEdit", "canDelete"], outputs: ["deleteClicked"] }] });
1166
1242
  }
1167
1243
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.1", ngImport: i0, type: UserDetailComponent, decorators: [{
1168
1244
  type: Component,
1169
- args: [{ selector: 'lib-user-detail', imports: [NgIf,
1170
- BackButtonComponent,
1171
- DetailHeaderComponent], template: "<lib-back-button></lib-back-button>\n\n<div class=\"content-container\">\n @if(item(); as item){\n <div class=\"item-detail-container\">\n\n <lib-detail-header\n [text]=\"item.username\"\n [id]=\"item.id\"\n [route]=\"typename\"\n [canEdit]=\"canEdit()\"\n [canDelete]=\"canDelete()\"\n (onDelete)=\"onDelete\"></lib-detail-header>\n\n @if(item.profile_url){\n <div class=\"image-holder\">\n <img alt=\"User Profile Image\" src=\"{{item.url}}{{item.profile_url}}\" style=\"height:120px;\">\n </div> \n }\n \n <div class=\"item-field\">\n <span><b>Email: </b></span>\n {{item.email}}\n </div>\n \n <div class=\"item-field\">\n <span><b>First Name: </b></span>\n {{item.first_name}}\n </div>\n \n <div class=\"item-field\">\n <span><b>Last Name: </b></span>\n {{item.last_name}}\n </div>\n \n </div>\n }\n</div>\n", styles: [":host{flex-grow:1}\n"] }]
1245
+ args: [{ selector: 'lib-user-detail', imports: [BackButtonComponent,
1246
+ DetailHeaderComponent], template: "<lib-back-button></lib-back-button>\n\n<div class=\"content-container\">\n @if(item(); as item){\n <div class=\"item-detail-container\">\n\n <lib-detail-header\n [text]=\"item.username\"\n [id]=\"item.id\"\n [route]=\"itemService.typePlural()\"\n [canEdit]=\"canEdit()\"\n [canDelete]=\"canDelete()\"\n (onDelete)=\"onDelete()\"></lib-detail-header>\n\n @if(item.profile_url){\n <div class=\"image-holder\">\n <img alt=\"User Profile Image\" src=\"{{item.url}}{{item.profile_url}}\" style=\"height:120px;\">\n </div> \n }\n \n <div class=\"item-field\">\n <span><b>Email: </b></span>\n {{item.email}}\n </div>\n \n <div class=\"item-field\">\n <span><b>First Name: </b></span>\n {{item.first_name}}\n </div>\n \n <div class=\"item-field\">\n <span><b>Last Name: </b></span>\n {{item.last_name}}\n </div>\n \n </div>\n }\n</div>\n", styles: [":host{flex-grow:1}\n"] }]
1172
1247
  }], ctorParameters: () => [] });
1173
1248
 
1174
1249
  class UserEditComponent extends EditView {
1175
- typename = "User";
1176
- files = signal([new FileRecord({ name: "image",
1250
+ files = signal([new FileRecord({
1251
+ name: "image",
1177
1252
  display_name: "Profile Image",
1178
- file_type: "image" })], ...(ngDevMode ? [{ debugName: "files" }] : []));
1253
+ file_type: "image"
1254
+ })], ...(ngDevMode ? [{ debugName: "files" }] : []));
1179
1255
  formBuilder = inject(FormBuilder);
1180
1256
  form = this.formBuilder.group({
1181
1257
  username: [''],
@@ -1239,23 +1315,24 @@ class UserEditComponent extends EditView {
1239
1315
  }
1240
1316
  saveFiles(item) {
1241
1317
  this.files().forEach(record => { if (record.file) {
1242
- this._restService.postFile(item.id, record.name, record.file).subscribe(item => this.onItemUpdated(item));
1318
+ this.itemService.postFile(item.id, record.name, record.file).subscribe(item => this.onItemUpdated(item));
1243
1319
  } ; });
1244
1320
  }
1245
1321
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.1", ngImport: i0, type: UserEditComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1246
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.1", type: UserEditComponent, isStandalone: true, selector: "lib-user-edit", usesInheritance: true, ngImport: i0, template: "<lib-back-button></lib-back-button>\n\n<div class=\"content-container\">\n\n @if(item(); as item){\n <div class=\"item-edit-container\">\n\n <h1 class=\"item-edit-header\">{{heading()}}</h1>\n\n <form class=\"form-card\" [formGroup]=\"form\" (ngSubmit)=\"submit()\">\n <mat-form-field class=\"form-field-wide\">\n <mat-label>Username</mat-label>\n <input matInput\n placeholder=\"Username\"\n type=\"text\"\n formControlName=\"username\"\n name=\"username\">\n </mat-form-field>\n \n <mat-form-field class=\"form-field-wide\">\n <mat-label>Email</mat-label>\n <input matInput\n placeholder=\"Email\"\n type=\"text\"\n formControlName=\"email\"\n name=\"email\">\n </mat-form-field>\n\n <mat-form-field class=\"form-field\">\n <mat-label>First Name</mat-label>\n <input matInput\n placeholder=\"First Name\"\n type=\"text\"\n formControlName=\"first_name\"\n name=\"first_name\">\n </mat-form-field>\n \n <mat-form-field class=\"form-field\">\n <mat-label>Last Name</mat-label>\n <input matInput\n placeholder=\"Last Name\"\n type=\"text\"\n formControlName=\"last_name\"\n name=\"last_name\">\n </mat-form-field>\n\n <lib-file-upload [files]=\"files()\"\n (previewLoaded)=\"onPreviewLoaded($event)\"\n (fileUploaded)=\"onFileUploaded($event)\"></lib-file-upload>\n \n <div class=\"button_group\">\n <button mat-fab\n class=\"form_action_button\"\n type=\"submit\"\n [disabled]=\"!form.valid\">\n <mat-icon>save</mat-icon></button>\n \n <button mat-fab\n class=\"form_action_button\"\n (click)=\"cancel()\">\n <mat-icon>cancel</mat-icon></button>\n </div>\n \n </form>\n </div>\n }\n</div>\n", styles: [":host{flex-grow:1}.item_view{display:flex;justify-content:left;align-items:left;flex-direction:column}.checkbox_group{justify-content:left;display:flex;flex-direction:column}.button_group{margin:10px;display:flex;flex-direction:row;justify-content:center}.btn-block{padding:5px}.form_action_button{padding:5px;margin:5px}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$2.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: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2$1.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: BackButtonComponent, selector: "lib-back-button" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$2.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i3$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3$1.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: FileUploadComponent, selector: "lib-file-upload", inputs: ["files"], outputs: ["previewLoaded", "fileUploaded"] }] });
1322
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.1", type: UserEditComponent, isStandalone: true, selector: "lib-user-edit", usesInheritance: true, ngImport: i0, template: "<lib-back-button></lib-back-button>\n\n<div class=\"content-container\">\n\n @if(item(); as item){\n <div class=\"item-edit-container\">\n\n <h1 class=\"item-edit-header\">{{heading() | titlecase}}</h1>\n\n <form class=\"form-card\" [formGroup]=\"form\" (ngSubmit)=\"submit()\">\n <mat-form-field class=\"form-field-wide\">\n <mat-label>Username</mat-label>\n <input matInput\n placeholder=\"Username\"\n type=\"text\"\n formControlName=\"username\"\n name=\"username\">\n </mat-form-field>\n \n <mat-form-field class=\"form-field-wide\">\n <mat-label>Email</mat-label>\n <input matInput\n placeholder=\"Email\"\n type=\"text\"\n formControlName=\"email\"\n name=\"email\">\n </mat-form-field>\n\n <mat-form-field class=\"form-field\">\n <mat-label>First Name</mat-label>\n <input matInput\n placeholder=\"First Name\"\n type=\"text\"\n formControlName=\"first_name\"\n name=\"first_name\">\n </mat-form-field>\n \n <mat-form-field class=\"form-field\">\n <mat-label>Last Name</mat-label>\n <input matInput\n placeholder=\"Last Name\"\n type=\"text\"\n formControlName=\"last_name\"\n name=\"last_name\">\n </mat-form-field>\n\n <lib-file-upload [files]=\"files()\"\n (previewLoaded)=\"onPreviewLoaded($event)\"\n (fileUploaded)=\"onFileUploaded($event)\"></lib-file-upload>\n </form>\n\n <div class=\"button_group\">\n <button mat-fab\n class=\"form_action_button\"\n type=\"submit\"\n [disabled]=\"!form.valid\">\n <mat-icon>save</mat-icon></button>\n \n <button mat-fab\n class=\"form_action_button\"\n (click)=\"cancel()\">\n <mat-icon>cancel</mat-icon></button>\n </div>\n </div>\n }\n</div>\n", styles: [":host{flex-grow:1}.item_view{display:flex;justify-content:left;align-items:left;flex-direction:column}.checkbox_group{justify-content:left;display:flex;flex-direction:column}.button_group{margin:10px;display:flex;flex-direction:row;justify-content:center}.btn-block{padding:5px}.form_action_button{padding:5px;margin:5px}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$2.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: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2$1.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: BackButtonComponent, selector: "lib-back-button" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$2.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i3$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3$1.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: FileUploadComponent, selector: "lib-file-upload", inputs: ["files"], outputs: ["previewLoaded", "fileUploaded", "clearLocal"] }, { kind: "pipe", type: TitleCasePipe, name: "titlecase" }] });
1247
1323
  }
1248
1324
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.1", ngImport: i0, type: UserEditComponent, decorators: [{
1249
1325
  type: Component,
1250
- args: [{ selector: 'lib-user-edit', imports: [NgIf,
1326
+ args: [{ selector: 'lib-user-edit', imports: [
1251
1327
  ReactiveFormsModule,
1252
1328
  MatButtonModule,
1253
1329
  BackButtonComponent,
1254
1330
  MatInputModule,
1255
1331
  MatFormFieldModule,
1256
1332
  MatIconModule,
1257
- FileUploadComponent
1258
- ], template: "<lib-back-button></lib-back-button>\n\n<div class=\"content-container\">\n\n @if(item(); as item){\n <div class=\"item-edit-container\">\n\n <h1 class=\"item-edit-header\">{{heading()}}</h1>\n\n <form class=\"form-card\" [formGroup]=\"form\" (ngSubmit)=\"submit()\">\n <mat-form-field class=\"form-field-wide\">\n <mat-label>Username</mat-label>\n <input matInput\n placeholder=\"Username\"\n type=\"text\"\n formControlName=\"username\"\n name=\"username\">\n </mat-form-field>\n \n <mat-form-field class=\"form-field-wide\">\n <mat-label>Email</mat-label>\n <input matInput\n placeholder=\"Email\"\n type=\"text\"\n formControlName=\"email\"\n name=\"email\">\n </mat-form-field>\n\n <mat-form-field class=\"form-field\">\n <mat-label>First Name</mat-label>\n <input matInput\n placeholder=\"First Name\"\n type=\"text\"\n formControlName=\"first_name\"\n name=\"first_name\">\n </mat-form-field>\n \n <mat-form-field class=\"form-field\">\n <mat-label>Last Name</mat-label>\n <input matInput\n placeholder=\"Last Name\"\n type=\"text\"\n formControlName=\"last_name\"\n name=\"last_name\">\n </mat-form-field>\n\n <lib-file-upload [files]=\"files()\"\n (previewLoaded)=\"onPreviewLoaded($event)\"\n (fileUploaded)=\"onFileUploaded($event)\"></lib-file-upload>\n \n <div class=\"button_group\">\n <button mat-fab\n class=\"form_action_button\"\n type=\"submit\"\n [disabled]=\"!form.valid\">\n <mat-icon>save</mat-icon></button>\n \n <button mat-fab\n class=\"form_action_button\"\n (click)=\"cancel()\">\n <mat-icon>cancel</mat-icon></button>\n </div>\n \n </form>\n </div>\n }\n</div>\n", styles: [":host{flex-grow:1}.item_view{display:flex;justify-content:left;align-items:left;flex-direction:column}.checkbox_group{justify-content:left;display:flex;flex-direction:column}.button_group{margin:10px;display:flex;flex-direction:row;justify-content:center}.btn-block{padding:5px}.form_action_button{padding:5px;margin:5px}\n"] }]
1333
+ FileUploadComponent,
1334
+ TitleCasePipe
1335
+ ], template: "<lib-back-button></lib-back-button>\n\n<div class=\"content-container\">\n\n @if(item(); as item){\n <div class=\"item-edit-container\">\n\n <h1 class=\"item-edit-header\">{{heading() | titlecase}}</h1>\n\n <form class=\"form-card\" [formGroup]=\"form\" (ngSubmit)=\"submit()\">\n <mat-form-field class=\"form-field-wide\">\n <mat-label>Username</mat-label>\n <input matInput\n placeholder=\"Username\"\n type=\"text\"\n formControlName=\"username\"\n name=\"username\">\n </mat-form-field>\n \n <mat-form-field class=\"form-field-wide\">\n <mat-label>Email</mat-label>\n <input matInput\n placeholder=\"Email\"\n type=\"text\"\n formControlName=\"email\"\n name=\"email\">\n </mat-form-field>\n\n <mat-form-field class=\"form-field\">\n <mat-label>First Name</mat-label>\n <input matInput\n placeholder=\"First Name\"\n type=\"text\"\n formControlName=\"first_name\"\n name=\"first_name\">\n </mat-form-field>\n \n <mat-form-field class=\"form-field\">\n <mat-label>Last Name</mat-label>\n <input matInput\n placeholder=\"Last Name\"\n type=\"text\"\n formControlName=\"last_name\"\n name=\"last_name\">\n </mat-form-field>\n\n <lib-file-upload [files]=\"files()\"\n (previewLoaded)=\"onPreviewLoaded($event)\"\n (fileUploaded)=\"onFileUploaded($event)\"></lib-file-upload>\n </form>\n\n <div class=\"button_group\">\n <button mat-fab\n class=\"form_action_button\"\n type=\"submit\"\n [disabled]=\"!form.valid\">\n <mat-icon>save</mat-icon></button>\n \n <button mat-fab\n class=\"form_action_button\"\n (click)=\"cancel()\">\n <mat-icon>cancel</mat-icon></button>\n </div>\n </div>\n }\n</div>\n", styles: [":host{flex-grow:1}.item_view{display:flex;justify-content:left;align-items:left;flex-direction:column}.checkbox_group{justify-content:left;display:flex;flex-direction:column}.button_group{margin:10px;display:flex;flex-direction:row;justify-content:center}.btn-block{padding:5px}.form_action_button{padding:5px;margin:5px}\n"] }]
1259
1336
  }], ctorParameters: () => [] });
1260
1337
 
1261
1338
  class UserListDetailComponent {
@@ -1269,20 +1346,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.1", ngImpor
1269
1346
  MatCardModule], template: "<mat-card mat-list-item class=\"item-card\">\n @if(item(); as item){\n <mat-card-header>\n <mat-card-title-group style=\"padding: 5px\">\n <mat-card-title\n >{{ item.first_name }} {{ item.last_name }}</mat-card-title\n >\n <mat-card-subtitle>{{ item.username }}</mat-card-subtitle>\n </mat-card-title-group>\n </mat-card-header>\n\n <mat-card-content>\n <p>Email: {{ item.email }}</p>\n </mat-card-content>\n }\n</mat-card>\n", styles: [":host{flex-grow:1}.mat-mdc-row .mat-mdc-cell{border-bottom:1px solid transparent;border-top:1px solid transparent;cursor:pointer}.mat-mdc-row:hover .mat-mdc-cell{border-color:currentColor}.is-active{font-weight:700}\n"] }]
1270
1347
  }] });
1271
1348
 
1349
+ const USER_TABLE_FULL = [
1350
+ { name: 'first_name', title: 'First Name', element_type: 'string' },
1351
+ { name: 'last_name', title: 'Surname', element_type: 'string' },
1352
+ { name: 'username', title: 'Username', element_type: 'string' },
1353
+ { name: 'email', title: 'Email', element_type: 'email' }
1354
+ ];
1355
+
1272
1356
  class UserComponent {
1273
1357
  viewType = signal("list", ...(ngDevMode ? [{ debugName: "viewType" }] : []));
1274
1358
  itemService = inject(UserService);
1275
- columns = [{ name: 'username', title: 'Name', element_type: 'string' }];
1359
+ columns = USER_TABLE_FULL;
1276
1360
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.1", ngImport: i0, type: UserComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1277
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.1", type: UserComponent, isStandalone: true, selector: "lib-user", ngImport: i0, template: "<lib-list-view\n [itemService]=\"itemService\"\n [itemDetailTemplate]=\"listItemTemplate\"\n [columns]=\"columns\"\n [viewType]=\"viewType()\"\n >\n <ng-template #listItemTemplate let-item=\"item\">\n <lib-user-list-detail [item]=\"item\"\n [routerLink]=\"['/' + itemService.itemType + '/detail/', item.id]\"\n [routerLinkActive]=\"['is-active']\">\n </lib-user-list-detail>\n </ng-template>\n</lib-list-view>\n", styles: [":host{flex-grow:1}\n"], dependencies: [{ kind: "component", type: ListViewComponent, selector: "lib-list-view", inputs: ["viewType", "itemService", "itemDetailTemplate", "columns", "sortFields", "noSelfItemsMessage", "noItemsCanCreateMessage", "noItemsMessage"] }, { kind: "component", type: UserListDetailComponent, selector: "lib-user-list-detail", inputs: ["item"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i1.RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }] });
1361
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.1", type: UserComponent, isStandalone: true, selector: "lib-user", ngImport: i0, template: "<lib-list-view\n [itemService]=\"itemService\"\n [itemDetailTemplate]=\"listItemTemplate\"\n [columns]=\"columns\"\n [viewType]=\"viewType()\"\n >\n <ng-template #listItemTemplate let-item=\"item\">\n <lib-user-list-detail [item]=\"item\"\n [routerLink]=\"['/' + itemService.typePlural() + '/detail/', item.id]\"\n [routerLinkActive]=\"['is-active']\">\n </lib-user-list-detail>\n </ng-template>\n</lib-list-view>\n", styles: [":host{flex-grow:1}\n"], dependencies: [{ kind: "component", type: ListViewComponent, selector: "lib-list-view", inputs: ["viewType", "itemService", "itemDetailTemplate", "columns", "sortFields", "noSelfItemsMessage", "noItemsCanCreateMessage", "noItemsMessage"] }, { kind: "component", type: UserListDetailComponent, selector: "lib-user-list-detail", inputs: ["item"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i1.RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }] });
1278
1362
  }
1279
1363
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.1", ngImport: i0, type: UserComponent, decorators: [{
1280
1364
  type: Component,
1281
- args: [{ selector: 'lib-user', imports: [ListViewComponent, UserListDetailComponent, RouterModule], template: "<lib-list-view\n [itemService]=\"itemService\"\n [itemDetailTemplate]=\"listItemTemplate\"\n [columns]=\"columns\"\n [viewType]=\"viewType()\"\n >\n <ng-template #listItemTemplate let-item=\"item\">\n <lib-user-list-detail [item]=\"item\"\n [routerLink]=\"['/' + itemService.itemType + '/detail/', item.id]\"\n [routerLinkActive]=\"['is-active']\">\n </lib-user-list-detail>\n </ng-template>\n</lib-list-view>\n", styles: [":host{flex-grow:1}\n"] }]
1365
+ args: [{ selector: 'lib-user', imports: [ListViewComponent, UserListDetailComponent, RouterModule], template: "<lib-list-view\n [itemService]=\"itemService\"\n [itemDetailTemplate]=\"listItemTemplate\"\n [columns]=\"columns\"\n [viewType]=\"viewType()\"\n >\n <ng-template #listItemTemplate let-item=\"item\">\n <lib-user-list-detail [item]=\"item\"\n [routerLink]=\"['/' + itemService.typePlural() + '/detail/', item.id]\"\n [routerLinkActive]=\"['is-active']\">\n </lib-user-list-detail>\n </ng-template>\n</lib-list-view>\n", styles: [":host{flex-grow:1}\n"] }]
1282
1366
  }] });
1283
1367
 
1284
1368
  class GroupDetailComponent extends DetailView {
1285
- typename = "group";
1286
1369
  constructor() {
1287
1370
  super(inject(GroupService));
1288
1371
  }
@@ -1290,13 +1373,12 @@ class GroupDetailComponent extends DetailView {
1290
1373
  this.onInit();
1291
1374
  }
1292
1375
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.1", ngImport: i0, type: GroupDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1293
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.1", type: GroupDetailComponent, isStandalone: true, selector: "lib-group-detail", usesInheritance: true, ngImport: i0, template: "<lib-back-button></lib-back-button>\n\n<div class=\"content-container\">\n @if(item(); as item) {\n <div class=\"item-detail-container\">\n\n <lib-detail-header\n [text]=\"item.name\"\n [id]=\"item.id\"\n [route]=\"typename\"></lib-detail-header>\n \n <div class=\"item-field\">{{item.name}}</div>\n </div>\n }\n</div>\n", styles: [":host{flex-grow:1}\n"], dependencies: [{ kind: "component", type: BackButtonComponent, selector: "lib-back-button" }, { kind: "component", type: DetailHeaderComponent, selector: "lib-detail-header", inputs: ["id", "text", "route", "canEdit", "canDelete"], outputs: ["deleteClicked"] }] });
1376
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.1", type: GroupDetailComponent, isStandalone: true, selector: "lib-group-detail", usesInheritance: true, ngImport: i0, template: "<lib-back-button></lib-back-button>\n\n<div class=\"content-container\">\n @if(item(); as item) {\n <div class=\"item-detail-container\">\n\n <lib-detail-header\n [text]=\"item.name\"\n [id]=\"item.id\"\n [canEdit]=\"itemService.canEdit()\"\n [canDelete]=\"itemService.canDelete()\"\n [route]=\"itemService.typePlural()\"></lib-detail-header>\n \n <div class=\"item-field\">{{item.name}}</div>\n </div>\n }\n</div>\n", styles: [":host{flex-grow:1}\n"], dependencies: [{ kind: "component", type: BackButtonComponent, selector: "lib-back-button" }, { kind: "component", type: DetailHeaderComponent, selector: "lib-detail-header", inputs: ["id", "text", "route", "canEdit", "canDelete"], outputs: ["deleteClicked"] }] });
1294
1377
  }
1295
1378
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.1", ngImport: i0, type: GroupDetailComponent, decorators: [{
1296
1379
  type: Component,
1297
- args: [{ selector: 'lib-group-detail', imports: [NgIf,
1298
- BackButtonComponent,
1299
- DetailHeaderComponent], template: "<lib-back-button></lib-back-button>\n\n<div class=\"content-container\">\n @if(item(); as item) {\n <div class=\"item-detail-container\">\n\n <lib-detail-header\n [text]=\"item.name\"\n [id]=\"item.id\"\n [route]=\"typename\"></lib-detail-header>\n \n <div class=\"item-field\">{{item.name}}</div>\n </div>\n }\n</div>\n", styles: [":host{flex-grow:1}\n"] }]
1380
+ args: [{ selector: 'lib-group-detail', imports: [BackButtonComponent,
1381
+ DetailHeaderComponent], template: "<lib-back-button></lib-back-button>\n\n<div class=\"content-container\">\n @if(item(); as item) {\n <div class=\"item-detail-container\">\n\n <lib-detail-header\n [text]=\"item.name\"\n [id]=\"item.id\"\n [canEdit]=\"itemService.canEdit()\"\n [canDelete]=\"itemService.canDelete()\"\n [route]=\"itemService.typePlural()\"></lib-detail-header>\n \n <div class=\"item-field\">{{item.name}}</div>\n </div>\n }\n</div>\n", styles: [":host{flex-grow:1}\n"] }]
1300
1382
  }], ctorParameters: () => [] });
1301
1383
 
1302
1384
  class GroupComponent {
@@ -1314,7 +1396,7 @@ class AddressEditComponent {
1314
1396
  countryOptions = input([], ...(ngDevMode ? [{ debugName: "countryOptions" }] : []));
1315
1397
  form = input(...(ngDevMode ? [undefined, { debugName: "form" }] : []));
1316
1398
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.1", ngImport: i0, type: AddressEditComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1317
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.1", type: AddressEditComponent, isStandalone: true, selector: "lib-address-edit", inputs: { countryOptions: { classPropertyName: "countryOptions", publicName: "countryOptions", isSignal: true, isRequired: false, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if(form(); as form){\n<div [formGroup]=\"form\" style=\"display: flex; flex-direction: column; width:100%\">\n <mat-form-field class=\"form-field-wide\">\n <mat-label for=\"line1\">Line 1</mat-label>\n <input matInput placeholder=\"Address Line 1\" type=\"text\" id=\"line1\" formControlName=\"line1\" name=\"line1\"\n required>\n </mat-form-field>\n\n <mat-form-field class=\"form-field-wide\">\n <mat-label for=\"line2\">Line 2</mat-label>\n <input matInput placeholder=\"Address Line 2\" type=\"text\" id=\"line2\" formControlName=\"line2\" name=\"line1\" required>\n </mat-form-field>\n\n <mat-form-field class=\"form-field-wide\">\n <mat-label for=\"line3\">Line 3</mat-label>\n <input matInput placeholder=\"Address Line 3\" type=\"text\" id=\"line3\" formControlName=\"line3\" name=\"line3\" required>\n </mat-form-field>\n\n <mat-form-field class=\"form-field\">\n <mat-label for=\"city\">City</mat-label>\n <input matInput placeholder=\"City\" type=\"text\" id=\"city\" formControlName=\"city\" name=\"city\" required>\n </mat-form-field>\n\n <mat-form-field class=\"form-field\">\n <mat-label for=\"region\">Region</mat-label>\n <input matInput placeholder=\"Region\" type=\"text\" id=\"region\" formControlName=\"region\" name=\"region\" required>\n </mat-form-field>\n\n <mat-form-field class=\"form-field\">\n <mat-label for=\"line1\">Post Code</mat-label>\n <input matInput placeholder=\"Post Code\" type=\"text\" id=\"postcode\" formControlName=\"postcode\" name=\"postcode\" required>\n </mat-form-field>\n\n <mat-form-field class=\"form-field\">\n <mat-label for=\"country\">Country</mat-label>\n <mat-select formControlName=\"country\" name=\"country\" id=\"country\" required>\n @for(country of countryOptions(); track $index){\n <mat-option [value]=\"country.code\">{{country.name}}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n</div>\n}", styles: [""], dependencies: [{ kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3$1.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i6.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$2.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.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: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }] });
1399
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.1", type: AddressEditComponent, isStandalone: true, selector: "lib-address-edit", inputs: { countryOptions: { classPropertyName: "countryOptions", publicName: "countryOptions", isSignal: true, isRequired: false, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if(form(); as form){\n<div [formGroup]=\"form\" style=\"display: flex; flex-direction: column; width:100%\">\n <mat-form-field class=\"form-field-wide\">\n <mat-label for=\"line1\">Line 1</mat-label>\n <input matInput placeholder=\"Address Line 1\" type=\"text\" id=\"line1\" formControlName=\"line1\" name=\"line1\"\n required>\n </mat-form-field>\n\n <mat-form-field class=\"form-field-wide\">\n <mat-label for=\"line2\">Line 2</mat-label>\n <input matInput placeholder=\"Address Line 2\" type=\"text\" id=\"line2\" formControlName=\"line2\" name=\"line1\">\n </mat-form-field>\n\n <mat-form-field class=\"form-field-wide\">\n <mat-label for=\"line3\">Line 3</mat-label>\n <input matInput placeholder=\"Address Line 3\" type=\"text\" id=\"line3\" formControlName=\"line3\" name=\"line3\">\n </mat-form-field>\n\n <mat-form-field class=\"form-field\">\n <mat-label for=\"city\">City</mat-label>\n <input matInput placeholder=\"City\" type=\"text\" id=\"city\" formControlName=\"city\" name=\"city\">\n </mat-form-field>\n\n <mat-form-field class=\"form-field\">\n <mat-label for=\"region\">Region</mat-label>\n <input matInput placeholder=\"Region\" type=\"text\" id=\"region\" formControlName=\"region\" name=\"region\" required>\n </mat-form-field>\n\n <mat-form-field class=\"form-field\">\n <mat-label for=\"line1\">Post Code</mat-label>\n <input matInput placeholder=\"Post Code\" type=\"text\" id=\"postcode\" formControlName=\"postcode\" name=\"postcode\">\n </mat-form-field>\n\n <mat-form-field class=\"form-field\">\n <mat-label for=\"country\">Country</mat-label>\n <mat-select formControlName=\"country\" name=\"country\" id=\"country\" required>\n @for(country of countryOptions(); track $index){\n <mat-option [value]=\"country.code\">{{country.name}}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n</div>\n}", styles: [""], dependencies: [{ kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3$1.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i6.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$2.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.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: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }] });
1318
1400
  }
1319
1401
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.1", ngImport: i0, type: AddressEditComponent, decorators: [{
1320
1402
  type: Component,
@@ -1322,7 +1404,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.1", ngImpor
1322
1404
  MatSelectModule,
1323
1405
  MatInputModule,
1324
1406
  ReactiveFormsModule
1325
- ], template: "@if(form(); as form){\n<div [formGroup]=\"form\" style=\"display: flex; flex-direction: column; width:100%\">\n <mat-form-field class=\"form-field-wide\">\n <mat-label for=\"line1\">Line 1</mat-label>\n <input matInput placeholder=\"Address Line 1\" type=\"text\" id=\"line1\" formControlName=\"line1\" name=\"line1\"\n required>\n </mat-form-field>\n\n <mat-form-field class=\"form-field-wide\">\n <mat-label for=\"line2\">Line 2</mat-label>\n <input matInput placeholder=\"Address Line 2\" type=\"text\" id=\"line2\" formControlName=\"line2\" name=\"line1\" required>\n </mat-form-field>\n\n <mat-form-field class=\"form-field-wide\">\n <mat-label for=\"line3\">Line 3</mat-label>\n <input matInput placeholder=\"Address Line 3\" type=\"text\" id=\"line3\" formControlName=\"line3\" name=\"line3\" required>\n </mat-form-field>\n\n <mat-form-field class=\"form-field\">\n <mat-label for=\"city\">City</mat-label>\n <input matInput placeholder=\"City\" type=\"text\" id=\"city\" formControlName=\"city\" name=\"city\" required>\n </mat-form-field>\n\n <mat-form-field class=\"form-field\">\n <mat-label for=\"region\">Region</mat-label>\n <input matInput placeholder=\"Region\" type=\"text\" id=\"region\" formControlName=\"region\" name=\"region\" required>\n </mat-form-field>\n\n <mat-form-field class=\"form-field\">\n <mat-label for=\"line1\">Post Code</mat-label>\n <input matInput placeholder=\"Post Code\" type=\"text\" id=\"postcode\" formControlName=\"postcode\" name=\"postcode\" required>\n </mat-form-field>\n\n <mat-form-field class=\"form-field\">\n <mat-label for=\"country\">Country</mat-label>\n <mat-select formControlName=\"country\" name=\"country\" id=\"country\" required>\n @for(country of countryOptions(); track $index){\n <mat-option [value]=\"country.code\">{{country.name}}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n</div>\n}" }]
1407
+ ], template: "@if(form(); as form){\n<div [formGroup]=\"form\" style=\"display: flex; flex-direction: column; width:100%\">\n <mat-form-field class=\"form-field-wide\">\n <mat-label for=\"line1\">Line 1</mat-label>\n <input matInput placeholder=\"Address Line 1\" type=\"text\" id=\"line1\" formControlName=\"line1\" name=\"line1\"\n required>\n </mat-form-field>\n\n <mat-form-field class=\"form-field-wide\">\n <mat-label for=\"line2\">Line 2</mat-label>\n <input matInput placeholder=\"Address Line 2\" type=\"text\" id=\"line2\" formControlName=\"line2\" name=\"line1\">\n </mat-form-field>\n\n <mat-form-field class=\"form-field-wide\">\n <mat-label for=\"line3\">Line 3</mat-label>\n <input matInput placeholder=\"Address Line 3\" type=\"text\" id=\"line3\" formControlName=\"line3\" name=\"line3\">\n </mat-form-field>\n\n <mat-form-field class=\"form-field\">\n <mat-label for=\"city\">City</mat-label>\n <input matInput placeholder=\"City\" type=\"text\" id=\"city\" formControlName=\"city\" name=\"city\">\n </mat-form-field>\n\n <mat-form-field class=\"form-field\">\n <mat-label for=\"region\">Region</mat-label>\n <input matInput placeholder=\"Region\" type=\"text\" id=\"region\" formControlName=\"region\" name=\"region\" required>\n </mat-form-field>\n\n <mat-form-field class=\"form-field\">\n <mat-label for=\"line1\">Post Code</mat-label>\n <input matInput placeholder=\"Post Code\" type=\"text\" id=\"postcode\" formControlName=\"postcode\" name=\"postcode\">\n </mat-form-field>\n\n <mat-form-field class=\"form-field\">\n <mat-label for=\"country\">Country</mat-label>\n <mat-select formControlName=\"country\" name=\"country\" id=\"country\" required>\n @for(country of countryOptions(); track $index){\n <mat-option [value]=\"country.code\">{{country.name}}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n</div>\n}" }]
1326
1408
  }] });
1327
1409
 
1328
1410
  class AddressDetailComponent {
@@ -1349,22 +1431,22 @@ class AddressForm {
1349
1431
  setValue(item) {
1350
1432
  this.form.setValue({
1351
1433
  line1: item.line1,
1352
- line2: item.line2,
1353
- line3: item.line3,
1354
- city: item.city,
1434
+ line2: item.line2 || "",
1435
+ line3: item.line3 || "",
1436
+ city: item.city || "",
1355
1437
  region: item.region,
1356
- postcode: item.postcode,
1438
+ postcode: item.postcode || "",
1357
1439
  country: item.country
1358
1440
  });
1359
1441
  }
1360
1442
  updateItem(item) {
1361
1443
  const value = this.form.value;
1362
1444
  item.line1 = value.line1 || "";
1363
- item.line2 = value.line2 || "";
1364
- item.line3 = value.line3 || "";
1365
- item.city = value.city || "";
1445
+ item.line2 = value.line2 || null;
1446
+ item.line3 = value.line3 || null;
1447
+ item.city = value.city || null;
1366
1448
  item.region = value.region || "";
1367
- item.postcode = value.postcode || "";
1449
+ item.postcode = value.postcode || null;
1368
1450
  item.country = value.country || "";
1369
1451
  return item;
1370
1452
  }
@@ -1386,7 +1468,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.1", ngImpor
1386
1468
  }] });
1387
1469
 
1388
1470
  class OrganizationDetailComponent extends DetailView {
1389
- typename = "organization";
1390
1471
  address = signal(null, ...(ngDevMode ? [{ debugName: "address" }] : []));
1391
1472
  addressService = inject(AddressService);
1392
1473
  constructor() {
@@ -1399,14 +1480,51 @@ class OrganizationDetailComponent extends DetailView {
1399
1480
  this.addressService.getUrl(item.address).subscribe(address => this.address.set(address));
1400
1481
  }
1401
1482
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.1", ngImport: i0, type: OrganizationDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1402
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.1", type: OrganizationDetailComponent, isStandalone: true, selector: "lib-organization-detail", usesInheritance: true, ngImport: i0, template: "<lib-back-button></lib-back-button>\n\n<div class=\"content-container\">\n\n @if(item(); as item){\n <div class=\"item-detail-container\">\n\n <lib-detail-header\n [text]=\"item.name\"\n [id]=\"item.id\"\n [canEdit]=\"itemService.canEdit()\"\n [canDelete]=\"itemService.canDelete()\"\n [route]=\"typename\"></lib-detail-header>\n\n <div class=\"item-field\">\n {{item.description}}\n </div>\n \n @if(address(); as address)\n {\n <h3>Address</h3>\n <lib-address-detail [address]=\"address\"></lib-address-detail>\n }\n <div class=\"item-field\">\n <h3>Website</h3>{{item.website}}\n </div>\n </div>\n }\n</div>\n", styles: [":host{flex-grow:1}.padded_button{padding:5px}\n"], dependencies: [{ kind: "component", type: BackButtonComponent, selector: "lib-back-button" }, { kind: "component", type: DetailHeaderComponent, selector: "lib-detail-header", inputs: ["id", "text", "route", "canEdit", "canDelete"], outputs: ["deleteClicked"] }, { kind: "component", type: AddressDetailComponent, selector: "lib-address-detail", inputs: ["address"] }] });
1483
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.1", type: OrganizationDetailComponent, isStandalone: true, selector: "lib-organization-detail", usesInheritance: true, ngImport: i0, template: "<lib-back-button></lib-back-button>\n\n<div class=\"content-container\">\n\n @if(item(); as item){\n <div class=\"item-detail-container\">\n\n <lib-detail-header\n [text]=\"item.name\"\n [id]=\"item.id\"\n [canEdit]=\"itemService.canEdit()\"\n [canDelete]=\"itemService.canDelete()\"\n [route]=\"itemService.typePlural()\"></lib-detail-header>\n\n <div class=\"item-field\">\n {{item.description}}\n </div>\n \n @if(address(); as address)\n {\n <h3>Address</h3>\n <lib-address-detail [address]=\"address\"></lib-address-detail>\n }\n <div class=\"item-field\">\n <h3>Website</h3>{{item.website}}\n </div>\n </div>\n }\n</div>\n", styles: [":host{flex-grow:1}.padded_button{padding:5px}\n"], dependencies: [{ kind: "component", type: BackButtonComponent, selector: "lib-back-button" }, { kind: "component", type: DetailHeaderComponent, selector: "lib-detail-header", inputs: ["id", "text", "route", "canEdit", "canDelete"], outputs: ["deleteClicked"] }, { kind: "component", type: AddressDetailComponent, selector: "lib-address-detail", inputs: ["address"] }] });
1403
1484
  }
1404
1485
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.1", ngImport: i0, type: OrganizationDetailComponent, decorators: [{
1405
1486
  type: Component,
1406
1487
  args: [{ selector: 'lib-organization-detail', imports: [BackButtonComponent,
1407
- DetailHeaderComponent, AddressDetailComponent], template: "<lib-back-button></lib-back-button>\n\n<div class=\"content-container\">\n\n @if(item(); as item){\n <div class=\"item-detail-container\">\n\n <lib-detail-header\n [text]=\"item.name\"\n [id]=\"item.id\"\n [canEdit]=\"itemService.canEdit()\"\n [canDelete]=\"itemService.canDelete()\"\n [route]=\"typename\"></lib-detail-header>\n\n <div class=\"item-field\">\n {{item.description}}\n </div>\n \n @if(address(); as address)\n {\n <h3>Address</h3>\n <lib-address-detail [address]=\"address\"></lib-address-detail>\n }\n <div class=\"item-field\">\n <h3>Website</h3>{{item.website}}\n </div>\n </div>\n }\n</div>\n", styles: [":host{flex-grow:1}.padded_button{padding:5px}\n"] }]
1488
+ DetailHeaderComponent, AddressDetailComponent], template: "<lib-back-button></lib-back-button>\n\n<div class=\"content-container\">\n\n @if(item(); as item){\n <div class=\"item-detail-container\">\n\n <lib-detail-header\n [text]=\"item.name\"\n [id]=\"item.id\"\n [canEdit]=\"itemService.canEdit()\"\n [canDelete]=\"itemService.canDelete()\"\n [route]=\"itemService.typePlural()\"></lib-detail-header>\n\n <div class=\"item-field\">\n {{item.description}}\n </div>\n \n @if(address(); as address)\n {\n <h3>Address</h3>\n <lib-address-detail [address]=\"address\"></lib-address-detail>\n }\n <div class=\"item-field\">\n <h3>Website</h3>{{item.website}}\n </div>\n </div>\n }\n</div>\n", styles: [":host{flex-grow:1}.padded_button{padding:5px}\n"] }]
1408
1489
  }], ctorParameters: () => [] });
1409
1490
 
1491
+ class SelectionManager {
1492
+ candidates = signal([], ...(ngDevMode ? [{ debugName: "candidates" }] : []));
1493
+ selected = signal([], ...(ngDevMode ? [{ debugName: "selected" }] : []));
1494
+ columns = USER_TABLE_FULL;
1495
+ userService = inject(UserService);
1496
+ fetchCandidates(item, id) {
1497
+ const queries = new Map([[item, id.toString()]]);
1498
+ const query = new ItemQuery({ queries: queries });
1499
+ this.userService.get(query).subscribe(users => this.selected.set(users.results));
1500
+ this.userService.get().subscribe(users => this.updateCandidates(users));
1501
+ }
1502
+ updateCandidates(users) {
1503
+ this.candidates.set(users.results.map(user => { return { title: user.username, item: user }; }));
1504
+ }
1505
+ onRemoved(id) {
1506
+ const url = this.selected().find(m => m.id == id).url;
1507
+ this.selected.update(members => members.filter(m => m.id != id));
1508
+ return url;
1509
+ }
1510
+ onAdded(title) {
1511
+ const selected = this.candidates().find(m => m.title == title);
1512
+ if (selected) {
1513
+ this.selected.update(members => { members.push(selected.item); return members; });
1514
+ }
1515
+ this.candidates.update(members => members.filter(m => m.title != title));
1516
+ return this.selected().find(m => m.username == title).url;
1517
+ }
1518
+ onSearchChanged(searchTerm) {
1519
+ if (searchTerm) {
1520
+ this.userService.get(new ItemQuery({ filter: searchTerm })).subscribe(users => this.updateCandidates(users));
1521
+ }
1522
+ else {
1523
+ this.userService.get().subscribe(users => this.updateCandidates(users));
1524
+ }
1525
+ }
1526
+ }
1527
+
1410
1528
  class OrganizationForm {
1411
1529
  formBuilder = inject(FormBuilder);
1412
1530
  form;
@@ -1439,14 +1557,8 @@ class OrganizationForm {
1439
1557
  }
1440
1558
 
1441
1559
  class OrganizationEditComponent extends EditView {
1442
- typename = "Organization";
1443
1560
  countryOptions = signal([], ...(ngDevMode ? [{ debugName: "countryOptions" }] : []));
1444
- candidateMembers = signal([], ...(ngDevMode ? [{ debugName: "candidateMembers" }] : []));
1445
- selectedMembers = signal([], ...(ngDevMode ? [{ debugName: "selectedMembers" }] : []));
1446
- columns = [{ name: 'first_name', title: 'First Name', element_type: 'string' },
1447
- { name: 'last_name', title: 'Surname', element_type: 'string' },
1448
- { name: 'username', title: 'Username', element_type: 'string' },
1449
- { name: 'email', title: 'Email', element_type: 'string' }];
1561
+ selectionManager = new SelectionManager();
1450
1562
  form = new OrganizationForm();
1451
1563
  address = new Address();
1452
1564
  addressService = inject(AddressService);
@@ -1462,15 +1574,9 @@ class OrganizationEditComponent extends EditView {
1462
1574
  if (item) {
1463
1575
  this.fetchAddress(item.address);
1464
1576
  this.form.updateFromItem(item);
1465
- const queries = new Map([["organization", item.id.toString()]]);
1466
- const query = new ItemQuery({ queries: queries });
1467
- this._userService.get(query).subscribe(users => this.selectedMembers.set(users.results));
1468
- this._userService.get().subscribe(users => this.updateCandidates(users));
1577
+ this.selectionManager.fetchCandidates(this.itemService.typename, item.id);
1469
1578
  }
1470
1579
  }
1471
- updateCandidates(users) {
1472
- this.candidateMembers.set(users.results.map(user => { return { title: user.username, item: user }; }));
1473
- }
1474
1580
  submit() {
1475
1581
  this.syncFromForm();
1476
1582
  if (this.createMode()) {
@@ -1519,35 +1625,24 @@ class OrganizationEditComponent extends EditView {
1519
1625
  onCountryChoices(choices) {
1520
1626
  this.countryOptions.update(() => choices.map(c => { return { name: c.display_name, code: c.value, flag: "" }; }));
1521
1627
  }
1522
- onMemberRemoved(id) {
1523
- const url = this.selectedMembers().find(m => m.id == id).url;
1524
- this.selectedMembers.update(members => members.filter(m => m.id != id));
1628
+ addMember(url) {
1525
1629
  this.item.update(item => { if (item) {
1526
- item.members.filter(m => m != url);
1630
+ item.members.push(url);
1527
1631
  } return item; });
1528
1632
  }
1529
- onMemberAdded(title) {
1530
- const selected = this.candidateMembers().find(m => m.title == title);
1531
- if (selected) {
1532
- this.selectedMembers.update(members => { members.push(selected.item); return members; });
1533
- }
1534
- this.candidateMembers.update(members => members.filter(m => m.title != title));
1535
- const url = this.selectedMembers().find(m => m.username == title).url;
1633
+ removeMember(url) {
1536
1634
  this.item.update(item => { if (item) {
1537
- item.members.push(url);
1635
+ item.members.filter(m => m != url);
1538
1636
  } return item; });
1539
1637
  }
1540
- onSearchChanged(searchTerm) {
1541
- console.log("Batch requested");
1542
- if (searchTerm) {
1543
- this._userService.get(new ItemQuery({ filter: searchTerm })).subscribe(users => this.updateCandidates(users));
1544
- }
1545
- else {
1546
- this._userService.get().subscribe(users => this.updateCandidates(users));
1547
- }
1638
+ onMemberRemoved(id) {
1639
+ this.removeMember(this.selectionManager.onRemoved(id));
1640
+ }
1641
+ onMemberAdded(title) {
1642
+ this.addMember(this.selectionManager.onAdded(title));
1548
1643
  }
1549
1644
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.1", ngImport: i0, type: OrganizationEditComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1550
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.1", type: OrganizationEditComponent, isStandalone: true, selector: "lib-organization-edit", usesInheritance: true, ngImport: i0, template: "<lib-back-button></lib-back-button>\n\n<div class=\"content-container\">\n\n @if(item(); as item){\n <div class=\"item-edit-container\">\n \n <h1 class=\"item-edit-header\">{{heading()}}</h1>\n\n <form class=\"form-card\" [formGroup]=\"form.form\" (ngSubmit)=\"submit()\">\n\n <mat-form-field class=\"form-field-wide\">\n <mat-label for=\"name\">Name</mat-label>\n <input matInput placeholder=\"Name\"\n type=\"text\"\n id=\"name\"\n formControlName=\"name\"\n name=\"name\"\n required>\n </mat-form-field>\n \n <mat-form-field class=\"form-field\">\n <mat-label for=\"acronym\">Acronym</mat-label> \n <input matInput placeholder=\"Acronym\"\n type=\"text\"\n id=\"acronym\"\n formControlName=\"acronym\"\n name=\"acronym\">\n </mat-form-field>\n \n <mat-form-field class=\"form-field-wide\">\n <mat-label for=\"description\">Description</mat-label>\n <textarea matInput placeholder=\"Description\"\n type=\"text\"\n id=\"description\"\n formControlName=\"description\"\n name=\"description\"\n class=\"medium-textarea\"\n required></textarea>\n </mat-form-field>\n\n <h3>Address</h3>\n <lib-address-edit \n [countryOptions]=\"countryOptions()\"\n [form]=\"form.address.form\"\n ></lib-address-edit>\n\n <h3>Members</h3>\n <lib-select-table [selected]=\"selectedMembers()\"\n [itemType]=\"'Members'\"\n [columns]=\"columns\"\n [options]=\"candidateMembers()\"\n (itemAdded)=\"onMemberAdded($event)\"\n (itemRemoved)=\"onMemberRemoved($event)\"\n (searchChanged)=\"onSearchChanged($event)\">\n </lib-select-table>\n\n <div class=\"button_group\">\n <button mat-fab\n class=\"form_action_button\"\n type=\"submit\"\n [disabled]=\"!form.form.valid\">\n <mat-icon>save</mat-icon>\n </button>\n \n <button mat-fab\n class=\"form_action_button\"\n (click)=\"cancel()\">\n <mat-icon>cancel</mat-icon>\n </button>\n </div>\n \n </form>\n </div>\n }\n</div>\n", styles: [":host{flex-grow:1}.medium-textarea{min-height:150px}.checkbox_group{justify-content:left;display:flex;flex-direction:column}.button_group{margin:10px;display:flex;flex-direction:row;justify-content:center}.btn-block{padding:5px}.form_action_button{padding:5px;margin:5px}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$2.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: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2$1.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$2.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i3$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3$1.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatSelectModule }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: BackButtonComponent, selector: "lib-back-button" }, { kind: "component", type: AddressEditComponent, selector: "lib-address-edit", inputs: ["countryOptions", "form"] }, { kind: "component", type: SelectTableComponent, selector: "lib-select-table", inputs: ["itemType", "selected", "options", "columns"], outputs: ["itemAdded", "itemRemoved", "searchChanged"] }] });
1645
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.1", type: OrganizationEditComponent, isStandalone: true, selector: "lib-organization-edit", usesInheritance: true, ngImport: i0, template: "<lib-back-button></lib-back-button>\n\n<div class=\"content-container\">\n\n @if(item(); as item){\n <div class=\"item-edit-container\">\n \n <h1 class=\"item-edit-header\">{{heading() | titlecase}}</h1>\n\n <form class=\"form-card\" [formGroup]=\"form.form\" (ngSubmit)=\"submit()\">\n\n <mat-form-field class=\"form-field-wide\">\n <mat-label for=\"name\">Name</mat-label>\n <input matInput placeholder=\"Name\"\n type=\"text\"\n id=\"name\"\n formControlName=\"name\"\n name=\"name\"\n required>\n </mat-form-field>\n \n <mat-form-field class=\"form-field\">\n <mat-label for=\"acronym\">Acronym</mat-label> \n <input matInput placeholder=\"Acronym\"\n type=\"text\"\n id=\"acronym\"\n formControlName=\"acronym\"\n name=\"acronym\">\n </mat-form-field>\n \n <mat-form-field class=\"form-field-wide\">\n <mat-label for=\"description\">Description</mat-label>\n <textarea matInput placeholder=\"Description\"\n type=\"text\"\n id=\"description\"\n formControlName=\"description\"\n name=\"description\"\n class=\"medium-textarea\"\n required></textarea>\n </mat-form-field>\n\n <h3>Address</h3>\n <lib-address-edit \n [countryOptions]=\"countryOptions()\"\n [form]=\"form.address.form\"\n ></lib-address-edit>\n\n <h3>Members</h3>\n <lib-select-table [selected]=\"selectionManager.selected()\"\n [itemType]=\"'Members'\"\n [columns]=\"selectionManager.columns\"\n [options]=\"selectionManager.candidates()\"\n (itemAdded)=\"onMemberAdded($event)\"\n (itemRemoved)=\"onMemberRemoved($event)\"\n (searchChanged)=\"selectionManager.onSearchChanged($event)\">\n </lib-select-table>\n\n </form>\n <div class=\"button_group\">\n <button mat-fab\n class=\"form_action_button\"\n type=\"submit\"\n [disabled]=\"!form.form.valid\">\n <mat-icon>save</mat-icon>\n </button>\n \n <button mat-fab\n class=\"form_action_button\"\n (click)=\"cancel()\">\n <mat-icon>cancel</mat-icon>\n </button>\n </div>\n\n </div>\n }\n</div>\n", styles: [":host{flex-grow:1}.medium-textarea{min-height:150px}.checkbox_group{justify-content:left;display:flex;flex-direction:column}.button_group{margin:10px;display:flex;flex-direction:row;justify-content:center}.btn-block{padding:5px}.form_action_button{padding:5px;margin:5px}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$2.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: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2$1.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$2.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i3$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3$1.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatSelectModule }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: BackButtonComponent, selector: "lib-back-button" }, { kind: "component", type: AddressEditComponent, selector: "lib-address-edit", inputs: ["countryOptions", "form"] }, { kind: "component", type: SelectTableComponent, selector: "lib-select-table", inputs: ["itemType", "selected", "options", "columns"], outputs: ["itemAdded", "itemRemoved", "searchChanged"] }, { kind: "pipe", type: TitleCasePipe, name: "titlecase" }] });
1551
1646
  }
1552
1647
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.1", ngImport: i0, type: OrganizationEditComponent, decorators: [{
1553
1648
  type: Component,
@@ -1561,8 +1656,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.1", ngImpor
1561
1656
  MatCheckboxModule,
1562
1657
  BackButtonComponent,
1563
1658
  AddressEditComponent,
1564
- SelectTableComponent
1565
- ], template: "<lib-back-button></lib-back-button>\n\n<div class=\"content-container\">\n\n @if(item(); as item){\n <div class=\"item-edit-container\">\n \n <h1 class=\"item-edit-header\">{{heading()}}</h1>\n\n <form class=\"form-card\" [formGroup]=\"form.form\" (ngSubmit)=\"submit()\">\n\n <mat-form-field class=\"form-field-wide\">\n <mat-label for=\"name\">Name</mat-label>\n <input matInput placeholder=\"Name\"\n type=\"text\"\n id=\"name\"\n formControlName=\"name\"\n name=\"name\"\n required>\n </mat-form-field>\n \n <mat-form-field class=\"form-field\">\n <mat-label for=\"acronym\">Acronym</mat-label> \n <input matInput placeholder=\"Acronym\"\n type=\"text\"\n id=\"acronym\"\n formControlName=\"acronym\"\n name=\"acronym\">\n </mat-form-field>\n \n <mat-form-field class=\"form-field-wide\">\n <mat-label for=\"description\">Description</mat-label>\n <textarea matInput placeholder=\"Description\"\n type=\"text\"\n id=\"description\"\n formControlName=\"description\"\n name=\"description\"\n class=\"medium-textarea\"\n required></textarea>\n </mat-form-field>\n\n <h3>Address</h3>\n <lib-address-edit \n [countryOptions]=\"countryOptions()\"\n [form]=\"form.address.form\"\n ></lib-address-edit>\n\n <h3>Members</h3>\n <lib-select-table [selected]=\"selectedMembers()\"\n [itemType]=\"'Members'\"\n [columns]=\"columns\"\n [options]=\"candidateMembers()\"\n (itemAdded)=\"onMemberAdded($event)\"\n (itemRemoved)=\"onMemberRemoved($event)\"\n (searchChanged)=\"onSearchChanged($event)\">\n </lib-select-table>\n\n <div class=\"button_group\">\n <button mat-fab\n class=\"form_action_button\"\n type=\"submit\"\n [disabled]=\"!form.form.valid\">\n <mat-icon>save</mat-icon>\n </button>\n \n <button mat-fab\n class=\"form_action_button\"\n (click)=\"cancel()\">\n <mat-icon>cancel</mat-icon>\n </button>\n </div>\n \n </form>\n </div>\n }\n</div>\n", styles: [":host{flex-grow:1}.medium-textarea{min-height:150px}.checkbox_group{justify-content:left;display:flex;flex-direction:column}.button_group{margin:10px;display:flex;flex-direction:row;justify-content:center}.btn-block{padding:5px}.form_action_button{padding:5px;margin:5px}\n"] }]
1659
+ SelectTableComponent,
1660
+ TitleCasePipe
1661
+ ], template: "<lib-back-button></lib-back-button>\n\n<div class=\"content-container\">\n\n @if(item(); as item){\n <div class=\"item-edit-container\">\n \n <h1 class=\"item-edit-header\">{{heading() | titlecase}}</h1>\n\n <form class=\"form-card\" [formGroup]=\"form.form\" (ngSubmit)=\"submit()\">\n\n <mat-form-field class=\"form-field-wide\">\n <mat-label for=\"name\">Name</mat-label>\n <input matInput placeholder=\"Name\"\n type=\"text\"\n id=\"name\"\n formControlName=\"name\"\n name=\"name\"\n required>\n </mat-form-field>\n \n <mat-form-field class=\"form-field\">\n <mat-label for=\"acronym\">Acronym</mat-label> \n <input matInput placeholder=\"Acronym\"\n type=\"text\"\n id=\"acronym\"\n formControlName=\"acronym\"\n name=\"acronym\">\n </mat-form-field>\n \n <mat-form-field class=\"form-field-wide\">\n <mat-label for=\"description\">Description</mat-label>\n <textarea matInput placeholder=\"Description\"\n type=\"text\"\n id=\"description\"\n formControlName=\"description\"\n name=\"description\"\n class=\"medium-textarea\"\n required></textarea>\n </mat-form-field>\n\n <h3>Address</h3>\n <lib-address-edit \n [countryOptions]=\"countryOptions()\"\n [form]=\"form.address.form\"\n ></lib-address-edit>\n\n <h3>Members</h3>\n <lib-select-table [selected]=\"selectionManager.selected()\"\n [itemType]=\"'Members'\"\n [columns]=\"selectionManager.columns\"\n [options]=\"selectionManager.candidates()\"\n (itemAdded)=\"onMemberAdded($event)\"\n (itemRemoved)=\"onMemberRemoved($event)\"\n (searchChanged)=\"selectionManager.onSearchChanged($event)\">\n </lib-select-table>\n\n </form>\n <div class=\"button_group\">\n <button mat-fab\n class=\"form_action_button\"\n type=\"submit\"\n [disabled]=\"!form.form.valid\">\n <mat-icon>save</mat-icon>\n </button>\n \n <button mat-fab\n class=\"form_action_button\"\n (click)=\"cancel()\">\n <mat-icon>cancel</mat-icon>\n </button>\n </div>\n\n </div>\n }\n</div>\n", styles: [":host{flex-grow:1}.medium-textarea{min-height:150px}.checkbox_group{justify-content:left;display:flex;flex-direction:column}.button_group{margin:10px;display:flex;flex-direction:row;justify-content:center}.btn-block{padding:5px}.form_action_button{padding:5px;margin:5px}\n"] }]
1566
1662
  }], ctorParameters: () => [] });
1567
1663
 
1568
1664
  /*
@@ -1573,5 +1669,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.1", ngImpor
1573
1669
  * Generated bundle index. Do not edit.
1574
1670
  */
1575
1671
 
1576
- export { Address, AddressDetailComponent, AddressEditComponent, AddressForm, AddressService, ApiError, BackButtonComponent, DetailHeaderComponent, DetailView, ENDPOINT_URL, EditView, ErrorCode, FileRecord, FileUploadComponent, Group, GroupComponent, GroupDetailComponent, GroupService, ItemQuery, ItemService, LOGIN_USER, LandingComponent, LeftNavComponent, LeftNavService, ListTableViewComponent, ListView, ListViewComponent, Organization, OrganizationComponent, OrganizationDetailComponent, OrganizationEditComponent, OrganizationService, Paginated, Permission, PortalMember, ResolvedPermission, RestService, SearchBarComponent, SelectTableComponent, TopBarComponent, UserComponent, UserDetailComponent, UserEditComponent, UserService };
1672
+ export { Address, AddressDetailComponent, AddressEditComponent, AddressForm, AddressService, ApiError, AvatarComponent, BackButtonComponent, DetailHeaderComponent, DetailView, ENDPOINT_URL, EditView, ErrorCode, FeedbackComponent, FileRecord, FileUploadComponent, Group, GroupComponent, GroupDetailComponent, GroupService, ItemQuery, ItemService, ItemWithUserService, LOGIN_USER, LandingComponent, LeftNavComponent, LeftNavService, ListTableViewComponent, ListViewComponent, MockItemService, Organization, OrganizationComponent, OrganizationDetailComponent, OrganizationEditComponent, OrganizationService, Paginated, Permission, PortalMember, ResolvedPermission, RestService, SearchBarComponent, SelectTableComponent, TopBarComponent, UserComponent, UserDetailComponent, UserEditComponent, UserService };
1577
1673
  //# sourceMappingURL=ichec-angular-core.mjs.map