http-request-manager 18.11.9 → 18.11.12

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,58 +1,17 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, Injectable, APP_ID, Inject, InjectionToken, isDevMode, signal, effect, computed, Injector, Optional, EventEmitter, Input, Output, ViewEncapsulation, Component, NgModule, ViewChild } from '@angular/core';
2
+ import { inject, Injectable, APP_ID, Inject, InjectionToken, isDevMode, signal, effect, computed, Injector, Optional, NgModule } from '@angular/core';
3
3
  import { ComponentStore } from '@ngrx/component-store';
4
4
  import { map, catchError, filter, tap, finalize, takeWhile, retry, startWith, mergeMap, takeUntil, concatMap, toArray, withLatestFrom, switchMap, delay, take, scan, distinctUntilChanged } from 'rxjs/operators';
5
5
  import { HttpClient, HttpHeaders, HttpEventType, HttpHeaderResponse, HttpErrorResponse, HTTP_INTERCEPTORS } from '@angular/common/http';
6
6
  import * as CryptoJS from 'crypto-js';
7
- import { from, BehaviorSubject, EMPTY, throwError, defer, interval, timer, Subject, of, merge, Subscription, take as take$1, catchError as catchError$1, map as map$1, tap as tap$1, switchMap as switchMap$1, startWith as startWith$1, distinctUntilChanged as distinctUntilChanged$1, combineLatest, filter as filter$1, takeUntil as takeUntil$1, ReplaySubject } from 'rxjs';
7
+ import { from, BehaviorSubject, EMPTY, throwError, defer, interval, timer, Subject, of, merge, Subscription, take as take$1, catchError as catchError$1 } from 'rxjs';
8
8
  import { toObservable } from '@angular/core/rxjs-interop';
9
9
  import { ToastMessageDisplayService, ToastDisplay, ToastColors, ToastMessageDisplayModule } from 'toast-message-display';
10
10
  import Dexie from 'dexie';
11
11
  import * as i1 from '@ngx-translate/core';
12
12
  import { TranslateModule } from '@ngx-translate/core';
13
- import * as i1$1 from '@angular/common';
14
13
  import { CommonModule } from '@angular/common';
15
- import * as i2$1 from '@angular/forms';
16
- import { FormBuilder, Validators, FormsModule, ReactiveFormsModule } from '@angular/forms';
17
- import { MatAutocompleteModule } from '@angular/material/autocomplete';
18
- import * as i3 from '@angular/material/button';
19
- import { MatButtonModule } from '@angular/material/button';
20
- import * as i3$1 from '@angular/material/chips';
21
- import { MatChipsModule } from '@angular/material/chips';
22
- import * as i12 from '@angular/material/divider';
23
- import { MatDividerModule } from '@angular/material/divider';
24
- import * as i4 from '@angular/material/form-field';
25
- import { MatFormFieldModule } from '@angular/material/form-field';
26
- import * as i8 from '@angular/material/icon';
27
- import { MatIconModule } from '@angular/material/icon';
28
- import * as i13 from '@angular/material/input';
29
- import { MatInputModule } from '@angular/material/input';
30
- import * as i8$1 from '@angular/material/progress-bar';
31
- import { MatProgressBarModule } from '@angular/material/progress-bar';
32
- import * as i2 from '@angular/material/progress-spinner';
33
- import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
34
- import * as i5 from '@angular/material/select';
35
- import { MatSelectModule } from '@angular/material/select';
36
- import { MatSidenavModule } from '@angular/material/sidenav';
37
- import * as i11 from '@angular/material/slide-toggle';
38
- import { MatSlideToggleModule } from '@angular/material/slide-toggle';
39
- import * as i7 from '@angular/material/menu';
40
- import { MatMenuModule } from '@angular/material/menu';
41
- import * as i3$2 from '@angular/material/toolbar';
42
- import { MatToolbarModule } from '@angular/material/toolbar';
43
- import * as i9 from '@angular/material/table';
44
- import { MatTableModule } from '@angular/material/table';
45
- import * as i10 from '@angular/material/button-toggle';
46
- import { MatButtonToggleModule } from '@angular/material/button-toggle';
47
- import * as i9$1 from '@angular/material/datepicker';
48
- import { MatDatepickerModule } from '@angular/material/datepicker';
49
- import * as i6 from '@angular/material/core';
50
- import { MatNativeDateModule } from '@angular/material/core';
51
- import * as i16 from '@angular/material/card';
52
- import { MatCardModule } from '@angular/material/card';
53
- import * as i1$2 from '@angular/material/tabs';
54
- import { MatTabsModule } from '@angular/material/tabs';
55
- import { DataSource } from '@angular/cdk/collections';
14
+ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
56
15
 
57
16
  var StorageType;
58
17
  (function (StorageType) {
@@ -2961,6 +2920,19 @@ class BatchResult {
2961
2920
  }
2962
2921
  }
2963
2922
 
2923
+ class BatchRequestResultModel {
2924
+ constructor(request = null, data = null, index = 0, status = 'success', error) {
2925
+ this.request = request;
2926
+ this.data = data;
2927
+ this.index = index;
2928
+ this.status = status;
2929
+ this.error = error;
2930
+ }
2931
+ static adapt(item) {
2932
+ return new BatchRequestResultModel(item?.request, item?.data, item?.index, item?.status, item?.error);
2933
+ }
2934
+ }
2935
+
2964
2936
  function isPendingState(state) {
2965
2937
  return state.isPending === true;
2966
2938
  }
@@ -3778,7 +3750,7 @@ class HTTPManagerService extends RequestService {
3778
3750
  * Execute multiple HTTP requests with configurable execution strategy
3779
3751
  * @param requests Array of ApiRequest configurations
3780
3752
  * @param options Optional batch configuration (all properties optional)
3781
- * @returns Observable emitting array of data in the order of requests provided
3753
+ * @returns Observable emitting array of wrapped results in the order of requests provided
3782
3754
  */
3783
3755
  getBatchRequests(requests, options) {
3784
3756
  const batchOptions = BatchOptions.adapt(options);
@@ -3794,22 +3766,22 @@ class HTTPManagerService extends RequestService {
3794
3766
  * Execute requests sequentially (one at a time, in order)
3795
3767
  * @param requests Array of ApiRequest configurations
3796
3768
  * @param options Optional batch configuration
3797
- * @returns Observable emitting array of data when all requests complete
3769
+ * @returns Observable emitting array of wrapped results when all requests complete
3798
3770
  */
3799
3771
  getSequentialRequest(requests, options) {
3800
3772
  const batchOptions = BatchOptions.adapt(options);
3801
3773
  const results = new Array(requests.length);
3802
3774
  return from(requests).pipe(concatMap((req, index) => {
3803
3775
  return this.getRequest(req).pipe(tap(data => {
3804
- results[index] = data;
3805
- }), catchError(error => this.handleSequentialError(req, error, index, batchOptions)));
3776
+ results[index] = new BatchRequestResultModel(req, data, index, 'success');
3777
+ }), catchError(error => this.handleSequentialError(req, error, index, batchOptions).pipe(tap(result => { results[index] = result; }))));
3806
3778
  }), toArray(), map(() => results));
3807
3779
  }
3808
3780
  /**
3809
3781
  * Execute requests in parallel with concurrency control
3810
3782
  * @param requests Array of ApiRequest configurations
3811
3783
  * @param options Optional batch configuration with concurrency limit
3812
- * @returns Observable emitting array of data when all requests complete
3784
+ * @returns Observable emitting array of wrapped results when all requests complete
3813
3785
  */
3814
3786
  getParallelRequest(requests, options) {
3815
3787
  const batchOptions = BatchOptions.adapt(options);
@@ -3817,8 +3789,8 @@ class HTTPManagerService extends RequestService {
3817
3789
  const results = new Array(requests.length);
3818
3790
  return from(requests).pipe(mergeMap((req, index) => {
3819
3791
  return this.getRequest(req).pipe(tap(data => {
3820
- results[index] = data;
3821
- }), catchError(error => this.handleParallelError(req, error, index, batchOptions)));
3792
+ results[index] = new BatchRequestResultModel(req, data, index, 'success');
3793
+ }), catchError(error => this.handleParallelError(req, error, index, batchOptions).pipe(tap(result => { results[index] = result; }))));
3822
3794
  }, concurrency), toArray(), map(() => results));
3823
3795
  }
3824
3796
  /**
@@ -3893,19 +3865,13 @@ class HTTPManagerService extends RequestService {
3893
3865
  if (options.stopOnError) {
3894
3866
  return throwError(() => error);
3895
3867
  }
3896
- if (options.ignoreErrors) {
3897
- return of(undefined);
3898
- }
3899
- return throwError(() => error);
3868
+ return of(new BatchRequestResultModel(request, null, index, 'error', error));
3900
3869
  }
3901
3870
  handleParallelError(request, error, index, options) {
3902
3871
  if (options.logErrors !== false) {
3903
3872
  console.error(`Batch request ${index} failed:`, error);
3904
3873
  }
3905
- if (options.ignoreErrors) {
3906
- return of(undefined);
3907
- }
3908
- return of(undefined);
3874
+ return of(new BatchRequestResultModel(request, null, index, 'error', error));
3909
3875
  }
3910
3876
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: HTTPManagerService, deps: [{ token: CONFIG_SETTINGS_TOKEN, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
3911
3877
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: HTTPManagerService, providedIn: 'root' }); }
@@ -4442,22 +4408,22 @@ class HTTPManagerSignalsService extends RequestSignalsService {
4442
4408
  * Execute requests sequentially (one at a time, in order)
4443
4409
  * @param requests Array of ApiRequest configurations
4444
4410
  * @param options Optional batch configuration
4445
- * @returns Observable emitting array of data when all requests complete
4411
+ * @returns Observable emitting array of wrapped results when all requests complete
4446
4412
  */
4447
4413
  getSequentialRequest(requests, options) {
4448
4414
  const batchOptions = BatchOptions.adapt(options);
4449
4415
  const results = new Array(requests.length);
4450
4416
  return from(requests).pipe(concatMap((req, index) => {
4451
4417
  return this.getRequest(req).pipe(tap(data => {
4452
- results[index] = data;
4453
- }), catchError(error => this.handleSequentialError(req, error, index, batchOptions)));
4418
+ results[index] = new BatchRequestResultModel(req, data, index, 'success');
4419
+ }), catchError(error => this.handleSequentialError(req, error, index, batchOptions).pipe(tap(result => { results[index] = result; }))));
4454
4420
  }), toArray(), map(() => results));
4455
4421
  }
4456
4422
  /**
4457
4423
  * Execute requests in parallel with concurrency control
4458
4424
  * @param requests Array of ApiRequest configurations
4459
4425
  * @param options Optional batch configuration with concurrency limit
4460
- * @returns Observable emitting array of data when all requests complete
4426
+ * @returns Observable emitting array of wrapped results when all requests complete
4461
4427
  */
4462
4428
  getParallelRequest(requests, options) {
4463
4429
  const batchOptions = BatchOptions.adapt(options);
@@ -4465,8 +4431,8 @@ class HTTPManagerSignalsService extends RequestSignalsService {
4465
4431
  const results = new Array(requests.length);
4466
4432
  return from(requests).pipe(mergeMap((req, index) => {
4467
4433
  return this.getRequest(req).pipe(tap(data => {
4468
- results[index] = data;
4469
- }), catchError(error => this.handleParallelError(req, error, index, batchOptions)));
4434
+ results[index] = new BatchRequestResultModel(req, data, index, 'success');
4435
+ }), catchError(error => this.handleParallelError(req, error, index, batchOptions).pipe(tap(result => { results[index] = result; }))));
4470
4436
  }, concurrency), toArray(), map(() => results));
4471
4437
  }
4472
4438
  /**
@@ -4541,19 +4507,13 @@ class HTTPManagerSignalsService extends RequestSignalsService {
4541
4507
  if (options.stopOnError) {
4542
4508
  return throwError(() => error);
4543
4509
  }
4544
- if (options.ignoreErrors) {
4545
- return of(undefined);
4546
- }
4547
- return throwError(() => error);
4510
+ return of(new BatchRequestResultModel(request, null, index, 'error', error));
4548
4511
  }
4549
4512
  handleParallelError(request, error, index, options) {
4550
4513
  if (options.logErrors !== false) {
4551
4514
  console.error(`Batch request ${index} failed:`, error);
4552
4515
  }
4553
- if (options.ignoreErrors) {
4554
- return of(undefined);
4555
- }
4556
- return of(undefined);
4516
+ return of(new BatchRequestResultModel(request, null, index, 'error', error));
4557
4517
  }
4558
4518
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: HTTPManagerSignalsService, deps: [{ token: CONFIG_SETTINGS_TOKEN, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
4559
4519
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: HTTPManagerSignalsService, providedIn: 'root' }); }
@@ -7277,227 +7237,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
7277
7237
  args: [PROXY_CONFIG]
7278
7238
  }] }] });
7279
7239
 
7280
- class DownloadFileComponent {
7281
- constructor() {
7282
- this.subscriptions = new Subscription();
7283
- this.displayError = 3; // seconds
7284
- this.diameter = 32;
7285
- this.mode = 'determinate';
7286
- this.isPending = false;
7287
- this.active = false;
7288
- this.disabled = false;
7289
- this.error = new EventEmitter();
7290
- this._progress = 0;
7291
- this._hasError = false;
7292
- this.errorTimerActive = false;
7293
- }
7294
- set progress(value) {
7295
- this._progress = value ?? 0;
7296
- }
7297
- get progress() {
7298
- return this._progress;
7299
- }
7300
- set hasError(value) {
7301
- this._hasError = !!value;
7302
- if (this._hasError && !this.errorTimerActive) {
7303
- this.errorTimerActive = true;
7304
- this.active = false;
7305
- this.error.emit();
7306
- this.subscriptions.add(timer(this.displayError * 1000)
7307
- .subscribe((err) => {
7308
- this._hasError = false;
7309
- this.errorTimerActive = false;
7310
- }));
7311
- }
7312
- }
7313
- get hasError() {
7314
- return this._hasError;
7315
- }
7316
- ngOnInit() { }
7317
- onAction() {
7318
- this.isPending = false;
7319
- if (this.event)
7320
- this.event();
7321
- }
7322
- ngOnDestroy() {
7323
- this.subscriptions.unsubscribe();
7324
- }
7325
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DownloadFileComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7326
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: DownloadFileComponent, selector: "app-download-file", inputs: { event: "event", displayError: "displayError", diameter: "diameter", mode: "mode", isPending: "isPending", active: "active", disabled: "disabled", progress: "progress", hasError: "hasError" }, outputs: { error: "error" }, ngImport: i0, template: "@if (!isPending) {\n @if (hasError) {\n <div class=\"width center-txt\" style=\"margin-bottom: 4px;\">\n <mat-icon color=\"warn\" class=\"warn-icon\">warning</mat-icon>\n </div>\n } @else {\n @if (active) {\n <div class=\"container-obj\">\n <div class=\"centered-obj-div\">\n <mat-progress-spinner\n color=\"primary\"\n mode=\"indeterminate\"\n [diameter]=\"44\"\n ></mat-progress-spinner>\n </div>\n </div>\n } @else {\n <button data-tracking=\"export-btn\" mat-icon-button (click)=\"onAction()\" class=\"icon-button\" [disabled]=\"disabled\">\n <mat-icon class=\"custom-icon\">file_download</mat-icon>\n </button>\n }\n }\n} @else {\n @if ((progress > 0 && progress < 100)) {\n <div\n class=\"spinner-container\"\n >\n <div class=\"spinner-background\">\n {{progress}}%\n </div>\n <mat-progress-spinner\n color=\"primary\"\n [mode]=\"mode\"\n [value]=\"progress\"\n [diameter]=\"44\"\n ></mat-progress-spinner>\n </div>\n } @else {\n <div class=\"container-obj\">\n <div class=\"centered-obj-div\">\n <mat-progress-spinner\n color=\"primary\"\n mode=\"indeterminate\"\n [diameter]=\"44\"\n ></mat-progress-spinner>\n </div>\n </div>\n }\n}\n\n\n\n\n", styles: [":not(spinner-container).spinner-container{position:relative}:not(spinner-container).spinner-container .spinner-background{position:absolute;width:44px;height:44px;font-size:12px;line-height:32px;text-align:center;overflow:hidden;border-radius:50%;border:solid 5px whitesmoke}.center-txt{align-content:center;text-align:-webkit-center}.width{width:48px;height:48px}.icon-button{display:flex;align-items:center;justify-content:center;width:48px;height:48px;padding:0}.container-obj{display:flex;justify-content:center;align-items:center;width:48px;height:48px}.centered-obj-div{text-align:center}\n"], dependencies: [{ kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i2.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }, { kind: "component", type: i3.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }], encapsulation: i0.ViewEncapsulation.None }); }
7327
- }
7328
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DownloadFileComponent, decorators: [{
7329
- type: Component,
7330
- args: [{ selector: 'app-download-file', encapsulation: ViewEncapsulation.None, standalone: false, template: "@if (!isPending) {\n @if (hasError) {\n <div class=\"width center-txt\" style=\"margin-bottom: 4px;\">\n <mat-icon color=\"warn\" class=\"warn-icon\">warning</mat-icon>\n </div>\n } @else {\n @if (active) {\n <div class=\"container-obj\">\n <div class=\"centered-obj-div\">\n <mat-progress-spinner\n color=\"primary\"\n mode=\"indeterminate\"\n [diameter]=\"44\"\n ></mat-progress-spinner>\n </div>\n </div>\n } @else {\n <button data-tracking=\"export-btn\" mat-icon-button (click)=\"onAction()\" class=\"icon-button\" [disabled]=\"disabled\">\n <mat-icon class=\"custom-icon\">file_download</mat-icon>\n </button>\n }\n }\n} @else {\n @if ((progress > 0 && progress < 100)) {\n <div\n class=\"spinner-container\"\n >\n <div class=\"spinner-background\">\n {{progress}}%\n </div>\n <mat-progress-spinner\n color=\"primary\"\n [mode]=\"mode\"\n [value]=\"progress\"\n [diameter]=\"44\"\n ></mat-progress-spinner>\n </div>\n } @else {\n <div class=\"container-obj\">\n <div class=\"centered-obj-div\">\n <mat-progress-spinner\n color=\"primary\"\n mode=\"indeterminate\"\n [diameter]=\"44\"\n ></mat-progress-spinner>\n </div>\n </div>\n }\n}\n\n\n\n\n", styles: [":not(spinner-container).spinner-container{position:relative}:not(spinner-container).spinner-container .spinner-background{position:absolute;width:44px;height:44px;font-size:12px;line-height:32px;text-align:center;overflow:hidden;border-radius:50%;border:solid 5px whitesmoke}.center-txt{align-content:center;text-align:-webkit-center}.width{width:48px;height:48px}.icon-button{display:flex;align-items:center;justify-content:center;width:48px;height:48px;padding:0}.container-obj{display:flex;justify-content:center;align-items:center;width:48px;height:48px}.centered-obj-div{text-align:center}\n"] }]
7331
- }], propDecorators: { event: [{
7332
- type: Input
7333
- }], displayError: [{
7334
- type: Input
7335
- }], diameter: [{
7336
- type: Input
7337
- }], mode: [{
7338
- type: Input
7339
- }], isPending: [{
7340
- type: Input
7341
- }], active: [{
7342
- type: Input
7343
- }], disabled: [{
7344
- type: Input
7345
- }], error: [{
7346
- type: Output
7347
- }], progress: [{
7348
- type: Input
7349
- }], hasError: [{
7350
- type: Input
7351
- }] } });
7352
-
7353
- class DownloadLabels {
7354
- constructor(error = '', action = '', icon = 'error') {
7355
- this.error = error;
7356
- this.action = action;
7357
- this.icon = icon;
7358
- }
7359
- static adapt(item) {
7360
- return new DownloadLabels(item?.error, item?.action, item?.icon);
7361
- }
7362
- }
7363
-
7364
- class FileDownloaderComponent extends HTTPManagerService {
7365
- set labels(value) {
7366
- this._labels = (value) ? DownloadLabels.adapt(value) : DownloadLabels.adapt();
7367
- }
7368
- get labels() {
7369
- return this._labels;
7370
- }
7371
- constructor() {
7372
- super();
7373
- this.delayError = 3;
7374
- this.apiRequest = ApiRequest.adapt();
7375
- this.displayErrorMessage = false;
7376
- this._labels = DownloadLabels.adapt();
7377
- this.active = false;
7378
- this.subscription = new Subscription();
7379
- this.completed = new EventEmitter();
7380
- this.failed = new EventEmitter();
7381
- this.disabled = false;
7382
- }
7383
- ngOnInit() {
7384
- }
7385
- onDownloadStreaming() {
7386
- if (this.active)
7387
- return;
7388
- this.active = true;
7389
- return this.downloadRequest(this.apiRequest, [])
7390
- .pipe(distinctUntilChanged(), catchError((err) => {
7391
- this.onError(err.message);
7392
- this.active = false;
7393
- this.failed.emit(err);
7394
- return throwError(() => err);
7395
- }), finalize(() => {
7396
- if (!this.active)
7397
- return;
7398
- this.active = false;
7399
- this.completed.emit();
7400
- }))
7401
- .subscribe();
7402
- }
7403
- onError(message) {
7404
- if (!message || !this.displayErrorMessage)
7405
- return;
7406
- const display = ToastDisplay.adapt({
7407
- message,
7408
- action: 'Ok',
7409
- color: ToastColors.ERROR,
7410
- icon: 'error',
7411
- });
7412
- this.active = false;
7413
- this.toastMessage.toastMessage(display);
7414
- }
7415
- OnDestroy() {
7416
- this.subscription.unsubscribe();
7417
- }
7418
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FileDownloaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7419
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: FileDownloaderComponent, selector: "app-file-downloader", inputs: { delayError: "delayError", apiRequest: "apiRequest", displayErrorMessage: "displayErrorMessage", saveFileAs: "saveFileAs", labels: "labels", disabled: "disabled" }, outputs: { completed: "completed", failed: "failed" }, usesInheritance: true, ngImport: i0, template: "<app-download-file\n [disabled]=\"disabled\"\n [displayError]=\"3\"\n [event]=\"onDownloadStreaming.bind(this)\"\n [isPending]=\"(isPending$ | async) || false\"\n [progress]=\"(progress$ | async)\"\n [hasError]=\"(error$ | async)\"\n (error)=\"onError(labels.error)\"\n [active]=\"active\"\n></app-download-file>\n", styles: [".snackBarInfo{background-color:#f44336;color:#fff}.mat-simple-snackbar>span{font-weight:700}.mat-simple-snackbar-action .mat-button .mat-button-wrapper{color:#fff}.cdk-overlay-pane>.mat-snack-bar-container{width:100%}.mat-snack-bar-container{max-width:100%!important;width:100%}\n"], dependencies: [{ kind: "component", type: DownloadFileComponent, selector: "app-download-file", inputs: ["event", "displayError", "diameter", "mode", "isPending", "active", "disabled", "progress", "hasError"], outputs: ["error"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }], encapsulation: i0.ViewEncapsulation.None }); }
7420
- }
7421
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FileDownloaderComponent, decorators: [{
7422
- type: Component,
7423
- args: [{ selector: 'app-file-downloader', encapsulation: ViewEncapsulation.None, standalone: false, template: "<app-download-file\n [disabled]=\"disabled\"\n [displayError]=\"3\"\n [event]=\"onDownloadStreaming.bind(this)\"\n [isPending]=\"(isPending$ | async) || false\"\n [progress]=\"(progress$ | async)\"\n [hasError]=\"(error$ | async)\"\n (error)=\"onError(labels.error)\"\n [active]=\"active\"\n></app-download-file>\n", styles: [".snackBarInfo{background-color:#f44336;color:#fff}.mat-simple-snackbar>span{font-weight:700}.mat-simple-snackbar-action .mat-button .mat-button-wrapper{color:#fff}.cdk-overlay-pane>.mat-snack-bar-container{width:100%}.mat-snack-bar-container{max-width:100%!important;width:100%}\n"] }]
7424
- }], ctorParameters: () => [], propDecorators: { delayError: [{
7425
- type: Input
7426
- }], apiRequest: [{
7427
- type: Input
7428
- }], displayErrorMessage: [{
7429
- type: Input
7430
- }], saveFileAs: [{
7431
- type: Input
7432
- }], labels: [{
7433
- type: Input
7434
- }], completed: [{
7435
- type: Output
7436
- }], failed: [{
7437
- type: Output
7438
- }], disabled: [{
7439
- type: Input
7440
- }] } });
7441
-
7442
- class SpinnerComponent {
7443
- constructor() {
7444
- this.value = 0;
7445
- }
7446
- ngOnInit() {
7447
- }
7448
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SpinnerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7449
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: SpinnerComponent, selector: "app-spinner", inputs: { color: "color", diameter: "diameter", display: "display", mode: "mode", strokeWidth: "strokeWidth", value: "value" }, ngImport: i0, template: "<div class=\"spinner-background\">{{display}}</div>\n<mat-progress-spinner\n [color]=\"color\"\n [diameter]=\"diameter\"\n [mode]=\"mode || 'indeterminate'\"\n [strokeWidth]=\"strokeWidth\"\n [value]=\"value\">\n</mat-progress-spinner>\n", styles: [".example-h2{margin:24px 0}:not(spinner-container).spinner-container{position:relative}:not(spinner-container).spinner-container .spinner-background{position:absolute;width:80px;height:80px;line-height:80px;text-align:center;overflow:hidden;border-color:#673ab71f;border-radius:50%;border-style:solid;border-width:10px}\n"], dependencies: [{ kind: "component", type: i2.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }] }); }
7450
- }
7451
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SpinnerComponent, decorators: [{
7452
- type: Component,
7453
- args: [{ selector: 'app-spinner', standalone: false, template: "<div class=\"spinner-background\">{{display}}</div>\n<mat-progress-spinner\n [color]=\"color\"\n [diameter]=\"diameter\"\n [mode]=\"mode || 'indeterminate'\"\n [strokeWidth]=\"strokeWidth\"\n [value]=\"value\">\n</mat-progress-spinner>\n", styles: [".example-h2{margin:24px 0}:not(spinner-container).spinner-container{position:relative}:not(spinner-container).spinner-container .spinner-background{position:absolute;width:80px;height:80px;line-height:80px;text-align:center;overflow:hidden;border-color:#673ab71f;border-radius:50%;border-style:solid;border-width:10px}\n"] }]
7454
- }], ctorParameters: () => [], propDecorators: { color: [{
7455
- type: Input
7456
- }], diameter: [{
7457
- type: Input
7458
- }], display: [{
7459
- type: Input
7460
- }], mode: [{
7461
- type: Input
7462
- }], strokeWidth: [{
7463
- type: Input
7464
- }], value: [{
7465
- type: Input
7466
- }] } });
7467
-
7468
- class FileDownloaderModule {
7469
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FileDownloaderModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
7470
- static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.14", ngImport: i0, type: FileDownloaderModule, declarations: [SpinnerComponent,
7471
- DownloadFileComponent,
7472
- FileDownloaderComponent], imports: [CommonModule,
7473
- MatIconModule,
7474
- MatProgressSpinnerModule,
7475
- MatButtonModule], exports: [FileDownloaderComponent] }); }
7476
- static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FileDownloaderModule, imports: [CommonModule,
7477
- MatIconModule,
7478
- MatProgressSpinnerModule,
7479
- MatButtonModule] }); }
7480
- }
7481
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FileDownloaderModule, decorators: [{
7482
- type: NgModule,
7483
- args: [{
7484
- imports: [
7485
- CommonModule,
7486
- MatIconModule,
7487
- MatProgressSpinnerModule,
7488
- MatButtonModule,
7489
- ],
7490
- declarations: [
7491
- SpinnerComponent,
7492
- DownloadFileComponent,
7493
- FileDownloaderComponent,
7494
- ],
7495
- exports: [
7496
- FileDownloaderComponent
7497
- ]
7498
- }]
7499
- }] });
7500
-
7501
7240
  class DisplayConfig {
7502
7241
  constructor(type = 'snackbar', supportsMarkdown, stackable, queueBehavior, autoDismiss, width, height) {
7503
7242
  this.type = type;
@@ -7704,3284 +7443,37 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
7704
7443
  args: [{ providedIn: 'root' }]
7705
7444
  }] });
7706
7445
 
7707
- let ClientInfo$2 = class ClientInfo {
7708
- constructor(domain = '', service = '', id = 0, name = '') {
7709
- this.domain = domain;
7710
- this.service = service;
7711
- this.id = id;
7712
- this.name = name;
7713
- }
7714
- static adapt(item) {
7715
- return new ClientInfo(item?.domain, item?.service, item?.id, (item?.first_name || item?.last_name) ? `${item?.first_name} ${item?.last_name}` : '');
7716
- }
7717
- };
7718
-
7719
- let ClientInfoMapper$2 = class ClientInfoMapper {
7720
- constructor(id = 0, first_name = '', last_name = '', email = '') {
7721
- this.id = id;
7722
- this.first_name = first_name;
7723
- this.last_name = last_name;
7724
- this.email = email;
7725
- }
7726
- static adapt(item) {
7727
- const first_name = (item?.name) ? item.name.split(' ')[0] : '';
7728
- const last_name = (item?.name) ? item.name.split(' ')[1] : '';
7729
- return new ClientInfoMapper(item?.id, first_name, last_name, item?.email);
7730
- }
7731
- };
7732
-
7733
- let AIPrompt$2 = class AIPrompt {
7734
- constructor(response = '') {
7735
- this.response = response;
7736
- }
7737
- static adapt(item) {
7738
- return new AIPrompt(item?.response);
7739
- }
7740
- };
7741
-
7742
- class RequestManagerBasicDemoComponent {
7743
- // Dynamic columns based on data structure
7744
- getColumnsFromData(data) {
7745
- if (!data || data.length === 0) {
7746
- return [];
7747
- }
7748
- const firstRecord = data[0];
7749
- if (!firstRecord || typeof firstRecord !== 'object') {
7750
- return [];
7751
- }
7752
- // Extract all keys from the first record, excluding null/undefined
7753
- return Object.keys(firstRecord).filter(key => firstRecord[key] !== null && firstRecord[key] !== undefined);
7754
- }
7755
- // Update displayed columns when data changes
7756
- updateDisplayedColumns(data) {
7757
- this.displayedColumns = this.getColumnsFromData(data);
7758
- console.log('[DEMO] Updated columns:', this.displayedColumns);
7759
- }
7760
- // Helper to check if value is an object
7761
- isObject(value) {
7762
- return value !== null && typeof value === 'object' && !Array.isArray(value);
7763
- }
7764
- get retry() {
7765
- return this.requestForm.get('retry')?.value;
7766
- }
7767
- get headers() {
7768
- return this.requestForm.get('headers');
7769
- }
7770
- get isValid() {
7771
- this.requestForm.markAllAsTouched();
7772
- return this.requestForm.valid;
7773
- }
7774
- constructor() {
7775
- this.server = 'http://localhost:8080';
7776
- this.displayedColumns = [];
7777
- this.fb = inject(FormBuilder);
7778
- this.toastMessage = inject(ToastMessageDisplayService);
7779
- this.questionControl = this.fb.control("", [Validators.required]);
7780
- this.httpManagerService = inject(HTTPManagerService);
7781
- this.isPending$ = this.httpManagerService.isPending$;
7782
- this.countdown$ = this.httpManagerService.countdown$;
7783
- this.GET_error$ = new BehaviorSubject('');
7784
- this.POST_error$ = new BehaviorSubject('');
7785
- this.PUT_error$ = new BehaviorSubject('');
7786
- this.DELETE_error$ = new BehaviorSubject('');
7787
- this.STREAM_error$ = new BehaviorSubject('');
7788
- this.STREAM_AI_error$ = new BehaviorSubject('');
7789
- this.requestParams = {
7790
- GET: ApiRequest.adapt(),
7791
- POST: ApiRequest.adapt(),
7792
- PUT: ApiRequest.adapt(),
7793
- DELETE: ApiRequest.adapt(),
7794
- STREAM: ApiRequest.adapt(),
7795
- };
7796
- this.streamTypes = [
7797
- { id: 'JSON', value: 'json' },
7798
- { id: 'NDJSON', value: 'ndjson' },
7799
- { id: 'AI Streaming', value: 'ai_streaming' },
7800
- { id: 'Event Stream', value: 'event_stream' },
7801
- { id: 'Auto', value: 'auto' },
7802
- ];
7803
- this.streamType = 'Auto';
7804
- this.downloadRequest = ApiRequest.adapt({
7805
- server: 'assets/images',
7806
- path: ['lego.png'],
7807
- saveAs: 'john.jpg', // Optional
7808
- headers: { 'Authorization': 'Bearer Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6InJ0c0ZULWItN0x1WTdEVlllU05LY0lKN1ZuYyJ9.eyJhdWQiOiI4ODhhMzFjZS01OWQzLTRhMTItYTU5Ni04ZDYyZjY0MWI1MDUiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vNmY0MTE5ODItY2YyMy00ZTQ1LTk0NDktNGI2MDdiN2E4OGVjL3YyLjAiLCJpYXQiOjE3NjU4MzQxNDksIm5iZiI6MTc2NTgzNDE0OSwiZXhwIjoxNzY1ODM5NzQ2LCJhY2N0IjowLCJhaW8iOiJBV1FBbS84YUFBQUFVUEZOYUhSZkZ4ZVkwRWthcFBzdWZaNG1QYXV6YVRoODRRZUtQRzkrbEFIVE5XanVwQy9ZSjEwK2IrMktWVlJSSXZRNFpwS2xyWTFGd0xRZmtXOTNLbkRNckxSeEMzWTVOOGlQREZ4b1liZExucW1QL1N1ZE1pRW1Va05tTklSWSIsImF6cCI6Ijg4OGEzMWNlLTU5ZDMtNGExMi1hNTk2LThkNjJmNjQxYjUwNSIsImF6cGFjciI6IjAiLCJncm91cHMiOlsiZWE2ZDk5YjAtNDgyNC00MDU0LTk0MTQtZDNhOTZkZDA3MjRiIl0sIm5hbWUiOiJHVExDIEFwcCBTdXBlciBBZG1pbiIsIm9pZCI6IjI2NzUxY2I2LWNlMDEtNDMzMC05OTc0LWZjMzgxMjQ3YTEzYyIsInByZWZlcnJlZF91c2VybmFtZSI6Imd0bGMwMDNAZ3RsYy5jYSIsInJoIjoiMS5BUzRBZ2hsQmJ5UFBSVTZVU1V0Z2UzcUk3TTR4aW9qVFdSSktwWmFOWXZaQnRRVm1BYTR1QUEuIiwicm9sZXMiOlsiZ3RsYy5hY2wuc3VwZXJhZG1pbiJdLCJzY3AiOiJHVExDX0FwcGxpY2F0aW9uIiwic2lkIjoiMDAzZjVhZDktZmJmNi1jZTkyLTg3MjItMWEzNDExYjMzMDJiIiwic3ViIjoiS0RYQ3drVlhUbTRUUmcwaTkxbXZDZzgtQ29uLXpWbk5FQ2Y5LVg0dkpzUSIsInRpZCI6IjZmNDExOTgyLWNmMjMtNGU0NS05NDQ5LTRiNjA3YjdhODhlYyIsInVwbiI6Imd0bGMwMDNAZ3RsYy5jYSIsInV0aSI6InpBaTJoMmpVUUVHbVY0RTJXMWxqQUEiLCJ2ZXIiOiIyLjAiLCJ3aWRzIjpbImI3OWZiZjRkLTNlZjktNDY4OS04MTQzLTc2YjE5NGU4NTUwOSJdLCJ4bXNfZnRkIjoiUDJraW1ZM0tqc1BTRndxNHlZdmJ0bGhrSUk3d2tXNmFpcW1FQ1BaNEdja0JkWE5sWVhOMExXUnpiWE0ifQ.LzppoggMm27smSAy9SamtPN95vCzdELCAfhtOj5n_T_H6g9xCmNRLS9FaUFQMau6Qvl0lROKl7WDklTswLFkfxbIxCBWtXdL-LTqT5cDURSJAll8vC3zlN3Hg9pAFBUVZFRolt6Z7LvPdI3pvUOQs0yFwVzp9k6cLF8aemKdwKQrMX3XXua1MfBWZcqQ4WiBVNmKh8w6yQB35I4u5WqdFnu33nUGb-kvc18SOpoUfiJnlV-PudaEzFXdU3CjAaMEcuPFv5xLwWJKuhU73dNH4EyQDFMVGtcIHNnieOfiY_nK2_0-5DM6aI40UIRK6Bt-HmMQpnbhLps5y3ep6Z7RNw' } // Optional
7809
- });
7810
- // downloadRequest = ApiRequest.adapt({
7811
- // server: 'oidc/ai/file'
7812
- // })
7813
- this.sampleClientData = {
7814
- id: 0,
7815
- name: "Old School Dates",
7816
- domain: "osd.com",
7817
- service: "osd",
7818
- spiffe: "osd.com/osd",
7819
- secret: "SMOPECXP-OS4P-USOG-X2II-3XMD1FQDR3IJX",
7820
- created: 1693003138,
7821
- modified: 1693003138,
7822
- icon: "",
7823
- imageFile: "",
7824
- email: "wavecoders@gmail.com"
7825
- };
7826
- this.requestForm = this.fb.group({
7827
- path: this.fb.control("ai/"),
7828
- headers: this.fb.array([]),
7829
- adapter: [null],
7830
- mapper: [null],
7831
- retry: this.fb.group({
7832
- times: [3],
7833
- delay: [3],
7834
- }),
7835
- polling: [3],
7836
- });
7837
- this.AIType = 0;
7838
- this.sampleAdaptors = [
7839
- { label: "ClientInfo Basic", value: ClientInfo$2.adapt },
7840
- { label: "AI Prompt", value: AIPrompt$2.adapt },
7841
- ];
7842
- this.sampleMappers = [
7843
- { label: "Mapper Basic", value: ClientInfoMapper$2.adapt },
7844
- { label: "AI Prompt", value: AIPrompt$2.adapt },
7845
- ];
7846
- this.hasId = (arr) => {
7847
- if (arr.length === 0)
7848
- return false;
7849
- return !isNaN(arr[arr.length - 1]);
7850
- };
7851
- this.props = (adapter) => {
7852
- return (adapter) ? adapter() : null;
7853
- };
7854
- // server = `http://sample-endpoint/as/authorization.oauth2`
7855
- this.arrayObjectsToObjects = (arr) => {
7856
- return Array.isArray(arr) ? arr.reduce((obj, item) => Object.assign(obj, { [item.key]: item.value }), {}) : {};
7857
- };
7858
- }
7859
- ngOnInit() {
7860
- // const reqGet2 = ApiRequest.adapt({
7861
- // server,
7862
- // path: ['clients'],
7863
- // headers: { authentication: "Bearer <KEY>" },
7864
- // adapter: ClientInfo,
7865
- // dataType: DataType.OBJECT,
7866
- // // concurrent: false,
7867
- // // polling: 3, //seconds
7868
- // })
7869
- // const req2 = [1024,1025,1026].map(item => {
7870
- // return this.httpManagerService.getRequest<ClientInfo[]>(reqGet2, [item])
7871
- // .pipe(
7872
- // catchError(error => {
7873
- // return throwError(() => new Error(error.error.message))
7874
- // })
7875
- // )
7876
- // })
7877
- // forkJoin(req2)
7878
- // .subscribe(res => console.log(res))
7879
- }
7880
- onStreamType(type) {
7881
- this.streamType = type;
7882
- }
7883
- addHeader() {
7884
- const header = this.fb.group({
7885
- key: ['', Validators.required],
7886
- value: ['']
7887
- });
7888
- this.headers.push(header);
7889
- }
7890
- removeHeader(index) {
7891
- this.headers.removeAt(index);
7892
- }
7893
- compileRequest() {
7894
- const requestParams = this.requestForm.value;
7895
- requestParams.headers = this.arrayObjectsToObjects(requestParams.headers || []);
7896
- const pathReq = (requestParams.path === "") ? [] : (requestParams.path || "").split("/");
7897
- if (!this.pollingState.checked)
7898
- requestParams.polling = 0;
7899
- if (!this.failedState.checked) {
7900
- requestParams.retry = { times: 0, delay: 0 };
7901
- }
7902
- const apiOptions = ApiRequest.adapt(requestParams);
7903
- apiOptions.path = [];
7904
- apiOptions.server = this.server;
7905
- apiOptions.adapter = this.adapter;
7906
- apiOptions.mapper = this.mapper;
7907
- return { apiOptions: apiOptions, path: pathReq };
7908
- }
7909
- onGetRequest() {
7910
- if (!this.isValid)
7911
- return;
7912
- const reqParams = this.compileRequest();
7913
- this.requestParams.GET = reqParams.apiOptions;
7914
- this.GET$ = EMPTY; //Cancels Previous
7915
- this.GET_error$.next('');
7916
- this.GET$ = this.httpManagerService.getRequest({ ...reqParams.apiOptions }, reqParams.path)
7917
- .pipe(
7918
- // tap((data) => console.log("API GET response", data)),
7919
- catchError(error => {
7920
- return throwError(() => this.errorHandling(error, 'GET'));
7921
- }));
7922
- }
7923
- onCreateRequest() {
7924
- if (!this.isValid)
7925
- return;
7926
- const reqParams = this.compileRequest();
7927
- this.requestParams.POST = reqParams.apiOptions;
7928
- this.POST$ = EMPTY; //Cancels Previous
7929
- this.POST_error$.next('');
7930
- console.log("POST", this.sampleClientData);
7931
- console.log("POST", reqParams.apiOptions);
7932
- console.log("POST", reqParams.path);
7933
- this.POST$ = this.httpManagerService.postRequest(this.sampleClientData, reqParams.apiOptions, reqParams.path)
7934
- .pipe(
7935
- // tap((data) => console.log("API POST response", data)),
7936
- catchError(error => {
7937
- return throwError(() => this.errorHandling(error, 'POST'));
7938
- }));
7939
- }
7940
- onUpdateRequest() {
7941
- if (!this.isValid)
7942
- return;
7943
- const reqParams = this.compileRequest();
7944
- if (!this.hasId(reqParams.path)) {
7945
- console.log("Missing ID");
7946
- return;
7947
- }
7948
- this.sampleClientData.id = parseInt(reqParams.path[reqParams.path.length - 1]);
7949
- this.requestParams.PUT = reqParams.apiOptions;
7950
- this.PUT$ = EMPTY; //Cancels Previous
7951
- this.PUT_error$.next('');
7952
- this.PUT$ = this.httpManagerService.putRequest(this.sampleClientData, reqParams.apiOptions, reqParams.path)
7953
- .pipe(
7954
- // tap((data) => console.log("API PUT response", data)),
7955
- catchError(error => {
7956
- return throwError(() => this.errorHandling(error, 'PUT'));
7957
- }));
7958
- }
7959
- onDeleteRequest() {
7960
- if (!this.isValid)
7961
- return;
7962
- const reqParams = this.compileRequest();
7963
- this.requestParams.DELETE = reqParams.apiOptions;
7964
- if (!this.hasId(reqParams.path)) {
7965
- console.log("Missing ID");
7966
- return;
7967
- }
7968
- this.sampleClientData.id = parseInt(reqParams.path[reqParams.path.length - 1]);
7969
- this.requestParams.DELETE = reqParams.apiOptions;
7970
- this.DELETE$ = EMPTY; //Cancels Previous
7971
- this.DELETE_error$.next('');
7972
- this.DELETE$ = this.httpManagerService.deleteRequest(reqParams.apiOptions, reqParams.path)
7973
- .pipe(
7974
- // tap((data) => console.log("API DELETE response", data)),
7975
- catchError(error => {
7976
- return throwError(() => this.errorHandling(error, 'DELETE'));
7977
- }));
7978
- }
7979
- onStreamPostRequest() {
7980
- if (!this.isValid)
7981
- return;
7982
- const reqParams = this.compileRequest();
7983
- let payload = {};
7984
- let apiPath = reqParams.path;
7985
- let apiOptions = reqParams.apiOptions;
7986
- let responseMapper = (items) => items.response;
7987
- if (this.AIType === 0) {
7988
- // API request
7989
- payload = { prompt: this.questionControl.value };
7990
- }
7991
- else {
7992
- // Local Ollama request
7993
- apiOptions.server = "api";
7994
- apiPath = ["generate"];
7995
- apiOptions.stream = true;
7996
- apiOptions.streamType = this.streamType;
7997
- payload = {
7998
- model: "phi3:latest",
7999
- prompt: this.questionControl.value,
8000
- stream: true,
8001
- };
8002
- responseMapper = (items) => items.map((word) => word.response).flat().join('');
8003
- }
8004
- this.requestParams.STREAM = apiOptions;
8005
- this.STREAM_AI$ = EMPTY;
8006
- this.STREAM_AI_error$.next('');
8007
- this.STREAM_AI$ = this.httpManagerService.postRequest(payload, apiOptions, apiPath).pipe(map(responseMapper), tap(() => this.questionControl.reset()), catchError(error => throwError(() => this.errorHandling(error, 'STREAM'))));
8008
- }
8009
- onStreamRequest() {
8010
- if (!this.isValid)
8011
- return;
8012
- const reqParams = this.compileRequest();
8013
- reqParams.apiOptions.stream = true;
8014
- reqParams.apiOptions.streamType = StreamType.NDJSON;
8015
- this.requestParams.GET = reqParams.apiOptions;
8016
- this.STREAM$ = this.httpManagerService.getRequest(reqParams.apiOptions, reqParams.path)
8017
- .pipe(tap((data) => {
8018
- console.log("API STREAM response", data);
8019
- if (data && data.length > 0) {
8020
- this.updateDisplayedColumns(data);
8021
- }
8022
- }), catchError(error => {
8023
- return throwError(() => this.errorHandling(error, 'STREAM'));
8024
- }));
8025
- }
8026
- onDownloadCompleted() {
8027
- const message = "Download Completed";
8028
- const display = ToastDisplay.adapt({
8029
- message,
8030
- action: 'Ok',
8031
- color: ToastColors.SUCCESS,
8032
- icon: 'sentiment_satisfied_alt',
8033
- });
8034
- this.toastMessage.toastMessage(display);
8035
- }
8036
- onDownloadFailed(err) {
8037
- const message = "Download Failed";
8038
- const display = ToastDisplay.adapt({
8039
- message,
8040
- action: 'Ok',
8041
- color: ToastColors.ERROR,
8042
- icon: 'warning',
8043
- });
8044
- this.toastMessage.toastMessage(display);
8045
- }
8046
- errorHandling(err, type) {
8047
- if (type === 'GET')
8048
- this.GET_error$.next(err.message);
8049
- if (type === 'POST')
8050
- this.POST_error$.next(err.message);
8051
- if (type === 'PUT')
8052
- this.PUT_error$.next(err.message);
8053
- if (type === 'DELETE')
8054
- this.DELETE_error$.next(err.message);
8055
- if (type === 'STREAM')
8056
- this.STREAM_error$.next(err.message);
8057
- }
8058
- onSelectAIType(type) {
8059
- this.AIType = type;
8060
- }
8061
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerBasicDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8062
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: RequestManagerBasicDemoComponent, selector: "app-request-manager-basic-demo", providers: [HTTPManagerService], viewQueries: [{ propertyName: "failedState", first: true, predicate: ["failedState"], descendants: true, static: true }, { propertyName: "pollingState", first: true, predicate: ["pollingState"], descendants: true, static: true }], ngImport: i0, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n HTTP Basic Request Manager Setup\n </h2>\n\n <div style=\"margin-bottom: 1rem; margin-top: 2rem;\">\n @if ((isPending$ | async)) {\n <mat-progress-bar mode=\"indeterminate\"\n ></mat-progress-bar>\n }\n @if (pollingState.checked && !(isPending$ | async)) {\n <mat-progress-bar mode=\"determinate\"\n [value]=\"(this.countdown$ | async)\"\n ></mat-progress-bar>\n }\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">GET Request</h2>\n <div>\n <button mat-raised-button (click)=\"onGetRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((GET_error$ | async); as get_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ get_error }}</mat-error>\n </div>\n }\n\n @if ((GET$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(GET$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">POST Request</h2>\n <div>\n <button mat-raised-button (click)=\"onCreateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((POST_error$ | async); as post_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ post_error }}</mat-error>\n </div>\n }\n\n @if ((POST$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(POST$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">PUT Request</h2>\n <div>\n <button mat-raised-button (click)=\"onUpdateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((PUT_error$ | async); as put_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ put_error }}</mat-error>\n </div>\n }\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if ((PUT$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(PUT$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">DELETE Request</h2>\n <div>\n <button mat-raised-button (click)=\"onDeleteRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if ((DELETE_error$ | async); as delete_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ delete_error }}</mat-error>\n </div>\n }\n\n @if ((DELETE$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(DELETE$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">Streaming GET Request</h2>\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n {{ streamType }}\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>data_usage</mat-icon>\n </button>\n </div>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item *ngFor=\"let item of streamTypes\" (click)=\"onStreamType(item.id)\">{{ item.value }}</button>\n </mat-menu>\n <button mat-raised-button (click)=\"onStreamRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <!-- <div *ngIf=\"(STREAM_error$ | async) as stream_error\" style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div> -->\n\n <div style=\"margin-top: 1rem;\">\n @if ((STREAM$ | async); as data) {\n <div class=\"container\">\n <table mat-table [dataSource]=\"data\" class=\"mat-elevation-z8\">\n\n <!-- Dynamic columns -->\n <ng-container *ngFor=\"let column of displayedColumns\" [matColumnDef]=\"column\">\n <th mat-header-cell *matHeaderCellDef> {{ column | titlecase }} </th>\n <td mat-cell *matCellDef=\"let element\">\n @if (isObject(element[column]); as objValue) {\n <pre style=\"margin: 0; font-size: 0.8em; white-space: pre-wrap;\">{{ objValue | json }}</pre>\n } @else {\n {{ element[column] }}\n }\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n\n <!-- Debug info -->\n <div style=\"margin-top: 1rem; font-size: 0.8em; color: #666;\">\n Columns: {{ displayedColumns.join(', ') }} | Records: {{ data.length }}\n </div>\n </div>\n }\n </div>\n\n</div>\n\n<div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n</div>\n\n<div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1; padding-top: .5rem;\">AI -@if (AIType === 1) {\n <span>STREAMING</span>\n } POST Request</h2>\n <div style=\"display: flex; gap: 1rem;\">\n <button mat-raised-button [matMenuTriggerFor]=\"menu\" style=\"min-width: 120px;\">\n <mat-icon>lan</mat-icon>\n @if (AIType === 0) {\n <span>Server</span>\n } @else {\n Local\n }\n </button>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item (click)=\"onSelectAIType(0)\">Server</button>\n <button mat-menu-item (click)=\"onSelectAIType(1)\">Local</button>\n </mat-menu>\n <div>\n <button mat-raised-button (click)=\"onStreamPostRequest()\" class=\"btn\">Ask Me</button>\n </div>\n </div>\n </div>\n\n <div style=\"display: flex;\">\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Ask me a Question</mat-label>\n <textarea matInput placeholder=\"Why is the sky blue?\" [formControl]=\"questionControl\"></textarea>\n </mat-form-field>\n </div>\n\n @if ((STREAM_AI_error$ | async); as stream_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div>\n }\n\n @if (AIType === 1) {\n <div style=\"color: red;\">\n You must have Ollama active and the 'phi3:latest' model to use this feature.\n </div>\n } @else {\n <span style=\"color: gray;\">\n Define the RestPath to the API endpoint that will handle the AI request.\n Use: 'ai/chat' for server\n </span>\n }\n\n <div>\n @if ((STREAM_AI$ | async); as data) {\n <div style=\"margin-top: 1rem; font-size: 1.2rem; border-radius: 1rem; border: black 1px solid; padding: 2rem;\">\n <p style=\"margin-bottom: .5rem; white-space:pre-wrap; line-height: 1.6rem;\">{{data}}</p>\n </div>\n }\n </div>\n\n</div>\n\n<div style=\"margin-top: 1.5rem; margin-bottom: 1rem; line-height: 1.5rem;\">\n <mat-divider></mat-divider>\n</div>\n\n<div>\n <div style=\"display: flex;\">\n <h2 style=\"flex:1; margin-bottom: 0; padding-top: .5rem; display: flex;\">\n <div>\n Download File\n </div>\n <div style=\"flex:1; margin-left: 1rem;\">\n <mat-slide-toggle #disable>\n @if (disable.checked) {\n <span>\n Enable\n </span>\n } @else {\n Disable\n }\n </mat-slide-toggle>\n </div>\n </h2>\n <div>\n <app-file-downloader\n [disabled]=\"disable.checked\"\n [delayError]=\"3\"\n [apiRequest]=\"downloadRequest\"\n (completed)=\"onDownloadCompleted()\"\n (failed)=\"onDownloadFailed($event)\"\n ></app-file-downloader>\n </div>\n </div>\n</div>\n\n</div>\n", styles: [".btn{min-width:120px}.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;background-color:#f5f5f5}.container{height:400px;overflow:auto}.box{padding:10px;border:1px solid #ccc}\n"], dependencies: [{ kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2$1.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: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i3.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "component", type: i7.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: i7.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i7.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i9.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i9.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i9.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i9.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i9.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i9.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i9.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i9.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i9.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i8$1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: i11.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "component", type: i12.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i13.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: FileDownloaderComponent, selector: "app-file-downloader", inputs: ["delayError", "apiRequest", "displayErrorMessage", "saveFileAs", "labels", "disabled"], outputs: ["completed", "failed"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }, { kind: "pipe", type: i1$1.TitleCasePipe, name: "titlecase" }] }); }
8063
- }
8064
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerBasicDemoComponent, decorators: [{
8065
- type: Component,
8066
- args: [{ selector: 'app-request-manager-basic-demo', providers: [HTTPManagerService], standalone: false, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n HTTP Basic Request Manager Setup\n </h2>\n\n <div style=\"margin-bottom: 1rem; margin-top: 2rem;\">\n @if ((isPending$ | async)) {\n <mat-progress-bar mode=\"indeterminate\"\n ></mat-progress-bar>\n }\n @if (pollingState.checked && !(isPending$ | async)) {\n <mat-progress-bar mode=\"determinate\"\n [value]=\"(this.countdown$ | async)\"\n ></mat-progress-bar>\n }\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">GET Request</h2>\n <div>\n <button mat-raised-button (click)=\"onGetRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((GET_error$ | async); as get_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ get_error }}</mat-error>\n </div>\n }\n\n @if ((GET$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(GET$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">POST Request</h2>\n <div>\n <button mat-raised-button (click)=\"onCreateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((POST_error$ | async); as post_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ post_error }}</mat-error>\n </div>\n }\n\n @if ((POST$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(POST$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">PUT Request</h2>\n <div>\n <button mat-raised-button (click)=\"onUpdateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((PUT_error$ | async); as put_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ put_error }}</mat-error>\n </div>\n }\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if ((PUT$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(PUT$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">DELETE Request</h2>\n <div>\n <button mat-raised-button (click)=\"onDeleteRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if ((DELETE_error$ | async); as delete_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ delete_error }}</mat-error>\n </div>\n }\n\n @if ((DELETE$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(DELETE$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">Streaming GET Request</h2>\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n {{ streamType }}\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>data_usage</mat-icon>\n </button>\n </div>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item *ngFor=\"let item of streamTypes\" (click)=\"onStreamType(item.id)\">{{ item.value }}</button>\n </mat-menu>\n <button mat-raised-button (click)=\"onStreamRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <!-- <div *ngIf=\"(STREAM_error$ | async) as stream_error\" style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div> -->\n\n <div style=\"margin-top: 1rem;\">\n @if ((STREAM$ | async); as data) {\n <div class=\"container\">\n <table mat-table [dataSource]=\"data\" class=\"mat-elevation-z8\">\n\n <!-- Dynamic columns -->\n <ng-container *ngFor=\"let column of displayedColumns\" [matColumnDef]=\"column\">\n <th mat-header-cell *matHeaderCellDef> {{ column | titlecase }} </th>\n <td mat-cell *matCellDef=\"let element\">\n @if (isObject(element[column]); as objValue) {\n <pre style=\"margin: 0; font-size: 0.8em; white-space: pre-wrap;\">{{ objValue | json }}</pre>\n } @else {\n {{ element[column] }}\n }\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n\n <!-- Debug info -->\n <div style=\"margin-top: 1rem; font-size: 0.8em; color: #666;\">\n Columns: {{ displayedColumns.join(', ') }} | Records: {{ data.length }}\n </div>\n </div>\n }\n </div>\n\n</div>\n\n<div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n</div>\n\n<div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1; padding-top: .5rem;\">AI -@if (AIType === 1) {\n <span>STREAMING</span>\n } POST Request</h2>\n <div style=\"display: flex; gap: 1rem;\">\n <button mat-raised-button [matMenuTriggerFor]=\"menu\" style=\"min-width: 120px;\">\n <mat-icon>lan</mat-icon>\n @if (AIType === 0) {\n <span>Server</span>\n } @else {\n Local\n }\n </button>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item (click)=\"onSelectAIType(0)\">Server</button>\n <button mat-menu-item (click)=\"onSelectAIType(1)\">Local</button>\n </mat-menu>\n <div>\n <button mat-raised-button (click)=\"onStreamPostRequest()\" class=\"btn\">Ask Me</button>\n </div>\n </div>\n </div>\n\n <div style=\"display: flex;\">\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Ask me a Question</mat-label>\n <textarea matInput placeholder=\"Why is the sky blue?\" [formControl]=\"questionControl\"></textarea>\n </mat-form-field>\n </div>\n\n @if ((STREAM_AI_error$ | async); as stream_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div>\n }\n\n @if (AIType === 1) {\n <div style=\"color: red;\">\n You must have Ollama active and the 'phi3:latest' model to use this feature.\n </div>\n } @else {\n <span style=\"color: gray;\">\n Define the RestPath to the API endpoint that will handle the AI request.\n Use: 'ai/chat' for server\n </span>\n }\n\n <div>\n @if ((STREAM_AI$ | async); as data) {\n <div style=\"margin-top: 1rem; font-size: 1.2rem; border-radius: 1rem; border: black 1px solid; padding: 2rem;\">\n <p style=\"margin-bottom: .5rem; white-space:pre-wrap; line-height: 1.6rem;\">{{data}}</p>\n </div>\n }\n </div>\n\n</div>\n\n<div style=\"margin-top: 1.5rem; margin-bottom: 1rem; line-height: 1.5rem;\">\n <mat-divider></mat-divider>\n</div>\n\n<div>\n <div style=\"display: flex;\">\n <h2 style=\"flex:1; margin-bottom: 0; padding-top: .5rem; display: flex;\">\n <div>\n Download File\n </div>\n <div style=\"flex:1; margin-left: 1rem;\">\n <mat-slide-toggle #disable>\n @if (disable.checked) {\n <span>\n Enable\n </span>\n } @else {\n Disable\n }\n </mat-slide-toggle>\n </div>\n </h2>\n <div>\n <app-file-downloader\n [disabled]=\"disable.checked\"\n [delayError]=\"3\"\n [apiRequest]=\"downloadRequest\"\n (completed)=\"onDownloadCompleted()\"\n (failed)=\"onDownloadFailed($event)\"\n ></app-file-downloader>\n </div>\n </div>\n</div>\n\n</div>\n", styles: [".btn{min-width:120px}.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;background-color:#f5f5f5}.container{height:400px;overflow:auto}.box{padding:10px;border:1px solid #ccc}\n"] }]
8067
- }], ctorParameters: () => [], propDecorators: { failedState: [{
8068
- type: ViewChild,
8069
- args: ["failedState", { static: true }]
8070
- }], pollingState: [{
8071
- type: ViewChild,
8072
- args: ["pollingState", { static: true }]
8073
- }] } });
8074
-
8075
- let ClientInfo$1 = class ClientInfo {
8076
- constructor(domain = '', service = '', id = 0, name = '') {
8077
- this.domain = domain;
8078
- this.service = service;
8079
- this.id = id;
8080
- this.name = name;
8081
- }
8082
- static adapt(item) {
8083
- return new ClientInfo(item?.domain, item?.service, item?.id, (item?.first_name || item?.last_name) ? `${item?.first_name} ${item?.last_name}` : '');
8084
- }
8085
- };
8086
-
8087
- let ClientInfoMapper$1 = class ClientInfoMapper {
8088
- constructor(id = 0, first_name = '', last_name = '', email = '') {
8089
- this.id = id;
8090
- this.first_name = first_name;
8091
- this.last_name = last_name;
8092
- this.email = email;
8093
- }
8094
- static adapt(item) {
8095
- const first_name = (item?.name) ? item.name.split(' ')[0] : '';
8096
- const last_name = (item?.name) ? item.name.split(' ')[1] : '';
8097
- return new ClientInfoMapper(item?.id, first_name, last_name, item?.email);
8098
- }
8099
- };
8100
-
8101
- class StateManagerDemoService extends HTTPManagerStateService {
8102
- constructor() {
8103
- super(ApiRequest.adapt({
8104
- server: "",
8105
- path: [],
8106
- headers: {},
8107
- adapter: ClientInfo$1.adapt,
8108
- mapper: ClientInfoMapper$1.adapt,
8109
- stream: false,
8110
- }), DataType.ARRAY, DatabaseStorage.adapt());
8111
- }
8112
- setAPIOptions(apiOptions, dataType, database) {
8113
- this.setApiRequestOptions(apiOptions, dataType, database);
8114
- }
8115
- getClients() {
8116
- // const headers = {
8117
- // auth: "sample-auth-token"
8118
- // }
8119
- // const sampleOptions = RequestOptions.adapt({ path: ["id", 12], headers, sample: true })
8120
- this.fetchRecords();
8121
- }
8122
- createClient(data) {
8123
- // const headers = {
8124
- // auth: "sample-auth-token"
8125
- // }
8126
- // const sampleOptions = RequestOptions.adapt({ path: ["id", 12], headers, sample: true })
8127
- this.createRecord(data);
8128
- }
8129
- updateClient(data) {
8130
- // const headers = {
8131
- // auth: "sample-auth-token"
8132
- // }
8133
- data.id = 1031;
8134
- const sampleOptions = RequestOptions.adapt({ path: [data.id] });
8135
- this.updateRecord(data, sampleOptions);
8136
- }
8137
- deleteClient(data) {
8138
- // const headers = {
8139
- // auth: "sample-auth-token"
8140
- // }
8141
- data.id = 1031;
8142
- const sampleOptions = RequestOptions.adapt({ path: [data.id] });
8143
- this.deleteRecord(sampleOptions);
8144
- }
8145
- streamRequest() {
8146
- console.log('[DEMO SERVICE] streamRequest called');
8147
- const headers = {
8148
- auth: "sample-auth-token"
8149
- };
8150
- console.log('[DEMO SERVICE] Calling fetchStream with headers:', headers);
8151
- this.fetchStream();
8152
- }
8153
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateManagerDemoService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
8154
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateManagerDemoService }); }
8155
- }
8156
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateManagerDemoService, decorators: [{
8157
- type: Injectable
8158
- }], ctorParameters: () => [] });
8159
-
8160
- let AIPrompt$1 = class AIPrompt {
8161
- constructor(response = '') {
8162
- this.response = response;
8163
- }
8164
- static adapt(item) {
8165
- return new AIPrompt(item?.response);
8166
- }
8167
- };
8168
-
8169
- class RequestManagerStateDemoComponent {
8170
- // Dynamic columns based on data structure
8171
- getColumnsFromData(data) {
8172
- if (!data || data.length === 0) {
8173
- return [];
8174
- }
8175
- const firstRecord = data[0];
8176
- if (!firstRecord || typeof firstRecord !== 'object') {
8177
- return [];
8178
- }
8179
- // Extract all keys from the first record, excluding null/undefined
8180
- return Object.keys(firstRecord).filter(key => firstRecord[key] !== null && firstRecord[key] !== undefined);
8181
- }
8182
- // Update displayed columns when data changes
8183
- updateDisplayedColumns(data) {
8184
- this.displayedColumns = this.getColumnsFromData(data);
8185
- console.log('[STATE DEMO] Updated columns:', this.displayedColumns);
8186
- }
8187
- // Helper to check if value is an object
8188
- isObject(value) {
8189
- return value !== null && typeof value === 'object' && !Array.isArray(value);
8190
- }
8191
- // Helper to safely get data length
8192
- getDataLength(data) {
8193
- if (Array.isArray(data)) {
8194
- return data.length;
8195
- }
8196
- return 0;
8197
- }
8198
- get dataObservable$() {
8199
- switch (this.requestType) {
8200
- case 'GET':
8201
- return this.GET$;
8202
- case 'PUT':
8203
- return this.PUT$;
8204
- case 'POST':
8205
- return this.POST$;
8206
- case 'DELETE':
8207
- return this.DELETE$;
8208
- case 'STREAM':
8209
- return this.STREAM;
8210
- case 'STREAM_AI':
8211
- return this.STREAM_AI;
8212
- default:
8213
- return this.GET$;
8214
- break;
8215
- }
8216
- }
8217
- get hasChanged() {
8218
- return this.requestForm.dirty || this.requestForm.untouched;
8219
- }
8220
- get dataType() {
8221
- return this.requestForm.get("datatype")?.value;
8222
- }
8223
- get database() {
8224
- return this.requestForm.get("database")?.value;
8225
- }
8226
- get retry() {
8227
- return this.requestForm.get("retry")?.value;
8228
- }
8229
- get headers() {
8230
- return this.requestForm.get('headers');
8231
- }
8232
- get isValid() {
8233
- this.requestForm.markAllAsTouched();
8234
- return this.requestForm.valid;
8235
- }
8236
- constructor() {
8237
- this.server = 'http://localhost:8080';
8238
- this.stateManagerDemoService = inject(StateManagerDemoService);
8239
- this.displayedColumns = [];
8240
- this.fb = inject(FormBuilder);
8241
- this.streamTypes = [
8242
- { id: 'JSON', value: 'json' },
8243
- { id: 'NDJSON', value: 'ndjson' },
8244
- { id: 'AI Streaming', value: 'ai_streaming' },
8245
- { id: 'Event Stream', value: 'event_stream' },
8246
- { id: 'Auto', value: 'auto' },
8247
- ];
8248
- this.streamType = 'Auto';
8249
- this.httpManagerService = inject(HTTPManagerService);
8250
- this.isPending$ = this.stateManagerDemoService.isPending$;
8251
- this.error$ = this.stateManagerDemoService.error$;
8252
- this.countdown$ = this.httpManagerService.countdown$;
8253
- this.GET_error$ = new BehaviorSubject('');
8254
- this.POST_error$ = new BehaviorSubject('');
8255
- this.PUT_error$ = new BehaviorSubject('');
8256
- this.DELETE_error$ = new BehaviorSubject('');
8257
- this.STREAM_error$ = new BehaviorSubject('');
8258
- this.STREAM_AI_error$ = new BehaviorSubject('');
8259
- this.GET$ = new BehaviorSubject(null);
8260
- this.POST$ = new BehaviorSubject(null);
8261
- this.PUT$ = new BehaviorSubject(null);
8262
- this.DELETE$ = new BehaviorSubject(null);
8263
- this.STREAM = new BehaviorSubject(null);
8264
- this.STREAM$ = this.STREAM.asObservable();
8265
- this.STREAM_AI = new BehaviorSubject([]);
8266
- this.STREAM_AI$ = this.STREAM_AI.asObservable()
8267
- .pipe(map$1((items) => (items) ? items.map((item) => item.response) : []), map$1((items) => items.join('\n').trim()));
8268
- this.questionControl = this.fb.control("", [Validators.required]);
8269
- this.requestType = '';
8270
- this.prompts = [];
8271
- this.AIType = 0;
8272
- this.sampleClientData = {
8273
- id: 0,
8274
- name: "Old School Dates",
8275
- domain: "osd.com",
8276
- service: "osd",
8277
- spiffe: "osd.com/osd",
8278
- secret: "SMOPECXP-OS4P-USOG-X2II-3XMD1FQDR3IJX",
8279
- created: 1693003138,
8280
- modified: 1693003138,
8281
- icon: "",
8282
- imageFile: "",
8283
- };
8284
- this.selectedRecord = this.fb.control(null);
8285
- this.requestForm = this.fb.group({
8286
- datatype: this.fb.control('ARRAY'),
8287
- path: this.fb.control("ai/"),
8288
- headers: this.fb.array([]),
8289
- adapter: [null],
8290
- mapper: [null],
8291
- retry: this.fb.group({
8292
- times: [3],
8293
- delay: [3],
8294
- }),
8295
- polling: [3],
8296
- database: this.fb.group({
8297
- table: [''],
8298
- expiresIn: ['1m'],
8299
- })
8300
- });
8301
- this.sampleAdaptors = [
8302
- { label: "ClientInfo Basic", value: ClientInfo$1.adapt },
8303
- { label: "AI Prompt", value: AIPrompt$1.adapt },
8304
- ];
8305
- this.sampleMappers = [
8306
- { label: "Mapper Basic", value: ClientInfoMapper$1.adapt },
8307
- { label: "AI Prompt", value: AIPrompt$1.adapt },
8308
- ];
8309
- // server = `http://sample-endpoint/as/authorization.oauth2`
8310
- this.arrayObjectsToObjects = (arr) => {
8311
- return Array.isArray(arr) ? arr.reduce((obj, item) => Object.assign(obj, { [item.key]: item.value }), {}) : {};
8312
- };
8313
- this.props = (adapter) => {
8314
- return (adapter) ? adapter() : null;
7446
+ class HttpRequestManagerModule {
7447
+ static forRoot(config = ConfigOptions.adapt()) {
7448
+ return {
7449
+ ngModule: HttpRequestManagerModule,
7450
+ providers: [
7451
+ { provide: CONFIG_SETTINGS_TOKEN, useValue: ConfigOptions.adapt(config) },
7452
+ HTTPManagerService,
7453
+ LocalStorageManagerService, // all services that need access to config
7454
+ ],
8315
7455
  };
8316
7456
  }
8317
- ngOnInit() {
8318
- this.stateManagerDemoService.data$.pipe(tap$1((data) => console.log("API STREAM_AI response", data)));
8319
- this.error$.pipe(tap$1((data) => {
8320
- debugger;
8321
- console.log("API STREAM response", data);
8322
- }), catchError$1(error => {
8323
- return throwError(() => this.errorHandling(error, 'STREAM'));
8324
- }));
8325
- this.selectedRecord.valueChanges
8326
- .subscribe((data) => {
8327
- this.selectedRecord$ = (data) ? this.stateManagerDemoService.selectRecord$(data.id) : EMPTY;
8328
- });
8329
- this.stateManagerDemoService.data$
8330
- .pipe(tap$1((data) => {
8331
- console.log('[COMPONENT] State data received:', data);
8332
- switch (this.requestType) {
8333
- case 'GET':
8334
- console.log('[COMPONENT] Updating GET$ with data:', data);
8335
- this.GET$.next(data);
8336
- break;
8337
- case 'PUT':
8338
- console.log('[COMPONENT] Updating PUT$ with data:', data);
8339
- this.PUT$.next(data);
8340
- break;
8341
- case 'POST':
8342
- console.log('[COMPONENT] Updating POST$ with data:', data);
8343
- this.POST$.next(data);
8344
- break;
8345
- case 'DELETE':
8346
- console.log('[COMPONENT] Updating DELETE$ with data:', data);
8347
- this.DELETE$.next(data);
8348
- break;
8349
- case 'STREAM':
8350
- console.log('[COMPONENT] Updating STREAM$ with data:', data);
8351
- this.STREAM.next(data);
8352
- // Update table columns dynamically based on streaming data
8353
- if (data && Array.isArray(data) && data.length > 0) {
8354
- this.updateDisplayedColumns(data);
8355
- }
8356
- break;
8357
- case 'STREAM_AI':
8358
- console.log('[COMPONENT] Updating STREAM_AI$ with data:', data);
8359
- this.STREAM_AI.next(data);
8360
- break;
8361
- default:
8362
- console.log('[COMPONENT] No requestType set, ignoring data');
8363
- break;
8364
- }
8365
- })).subscribe();
8366
- }
8367
- onStreamType(type) {
8368
- this.streamType = type;
8369
- }
8370
- addHeader() {
8371
- const header = this.fb.group({
8372
- key: ['', Validators.required],
8373
- value: ['']
8374
- });
8375
- this.headers.push(header);
8376
- }
8377
- removeHeader(index) {
8378
- this.headers.removeAt(index);
8379
- }
8380
- compileRequest() {
8381
- const requestParams = this.requestForm.value;
8382
- requestParams.headers = this.arrayObjectsToObjects(requestParams.headers || []);
8383
- const pathReq = (requestParams.path === "") ? [] : (requestParams.path || "").split("/");
8384
- if (!this.pollingState.checked)
8385
- requestParams.polling = 0;
8386
- if (!this.failedState.checked) {
8387
- requestParams.retry = { times: 0, delay: 0 };
8388
- }
8389
- const currentOptions = ApiRequest.adapt(requestParams);
8390
- currentOptions.path = [];
8391
- currentOptions.server = this.server;
8392
- currentOptions.adapter = this.adapter;
8393
- currentOptions.mapper = this.mapper;
8394
- const apiOptions = ApiRequest.adapt({ ...currentOptions, path: pathReq });
8395
- return { apiOptions: apiOptions, path: pathReq };
8396
- }
8397
- onSetStateOptions() {
8398
- if (!this.isValid)
8399
- return;
8400
- const reqParams = this.compileRequest();
8401
- const db = DatabaseStorage.adapt(this.database);
8402
- const type = this.dataType === "ARRAY" ? DataType.ARRAY : DataType.OBJECT;
8403
- this.stateManagerDemoService.setAPIOptions(reqParams.apiOptions, type, db);
8404
- this.requestForm.markAsPristine();
8405
- }
8406
- onClearRecords() {
8407
- this.stateManagerDemoService.clearRecords();
8408
- }
8409
- onGetRequest() {
8410
- this.requestType = 'GET';
8411
- this.stateManagerDemoService.getClients();
8412
- }
8413
- onCreateRequest() {
8414
- this.requestType = 'POST';
8415
- this.stateManagerDemoService.createClient(this.sampleClientData);
8416
- }
8417
- onUpdateRequest() {
8418
- this.requestType = 'PUT';
8419
- this.stateManagerDemoService.updateClient(this.sampleClientData);
8420
- }
8421
- onDeleteRequest() {
8422
- this.requestType = 'DELETE';
8423
- this.stateManagerDemoService.deleteClient(this.sampleClientData);
8424
- }
8425
- onStreamRequest() {
8426
- console.log('[COMPONENT] onStreamRequest called');
8427
- if (!this.isValid) {
8428
- console.log('[COMPONENT] Form invalid, aborting');
8429
- return;
8430
- }
8431
- console.log('[COMPONENT] Compiling request...');
8432
- const reqParams = this.compileRequest();
8433
- console.log('[COMPONENT] Setting stream options:', reqParams.apiOptions);
8434
- reqParams.apiOptions.stream = true;
8435
- reqParams.apiOptions.streamType = this.streamType;
8436
- this.requestType = 'STREAM';
8437
- console.log('[COMPONENT] Calling streamRequest...');
8438
- this.stateManagerDemoService.streamRequest();
8439
- }
8440
- errorHandling(err, type) {
8441
- console.log(err, type);
8442
- if (type === 'GET')
8443
- this.GET_error$.next(err.message);
8444
- if (type === 'POST')
8445
- this.POST_error$.next(err.message);
8446
- if (type === 'PUT')
8447
- this.PUT_error$.next(err.message);
8448
- if (type === 'DELETE')
8449
- this.DELETE_error$.next(err.message);
8450
- if (type === 'STREAM')
8451
- this.STREAM_error$.next(err.message);
8452
- if (type === 'STREAM_AI')
8453
- this.STREAM_AI_error$.next(err.message);
8454
- }
8455
- onSelectAIType(type) {
8456
- this.AIType = type;
8457
- }
8458
- onClearHistory() {
8459
- this.prompts = [];
8460
- }
8461
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerStateDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8462
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: RequestManagerStateDemoComponent, selector: "app-request-manager-state-demo", inputs: { server: "server", adapter: "adapter", mapper: "mapper" }, providers: [StateManagerDemoService, HTTPManagerService], viewQueries: [{ propertyName: "failedState", first: true, predicate: ["failedState"], descendants: true, static: true }, { propertyName: "pollingState", first: true, predicate: ["pollingState"], descendants: true, static: true }], ngImport: i0, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n HTTP Request State Manager\n </h2>\n\n <div [formGroup]=\"requestForm\" style=\"margin-top: 2rem;\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>State Data Type</mat-label>\n <mat-select formControlName=\"datatype\">\n <mat-option value=\"ARRAY\">Array</mat-option>\n <mat-option value=\"OBJECT\">Object</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Adapter (Model)</mat-label>\n <mat-select formControlName=\"adapter\" #adapterSelect>\n <mat-option>None</mat-option>\n @for (adapter of sampleAdaptors; track adapter) {\n <mat-option [value]=\"adapter.value\">\n {{adapter.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Mapper (Model)</mat-label>\n <mat-select formControlName=\"mapper\" #mapperSelect>\n <mat-option>None</mat-option>\n @for (mapper of sampleMappers; track mapper) {\n <mat-option [value]=\"mapper.value\">\n {{mapper.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n @if (adapterSelect.value || mapperSelect.value) {\n <div style=\"display: flex; margin-bottom: 2rem;\">\n <div style=\"flex:1\" class=\"box\">\n <h3>Adapter (Incoming)</h3>\n @if (adapterSelect.value) {\n <div>\n {{ props(adapterSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n <div style=\"flex:1\" class=\"box\">\n <h3>Mapper (Outgoing)</h3>\n @if (mapperSelect.value) {\n <div>\n {{ props(mapperSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n </div>\n }\n\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>RestPath (/ delimited)</mat-label>\n <input matInput placeholder=\"clients/list\" formControlName=\"path\">\n </mat-form-field>\n </div>\n <div>\n <div formArrayName=\"headers\">\n @for (task of headers.controls; track task; let i = $index) {\n <div [formGroupName]=\"i\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Key</mat-label>\n <input matInput placeholder=\"authentication\" formControlName=\"key\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Value</mat-label>\n <input matInput placeholder=\"sample\" formControlName=\"value\">\n </mat-form-field>\n <div style=\"margin-top: .5rem;\">\n <button mat-icon-button (click)=\"removeHeader(i)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n <button mat-stroked-button (click)=\"addHeader()\">Add Header</button>\n </div>\n <div style=\"margin-top: 2rem; display: flex; flex-direction:column; gap: 1rem;\">\n <div>\n <mat-slide-toggle #failedState>Retry on Failed</mat-slide-toggle>\n @if (failedState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem;\" formGroupName=\"retry\">\n <mat-form-field appearance=\"outline\">\n <mat-label>#of Times</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"times\" value=\"3\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Delay Until Next</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"delay\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #pollingState>Polling</mat-slide-toggle>\n @if (pollingState.checked) {\n <div>\n <mat-form-field appearance=\"outline\" style=\"margin-top: 1rem\">\n <mat-label>#of Seconds</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"polling\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #DBState>Database Storage</mat-slide-toggle>\n @if (DBState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem\" formGroupName=\"database\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Table Name</mat-label>\n <input matInput placeholder=\"table\" formControlName=\"table\" value=\"\">\n </mat-form-field>\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Expires In</mat-label>\n <mat-select formControlName=\"expiresIn\">\n <mat-option value=\"1m\">One Minute</mat-option>\n <mat-option value=\"1h\">One Hour</mat-option>\n <mat-option value=\"1d\">One Day</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </div>\n }\n </div>\n <div style=\"margin-top: 1rem; display: flex;\">\n <span style=\"flex:1\"></span>\n <button mat-stroked-button (click)=\"onSetStateOptions()\" [disabled]=\"!hasChanged\">\n Set API Request Options\n </button>\n </div>\n <div>\n @if ((error$ | async); as error) {\n <mat-error>\n {{ error }}\n </mat-error>\n }\n </div>\n </div>\n </div>\n\n <div style=\"margin-bottom: 1rem; margin-top: 2rem;\">\n @if ((isPending$ | async)) {\n <mat-progress-bar mode=\"indeterminate\"\n ></mat-progress-bar>\n }\n @if (pollingState.checked) {\n <div>\n @if (!(isPending$ | async) && (this.countdown$ | async) || -1 > 0) {\n <mat-progress-bar mode=\"determinate\"\n [value]=\"(this.countdown$ | async)\"\n ></mat-progress-bar>\n }\n </div>\n }\n\n </div>\n\n @if ((GET$ | async); as data) {\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1;\">CURRENT STATE ({{ dataType }})</h2>\n </div>\n @if (data.length > 1) {\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Records</mat-label>\n <mat-select [formControl]=\"selectedRecord\" [disabled]=\"data === null && data?.length === 0\">\n <mat-option [value]=\"\">None</mat-option>\n @for (item of data; track item) {\n <mat-option [value]=\"item\">\n {{item.name || (item.first_name) | titlecase}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n }\n @if ((dataObservable$ | async); as dataRecord) {\n <div>\n @if ((selectedRecord$ | async); as record) {\n <div>\n {{ record | json }}\n </div>\n } @else {\n No Record Selected from State\n }\n </div>\n }\n <div style=\"margin-top: 1rem;\">\n @if (data !== null && data?.length > 0) {\n <span>\n State contains a Total of {{ data.length }} Records\n </span>\n } @else {\n No Records\n }\n <div style=\"display: flex; gap: .5rem; text-align: end; justify-content: flex-end;\">\n <button class=\"btn\" mat-stroked-button (click)=\"onClearRecords()\">Clear</button>\n </div>\n </div>\n </div>\n }\n\n\n\n\n <div style=\"margin-top: 1rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">GET Request ({{ dataType }})</h2>\n <div>\n <button mat-raised-button (click)=\"onGetRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((GET_error$ | async); as get_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ get_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(GET$ | async) | jsonv\"></div> -->\n @if ((GET$ | async); as getData) {\n <div>{{ getData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">POST Request</h2>\n <div>\n <button mat-raised-button (click)=\"onCreateRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((POST_error$ | async); as post_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ post_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(POST$ | async) | jsonv\"></div> -->\n @if ((POST$ | async); as postData) {\n <div>{{ postData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">PUT Request</h2>\n <div>\n <button mat-raised-button (click)=\"onUpdateRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((PUT_error$ | async); as put_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ put_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(PUT$ | async) | jsonv\"></div> -->\n @if ((PUT$ | async); as putData) {\n <div>{{ putData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">DELETE Request</h2>\n <div>\n <button mat-raised-button (click)=\"onDeleteRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((DELETE_error$ | async); as delete_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ delete_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(DELETE$ | async) | jsonv\"></div> -->\n @if ((DELETE$ | async); as deleteData) {\n <div>{{ deleteData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">Streaming GET Request</h2>\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n {{ streamType }}\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>data_usage</mat-icon>\n </button>\n </div>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item *ngFor=\"let item of streamTypes\" (click)=\"onStreamType(item.id)\">{{ item.value }}</button>\n </mat-menu>\n <button mat-raised-button (click)=\"onStreamRequest()\" class=\"btn\" [disabled]=\"hasChanged\">Request</button>\n </div>\n </div>\n\n @if ((STREAM_error$ | async); as stream_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n @if ((STREAM$ | async); as data) {\n <div class=\"container\">\n <table mat-table [dataSource]=\"data\" class=\"mat-elevation-z8\">\n\n <!-- Dynamic columns -->\n <ng-container *ngFor=\"let column of displayedColumns\" [matColumnDef]=\"column\">\n <th mat-header-cell *matHeaderCellDef> {{ column | titlecase }} </th>\n <td mat-cell *matCellDef=\"let element\">\n @if (isObject(element[column]); as objValue) {\n <pre style=\"margin: 0; font-size: 0.8em; white-space: pre-wrap;\">{{ objValue | json }}</pre>\n } @else {\n {{ element[column] }}\n }\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n\n <!-- Debug info -->\n <div style=\"margin-top: 1rem; font-size: 0.8em; color: #666;\">\n Columns: {{ displayedColumns.join(', ') }} | Data received\n </div>\n </div>\n }\n </div>\n\n </div>\n\n</div>\n", styles: [".btn{min-width:120px}.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;background-color:#f5f5f5}.container{height:400px;overflow:auto}.box{padding:10px;border:1px solid #ccc}\n"], dependencies: [{ kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2$1.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: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i2$1.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "directive", type: i2$1.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i3.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "component", type: i5.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"], 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: "component", type: i7.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: i7.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i7.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i9.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i9.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i9.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i9.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i9.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i9.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i9.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i9.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i9.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i8$1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: i11.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "component", type: i12.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i13.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }, { kind: "pipe", type: i1$1.TitleCasePipe, name: "titlecase" }] }); }
8463
- }
8464
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerStateDemoComponent, decorators: [{
8465
- type: Component,
8466
- args: [{ selector: 'app-request-manager-state-demo', providers: [StateManagerDemoService, HTTPManagerService], standalone: false, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n HTTP Request State Manager\n </h2>\n\n <div [formGroup]=\"requestForm\" style=\"margin-top: 2rem;\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>State Data Type</mat-label>\n <mat-select formControlName=\"datatype\">\n <mat-option value=\"ARRAY\">Array</mat-option>\n <mat-option value=\"OBJECT\">Object</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Adapter (Model)</mat-label>\n <mat-select formControlName=\"adapter\" #adapterSelect>\n <mat-option>None</mat-option>\n @for (adapter of sampleAdaptors; track adapter) {\n <mat-option [value]=\"adapter.value\">\n {{adapter.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Mapper (Model)</mat-label>\n <mat-select formControlName=\"mapper\" #mapperSelect>\n <mat-option>None</mat-option>\n @for (mapper of sampleMappers; track mapper) {\n <mat-option [value]=\"mapper.value\">\n {{mapper.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n @if (adapterSelect.value || mapperSelect.value) {\n <div style=\"display: flex; margin-bottom: 2rem;\">\n <div style=\"flex:1\" class=\"box\">\n <h3>Adapter (Incoming)</h3>\n @if (adapterSelect.value) {\n <div>\n {{ props(adapterSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n <div style=\"flex:1\" class=\"box\">\n <h3>Mapper (Outgoing)</h3>\n @if (mapperSelect.value) {\n <div>\n {{ props(mapperSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n </div>\n }\n\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>RestPath (/ delimited)</mat-label>\n <input matInput placeholder=\"clients/list\" formControlName=\"path\">\n </mat-form-field>\n </div>\n <div>\n <div formArrayName=\"headers\">\n @for (task of headers.controls; track task; let i = $index) {\n <div [formGroupName]=\"i\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Key</mat-label>\n <input matInput placeholder=\"authentication\" formControlName=\"key\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Value</mat-label>\n <input matInput placeholder=\"sample\" formControlName=\"value\">\n </mat-form-field>\n <div style=\"margin-top: .5rem;\">\n <button mat-icon-button (click)=\"removeHeader(i)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n <button mat-stroked-button (click)=\"addHeader()\">Add Header</button>\n </div>\n <div style=\"margin-top: 2rem; display: flex; flex-direction:column; gap: 1rem;\">\n <div>\n <mat-slide-toggle #failedState>Retry on Failed</mat-slide-toggle>\n @if (failedState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem;\" formGroupName=\"retry\">\n <mat-form-field appearance=\"outline\">\n <mat-label>#of Times</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"times\" value=\"3\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Delay Until Next</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"delay\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #pollingState>Polling</mat-slide-toggle>\n @if (pollingState.checked) {\n <div>\n <mat-form-field appearance=\"outline\" style=\"margin-top: 1rem\">\n <mat-label>#of Seconds</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"polling\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #DBState>Database Storage</mat-slide-toggle>\n @if (DBState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem\" formGroupName=\"database\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Table Name</mat-label>\n <input matInput placeholder=\"table\" formControlName=\"table\" value=\"\">\n </mat-form-field>\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Expires In</mat-label>\n <mat-select formControlName=\"expiresIn\">\n <mat-option value=\"1m\">One Minute</mat-option>\n <mat-option value=\"1h\">One Hour</mat-option>\n <mat-option value=\"1d\">One Day</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </div>\n }\n </div>\n <div style=\"margin-top: 1rem; display: flex;\">\n <span style=\"flex:1\"></span>\n <button mat-stroked-button (click)=\"onSetStateOptions()\" [disabled]=\"!hasChanged\">\n Set API Request Options\n </button>\n </div>\n <div>\n @if ((error$ | async); as error) {\n <mat-error>\n {{ error }}\n </mat-error>\n }\n </div>\n </div>\n </div>\n\n <div style=\"margin-bottom: 1rem; margin-top: 2rem;\">\n @if ((isPending$ | async)) {\n <mat-progress-bar mode=\"indeterminate\"\n ></mat-progress-bar>\n }\n @if (pollingState.checked) {\n <div>\n @if (!(isPending$ | async) && (this.countdown$ | async) || -1 > 0) {\n <mat-progress-bar mode=\"determinate\"\n [value]=\"(this.countdown$ | async)\"\n ></mat-progress-bar>\n }\n </div>\n }\n\n </div>\n\n @if ((GET$ | async); as data) {\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1;\">CURRENT STATE ({{ dataType }})</h2>\n </div>\n @if (data.length > 1) {\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Records</mat-label>\n <mat-select [formControl]=\"selectedRecord\" [disabled]=\"data === null && data?.length === 0\">\n <mat-option [value]=\"\">None</mat-option>\n @for (item of data; track item) {\n <mat-option [value]=\"item\">\n {{item.name || (item.first_name) | titlecase}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n }\n @if ((dataObservable$ | async); as dataRecord) {\n <div>\n @if ((selectedRecord$ | async); as record) {\n <div>\n {{ record | json }}\n </div>\n } @else {\n No Record Selected from State\n }\n </div>\n }\n <div style=\"margin-top: 1rem;\">\n @if (data !== null && data?.length > 0) {\n <span>\n State contains a Total of {{ data.length }} Records\n </span>\n } @else {\n No Records\n }\n <div style=\"display: flex; gap: .5rem; text-align: end; justify-content: flex-end;\">\n <button class=\"btn\" mat-stroked-button (click)=\"onClearRecords()\">Clear</button>\n </div>\n </div>\n </div>\n }\n\n\n\n\n <div style=\"margin-top: 1rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">GET Request ({{ dataType }})</h2>\n <div>\n <button mat-raised-button (click)=\"onGetRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((GET_error$ | async); as get_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ get_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(GET$ | async) | jsonv\"></div> -->\n @if ((GET$ | async); as getData) {\n <div>{{ getData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">POST Request</h2>\n <div>\n <button mat-raised-button (click)=\"onCreateRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((POST_error$ | async); as post_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ post_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(POST$ | async) | jsonv\"></div> -->\n @if ((POST$ | async); as postData) {\n <div>{{ postData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">PUT Request</h2>\n <div>\n <button mat-raised-button (click)=\"onUpdateRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((PUT_error$ | async); as put_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ put_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(PUT$ | async) | jsonv\"></div> -->\n @if ((PUT$ | async); as putData) {\n <div>{{ putData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">DELETE Request</h2>\n <div>\n <button mat-raised-button (click)=\"onDeleteRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((DELETE_error$ | async); as delete_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ delete_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(DELETE$ | async) | jsonv\"></div> -->\n @if ((DELETE$ | async); as deleteData) {\n <div>{{ deleteData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">Streaming GET Request</h2>\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n {{ streamType }}\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>data_usage</mat-icon>\n </button>\n </div>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item *ngFor=\"let item of streamTypes\" (click)=\"onStreamType(item.id)\">{{ item.value }}</button>\n </mat-menu>\n <button mat-raised-button (click)=\"onStreamRequest()\" class=\"btn\" [disabled]=\"hasChanged\">Request</button>\n </div>\n </div>\n\n @if ((STREAM_error$ | async); as stream_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n @if ((STREAM$ | async); as data) {\n <div class=\"container\">\n <table mat-table [dataSource]=\"data\" class=\"mat-elevation-z8\">\n\n <!-- Dynamic columns -->\n <ng-container *ngFor=\"let column of displayedColumns\" [matColumnDef]=\"column\">\n <th mat-header-cell *matHeaderCellDef> {{ column | titlecase }} </th>\n <td mat-cell *matCellDef=\"let element\">\n @if (isObject(element[column]); as objValue) {\n <pre style=\"margin: 0; font-size: 0.8em; white-space: pre-wrap;\">{{ objValue | json }}</pre>\n } @else {\n {{ element[column] }}\n }\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n\n <!-- Debug info -->\n <div style=\"margin-top: 1rem; font-size: 0.8em; color: #666;\">\n Columns: {{ displayedColumns.join(', ') }} | Data received\n </div>\n </div>\n }\n </div>\n\n </div>\n\n</div>\n", styles: [".btn{min-width:120px}.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;background-color:#f5f5f5}.container{height:400px;overflow:auto}.box{padding:10px;border:1px solid #ccc}\n"] }]
8467
- }], ctorParameters: () => [], propDecorators: { server: [{
8468
- type: Input
8469
- }], adapter: [{
8470
- type: Input
8471
- }], mapper: [{
8472
- type: Input
8473
- }], failedState: [{
8474
- type: ViewChild,
8475
- args: ["failedState", { static: true }]
8476
- }], pollingState: [{
8477
- type: ViewChild,
8478
- args: ["pollingState", { static: true }]
8479
- }] } });
8480
-
8481
- class RequestManagerDemoComponent {
8482
- // Dynamic columns based on data structure
8483
- getColumnsFromData(data) {
8484
- if (!data || data.length === 0) {
8485
- return [];
8486
- }
8487
- const firstRecord = data[0];
8488
- if (!firstRecord || typeof firstRecord !== 'object') {
8489
- return [];
8490
- }
8491
- // Extract all keys from the first record, excluding null/undefined
8492
- return Object.keys(firstRecord).filter(key => firstRecord[key] !== null && firstRecord[key] !== undefined);
8493
- }
8494
- // Update displayed columns when data changes
8495
- updateDisplayedColumns(data) {
8496
- this.displayedColumns = this.getColumnsFromData(data);
8497
- console.log('[DEMO] Updated columns:', this.displayedColumns);
8498
- }
8499
- // Helper to check if value is an object
8500
- isObject(value) {
8501
- return value !== null && typeof value === 'object' && !Array.isArray(value);
8502
- }
8503
- // Computed properties for filtered states (removes undefined)
8504
- get filteredParallelStates() {
8505
- return this.parallelBatchStates.filter(s => s !== undefined);
8506
- }
8507
- get filteredSequentialStates() {
8508
- return this.sequentialBatchStates.filter(s => s !== undefined);
8509
- }
8510
- get filteredStreamStates() {
8511
- return this.streamBatchStates.filter(s => s !== undefined);
8512
- }
8513
- get retry() {
8514
- return this.requestForm.get('retry')?.value;
8515
- }
8516
- get headers() {
8517
- return this.requestForm.get('headers');
8518
- }
8519
- get isValid() {
8520
- this.requestForm.markAllAsTouched();
8521
- return this.requestForm.valid;
8522
- }
8523
- constructor() {
8524
- this.server = 'http://localhost:8080';
8525
- this.displayedColumns = [];
8526
- this.fb = inject(FormBuilder);
8527
- this.toastMessage = inject(ToastMessageDisplayService);
8528
- this.questionControl = this.fb.control("", [Validators.required]);
8529
- this.httpManagerService = inject(HTTPManagerService);
8530
- this.isPending$ = this.httpManagerService.isPending$;
8531
- this.countdown$ = this.httpManagerService.countdown$;
8532
- this.GET_error$ = new BehaviorSubject('');
8533
- this.POST_error$ = new BehaviorSubject('');
8534
- this.PUT_error$ = new BehaviorSubject('');
8535
- this.DELETE_error$ = new BehaviorSubject('');
8536
- this.STREAM_error$ = new BehaviorSubject('');
8537
- this.STREAM_AI_error$ = new BehaviorSubject('');
8538
- this.isParallelLoading = false;
8539
- this.isSequentialLoading = false;
8540
- // Parallel batch states for individual request tracking
8541
- this.parallelBatchStates = [];
8542
- // Sequential batch states for individual request tracking
8543
- this.sequentialBatchStates = [];
8544
- // Stream batch request properties
8545
- this.streamBatchStates = [];
8546
- this.isStreamLoading = false;
8547
- this.requestParams = {
8548
- GET: ApiRequest.adapt(),
8549
- POST: ApiRequest.adapt(),
8550
- PUT: ApiRequest.adapt(),
8551
- DELETE: ApiRequest.adapt(),
8552
- STREAM: ApiRequest.adapt(),
8553
- };
8554
- this.streamTypes = [
8555
- { id: 'JSON', value: 'json' },
8556
- { id: 'NDJSON', value: 'ndjson' },
8557
- { id: 'AI Streaming', value: 'ai_streaming' },
8558
- { id: 'Event Stream', value: 'event_stream' },
8559
- { id: 'Auto', value: 'auto' },
8560
- ];
8561
- this.streamType = 'Auto';
8562
- this.downloadRequest = ApiRequest.adapt({
8563
- server: 'assets/images',
8564
- path: ['lego.png'],
8565
- saveAs: 'john.jpg', // Optional
8566
- headers: { 'Authorization': 'Bearer Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6InJ0c0ZULWItN0x1WTdEVlllU05LY0lKN1ZuYyJ9.eyJhdWQiOiI4ODhhMzFjZS01OWQzLTRhMTItYTU5Ni04ZDYyZjY0MWI1MDUiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vNmY0MTE5ODItY2YyMy00ZTQ1LTk0NDktNGI2MDdiN2E4OGVjL3YyLjAiLCJpYXQiOjE3NjU4MzQxNDksIm5iZiI6MTc2NTgzNDE0OSwiZXhwIjoxNzY1ODM5NzQ2LCJhY2N0IjowLCJhaW8iOiJBV1FBbS84YUFBQUFVUEZOYUhSZkZ4ZVkwRWthcFBzdWZaNG1QYXV6YVRoODRRZUtQRzkrbEFIVE5XanVwQy9ZSjEwK2IrMktWVlJSSXZRNFpwS2xyWTFGd0xRZmtXOTNLbkRNckxSeEMzWTVOOGlQREZ4b1liZExucW1QL1N1ZE1pRW1Va05tTklSWSIsImF6cCI6Ijg4OGEzMWNlLTU5ZDMtNGExMi1hNTk2LThkNjJmNjQxYjUwNSIsImF6cGFjciI6IjAiLCJncm91cHMiOlsiZWE2ZDk5YjAtNDgyNC00MDU0LTk0MTQtZDNhOTZkZDA3MjRiIl0sIm5hbWUiOiJHVExDIEFwcCBTdXBlciBBZG1pbiIsIm9pZCI6IjI2NzUxY2I2LWNlMDEtNDMzMC05OTc0LWZjMzgxMjQ3YTEzYyIsInByZWZlcnJlZF91c2VybmFtZSI6Imd0bGMwMDNAZ3RsYy5jYSIsInJoIjoiMS5BUzRBZ2hsQmJ5UFBSVTZVU1V0Z2UzcUk3TTR4aW9qVFdSSktwWmFOWXZaQnRRVm1BYTR1QUEuIiwicm9sZXMiOlsiZ3RsYy5hY2wuc3VwZXJhZG1pbiJdLCJzY3AiOiJHVExDX0FwcGxpY2F0aW9uIiwic2lkIjoiMDAzZjVhZDktZmJmNi1jZTkyLTg3MjItMWEzNDExYjMzMDJiIiwic3ViIjoiS0RYQ3drVlhUbTRUUmcwaTkxbXZDZzgtQ29uLXpWbk5FQ2Y5LVg0dkpzUSIsInRpZCI6IjZmNDExOTgyLWNmMjMtNGU0NS05NDQ5LTRiNjA3YjdhODhlYyIsInVwbiI6Imd0bGMwMDNAZ3RsYy5jYSIsInV0aSI6InpBaTJoMmpVUUVHbVY0RTJXMWxqQUEiLCJ2ZXIiOiIyLjAiLCJ3aWRzIjpbImI3OWZiZjRkLTNlZjktNDY4OS04MTQzLTc2YjE5NGU4NTUwOSJdLCJ4bXNfZnRkIjoiUDJraW1ZM0tqc1BTRndxNHlZdmJ0bGhrSUk3d2tXNmFpcW1FQ1BaNEdja0JkWE5sWVhOMExXUnpiWE0ifQ.LzppoggMm27smSAy9SamtPN95vCzdELCAfhtOj5n_T_H6g9xCmNRLS9FaUFQMau6Qvl0lROKl7WDklTswLFkfxbIxCBWtXdL-LTqT5cDURSJAll8vC3zlN3Hg9pAFBUVZFRolt6Z7LvPdI3pvUOQs0yFwVzp9k6cLF8aemKdwKQrMX3XXua1MfBWZcqQ4WiBVNmKh8w6yQB35I4u5WqdFnu33nUGb-kvc18SOpoUfiJnlV-PudaEzFXdU3CjAaMEcuPFv5xLwWJKuhU73dNH4EyQDFMVGtcIHNnieOfiY_nK2_0-5DM6aI40UIRK6Bt-HmMQpnbhLps5y3ep6Z7RNw' } // Optional
8567
- });
8568
- // downloadRequest = ApiRequest.adapt({
8569
- // server: 'oidc/ai/file'
8570
- // })
8571
- this.sampleClientData = {
8572
- id: 0,
8573
- name: "Old School Dates",
8574
- domain: "osd.com",
8575
- service: "osd",
8576
- spiffe: "osd.com/osd",
8577
- secret: "SMOPECXP-OS4P-USOG-X2II-3XMD1FQDR3IJX",
8578
- created: 1693003138,
8579
- modified: 1693003138,
8580
- icon: "",
8581
- imageFile: "",
8582
- email: "wavecoders@gmail.com"
8583
- };
8584
- this.requestForm = this.fb.group({
8585
- path: this.fb.control("ai/"),
8586
- headers: this.fb.array([]),
8587
- adapter: [null],
8588
- mapper: [null],
8589
- retry: this.fb.group({
8590
- times: [3],
8591
- delay: [3],
8592
- }),
8593
- polling: [3],
8594
- });
8595
- this.AIType = 0;
8596
- this.sampleAdaptors = [
8597
- { label: "ClientInfo Basic", value: ClientInfo$1.adapt },
8598
- { label: "AI Prompt", value: AIPrompt$1.adapt },
8599
- ];
8600
- this.sampleMappers = [
8601
- { label: "Mapper Basic", value: ClientInfoMapper$1.adapt },
8602
- { label: "AI Prompt", value: AIPrompt$1.adapt },
8603
- ];
8604
- this.hasId = (arr) => {
8605
- if (arr.length === 0)
8606
- return false;
8607
- return !isNaN(arr[arr.length - 1]);
8608
- };
8609
- this.props = (adapter) => {
8610
- return (adapter) ? adapter() : null;
8611
- };
8612
- // server = `http://sample-endpoint/as/authorization.oauth2`
8613
- this.arrayObjectsToObjects = (arr) => {
8614
- return Array.isArray(arr) ? arr.reduce((obj, item) => Object.assign(obj, { [item.key]: item.value }), {}) : {};
8615
- };
8616
- }
8617
- ngOnInit() {
8618
- // const reqGet2 = ApiRequest.adapt({
8619
- // server,
8620
- // path: ['clients'],
8621
- // headers: { authentication: "Bearer <KEY>" },
8622
- // adapter: ClientInfo,
8623
- // dataType: DataType.OBJECT,
8624
- // // concurrent: false,
8625
- // // polling: 3, //seconds
8626
- // })
8627
- // const req2 = [1024,1025,1026].map(item => {
8628
- // return this.httpManagerService.getRequest<ClientInfo[]>(reqGet2, [item])
8629
- // .pipe(
8630
- // catchError(error => {
8631
- // return throwError(() => new Error(error.error.message))
8632
- // })
8633
- // )
8634
- // })
8635
- // forkJoin(req2)
8636
- // .subscribe(res => console.log(res))
8637
- }
8638
- onStreamType(type) {
8639
- this.streamType = type;
8640
- }
8641
- addHeader() {
8642
- const header = this.fb.group({
8643
- key: ['', Validators.required],
8644
- value: ['']
8645
- });
8646
- this.headers.push(header);
8647
- }
8648
- removeHeader(index) {
8649
- this.headers.removeAt(index);
8650
- }
8651
- compileRequest() {
8652
- const requestParams = this.requestForm.value;
8653
- requestParams.headers = this.arrayObjectsToObjects(requestParams.headers || []);
8654
- const pathReq = (requestParams.path === "") ? [] : (requestParams.path || "").split("/");
8655
- if (!this.pollingState.checked)
8656
- requestParams.polling = 0;
8657
- if (!this.failedState.checked) {
8658
- requestParams.retry = { times: 0, delay: 0 };
8659
- }
8660
- const apiOptions = ApiRequest.adapt(requestParams);
8661
- apiOptions.path = [];
8662
- apiOptions.server = this.server;
8663
- apiOptions.adapter = this.adapter;
8664
- apiOptions.mapper = this.mapper;
8665
- return { apiOptions: apiOptions, path: pathReq };
8666
- }
8667
- onGetRequest() {
8668
- if (!this.isValid)
8669
- return;
8670
- const reqParams = this.compileRequest();
8671
- this.requestParams.GET = reqParams.apiOptions;
8672
- this.GET$ = EMPTY; //Cancels Previous
8673
- this.GET_error$.next('');
8674
- this.GET$ = this.httpManagerService.getRequest({ ...reqParams.apiOptions }, reqParams.path)
8675
- .pipe(
8676
- // tap((data) => console.log("API GET response", data)),
8677
- catchError(error => {
8678
- return throwError(() => this.errorHandling(error, 'GET'));
8679
- }));
8680
- }
8681
- onCreateRequest() {
8682
- if (!this.isValid)
8683
- return;
8684
- const reqParams = this.compileRequest();
8685
- this.requestParams.POST = reqParams.apiOptions;
8686
- this.POST$ = EMPTY; //Cancels Previous
8687
- this.POST_error$.next('');
8688
- console.log("POST", this.sampleClientData);
8689
- console.log("POST", reqParams.apiOptions);
8690
- console.log("POST", reqParams.path);
8691
- this.POST$ = this.httpManagerService.postRequest(this.sampleClientData, reqParams.apiOptions, reqParams.path)
8692
- .pipe(
8693
- // tap((data) => console.log("API POST response", data)),
8694
- catchError(error => {
8695
- return throwError(() => this.errorHandling(error, 'POST'));
8696
- }));
8697
- }
8698
- onUpdateRequest() {
8699
- if (!this.isValid)
8700
- return;
8701
- const reqParams = this.compileRequest();
8702
- if (!this.hasId(reqParams.path)) {
8703
- console.log("Missing ID");
8704
- return;
8705
- }
8706
- this.sampleClientData.id = parseInt(reqParams.path[reqParams.path.length - 1]);
8707
- this.requestParams.PUT = reqParams.apiOptions;
8708
- this.PUT$ = EMPTY; //Cancels Previous
8709
- this.PUT_error$.next('');
8710
- this.PUT$ = this.httpManagerService.putRequest(this.sampleClientData, reqParams.apiOptions, reqParams.path)
8711
- .pipe(
8712
- // tap((data) => console.log("API PUT response", data)),
8713
- catchError(error => {
8714
- return throwError(() => this.errorHandling(error, 'PUT'));
8715
- }));
8716
- }
8717
- onDeleteRequest() {
8718
- if (!this.isValid)
8719
- return;
8720
- const reqParams = this.compileRequest();
8721
- this.requestParams.DELETE = reqParams.apiOptions;
8722
- if (!this.hasId(reqParams.path)) {
8723
- console.log("Missing ID");
8724
- return;
8725
- }
8726
- this.sampleClientData.id = parseInt(reqParams.path[reqParams.path.length - 1]);
8727
- this.requestParams.DELETE = reqParams.apiOptions;
8728
- this.DELETE$ = EMPTY; //Cancels Previous
8729
- this.DELETE_error$.next('');
8730
- this.DELETE$ = this.httpManagerService.deleteRequest(reqParams.apiOptions, reqParams.path)
8731
- .pipe(
8732
- // tap((data) => console.log("API DELETE response", data)),
8733
- catchError(error => {
8734
- return throwError(() => this.errorHandling(error, 'DELETE'));
8735
- }));
8736
- }
8737
- onStreamPostRequest() {
8738
- if (!this.isValid)
8739
- return;
8740
- const reqParams = this.compileRequest();
8741
- let payload = {};
8742
- let apiPath = reqParams.path;
8743
- let apiOptions = reqParams.apiOptions;
8744
- let responseMapper = (items) => items.response;
8745
- if (this.AIType === 0) {
8746
- // API request
8747
- payload = { prompt: this.questionControl.value };
8748
- }
8749
- else {
8750
- // Local Ollama request
8751
- apiOptions.server = "api";
8752
- apiPath = ["generate"];
8753
- apiOptions.stream = true;
8754
- apiOptions.streamType = this.streamType;
8755
- payload = {
8756
- model: "phi3:latest",
8757
- prompt: this.questionControl.value,
8758
- stream: true,
8759
- };
8760
- responseMapper = (items) => items.map((word) => word.response).flat().join('');
8761
- }
8762
- this.requestParams.STREAM = apiOptions;
8763
- this.STREAM_AI$ = EMPTY;
8764
- this.STREAM_AI_error$.next('');
8765
- this.STREAM_AI$ = this.httpManagerService.postRequest(payload, apiOptions, apiPath).pipe(map(responseMapper), tap(() => this.questionControl.reset()), catchError(error => throwError(() => this.errorHandling(error, 'STREAM'))));
8766
- }
8767
- onStreamRequest() {
8768
- if (!this.isValid)
8769
- return;
8770
- const reqParams = this.compileRequest();
8771
- reqParams.apiOptions.stream = true;
8772
- reqParams.apiOptions.streamType = StreamType.NDJSON;
8773
- this.requestParams.GET = reqParams.apiOptions;
8774
- this.STREAM$ = this.httpManagerService.getRequest(reqParams.apiOptions, reqParams.path)
8775
- .pipe(tap((data) => {
8776
- console.log("API STREAM response", data);
8777
- if (data && data.length > 0) {
8778
- this.updateDisplayedColumns(data);
8779
- }
8780
- }), catchError(error => {
8781
- return throwError(() => this.errorHandling(error, 'STREAM'));
8782
- }));
8783
- }
8784
- onDownloadCompleted() {
8785
- const message = "Download Completed";
8786
- const display = ToastDisplay.adapt({
8787
- message,
8788
- action: 'Ok',
8789
- color: ToastColors.SUCCESS,
8790
- icon: 'sentiment_satisfied_alt',
8791
- });
8792
- this.toastMessage.toastMessage(display);
8793
- }
8794
- onDownloadFailed(err) {
8795
- const message = "Download Failed";
8796
- const display = ToastDisplay.adapt({
8797
- message,
8798
- action: 'Ok',
8799
- color: ToastColors.ERROR,
8800
- icon: 'warning',
8801
- });
8802
- this.toastMessage.toastMessage(display);
8803
- }
8804
- errorHandling(err, type) {
8805
- if (type === 'GET')
8806
- this.GET_error$.next(err.message);
8807
- if (type === 'POST')
8808
- this.POST_error$.next(err.message);
8809
- if (type === 'PUT')
8810
- this.PUT_error$.next(err.message);
8811
- if (type === 'DELETE')
8812
- this.DELETE_error$.next(err.message);
8813
- if (type === 'STREAM')
8814
- this.STREAM_error$.next(err.message);
8815
- }
8816
- onSelectAIType(type) {
8817
- this.AIType = type;
8818
- }
8819
- // ═══════════════════════════════════════════════════════════════════════════
8820
- // BATCH REQUEST DEMO METHODS
8821
- // ═══════════════════════════════════════════════════════════════════════════
8822
- /**
8823
- * Execute parallel batch request - fetch 5 users from JSONPlaceholder with individual states
8824
- */
8825
- onExecuteParallelBatch() {
8826
- this.isParallelLoading = true;
8827
- this.parallelError = undefined;
8828
- this.parallelExecutionTime = undefined;
8829
- this.parallelResults = undefined;
8830
- this.parallelBatchStates = [];
8831
- const requests = this.createBatchRequests('https://jsonplaceholder.typicode.com', ['users'], [1, 2, 3, 4, 5]);
8832
- const startTime = performance.now();
8833
- this.httpManagerService.getBatchRequestsStream(requests, {
8834
- mode: 'parallel',
8835
- concurrency: 3,
8836
- stopOnError: false,
8837
- ignoreErrors: true,
8838
- logErrors: true
8839
- }).pipe(tap(state => {
8840
- this.parallelBatchStates[state.index] = state;
8841
- if (!state.isPending && this.parallelBatchStates.every(s => s && !s.isPending)) {
8842
- this.parallelExecutionTime = performance.now() - startTime;
8843
- this.isParallelLoading = false;
8844
- }
8845
- }), catchError(error => {
8846
- this.parallelError = error.message || 'Parallel batch failed';
8847
- this.isParallelLoading = false;
8848
- return throwError(() => error);
8849
- })).subscribe();
8850
- }
8851
- /**
8852
- * Execute sequential batch request - fetch 5 todos from JSONPlaceholder with individual states
8853
- */
8854
- onExecuteSequentialBatch() {
8855
- this.isSequentialLoading = true;
8856
- this.sequentialError = undefined;
8857
- this.sequentialExecutionTime = undefined;
8858
- this.sequentialResults = undefined;
8859
- this.sequentialBatchStates = [];
8860
- const requests = this.createBatchRequests('https://jsonplaceholder.typicode.com', ['todos'], [1, 2, 3, 4, 5]);
8861
- const startTime = performance.now();
8862
- this.httpManagerService.getBatchRequestsStream(requests, {
8863
- mode: 'sequential',
8864
- stopOnError: false,
8865
- concurrency: 3,
8866
- ignoreErrors: true,
8867
- logErrors: true
8868
- }).pipe(tap(state => {
8869
- this.sequentialBatchStates[state.index] = state;
8870
- if (!state.isPending && this.sequentialBatchStates.every(s => s && !s.isPending)) {
8871
- this.sequentialExecutionTime = performance.now() - startTime;
8872
- this.isSequentialLoading = false;
8873
- }
8874
- }), catchError(error => {
8875
- this.sequentialError = error.message || 'Sequential batch failed';
8876
- this.isSequentialLoading = false;
8877
- return throwError(() => error);
8878
- })).subscribe();
8879
- }
8880
- /**
8881
- * Execute stream batch request - fetch 5 posts with real-time state updates
8882
- */
8883
- onExecuteStreamBatch() {
8884
- this.isStreamLoading = true;
8885
- this.streamError = undefined;
8886
- this.streamExecutionTime = undefined;
8887
- this.streamBatchStates = [];
8888
- this.streamProgress = undefined;
8889
- const requests = this.createBatchRequests('https://jsonplaceholder.typicode.com', ['posts'], [1, 2, 3, 4, 5]);
8890
- const startTime = performance.now();
8891
- this.httpManagerService.getBatchRequestsStream(requests, {
8892
- mode: 'parallel',
8893
- concurrency: 3,
8894
- stopOnError: false,
8895
- ignoreErrors: true,
8896
- logErrors: true
8897
- }).pipe(tap(state => {
8898
- this.streamBatchStates[state.index] = state;
8899
- this.streamProgress = calculateBatchProgress(this.streamBatchStates.filter(s => s !== undefined));
8900
- if (!state.isPending) {
8901
- this.streamExecutionTime = performance.now() - startTime;
8902
- }
8903
- this.isStreamLoading = this.streamBatchStates.some(s => s && s.isPending);
8904
- }), catchError(error => {
8905
- this.streamError = error.message || 'Stream batch failed';
8906
- this.isStreamLoading = false;
8907
- return throwError(() => error);
8908
- })).subscribe();
8909
- }
8910
- /**
8911
- * Type guard for pending state
8912
- */
8913
- isPendingState(state) {
8914
- return state.isPending === true;
8915
- }
8916
- /**
8917
- * Type guard for success state
8918
- */
8919
- isSuccessState(state) {
8920
- return state.isPending === false && state.data !== undefined;
8921
- }
8922
- /**
8923
- * Type guard for error state
8924
- */
8925
- isErrorState(state) {
8926
- return state.isPending === false && state.error !== undefined;
8927
- }
8928
- /**
8929
- * Helper to create batch requests from IDs
8930
- */
8931
- createBatchRequests(baseUrl, basePath, ids) {
8932
- return ids.map(id => ApiRequest.adapt({
8933
- server: baseUrl,
8934
- path: [...basePath, id]
8935
- }));
8936
- }
8937
- /**
8938
- * Check if result is successful (not undefined)
8939
- */
8940
- isSuccess(result) {
8941
- return result !== undefined && result !== null;
8942
- }
8943
- /**
8944
- * Format execution time for display
8945
- */
8946
- formatTime(ms) {
8947
- if (ms < 1000) {
8948
- return `${Math.round(ms)}ms`;
8949
- }
8950
- return `${(ms / 1000).toFixed(2)}s`;
8951
- }
8952
- /**
8953
- * Get success count from results array
8954
- */
8955
- getSuccessCount(results) {
8956
- if (!results)
8957
- return 0;
8958
- return results.filter(r => r !== undefined).length;
8959
- }
8960
- /**
8961
- * Get failure count from results array
8962
- */
8963
- getFailureCount(results) {
8964
- if (!results)
8965
- return 0;
8966
- return results.filter(r => r === undefined).length;
8967
- }
8968
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8969
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: RequestManagerDemoComponent, selector: "app-request-manager-demo", inputs: { server: "server", adapter: "adapter", mapper: "mapper" }, providers: [HTTPManagerService], viewQueries: [{ propertyName: "failedState", first: true, predicate: ["failedState"], descendants: true, static: true }, { propertyName: "pollingState", first: true, predicate: ["pollingState"], descendants: true, static: true }], ngImport: i0, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n HTTP Request Manager\n </h2>\n\n <div [formGroup]=\"requestForm\" style=\"margin-top: 2rem;\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Adapter (Model)</mat-label>\n <mat-select formControlName=\"adapter\" #adapterSelect>\n <mat-option>None</mat-option>\n @for (adapter of sampleAdaptors; track adapter) {\n <mat-option [value]=\"adapter.value\">\n {{adapter.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Mapper (Model)</mat-label>\n <mat-select formControlName=\"mapper\" #mapperSelect>\n <mat-option>None</mat-option>\n @for (mapper of sampleMappers; track mapper) {\n <mat-option [value]=\"mapper.value\">\n {{mapper.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n @if (adapterSelect.value || mapperSelect.value) {\n <div style=\"display: flex; margin-bottom: 2rem;\">\n <div style=\"flex:1\" class=\"box\">\n <h3>Adapter (Incoming)</h3>\n @if (adapterSelect.value) {\n <div>\n {{ props(adapterSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n <div style=\"flex:1\" class=\"box\">\n <h3>Mapper (Outgoing)</h3>\n @if (mapperSelect.value) {\n <div>\n {{ props(mapperSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n </div>\n }\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>RestPath (/ delimited)</mat-label>\n <input matInput placeholder=\"clients/list\" formControlName=\"path\">\n </mat-form-field>\n </div>\n <div>\n <div formArrayName=\"headers\">\n @for (task of headers.controls; track task; let i = $index) {\n <div [formGroupName]=\"i\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Key</mat-label>\n <input matInput placeholder=\"authentication\" formControlName=\"key\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Value</mat-label>\n <input matInput placeholder=\"sample\" formControlName=\"value\">\n </mat-form-field>\n <div style=\"margin-top: .5rem;\">\n <button mat-icon-button (click)=\"removeHeader(i)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n <button mat-stroked-button (click)=\"addHeader()\" class=\"btn\">Add Header</button>\n </div>\n <div style=\"margin-top: 2rem; display: flex; flex-direction:column; gap: 1rem;\">\n <div>\n <mat-slide-toggle #failedState>Retry on Failed</mat-slide-toggle>\n @if (failedState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem;\" formGroupName=\"retry\">\n <mat-form-field appearance=\"outline\">\n <mat-label>#of Times</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"times\" value=\"3\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Delay Until Next</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"delay\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #pollingState>Polling</mat-slide-toggle>\n @if (pollingState.checked) {\n <div>\n <mat-form-field appearance=\"outline\" style=\"margin-top: 1rem\">\n <mat-label>#of Seconds</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"polling\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n </div>\n </div>\n\n <div style=\"margin-bottom: 1rem; margin-top: 2rem;\">\n @if ((isPending$ | async)) {\n <mat-progress-bar mode=\"indeterminate\"\n ></mat-progress-bar>\n }\n @if (pollingState.checked && !(isPending$ | async)) {\n <mat-progress-bar mode=\"determinate\"\n [value]=\"(this.countdown$ | async)\"\n ></mat-progress-bar>\n }\n </div>\n\n <div style=\"margin-top: 1rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">GET Request</h2>\n <div>\n <button mat-raised-button (click)=\"onGetRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((GET_error$ | async); as get_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ get_error }}</mat-error>\n </div>\n }\n\n @if ((GET$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(GET$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">POST Request</h2>\n <div>\n <button mat-raised-button (click)=\"onCreateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((POST_error$ | async); as post_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ post_error }}</mat-error>\n </div>\n }\n\n @if ((POST$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(POST$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">PUT Request</h2>\n <div>\n <button mat-raised-button (click)=\"onUpdateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((PUT_error$ | async); as put_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ put_error }}</mat-error>\n </div>\n }\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if ((PUT$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(PUT$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">DELETE Request</h2>\n <div>\n <button mat-raised-button (click)=\"onDeleteRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if ((DELETE_error$ | async); as delete_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ delete_error }}</mat-error>\n </div>\n }\n\n @if ((DELETE$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(DELETE$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">Streaming GET Request</h2>\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n {{ streamType }}\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>data_usage</mat-icon>\n </button>\n </div>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item *ngFor=\"let item of streamTypes\" (click)=\"onStreamType(item.id)\">{{ item.value }}</button>\n </mat-menu>\n <button mat-raised-button (click)=\"onStreamRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <!-- <div *ngIf=\"(STREAM_error$ | async) as stream_error\" style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div> -->\n\n <div style=\"margin-top: 1rem;\">\n @if ((STREAM$ | async); as data) {\n <div class=\"container\">\n <table mat-table [dataSource]=\"data\" class=\"mat-elevation-z8\">\n\n <!-- Dynamic columns -->\n <ng-container *ngFor=\"let column of displayedColumns\" [matColumnDef]=\"column\">\n <th mat-header-cell *matHeaderCellDef> {{ column | titlecase }} </th>\n <td mat-cell *matCellDef=\"let element\">\n @if (isObject(element[column]); as objValue) {\n <pre style=\"margin: 0; font-size: 0.8em; white-space: pre-wrap;\">{{ objValue | json }}</pre>\n } @else {\n {{ element[column] }}\n }\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n\n <!-- Debug info -->\n <div style=\"margin-top: 1rem; font-size: 0.8em; color: #666;\">\n Columns: {{ displayedColumns.join(', ') }} | Records: {{ data.length }}\n </div>\n </div>\n }\n </div>\n\n</div>\n\n<div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n</div>\n\n<div style=\"margin-top: 2rem\">\n <!-- Parallel Requests Section -->\n <div>\n <div style=\"display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;\">\n <h2 style=\"flex: 1; margin: 0;\">Parallel Requests</h2>\n <button\n mat-raised-button\n color=\"primary\"\n (click)=\"onExecuteParallelBatch()\"\n [disabled]=\"isParallelLoading\"\n style=\"min-width: 200px;\">\n <mat-icon>sync</mat-icon>\n <span style=\"margin-left: 8px;\">\n {{ isParallelLoading ? 'Loading...' : 'Fetch 5 Users' }}\n </span>\n </button>\n </div>\n\n <!-- Loading Indicator -->\n @if (isParallelLoading) {\n <div style=\"margin: 1rem 0;\">\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n <p style=\"margin-top: 0.5rem; color: #666;\">\n Executing parallel requests (concurrency: 3)...\n </p>\n </div>\n }\n\n <!-- Individual Request States -->\n @if (parallelBatchStates.length > 0) {\n <div style=\"margin: 1rem 0;\">\n <mat-card appearance=\"outlined\">\n <mat-card-content>\n <div style=\"display: grid; gap: 0.5rem;\">\n @for (state of filteredParallelStates; track state.index; let i = $index) {\n <div\n style=\"padding: 0.75rem; border-radius: 4px; border: 1px solid;\n {{ state.isPending ? 'background: #fff3e0; border-color: #ff9800;' :\n state.data ? 'background: #e3f2fd; border-color: #2196f3;' :\n 'background: #ffebee; border-color: #f44336;' }}\">\n @if (state.isPending) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-spinner diameter=\"16\" style=\"margin-right: 0.5rem;\"></mat-spinner>\n <strong>Request {{ state.index + 1 }}:</strong>\n <span>Loading...</span>\n </div>\n <span style=\"color: #ff9800; font-weight: bold;\">Pending</span>\n </div>\n } @else if (state.data) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #2196f3; font-size: 18px;\">check_circle</mat-icon>\n <strong>User #{{ state.data.id }}:</strong>\n <span>{{ state.data.name }}</span>\n </div>\n <span style=\"color: #2196f3; font-weight: bold;\">Complete</span>\n </div>\n } @else {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #f44336; font-size: 18px;\">error</mat-icon>\n <strong>Request {{ state.index + 1 }}:</strong>\n <span>Failed</span>\n </div>\n <span style=\"color: #f44336; font-weight: bold;\">Error</span>\n </div>\n }\n </div>\n }\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n\n <!-- Execution Metrics -->\n @if (parallelExecutionTime !== undefined) {\n <div style=\"background: #f5f5f5; padding: 1rem; border-radius: 4px; margin: 1rem 0;\">\n <div style=\"display: flex; gap: 2rem;\">\n <div>\n <strong>\u23F1Execution Time:</strong>\n <span style=\"margin-left: 0.5rem; color: #4caf50;\">\n {{ formatTime(parallelExecutionTime) }}\n </span>\n </div>\n <div>\n <strong>Mode:</strong>\n <span style=\"margin-left: 0.5rem;\">Parallel</span>\n </div>\n </div>\n </div>\n }\n\n <!-- Error Display -->\n @if (parallelError) {\n <div style=\"margin-top: 1rem;\">\n <mat-error>{{ parallelError }}</mat-error>\n </div>\n }\n </div>\n\n <!-- Sequential Requests Section -->\n <div>\n <div style=\"display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;\">\n <h2 style=\"flex: 1; margin: 0;\">Sequential Requests</h2>\n <button\n mat-raised-button\n color=\"accent\"\n (click)=\"onExecuteSequentialBatch()\"\n [disabled]=\"isSequentialLoading\"\n style=\"min-width: 200px;\">\n <mat-icon>timeline</mat-icon>\n <span style=\"margin-left: 8px;\">\n {{ isSequentialLoading ? 'Loading...' : 'Fetch 5 Todos' }}\n </span>\n </button>\n </div>\n\n <!-- Loading Indicator -->\n @if (isSequentialLoading) {\n <div style=\"margin: 1rem 0;\">\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n <p style=\"margin-top: 0.5rem; color: #666;\">\n Executing sequential requests (one at a time)...\n </p>\n </div>\n }\n\n <!-- Individual Request States -->\n @if (sequentialBatchStates.length > 0) {\n <div style=\"margin: 1rem 0;\">\n <mat-card appearance=\"outlined\">\n <mat-card-content>\n <div style=\"display: grid; gap: 0.5rem;\">\n @for (state of filteredSequentialStates; track state.index; let i = $index) {\n <div\n style=\"padding: 0.75rem; border-radius: 4px; border: 1px solid;\n {{ state.isPending ? 'background: #fff3e0; border-color: #ff9800;' :\n state.data ? 'background: #e3f2fd; border-color: #2196f3;' :\n 'background: #ffebee; border-color: #f44336;' }}\">\n @if (state.isPending) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-spinner diameter=\"16\" style=\"margin-right: 0.5rem;\"></mat-spinner>\n <strong>Request {{ state.index + 1 }}:</strong>\n <span>Loading...</span>\n </div>\n <span style=\"color: #ff9800; font-weight: bold;\">Pending</span>\n </div>\n } @else if (state.data) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #2196f3; font-size: 18px;\">check_circle</mat-icon>\n <strong>Todo #{{ state.data.id }}:</strong>\n <span style=\"flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\">\n {{ state.data.title }}\n </span>\n </div>\n </div>\n } @else {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #f44336; font-size: 18px;\">error</mat-icon>\n <strong>Request {{ state.index + 1 }}:</strong>\n <span>Failed</span>\n </div>\n <span style=\"color: #f44336; font-weight: bold;\">Error</span>\n </div>\n }\n </div>\n }\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n\n <!-- Execution Metrics -->\n @if (sequentialExecutionTime !== undefined) {\n <div style=\"background: #f5f5f5; padding: 1rem; border-radius: 4px; margin: 1rem 0;\">\n <div style=\"display: flex; gap: 2rem;\">\n <div>\n <strong>Execution Time:</strong>\n <span style=\"margin-left: 0.5rem; color: #4caf50;\">\n {{ formatTime(sequentialExecutionTime) }}\n </span>\n </div>\n <div>\n <strong>Mode:</strong>\n <span style=\"margin-left: 0.5rem;\">Sequential</span>\n </div>\n </div>\n </div>\n }\n </div>\n\n <!-- Stream Mode Section -->\n <div>\n <div style=\"display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;\">\n <h2 style=\"flex: 1; margin: 0;\">Stream Mode (Real-time States)</h2>\n <button\n mat-raised-button\n color=\"primary\"\n (click)=\"onExecuteStreamBatch()\"\n [disabled]=\"isStreamLoading\"\n style=\"min-width: 200px;\">\n <mat-icon>auto_awesome</mat-icon>\n <span style=\"margin-left: 8px;\">\n {{ isStreamLoading ? 'Loading...' : 'Fetch 5 Posts' }}\n </span>\n </button>\n </div>\n\n <!-- Loading Indicator -->\n @if (isStreamLoading) {\n <div style=\"margin: 1rem 0;\">\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n <p style=\"margin-top: 0.5rem; color: #666;\">\n Executing parallel requests with real-time state updates...\n </p>\n </div>\n }\n\n <!-- Progress Bar -->\n @if (streamProgress) {\n <div style=\"background: #f5f5f5; padding: 1rem; border-radius: 4px; margin: 1rem 0;\">\n <div style=\"display: flex; gap: 2rem; margin-bottom: 0.5rem;\">\n <div>\n <strong>Execution Time:</strong>\n <span style=\"margin-left: 0.5rem; color: #4caf50;\">\n {{ formatTime(streamExecutionTime || 0) }}\n </span>\n </div>\n <div>\n <strong>Progress:</strong>\n <span style=\"margin-left: 0.5rem;\">\n {{ streamProgress.completed }} / {{ streamProgress.total }} completed\n </span>\n </div>\n <div>\n <strong>Pending:</strong>\n <span style=\"margin-left: 0.5rem;\">\n {{ streamProgress.pending }}\n </span>\n </div>\n <div>\n <strong>Failed:</strong>\n <span style=\"margin-left: 0.5rem;\">\n {{ streamProgress.failed }}\n </span>\n </div>\n </div>\n <mat-progress-bar\n mode=\"determinate\"\n [value]=\"streamProgress.percent\"\n color=\"accent\">\n </mat-progress-bar>\n <p style=\"margin-top: 0.5rem; text-align: right; color: #666;\">\n {{ streamProgress.percent }}%\n </p>\n </div>\n }\n\n <!-- Error Display -->\n @if (streamError) {\n <div style=\"margin-top: 1rem;\">\n <mat-error>{{ streamError }}</mat-error>\n </div>\n }\n\n <!-- Individual Request States -->\n @if (streamBatchStates.length > 0) {\n <div style=\"margin-top: 1rem;\">\n <mat-card appearance=\"outlined\">\n <mat-card-content>\n <div style=\"display: grid; gap: 0.5rem;\">\n @for (state of filteredStreamStates; track state.index; let i = $index) {\n <div\n style=\"padding: 0.75rem; border-radius: 4px; border: 1px solid;\n {{ isPendingState(state) ? 'background: #fff3e0; border-color: #ff9800;' :\n isSuccessState(state) ? 'background: #e3f2fd; border-color: #2196f3;' :\n 'background: #ffebee; border-color: #f44336;' }}\">\n @if (isPendingState(state)) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-spinner diameter=\"16\" style=\"margin-right: 0.5rem;\"></mat-spinner>\n <strong>Request {{ state.index + 1 }}:</strong>\n <span>Loading...</span>\n </div>\n } @else if (isSuccessState(state)) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #2196f3; font-size: 18px;\">check_circle</mat-icon>\n <strong>Post #{{ state.data.id }}:</strong>\n <span style=\"flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\">\n {{ state.data.title }}\n </span>\n </div>\n } @else {\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #f44336; font-size: 18px;\">error</mat-icon>\n <strong>Request {{ state.index + 1 }}:</strong>\n <span>Failed</span>\n <mat-chip style=\"margin-left: auto; font-size: 0.8em; background: #f44336; color: white;\">\n Error\n </mat-chip>\n </div>\n }\n </div>\n }\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n </div>\n\n <!-- Comparison Info -->\n <div style=\"padding: 1rem; background: whitesmoke; border-radius: 4px; border: 1px solid #ccc;\">\n <h4 style=\"margin-top: 0;\">Key Differences</h4>\n <div style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;\">\n <div>\n <strong>Parallel:</strong>\n <ul style=\"margin: 0.5rem 0 0 1rem; padding: 0;\">\n <li>Faster execution (concurrent)</li>\n <li>Multiple requests simultaneously</li>\n <li>Results may complete out of order</li>\n <li>Best for: Independent requests</li>\n </ul>\n </div>\n <div>\n <strong>Sequential:</strong>\n <ul style=\"margin: 0.5rem 0 0 1rem; padding: 0;\">\n <li>Slower execution (one-by-one)</li>\n <li>Requests execute in order</li>\n <li>Results always in request order</li>\n <li>Best for: Dependent requests</li>\n </ul>\n </div>\n </div>\n </div>\n</div>\n\n</div>\n", styles: [".btn{min-width:120px}.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;background-color:#f5f5f5}.container{height:400px;overflow:auto}.box{padding:10px;border:1px solid #ccc}.batch-demo-section{margin:2rem 0;padding:1.5rem;border:1px solid #e0e0e0;border-radius:8px;background:#fafafa}.batch-result-item{padding:.75rem;border-radius:4px;margin-bottom:.5rem;display:flex;align-items:center;gap:.5rem}.batch-result-item.success{background:#e8f5e9;border-left:3px solid #4caf50}.batch-result-item.error{background:#ffebee;border-left:3px solid #f44336}.execution-metrics{display:flex;gap:2rem;padding:1rem;background:#f5f5f5;border-radius:4px;margin:1rem 0}.execution-metrics>div{display:flex;align-items:center;gap:.5rem}.comparison-box{padding:1rem;background:#fff3e0;border-radius:4px;margin-top:2rem}.comparison-box h4{margin-top:0;margin-bottom:1rem}.comparison-box .comparison-grid{display:grid;grid-template-columns:1fr 1fr;gap:1rem}@media(max-width:768px){.comparison-box .comparison-grid{grid-template-columns:1fr}}.loading-indicator{margin:1rem 0}.result-card{margin-top:1rem}.result-card mat-card-content{padding:1rem}\n"], dependencies: [{ kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2$1.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: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i2$1.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "directive", type: i2$1.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i3.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "component", type: i5.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"], 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: "component", type: i3$1.MatChip, selector: "mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]", inputs: ["role", "id", "aria-label", "aria-description", "value", "color", "removable", "highlighted", "disableRipple", "disabled"], outputs: ["removed", "destroyed"], exportAs: ["matChip"] }, { kind: "component", type: i7.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: i7.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i7.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i9.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i9.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i9.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i9.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i9.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i9.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i9.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i9.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i9.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i8$1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: i2.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }, { kind: "component", type: i11.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "component", type: i12.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i13.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i16.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i16.MatCardContent, selector: "mat-card-content" }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }, { kind: "pipe", type: i1$1.TitleCasePipe, name: "titlecase" }] }); }
8970
- }
8971
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerDemoComponent, decorators: [{
8972
- type: Component,
8973
- args: [{ selector: 'app-request-manager-demo', providers: [HTTPManagerService], standalone: false, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n HTTP Request Manager\n </h2>\n\n <div [formGroup]=\"requestForm\" style=\"margin-top: 2rem;\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Adapter (Model)</mat-label>\n <mat-select formControlName=\"adapter\" #adapterSelect>\n <mat-option>None</mat-option>\n @for (adapter of sampleAdaptors; track adapter) {\n <mat-option [value]=\"adapter.value\">\n {{adapter.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Mapper (Model)</mat-label>\n <mat-select formControlName=\"mapper\" #mapperSelect>\n <mat-option>None</mat-option>\n @for (mapper of sampleMappers; track mapper) {\n <mat-option [value]=\"mapper.value\">\n {{mapper.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n @if (adapterSelect.value || mapperSelect.value) {\n <div style=\"display: flex; margin-bottom: 2rem;\">\n <div style=\"flex:1\" class=\"box\">\n <h3>Adapter (Incoming)</h3>\n @if (adapterSelect.value) {\n <div>\n {{ props(adapterSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n <div style=\"flex:1\" class=\"box\">\n <h3>Mapper (Outgoing)</h3>\n @if (mapperSelect.value) {\n <div>\n {{ props(mapperSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n </div>\n }\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>RestPath (/ delimited)</mat-label>\n <input matInput placeholder=\"clients/list\" formControlName=\"path\">\n </mat-form-field>\n </div>\n <div>\n <div formArrayName=\"headers\">\n @for (task of headers.controls; track task; let i = $index) {\n <div [formGroupName]=\"i\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Key</mat-label>\n <input matInput placeholder=\"authentication\" formControlName=\"key\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Value</mat-label>\n <input matInput placeholder=\"sample\" formControlName=\"value\">\n </mat-form-field>\n <div style=\"margin-top: .5rem;\">\n <button mat-icon-button (click)=\"removeHeader(i)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n <button mat-stroked-button (click)=\"addHeader()\" class=\"btn\">Add Header</button>\n </div>\n <div style=\"margin-top: 2rem; display: flex; flex-direction:column; gap: 1rem;\">\n <div>\n <mat-slide-toggle #failedState>Retry on Failed</mat-slide-toggle>\n @if (failedState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem;\" formGroupName=\"retry\">\n <mat-form-field appearance=\"outline\">\n <mat-label>#of Times</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"times\" value=\"3\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Delay Until Next</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"delay\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #pollingState>Polling</mat-slide-toggle>\n @if (pollingState.checked) {\n <div>\n <mat-form-field appearance=\"outline\" style=\"margin-top: 1rem\">\n <mat-label>#of Seconds</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"polling\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n </div>\n </div>\n\n <div style=\"margin-bottom: 1rem; margin-top: 2rem;\">\n @if ((isPending$ | async)) {\n <mat-progress-bar mode=\"indeterminate\"\n ></mat-progress-bar>\n }\n @if (pollingState.checked && !(isPending$ | async)) {\n <mat-progress-bar mode=\"determinate\"\n [value]=\"(this.countdown$ | async)\"\n ></mat-progress-bar>\n }\n </div>\n\n <div style=\"margin-top: 1rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">GET Request</h2>\n <div>\n <button mat-raised-button (click)=\"onGetRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((GET_error$ | async); as get_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ get_error }}</mat-error>\n </div>\n }\n\n @if ((GET$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(GET$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">POST Request</h2>\n <div>\n <button mat-raised-button (click)=\"onCreateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((POST_error$ | async); as post_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ post_error }}</mat-error>\n </div>\n }\n\n @if ((POST$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(POST$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">PUT Request</h2>\n <div>\n <button mat-raised-button (click)=\"onUpdateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((PUT_error$ | async); as put_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ put_error }}</mat-error>\n </div>\n }\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if ((PUT$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(PUT$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">DELETE Request</h2>\n <div>\n <button mat-raised-button (click)=\"onDeleteRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if ((DELETE_error$ | async); as delete_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ delete_error }}</mat-error>\n </div>\n }\n\n @if ((DELETE$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(DELETE$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">Streaming GET Request</h2>\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n {{ streamType }}\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>data_usage</mat-icon>\n </button>\n </div>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item *ngFor=\"let item of streamTypes\" (click)=\"onStreamType(item.id)\">{{ item.value }}</button>\n </mat-menu>\n <button mat-raised-button (click)=\"onStreamRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <!-- <div *ngIf=\"(STREAM_error$ | async) as stream_error\" style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div> -->\n\n <div style=\"margin-top: 1rem;\">\n @if ((STREAM$ | async); as data) {\n <div class=\"container\">\n <table mat-table [dataSource]=\"data\" class=\"mat-elevation-z8\">\n\n <!-- Dynamic columns -->\n <ng-container *ngFor=\"let column of displayedColumns\" [matColumnDef]=\"column\">\n <th mat-header-cell *matHeaderCellDef> {{ column | titlecase }} </th>\n <td mat-cell *matCellDef=\"let element\">\n @if (isObject(element[column]); as objValue) {\n <pre style=\"margin: 0; font-size: 0.8em; white-space: pre-wrap;\">{{ objValue | json }}</pre>\n } @else {\n {{ element[column] }}\n }\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n\n <!-- Debug info -->\n <div style=\"margin-top: 1rem; font-size: 0.8em; color: #666;\">\n Columns: {{ displayedColumns.join(', ') }} | Records: {{ data.length }}\n </div>\n </div>\n }\n </div>\n\n</div>\n\n<div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n</div>\n\n<div style=\"margin-top: 2rem\">\n <!-- Parallel Requests Section -->\n <div>\n <div style=\"display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;\">\n <h2 style=\"flex: 1; margin: 0;\">Parallel Requests</h2>\n <button\n mat-raised-button\n color=\"primary\"\n (click)=\"onExecuteParallelBatch()\"\n [disabled]=\"isParallelLoading\"\n style=\"min-width: 200px;\">\n <mat-icon>sync</mat-icon>\n <span style=\"margin-left: 8px;\">\n {{ isParallelLoading ? 'Loading...' : 'Fetch 5 Users' }}\n </span>\n </button>\n </div>\n\n <!-- Loading Indicator -->\n @if (isParallelLoading) {\n <div style=\"margin: 1rem 0;\">\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n <p style=\"margin-top: 0.5rem; color: #666;\">\n Executing parallel requests (concurrency: 3)...\n </p>\n </div>\n }\n\n <!-- Individual Request States -->\n @if (parallelBatchStates.length > 0) {\n <div style=\"margin: 1rem 0;\">\n <mat-card appearance=\"outlined\">\n <mat-card-content>\n <div style=\"display: grid; gap: 0.5rem;\">\n @for (state of filteredParallelStates; track state.index; let i = $index) {\n <div\n style=\"padding: 0.75rem; border-radius: 4px; border: 1px solid;\n {{ state.isPending ? 'background: #fff3e0; border-color: #ff9800;' :\n state.data ? 'background: #e3f2fd; border-color: #2196f3;' :\n 'background: #ffebee; border-color: #f44336;' }}\">\n @if (state.isPending) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-spinner diameter=\"16\" style=\"margin-right: 0.5rem;\"></mat-spinner>\n <strong>Request {{ state.index + 1 }}:</strong>\n <span>Loading...</span>\n </div>\n <span style=\"color: #ff9800; font-weight: bold;\">Pending</span>\n </div>\n } @else if (state.data) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #2196f3; font-size: 18px;\">check_circle</mat-icon>\n <strong>User #{{ state.data.id }}:</strong>\n <span>{{ state.data.name }}</span>\n </div>\n <span style=\"color: #2196f3; font-weight: bold;\">Complete</span>\n </div>\n } @else {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #f44336; font-size: 18px;\">error</mat-icon>\n <strong>Request {{ state.index + 1 }}:</strong>\n <span>Failed</span>\n </div>\n <span style=\"color: #f44336; font-weight: bold;\">Error</span>\n </div>\n }\n </div>\n }\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n\n <!-- Execution Metrics -->\n @if (parallelExecutionTime !== undefined) {\n <div style=\"background: #f5f5f5; padding: 1rem; border-radius: 4px; margin: 1rem 0;\">\n <div style=\"display: flex; gap: 2rem;\">\n <div>\n <strong>\u23F1Execution Time:</strong>\n <span style=\"margin-left: 0.5rem; color: #4caf50;\">\n {{ formatTime(parallelExecutionTime) }}\n </span>\n </div>\n <div>\n <strong>Mode:</strong>\n <span style=\"margin-left: 0.5rem;\">Parallel</span>\n </div>\n </div>\n </div>\n }\n\n <!-- Error Display -->\n @if (parallelError) {\n <div style=\"margin-top: 1rem;\">\n <mat-error>{{ parallelError }}</mat-error>\n </div>\n }\n </div>\n\n <!-- Sequential Requests Section -->\n <div>\n <div style=\"display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;\">\n <h2 style=\"flex: 1; margin: 0;\">Sequential Requests</h2>\n <button\n mat-raised-button\n color=\"accent\"\n (click)=\"onExecuteSequentialBatch()\"\n [disabled]=\"isSequentialLoading\"\n style=\"min-width: 200px;\">\n <mat-icon>timeline</mat-icon>\n <span style=\"margin-left: 8px;\">\n {{ isSequentialLoading ? 'Loading...' : 'Fetch 5 Todos' }}\n </span>\n </button>\n </div>\n\n <!-- Loading Indicator -->\n @if (isSequentialLoading) {\n <div style=\"margin: 1rem 0;\">\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n <p style=\"margin-top: 0.5rem; color: #666;\">\n Executing sequential requests (one at a time)...\n </p>\n </div>\n }\n\n <!-- Individual Request States -->\n @if (sequentialBatchStates.length > 0) {\n <div style=\"margin: 1rem 0;\">\n <mat-card appearance=\"outlined\">\n <mat-card-content>\n <div style=\"display: grid; gap: 0.5rem;\">\n @for (state of filteredSequentialStates; track state.index; let i = $index) {\n <div\n style=\"padding: 0.75rem; border-radius: 4px; border: 1px solid;\n {{ state.isPending ? 'background: #fff3e0; border-color: #ff9800;' :\n state.data ? 'background: #e3f2fd; border-color: #2196f3;' :\n 'background: #ffebee; border-color: #f44336;' }}\">\n @if (state.isPending) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-spinner diameter=\"16\" style=\"margin-right: 0.5rem;\"></mat-spinner>\n <strong>Request {{ state.index + 1 }}:</strong>\n <span>Loading...</span>\n </div>\n <span style=\"color: #ff9800; font-weight: bold;\">Pending</span>\n </div>\n } @else if (state.data) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #2196f3; font-size: 18px;\">check_circle</mat-icon>\n <strong>Todo #{{ state.data.id }}:</strong>\n <span style=\"flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\">\n {{ state.data.title }}\n </span>\n </div>\n </div>\n } @else {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #f44336; font-size: 18px;\">error</mat-icon>\n <strong>Request {{ state.index + 1 }}:</strong>\n <span>Failed</span>\n </div>\n <span style=\"color: #f44336; font-weight: bold;\">Error</span>\n </div>\n }\n </div>\n }\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n\n <!-- Execution Metrics -->\n @if (sequentialExecutionTime !== undefined) {\n <div style=\"background: #f5f5f5; padding: 1rem; border-radius: 4px; margin: 1rem 0;\">\n <div style=\"display: flex; gap: 2rem;\">\n <div>\n <strong>Execution Time:</strong>\n <span style=\"margin-left: 0.5rem; color: #4caf50;\">\n {{ formatTime(sequentialExecutionTime) }}\n </span>\n </div>\n <div>\n <strong>Mode:</strong>\n <span style=\"margin-left: 0.5rem;\">Sequential</span>\n </div>\n </div>\n </div>\n }\n </div>\n\n <!-- Stream Mode Section -->\n <div>\n <div style=\"display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;\">\n <h2 style=\"flex: 1; margin: 0;\">Stream Mode (Real-time States)</h2>\n <button\n mat-raised-button\n color=\"primary\"\n (click)=\"onExecuteStreamBatch()\"\n [disabled]=\"isStreamLoading\"\n style=\"min-width: 200px;\">\n <mat-icon>auto_awesome</mat-icon>\n <span style=\"margin-left: 8px;\">\n {{ isStreamLoading ? 'Loading...' : 'Fetch 5 Posts' }}\n </span>\n </button>\n </div>\n\n <!-- Loading Indicator -->\n @if (isStreamLoading) {\n <div style=\"margin: 1rem 0;\">\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n <p style=\"margin-top: 0.5rem; color: #666;\">\n Executing parallel requests with real-time state updates...\n </p>\n </div>\n }\n\n <!-- Progress Bar -->\n @if (streamProgress) {\n <div style=\"background: #f5f5f5; padding: 1rem; border-radius: 4px; margin: 1rem 0;\">\n <div style=\"display: flex; gap: 2rem; margin-bottom: 0.5rem;\">\n <div>\n <strong>Execution Time:</strong>\n <span style=\"margin-left: 0.5rem; color: #4caf50;\">\n {{ formatTime(streamExecutionTime || 0) }}\n </span>\n </div>\n <div>\n <strong>Progress:</strong>\n <span style=\"margin-left: 0.5rem;\">\n {{ streamProgress.completed }} / {{ streamProgress.total }} completed\n </span>\n </div>\n <div>\n <strong>Pending:</strong>\n <span style=\"margin-left: 0.5rem;\">\n {{ streamProgress.pending }}\n </span>\n </div>\n <div>\n <strong>Failed:</strong>\n <span style=\"margin-left: 0.5rem;\">\n {{ streamProgress.failed }}\n </span>\n </div>\n </div>\n <mat-progress-bar\n mode=\"determinate\"\n [value]=\"streamProgress.percent\"\n color=\"accent\">\n </mat-progress-bar>\n <p style=\"margin-top: 0.5rem; text-align: right; color: #666;\">\n {{ streamProgress.percent }}%\n </p>\n </div>\n }\n\n <!-- Error Display -->\n @if (streamError) {\n <div style=\"margin-top: 1rem;\">\n <mat-error>{{ streamError }}</mat-error>\n </div>\n }\n\n <!-- Individual Request States -->\n @if (streamBatchStates.length > 0) {\n <div style=\"margin-top: 1rem;\">\n <mat-card appearance=\"outlined\">\n <mat-card-content>\n <div style=\"display: grid; gap: 0.5rem;\">\n @for (state of filteredStreamStates; track state.index; let i = $index) {\n <div\n style=\"padding: 0.75rem; border-radius: 4px; border: 1px solid;\n {{ isPendingState(state) ? 'background: #fff3e0; border-color: #ff9800;' :\n isSuccessState(state) ? 'background: #e3f2fd; border-color: #2196f3;' :\n 'background: #ffebee; border-color: #f44336;' }}\">\n @if (isPendingState(state)) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-spinner diameter=\"16\" style=\"margin-right: 0.5rem;\"></mat-spinner>\n <strong>Request {{ state.index + 1 }}:</strong>\n <span>Loading...</span>\n </div>\n } @else if (isSuccessState(state)) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #2196f3; font-size: 18px;\">check_circle</mat-icon>\n <strong>Post #{{ state.data.id }}:</strong>\n <span style=\"flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\">\n {{ state.data.title }}\n </span>\n </div>\n } @else {\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #f44336; font-size: 18px;\">error</mat-icon>\n <strong>Request {{ state.index + 1 }}:</strong>\n <span>Failed</span>\n <mat-chip style=\"margin-left: auto; font-size: 0.8em; background: #f44336; color: white;\">\n Error\n </mat-chip>\n </div>\n }\n </div>\n }\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n </div>\n\n <!-- Comparison Info -->\n <div style=\"padding: 1rem; background: whitesmoke; border-radius: 4px; border: 1px solid #ccc;\">\n <h4 style=\"margin-top: 0;\">Key Differences</h4>\n <div style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;\">\n <div>\n <strong>Parallel:</strong>\n <ul style=\"margin: 0.5rem 0 0 1rem; padding: 0;\">\n <li>Faster execution (concurrent)</li>\n <li>Multiple requests simultaneously</li>\n <li>Results may complete out of order</li>\n <li>Best for: Independent requests</li>\n </ul>\n </div>\n <div>\n <strong>Sequential:</strong>\n <ul style=\"margin: 0.5rem 0 0 1rem; padding: 0;\">\n <li>Slower execution (one-by-one)</li>\n <li>Requests execute in order</li>\n <li>Results always in request order</li>\n <li>Best for: Dependent requests</li>\n </ul>\n </div>\n </div>\n </div>\n</div>\n\n</div>\n", styles: [".btn{min-width:120px}.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;background-color:#f5f5f5}.container{height:400px;overflow:auto}.box{padding:10px;border:1px solid #ccc}.batch-demo-section{margin:2rem 0;padding:1.5rem;border:1px solid #e0e0e0;border-radius:8px;background:#fafafa}.batch-result-item{padding:.75rem;border-radius:4px;margin-bottom:.5rem;display:flex;align-items:center;gap:.5rem}.batch-result-item.success{background:#e8f5e9;border-left:3px solid #4caf50}.batch-result-item.error{background:#ffebee;border-left:3px solid #f44336}.execution-metrics{display:flex;gap:2rem;padding:1rem;background:#f5f5f5;border-radius:4px;margin:1rem 0}.execution-metrics>div{display:flex;align-items:center;gap:.5rem}.comparison-box{padding:1rem;background:#fff3e0;border-radius:4px;margin-top:2rem}.comparison-box h4{margin-top:0;margin-bottom:1rem}.comparison-box .comparison-grid{display:grid;grid-template-columns:1fr 1fr;gap:1rem}@media(max-width:768px){.comparison-box .comparison-grid{grid-template-columns:1fr}}.loading-indicator{margin:1rem 0}.result-card{margin-top:1rem}.result-card mat-card-content{padding:1rem}\n"] }]
8974
- }], ctorParameters: () => [], propDecorators: { server: [{
8975
- type: Input
8976
- }], adapter: [{
8977
- type: Input
8978
- }], mapper: [{
8979
- type: Input
8980
- }], failedState: [{
8981
- type: ViewChild,
8982
- args: ["failedState", { static: true }]
8983
- }], pollingState: [{
8984
- type: ViewChild,
8985
- args: ["pollingState", { static: true }]
8986
- }] } });
8987
-
8988
- class LocalStorageDemoComponent {
8989
- get type() {
8990
- return (this.typeControl.value) ? +this.typeControl.value : 0;
8991
- }
8992
- get isValid() {
8993
- return this.newStoreForm.valid;
8994
- }
8995
- get isValidData() {
8996
- return this.storageForm.valid;
8997
- }
8998
- constructor(configOptions) {
8999
- this.configOptions = configOptions;
9000
- this.fb = inject(FormBuilder);
9001
- this.utils = inject(UtilsService);
9002
- this.type$ = new BehaviorSubject(StorageType.GLOBAL);
9003
- this.typeControl = this.fb.control(StorageType.GLOBAL.toString());
9004
- this.localStorageManagerService = inject(LocalStorageManagerService);
9005
- this.settings$ = this.localStorageManagerService.settings$;
9006
- this.setting$ = (store) => this.localStorageManagerService.setting$(store);
9007
- this.storageForm = this.fb.group({
9008
- store: this.fb.control(null),
9009
- type: 'local',
9010
- settingType: 'local',
9011
- encrypted: false,
9012
- data: this.fb.control('', Validators.required),
9013
- });
9014
- this.newStoreForm = this.fb.group({
9015
- name: this.fb.control(null, Validators.required),
9016
- storage: 'local',
9017
- encrypted: false,
9018
- data: this.fb.control('', Validators.required),
9019
- expiresIn: this.fb.control('0')
9020
- });
9021
- this.storeData$ = this.storageForm.get('store')?.valueChanges
9022
- .pipe(switchMap((data) => {
9023
- return data
9024
- ? this.localStorageManagerService.store$(data.name)
9025
- : of('');
9026
- }), tap(data => {
9027
- this.storageForm.get('data')?.patchValue(data, { emitEvent: false });
9028
- }));
9029
- this.expiresIn = (epoch) => this.utils.expiresIn(epoch);
9030
- this.isValidJSON = (str) => {
9031
- try {
9032
- JSON.parse(str);
9033
- return true;
9034
- }
9035
- catch (e) {
9036
- return false;
9037
- }
9038
- };
9039
- this.displayedColumns = ['name', 'id', 'encrypted', 'expires', "option"];
9040
- this.filterData = (values) => {
9041
- if (!values)
9042
- return [];
9043
- return values.filter((item) => item.options.storage === +this.type);
9044
- };
9045
- this.create = false;
9046
- }
9047
- ngOnInit() {
9048
- this.storeProps = this.configOptions?.LocalStorageOptions;
9049
- this.options = this.storeProps?.options;
9050
- if (this.options?.storage) {
9051
- this.typeControl.patchValue(this.options.storage.toString());
9052
- this.typeControl.disable();
9053
- }
9054
- else {
9055
- this.typeControl.enable();
9056
- }
9057
- if (this.options?.expiresIn) {
9058
- this.newStoreForm.get('expiresIn')?.patchValue(this.options.expiresIn);
9059
- this.newStoreForm.get('expiresIn')?.disable();
9060
- }
9061
- else {
9062
- this.newStoreForm.get('expiresIn')?.enable();
9063
- }
9064
- if (this.options?.encrypted) {
9065
- this.newStoreForm.get('encrypted')?.patchValue(this.options.encrypted);
9066
- this.newStoreForm.get('encrypted')?.disable();
9067
- }
9068
- else {
9069
- this.newStoreForm.get('encrypted')?.enable();
9070
- }
9071
- }
9072
- onCreateStore() {
9073
- if (!this.isValid)
9074
- return;
9075
- const store = this.newStoreForm.value;
9076
- if (!store.name || store.name === '')
9077
- return;
9078
- const options = { storage: this.type, encrypted: store.encrypted, expiresIn: store.expiresIn };
9079
- this.localStorageManagerService.createStore({
9080
- name: store.name,
9081
- data: store.data,
9082
- options: SettingOptions.adapt(options)
9083
- });
9084
- this.newStoreForm.reset();
9085
- this.create = false;
9086
- }
9087
- onUpdateStore(store) {
9088
- if (!this.storageForm.valid)
9089
- return;
9090
- const storeData = this.storageForm.value;
9091
- const data = JSON.parse(storeData.data || '');
9092
- const type = (storeData.type === 'local') ? StorageType.GLOBAL : StorageType.SESSION;
9093
- this.localStorageManagerService.updateStore({
9094
- name: store.name,
9095
- data
9096
- });
9097
- }
9098
- onSelectedRow(store) {
9099
- this.store = store;
9100
- this.data$ = this.localStorageManagerService.store$(store.name).pipe(map(item => JSON.stringify(item)));
9101
- this.create = false;
9102
- }
9103
- onCreate() {
9104
- this.onCancel();
9105
- this.create = true;
9106
- }
9107
- onDelete(store) {
9108
- this.localStorageManagerService.deleteStore({
9109
- name: store.name,
9110
- });
9111
- this.onCancel();
9112
- }
9113
- onCancel() {
9114
- this.data$ = EMPTY;
9115
- this.store = null;
9116
- this.create = false;
9117
- }
9118
- onUpdate(store, data) {
9119
- this.localStorageManagerService.updateStore({
9120
- name: store.name,
9121
- data: JSON.parse(data)
9122
- });
9123
- this.onCancel();
9124
- }
9125
- onReset() {
9126
- this.localStorageManagerService.resetStore();
9127
- }
9128
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LocalStorageDemoComponent, deps: [{ token: CONFIG_SETTINGS_TOKEN }], target: i0.ɵɵFactoryTarget.Component }); }
9129
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: LocalStorageDemoComponent, selector: "app-local-storage-demo", ngImport: i0, template: "<div style=\"margin: 2rem;\">\n\n <div style=\"display: flex; gap: 1rem\">\n <h2 style=\"padding-top: .5rem; display: flex;\">\n <div style=\"padding-top: .5rem;\">Local Storage Manager</div>\n <div>\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>info</mat-icon>\n </button>\n </div>\n </h2>\n <span style=\"flex:1\"></span>\n <div style=\"padding-top: .25rem;\">\n <mat-button-toggle-group name=\"storage\" [formControl]=\"typeControl\" (change)=\"onCancel()\">\n <mat-button-toggle value=\"0\">Local Storage</mat-button-toggle>\n <mat-button-toggle value=\"1\">Session Storage</mat-button-toggle>\n </mat-button-toggle-group>\n </div>\n <div style=\"margin-top: .25rem;\">\n <button mat-icon-button (click)=\"onCreate()\">\n <mat-icon>add</mat-icon>\n </button>\n </div>\n </div>\n\n <ng-container *ngIf=\"storeProps?.storageName || storeProps?.storageSettingsName\">\n <div style=\"display: flex; gap: .5rem; flex-direction: column; border: gray solid thin; background-color: whitesmoke; padding: 1rem;\"\n >\n <div style=\"display: flex;\">\n <div *ngIf=\"storeProps?.storageSettingsName\" style=\"flex:1\">\n Database: <b>{{ storeProps?.storageSettingsName }}</b>\n </div>\n <div *ngIf=\"storeProps?.storageName\" style=\"flex:1\">\n Data: <b>{{ storeProps?.storageName }}</b>\n </div>\n </div>\n </div>\n </ng-container>\n\n <div style=\"margin-top: 2rem;\" [formGroup]=\"newStoreForm\" *ngIf=\"create; else LIST\">\n\n <h2>Create Store</h2>\n\n <div style=\"display: flex; gap: 1rem;\">\n <div style=\"flex:1\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Store Name</mat-label>\n <input matInput placeholder=\"sample\" formControlName=\"name\">\n </mat-form-field>\n </div>\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Expires In...</mat-label>\n <mat-select formControlName=\"expiresIn\">\n <mat-option value=\"0\">None</mat-option>\n <mat-option value=\"30s\">30 Seconds</mat-option>\n <mat-option value=\"1mn\">1 Minute</mat-option>\n <mat-option value=\"1hr\">1 Hour</mat-option>\n <mat-option value=\"1d\">1 Day</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div style=\"margin-top: 1rem;\">\n <mat-slide-toggle formControlName=\"encrypted\">Encrypted</mat-slide-toggle>\n </div>\n </div>\n\n <div style=\"display: flex;\">\n <mat-form-field appearance=\"outline\" style=\"flex: 1;\">\n <mat-label>Data (Must be an Object or Array of any)</mat-label>\n <textarea matInput placeholder=\"[]\" formControlName=\"data\" #json></textarea>\n </mat-form-field>\n </div>\n <mat-error *ngIf=\"!isValidJSON(json.value)\">Not Valid Data</mat-error>\n\n <div style=\"display: flex;\">\n <span style=\"flex:1\"></span>\n <button mat-stroked-button (click)=\"onCreateStore()\" [disabled]=\"!(isValid && isValidJSON(json.value))\">\n Save Store\n </button>\n </div>\n\n </div>\n\n <ng-template #LIST>\n\n <div style=\"margin-top: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n{{ settings$ | async | json }}\n <div *ngIf=\"filterData(settings$ | async) as data\">\n <ng-container *ngIf=\"data.length > 0; else NO_DATA\">\n <table mat-table [dataSource]=\"data\">\n <ng-container matColumnDef=\"name\">\n <th mat-header-cell *matHeaderCellDef> Store </th>\n <td mat-cell *matCellDef=\"let element\" style=\"font-weight: bold; text-transform: uppercase;\"> {{element.name}} </td>\n </ng-container>\n\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef> ID </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.id}} </td>\n </ng-container>\n\n <ng-container matColumnDef=\"encrypted\">\n <th mat-header-cell *matHeaderCellDef style=\"text-align: center;\"> Encrypted </th>\n <td mat-cell *matCellDef=\"let element\" style=\"text-align: center;\">\n <ng-container *ngIf=\"element.options.encrypted; else NO\">YES</ng-container>\n <ng-template #NO><span style=\"color:gray\">NO</span></ng-template>\n </td>\n </ng-container>\n\n <ng-container matColumnDef=\"expires\">\n <th mat-header-cell *matHeaderCellDef style=\"text-align: center;\"> Expires </th>\n <td mat-cell *matCellDef=\"let element\" style=\"text-align: center;\">\n <ng-container *ngIf=\"element.options.expires !== 0; else NO_DATA\">\n {{expiresIn(element.options.expires)}}\n </ng-container>\n <ng-template #NO_DATA>\n \u221E\n </ng-template>\n </td>\n </ng-container>\n\n <ng-container matColumnDef=\"option\">\n <th mat-header-cell *matHeaderCellDef></th>\n <td mat-cell *matCellDef=\"let element\" style=\"padding-right: 0;\">\n <div style=\"display: flex;justify-content: flex-end;\">\n <button mat-icon-button color=\"warn\" (click)=\"onDelete(element); $event.stopPropagation()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\" (click)=\"onSelectedRow(row)\"></tr>\n </table>\n </ng-container>\n\n <ng-template #NO_DATA>\n <h3 style=\"margin-top: 1rem;\">No Data</h3>\n </ng-template>\n\n <div *ngIf=\"(data$ | async) as data\" style=\"margin-top: 2rem;\">\n <div style=\"margin-bottom: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <h3>STORE: <span style=\"font-weight: bold;\">{{ store.name | uppercase }}</span></h3>\n <h3>SETTINGS: {{ setting$(store.name) | async | json }}</h3>\n <div style=\"display: flex;\">\n <mat-form-field appearance=\"outline\" style=\"flex: 1;\">\n <mat-label>Data (Must be an Object or Array of any)</mat-label>\n <textarea matInput placeholder=\"[]\" #json [value]=\"data\"></textarea>\n </mat-form-field>\n </div>\n <mat-error *ngIf=\"!isValidJSON(json.value)\">Not Valid Data</mat-error>\n\n <div style=\"display: flex; gap: .5rem;\">\n <span style=\"flex:1\"></span>\n <button mat-stroked-button (click)=\"onCancel()\">Cancel</button>\n <button mat-stroked-button (click)=\"onUpdate(store, json.value)\" [disabled]=\"!(isValidJSON(json.value))\">Update</button>\n </div>\n </div>\n </div>\n\n </ng-template>\n\n <div style=\"margin-top: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 1rem;\">\n <button mat-stroked-button (click)=\"onReset()\">Delete All Stores</button>\n </div>\n\n</div>\n\n\n<mat-menu #menu=\"matMenu\">\n <div style=\"padding: 1rem;\">\n <p>Please note that the LocalStorage (encryption) and management of data is dependant on initializing the APP_ID</p>\n <p>Must provide a value for <b>APP_ID</b> in AppModule->Providers</p>\n <mat-divider></mat-divider>\n <div style=\"font-size: smaller; margin-top: .5rem;\">\n <p>Example: UUID (self.crypto.randomUUID() to generate)</p>\n <div style=\"background-color: whitesmoke; padding: 1rem;\">\n &#123;<br>\n provide: APP_ID,<br>\n useValue: \"056991ac-3537-43ab-b5b9-83edf6554eff\",<br>\n &#125;\n </div>\n </div>\n </div>\n</mat-menu>\n", styles: [".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}.demo-row-is-clicked{font-weight:700}.mat-mdc-menu-panel.mat-mdc-menu-panel{max-width:280px!important}\n"], dependencies: [{ kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2$1.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: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i3.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "component", type: i5.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"], 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: "component", type: i7.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "directive", type: i7.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i9.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i9.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i9.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i9.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i9.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i9.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i9.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i9.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i9.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "directive", type: i10.MatButtonToggleGroup, selector: "mat-button-toggle-group", inputs: ["appearance", "name", "vertical", "value", "multiple", "disabled", "disabledInteractive", "hideSingleSelectionIndicator", "hideMultipleSelectionIndicator"], outputs: ["valueChange", "change"], exportAs: ["matButtonToggleGroup"] }, { kind: "component", type: i10.MatButtonToggle, selector: "mat-button-toggle", inputs: ["aria-label", "aria-labelledby", "id", "name", "value", "tabIndex", "disableRipple", "appearance", "checked", "disabled", "disabledInteractive"], outputs: ["change"], exportAs: ["matButtonToggle"] }, { kind: "component", type: i11.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "component", type: i12.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i13.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.UpperCasePipe, name: "uppercase" }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }], encapsulation: i0.ViewEncapsulation.None }); }
9130
- }
9131
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LocalStorageDemoComponent, decorators: [{
9132
- type: Component,
9133
- args: [{ selector: 'app-local-storage-demo', encapsulation: ViewEncapsulation.None, standalone: false, template: "<div style=\"margin: 2rem;\">\n\n <div style=\"display: flex; gap: 1rem\">\n <h2 style=\"padding-top: .5rem; display: flex;\">\n <div style=\"padding-top: .5rem;\">Local Storage Manager</div>\n <div>\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>info</mat-icon>\n </button>\n </div>\n </h2>\n <span style=\"flex:1\"></span>\n <div style=\"padding-top: .25rem;\">\n <mat-button-toggle-group name=\"storage\" [formControl]=\"typeControl\" (change)=\"onCancel()\">\n <mat-button-toggle value=\"0\">Local Storage</mat-button-toggle>\n <mat-button-toggle value=\"1\">Session Storage</mat-button-toggle>\n </mat-button-toggle-group>\n </div>\n <div style=\"margin-top: .25rem;\">\n <button mat-icon-button (click)=\"onCreate()\">\n <mat-icon>add</mat-icon>\n </button>\n </div>\n </div>\n\n <ng-container *ngIf=\"storeProps?.storageName || storeProps?.storageSettingsName\">\n <div style=\"display: flex; gap: .5rem; flex-direction: column; border: gray solid thin; background-color: whitesmoke; padding: 1rem;\"\n >\n <div style=\"display: flex;\">\n <div *ngIf=\"storeProps?.storageSettingsName\" style=\"flex:1\">\n Database: <b>{{ storeProps?.storageSettingsName }}</b>\n </div>\n <div *ngIf=\"storeProps?.storageName\" style=\"flex:1\">\n Data: <b>{{ storeProps?.storageName }}</b>\n </div>\n </div>\n </div>\n </ng-container>\n\n <div style=\"margin-top: 2rem;\" [formGroup]=\"newStoreForm\" *ngIf=\"create; else LIST\">\n\n <h2>Create Store</h2>\n\n <div style=\"display: flex; gap: 1rem;\">\n <div style=\"flex:1\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Store Name</mat-label>\n <input matInput placeholder=\"sample\" formControlName=\"name\">\n </mat-form-field>\n </div>\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Expires In...</mat-label>\n <mat-select formControlName=\"expiresIn\">\n <mat-option value=\"0\">None</mat-option>\n <mat-option value=\"30s\">30 Seconds</mat-option>\n <mat-option value=\"1mn\">1 Minute</mat-option>\n <mat-option value=\"1hr\">1 Hour</mat-option>\n <mat-option value=\"1d\">1 Day</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div style=\"margin-top: 1rem;\">\n <mat-slide-toggle formControlName=\"encrypted\">Encrypted</mat-slide-toggle>\n </div>\n </div>\n\n <div style=\"display: flex;\">\n <mat-form-field appearance=\"outline\" style=\"flex: 1;\">\n <mat-label>Data (Must be an Object or Array of any)</mat-label>\n <textarea matInput placeholder=\"[]\" formControlName=\"data\" #json></textarea>\n </mat-form-field>\n </div>\n <mat-error *ngIf=\"!isValidJSON(json.value)\">Not Valid Data</mat-error>\n\n <div style=\"display: flex;\">\n <span style=\"flex:1\"></span>\n <button mat-stroked-button (click)=\"onCreateStore()\" [disabled]=\"!(isValid && isValidJSON(json.value))\">\n Save Store\n </button>\n </div>\n\n </div>\n\n <ng-template #LIST>\n\n <div style=\"margin-top: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n{{ settings$ | async | json }}\n <div *ngIf=\"filterData(settings$ | async) as data\">\n <ng-container *ngIf=\"data.length > 0; else NO_DATA\">\n <table mat-table [dataSource]=\"data\">\n <ng-container matColumnDef=\"name\">\n <th mat-header-cell *matHeaderCellDef> Store </th>\n <td mat-cell *matCellDef=\"let element\" style=\"font-weight: bold; text-transform: uppercase;\"> {{element.name}} </td>\n </ng-container>\n\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef> ID </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.id}} </td>\n </ng-container>\n\n <ng-container matColumnDef=\"encrypted\">\n <th mat-header-cell *matHeaderCellDef style=\"text-align: center;\"> Encrypted </th>\n <td mat-cell *matCellDef=\"let element\" style=\"text-align: center;\">\n <ng-container *ngIf=\"element.options.encrypted; else NO\">YES</ng-container>\n <ng-template #NO><span style=\"color:gray\">NO</span></ng-template>\n </td>\n </ng-container>\n\n <ng-container matColumnDef=\"expires\">\n <th mat-header-cell *matHeaderCellDef style=\"text-align: center;\"> Expires </th>\n <td mat-cell *matCellDef=\"let element\" style=\"text-align: center;\">\n <ng-container *ngIf=\"element.options.expires !== 0; else NO_DATA\">\n {{expiresIn(element.options.expires)}}\n </ng-container>\n <ng-template #NO_DATA>\n \u221E\n </ng-template>\n </td>\n </ng-container>\n\n <ng-container matColumnDef=\"option\">\n <th mat-header-cell *matHeaderCellDef></th>\n <td mat-cell *matCellDef=\"let element\" style=\"padding-right: 0;\">\n <div style=\"display: flex;justify-content: flex-end;\">\n <button mat-icon-button color=\"warn\" (click)=\"onDelete(element); $event.stopPropagation()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\" (click)=\"onSelectedRow(row)\"></tr>\n </table>\n </ng-container>\n\n <ng-template #NO_DATA>\n <h3 style=\"margin-top: 1rem;\">No Data</h3>\n </ng-template>\n\n <div *ngIf=\"(data$ | async) as data\" style=\"margin-top: 2rem;\">\n <div style=\"margin-bottom: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <h3>STORE: <span style=\"font-weight: bold;\">{{ store.name | uppercase }}</span></h3>\n <h3>SETTINGS: {{ setting$(store.name) | async | json }}</h3>\n <div style=\"display: flex;\">\n <mat-form-field appearance=\"outline\" style=\"flex: 1;\">\n <mat-label>Data (Must be an Object or Array of any)</mat-label>\n <textarea matInput placeholder=\"[]\" #json [value]=\"data\"></textarea>\n </mat-form-field>\n </div>\n <mat-error *ngIf=\"!isValidJSON(json.value)\">Not Valid Data</mat-error>\n\n <div style=\"display: flex; gap: .5rem;\">\n <span style=\"flex:1\"></span>\n <button mat-stroked-button (click)=\"onCancel()\">Cancel</button>\n <button mat-stroked-button (click)=\"onUpdate(store, json.value)\" [disabled]=\"!(isValidJSON(json.value))\">Update</button>\n </div>\n </div>\n </div>\n\n </ng-template>\n\n <div style=\"margin-top: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 1rem;\">\n <button mat-stroked-button (click)=\"onReset()\">Delete All Stores</button>\n </div>\n\n</div>\n\n\n<mat-menu #menu=\"matMenu\">\n <div style=\"padding: 1rem;\">\n <p>Please note that the LocalStorage (encryption) and management of data is dependant on initializing the APP_ID</p>\n <p>Must provide a value for <b>APP_ID</b> in AppModule->Providers</p>\n <mat-divider></mat-divider>\n <div style=\"font-size: smaller; margin-top: .5rem;\">\n <p>Example: UUID (self.crypto.randomUUID() to generate)</p>\n <div style=\"background-color: whitesmoke; padding: 1rem;\">\n &#123;<br>\n provide: APP_ID,<br>\n useValue: \"056991ac-3537-43ab-b5b9-83edf6554eff\",<br>\n &#125;\n </div>\n </div>\n </div>\n</mat-menu>\n", styles: [".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}.demo-row-is-clicked{font-weight:700}.mat-mdc-menu-panel.mat-mdc-menu-panel{max-width:280px!important}\n"] }]
9134
- }], ctorParameters: () => [{ type: ConfigOptions, decorators: [{
9135
- type: Inject,
9136
- args: [CONFIG_SETTINGS_TOKEN]
9137
- }] }] });
9138
-
9139
- class OIDCClient {
9140
- constructor(spiffe = '', id = 0, first_name = '', last_name = '', email = '') {
9141
- this.spiffe = spiffe;
9142
- this.id = id;
9143
- this.first_name = first_name;
9144
- this.last_name = last_name;
9145
- this.email = email;
9146
- }
9147
- static adapt(item) {
9148
- return new OIDCClient(item?.spiffe, item?.id, item?.first_name, item?.last_name, item?.email);
9149
- }
9150
- }
9151
-
9152
- /**
9153
- * StateServiceDemo - Core state management and WebSocket connection service
9154
- *
9155
- * Handles:
9156
- * - WebSocket connection setup
9157
- * - Base HTTP state management
9158
- * - Connection status and retry logic
9159
- */
9160
- class StateServiceDemo extends HTTPManagerStateService {
9161
- constructor() {
9162
- super(ApiRequest.adapt({
9163
- server: '',
9164
- retry: RetryOptions.adapt({
9165
- times: 3,
9166
- delay: 1,
9167
- }),
9168
- adapter: OIDCClient.adapt,
9169
- }), DataType.ARRAY, DatabaseStorage.adapt({
9170
- table: 'ws_demo_data',
9171
- expiresIn: '5mn',
9172
- }));
9173
- this.attempts$ = this.wsRetryAttempts$;
9174
- this.nextRetry$ = this.wsNextRetry$;
9175
- }
9176
- /**
9177
- * Initialize WebSocket connection with server configuration
9178
- * @param server - Backend server URL
9179
- * @param wsServer - WebSocket server URL
9180
- * @param jwtToken - JWT authentication token
9181
- * @param user - User information
9182
- * @param path - Path for constructing channel name (e.g., ['ai','tests'])
9183
- */
9184
- updateConnection(server, wsServer, jwtToken, user, path = ['ai', 'tests']) {
9185
- // Construct channel name from path: ['ai','tests'] → 'ai/tests'
9186
- const channelId = path.join('/');
9187
- this.setApiRequestOptions({
9188
- server,
9189
- path, // Set the path for HTTP requests
9190
- retry: RetryOptions.adapt({
9191
- times: 3,
9192
- delay: 1,
9193
- }),
9194
- adapter: OIDCClient.adapt,
9195
- ws: {
9196
- id: channelId, // Use path-based channel ID instead of hardcoded 'USERS123'
9197
- wsServer,
9198
- jwtToken,
9199
- user,
9200
- retry: RetryOptions.adapt({
9201
- times: 10,
9202
- delay: 5,
9203
- }),
9204
- }
9205
- });
9206
- this.userAction$.subscribe(user => {
9207
- console.log('User Action:', user);
9208
- });
9209
- }
9210
- /**
9211
- * Get all available channels
9212
- */
9213
- getAllChannels() {
9214
- this.httpManagerService.getAllChannels();
9215
- }
9216
- /**
9217
- * Subscribe to a channel
9218
- */
9219
- subscribeToChannel(channel) {
9220
- super.subscribeToChannel(channel);
9221
- }
9222
- /**
9223
- * Unsubscribe from a channel
9224
- */
9225
- unsubscribeFromChannel(channel) {
9226
- super.unsubscribeFromChannel(channel);
9227
- }
9228
- // =====================
9229
- // Notification Channel Methods (MES- prefix)
9230
- // These override methods from HTTPManagerStateService
9231
- // =====================
9232
- /**
9233
- * Create a notification channel (MES- prefix)
9234
- */
9235
- createNotificationChannel(channel) {
9236
- super.createNotificationChannel(channel);
9237
- }
9238
- /**
9239
- * Get all notification channels list (in-memory)
9240
- */
9241
- getNotificationChannels() {
9242
- super.getNotificationChannels();
9243
- }
9244
- /**
9245
- * Get today's notification channels from database
9246
- * Returns unique channels that have notifications posted today
9247
- */
9248
- getTodaysNotificationChannels() {
9249
- super.getTodaysNotificationChannels();
9250
- }
9251
- /**
9252
- * Define and load previous day's notification channels from database
9253
- * Creates channels in memory and broadcasts updated list
9254
- */
9255
- definePreviousNotificationChannels() {
9256
- const options = ApiRequest.adapt({
9257
- path: ['notifications', 'channels', 'define-previous']
9258
- });
9259
- this.httpManagerService.postRequest({}, options).subscribe({
9260
- next: (response) => {
9261
- console.log('📋 Previous day channels loaded:', response);
9262
- // The server will broadcast updated channel list to all WebSocket clients
9263
- },
9264
- error: (error) => {
9265
- console.error('❌ Failed to load previous day channels:', error);
9266
- }
9267
- });
9268
- }
9269
- /**
9270
- * Subscribe to notification channel with optional date filters
9271
- */
9272
- subscribeToNotificationChannel(channel, options, user) {
9273
- super.subscribeToNotificationChannel(channel, options, user);
9274
- }
9275
- /**
9276
- * Unsubscribe from notification channel
9277
- */
9278
- unsubscribeFromNotificationChannel(channel) {
9279
- super.unsubscribeFromNotificationChannel(channel);
9280
- }
9281
- /**
9282
- * Send a notification to a channel
9283
- */
9284
- sendNotification(channel, content) {
9285
- super.sendNotification(channel, content);
9286
- }
9287
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateServiceDemo, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
9288
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateServiceDemo, providedIn: 'root' }); }
9289
- }
9290
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateServiceDemo, decorators: [{
9291
- type: Injectable,
9292
- args: [{
9293
- providedIn: 'root'
9294
- }]
9295
- }], ctorParameters: () => [] });
9296
-
9297
- /**
9298
- * MessageServiceDemo - Channel messaging service (PUB- prefix channels)
9299
- *
9300
- * Handles:
9301
- * - Sending messages to channels
9302
- * - Channel subscription for messaging
9303
- * - Real-time message broadcast
9304
- */
9305
- class MessageServiceDemo {
9306
- constructor() {
9307
- this.stateService = inject(StateServiceDemo);
9308
- // Expose observables from state service
9309
- this.channels$ = this.stateService.channels$;
9310
- this.subscribedChannels$ = this.stateService.subscribedChannels$;
9311
- this.communicationMessages$ = this.stateService.communicationMessages$;
9312
- this.latestCommunicationMessages$ = this.stateService.latestCommunicationMessages$;
9313
- this.connectionStatus$ = this.stateService.connectionStatus$;
9314
- this.user$ = this.stateService.user$;
9315
- this.data$ = this.stateService.data$;
9316
- }
9317
- /**
9318
- * Helper to ensure channel has PUB- prefix
9319
- */
9320
- toPublicChannel(channel) {
9321
- return channel.startsWith('PUB-') ? channel : `PUB-${channel}`;
9322
- }
9323
- /**
9324
- * Helper to strip PUB- prefix for display
9325
- */
9326
- fromPublicChannel(channel) {
9327
- return channel.startsWith('PUB-') ? channel.replace('PUB-', '') : channel;
9328
- }
9329
- /**
9330
- * Create a new public channel
9331
- */
9332
- createChannel(channel) {
9333
- const publicChannel = this.toPublicChannel(channel);
9334
- this.stateService.createChannel(publicChannel);
9335
- }
9336
- /**
9337
- * Get all available channels
9338
- */
9339
- getAllChannels() {
9340
- this.stateService.getAllChannels();
9341
- }
9342
- /**
9343
- * Subscribe to a channel to receive messages
9344
- */
9345
- subscribeToChannel(channel) {
9346
- const publicChannel = this.toPublicChannel(channel);
9347
- this.stateService.subscribeToChannel(publicChannel);
9348
- }
9349
- /**
9350
- * Unsubscribe from a channel
9351
- */
9352
- unsubscribeFromChannel(channel) {
9353
- const publicChannel = this.toPublicChannel(channel);
9354
- this.stateService.unsubscribeFromChannel(publicChannel);
9355
- }
9356
- /**
9357
- * Send a message to one or more channels
9358
- */
9359
- sendMessage(data, channels) {
9360
- const publicChannels = channels?.map(ch => this.toPublicChannel(ch));
9361
- console.log('sendMessage', data, 'to channels:', publicChannels);
9362
- this.stateService.wsMessaging(data, publicChannels);
9363
- }
9364
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageServiceDemo, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
9365
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageServiceDemo, providedIn: 'root' }); }
9366
- }
9367
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageServiceDemo, decorators: [{
9368
- type: Injectable,
9369
- args: [{
9370
- providedIn: 'root'
9371
- }]
9372
- }] });
9373
-
9374
- /**
9375
- * NotificationServiceDemo - Notification channel service (MES- prefix channels)
9376
- *
9377
- * Handles:
9378
- * - Creating notification channels
9379
- * - Subscribing to notifications with date filters
9380
- * - Sending notifications (persisted to database)
9381
- * - Receiving real-time and historical notifications
9382
- */
9383
- class NotificationServiceDemo {
9384
- constructor() {
9385
- this.stateService = inject(StateServiceDemo);
9386
- // Notification channels from state service (in-memory - contains MES- channels from server)
9387
- this.notificationChannels$ = this.stateService.notificationChannels$;
9388
- // Today's notification channels from database (channels with data for today)
9389
- this.todaysNotificationChannels$ = this.stateService.todaysNotificationChannels$;
9390
- this.subscribedNotificationChannelsSubject = new BehaviorSubject(new Set());
9391
- this.subscribedNotificationChannels$ = this.subscribedNotificationChannelsSubject.asObservable();
9392
- // Notification messages from state service
9393
- this.notificationMessages$ = this.stateService.notificationMessages$;
9394
- // Latest notification from state service
9395
- this.latestNotification$ = this.stateService.latestNotification$;
9396
- // Expose connection status from state service
9397
- this.connectionStatus$ = this.stateService.connectionStatus$;
9398
- }
9399
- /**
9400
- * Create a notification channel
9401
- * NOTE: MES- prefix is added automatically by HTTPManagerStateService
9402
- */
9403
- createNotificationChannel(channel) {
9404
- console.log('📢 createNotificationChannel:', channel);
9405
- this.stateService.createNotificationChannel(channel);
9406
- }
9407
- /**
9408
- * Get all notification channels (in-memory)
9409
- */
9410
- getNotificationChannels() {
9411
- console.log('📢 getNotificationChannels');
9412
- this.stateService.getNotificationChannels();
9413
- }
9414
- /**
9415
- * Get today's notification channels from database
9416
- * Returns unique channels that have notifications posted today
9417
- */
9418
- getTodaysNotificationChannels() {
9419
- console.log('📢 getTodaysNotificationChannels (from DB)');
9420
- this.stateService.getTodaysNotificationChannels();
9421
- }
9422
- /**
9423
- * Define and load previous day's notification channels from database
9424
- * Creates channels in memory and broadcasts updated list
9425
- */
9426
- definePreviousNotificationChannels() {
9427
- console.log('📢 definePreviousNotificationChannels');
9428
- this.stateService.definePreviousNotificationChannels();
9429
- }
9430
- /**
9431
- * Subscribe to a notification channel with optional date filter
9432
- * NOTE: MES- prefix is added automatically by HTTPManagerStateService
9433
- * @param channel Base channel name (without MES- prefix)
9434
- * @param options { startEpoch?, endEpoch? }
9435
- * @param user User info for subscription
9436
- */
9437
- subscribeToNotificationChannel(channel, options, user) {
9438
- // Add MES- prefix for local tracking (to match what state service sends)
9439
- const prefixedChannel = channel.startsWith('MES-') ? channel : `MES-${channel}`;
9440
- console.log('📢 subscribeToNotificationChannel:', prefixedChannel, options);
9441
- // Add to local subscribed channels immediately for UI feedback
9442
- const currentSubs = this.subscribedNotificationChannelsSubject.value;
9443
- currentSubs.add(prefixedChannel);
9444
- this.subscribedNotificationChannelsSubject.next(new Set(currentSubs));
9445
- // Send subscription request to server (state service adds MES- prefix)
9446
- this.stateService.subscribeToNotificationChannel(channel, options, user);
9447
- }
9448
- /**
9449
- * Unsubscribe from a notification channel
9450
- * NOTE: MES- prefix is added automatically by HTTPManagerStateService
9451
- */
9452
- unsubscribeFromNotificationChannel(channel) {
9453
- // Add MES- prefix for local tracking (to match what state service sends)
9454
- const prefixedChannel = channel.startsWith('MES-') ? channel : `MES-${channel}`;
9455
- console.log('📢 unsubscribeFromNotificationChannel:', prefixedChannel);
9456
- // Remove from local subscribed channels immediately for UI feedback
9457
- const currentSubs = this.subscribedNotificationChannelsSubject.value;
9458
- currentSubs.delete(prefixedChannel);
9459
- this.subscribedNotificationChannelsSubject.next(new Set(currentSubs));
9460
- // Send unsubscription request to server (state service adds MES- prefix)
9461
- this.stateService.unsubscribeFromNotificationChannel(channel);
9462
- }
9463
- /**
9464
- * Send a notification to a channel
9465
- * NOTE: MES- prefix is added automatically by HTTPManagerStateService
9466
- */
9467
- sendNotification(channel, content, user) {
9468
- const notificationContent = {
9469
- sessionId: {
9470
- id: user?.id,
9471
- ldap: user?.ldap,
9472
- name: user?.name,
9473
- email: user?.email,
9474
- color: user?.color,
9475
- },
9476
- ...content
9477
- };
9478
- console.log('📢 sendNotification:', channel, notificationContent);
9479
- // State service adds MES- prefix
9480
- this.stateService.sendNotification(channel, notificationContent);
9481
- }
9482
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NotificationServiceDemo, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
9483
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NotificationServiceDemo, providedIn: 'root' }); }
9484
- }
9485
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NotificationServiceDemo, decorators: [{
9486
- type: Injectable,
9487
- args: [{
9488
- providedIn: 'root'
9489
- }]
9490
- }] });
9491
-
9492
- class StateDataRequestService extends HTTPManagerStateService {
9493
- constructor() {
9494
- super(ApiRequest.adapt({
9495
- server: '',
9496
- retry: RetryOptions.adapt({
9497
- times: 3,
9498
- delay: 1,
9499
- }),
9500
- adapter: OIDCClient.adapt,
9501
- ws: {
9502
- id: ``,
9503
- // wsChannels: ['allChannels'],
9504
- // access: ['read,write,delete,update'],
9505
- wsServer: '',
9506
- jwtToken: '',
9507
- user: UserData.adapt(),
9508
- retry: RetryOptions.adapt({
9509
- times: 3,
9510
- delay: 10,
9511
- }),
9512
- }
9513
- }), DataType.ARRAY, DatabaseStorage.adapt({
9514
- table: 'ws_demo_data',
9515
- expiresIn: '5mn',
9516
- }));
9517
- this.attempts$ = this.wsRetryAttempts$;
9518
- this.nextRetry$ = this.wsNextRetry$;
9519
- this.path = ['ai', 'tests'];
9520
- }
9521
- updateConnection(server, wsServer, jwtToken, user, path = []) {
9522
- this.path = path;
9523
- this.setApiRequestOptions({
9524
- server,
9525
- retry: RetryOptions.adapt({
9526
- times: 3,
9527
- delay: 1,
9528
- }),
9529
- adapter: OIDCClient.adapt,
9530
- ws: {
9531
- id: this.path.join('/'),
9532
- wsServer,
9533
- jwtToken,
9534
- user, // general info about user
9535
- retry: RetryOptions.adapt({
9536
- times: 3,
9537
- delay: 10,
9538
- }),
9539
- }
9540
- });
9541
- }
9542
- addData() {
9543
- const num = RandomNumber(1000, 9999);
9544
- const newData = {
9545
- "spiffe": `sample.com/developer/${num}`,
9546
- "last_name": "boni",
9547
- "email": "mikeboni@hotmail.com",
9548
- "first_name": "mike"
9549
- };
9550
- this.createRecord(newData, RequestOptions.adapt({ path: this.path }));
9551
- }
9552
- sendMessage(data) {
9553
- console.log('sendMessage', data);
9554
- this.wsMessaging(data);
9555
- }
9556
- getData() {
9557
- this.fetchRecords(RequestOptions.adapt({ path: this.path }));
9558
- }
9559
- updateData(data) {
9560
- const sampleOptions = RequestOptions.adapt({ path: [...this.path, data.id] });
9561
- console.log('updateData', sampleOptions);
9562
- this.updateRecord(data, sampleOptions);
9563
- }
9564
- deleteData(data) {
9565
- const sampleOptions = RequestOptions.adapt({ path: [...this.path, data.id] });
9566
- console.log('deleteData', sampleOptions);
9567
- this.deleteRecord(sampleOptions);
9568
- }
9569
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateDataRequestService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
9570
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateDataRequestService, providedIn: 'root' }); }
9571
- }
9572
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateDataRequestService, decorators: [{
9573
- type: Injectable,
9574
- args: [{
9575
- providedIn: 'root'
9576
- }]
9577
- }], ctorParameters: () => [] });
9578
-
9579
- class WsDataControlComponent {
9580
- constructor() {
9581
- this.path = ['ai', 'tests'];
9582
- this.stateDataRequestService = inject(StateDataRequestService);
9583
- this.webSocketMessageService = inject(WebSocketMessageService);
9584
- this.user$ = this.stateDataRequestService.user$;
9585
- this.users$ = this.stateDataRequestService.userList$;
9586
- this.userAction$ = this.stateDataRequestService.userAction$
9587
- .pipe(switchMap$1((action) => timer(3 * 1000).pipe(map$1(() => null), startWith$1(action))));
9588
- this.data$ = this.stateDataRequestService.data$;
9589
- this.isUser = (user, userItem) => {
9590
- return user.sessionId === userItem.sessionId;
9591
- };
9592
- }
9593
- ngOnInit() {
9594
- this.stateDataRequestService.updateConnection(this.server, this.wsServer, this.jwtToken, this.user, this.path);
9595
- this.stateDataRequestService.getData();
9596
- }
9597
- onGetData() {
9598
- this.stateDataRequestService.getData();
9599
- }
9600
- onAddData() {
9601
- this.stateDataRequestService.addData();
9602
- }
9603
- onUpdateData(data) {
9604
- const num = RandomNumber(1000, 9999);
9605
- const newData = {
9606
- "spiffe": `wavecoders.com/developer/${num}`,
9607
- "id": 63,
9608
- "last_name": "boni",
9609
- "email": "mikeboni@hotmail.com",
9610
- "first_name": "mike"
9611
- };
9612
- this.stateDataRequestService.updateData(newData);
9613
- }
9614
- onRemoveData(data) {
9615
- const lastRec = data[data.length - 1];
9616
- this.stateDataRequestService.deleteData(lastRec);
9617
- }
9618
- /**
9619
- * Test direct state message via WebSocketMessageService
9620
- * Sends an UPDATE message that should trigger fetchRecord() in the state manager
9621
- *
9622
- * @param recordId - The record ID to update (default: 63)
9623
- * @param useFakeSessionId - If true, uses a fake sessionId to avoid filtering (default: true)
9624
- * @param customSessionId - Optional custom sessionId (overrides useFakeSessionId if provided)
9625
- */
9626
- onTestDirectStateMessage(recordId = 63, useFakeSessionId = true, customSessionId) {
9627
- // Get actual sessionId from sessionStorage (stored by HTTPManagerStateService)
9628
- const actualSessionId = sessionStorage.getItem('WSID') || 'unknown';
9629
- // Determine sessionId to use
9630
- let sessionId;
9631
- if (customSessionId) {
9632
- sessionId = customSessionId;
9633
- }
9634
- else if (useFakeSessionId) {
9635
- sessionId = `test-${Date.now()}`;
9636
- }
9637
- else {
9638
- // Use actual sessionId (will be filtered out by receiver)
9639
- sessionId = actualSessionId;
9640
- }
9641
- // Build StateMessage with same structure as CRUD operations
9642
- const stateMessage = {
9643
- sessionId: {
9644
- id: sessionId,
9645
- name: useFakeSessionId ? 'Test Sender' : (this.user?.name || 'Current User')
9646
- },
9647
- content: {
9648
- method: 'UPDATE',
9649
- path: ['ai', 'tests', recordId],
9650
- user: {
9651
- id: sessionId,
9652
- name: useFakeSessionId ? 'Test Sender' : (this.user?.name || 'Current User')
9653
- }
9654
- }
9655
- };
9656
- console.log('🧪 [TEST] Sending direct state message:', {
9657
- recordId,
9658
- sessionId,
9659
- actualSessionId,
9660
- useFakeSessionId,
9661
- stateMessage
9662
- });
9663
- const success = this.webSocketMessageService.sendStateMessage(['ai', 'tests'], stateMessage);
9664
- console.log('🧪 [TEST] Message sent:', {
9665
- success,
9666
- actualSessionId,
9667
- sentSessionId: sessionId,
9668
- expectedBehavior: success
9669
- ? 'Server will broadcast to SYS-ai/tests subscribers'
9670
- : 'Failed to send (check connection)',
9671
- receiverBehavior: sessionId !== actualSessionId
9672
- ? 'Receiver will process message and call fetchRecord()'
9673
- : 'Receiver will filter out (sessionId match)'
9674
- });
9675
- }
9676
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsDataControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9677
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: WsDataControlComponent, selector: "app-ws-data-control", inputs: { server: "server", wsServer: "wsServer", jwtToken: "jwtToken", user: "user", path: "path" }, ngImport: i0, template: "@if ((data$ | async); as data) {\n <div style=\"margin: 1rem;\">\n @if ((users$ |async); as users) {\n <div>\n @if (users.length > 0) {\n <h3 style=\"margin: 0;\">Connected Users</h3>\n } @else {\n <h3 style=\"margin: 0;\">No Users</h3>\n }\n <mat-chip-set>\n @for (user of users; track $index) {\n <mat-chip\n [class.user-chip--primary]=\"isUser(user, (user$ | async))\"\n [style.color]=\"isUser(user, (user$ | async)) ? '#fff' : null\"\n [disableRipple]=\"true\"\n >\n {{ user.name || user.ldap || user.id || 'Anonymous' }}\n </mat-chip>\n }\n </mat-chip-set>\n </div>\n }\n <div style=\"margin-top: 1rem; margin-bottom: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <div class=\"box\" style=\"margin-bottom: 1rem;\" *ngIf=\"(userAction$ | async) as userAction\">\n {{ userAction?.content?.user?.name }} has {{ userAction?.content?.method }}\n </div>\n\n <h3 style=\"margin: 0;\">Data Actions</h3>\n <div style=\"display: flex; gap: 1rem; margin-bottom: 1rem;\">\n <button mat-stroked-button (click)=\"onGetData()\">Get Data</button>\n <div style=\"flex:1\"></div>\n <button mat-stroked-button color=\"accent\" (click)=\"onUpdateData(data)\">Update Data</button>\n <button mat-stroked-button color=\"warn\" (click)=\"onRemoveData(data)\">Remove Data</button>\n <button mat-stroked-button color=\"primary\" (click)=\"onAddData()\">Add Data</button>\n </div>\n\n <h3 style=\"margin: 0; margin-top: 1rem;\">WebSocketMessageService Test</h3>\n\n <p style=\"font-size: 0.875rem; color: #666; margin: 0;\">\n <strong>Note:</strong> \"Fake SessionId\" will be received and processed. \"Current SessionId\" will be filtered out (self-message).\n @if (data.length > 0) {\n <div>\n <table mat-table [dataSource]=\"data\" style=\"border: 1px solid grey;\">\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef> ID </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.id}} </td>\n </ng-container>\n <ng-container matColumnDef=\"spiffe\">\n <th mat-header-cell *matHeaderCellDef> Spiffe </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.spiffe}} </td>\n </ng-container>\n <ng-container matColumnDef=\"name\">\n <th mat-header-cell *matHeaderCellDef> Name </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.first_name}} {{element.last_name}}</td>\n </ng-container>\n <ng-container matColumnDef=\"email\">\n <th mat-header-cell *matHeaderCellDef> Email </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.email}} </td>\n </ng-container>\n <tr mat-header-row *matHeaderRowDef=\"['id', 'spiffe', 'name', 'email']\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: ['id', 'spiffe', 'name', 'email'];\"></tr>\n </table>\n <div style=\"border: 1px solid grey; padding: .5rem; border-top: none;\">\n <h3 style=\"margin: 0;\">Total Records {{ data.length }}</h3>\n </div>\n </div>\n } @else {\n <div style=\"margin-top: 1rem; font-style: italic;\">\n No Data Available\n </div>\n }\n\n <div style=\"margin-bottom: 1rem; margin-top: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"display: flex; gap: 0.5rem; margin-bottom: 1rem;\">\n <button mat-flat-button color=\"primary\" (click)=\"onTestDirectStateMessage(63, true)\">\n Test UPDATE (Fake Id)\n </button>\n <button mat-stroked-button (click)=\"onTestDirectStateMessage(63, false)\">\n Test UPDATE (Current Id)\n </button>\n <button mat-stroked-button (click)=\"onTestDirectStateMessage(63, false, 'custom-session-123')\">\n Test UPDATE (Custom Id)\n </button>\n </div>\n\n </div>\n}\n\n", styles: [".user-chip--primary{background-color:var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5))!important;color:#fff!important;--mdc-evolution-chip-container-color: var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5));--mdc-evolution-chip-label-text-color: #fff}.user-chip--primary :is(.mdc-evolution-chip__text-label,.mdc-evolution-chip__action,.mdc-evolution-chip__cell,.mat-mdc-chip-action-label){color:#fff!important}.user-chip--primary,.user-chip--primary *{color:#fff!important}:host ::ng-deep .user-chip--primary{background-color:var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5))!important;color:#fff!important;--mdc-evolution-chip-container-color: var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5));--mdc-evolution-chip-label-text-color: #fff}:host ::ng-deep .user-chip--primary .mdc-evolution-chip__text-label,:host ::ng-deep .user-chip--primary .mdc-evolution-chip__action,:host ::ng-deep .user-chip--primary .mdc-evolution-chip__cell,:host ::ng-deep .user-chip--primary .mat-mdc-chip-action-label,:host ::ng-deep .user-chip--primary *{color:#fff!important}.box{padding:.5rem;border:1px solid rgb(174,174,13);background-color:#ececaf}\n"], dependencies: [{ kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i3$1.MatChip, selector: "mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]", inputs: ["role", "id", "aria-label", "aria-description", "value", "color", "removable", "highlighted", "disableRipple", "disabled"], outputs: ["removed", "destroyed"], exportAs: ["matChip"] }, { kind: "component", type: i3$1.MatChipSet, selector: "mat-chip-set", inputs: ["disabled", "role", "tabIndex"] }, { kind: "component", type: i9.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i9.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i9.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i9.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i9.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i9.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i9.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i9.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i9.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i9.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i12.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }] }); }
9678
- }
9679
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsDataControlComponent, decorators: [{
9680
- type: Component,
9681
- args: [{ selector: 'app-ws-data-control', standalone: false, template: "@if ((data$ | async); as data) {\n <div style=\"margin: 1rem;\">\n @if ((users$ |async); as users) {\n <div>\n @if (users.length > 0) {\n <h3 style=\"margin: 0;\">Connected Users</h3>\n } @else {\n <h3 style=\"margin: 0;\">No Users</h3>\n }\n <mat-chip-set>\n @for (user of users; track $index) {\n <mat-chip\n [class.user-chip--primary]=\"isUser(user, (user$ | async))\"\n [style.color]=\"isUser(user, (user$ | async)) ? '#fff' : null\"\n [disableRipple]=\"true\"\n >\n {{ user.name || user.ldap || user.id || 'Anonymous' }}\n </mat-chip>\n }\n </mat-chip-set>\n </div>\n }\n <div style=\"margin-top: 1rem; margin-bottom: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <div class=\"box\" style=\"margin-bottom: 1rem;\" *ngIf=\"(userAction$ | async) as userAction\">\n {{ userAction?.content?.user?.name }} has {{ userAction?.content?.method }}\n </div>\n\n <h3 style=\"margin: 0;\">Data Actions</h3>\n <div style=\"display: flex; gap: 1rem; margin-bottom: 1rem;\">\n <button mat-stroked-button (click)=\"onGetData()\">Get Data</button>\n <div style=\"flex:1\"></div>\n <button mat-stroked-button color=\"accent\" (click)=\"onUpdateData(data)\">Update Data</button>\n <button mat-stroked-button color=\"warn\" (click)=\"onRemoveData(data)\">Remove Data</button>\n <button mat-stroked-button color=\"primary\" (click)=\"onAddData()\">Add Data</button>\n </div>\n\n <h3 style=\"margin: 0; margin-top: 1rem;\">WebSocketMessageService Test</h3>\n\n <p style=\"font-size: 0.875rem; color: #666; margin: 0;\">\n <strong>Note:</strong> \"Fake SessionId\" will be received and processed. \"Current SessionId\" will be filtered out (self-message).\n @if (data.length > 0) {\n <div>\n <table mat-table [dataSource]=\"data\" style=\"border: 1px solid grey;\">\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef> ID </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.id}} </td>\n </ng-container>\n <ng-container matColumnDef=\"spiffe\">\n <th mat-header-cell *matHeaderCellDef> Spiffe </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.spiffe}} </td>\n </ng-container>\n <ng-container matColumnDef=\"name\">\n <th mat-header-cell *matHeaderCellDef> Name </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.first_name}} {{element.last_name}}</td>\n </ng-container>\n <ng-container matColumnDef=\"email\">\n <th mat-header-cell *matHeaderCellDef> Email </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.email}} </td>\n </ng-container>\n <tr mat-header-row *matHeaderRowDef=\"['id', 'spiffe', 'name', 'email']\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: ['id', 'spiffe', 'name', 'email'];\"></tr>\n </table>\n <div style=\"border: 1px solid grey; padding: .5rem; border-top: none;\">\n <h3 style=\"margin: 0;\">Total Records {{ data.length }}</h3>\n </div>\n </div>\n } @else {\n <div style=\"margin-top: 1rem; font-style: italic;\">\n No Data Available\n </div>\n }\n\n <div style=\"margin-bottom: 1rem; margin-top: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"display: flex; gap: 0.5rem; margin-bottom: 1rem;\">\n <button mat-flat-button color=\"primary\" (click)=\"onTestDirectStateMessage(63, true)\">\n Test UPDATE (Fake Id)\n </button>\n <button mat-stroked-button (click)=\"onTestDirectStateMessage(63, false)\">\n Test UPDATE (Current Id)\n </button>\n <button mat-stroked-button (click)=\"onTestDirectStateMessage(63, false, 'custom-session-123')\">\n Test UPDATE (Custom Id)\n </button>\n </div>\n\n </div>\n}\n\n", styles: [".user-chip--primary{background-color:var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5))!important;color:#fff!important;--mdc-evolution-chip-container-color: var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5));--mdc-evolution-chip-label-text-color: #fff}.user-chip--primary :is(.mdc-evolution-chip__text-label,.mdc-evolution-chip__action,.mdc-evolution-chip__cell,.mat-mdc-chip-action-label){color:#fff!important}.user-chip--primary,.user-chip--primary *{color:#fff!important}:host ::ng-deep .user-chip--primary{background-color:var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5))!important;color:#fff!important;--mdc-evolution-chip-container-color: var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5));--mdc-evolution-chip-label-text-color: #fff}:host ::ng-deep .user-chip--primary .mdc-evolution-chip__text-label,:host ::ng-deep .user-chip--primary .mdc-evolution-chip__action,:host ::ng-deep .user-chip--primary .mdc-evolution-chip__cell,:host ::ng-deep .user-chip--primary .mat-mdc-chip-action-label,:host ::ng-deep .user-chip--primary *{color:#fff!important}.box{padding:.5rem;border:1px solid rgb(174,174,13);background-color:#ececaf}\n"] }]
9682
- }], propDecorators: { server: [{
9683
- type: Input
9684
- }], wsServer: [{
9685
- type: Input
9686
- }], jwtToken: [{
9687
- type: Input
9688
- }], user: [{
9689
- type: Input
9690
- }], path: [{
9691
- type: Input
9692
- }] } });
9693
-
9694
- class WsMessagingComponent {
9695
- constructor() {
9696
- this.path = ['ai', 'tests']; // Default path for channel name
9697
- this.destroy$ = new Subject();
9698
- this.fb = inject(FormBuilder);
9699
- this.messageService = inject(MessageServiceDemo);
9700
- this.stateService = inject(StateServiceDemo);
9701
- this.messageDisplayService = inject(MessageDisplayRouterService);
9702
- // Only show public channels (starting with "PUB-"), strip prefix for display
9703
- // SYS- channels are private/internal and hidden from users
9704
- this.channels$ = this.messageService.channels$.pipe(map$1(channels => channels
9705
- ?.filter(channel => channel.startsWith('PUB-'))
9706
- .map(channel => channel.replace('PUB-', '')) || []), startWith$1([]), distinctUntilChanged$1((prev, curr) => prev.length === curr.length && prev.every((v, i) => v === curr[i])));
9707
- // Track subscribed public channels only, strip prefix for display
9708
- this.subscribedChannels$ = this.messageService.subscribedChannels$.pipe(map$1(set => Array.from(set)
9709
- .filter(channel => channel.startsWith('PUB-'))
9710
- .map(channel => channel.replace('PUB-', ''))), startWith$1([]), distinctUntilChanged$1((prev, curr) => prev.length === curr.length && prev.every((v, i) => v === curr[i])));
9711
- this.user$ = this.stateService.user$;
9712
- this.data$ = this.stateService.data$;
9713
- this.connectionStatus$ = this.stateService.connectionStatus$;
9714
- // Form for creating new channels
9715
- this.newChannelName = this.fb.control('', [Validators.required, Validators.minLength(2)]);
9716
- // Form for selecting channels to send to and message content
9717
- this.messages = this.fb.group({
9718
- selectedChannels: this.fb.control([], Validators.required),
9719
- content: this.fb.control(null, Validators.required),
9720
- });
9721
- this.communicationMessages$ = this.messageService.communicationMessages$;
9722
- this.latestCommunicationMessages$ = this.messageService.latestCommunicationMessages$;
9723
- this.chat$ = combineLatest([this.user$, this.communicationMessages$])
9724
- .pipe(map$1(([user, messages]) => ({ user, messages })), map$1(obj => {
9725
- if (!obj.user)
9726
- return EMPTY;
9727
- const mainUser = '';
9728
- const messages = obj.messages.map((item) => {
9729
- // Message transformation logic
9730
- });
9731
- return { user: mainUser, messages };
9732
- }));
9733
- }
9734
- get selectedChannels() {
9735
- return this.messages.get('selectedChannels');
9736
- }
9737
- get content() {
9738
- return this.messages.get('content');
9739
- }
9740
- ngOnInit() {
9741
- this.stateService.updateConnection(this.server, this.wsServer, this.jwtToken, this.user, this.path);
9742
- // Only trigger once when connection becomes true
9743
- this.connectionStatus$.pipe(filter$1(status => status === true), take$1(1), takeUntil$1(this.destroy$)).subscribe(() => {
9744
- // Wait a moment for subscription to be processed, then fetch channels
9745
- setTimeout(() => {
9746
- console.log('📋 Fetching channels after connection...');
9747
- this.messageService.getAllChannels();
9748
- }, 500); // 500ms delay to ensure subscription is processed
9749
- });
9750
- // Subscribe to latest messages and display using rule-based routing
9751
- this.latestCommunicationMessages$.pipe(filter$1(message => !!message), takeUntil$1(this.destroy$)).subscribe((message) => {
9752
- console.log('🔔 Message received, routing to display:', message);
9753
- // NEW: Delegate to MessageDisplayRouterService
9754
- // The message payload determines the display type via rules
9755
- this.messageDisplayService.display(message);
9756
- // OLD: Hardcoded snackbar (removed)
9757
- // const messageContent = message.content?.content?.message || message.content?.message || '';
9758
- // this.toastService.toastMessage(...);
9759
- });
9760
- }
9761
- ngOnDestroy() {
9762
- this.destroy$.next();
9763
- this.destroy$.complete();
9764
- }
9765
- /**
9766
- * Create a new public channel without auto-subscribing
9767
- * NOTE: PUB- prefix is added by messageService.createChannel
9768
- */
9769
- onCreateChannel() {
9770
- if (this.newChannelName.invalid)
9771
- return;
9772
- const channelName = this.newChannelName.value?.trim();
9773
- if (channelName) {
9774
- // Pass base channel name - service adds PUB- prefix
9775
- this.messageService.createChannel(channelName);
9776
- this.newChannelName.reset();
9777
- // Server will broadcast updated channel list to all clients automatically
9778
- }
9779
- }
9780
- /**
9781
- * Subscribe to a channel to receive messages
9782
- * NOTE: PUB- prefix is added by messageService.subscribeToChannel
9783
- */
9784
- onSubscribeToChannel(channel) {
9785
- // Pass base channel name - service adds PUB- prefix
9786
- this.messageService.subscribeToChannel(channel);
9787
- // Reset channel selection when subscriptions change
9788
- this.selectedChannels.reset([]);
9789
- }
9790
- /**
9791
- * Unsubscribe from a channel
9792
- * NOTE: PUB- prefix is added by messageService.unsubscribeFromChannel
9793
- */
9794
- onUnsubscribeFromChannel(channel) {
9795
- // Pass base channel name - service adds PUB- prefix
9796
- this.messageService.unsubscribeFromChannel(channel);
9797
- // Reset channel selection when subscriptions change
9798
- this.selectedChannels.reset([]);
9799
- }
9800
- /**
9801
- * Handle chip toggle for subscribe/unsubscribe
9802
- */
9803
- onChipToggle(event, channel) {
9804
- const isSelected = event.selected;
9805
- console.log('🎯 Chip toggle event:', {
9806
- channel,
9807
- isSelected,
9808
- option: event.source
9809
- });
9810
- if (isSelected) {
9811
- this.onSubscribeToChannel(channel);
9812
- }
9813
- else {
9814
- this.onUnsubscribeFromChannel(channel);
9815
- }
9816
- }
9817
- /**
9818
- * Check if currently subscribed to a channel
9819
- * Compares display names (without PUB- prefix)
9820
- */
9821
- isSubscribed(channel, subscribedChannels) {
9822
- return subscribedChannels.includes(channel);
9823
- }
9824
- onSendMessage() {
9825
- this.messages.markAllAsTouched();
9826
- if (this.messages.invalid)
9827
- return;
9828
- const channelsToSend = this.selectedChannels.value;
9829
- if (channelsToSend.length === 0)
9830
- return;
9831
- // Get user from observable - subscribe once
9832
- this.user$.pipe(take$1(1)).subscribe(user => {
9833
- if (!user) {
9834
- console.error('❌ No user found');
9835
- return;
9836
- }
9837
- // Pass base channel names - service adds PUB- prefix
9838
- const message = ChannelMessage.adapt({
9839
- sessionId: {
9840
- id: user.id,
9841
- ldap: user.ldap,
9842
- name: user.name,
9843
- email: user.email,
9844
- },
9845
- content: { message: this.messages.value.content },
9846
- });
9847
- this.messageService.sendMessage(message, channelsToSend);
9848
- this.content.reset();
9849
- });
9850
- }
9851
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsMessagingComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9852
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: WsMessagingComponent, selector: "app-ws-messaging", inputs: { server: "server", wsServer: "wsServer", jwtToken: "jwtToken", user: "user", path: "path" }, ngImport: i0, template: "\n @if ((data$ | async); as data) {\n @if ((user$ | async); as user) {\n <div style=\"display: flex; gap: 1rem; flex-direction: column; margin-top: 1rem;\">\n\n <!-- Channel Creation Section -->\n <div style=\"padding: 1rem; background: #e3f2fd; border-radius: 4px;\">\n <strong>Create New Channel</strong>\n <div style=\"display: flex; flex-direction: column; margin-top: 0.5rem;\">\n <mat-form-field appearance=\"outline\" style=\"width: 100%;\">\n <mat-label>Channel Name</mat-label>\n <input matInput [formControl]=\"newChannelName\" placeholder=\"Enter channel name\"\n (keydown.enter)=\"onCreateChannel()\">\n </mat-form-field>\n <div style=\"display: flex; justify-content: flex-end; margin-top: -0.5rem;\">\n <button mat-raised-button color=\"primary\" (click)=\"onCreateChannel()\"\n [disabled]=\"newChannelName.invalid\">\n Create Channel\n </button>\n </div>\n </div>\n </div>\n\n <!-- Messaging Section - only show when subscribed to channels -->\n @if ((subscribedChannels$ | async); as subscribedChannels) {\n @if (subscribedChannels.length > 0) {\n <div style=\"padding: 1rem; background: #fff3e0; border-radius: 4px;\" [formGroup]=\"messages\">\n <strong>Send Message</strong>\n <!-- Debug: Show subscribed channels count -->\n <div style=\"font-size: 0.8rem; color: #666; margin-bottom: 0.5rem;\">\n Subscribed to {{ subscribedChannels.length }} channel(s): {{ subscribedChannels | json }}\n </div>\n\n <!-- Channel Selection with Checkboxes -->\n <div style=\"margin-top: 0.5rem;\">\n <mat-form-field appearance=\"outline\" style=\"width: 100%;\">\n <mat-label>Select Channels</mat-label>\n <mat-select formControlName=\"selectedChannels\" multiple>\n <mat-select-trigger>\n @if (selectedChannels.value?.length === 1) {\n {{ selectedChannels.value[0] }}\n } @else if (selectedChannels.value?.length > 1) {\n {{ selectedChannels.value[0] }} (+{{ selectedChannels.value.length - 1 }} {{ selectedChannels.value.length === 2 ? 'other' : 'others' }})\n }\n </mat-select-trigger>\n @for (channel of subscribedChannels; track channel; let i = $index) {\n <mat-option [value]=\"channel\">{{ channel }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n <!-- Message Input -->\n <div style=\"display: flex; flex-direction: column;\">\n <mat-form-field style=\"width: 100%;\" appearance=\"outline\">\n <mat-label>Message</mat-label>\n <textarea\n matInput placeholder=\"Type your message...\"\n formControlName=\"content\"\n (keydown.enter)=\"onSendMessage(); $event.preventDefault()\"\n ></textarea>\n </mat-form-field>\n <div style=\"display: flex; justify-content: flex-end; margin-top: -0.5rem;\">\n <button mat-raised-button color=\"primary\" (click)=\"onSendMessage()\"\n [disabled]=\"messages.invalid\">\n Send\n </button>\n </div>\n </div>\n </div>\n }\n }\n\n </div>\n }\n }\n\n <!-- Channel List with Subscription Controls using Chips -->\n @if ((channels$ | async); as channels) {\n @if ((subscribedChannels$ | async); as subscribedChannels) {\n <div style=\"padding: 1rem; background: #f5f5f5; border-radius: 4px; margin-top: 1rem;\">\n <strong>Subscribe to Channel(s)</strong>\n <div style=\"margin-top: 0.5rem;\">\n @if (channels.length === 0) {\n <div style=\"color: #666; font-style: italic;\">No channels available. Create one above.</div>\n } @else {\n <mat-chip-listbox\n aria-label=\"Channel selection\"\n [multiple]=\"true\">\n @for (channel of channels; track channel; let i = $index) {\n <mat-chip-option\n [selected]=\"isSubscribed(channel, subscribedChannels)\"\n (selectionChange)=\"onChipToggle($event, channel)\">\n {{ channel }}\n </mat-chip-option>\n }\n </mat-chip-listbox>\n }\n </div>\n </div>\n }\n }\n", styles: ["button[mat-raised-button],button[mat-stroked-button]{min-width:120px}mat-chip-option{min-width:120px;justify-content:center;border-radius:4px!important;padding:.25rem!important}\n"], dependencies: [{ kind: "directive", type: i2$1.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: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "component", type: i5.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"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "directive", type: i5.MatSelectTrigger, selector: "mat-select-trigger" }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i3$1.MatChipListbox, selector: "mat-chip-listbox", inputs: ["multiple", "aria-orientation", "selectable", "compareWith", "required", "hideSingleSelectionIndicator", "value"], outputs: ["change"] }, { kind: "component", type: i3$1.MatChipOption, selector: "mat-basic-chip-option, [mat-basic-chip-option], mat-chip-option, [mat-chip-option]", inputs: ["selectable", "selected"], outputs: ["selectionChange"] }, { kind: "directive", type: i13.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }] }); }
9853
- }
9854
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsMessagingComponent, decorators: [{
9855
- type: Component,
9856
- args: [{ selector: 'app-ws-messaging', standalone: false, template: "\n @if ((data$ | async); as data) {\n @if ((user$ | async); as user) {\n <div style=\"display: flex; gap: 1rem; flex-direction: column; margin-top: 1rem;\">\n\n <!-- Channel Creation Section -->\n <div style=\"padding: 1rem; background: #e3f2fd; border-radius: 4px;\">\n <strong>Create New Channel</strong>\n <div style=\"display: flex; flex-direction: column; margin-top: 0.5rem;\">\n <mat-form-field appearance=\"outline\" style=\"width: 100%;\">\n <mat-label>Channel Name</mat-label>\n <input matInput [formControl]=\"newChannelName\" placeholder=\"Enter channel name\"\n (keydown.enter)=\"onCreateChannel()\">\n </mat-form-field>\n <div style=\"display: flex; justify-content: flex-end; margin-top: -0.5rem;\">\n <button mat-raised-button color=\"primary\" (click)=\"onCreateChannel()\"\n [disabled]=\"newChannelName.invalid\">\n Create Channel\n </button>\n </div>\n </div>\n </div>\n\n <!-- Messaging Section - only show when subscribed to channels -->\n @if ((subscribedChannels$ | async); as subscribedChannels) {\n @if (subscribedChannels.length > 0) {\n <div style=\"padding: 1rem; background: #fff3e0; border-radius: 4px;\" [formGroup]=\"messages\">\n <strong>Send Message</strong>\n <!-- Debug: Show subscribed channels count -->\n <div style=\"font-size: 0.8rem; color: #666; margin-bottom: 0.5rem;\">\n Subscribed to {{ subscribedChannels.length }} channel(s): {{ subscribedChannels | json }}\n </div>\n\n <!-- Channel Selection with Checkboxes -->\n <div style=\"margin-top: 0.5rem;\">\n <mat-form-field appearance=\"outline\" style=\"width: 100%;\">\n <mat-label>Select Channels</mat-label>\n <mat-select formControlName=\"selectedChannels\" multiple>\n <mat-select-trigger>\n @if (selectedChannels.value?.length === 1) {\n {{ selectedChannels.value[0] }}\n } @else if (selectedChannels.value?.length > 1) {\n {{ selectedChannels.value[0] }} (+{{ selectedChannels.value.length - 1 }} {{ selectedChannels.value.length === 2 ? 'other' : 'others' }})\n }\n </mat-select-trigger>\n @for (channel of subscribedChannels; track channel; let i = $index) {\n <mat-option [value]=\"channel\">{{ channel }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n <!-- Message Input -->\n <div style=\"display: flex; flex-direction: column;\">\n <mat-form-field style=\"width: 100%;\" appearance=\"outline\">\n <mat-label>Message</mat-label>\n <textarea\n matInput placeholder=\"Type your message...\"\n formControlName=\"content\"\n (keydown.enter)=\"onSendMessage(); $event.preventDefault()\"\n ></textarea>\n </mat-form-field>\n <div style=\"display: flex; justify-content: flex-end; margin-top: -0.5rem;\">\n <button mat-raised-button color=\"primary\" (click)=\"onSendMessage()\"\n [disabled]=\"messages.invalid\">\n Send\n </button>\n </div>\n </div>\n </div>\n }\n }\n\n </div>\n }\n }\n\n <!-- Channel List with Subscription Controls using Chips -->\n @if ((channels$ | async); as channels) {\n @if ((subscribedChannels$ | async); as subscribedChannels) {\n <div style=\"padding: 1rem; background: #f5f5f5; border-radius: 4px; margin-top: 1rem;\">\n <strong>Subscribe to Channel(s)</strong>\n <div style=\"margin-top: 0.5rem;\">\n @if (channels.length === 0) {\n <div style=\"color: #666; font-style: italic;\">No channels available. Create one above.</div>\n } @else {\n <mat-chip-listbox\n aria-label=\"Channel selection\"\n [multiple]=\"true\">\n @for (channel of channels; track channel; let i = $index) {\n <mat-chip-option\n [selected]=\"isSubscribed(channel, subscribedChannels)\"\n (selectionChange)=\"onChipToggle($event, channel)\">\n {{ channel }}\n </mat-chip-option>\n }\n </mat-chip-listbox>\n }\n </div>\n </div>\n }\n }\n", styles: ["button[mat-raised-button],button[mat-stroked-button]{min-width:120px}mat-chip-option{min-width:120px;justify-content:center;border-radius:4px!important;padding:.25rem!important}\n"] }]
9857
- }], propDecorators: { server: [{
9858
- type: Input
9859
- }], wsServer: [{
9860
- type: Input
9861
- }], jwtToken: [{
9862
- type: Input
9863
- }], user: [{
9864
- type: Input
9865
- }], path: [{
9866
- type: Input
9867
- }] } });
9868
-
9869
- class WsNotificationsComponent {
9870
- constructor() {
9871
- this.destroy$ = new Subject();
9872
- this.fb = inject(FormBuilder);
9873
- this.notificationService = inject(NotificationServiceDemo);
9874
- this.stateService = inject(StateServiceDemo);
9875
- // Combine in-memory channels and today's DB channels, dedupe and strip prefix for display
9876
- this.todaysNotificationChannels$ = combineLatest([
9877
- this.notificationService.notificationChannels$.pipe(startWith$1([])),
9878
- this.notificationService.todaysNotificationChannels$.pipe(startWith$1([]))
9879
- ]).pipe(tap$1(([inMemory, fromDb]) => console.log('📢 In-memory channels:', inMemory, 'DB channels:', fromDb)), map$1(([inMemory, fromDb]) => {
9880
- // Merge both sources and dedupe
9881
- const allChannels = [...(inMemory || []), ...(fromDb || [])];
9882
- const uniqueChannels = [...new Set(allChannels)]
9883
- .filter(channel => channel.startsWith('MES-'))
9884
- .map(channel => channel.replace('MES-', ''));
9885
- return uniqueChannels.sort();
9886
- }), tap$1(channels => console.log('📢 Combined channels for display:', channels)), distinctUntilChanged$1((prev, curr) => prev.length === curr.length && prev.every((v, i) => v === curr[i])));
9887
- // Track subscribed notification channels, strip prefix for display
9888
- this.subscribedNotificationChannels$ = this.notificationService.subscribedNotificationChannels$.pipe(map$1(set => Array.from(set)
9889
- .filter(channel => channel.startsWith('MES-'))
9890
- .map(channel => channel.replace('MES-', ''))), startWith$1([]), distinctUntilChanged$1((prev, curr) => prev.length === curr.length && prev.every((v, i) => v === curr[i])));
9891
- // Notification messages
9892
- this.notificationMessages$ = this.notificationService.notificationMessages$;
9893
- this.connectionStatus$ = this.stateService.connectionStatus$;
9894
- // Form for creating new notification channels
9895
- this.newChannelName = this.fb.control('', [Validators.required, Validators.minLength(2)]);
9896
- // Channel connection selection
9897
- this.selectedConnectionChannel = this.fb.control(null);
9898
- this.connectedChannel = null;
9899
- // Table columns for notifications display
9900
- this.displayedColumns = ['date', 'user', 'message'];
9901
- // Form for date filter when subscribing
9902
- this.dateFilter = this.fb.group({
9903
- startDate: this.fb.control(null),
9904
- endDate: this.fb.control(null),
9905
- });
9906
- // Form for sending notifications
9907
- this.notificationForm = this.fb.group({
9908
- content: this.fb.control(null, Validators.required),
9909
- });
9910
- }
9911
- /**
9912
- * Helper to ensure channel has MES- prefix for outgoing communication
9913
- */
9914
- toNotificationChannel(channel) {
9915
- return channel.startsWith('MES-') ? channel : `MES-${channel}`;
9916
- }
9917
- get content() {
9918
- return this.notificationForm.get('content');
9919
- }
9920
- /**
9921
- * Check if currently connected to a channel
9922
- */
9923
- isChannelConnected(subscribedChannels) {
9924
- return this.connectedChannel !== null && subscribedChannels.includes(this.connectedChannel);
9925
- }
9926
- /**
9927
- * Connect to the selected channel
9928
- */
9929
- onConnectToChannel() {
9930
- const channel = this.selectedConnectionChannel.value;
9931
- if (!channel)
9932
- return;
9933
- this.connectedChannel = channel;
9934
- this.onSubscribeToChannel(channel);
9935
- }
9936
- /**
9937
- * Disconnect from the current channel
9938
- */
9939
- onDisconnectFromChannel() {
9940
- if (this.connectedChannel) {
9941
- this.onUnsubscribeFromChannel(this.connectedChannel);
9942
- this.connectedChannel = null;
9943
- }
9944
- }
9945
- ngOnInit() {
9946
- // Request today's notification channels when connection is established
9947
- this.connectionStatus$.pipe(filter$1(isConnected => isConnected), takeUntil$1(this.destroy$)).subscribe(() => {
9948
- // Request the list of today's notification channels from database
9949
- this.notificationService.getTodaysNotificationChannels();
9950
- });
9951
- }
9952
- ngOnDestroy() {
9953
- this.destroy$.next();
9954
- this.destroy$.complete();
9955
- }
9956
- /**
9957
- * Create a new notification channel
9958
- */
9959
- onCreateChannel() {
9960
- if (this.newChannelName.invalid)
9961
- return;
9962
- const channelName = this.newChannelName.value?.trim();
9963
- if (channelName) {
9964
- this.notificationService.createNotificationChannel(channelName);
9965
- this.newChannelName.reset();
9966
- }
9967
- }
9968
- /**
9969
- * Define and load previous channels
9970
- */
9971
- onDefinePreviousChannels() {
9972
- this.notificationService.definePreviousNotificationChannels();
9973
- }
9974
- /**
9975
- * Subscribe to a notification channel with optional date filter
9976
- */
9977
- onSubscribeToChannel(channel) {
9978
- const startDate = this.dateFilter.get('startDate')?.value;
9979
- const endDate = this.dateFilter.get('endDate')?.value;
9980
- const options = {};
9981
- if (startDate) {
9982
- // Set to start of day
9983
- const startOfDay = new Date(startDate);
9984
- startOfDay.setHours(0, 0, 0, 0);
9985
- options.startEpoch = Math.floor(startOfDay.getTime() / 1000);
9986
- }
9987
- if (endDate) {
9988
- // Set to end of day
9989
- const endOfDay = new Date(endDate);
9990
- endOfDay.setHours(23, 59, 59, 999);
9991
- options.endEpoch = Math.floor(endOfDay.getTime() / 1000);
9992
- }
9993
- this.notificationService.subscribeToNotificationChannel(channel, options, this.user);
9994
- }
9995
- /**
9996
- * Unsubscribe from a notification channel
9997
- */
9998
- onUnsubscribeFromChannel(channel) {
9999
- this.notificationService.unsubscribeFromNotificationChannel(channel);
10000
- }
10001
- /**
10002
- * Check if currently subscribed to a channel
10003
- */
10004
- isSubscribed(channel, subscribedChannels) {
10005
- return subscribedChannels.includes(channel);
10006
- }
10007
- /**
10008
- * Handle chip click - toggle subscription state
10009
- */
10010
- onChipClick(channel, subscribedChannels) {
10011
- const isCurrentlySubscribed = this.isSubscribed(channel, subscribedChannels);
10012
- if (isCurrentlySubscribed) {
10013
- this.onUnsubscribeFromChannel(channel);
10014
- }
10015
- else {
10016
- this.onSubscribeToChannel(channel);
10017
- }
10018
- }
10019
- /**
10020
- * Send a notification
10021
- */
10022
- onSendNotification() {
10023
- this.notificationForm.markAllAsTouched();
10024
- if (this.notificationForm.invalid)
10025
- return;
10026
- const channel = this.connectedChannel;
10027
- if (!channel)
10028
- return;
10029
- const messageContent = {
10030
- message: this.content.value
10031
- };
10032
- this.notificationService.sendNotification(channel, messageContent, this.user);
10033
- this.content.reset();
10034
- }
10035
- /**
10036
- * Format epoch timestamp to readable date/time
10037
- */
10038
- formatTimestamp(epoch) {
10039
- return new Date(epoch * 1000).toLocaleString();
10040
- }
10041
- /**
10042
- * Get today's date in YYYY-MM-DD format for date input default
10043
- */
10044
- getTodayDate() {
10045
- return new Date().toISOString().split('T')[0];
10046
- }
10047
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsNotificationsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10048
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: WsNotificationsComponent, selector: "app-ws-notifications", inputs: { server: "server", wsServer: "wsServer", jwtToken: "jwtToken", user: "user" }, ngImport: i0, template: "<div style=\"display: flex; gap: 1rem; flex-direction: column; margin-top: 1rem;\">\n\n <!-- Channel Creation Section -->\n <div style=\"padding: 1rem; background: #e8f5e9; border-radius: 4px;\">\n <strong>Create Notification Channel</strong>\n <div style=\"display: flex; flex-direction: column; margin-top: 0.5rem;\">\n <mat-form-field appearance=\"outline\" style=\"width: 100%;\">\n <mat-label>Channel Name</mat-label>\n <input\n matInput\n [formControl]=\"newChannelName\"\n placeholder=\"Enter notification channel name\"\n (keydown.enter)=\"onCreateChannel()\"\n >\n </mat-form-field>\n <div style=\"display: flex; justify-content: flex-end; gap: 0.5rem;\">\n <!-- <button mat-raised-button color=\"accent\" (click)=\"onDefinePreviousChannels()\">\n Define Previous Channels\n </button> -->\n <button mat-raised-button color=\"primary\" (click)=\"onCreateChannel()\"\n [disabled]=\"newChannelName.invalid\">\n Create Channel 1\n </button>\n </div>\n </div>\n </div>\n\n <!-- Channel Connection Section - Only shown when NOT connected -->\n @if ((subscribedNotificationChannels$ | async); as subscribedChannels) {\n @if (!isChannelConnected(subscribedChannels)) {\n <div style=\"padding: 1rem; background: #e3f2fd; border-radius: 4px;\">\n <strong>Connect to Channel</strong>\n <div style=\"display: flex; gap: 1rem; margin-top: 0.5rem; flex-wrap: wrap; align-items: flex-start;\">\n <!-- Channel Selection (from today's DB data) -->\n <mat-form-field appearance=\"outline\" style=\"flex: 1; min-width: 200px;\">\n <mat-label>Select Channel</mat-label>\n <mat-select [formControl]=\"selectedConnectionChannel\" placeholder=\"Select a notification channel\">\n @for (channel of (todaysNotificationChannels$ | async) || []; track channel) {\n <mat-option [value]=\"channel\">{{ channel }}</mat-option>\n }\n @empty {\n <mat-option disabled>No channels with data for today</mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <!-- Date Filter -->\n <mat-form-field appearance=\"outline\" style=\"flex: 1; min-width: 200px;\" [formGroup]=\"dateFilter\">\n <mat-label>Start Date</mat-label>\n <input matInput [matDatepicker]=\"startPicker\" formControlName=\"startDate\">\n <mat-datepicker-toggle matIconSuffix [for]=\"startPicker\"></mat-datepicker-toggle>\n <mat-datepicker #startPicker></mat-datepicker>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" style=\"flex: 1; min-width: 200px;\" [formGroup]=\"dateFilter\">\n <mat-label>End Date</mat-label>\n <input matInput [matDatepicker]=\"endPicker\" formControlName=\"endDate\">\n <mat-datepicker-toggle matIconSuffix [for]=\"endPicker\"></mat-datepicker-toggle>\n <mat-datepicker #endPicker></mat-datepicker>\n </mat-form-field>\n </div>\n\n <div style=\"display: flex; gap: 0.5rem;\">\n <span style=\"flex:1\"></span>\n <button\n mat-raised-button color=\"primary\"\n (click)=\"onConnectToChannel()\"\n [disabled]=\"!selectedConnectionChannel.value\"\n >\n Connect\n </button>\n </div>\n </div>\n }\n }\n\n <!-- Send Notification Section -->\n @if ((subscribedNotificationChannels$ | async); as subscribedChannels) {\n @if (subscribedChannels.length > 0) {\n <div\n style=\"padding: 1rem; background: #fff8e1; border-radius: 4px;\"\n [formGroup]=\"notificationForm\"\n >\n <strong>Send Notification</strong>\n\n <!-- Notification Content -->\n <div style=\"display: flex; flex-direction: column; margin-top: 0.5rem;\">\n <mat-form-field style=\"width: 100%;\" appearance=\"outline\">\n <mat-label>Notification Message</mat-label>\n <textarea\n matInput placeholder=\"Type your notification...\"\n formControlName=\"content\"\n rows=\"2\"\n (keydown.enter)=\"onSendNotification(); $event.preventDefault()\"\n ></textarea>\n </mat-form-field>\n <div style=\"display: flex; justify-content: flex-end; margin-top: -0.5rem;\">\n <button\n mat-raised-button color=\"accent\"\n (click)=\"onSendNotification()\"\n [disabled]=\"notificationForm.invalid\"\n >\n Send Notification\n </button>\n </div>\n </div>\n </div>\n }\n }\n\n <!-- Notifications Table - Only shown when connected -->\n @if ((subscribedNotificationChannels$ | async); as subscribedChannels) {\n @if (isChannelConnected(subscribedChannels)) {\n @if ((notificationMessages$ | async); as notifications) {\n <div>\n <div style=\"display: flex; justify-content: space-between; align-items: center;\">\n <strong>Notifications ({{ notifications.length }})</strong>\n </div>\n\n @if (notifications.length > 0) {\n <div style=\"margin-top: 0.5rem; height: 400px; overflow: auto; border: 1px solid #888; border-radius: 4px;\">\n <table mat-table [dataSource]=\"notifications\" style=\"width: 100%;\">\n\n <!-- Date Column -->\n <ng-container matColumnDef=\"date\">\n <th mat-header-cell *matHeaderCellDef style=\"width: 180px;\">Date</th>\n <td mat-cell *matCellDef=\"let notification\">\n {{ formatTimestamp(notification.created) | date }}\n </td>\n </ng-container>\n\n <!-- User Column -->\n <ng-container matColumnDef=\"user\">\n <th mat-header-cell *matHeaderCellDef style=\"width: 150px;\">User</th>\n <td mat-cell *matCellDef=\"let notification\">\n <strong>{{ notification.user_name }}</strong>\n </td>\n </ng-container>\n\n <!-- Message Column -->\n <ng-container matColumnDef=\"message\">\n <th mat-header-cell *matHeaderCellDef>Message</th>\n <td mat-cell *matCellDef=\"let notification\">\n <div class=\"pill\" [style.background-color]=\"notification.content.sessionId.color || '#000000'\">\n {{ notification.content?.message || notification.content }}\n </div>\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns; sticky: true\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n </div>\n } @else {\n <div style=\"text-align: center; color: #666; padding: 2rem;\">\n <mat-icon style=\"font-size: 48px; height: 48px; width: 48px; opacity: 0.5;\">notifications_none</mat-icon>\n <p>No notifications yet. Waiting for notifications on this channel...</p>\n </div>\n }\n </div>\n\n <!-- Disconnect Button - Below the table -->\n <div style=\"display: flex; justify-content: right; padding: 1rem;\">\n <button mat-raised-button color=\"warn\" (click)=\"onDisconnectFromChannel()\">\n Disconnect from Channel\n </button>\n </div>\n }\n }\n }\n\n</div>\n", styles: [".colored-message{font-weight:500;padding:4px 8px;border-radius:4px;display:inline-block;max-width:100%;word-wrap:break-word}.colored-message.black-text{color:#000!important}.colored-message.white-text{color:#fff!important;text-shadow:0 0 2px rgba(0,0,0,.5)}td.mat-mdc-cell{padding:8px 12px}.pill{border-radius:1rem;padding:.5rem 1rem;color:#fff}\n"], dependencies: [{ kind: "directive", type: i2$1.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: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "component", type: i5.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"], 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: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i9.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i9.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i9.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i9.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i9.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i9.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i9.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i9.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i9.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "directive", type: i13.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i9$1.MatDatepicker, selector: "mat-datepicker", exportAs: ["matDatepicker"] }, { kind: "directive", type: i9$1.MatDatepickerInput, selector: "input[matDatepicker]", inputs: ["matDatepicker", "min", "max", "matDatepickerFilter"], exportAs: ["matDatepickerInput"] }, { kind: "component", type: i9$1.MatDatepickerToggle, selector: "mat-datepicker-toggle", inputs: ["for", "tabIndex", "aria-label", "disabled", "disableRipple"], exportAs: ["matDatepickerToggle"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.DatePipe, name: "date" }] }); }
10049
- }
10050
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsNotificationsComponent, decorators: [{
10051
- type: Component,
10052
- args: [{ selector: 'app-ws-notifications', standalone: false, template: "<div style=\"display: flex; gap: 1rem; flex-direction: column; margin-top: 1rem;\">\n\n <!-- Channel Creation Section -->\n <div style=\"padding: 1rem; background: #e8f5e9; border-radius: 4px;\">\n <strong>Create Notification Channel</strong>\n <div style=\"display: flex; flex-direction: column; margin-top: 0.5rem;\">\n <mat-form-field appearance=\"outline\" style=\"width: 100%;\">\n <mat-label>Channel Name</mat-label>\n <input\n matInput\n [formControl]=\"newChannelName\"\n placeholder=\"Enter notification channel name\"\n (keydown.enter)=\"onCreateChannel()\"\n >\n </mat-form-field>\n <div style=\"display: flex; justify-content: flex-end; gap: 0.5rem;\">\n <!-- <button mat-raised-button color=\"accent\" (click)=\"onDefinePreviousChannels()\">\n Define Previous Channels\n </button> -->\n <button mat-raised-button color=\"primary\" (click)=\"onCreateChannel()\"\n [disabled]=\"newChannelName.invalid\">\n Create Channel 1\n </button>\n </div>\n </div>\n </div>\n\n <!-- Channel Connection Section - Only shown when NOT connected -->\n @if ((subscribedNotificationChannels$ | async); as subscribedChannels) {\n @if (!isChannelConnected(subscribedChannels)) {\n <div style=\"padding: 1rem; background: #e3f2fd; border-radius: 4px;\">\n <strong>Connect to Channel</strong>\n <div style=\"display: flex; gap: 1rem; margin-top: 0.5rem; flex-wrap: wrap; align-items: flex-start;\">\n <!-- Channel Selection (from today's DB data) -->\n <mat-form-field appearance=\"outline\" style=\"flex: 1; min-width: 200px;\">\n <mat-label>Select Channel</mat-label>\n <mat-select [formControl]=\"selectedConnectionChannel\" placeholder=\"Select a notification channel\">\n @for (channel of (todaysNotificationChannels$ | async) || []; track channel) {\n <mat-option [value]=\"channel\">{{ channel }}</mat-option>\n }\n @empty {\n <mat-option disabled>No channels with data for today</mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <!-- Date Filter -->\n <mat-form-field appearance=\"outline\" style=\"flex: 1; min-width: 200px;\" [formGroup]=\"dateFilter\">\n <mat-label>Start Date</mat-label>\n <input matInput [matDatepicker]=\"startPicker\" formControlName=\"startDate\">\n <mat-datepicker-toggle matIconSuffix [for]=\"startPicker\"></mat-datepicker-toggle>\n <mat-datepicker #startPicker></mat-datepicker>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" style=\"flex: 1; min-width: 200px;\" [formGroup]=\"dateFilter\">\n <mat-label>End Date</mat-label>\n <input matInput [matDatepicker]=\"endPicker\" formControlName=\"endDate\">\n <mat-datepicker-toggle matIconSuffix [for]=\"endPicker\"></mat-datepicker-toggle>\n <mat-datepicker #endPicker></mat-datepicker>\n </mat-form-field>\n </div>\n\n <div style=\"display: flex; gap: 0.5rem;\">\n <span style=\"flex:1\"></span>\n <button\n mat-raised-button color=\"primary\"\n (click)=\"onConnectToChannel()\"\n [disabled]=\"!selectedConnectionChannel.value\"\n >\n Connect\n </button>\n </div>\n </div>\n }\n }\n\n <!-- Send Notification Section -->\n @if ((subscribedNotificationChannels$ | async); as subscribedChannels) {\n @if (subscribedChannels.length > 0) {\n <div\n style=\"padding: 1rem; background: #fff8e1; border-radius: 4px;\"\n [formGroup]=\"notificationForm\"\n >\n <strong>Send Notification</strong>\n\n <!-- Notification Content -->\n <div style=\"display: flex; flex-direction: column; margin-top: 0.5rem;\">\n <mat-form-field style=\"width: 100%;\" appearance=\"outline\">\n <mat-label>Notification Message</mat-label>\n <textarea\n matInput placeholder=\"Type your notification...\"\n formControlName=\"content\"\n rows=\"2\"\n (keydown.enter)=\"onSendNotification(); $event.preventDefault()\"\n ></textarea>\n </mat-form-field>\n <div style=\"display: flex; justify-content: flex-end; margin-top: -0.5rem;\">\n <button\n mat-raised-button color=\"accent\"\n (click)=\"onSendNotification()\"\n [disabled]=\"notificationForm.invalid\"\n >\n Send Notification\n </button>\n </div>\n </div>\n </div>\n }\n }\n\n <!-- Notifications Table - Only shown when connected -->\n @if ((subscribedNotificationChannels$ | async); as subscribedChannels) {\n @if (isChannelConnected(subscribedChannels)) {\n @if ((notificationMessages$ | async); as notifications) {\n <div>\n <div style=\"display: flex; justify-content: space-between; align-items: center;\">\n <strong>Notifications ({{ notifications.length }})</strong>\n </div>\n\n @if (notifications.length > 0) {\n <div style=\"margin-top: 0.5rem; height: 400px; overflow: auto; border: 1px solid #888; border-radius: 4px;\">\n <table mat-table [dataSource]=\"notifications\" style=\"width: 100%;\">\n\n <!-- Date Column -->\n <ng-container matColumnDef=\"date\">\n <th mat-header-cell *matHeaderCellDef style=\"width: 180px;\">Date</th>\n <td mat-cell *matCellDef=\"let notification\">\n {{ formatTimestamp(notification.created) | date }}\n </td>\n </ng-container>\n\n <!-- User Column -->\n <ng-container matColumnDef=\"user\">\n <th mat-header-cell *matHeaderCellDef style=\"width: 150px;\">User</th>\n <td mat-cell *matCellDef=\"let notification\">\n <strong>{{ notification.user_name }}</strong>\n </td>\n </ng-container>\n\n <!-- Message Column -->\n <ng-container matColumnDef=\"message\">\n <th mat-header-cell *matHeaderCellDef>Message</th>\n <td mat-cell *matCellDef=\"let notification\">\n <div class=\"pill\" [style.background-color]=\"notification.content.sessionId.color || '#000000'\">\n {{ notification.content?.message || notification.content }}\n </div>\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns; sticky: true\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n </div>\n } @else {\n <div style=\"text-align: center; color: #666; padding: 2rem;\">\n <mat-icon style=\"font-size: 48px; height: 48px; width: 48px; opacity: 0.5;\">notifications_none</mat-icon>\n <p>No notifications yet. Waiting for notifications on this channel...</p>\n </div>\n }\n </div>\n\n <!-- Disconnect Button - Below the table -->\n <div style=\"display: flex; justify-content: right; padding: 1rem;\">\n <button mat-raised-button color=\"warn\" (click)=\"onDisconnectFromChannel()\">\n Disconnect from Channel\n </button>\n </div>\n }\n }\n }\n\n</div>\n", styles: [".colored-message{font-weight:500;padding:4px 8px;border-radius:4px;display:inline-block;max-width:100%;word-wrap:break-word}.colored-message.black-text{color:#000!important}.colored-message.white-text{color:#fff!important;text-shadow:0 0 2px rgba(0,0,0,.5)}td.mat-mdc-cell{padding:8px 12px}.pill{border-radius:1rem;padding:.5rem 1rem;color:#fff}\n"] }]
10053
- }], propDecorators: { server: [{
10054
- type: Input
10055
- }], wsServer: [{
10056
- type: Input
10057
- }], jwtToken: [{
10058
- type: Input
10059
- }], user: [{
10060
- type: Input
10061
- }] } });
10062
-
10063
- class WsAiMessagingComponent {
10064
- constructor() { }
10065
- ngOnInit() {
10066
- }
10067
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsAiMessagingComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10068
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: WsAiMessagingComponent, selector: "app-ws-ai-messaging", ngImport: i0, template: "<p>\n ws-ai-messaging coming soon!\n</p>\n", styles: [""] }); }
10069
- }
10070
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsAiMessagingComponent, decorators: [{
10071
- type: Component,
10072
- args: [{ selector: 'app-ws-ai-messaging', standalone: false, template: "<p>\n ws-ai-messaging coming soon!\n</p>\n" }]
10073
- }], ctorParameters: () => [] });
10074
-
10075
- class WsChatsComponent {
10076
- constructor() { }
10077
- ngOnInit() {
10078
- }
10079
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsChatsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10080
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: WsChatsComponent, selector: "app-ws-chats", ngImport: i0, template: "<p>\n ws-chats coming soon!\n</p>\n", styles: [""] }); }
10081
- }
10082
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsChatsComponent, decorators: [{
10083
- type: Component,
10084
- args: [{ selector: 'app-ws-chats', standalone: false, template: "<p>\n ws-chats coming soon!\n</p>\n" }]
10085
- }], ctorParameters: () => [] });
10086
-
10087
- class RequestManagerWsDemoComponent {
10088
- constructor() {
10089
- this.httpManagerService = inject(HTTPManagerService);
10090
- this.stateService = inject(StateServiceDemo);
10091
- this.fb = inject(FormBuilder);
10092
- this.path = ['ai', 'tests'];
10093
- this.user$ = this.stateService.user$;
10094
- this.attempts$ = this.stateService.wsRetryAttempts$;
10095
- this.nextRetry$ = this.stateService.wsNextRetry$;
10096
- this.connectionStatus$ = this.stateService.connectionStatus$;
10097
- this.data$ = this.stateService.data$;
10098
- this.isPending$ = this.stateService.isPending$;
10099
- }
10100
- ngOnInit() {
10101
- this.stateService.updateConnection(this.server, this.wsServer, this.jwtToken, this.user, this.path);
10102
- }
10103
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerWsDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10104
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: RequestManagerWsDemoComponent, selector: "app-request-manager-ws-demo", inputs: { server: "server", wsServer: "wsServer", jwtToken: "jwtToken", user: "user", path: "path" }, ngImport: i0, template: "<div style=\"margin: 2rem;\">\n\n <h2 style=\"display: flex;\">\n <span style=\"flex:1\">HTTP Request State Manager - Websockets</span>\n @if ((connectionStatus$ | async); as connected) {\n <span>\n WS -\n <span style=\"color: green;\">Connected</span>\n </span>\n } @else {\n <span style=\"color: red;\">Disconnected {{ attempts$ | async }} - {{ nextRetry$ |async }}</span>\n }\n </h2>\n\n <div>\n\n @if ((user$ | async); as userInfo) {\n <div>\n <mat-toolbar>\n <div style=\"display: flex; flex:1\">\n <div style=\"flex:1\">{{ userInfo.name }}</div>\n <div>({{ userInfo.ldap }})</div>\n </div>\n </mat-toolbar>\n </div>\n }\n\n @if ((isPending$ | async)) {\n <div>\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n </div>\n }\n\n <mat-tab-group animationDuration=\"0ms\" [selectedIndex]=\"1\">\n\n <mat-tab label=\"WS - Data Control\">\n <!-- DATA CONTROL -->\n <app-ws-data-control\n [server]=\"server\"\n [wsServer]=\"wsServer\"\n [jwtToken]=\"jwtToken\"\n [user]=\"user\"\n [path]=\"path\"\n ></app-ws-data-control>\n\n </mat-tab>\n\n <mat-tab label=\"WS - Messaging\">\n <!-- MESSAGING -->\n <app-ws-messaging\n [server]=\"server\"\n [wsServer]=\"wsServer\"\n [jwtToken]=\"jwtToken\"\n [user]=\"user\"\n [path]=\"path\"\n ></app-ws-messaging>\n\n </mat-tab>\n\n <mat-tab label=\"WS - Notifications\">\n <!-- WS - Notifications -->\n <app-ws-notifications\n [server]=\"server\"\n [wsServer]=\"wsServer\"\n [jwtToken]=\"jwtToken\"\n [user]=\"user\"\n ></app-ws-notifications>\n </mat-tab>\n\n <mat-tab label=\"WS - Chats\" [disabled]=\"true\">\n <!-- WS - Chats -->\n <app-ws-chats></app-ws-chats>\n </mat-tab>\n\n <mat-tab label=\"WS - AI Messaging\" [disabled]=\"true\">\n <!-- WS - AI Messaging -->\n <app-ws-ai-messaging></app-ws-ai-messaging>\n </mat-tab>\n\n </mat-tab-group>\n</div>\n\n</div>\n\n", styles: [""], dependencies: [{ kind: "component", type: i1$2.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass"], exportAs: ["matTab"] }, { kind: "component", type: i1$2.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "component", type: i8$1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: i3$2.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "component", type: WsDataControlComponent, selector: "app-ws-data-control", inputs: ["server", "wsServer", "jwtToken", "user", "path"] }, { kind: "component", type: WsMessagingComponent, selector: "app-ws-messaging", inputs: ["server", "wsServer", "jwtToken", "user", "path"] }, { kind: "component", type: WsNotificationsComponent, selector: "app-ws-notifications", inputs: ["server", "wsServer", "jwtToken", "user"] }, { kind: "component", type: WsAiMessagingComponent, selector: "app-ws-ai-messaging" }, { kind: "component", type: WsChatsComponent, selector: "app-ws-chats" }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }] }); }
10105
- }
10106
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerWsDemoComponent, decorators: [{
10107
- type: Component,
10108
- args: [{ selector: 'app-request-manager-ws-demo', standalone: false, template: "<div style=\"margin: 2rem;\">\n\n <h2 style=\"display: flex;\">\n <span style=\"flex:1\">HTTP Request State Manager - Websockets</span>\n @if ((connectionStatus$ | async); as connected) {\n <span>\n WS -\n <span style=\"color: green;\">Connected</span>\n </span>\n } @else {\n <span style=\"color: red;\">Disconnected {{ attempts$ | async }} - {{ nextRetry$ |async }}</span>\n }\n </h2>\n\n <div>\n\n @if ((user$ | async); as userInfo) {\n <div>\n <mat-toolbar>\n <div style=\"display: flex; flex:1\">\n <div style=\"flex:1\">{{ userInfo.name }}</div>\n <div>({{ userInfo.ldap }})</div>\n </div>\n </mat-toolbar>\n </div>\n }\n\n @if ((isPending$ | async)) {\n <div>\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n </div>\n }\n\n <mat-tab-group animationDuration=\"0ms\" [selectedIndex]=\"1\">\n\n <mat-tab label=\"WS - Data Control\">\n <!-- DATA CONTROL -->\n <app-ws-data-control\n [server]=\"server\"\n [wsServer]=\"wsServer\"\n [jwtToken]=\"jwtToken\"\n [user]=\"user\"\n [path]=\"path\"\n ></app-ws-data-control>\n\n </mat-tab>\n\n <mat-tab label=\"WS - Messaging\">\n <!-- MESSAGING -->\n <app-ws-messaging\n [server]=\"server\"\n [wsServer]=\"wsServer\"\n [jwtToken]=\"jwtToken\"\n [user]=\"user\"\n [path]=\"path\"\n ></app-ws-messaging>\n\n </mat-tab>\n\n <mat-tab label=\"WS - Notifications\">\n <!-- WS - Notifications -->\n <app-ws-notifications\n [server]=\"server\"\n [wsServer]=\"wsServer\"\n [jwtToken]=\"jwtToken\"\n [user]=\"user\"\n ></app-ws-notifications>\n </mat-tab>\n\n <mat-tab label=\"WS - Chats\" [disabled]=\"true\">\n <!-- WS - Chats -->\n <app-ws-chats></app-ws-chats>\n </mat-tab>\n\n <mat-tab label=\"WS - AI Messaging\" [disabled]=\"true\">\n <!-- WS - AI Messaging -->\n <app-ws-ai-messaging></app-ws-ai-messaging>\n </mat-tab>\n\n </mat-tab-group>\n</div>\n\n</div>\n\n" }]
10109
- }], propDecorators: { server: [{
10110
- type: Input
10111
- }], wsServer: [{
10112
- type: Input
10113
- }], jwtToken: [{
10114
- type: Input
10115
- }], user: [{
10116
- type: Input
10117
- }], path: [{
10118
- type: Input
10119
- }] } });
10120
-
10121
- class Settings {
10122
- constructor(enum_1 = [], enum_2 = [], enum_3 = [], enum_4 = []) {
10123
- this.enum_1 = enum_1;
10124
- this.enum_2 = enum_2;
10125
- this.enum_3 = enum_3;
10126
- this.enum_4 = enum_4;
10127
- }
10128
- static adapt(item) {
10129
- return new Settings(item?.enum_1, item?.enum_2, item?.enum_3, item?.enum_4);
10130
- }
10131
- }
10132
-
10133
- class SettingsStateService extends StoreStateManagerService {
10134
- constructor() {
10135
- super(StateStorageOptions.adapt({
10136
- store: 'sampleStore',
10137
- options: SettingOptions.adapt({
10138
- storage: StorageType.SESSION,
10139
- encrypted: false
10140
- }),
10141
- model: Settings.adapt,
10142
- }));
10143
- }
10144
- updateEnum_1(value) {
10145
- this.updateData(value);
10146
- }
10147
- updateEnum_2(value) {
10148
- this.updateData(value);
10149
- }
10150
- getEnum(key) {
10151
- return this.data$.pipe(map$1(item => item[key]));
10152
- }
10153
- getEnum_1() {
10154
- return this.data$.pipe(map$1(item => item?.enum_1));
10155
- }
10156
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SettingsStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
10157
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SettingsStateService }); }
10158
- }
10159
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SettingsStateService, decorators: [{
10160
- type: Injectable
10161
- }], ctorParameters: () => [] });
10162
-
10163
- class StoreStateManagerDemoComponent {
10164
- constructor() {
10165
- this.settingsStateService = inject(SettingsStateService);
10166
- this.dataState$ = this.settingsStateService.data$;
10167
- this.dataEnum$ = this.settingsStateService.getEnum_1();
10168
- }
10169
- ngOnInit() {
10170
- }
10171
- onUpdateEnum_1() {
10172
- const rnd = RandomNumber(1000, 9999);
10173
- this.settingsStateService.updateEnum_1({ enum_1: [{ users: [1, 2, 3, rnd], random: rnd }] });
10174
- }
10175
- onUpdateEnum_2() {
10176
- const rnd = RandomNumber(1000, 9999);
10177
- this.settingsStateService.updateEnum_2({ enum_2: [{ users: [1, 2, 3, rnd], random: rnd }] });
10178
- }
10179
- onGetEnum_1() {
10180
- this.settingsStateService.getEnum_1().subscribe(data => console.log('DUMP:', data));
10181
- }
10182
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StoreStateManagerDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10183
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: StoreStateManagerDemoComponent, selector: "app-store-state-manager-demo", providers: [SettingsStateService], ngImport: i0, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n <span>Store State Manager</span>\n <span style=\"margin-left: .5rem;\">\n <mat-icon color=\"accent\">fiber_new</mat-icon>\n </span>\n </h2>\n\n {{ dataState$ | async | json}}\n\n <div style=\"display: flex; gap:.5rem; margin-top: 1rem; margin-bottom: 1rem;\">\n <button mat-stroked-button (click)=\"onUpdateEnum_1()\">Update Start</button>\n <button mat-stroked-button (click)=\"onUpdateEnum_2()\">Update End</button>\n <button mat-stroked-button (click)=\"onGetEnum_1()\">Dump</button>\n </div>\n\n <div>\n <h3 style=\"margin: 0;\">Enum 1</h3>\n {{ dataEnum$ | async | json}}\n </div>\n\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }] }); }
10184
- }
10185
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StoreStateManagerDemoComponent, decorators: [{
10186
- type: Component,
10187
- args: [{ selector: 'app-store-state-manager-demo', providers: [SettingsStateService], standalone: false, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n <span>Store State Manager</span>\n <span style=\"margin-left: .5rem;\">\n <mat-icon color=\"accent\">fiber_new</mat-icon>\n </span>\n </h2>\n\n {{ dataState$ | async | json}}\n\n <div style=\"display: flex; gap:.5rem; margin-top: 1rem; margin-bottom: 1rem;\">\n <button mat-stroked-button (click)=\"onUpdateEnum_1()\">Update Start</button>\n <button mat-stroked-button (click)=\"onUpdateEnum_2()\">Update End</button>\n <button mat-stroked-button (click)=\"onGetEnum_1()\">Dump</button>\n </div>\n\n <div>\n <h3 style=\"margin: 0;\">Enum 1</h3>\n {{ dataEnum$ | async | json}}\n </div>\n\n</div>\n" }]
10188
- }] });
10189
-
10190
- class DatabaseDataDemoComponent {
10191
- constructor() {
10192
- this.db = inject(DatabaseManagerService);
10193
- this.destroy$ = new Subject();
10194
- this.dataToDisplay = [];
10195
- this.dataSource = new DatabaseDataSource(this.dataToDisplay);
10196
- this.displayedColumns = ['id', 'last_name', 'age', 'amount'];
10197
- this.names = ['Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Garcia', 'Miller', 'Davis'];
10198
- }
10199
- ngOnDestroy() {
10200
- this.destroy$.next();
10201
- this.destroy$.complete();
10202
- }
10203
- addData() {
10204
- const newRecord = {
10205
- id: Math.max(...this.dataToDisplay.map(r => r.id), 0) + 1,
10206
- last_name: this.names[Math.floor(Math.random() * this.names.length)],
10207
- age: Math.floor(Math.random() * 80) + 18,
10208
- amount: Math.floor(Math.random() * 1000)
10209
- };
10210
- this.dataToDisplay = [...this.dataToDisplay, newRecord];
10211
- this.db.createTableRecord('sample_table', newRecord).subscribe(() => this.refreshData());
10212
- }
10213
- removeData() {
10214
- if (this.dataToDisplay.length > 0) {
10215
- const removedRecord = this.dataToDisplay[this.dataToDisplay.length - 1];
10216
- this.dataToDisplay = this.dataToDisplay.slice(0, -1);
10217
- this.db.deleteTableRecord('sample_table', removedRecord.id).subscribe(() => this.refreshData());
10218
- }
10219
- }
10220
- updateData() {
10221
- if (this.dataToDisplay.length === 0)
10222
- return;
10223
- const randomIndex = Math.floor(Math.random() * this.dataToDisplay.length);
10224
- const recordToUpdate = this.dataToDisplay[randomIndex];
10225
- const updatedRecord = {
10226
- ...recordToUpdate,
10227
- last_name: this.names[Math.floor(Math.random() * this.names.length)],
10228
- age: Math.floor(Math.random() * 80) + 18,
10229
- amount: Math.floor(Math.random() * 1000)
10230
- };
10231
- this.dataToDisplay = this.dataToDisplay.map((r, index) => index === randomIndex ? updatedRecord : r);
10232
- this.db.updateTableRecord('sample_table', updatedRecord).subscribe(() => this.refreshData());
10233
- }
10234
- clearAllData() {
10235
- if (this.dataToDisplay.length === 0)
10236
- return;
10237
- const idsToDelete = this.dataToDisplay.map(r => r.id);
10238
- this.dataToDisplay = [];
10239
- this.db.deleteTableRecords('sample_table', idsToDelete).subscribe(() => this.refreshData());
10240
- }
10241
- refreshData() {
10242
- this.db.getTableRecords('sample_table').subscribe((records) => {
10243
- this.dataToDisplay = records || [];
10244
- this.dataSource.setData(this.dataToDisplay);
10245
- });
10246
- }
10247
- ngOnInit() {
10248
- const tableDef = TableSchemaDef.adapt({
10249
- table: 'sample_table',
10250
- schema: '++id, last_name, age, amount'
10251
- });
10252
- this.db.createDatabaseTable(tableDef).subscribe(() => {
10253
- console.log('Database Table Initialized');
10254
- // hasTable
10255
- this.db.hasDatabaseTable('sample_table')
10256
- .subscribe(data => console.log('hasDatabaseTable:', data));
10257
- // Tables
10258
- this.db.getDatabaseTables()
10259
- .subscribe(data => console.log('getDatabaseTables:', data));
10260
- // getTable
10261
- this.db.getDatabaseTable('sample_table')
10262
- .subscribe(data => console.log('getDatabaseTable:', data));
10263
- // Table Schema
10264
- this.db.getDatabaseTableSchema('sample_table')
10265
- .subscribe(data => console.log('getDatabaseTableSchema:', data));
10266
- // Insert Table Records
10267
- const records = [
10268
- { id: 1, age: 12, last_name: 'Bonifacio', amount: 80 },
10269
- { id: 2, age: 15, last_name: 'Johns', amount: 700 },
10270
- { id: 3, age: 22, last_name: 'Kims', amount: 234 },
10271
- { id: 4, age: 45, last_name: 'Harrys', amount: 1200 },
10272
- ];
10273
- this.db.createTableRecords('sample_table', records).subscribe(() => {
10274
- this.refreshData();
10275
- });
10276
- });
10277
- }
10278
- createTable(tableDef) {
10279
- return this.db.createDatabaseTable(tableDef);
10280
- }
10281
- // CRUD
10282
- findTableRecords(table, column, value) {
10283
- return this.db.findTableRecords(table, column, value)
10284
- .pipe(tap((data) => console.log('findTableRecords:', data)));
10285
- }
10286
- findTableRecord(table, column, value) {
10287
- return this.db.findTableRecord(table, column, value)
10288
- .pipe(tap((data) => console.log('findTableRecord:', data)));
10289
- }
10290
- getTableRecords(table) {
10291
- return this.db.getTableRecords(table)
10292
- .pipe(tap((data) => console.log('getTableRecords:', data)));
10293
- }
10294
- getTableRecord(table, id) {
10295
- return this.db.getTableRecord(table, id)
10296
- .pipe(tap((data) => console.log('getTableRecord:', data)));
10297
- }
10298
- createTableRecord(table, record) {
10299
- return this.db.createTableRecord(table, record)
10300
- .pipe(tap((data) => console.log('createTableRecord:', data)));
10301
- }
10302
- updateTableRecord(table, record) {
10303
- return this.db.updateTableRecord(table, record)
10304
- .pipe(tap((data) => console.log('updateTableRecord:', data)));
10305
- }
10306
- deleteTableRecord(table, id) {
10307
- return this.db.deleteTableRecord(table, id)
10308
- .pipe(tap((data) => console.log('deleteTableRecord:', data)));
10309
- }
10310
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DatabaseDataDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10311
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: DatabaseDataDemoComponent, selector: "app-database-data-demo", ngImport: i0, template: "<div style=\"padding: 2rem;\">\n\n <h2>\n Database Manager Service Demo\n </h2>\n\n <div style=\"margin-bottom: 1rem; display: flex; gap: 1rem;\">\n <button mat-stroked-button\n color=\"primary\"\n (click)=\"clearAllData()\"\n >\n Clear Data\n </button>\n <button mat-stroked-button\n color=\"primary\"\n (click)=\"updateData()\"\n >\n Update Data\n </button>\n <div style=\"flex:1\"></div>\n <button\n mat-stroked-button\n color=\"warn\"\n [disabled]=\"!dataToDisplay.length\"\n (click)=\"removeData()\"\n >\n Remove Data\n </button>\n <button mat-stroked-button\n color=\"primary\"\n (click)=\"addData()\"\n >\n Add Data\n </button>\n </div>\n\n <div class=\"table-container\">\n <table mat-table [dataSource]=\"dataSource\">\n\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef>ID</th>\n <td mat-cell *matCellDef=\"let element\">{{element.id}}</td>\n </ng-container>\n\n <ng-container matColumnDef=\"last_name\">\n <th mat-header-cell *matHeaderCellDef>Last Name</th>\n <td mat-cell *matCellDef=\"let element\">{{element.last_name}}</td>\n </ng-container>\n\n <ng-container matColumnDef=\"age\">\n <th mat-header-cell *matHeaderCellDef>Age</th>\n <td mat-cell *matCellDef=\"let element\">{{element.age}}</td>\n </ng-container>\n\n <ng-container matColumnDef=\"amount\">\n <th mat-header-cell *matHeaderCellDef>Amount</th>\n <td mat-cell *matCellDef=\"let element\">{{element.amount | currency}}</td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n </div>\n\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i9.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i9.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i9.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i9.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i9.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i9.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i9.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i9.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i9.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i9.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "pipe", type: i1$1.CurrencyPipe, name: "currency" }] }); }
10312
- }
10313
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DatabaseDataDemoComponent, decorators: [{
10314
- type: Component,
10315
- args: [{ selector: 'app-database-data-demo', standalone: false, template: "<div style=\"padding: 2rem;\">\n\n <h2>\n Database Manager Service Demo\n </h2>\n\n <div style=\"margin-bottom: 1rem; display: flex; gap: 1rem;\">\n <button mat-stroked-button\n color=\"primary\"\n (click)=\"clearAllData()\"\n >\n Clear Data\n </button>\n <button mat-stroked-button\n color=\"primary\"\n (click)=\"updateData()\"\n >\n Update Data\n </button>\n <div style=\"flex:1\"></div>\n <button\n mat-stroked-button\n color=\"warn\"\n [disabled]=\"!dataToDisplay.length\"\n (click)=\"removeData()\"\n >\n Remove Data\n </button>\n <button mat-stroked-button\n color=\"primary\"\n (click)=\"addData()\"\n >\n Add Data\n </button>\n </div>\n\n <div class=\"table-container\">\n <table mat-table [dataSource]=\"dataSource\">\n\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef>ID</th>\n <td mat-cell *matCellDef=\"let element\">{{element.id}}</td>\n </ng-container>\n\n <ng-container matColumnDef=\"last_name\">\n <th mat-header-cell *matHeaderCellDef>Last Name</th>\n <td mat-cell *matCellDef=\"let element\">{{element.last_name}}</td>\n </ng-container>\n\n <ng-container matColumnDef=\"age\">\n <th mat-header-cell *matHeaderCellDef>Age</th>\n <td mat-cell *matCellDef=\"let element\">{{element.age}}</td>\n </ng-container>\n\n <ng-container matColumnDef=\"amount\">\n <th mat-header-cell *matHeaderCellDef>Amount</th>\n <td mat-cell *matCellDef=\"let element\">{{element.amount | currency}}</td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n </div>\n\n</div>\n" }]
10316
- }], ctorParameters: () => [] });
10317
- class DatabaseDataSource extends DataSource {
10318
- constructor(initialData) {
10319
- super();
10320
- this._dataStream = new ReplaySubject();
10321
- this.setData(initialData);
10322
- }
10323
- connect() {
10324
- return this._dataStream;
10325
- }
10326
- disconnect() { }
10327
- setData(data) {
10328
- this._dataStream.next(data);
10329
- }
10330
- }
10331
-
10332
- class HttpRequestServicesDemoComponent {
10333
- constructor(configOptions) {
10334
- this.configOptions = configOptions;
10335
- this.wsServer = 'ws:';
10336
- this.jwtToken = '';
10337
- this.server = 'http:';
10338
- this.path = ['ai', 'tests'];
10339
- this.requestTypes = [
10340
- { name: "Http Service", value: 'http_service' },
10341
- // { name: "Http Signals Service", value: 'http_signals_service', new: true },
10342
- { name: "Http State Service", value: 'http_state_service' },
10343
- { name: "Http State Service - Websockets", value: 'http_state_service_ws', new: false },
10344
- { name: "Database Service", value: 'database_service', divider: true, disabled: false },
10345
- { name: "Local Storage Service", value: 'local_storage_service' },
10346
- // { name: "Local Signals Storage Service", value: 'local_storage_signals_service', new: true },
10347
- { name: "Store State Manager Service", value: 'store_state_manager', new: true },
10348
- { name: "Basic Http Service", value: 'basic_http_service', divider: true },
10349
- ];
10350
- this.selectedService = this.requestTypes[1].value; //menu selection default
10351
- }
10352
- ngOnInit() {
10353
- if (this.configOptions)
10354
- this.injectionOptions = this.configOptions;
10355
- }
10356
- onSelected(type) {
10357
- this.selectedService = this.requestTypes[type].value;
10358
- }
10359
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: HttpRequestServicesDemoComponent, deps: [{ token: CONFIG_SETTINGS_TOKEN }], target: i0.ɵɵFactoryTarget.Component }); }
10360
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: HttpRequestServicesDemoComponent, selector: "app-http-request-services-demo", inputs: { wsServer: "wsServer", jwtToken: "jwtToken", server: "server", user: "user", path: "path", adapter: "adapter", mapper: "mapper" }, ngImport: i0, template: "<mat-toolbar style=\"display:flex\">\n <div>Http Request Manager Services</div>\n <div style=\"flex:1\"></div>\n <button mat-stroked-button [matMenuTriggerFor]=\"menu\">Services</button>\n <mat-menu #menu=\"matMenu\">\n @for (type of requestTypes; track type; let i = $index) {\n @if (type?.divider) {\n <div\n style=\"margin-top: .5rem; margin-bottom: .5rem;\"\n >\n <mat-divider></mat-divider>\n </div>\n }\n <button\n mat-menu-item\n (click)=\"onSelected(i)\"\n [disabled]=\"type.disabled\"\n >\n {{ type.name }}\n </button>\n }\n\n </mat-menu>\n</mat-toolbar>\n\n<span>\n @switch (selectedService) {\n @case ('basic_http_service') {\n <p>\n <app-request-manager-basic-demo></app-request-manager-basic-demo>\n </p>\n }\n @case ('http_service') {\n <p>\n <ng-container *ngTemplateOutlet=\"HTTP_OPTIONS\"></ng-container>\n <app-request-manager-demo\n [server]=\"server\"\n [adapter]=\"adapter\"\n [mapper]=\"mapper\"\n ></app-request-manager-demo>\n </p>\n }\n <!-- <p *ngSwitchCase=\"'http_signals_service'\">\n <ng-container *ngTemplateOutlet=\"HTTP_OPTIONS\"></ng-container>\n <app-request-signals-manager-demo></app-request-signals-manager-demo>\n </p> -->\n @case ('http_state_service') {\n <p>\n <ng-container *ngTemplateOutlet=\"HTTP_OPTIONS\"></ng-container>\n <app-request-manager-state-demo\n [server]=\"server\"\n [adapter]=\"adapter\"\n [mapper]=\"mapper\"\n ></app-request-manager-state-demo>\n </p>\n }\n @case ('http_state_service_ws') {\n <p>\n <ng-container *ngTemplateOutlet=\"HTTP_OPTIONS\"></ng-container>\n <app-request-manager-ws-demo\n [server]=\"server\"\n [wsServer]=\"wsServer\"\n [jwtToken]=\"jwtToken\"\n [user]=\"user\"\n [path]=\"path\"\n ></app-request-manager-ws-demo>\n </p>\n }\n @case ('database_service') {\n <p>\n <app-database-data-demo></app-database-data-demo>\n </p>\n }\n @case ('local_storage_service') {\n <p>\n <ng-container *ngTemplateOutlet=\"LOCAL_OPTIONS\"></ng-container>\n <app-local-storage-demo></app-local-storage-demo>\n </p>\n }\n <!-- <p *ngSwitchCase=\"'local_storage_signals_service'\">\n <ng-container *ngTemplateOutlet=\"LOCAL_OPTIONS\"></ng-container>\n <app-local-storage-signals-demo></app-local-storage-signals-demo>\n</p> -->\n@case ('store_state_manager') {\n <p>\n <ng-container *ngTemplateOutlet=\"LOCAL_OPTIONS\"></ng-container>\n <app-store-state-manager-demo></app-store-state-manager-demo>\n </p>\n}\n@default {\n <p>\n Other\n </p>\n}\n}\n</span>\n\n<ng-template #HTTP_OPTIONS>\n @if (injectionOptions?.httpRequestOptions) {\n <div class=\"box\">\n <h3 style=\"font-weight: bold;\">Injection Token Detected - HTTP Options</h3>\n {{ injectionOptions?.httpRequestOptions| json }}\n </div>\n }\n</ng-template>\n\n<ng-template #LOCAL_OPTIONS>\n @if (injectionOptions?.LocalStorageOptions) {\n <ng-container class=\"box\">\n <div class=\"box\">\n <h3 style=\"font-weight: bold;\">Injection Token Detected - LocalStorage Options</h3>\n {{ injectionOptions?.LocalStorageOptions| json }}\n </div>\n </ng-container>\n }\n</ng-template>\n\n\n", styles: [".box{padding:1rem;background-color:#f5f5f5;border:thin gray solid;margin-top:1rem}\n"], dependencies: [{ kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i7.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: i7.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i7.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i12.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "component", type: i3$2.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "component", type: RequestManagerBasicDemoComponent, selector: "app-request-manager-basic-demo" }, { kind: "component", type: RequestManagerStateDemoComponent, selector: "app-request-manager-state-demo", inputs: ["server", "adapter", "mapper"] }, { kind: "component", type: RequestManagerDemoComponent, selector: "app-request-manager-demo", inputs: ["server", "adapter", "mapper"] }, { kind: "component", type: LocalStorageDemoComponent, selector: "app-local-storage-demo" }, { kind: "component", type: RequestManagerWsDemoComponent, selector: "app-request-manager-ws-demo", inputs: ["server", "wsServer", "jwtToken", "user", "path"] }, { kind: "component", type: StoreStateManagerDemoComponent, selector: "app-store-state-manager-demo" }, { kind: "component", type: DatabaseDataDemoComponent, selector: "app-database-data-demo" }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }] }); }
10361
- }
10362
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: HttpRequestServicesDemoComponent, decorators: [{
10363
- type: Component,
10364
- args: [{ selector: 'app-http-request-services-demo', standalone: false, template: "<mat-toolbar style=\"display:flex\">\n <div>Http Request Manager Services</div>\n <div style=\"flex:1\"></div>\n <button mat-stroked-button [matMenuTriggerFor]=\"menu\">Services</button>\n <mat-menu #menu=\"matMenu\">\n @for (type of requestTypes; track type; let i = $index) {\n @if (type?.divider) {\n <div\n style=\"margin-top: .5rem; margin-bottom: .5rem;\"\n >\n <mat-divider></mat-divider>\n </div>\n }\n <button\n mat-menu-item\n (click)=\"onSelected(i)\"\n [disabled]=\"type.disabled\"\n >\n {{ type.name }}\n </button>\n }\n\n </mat-menu>\n</mat-toolbar>\n\n<span>\n @switch (selectedService) {\n @case ('basic_http_service') {\n <p>\n <app-request-manager-basic-demo></app-request-manager-basic-demo>\n </p>\n }\n @case ('http_service') {\n <p>\n <ng-container *ngTemplateOutlet=\"HTTP_OPTIONS\"></ng-container>\n <app-request-manager-demo\n [server]=\"server\"\n [adapter]=\"adapter\"\n [mapper]=\"mapper\"\n ></app-request-manager-demo>\n </p>\n }\n <!-- <p *ngSwitchCase=\"'http_signals_service'\">\n <ng-container *ngTemplateOutlet=\"HTTP_OPTIONS\"></ng-container>\n <app-request-signals-manager-demo></app-request-signals-manager-demo>\n </p> -->\n @case ('http_state_service') {\n <p>\n <ng-container *ngTemplateOutlet=\"HTTP_OPTIONS\"></ng-container>\n <app-request-manager-state-demo\n [server]=\"server\"\n [adapter]=\"adapter\"\n [mapper]=\"mapper\"\n ></app-request-manager-state-demo>\n </p>\n }\n @case ('http_state_service_ws') {\n <p>\n <ng-container *ngTemplateOutlet=\"HTTP_OPTIONS\"></ng-container>\n <app-request-manager-ws-demo\n [server]=\"server\"\n [wsServer]=\"wsServer\"\n [jwtToken]=\"jwtToken\"\n [user]=\"user\"\n [path]=\"path\"\n ></app-request-manager-ws-demo>\n </p>\n }\n @case ('database_service') {\n <p>\n <app-database-data-demo></app-database-data-demo>\n </p>\n }\n @case ('local_storage_service') {\n <p>\n <ng-container *ngTemplateOutlet=\"LOCAL_OPTIONS\"></ng-container>\n <app-local-storage-demo></app-local-storage-demo>\n </p>\n }\n <!-- <p *ngSwitchCase=\"'local_storage_signals_service'\">\n <ng-container *ngTemplateOutlet=\"LOCAL_OPTIONS\"></ng-container>\n <app-local-storage-signals-demo></app-local-storage-signals-demo>\n</p> -->\n@case ('store_state_manager') {\n <p>\n <ng-container *ngTemplateOutlet=\"LOCAL_OPTIONS\"></ng-container>\n <app-store-state-manager-demo></app-store-state-manager-demo>\n </p>\n}\n@default {\n <p>\n Other\n </p>\n}\n}\n</span>\n\n<ng-template #HTTP_OPTIONS>\n @if (injectionOptions?.httpRequestOptions) {\n <div class=\"box\">\n <h3 style=\"font-weight: bold;\">Injection Token Detected - HTTP Options</h3>\n {{ injectionOptions?.httpRequestOptions| json }}\n </div>\n }\n</ng-template>\n\n<ng-template #LOCAL_OPTIONS>\n @if (injectionOptions?.LocalStorageOptions) {\n <ng-container class=\"box\">\n <div class=\"box\">\n <h3 style=\"font-weight: bold;\">Injection Token Detected - LocalStorage Options</h3>\n {{ injectionOptions?.LocalStorageOptions| json }}\n </div>\n </ng-container>\n }\n</ng-template>\n\n\n", styles: [".box{padding:1rem;background-color:#f5f5f5;border:thin gray solid;margin-top:1rem}\n"] }]
10365
- }], ctorParameters: () => [{ type: ConfigOptions, decorators: [{
10366
- type: Inject,
10367
- args: [CONFIG_SETTINGS_TOKEN]
10368
- }] }], propDecorators: { wsServer: [{
10369
- type: Input
10370
- }], jwtToken: [{
10371
- type: Input
10372
- }], server: [{
10373
- type: Input
10374
- }], user: [{
10375
- type: Input
10376
- }], path: [{
10377
- type: Input
10378
- }], adapter: [{
10379
- type: Input
10380
- }], mapper: [{
10381
- type: Input
10382
- }] } });
10383
-
10384
- class LocalStorageSignalsDemoComponent {
10385
- // store type is driven by the form control
10386
- get type() {
10387
- return (this.typeControl.value) ? +this.typeControl.value : 0;
10388
- }
10389
- get isValid() {
10390
- return this.newStoreForm.valid;
10391
- }
10392
- get isValidData() {
10393
- return this.storageForm.valid;
10394
- }
10395
- settingFor(name) {
10396
- const c = this.localStorageManagerService.setting(name);
10397
- return c ? c() : null;
10398
- }
10399
- constructor(configOptions) {
10400
- this.configOptions = configOptions;
10401
- this.fb = inject(FormBuilder);
10402
- this.utils = inject(UtilsService);
10403
- this.typeControl = this.fb.control(StorageType.GLOBAL.toString());
10404
- this.localStorageManagerService = inject(LocalStorageSignalsManagerService);
10405
- // Use signals directly and computed values for template binding
10406
- this.settings = computed(() => this.localStorageManagerService.settings());
10407
- // selected store (signal) and its JSON data for template
10408
- this.storeSelected = signal(null);
10409
- this.selectedStoreData = computed(() => {
10410
- const s = this.storeSelected();
10411
- if (!s)
10412
- return '';
10413
- const computed = this.localStorageManagerService.store(s.name);
10414
- return computed ? JSON.stringify(computed()) : '';
10415
- });
10416
- // filtered settings by selected type
10417
- this.selectedType = signal(StorageType.GLOBAL);
10418
- this.filteredSettings = computed(() => {
10419
- const values = this.settings() || [];
10420
- return values.filter((item) => item.options.storage === +this.selectedType());
10421
- });
10422
- this.storageForm = this.fb.group({
10423
- store: this.fb.control(null),
10424
- type: 'local',
10425
- settingType: 'local',
10426
- encrypted: false,
10427
- data: this.fb.control('', Validators.required),
10428
- });
10429
- this.newStoreForm = this.fb.group({
10430
- name: this.fb.control(null, Validators.required),
10431
- storage: 'local',
10432
- encrypted: false,
10433
- data: this.fb.control('', Validators.required),
10434
- expiresIn: this.fb.control('0')
10435
- });
10436
- // no RxJS Observables here; template uses signals/computed directly
10437
- this.expiresIn = (epoch) => this.utils.expiresIn(epoch);
10438
- this.isValidJSON = (str) => {
10439
- try {
10440
- JSON.parse(str);
10441
- return true;
10442
- }
10443
- catch (e) {
10444
- return false;
10445
- }
10446
- };
10447
- this.displayedColumns = ['name', 'id', 'encrypted', 'expires', "option"];
10448
- this.filterData = (values) => {
10449
- if (!values)
10450
- return [];
10451
- return values.filter((item) => item.options && item.options.storage === +this.type);
10452
- };
10453
- this.create = false;
10454
- }
10455
- ngOnInit() {
10456
- this.storeProps = this.configOptions?.LocalStorageOptions;
10457
- this.options = this.storeProps?.options;
10458
- if (this.options?.storage) {
10459
- this.typeControl.patchValue(this.options.storage.toString());
10460
- this.typeControl.disable();
10461
- }
10462
- else {
10463
- this.typeControl.enable();
10464
- }
10465
- if (this.options?.expiresIn) {
10466
- this.newStoreForm.get('expiresIn')?.patchValue(this.options.expiresIn);
10467
- this.newStoreForm.get('expiresIn')?.disable();
10468
- }
10469
- else {
10470
- this.newStoreForm.get('expiresIn')?.enable();
10471
- }
10472
- if (this.options?.encrypted) {
10473
- this.newStoreForm.get('encrypted')?.patchValue(this.options.encrypted);
10474
- this.newStoreForm.get('encrypted')?.disable();
10475
- }
10476
- else {
10477
- this.newStoreForm.get('encrypted')?.enable();
10478
- }
10479
- // nothing to synchronize - templates read computed signals directly
10480
- }
10481
- onCreateStore() {
10482
- if (!this.isValid)
10483
- return;
10484
- const store = this.newStoreForm.value;
10485
- if (!store.name || store.name === '')
10486
- return;
10487
- const options = { storage: this.type, encrypted: store.encrypted, expiresIn: store.expiresIn };
10488
- this.localStorageManagerService.createStore({
10489
- name: store.name,
10490
- data: store.data,
10491
- options: SettingOptions.adapt(options)
10492
- });
10493
- this.newStoreForm.reset();
10494
- this.create = false;
10495
- }
10496
- onUpdateStore(store) {
10497
- if (!this.storageForm.valid)
10498
- return;
10499
- const storeData = this.storageForm.value;
10500
- const data = JSON.parse(storeData.data || '');
10501
- const type = (storeData.type === 'local') ? StorageType.GLOBAL : StorageType.SESSION;
10502
- this.localStorageManagerService.updateStore({
10503
- name: store.name,
10504
- data
10505
- });
10506
- }
10507
- onSelectedRow(store) {
10508
- this.store = store;
10509
- this.storeSelected.set(store);
10510
- this.create = false;
10511
- }
10512
- onCreate() {
10513
- this.onCancel();
10514
- this.create = true;
10515
- }
10516
- onDelete(store) {
10517
- this.localStorageManagerService.deleteStore({
10518
- name: store.name,
10519
- });
10520
- this.onCancel();
10521
- }
10522
- onCancel() {
10523
- this.storeSelected.set(null);
10524
- this.store = null;
10525
- this.create = false;
10526
- }
10527
- onUpdate(store, data) {
10528
- this.localStorageManagerService.updateStore({
10529
- name: store.name,
10530
- data: JSON.parse(data)
10531
- });
10532
- this.onCancel();
10533
- }
10534
- onReset() {
10535
- this.localStorageManagerService.resetStore();
10536
- }
10537
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LocalStorageSignalsDemoComponent, deps: [{ token: CONFIG_SETTINGS_TOKEN }], target: i0.ɵɵFactoryTarget.Component }); }
10538
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: LocalStorageSignalsDemoComponent, selector: "app-local-storage-signals-demo", ngImport: i0, template: "<div style=\"margin: 2rem;\">\n\n <div style=\"display: flex; gap: 1rem\">\n <h2 style=\"padding-top: .5rem; display: flex;\">\n <div style=\"padding-top: .5rem;\">\n <span>Local Storage Manager</span>\n <span style=\"margin-left: .5rem;\">\n <mat-icon color=\"accent\">fiber_new</mat-icon>\n </span>\n </div>\n <div>\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>info</mat-icon>\n </button>\n </div>\n </h2>\n <span style=\"flex:1\"></span>\n <div style=\"padding-top: .25rem;\">\n <mat-button-toggle-group name=\"storage\" [formControl]=\"typeControl\" (change)=\"onCancel()\">\n <mat-button-toggle value=\"0\">Local Storage</mat-button-toggle>\n <mat-button-toggle value=\"1\">Session Storage</mat-button-toggle>\n </mat-button-toggle-group>\n </div>\n <div style=\"margin-top: .25rem;\">\n <button mat-icon-button (click)=\"onCreate()\">\n <mat-icon>add</mat-icon>\n </button>\n </div>\n </div>\n\n <ng-container *ngIf=\"storeProps?.storageName || storeProps?.storageSettingsName\">\n <div style=\"display: flex; gap: .5rem; flex-direction: column; border: gray solid thin; background-color: whitesmoke; padding: 1rem;\"\n >\n <div style=\"display: flex;\">\n <div *ngIf=\"storeProps?.storageSettingsName\" style=\"flex:1\">\n Database: <b>{{ storeProps?.storageSettingsName }}</b>\n </div>\n <div *ngIf=\"storeProps?.storageName\" style=\"flex:1\">\n Data: <b>{{ storeProps?.storageName }}</b>\n </div>\n </div>\n </div>\n </ng-container>\n\n <div style=\"margin-top: 2rem;\" [formGroup]=\"newStoreForm\" *ngIf=\"create; else LIST\">\n\n <h2>Create Store</h2>\n\n <div style=\"display: flex; gap: 1rem;\">\n <div style=\"flex:1\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Store Name</mat-label>\n <input matInput placeholder=\"sample\" formControlName=\"name\">\n </mat-form-field>\n </div>\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Expires In...</mat-label>\n <mat-select formControlName=\"expiresIn\">\n <mat-option value=\"0\">None</mat-option>\n <mat-option value=\"30s\">30 Seconds</mat-option>\n <mat-option value=\"1mn\">1 Minute</mat-option>\n <mat-option value=\"1hr\">1 Hour</mat-option>\n <mat-option value=\"1d\">1 Day</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div style=\"margin-top: 1rem;\">\n <mat-slide-toggle formControlName=\"encrypted\">Encrypted</mat-slide-toggle>\n </div>\n </div>\n\n <div style=\"display: flex;\">\n <mat-form-field appearance=\"outline\" style=\"flex: 1;\">\n <mat-label>Data (Must be an Object or Array of any)</mat-label>\n <textarea matInput placeholder=\"[]\" formControlName=\"data\" #json></textarea>\n </mat-form-field>\n </div>\n <mat-error *ngIf=\"!isValidJSON(json.value)\">Not Valid Data</mat-error>\n\n <div style=\"display: flex;\">\n <span style=\"flex:1\"></span>\n <button mat-stroked-button (click)=\"onCreateStore()\" [disabled]=\"!(isValid && isValidJSON(json.value))\">\n Save Store\n </button>\n </div>\n\n </div>\n\n <ng-template #LIST>\n\n <div style=\"margin-top: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n{{ settings() | json }}\n <div *ngIf=\"filterData(settings()) as data\">\n <ng-container *ngIf=\"data.length > 0; else NO_DATA\">\n <table mat-table [dataSource]=\"data\">\n <ng-container matColumnDef=\"name\">\n <th mat-header-cell *matHeaderCellDef> Store </th>\n <td mat-cell *matCellDef=\"let element\" style=\"font-weight: bold; text-transform: uppercase;\"> {{element.name}} </td>\n </ng-container>\n\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef> ID </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.id}} </td>\n </ng-container>\n\n <ng-container matColumnDef=\"encrypted\">\n <th mat-header-cell *matHeaderCellDef style=\"text-align: center;\"> Encrypted </th>\n <td mat-cell *matCellDef=\"let element\" style=\"text-align: center;\">\n <ng-container *ngIf=\"element.options.encrypted; else NO\">YES</ng-container>\n <ng-template #NO><span style=\"color:gray\">NO</span></ng-template>\n </td>\n </ng-container>\n\n <ng-container matColumnDef=\"expires\">\n <th mat-header-cell *matHeaderCellDef style=\"text-align: center;\"> Expires </th>\n <td mat-cell *matCellDef=\"let element\" style=\"text-align: center;\">\n <ng-container *ngIf=\"element.options.expires !== 0; else NO_DATA\">\n {{expiresIn(element.options.expires)}}\n </ng-container>\n <ng-template #NO_DATA>\n \u221E\n </ng-template>\n </td>\n </ng-container>\n\n <ng-container matColumnDef=\"option\">\n <th mat-header-cell *matHeaderCellDef></th>\n <td mat-cell *matCellDef=\"let element\" style=\"padding-right: 0;\">\n <div style=\"display: flex;justify-content: flex-end;\">\n <button mat-icon-button color=\"warn\" (click)=\"onDelete(element); $event.stopPropagation()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\" (click)=\"onSelectedRow(row)\"></tr>\n </table>\n </ng-container>\n\n <ng-template #NO_DATA>\n <h3 style=\"margin-top: 1rem;\">No Data</h3>\n </ng-template>\n\n <div *ngIf=\"storeSelected() as selectedStore\" style=\"margin-top: 2rem;\">\n <div style=\"margin-bottom: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <h3>STORE: <span style=\"font-weight: bold;\">{{ selectedStore.name | uppercase }}</span></h3>\n <h3>SETTINGS: {{ localStorageManagerService.setting(selectedStore.name) ? (localStorageManagerService.setting(selectedStore.name)() | json) : '{}' }}</h3>\n <div style=\"display: flex;\">\n <mat-form-field appearance=\"outline\" style=\"flex: 1;\">\n <mat-label>Data (Must be an Object or Array of any)</mat-label>\n <textarea matInput placeholder=\"[]\" #json [value]=\"selectedStoreData()\"></textarea>\n </mat-form-field>\n </div>\n <mat-error *ngIf=\"!isValidJSON(json.value)\">Not Valid Data</mat-error>\n\n <div style=\"display: flex; gap: .5rem;\">\n <span style=\"flex:1\"></span>\n <button mat-stroked-button (click)=\"onCancel()\">Cancel</button>\n <button mat-stroked-button (click)=\"onUpdate(selectedStore, json.value)\" [disabled]=\"!(isValidJSON(json.value))\">Update</button>\n </div>\n </div>\n </div>\n\n </ng-template>\n\n <div style=\"margin-top: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 1rem;\">\n <button mat-stroked-button (click)=\"onReset()\">Delete All Stores</button>\n </div>\n\n</div>\n\n\n<mat-menu #menu=\"matMenu\">\n <div style=\"padding: 1rem;\">\n <p>Please note that the LocalStorage (encryption) and management of data is dependant on initializing the APP_ID</p>\n <p>Must provide a value for <b>APP_ID</b> in AppModule->Providers</p>\n <mat-divider></mat-divider>\n <div style=\"font-size: smaller; margin-top: .5rem;\">\n <p>Example: UUID (self.crypto.randomUUID() to generate)</p>\n <div style=\"background-color: whitesmoke; padding: 1rem;\">\n &#123;<br>\n provide: APP_ID,<br>\n useValue: \"056991ac-3537-43ab-b5b9-83edf6554eff\",<br>\n &#125;\n </div>\n </div>\n </div>\n</mat-menu>\n", styles: [".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}.demo-row-is-clicked{font-weight:700}.mat-mdc-menu-panel.mat-mdc-menu-panel{max-width:280px!important}\n"], dependencies: [{ kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2$1.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: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i3.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "component", type: i5.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"], 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: "component", type: i7.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "directive", type: i7.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i9.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i9.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i9.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i9.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i9.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i9.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i9.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i9.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i9.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "directive", type: i10.MatButtonToggleGroup, selector: "mat-button-toggle-group", inputs: ["appearance", "name", "vertical", "value", "multiple", "disabled", "disabledInteractive", "hideSingleSelectionIndicator", "hideMultipleSelectionIndicator"], outputs: ["valueChange", "change"], exportAs: ["matButtonToggleGroup"] }, { kind: "component", type: i10.MatButtonToggle, selector: "mat-button-toggle", inputs: ["aria-label", "aria-labelledby", "id", "name", "value", "tabIndex", "disableRipple", "appearance", "checked", "disabled", "disabledInteractive"], outputs: ["change"], exportAs: ["matButtonToggle"] }, { kind: "component", type: i11.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "component", type: i12.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i13.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "pipe", type: i1$1.UpperCasePipe, name: "uppercase" }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }], encapsulation: i0.ViewEncapsulation.None }); }
10539
- }
10540
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LocalStorageSignalsDemoComponent, decorators: [{
10541
- type: Component,
10542
- args: [{ selector: 'app-local-storage-signals-demo', encapsulation: ViewEncapsulation.None, standalone: false, template: "<div style=\"margin: 2rem;\">\n\n <div style=\"display: flex; gap: 1rem\">\n <h2 style=\"padding-top: .5rem; display: flex;\">\n <div style=\"padding-top: .5rem;\">\n <span>Local Storage Manager</span>\n <span style=\"margin-left: .5rem;\">\n <mat-icon color=\"accent\">fiber_new</mat-icon>\n </span>\n </div>\n <div>\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>info</mat-icon>\n </button>\n </div>\n </h2>\n <span style=\"flex:1\"></span>\n <div style=\"padding-top: .25rem;\">\n <mat-button-toggle-group name=\"storage\" [formControl]=\"typeControl\" (change)=\"onCancel()\">\n <mat-button-toggle value=\"0\">Local Storage</mat-button-toggle>\n <mat-button-toggle value=\"1\">Session Storage</mat-button-toggle>\n </mat-button-toggle-group>\n </div>\n <div style=\"margin-top: .25rem;\">\n <button mat-icon-button (click)=\"onCreate()\">\n <mat-icon>add</mat-icon>\n </button>\n </div>\n </div>\n\n <ng-container *ngIf=\"storeProps?.storageName || storeProps?.storageSettingsName\">\n <div style=\"display: flex; gap: .5rem; flex-direction: column; border: gray solid thin; background-color: whitesmoke; padding: 1rem;\"\n >\n <div style=\"display: flex;\">\n <div *ngIf=\"storeProps?.storageSettingsName\" style=\"flex:1\">\n Database: <b>{{ storeProps?.storageSettingsName }}</b>\n </div>\n <div *ngIf=\"storeProps?.storageName\" style=\"flex:1\">\n Data: <b>{{ storeProps?.storageName }}</b>\n </div>\n </div>\n </div>\n </ng-container>\n\n <div style=\"margin-top: 2rem;\" [formGroup]=\"newStoreForm\" *ngIf=\"create; else LIST\">\n\n <h2>Create Store</h2>\n\n <div style=\"display: flex; gap: 1rem;\">\n <div style=\"flex:1\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Store Name</mat-label>\n <input matInput placeholder=\"sample\" formControlName=\"name\">\n </mat-form-field>\n </div>\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Expires In...</mat-label>\n <mat-select formControlName=\"expiresIn\">\n <mat-option value=\"0\">None</mat-option>\n <mat-option value=\"30s\">30 Seconds</mat-option>\n <mat-option value=\"1mn\">1 Minute</mat-option>\n <mat-option value=\"1hr\">1 Hour</mat-option>\n <mat-option value=\"1d\">1 Day</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div style=\"margin-top: 1rem;\">\n <mat-slide-toggle formControlName=\"encrypted\">Encrypted</mat-slide-toggle>\n </div>\n </div>\n\n <div style=\"display: flex;\">\n <mat-form-field appearance=\"outline\" style=\"flex: 1;\">\n <mat-label>Data (Must be an Object or Array of any)</mat-label>\n <textarea matInput placeholder=\"[]\" formControlName=\"data\" #json></textarea>\n </mat-form-field>\n </div>\n <mat-error *ngIf=\"!isValidJSON(json.value)\">Not Valid Data</mat-error>\n\n <div style=\"display: flex;\">\n <span style=\"flex:1\"></span>\n <button mat-stroked-button (click)=\"onCreateStore()\" [disabled]=\"!(isValid && isValidJSON(json.value))\">\n Save Store\n </button>\n </div>\n\n </div>\n\n <ng-template #LIST>\n\n <div style=\"margin-top: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n{{ settings() | json }}\n <div *ngIf=\"filterData(settings()) as data\">\n <ng-container *ngIf=\"data.length > 0; else NO_DATA\">\n <table mat-table [dataSource]=\"data\">\n <ng-container matColumnDef=\"name\">\n <th mat-header-cell *matHeaderCellDef> Store </th>\n <td mat-cell *matCellDef=\"let element\" style=\"font-weight: bold; text-transform: uppercase;\"> {{element.name}} </td>\n </ng-container>\n\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef> ID </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.id}} </td>\n </ng-container>\n\n <ng-container matColumnDef=\"encrypted\">\n <th mat-header-cell *matHeaderCellDef style=\"text-align: center;\"> Encrypted </th>\n <td mat-cell *matCellDef=\"let element\" style=\"text-align: center;\">\n <ng-container *ngIf=\"element.options.encrypted; else NO\">YES</ng-container>\n <ng-template #NO><span style=\"color:gray\">NO</span></ng-template>\n </td>\n </ng-container>\n\n <ng-container matColumnDef=\"expires\">\n <th mat-header-cell *matHeaderCellDef style=\"text-align: center;\"> Expires </th>\n <td mat-cell *matCellDef=\"let element\" style=\"text-align: center;\">\n <ng-container *ngIf=\"element.options.expires !== 0; else NO_DATA\">\n {{expiresIn(element.options.expires)}}\n </ng-container>\n <ng-template #NO_DATA>\n \u221E\n </ng-template>\n </td>\n </ng-container>\n\n <ng-container matColumnDef=\"option\">\n <th mat-header-cell *matHeaderCellDef></th>\n <td mat-cell *matCellDef=\"let element\" style=\"padding-right: 0;\">\n <div style=\"display: flex;justify-content: flex-end;\">\n <button mat-icon-button color=\"warn\" (click)=\"onDelete(element); $event.stopPropagation()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\" (click)=\"onSelectedRow(row)\"></tr>\n </table>\n </ng-container>\n\n <ng-template #NO_DATA>\n <h3 style=\"margin-top: 1rem;\">No Data</h3>\n </ng-template>\n\n <div *ngIf=\"storeSelected() as selectedStore\" style=\"margin-top: 2rem;\">\n <div style=\"margin-bottom: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <h3>STORE: <span style=\"font-weight: bold;\">{{ selectedStore.name | uppercase }}</span></h3>\n <h3>SETTINGS: {{ localStorageManagerService.setting(selectedStore.name) ? (localStorageManagerService.setting(selectedStore.name)() | json) : '{}' }}</h3>\n <div style=\"display: flex;\">\n <mat-form-field appearance=\"outline\" style=\"flex: 1;\">\n <mat-label>Data (Must be an Object or Array of any)</mat-label>\n <textarea matInput placeholder=\"[]\" #json [value]=\"selectedStoreData()\"></textarea>\n </mat-form-field>\n </div>\n <mat-error *ngIf=\"!isValidJSON(json.value)\">Not Valid Data</mat-error>\n\n <div style=\"display: flex; gap: .5rem;\">\n <span style=\"flex:1\"></span>\n <button mat-stroked-button (click)=\"onCancel()\">Cancel</button>\n <button mat-stroked-button (click)=\"onUpdate(selectedStore, json.value)\" [disabled]=\"!(isValidJSON(json.value))\">Update</button>\n </div>\n </div>\n </div>\n\n </ng-template>\n\n <div style=\"margin-top: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 1rem;\">\n <button mat-stroked-button (click)=\"onReset()\">Delete All Stores</button>\n </div>\n\n</div>\n\n\n<mat-menu #menu=\"matMenu\">\n <div style=\"padding: 1rem;\">\n <p>Please note that the LocalStorage (encryption) and management of data is dependant on initializing the APP_ID</p>\n <p>Must provide a value for <b>APP_ID</b> in AppModule->Providers</p>\n <mat-divider></mat-divider>\n <div style=\"font-size: smaller; margin-top: .5rem;\">\n <p>Example: UUID (self.crypto.randomUUID() to generate)</p>\n <div style=\"background-color: whitesmoke; padding: 1rem;\">\n &#123;<br>\n provide: APP_ID,<br>\n useValue: \"056991ac-3537-43ab-b5b9-83edf6554eff\",<br>\n &#125;\n </div>\n </div>\n </div>\n</mat-menu>\n", styles: [".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}.demo-row-is-clicked{font-weight:700}.mat-mdc-menu-panel.mat-mdc-menu-panel{max-width:280px!important}\n"] }]
10543
- }], ctorParameters: () => [{ type: ConfigOptions, decorators: [{
10544
- type: Inject,
10545
- args: [CONFIG_SETTINGS_TOKEN]
10546
- }] }] });
10547
-
10548
- class ClientInfo {
10549
- constructor(domain = '', service = '', id = 0, name = '') {
10550
- this.domain = domain;
10551
- this.service = service;
10552
- this.id = id;
10553
- this.name = name;
10554
- }
10555
- static adapt(item) {
10556
- return new ClientInfo(item?.domain, item?.service, item?.id, (item?.first_name || item?.last_name) ? `${item?.first_name} ${item?.last_name}` : '');
10557
- }
10558
- }
10559
-
10560
- class ClientInfoMapper {
10561
- constructor(id = 0, first_name = '', last_name = '', email = '') {
10562
- this.id = id;
10563
- this.first_name = first_name;
10564
- this.last_name = last_name;
10565
- this.email = email;
10566
- }
10567
- static adapt(item) {
10568
- const first_name = (item?.name) ? item.name.split(' ')[0] : '';
10569
- const last_name = (item?.name) ? item.name.split(' ')[1] : '';
10570
- return new ClientInfoMapper(item?.id, first_name, last_name, item?.email);
10571
- }
10572
- }
10573
-
10574
- class AIPrompt {
10575
- constructor(response = '') {
10576
- this.response = response;
10577
- }
10578
- static adapt(item) {
10579
- return new AIPrompt(item?.response);
10580
- }
10581
- }
10582
-
10583
- class RequestSignalsManagerDemoComponent {
10584
- get retry() {
10585
- return this.requestForm.get('retry')?.value;
10586
- }
10587
- get headers() {
10588
- return this.requestForm.get('headers');
10589
- }
10590
- get isValid() {
10591
- this.requestForm.markAllAsTouched();
10592
- return this.requestForm.valid;
10593
- }
10594
- constructor() {
10595
- this.displayedColumns = ['id', 'name', 'lastName', 'age'];
10596
- this.fb = inject(FormBuilder);
10597
- this.toastMessage = inject(ToastMessageDisplayService);
10598
- this.httpManagerSignalsService = inject(HTTPManagerSignalsService);
10599
- // Using signals service: signals are callable in templates (e.g. isPending())
10600
- this.isPending = this.httpManagerSignalsService.isPending;
10601
- this.countdown = this.httpManagerSignalsService.countdown;
10602
- // per-operation plain results and error messages (no rxjs in component)
10603
- this.GET_result = null;
10604
- this.POST_result = null;
10605
- this.PUT_result = null;
10606
- this.DELETE_result = null;
10607
- this.STREAM_result = null;
10608
- this.STREAM_AI_result = null;
10609
- this.GET_error = '';
10610
- this.POST_error = '';
10611
- this.PUT_error = '';
10612
- this.DELETE_error = '';
10613
- this.STREAM_error = '';
10614
- this.STREAM_AI_error = '';
10615
- this.requestParams = {
10616
- GET: ApiRequest.adapt(),
10617
- POST: ApiRequest.adapt(),
10618
- PUT: ApiRequest.adapt(),
10619
- DELETE: ApiRequest.adapt(),
10620
- STREAM: ApiRequest.adapt(),
10621
- };
10622
- this.questionControl = this.fb.control("", [Validators.required]);
10623
- this.downloadRequest = ApiRequest.adapt({
10624
- server: 'assets/images',
10625
- path: ['lego.png'],
10626
- // saveAs: 'john.jpg' // Optional
10627
- });
10628
- // downloadRequest = ApiRequest.adapt({
10629
- // server: 'oidc/ai/file'
10630
- // })
10631
- this.sampleClientData = {
10632
- id: 0,
10633
- name: "Old School Dates",
10634
- domain: "osd.com",
10635
- service: "osd",
10636
- spiffe: "osd.com/osd",
10637
- secret: "SMOPECXP-OS4P-USOG-X2II-3XMD1FQDR3IJX",
10638
- created: 1693003138,
10639
- modified: 1693003138,
10640
- icon: "",
10641
- imageFile: "",
10642
- email: "wavecoders@gmail.com"
10643
- };
10644
- this.requestForm = this.fb.group({
10645
- path: this.fb.control("ai/"),
10646
- headers: this.fb.array([]),
10647
- adapter: [null],
10648
- mapper: [null],
10649
- retry: this.fb.group({
10650
- times: [3],
10651
- delay: [3],
10652
- }),
10653
- polling: [3],
10654
- });
10655
- this.AIType = 0;
10656
- this.sampleAdaptors = [
10657
- { label: "ClientInfo Basic", value: ClientInfo.adapt },
10658
- { label: "AI Prompt", value: AIPrompt.adapt },
10659
- ];
10660
- this.sampleMappers = [
10661
- { label: "Mapper Basic", value: ClientInfoMapper.adapt },
10662
- { label: "AI Prompt", value: AIPrompt.adapt },
10663
- ];
10664
- this.hasId = (arr) => {
10665
- if (arr.length === 0)
10666
- return false;
10667
- return !isNaN(arr[arr.length - 1]);
10668
- };
10669
- this.props = (adapter) => {
10670
- return (adapter) ? adapter() : null;
10671
- };
10672
- // server = `http://sample-endpoint/as/authorization.oauth2`
10673
- this.arrayObjectsToObjects = (arr) => {
10674
- return Array.isArray(arr) ? arr.reduce((obj, item) => Object.assign(obj, { [item.key]: item.value }), {}) : {};
10675
- };
10676
- }
10677
- ngOnInit() {
10678
- // const reqGet2 = ApiRequest.adapt({
10679
- // server,
10680
- // path: ['clients'],
10681
- // headers: { authentication: "Bearer <KEY>" },
10682
- // adapter: ClientInfo,
10683
- // dataType: DataType.OBJECT,
10684
- // // concurrent: false,
10685
- // // polling: 3, //seconds
10686
- // })
10687
- // const req2 = [1024,1025,1026].map(item => {
10688
- // return this.httpManagerService.getRequest<ClientInfo[]>(reqGet2, [item])
10689
- // .pipe(
10690
- // catchError(error => {
10691
- // return throwError(() => new Error(error.error.message))
10692
- // })
10693
- // )
10694
- // })
10695
- // forkJoin(req2)
10696
- // .subscribe(res => console.log(res))
10697
- }
10698
- addHeader() {
10699
- const header = this.fb.group({
10700
- key: ['', Validators.required],
10701
- value: ['']
10702
- });
10703
- this.headers.push(header);
10704
- }
10705
- removeHeader(index) {
10706
- this.headers.removeAt(index);
10707
- }
10708
- compileRequest() {
10709
- const requestParams = this.requestForm.value;
10710
- requestParams.headers = this.arrayObjectsToObjects(requestParams.headers || []);
10711
- const pathReq = (requestParams.path === "") ? [] : (requestParams.path || "").split("/");
10712
- if (!this.pollingState.checked)
10713
- requestParams.polling = 0;
10714
- if (!this.failedState.checked) {
10715
- requestParams.retry = { times: 0, delay: 0 };
10716
- }
10717
- const apiOptions = ApiRequest.adapt(requestParams);
10718
- apiOptions.path = [];
10719
- return { apiOptions: apiOptions, path: pathReq };
10720
- }
10721
- onGetRequest() {
10722
- if (!this.isValid)
10723
- return;
10724
- const reqParams = this.compileRequest();
10725
- this.requestParams.GET = reqParams.apiOptions;
10726
- // reset local state
10727
- this.GET_result = null;
10728
- this.GET_error = '';
10729
- this.httpManagerSignalsService.getRequest(reqParams.apiOptions, reqParams.path)
10730
- .subscribe({
10731
- next: (res) => this.GET_result = res,
10732
- error: (err) => this.errorHandling(err, 'GET')
10733
- });
10734
- }
10735
- onCreateRequest() {
10736
- if (!this.isValid)
10737
- return;
10738
- const reqParams = this.compileRequest();
10739
- this.requestParams.POST = reqParams.apiOptions;
10740
- // reset local state
10741
- this.POST_result = null;
10742
- this.POST_error = '';
10743
- this.httpManagerSignalsService.postRequest(this.sampleClientData, reqParams.apiOptions, reqParams.path)
10744
- .subscribe({
10745
- next: (res) => this.POST_result = res,
10746
- error: (err) => this.errorHandling(err, 'POST')
10747
- });
10748
- }
10749
- onUpdateRequest() {
10750
- if (!this.isValid)
10751
- return;
10752
- const reqParams = this.compileRequest();
10753
- if (!this.hasId(reqParams.path)) {
10754
- console.log("Missing ID");
10755
- return;
10756
- }
10757
- this.sampleClientData.id = parseInt(reqParams.path[reqParams.path.length - 1]);
10758
- this.requestParams.PUT = reqParams.apiOptions;
10759
- // reset local state
10760
- this.PUT_result = null;
10761
- this.PUT_error = '';
10762
- this.httpManagerSignalsService.putRequest(this.sampleClientData, reqParams.apiOptions, reqParams.path)
10763
- .subscribe({
10764
- next: (res) => this.PUT_result = res,
10765
- error: (err) => this.errorHandling(err, 'PUT')
10766
- });
10767
- }
10768
- onDeleteRequest() {
10769
- if (!this.isValid)
10770
- return;
10771
- const reqParams = this.compileRequest();
10772
- this.requestParams.DELETE = reqParams.apiOptions;
10773
- if (!this.hasId(reqParams.path)) {
10774
- console.log("Missing ID");
10775
- return;
10776
- }
10777
- this.sampleClientData.id = parseInt(reqParams.path[reqParams.path.length - 1]);
10778
- this.requestParams.DELETE = reqParams.apiOptions;
10779
- // reset local state
10780
- this.DELETE_result = null;
10781
- this.DELETE_error = '';
10782
- this.httpManagerSignalsService.deleteRequest(reqParams.apiOptions, reqParams.path)
10783
- .subscribe({
10784
- next: (res) => this.DELETE_result = res,
10785
- error: (err) => this.errorHandling(err, 'DELETE')
10786
- });
10787
- }
10788
- onStreamPostRequest() {
10789
- if (!this.isValid)
10790
- return;
10791
- const reqParams = this.compileRequest();
10792
- let payload = {};
10793
- let apiPath = reqParams.path;
10794
- let apiOptions = reqParams.apiOptions;
10795
- let responseMapper = (items) => items.response;
10796
- if (this.AIType === 0) {
10797
- // API request
10798
- payload = { prompt: this.questionControl.value };
10799
- }
10800
- else {
10801
- // Local Ollama request
10802
- apiOptions.server = "api";
10803
- apiPath = ["generate"];
10804
- apiOptions.stream = true;
10805
- payload = {
10806
- model: "phi3:latest",
10807
- prompt: this.questionControl.value,
10808
- stream: true,
10809
- };
10810
- responseMapper = (items) => items.map((word) => word.response).flat().join('');
10811
- }
10812
- this.requestParams.STREAM = apiOptions;
10813
- this.STREAM_AI_result = null;
10814
- this.STREAM_AI_error = '';
10815
- this.httpManagerSignalsService.postRequest(payload, apiOptions, apiPath)
10816
- .subscribe({
10817
- next: (res) => {
10818
- try {
10819
- this.STREAM_AI_result = responseMapper(res);
10820
- }
10821
- catch (e) {
10822
- this.STREAM_AI_result = res;
10823
- }
10824
- this.questionControl.reset();
10825
- },
10826
- error: (err) => this.errorHandling(err, 'STREAM')
10827
- });
10828
- }
10829
- onStreamRequest() {
10830
- if (!this.isValid)
10831
- return;
10832
- const reqParams = this.compileRequest();
10833
- reqParams.apiOptions.stream = true;
10834
- this.requestParams.GET = reqParams.apiOptions;
10835
- this.STREAM_result = null;
10836
- this.STREAM_error = '';
10837
- this.httpManagerSignalsService.getRequest(reqParams.apiOptions, reqParams.path)
10838
- .subscribe({
10839
- next: (res) => this.STREAM_result = res,
10840
- error: (err) => this.errorHandling(err, 'STREAM')
10841
- });
10842
- }
10843
- onDownloadCompleted() {
10844
- const message = "Download Completed";
10845
- const display = ToastDisplay.adapt({
10846
- message,
10847
- action: 'Ok',
10848
- color: ToastColors.SUCCESS,
10849
- icon: 'sentiment_satisfied_alt',
10850
- });
10851
- this.toastMessage.toastMessage(display);
10852
- }
10853
- onDownloadFailed(err) {
10854
- const message = "Download Failed";
10855
- const display = ToastDisplay.adapt({
10856
- message,
10857
- action: 'Ok',
10858
- color: ToastColors.ERROR,
10859
- icon: 'warning',
10860
- });
10861
- this.toastMessage.toastMessage(display);
10862
- }
10863
- errorHandling(err, type) {
10864
- const message = err?.message || String(err);
10865
- // set local error state
10866
- if (type === 'GET')
10867
- this.GET_error = message;
10868
- if (type === 'POST')
10869
- this.POST_error = message;
10870
- if (type === 'PUT')
10871
- this.PUT_error = message;
10872
- if (type === 'DELETE')
10873
- this.DELETE_error = message;
10874
- if (type === 'STREAM')
10875
- this.STREAM_error = message;
10876
- // also set the shared service error signal
10877
- this.httpManagerSignalsService.error.set(message);
10878
- return err;
10879
- }
10880
- onSelectAIType(type) {
10881
- this.AIType = type;
10882
- }
10883
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestSignalsManagerDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10884
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: RequestSignalsManagerDemoComponent, selector: "app-request-signals-manager-demo", viewQueries: [{ propertyName: "failedState", first: true, predicate: ["failedState"], descendants: true, static: true }, { propertyName: "pollingState", first: true, predicate: ["pollingState"], descendants: true, static: true }], ngImport: i0, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n <span>HTTP Request Signals Manager</span>\n <span style=\"margin-left: .5rem;\">\n <mat-icon color=\"accent\">fiber_new</mat-icon>\n </span>\n </h2>\n\n <div [formGroup]=\"requestForm\" style=\"margin-top: 2rem;\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Adapter (Model)</mat-label>\n <mat-select formControlName=\"adapter\" #adapterSelect>\n <mat-option>None</mat-option>\n @for (adapter of sampleAdaptors; track adapter) {\n <mat-option [value]=\"adapter.value\">\n {{adapter.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Mapper (Model)</mat-label>\n <mat-select formControlName=\"mapper\" #mapperSelect>\n <mat-option>None</mat-option>\n @for (mapper of sampleMappers; track mapper) {\n <mat-option [value]=\"mapper.value\">\n {{mapper.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n @if (adapterSelect.value || mapperSelect.value) {\n <div style=\"display: flex; margin-bottom: 2rem;\">\n <div style=\"flex:1\" class=\"box\">\n <h3>Adapter (Incoming)</h3>\n @if (adapterSelect.value) {\n <div>\n {{ props(adapterSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n <div style=\"flex:1\" class=\"box\">\n <h3>Mapper (Outgoing)</h3>\n @if (mapperSelect.value) {\n <div>\n {{ props(mapperSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n </div>\n }\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>RestPath (/ delimited)</mat-label>\n <input matInput placeholder=\"clients/list\" formControlName=\"path\">\n </mat-form-field>\n </div>\n <div>\n <div formArrayName=\"headers\">\n @for (task of headers.controls; track task; let i = $index) {\n <div [formGroupName]=\"i\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Key</mat-label>\n <input matInput placeholder=\"authentication\" formControlName=\"key\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Value</mat-label>\n <input matInput placeholder=\"sample\" formControlName=\"value\">\n </mat-form-field>\n <div style=\"margin-top: .5rem;\">\n <button mat-icon-button (click)=\"removeHeader(i)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n <button mat-stroked-button (click)=\"addHeader()\" class=\"btn\">Add Header</button>\n </div>\n <div style=\"margin-top: 2rem; display: flex; flex-direction:column; gap: 1rem;\">\n <div>\n <mat-slide-toggle #failedState>Retry on Failed</mat-slide-toggle>\n @if (failedState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem;\" formGroupName=\"retry\">\n <mat-form-field appearance=\"outline\">\n <mat-label>#of Times</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"times\" value=\"3\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Delay Until Next</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"delay\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #pollingState>Polling</mat-slide-toggle>\n @if (pollingState.checked) {\n <div>\n <mat-form-field appearance=\"outline\" style=\"margin-top: 1rem\">\n <mat-label>#of Seconds</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"polling\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n </div>\n </div>\n\n <div style=\"margin-bottom: 1rem; margin-top: 2rem;\">\n @if (isPending()) {\n <mat-progress-bar mode=\"indeterminate\"\n ></mat-progress-bar>\n }\n @if (pollingState.checked && !isPending()) {\n <mat-progress-bar mode=\"determinate\"\n [value]=\"countdown()\"\n ></mat-progress-bar>\n }\n </div>\n\n <div style=\"margin-top: 1rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">GET Request</h2>\n <div>\n <button mat-raised-button (click)=\"onGetRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if (GET_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ GET_error }}</mat-error>\n </div>\n }\n\n @if (GET_result) {\n <div style=\"margin-top: 1rem;\">\n {{ GET_result | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">POST Request</h2>\n <div>\n <button mat-raised-button (click)=\"onCreateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if (POST_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ POST_error }}</mat-error>\n </div>\n }\n\n @if (POST_result) {\n <div style=\"margin-top: 1rem;\">\n {{ POST_result | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">PUT Request</h2>\n <div>\n <button mat-raised-button (click)=\"onUpdateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if (PUT_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ PUT_error }}</mat-error>\n </div>\n }\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if (PUT_result) {\n <div style=\"margin-top: 1rem;\">\n {{ PUT_result | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">DELETE Request</h2>\n <div>\n <button mat-raised-button (click)=\"onDeleteRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if (DELETE_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ DELETE_error }}</mat-error>\n </div>\n }\n\n @if (DELETE_result) {\n <div style=\"margin-top: 1rem;\">\n {{ DELETE_result | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">Streaming GET Request</h2>\n <div>\n <button mat-raised-button (click)=\"onStreamRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <!-- <div *ngIf=\"(STREAM_error$ | async) as stream_error\" style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div> -->\n\n <div style=\"margin-top: 1rem;\">\n @if (STREAM_result; as data) {\n <div class=\"container\">\n <table mat-table [dataSource]=\"data\">\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef> ID </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.id}} </td>\n </ng-container>\n <ng-container matColumnDef=\"name\">\n <th mat-header-cell *matHeaderCellDef> First Name </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.name}} </td>\n </ng-container>\n <ng-container matColumnDef=\"lastName\">\n <th mat-header-cell *matHeaderCellDef> Last Name </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.lastName}} </td>\n </ng-container>\n <ng-container matColumnDef=\"age\">\n <th mat-header-cell *matHeaderCellDef> Age </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.age}} </td>\n </ng-container>\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n </div>\n }\n </div>\n\n</div>\n\n<div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n</div>\n\n<div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1; padding-top: .5rem;\">AI -@if (AIType === 1) {\n <span>STREAMING</span>\n } POST Request</h2>\n <div style=\"display: flex; gap: 1rem;\">\n <button mat-raised-button [matMenuTriggerFor]=\"menu\" style=\"min-width: 120px;\">\n <mat-icon>lan</mat-icon>\n @if (AIType === 0) {\n <span>Server</span>\n } @else {\n Local\n }\n </button>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item (click)=\"onSelectAIType(0)\">Server</button>\n <button mat-menu-item (click)=\"onSelectAIType(1)\">Local</button>\n </mat-menu>\n <div>\n <button mat-raised-button (click)=\"onStreamPostRequest()\" class=\"btn\">Ask Me</button>\n </div>\n </div>\n </div>\n\n <div style=\"display: flex;\">\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Ask me a Question</mat-label>\n <textarea matInput placeholder=\"Why is the sky blue?\" [formControl]=\"questionControl\"></textarea>\n </mat-form-field>\n </div>\n\n @if (STREAM_AI_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ STREAM_AI_error }}</mat-error>\n </div>\n }\n\n @if (AIType === 1) {\n <div style=\"color: red;\">\n You must have Ollama active and the 'phi3:latest' model to use this feature.\n </div>\n } @else {\n <span style=\"color: gray;\">\n Define the RestPath to the API endpoint that will handle the AI request.\n Use: 'ai/chat' for server\n </span>\n }\n\n <div>\n @if (STREAM_AI_result) {\n <div style=\"margin-top: 1rem; font-size: 1.2rem; border-radius: 1rem; border: black 1px solid; padding: 2rem;\">\n <p style=\"margin-bottom: .5rem; white-space:pre-wrap; line-height: 1.6rem;\">{{STREAM_AI_result}}</p>\n </div>\n }\n </div>\n\n</div>\n\n<div style=\"margin-top: 1.5rem; margin-bottom: 1rem; line-height: 1.5rem;\">\n <mat-divider></mat-divider>\n</div>\n\n<div>\n <div style=\"display: flex;\">\n <h2 style=\"flex:1; margin-bottom: 0; padding-top: .5rem; display: flex;\">\n <div>\n Download File\n </div>\n <div style=\"flex:1; margin-left: 1rem;\">\n <mat-slide-toggle #disable>\n @if (disable.checked) {\n <span>\n Enable\n </span>\n } @else {\n Disable\n }\n </mat-slide-toggle>\n </div>\n </h2>\n <div>\n <app-file-downloader\n [disabled]=\"disable.checked\"\n [delayError]=\"3\"\n [apiRequest]=\"downloadRequest\"\n (completed)=\"onDownloadCompleted()\"\n (failed)=\"onDownloadFailed($event)\"\n ></app-file-downloader>\n </div>\n </div>\n</div>\n\n</div>\n", styles: [".btn{min-width:120px}.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;background-color:#f5f5f5}.container{height:400px;overflow:auto}.box{padding:10px;border:1px solid #ccc}\n"], dependencies: [{ kind: "directive", type: i2$1.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: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i2$1.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "directive", type: i2$1.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i3.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "component", type: i5.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"], 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: "component", type: i7.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: i7.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i7.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i9.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i9.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i9.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i9.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i9.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i9.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i9.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i9.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i9.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i8$1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: i11.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "component", type: i12.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i13.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: FileDownloaderComponent, selector: "app-file-downloader", inputs: ["delayError", "apiRequest", "displayErrorMessage", "saveFileAs", "labels", "disabled"], outputs: ["completed", "failed"] }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }] }); }
10885
- }
10886
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestSignalsManagerDemoComponent, decorators: [{
10887
- type: Component,
10888
- args: [{ selector: 'app-request-signals-manager-demo', standalone: false, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n <span>HTTP Request Signals Manager</span>\n <span style=\"margin-left: .5rem;\">\n <mat-icon color=\"accent\">fiber_new</mat-icon>\n </span>\n </h2>\n\n <div [formGroup]=\"requestForm\" style=\"margin-top: 2rem;\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Adapter (Model)</mat-label>\n <mat-select formControlName=\"adapter\" #adapterSelect>\n <mat-option>None</mat-option>\n @for (adapter of sampleAdaptors; track adapter) {\n <mat-option [value]=\"adapter.value\">\n {{adapter.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Mapper (Model)</mat-label>\n <mat-select formControlName=\"mapper\" #mapperSelect>\n <mat-option>None</mat-option>\n @for (mapper of sampleMappers; track mapper) {\n <mat-option [value]=\"mapper.value\">\n {{mapper.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n @if (adapterSelect.value || mapperSelect.value) {\n <div style=\"display: flex; margin-bottom: 2rem;\">\n <div style=\"flex:1\" class=\"box\">\n <h3>Adapter (Incoming)</h3>\n @if (adapterSelect.value) {\n <div>\n {{ props(adapterSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n <div style=\"flex:1\" class=\"box\">\n <h3>Mapper (Outgoing)</h3>\n @if (mapperSelect.value) {\n <div>\n {{ props(mapperSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n </div>\n }\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>RestPath (/ delimited)</mat-label>\n <input matInput placeholder=\"clients/list\" formControlName=\"path\">\n </mat-form-field>\n </div>\n <div>\n <div formArrayName=\"headers\">\n @for (task of headers.controls; track task; let i = $index) {\n <div [formGroupName]=\"i\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Key</mat-label>\n <input matInput placeholder=\"authentication\" formControlName=\"key\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Value</mat-label>\n <input matInput placeholder=\"sample\" formControlName=\"value\">\n </mat-form-field>\n <div style=\"margin-top: .5rem;\">\n <button mat-icon-button (click)=\"removeHeader(i)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n <button mat-stroked-button (click)=\"addHeader()\" class=\"btn\">Add Header</button>\n </div>\n <div style=\"margin-top: 2rem; display: flex; flex-direction:column; gap: 1rem;\">\n <div>\n <mat-slide-toggle #failedState>Retry on Failed</mat-slide-toggle>\n @if (failedState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem;\" formGroupName=\"retry\">\n <mat-form-field appearance=\"outline\">\n <mat-label>#of Times</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"times\" value=\"3\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Delay Until Next</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"delay\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #pollingState>Polling</mat-slide-toggle>\n @if (pollingState.checked) {\n <div>\n <mat-form-field appearance=\"outline\" style=\"margin-top: 1rem\">\n <mat-label>#of Seconds</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"polling\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n </div>\n </div>\n\n <div style=\"margin-bottom: 1rem; margin-top: 2rem;\">\n @if (isPending()) {\n <mat-progress-bar mode=\"indeterminate\"\n ></mat-progress-bar>\n }\n @if (pollingState.checked && !isPending()) {\n <mat-progress-bar mode=\"determinate\"\n [value]=\"countdown()\"\n ></mat-progress-bar>\n }\n </div>\n\n <div style=\"margin-top: 1rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">GET Request</h2>\n <div>\n <button mat-raised-button (click)=\"onGetRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if (GET_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ GET_error }}</mat-error>\n </div>\n }\n\n @if (GET_result) {\n <div style=\"margin-top: 1rem;\">\n {{ GET_result | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">POST Request</h2>\n <div>\n <button mat-raised-button (click)=\"onCreateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if (POST_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ POST_error }}</mat-error>\n </div>\n }\n\n @if (POST_result) {\n <div style=\"margin-top: 1rem;\">\n {{ POST_result | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">PUT Request</h2>\n <div>\n <button mat-raised-button (click)=\"onUpdateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if (PUT_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ PUT_error }}</mat-error>\n </div>\n }\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if (PUT_result) {\n <div style=\"margin-top: 1rem;\">\n {{ PUT_result | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">DELETE Request</h2>\n <div>\n <button mat-raised-button (click)=\"onDeleteRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if (DELETE_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ DELETE_error }}</mat-error>\n </div>\n }\n\n @if (DELETE_result) {\n <div style=\"margin-top: 1rem;\">\n {{ DELETE_result | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">Streaming GET Request</h2>\n <div>\n <button mat-raised-button (click)=\"onStreamRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <!-- <div *ngIf=\"(STREAM_error$ | async) as stream_error\" style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div> -->\n\n <div style=\"margin-top: 1rem;\">\n @if (STREAM_result; as data) {\n <div class=\"container\">\n <table mat-table [dataSource]=\"data\">\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef> ID </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.id}} </td>\n </ng-container>\n <ng-container matColumnDef=\"name\">\n <th mat-header-cell *matHeaderCellDef> First Name </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.name}} </td>\n </ng-container>\n <ng-container matColumnDef=\"lastName\">\n <th mat-header-cell *matHeaderCellDef> Last Name </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.lastName}} </td>\n </ng-container>\n <ng-container matColumnDef=\"age\">\n <th mat-header-cell *matHeaderCellDef> Age </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.age}} </td>\n </ng-container>\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n </div>\n }\n </div>\n\n</div>\n\n<div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n</div>\n\n<div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1; padding-top: .5rem;\">AI -@if (AIType === 1) {\n <span>STREAMING</span>\n } POST Request</h2>\n <div style=\"display: flex; gap: 1rem;\">\n <button mat-raised-button [matMenuTriggerFor]=\"menu\" style=\"min-width: 120px;\">\n <mat-icon>lan</mat-icon>\n @if (AIType === 0) {\n <span>Server</span>\n } @else {\n Local\n }\n </button>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item (click)=\"onSelectAIType(0)\">Server</button>\n <button mat-menu-item (click)=\"onSelectAIType(1)\">Local</button>\n </mat-menu>\n <div>\n <button mat-raised-button (click)=\"onStreamPostRequest()\" class=\"btn\">Ask Me</button>\n </div>\n </div>\n </div>\n\n <div style=\"display: flex;\">\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Ask me a Question</mat-label>\n <textarea matInput placeholder=\"Why is the sky blue?\" [formControl]=\"questionControl\"></textarea>\n </mat-form-field>\n </div>\n\n @if (STREAM_AI_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ STREAM_AI_error }}</mat-error>\n </div>\n }\n\n @if (AIType === 1) {\n <div style=\"color: red;\">\n You must have Ollama active and the 'phi3:latest' model to use this feature.\n </div>\n } @else {\n <span style=\"color: gray;\">\n Define the RestPath to the API endpoint that will handle the AI request.\n Use: 'ai/chat' for server\n </span>\n }\n\n <div>\n @if (STREAM_AI_result) {\n <div style=\"margin-top: 1rem; font-size: 1.2rem; border-radius: 1rem; border: black 1px solid; padding: 2rem;\">\n <p style=\"margin-bottom: .5rem; white-space:pre-wrap; line-height: 1.6rem;\">{{STREAM_AI_result}}</p>\n </div>\n }\n </div>\n\n</div>\n\n<div style=\"margin-top: 1.5rem; margin-bottom: 1rem; line-height: 1.5rem;\">\n <mat-divider></mat-divider>\n</div>\n\n<div>\n <div style=\"display: flex;\">\n <h2 style=\"flex:1; margin-bottom: 0; padding-top: .5rem; display: flex;\">\n <div>\n Download File\n </div>\n <div style=\"flex:1; margin-left: 1rem;\">\n <mat-slide-toggle #disable>\n @if (disable.checked) {\n <span>\n Enable\n </span>\n } @else {\n Disable\n }\n </mat-slide-toggle>\n </div>\n </h2>\n <div>\n <app-file-downloader\n [disabled]=\"disable.checked\"\n [delayError]=\"3\"\n [apiRequest]=\"downloadRequest\"\n (completed)=\"onDownloadCompleted()\"\n (failed)=\"onDownloadFailed($event)\"\n ></app-file-downloader>\n </div>\n </div>\n</div>\n\n</div>\n", styles: [".btn{min-width:120px}.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;background-color:#f5f5f5}.container{height:400px;overflow:auto}.box{padding:10px;border:1px solid #ccc}\n"] }]
10889
- }], ctorParameters: () => [], propDecorators: { failedState: [{
10890
- type: ViewChild,
10891
- args: ["failedState", { static: true }]
10892
- }], pollingState: [{
10893
- type: ViewChild,
10894
- args: ["pollingState", { static: true }]
10895
- }] } });
10896
-
10897
- // import { MessengerChatModule } from 'src/app/components/messenger-chat/messenger-chat.module';
10898
- // import { StoreStateManagerModule } from "src/app/beta_components/store-state-manager/store-state-manager.module";
10899
- class HttpRequestManagerModule {
10900
- static forRoot(config = ConfigOptions.adapt()) {
10901
- return {
10902
- ngModule: HttpRequestManagerModule,
10903
- providers: [
10904
- { provide: CONFIG_SETTINGS_TOKEN, useValue: ConfigOptions.adapt(config) },
10905
- HTTPManagerService, LocalStorageManagerService //all services that need access to config
10906
- ],
10907
- };
10908
- }
10909
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: HttpRequestManagerModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
10910
- static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.14", ngImport: i0, type: HttpRequestManagerModule, declarations: [RequestManagerBasicDemoComponent,
10911
- HttpRequestServicesDemoComponent,
10912
- RequestManagerStateDemoComponent,
10913
- RequestManagerDemoComponent,
10914
- RequestSignalsManagerDemoComponent,
10915
- LocalStorageDemoComponent,
10916
- LocalStorageSignalsDemoComponent,
10917
- RequestManagerWsDemoComponent,
10918
- StoreStateManagerDemoComponent,
10919
- DatabaseDataDemoComponent,
10920
- WsDataControlComponent,
10921
- WsMessagingComponent,
10922
- WsNotificationsComponent,
10923
- WsAiMessagingComponent,
10924
- WsChatsComponent], imports: [CommonModule,
10925
- ToastMessageDisplayModule,
10926
- FormsModule,
10927
- ReactiveFormsModule,
10928
- MatButtonModule,
10929
- MatTabsModule,
10930
- MatSelectModule,
10931
- MatChipsModule,
10932
- MatMenuModule,
10933
- MatIconModule,
10934
- MatTableModule,
10935
- MatButtonToggleModule,
10936
- MatAutocompleteModule,
10937
- MatProgressBarModule,
10938
- MatProgressSpinnerModule,
10939
- MatSlideToggleModule,
10940
- MatDividerModule,
10941
- MatFormFieldModule,
10942
- MatInputModule,
10943
- MatToolbarModule,
10944
- MatSlideToggleModule, i1.TranslateModule, MatSidenavModule,
10945
- MatDatepickerModule,
10946
- MatNativeDateModule,
10947
- MatCardModule,
10948
- FileDownloaderModule], exports: [HttpRequestServicesDemoComponent] }); }
10949
- static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: HttpRequestManagerModule, providers: [
10950
- { provide: HTTP_INTERCEPTORS, useClass: WithCredentialsInterceptor, multi: true },
10951
- { provide: HTTP_INTERCEPTORS, useClass: RequestHeadersInterceptor, multi: true },
10952
- { provide: HTTP_INTERCEPTORS, useClass: ProxyDebuggerInterceptor, multi: true },
10953
- { provide: CONFIG_SETTINGS_TOKEN, useValue: ConfigOptions.adapt() },
10954
- HTTPManagerService, LocalStorageManagerService,
10955
- ToastMessageDisplayService,
10956
- MessageDisplayRouterService,
10957
- SnackbarStrategy
10958
- ], imports: [CommonModule,
10959
- ToastMessageDisplayModule,
10960
- FormsModule,
10961
- ReactiveFormsModule,
10962
- MatButtonModule,
10963
- MatTabsModule,
10964
- MatSelectModule,
10965
- MatChipsModule,
10966
- MatMenuModule,
10967
- MatIconModule,
10968
- MatTableModule,
10969
- MatButtonToggleModule,
10970
- MatAutocompleteModule,
10971
- MatProgressBarModule,
10972
- MatProgressSpinnerModule,
10973
- MatSlideToggleModule,
10974
- MatDividerModule,
10975
- MatFormFieldModule,
10976
- MatInputModule,
10977
- MatToolbarModule,
10978
- MatSlideToggleModule,
10979
- TranslateModule.forRoot(),
10980
- MatSidenavModule,
10981
- MatDatepickerModule,
10982
- MatNativeDateModule,
10983
- MatCardModule,
10984
- FileDownloaderModule] }); }
7457
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: HttpRequestManagerModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
7458
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.14", ngImport: i0, type: HttpRequestManagerModule, imports: [CommonModule,
7459
+ ToastMessageDisplayModule,
7460
+ FormsModule,
7461
+ ReactiveFormsModule, i1.TranslateModule] }); }
7462
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: HttpRequestManagerModule, providers: [
7463
+ { provide: HTTP_INTERCEPTORS, useClass: WithCredentialsInterceptor, multi: true },
7464
+ { provide: HTTP_INTERCEPTORS, useClass: RequestHeadersInterceptor, multi: true },
7465
+ { provide: HTTP_INTERCEPTORS, useClass: ProxyDebuggerInterceptor, multi: true },
7466
+ { provide: CONFIG_SETTINGS_TOKEN, useValue: ConfigOptions.adapt() },
7467
+ HTTPManagerService,
7468
+ LocalStorageManagerService,
7469
+ ToastMessageDisplayService,
7470
+ MessageDisplayRouterService,
7471
+ SnackbarStrategy,
7472
+ ], imports: [CommonModule,
7473
+ ToastMessageDisplayModule,
7474
+ FormsModule,
7475
+ ReactiveFormsModule,
7476
+ TranslateModule.forRoot()] }); }
10985
7477
  }
10986
7478
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: HttpRequestManagerModule, decorators: [{
10987
7479
  type: NgModule,
@@ -10991,200 +7483,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
10991
7483
  ToastMessageDisplayModule,
10992
7484
  FormsModule,
10993
7485
  ReactiveFormsModule,
10994
- MatButtonModule,
10995
- MatTabsModule,
10996
- MatSelectModule,
10997
- MatChipsModule,
10998
- MatMenuModule,
10999
- MatIconModule,
11000
- MatTableModule,
11001
- MatButtonToggleModule,
11002
- MatAutocompleteModule,
11003
- MatProgressBarModule,
11004
- MatProgressSpinnerModule,
11005
- MatSlideToggleModule,
11006
- MatDividerModule,
11007
- MatFormFieldModule,
11008
- MatInputModule,
11009
- MatToolbarModule,
11010
- MatSlideToggleModule,
11011
7486
  TranslateModule.forRoot(),
11012
- MatSidenavModule,
11013
- MatDatepickerModule,
11014
- MatNativeDateModule,
11015
- MatCardModule,
11016
- FileDownloaderModule,
11017
- // MessengerChatModule,
11018
- // StoreStateManagerModule
11019
- ],
11020
- declarations: [
11021
- RequestManagerBasicDemoComponent,
11022
- HttpRequestServicesDemoComponent,
11023
- RequestManagerStateDemoComponent,
11024
- RequestManagerDemoComponent,
11025
- RequestSignalsManagerDemoComponent,
11026
- LocalStorageDemoComponent,
11027
- LocalStorageSignalsDemoComponent,
11028
- RequestManagerWsDemoComponent,
11029
- StoreStateManagerDemoComponent,
11030
- DatabaseDataDemoComponent,
11031
- WsDataControlComponent,
11032
- WsMessagingComponent,
11033
- WsNotificationsComponent,
11034
- WsAiMessagingComponent,
11035
- WsChatsComponent,
11036
- ],
11037
- exports: [
11038
- HttpRequestServicesDemoComponent,
11039
7487
  ],
7488
+ declarations: [],
7489
+ exports: [],
11040
7490
  providers: [
11041
7491
  { provide: HTTP_INTERCEPTORS, useClass: WithCredentialsInterceptor, multi: true },
11042
7492
  { provide: HTTP_INTERCEPTORS, useClass: RequestHeadersInterceptor, multi: true },
11043
7493
  { provide: HTTP_INTERCEPTORS, useClass: ProxyDebuggerInterceptor, multi: true },
11044
7494
  { provide: CONFIG_SETTINGS_TOKEN, useValue: ConfigOptions.adapt() },
11045
- HTTPManagerService, LocalStorageManagerService,
7495
+ HTTPManagerService,
7496
+ LocalStorageManagerService,
11046
7497
  ToastMessageDisplayService,
11047
7498
  MessageDisplayRouterService,
11048
- SnackbarStrategy
7499
+ SnackbarStrategy,
11049
7500
  ],
11050
7501
  }]
11051
7502
  }] });
11052
7503
 
11053
- const defaultCounterState = () => ({
11054
- count: 0,
11055
- lastUpdated: new Date().toISOString(),
11056
- history: []
11057
- });
11058
- class StoreStateSignalsDemoComponent {
11059
- constructor() {
11060
- // Signals from service
11061
- this.stateJson = signal('');
11062
- this.historyLength = signal(0);
11063
- this.history = signal([]);
11064
- }
11065
- ngOnInit() {
11066
- const options = StateStorageOptions.adapt({
11067
- store: 'counter-state',
11068
- model: defaultCounterState,
11069
- options: {
11070
- storage: 'global',
11071
- encrypted: false
11072
- }
11073
- });
11074
- this.stateManager = new StoreStateManagerSignalsService();
11075
- this.stateManager.init(options);
11076
- // Update signals when state changes
11077
- this.updateSignals();
11078
- }
11079
- updateSignals() {
11080
- const state = this.stateManager.data();
11081
- if (state) {
11082
- this.stateJson.set(JSON.stringify(state, null, 2));
11083
- this.historyLength.set(state.history.length);
11084
- this.history.set([...state.history]);
11085
- }
11086
- }
11087
- increment() {
11088
- const current = this.stateManager.data();
11089
- if (current) {
11090
- this.stateManager.updateState({
11091
- count: current.count + 1,
11092
- lastUpdated: new Date().toISOString()
11093
- });
11094
- this.updateSignals();
11095
- }
11096
- }
11097
- decrement() {
11098
- const current = this.stateManager.data();
11099
- if (current) {
11100
- this.stateManager.updateState({
11101
- count: current.count - 1,
11102
- lastUpdated: new Date().toISOString()
11103
- });
11104
- this.updateSignals();
11105
- }
11106
- }
11107
- reset() {
11108
- this.stateManager.resetState();
11109
- this.updateSignals();
11110
- }
11111
- addToHistory() {
11112
- const current = this.stateManager.data();
11113
- if (current) {
11114
- this.stateManager.updateState({
11115
- history: [...current.history, current.count],
11116
- lastUpdated: new Date().toISOString()
11117
- });
11118
- this.updateSignals();
11119
- }
11120
- }
11121
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StoreStateSignalsDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
11122
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: StoreStateSignalsDemoComponent, selector: "app-store-state-signals-demo", ngImport: i0, template: `
11123
- <div class="demo-container">
11124
- <h2>Store State Manager Signals Demo</h2>
11125
- <p>Signal-based state management with localStorage persistence</p>
11126
-
11127
- <div class="state-display">
11128
- <h3>Current State (Signal)</h3>
11129
- <pre>{{ stateJson() }}</pre>
11130
- </div>
11131
-
11132
- <div class="actions">
11133
- <button (click)="increment()">Increment</button>
11134
- <button (click)="decrement()">Decrement</button>
11135
- <button (click)="reset()">Reset</button>
11136
- <button (click)="addToHistory()">Add to History</button>
11137
- </div>
11138
-
11139
- <div class="history">
11140
- <h3>History: {{ historyLength() }} items</h3>
11141
- <ul>
11142
- @for (item of history(); track item; let i = $index) {
11143
- <li>{{ i + 1 }}. {{ item }}</li>
11144
- }
11145
- </ul>
11146
- </div>
11147
- </div>
11148
- `, isInline: true, styles: [".demo-container{padding:20px;max-width:600px;margin:0 auto}.state-display{background:#f5f5f5;padding:15px;border-radius:5px;margin:20px 0}pre{background:#fff;padding:10px;border:1px solid #ddd;border-radius:3px}.actions{display:flex;gap:10px;margin:20px 0}button{padding:10px 20px;background:#007bff;color:#fff;border:none;border-radius:4px;cursor:pointer}button:hover{background:#0056b3}.history{margin-top:20px}ul{max-height:200px;overflow-y:auto}\n"] }); }
11149
- }
11150
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StoreStateSignalsDemoComponent, decorators: [{
11151
- type: Component,
11152
- args: [{ selector: 'app-store-state-signals-demo', template: `
11153
- <div class="demo-container">
11154
- <h2>Store State Manager Signals Demo</h2>
11155
- <p>Signal-based state management with localStorage persistence</p>
11156
-
11157
- <div class="state-display">
11158
- <h3>Current State (Signal)</h3>
11159
- <pre>{{ stateJson() }}</pre>
11160
- </div>
11161
-
11162
- <div class="actions">
11163
- <button (click)="increment()">Increment</button>
11164
- <button (click)="decrement()">Decrement</button>
11165
- <button (click)="reset()">Reset</button>
11166
- <button (click)="addToHistory()">Add to History</button>
11167
- </div>
11168
-
11169
- <div class="history">
11170
- <h3>History: {{ historyLength() }} items</h3>
11171
- <ul>
11172
- @for (item of history(); track item; let i = $index) {
11173
- <li>{{ i + 1 }}. {{ item }}</li>
11174
- }
11175
- </ul>
11176
- </div>
11177
- </div>
11178
- `, styles: [".demo-container{padding:20px;max-width:600px;margin:0 auto}.state-display{background:#f5f5f5;padding:15px;border-radius:5px;margin:20px 0}pre{background:#fff;padding:10px;border:1px solid #ddd;border-radius:3px}.actions{display:flex;gap:10px;margin:20px 0}button{padding:10px 20px;background:#007bff;color:#fff;border:none;border-radius:4px;cursor:pointer}button:hover{background:#0056b3}.history{margin-top:20px}ul{max-height:200px;overflow-y:auto}\n"] }]
11179
- }] });
11180
-
11181
7504
  /*
11182
7505
  * Public API Surface of http-request-manager
11183
7506
  */
7507
+ // Core module and services
11184
7508
 
11185
7509
  /**
11186
7510
  * Generated bundle index. Do not edit.
11187
7511
  */
11188
7512
 
11189
- export { ApiRequest, AppService, AsymmetricalEncryptionService, BatchOptions, BatchResult, CONFIG_SETTINGS_TOKEN, ChannelType, ConfigHTTPOptions, ConfigOptions, DataType, DatabaseDataDemoComponent, DatabaseManagerService, DatabaseStorage, DbService, ErrorDisplaySettings, GlobalStoreOptions, HTTPManagerService, HTTPManagerSignalsService, HTTPManagerStateService, HeadersService, HttpRequestManagerModule, HttpRequestServicesDemoComponent, LocalStorageDemoComponent, LocalStorageManagerService, LocalStorageOptions, LocalStorageSignalsDemoComponent, LocalStorageSignalsManagerService, LoggerService, NotificationMessage, PathQueryService, PublicMessage, Random, RandomHSLColor, RandomHexColor, RandomNumber, RandomNumbers, RandomNumbersUnique, RandomPaletteColor, RandomSignature, RandomStr, RandomVisibleColor, RequestErrorInterceptor, RequestHeadersInterceptor, RequestManagerDemoComponent, RequestManagerStateDemoComponent, RequestOptions, RequestService, RequestSignalsService, RetryOptions, SettingOptions, StateMessage, StateStorageOptions, StorageData, StorageOption, StorageType, StoreStateManagerService, StoreStateManagerSignalsService, StoreStateSignalsDemoComponent, StreamType, SymmetricalEncryptionService, TableSchemaDef, UUID, UUID_STR, UserData, UtilsService, WSOptions, WebSocketMessageService, WithCredentialsInterceptor, calculateBatchProgress, countdown, createChannelName, delayedRetry, isErrorState, isPendingState, isSuccessState, requestPolling, requestStreaming, streamAI, streamAuto, streamEvents, streamJSON, streamNDJSON };
7513
+ export { Action, ApiRequest, AppService, AsymmetricalEncryptionService, BatchOptions, BatchRequestResultModel, BatchResult, CONFIG_SETTINGS_TOKEN, ChannelType, CommunicationMessage, ConfigHTTPOptions, ConfigOptions, DataType, DatabaseManagerService, DatabaseStorage, DbService, DisplayConfig, DisplayRule, ErrorDisplaySettings, GlobalStoreOptions, HTTPManagerService, HTTPManagerSignalsService, HTTPManagerStateService, HeadersService, HttpRequestManagerModule, LocalStorageManagerService, LocalStorageOptions, LocalStorageSignalsManagerService, LoggerService, MessageContent, MessageDisplayRouterService, NotificationMessage, PathQueryService, PublicMessage, Random, RandomHSLColor, RandomHexColor, RandomNumber, RandomNumbers, RandomNumbersUnique, RandomPaletteColor, RandomSignature, RandomStr, RandomVisibleColor, RequestErrorInterceptor, RequestHeadersInterceptor, RequestOptions, RequestService, RequestSignalsService, RetryOptions, SettingOptions, Slide, SnackbarStrategy, StateMessage, StateStorageOptions, StorageData, StorageOption, StorageType, StoreStateManagerService, StoreStateManagerSignalsService, StreamType, SymmetricalEncryptionService, TableSchemaDef, UUID, UUID_STR, UserData, UtilsService, WSOptions, WebSocketMessageService, WithCredentialsInterceptor, calculateBatchProgress, countdown, createChannelName, defaultDisplayRules, delayedRetry, isErrorState, isPendingState, isSuccessState, requestPolling, requestStreaming, streamAI, streamAuto, streamEvents, streamJSON, streamNDJSON };
11190
7514
  //# sourceMappingURL=http-request-manager.mjs.map