http-request-manager 18.11.10 → 18.11.13

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) {
@@ -7278,227 +7237,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
7278
7237
  args: [PROXY_CONFIG]
7279
7238
  }] }] });
7280
7239
 
7281
- class DownloadFileComponent {
7282
- constructor() {
7283
- this.subscriptions = new Subscription();
7284
- this.displayError = 3; // seconds
7285
- this.diameter = 32;
7286
- this.mode = 'determinate';
7287
- this.isPending = false;
7288
- this.active = false;
7289
- this.disabled = false;
7290
- this.error = new EventEmitter();
7291
- this._progress = 0;
7292
- this._hasError = false;
7293
- this.errorTimerActive = false;
7294
- }
7295
- set progress(value) {
7296
- this._progress = value ?? 0;
7297
- }
7298
- get progress() {
7299
- return this._progress;
7300
- }
7301
- set hasError(value) {
7302
- this._hasError = !!value;
7303
- if (this._hasError && !this.errorTimerActive) {
7304
- this.errorTimerActive = true;
7305
- this.active = false;
7306
- this.error.emit();
7307
- this.subscriptions.add(timer(this.displayError * 1000)
7308
- .subscribe((err) => {
7309
- this._hasError = false;
7310
- this.errorTimerActive = false;
7311
- }));
7312
- }
7313
- }
7314
- get hasError() {
7315
- return this._hasError;
7316
- }
7317
- ngOnInit() { }
7318
- onAction() {
7319
- this.isPending = false;
7320
- if (this.event)
7321
- this.event();
7322
- }
7323
- ngOnDestroy() {
7324
- this.subscriptions.unsubscribe();
7325
- }
7326
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DownloadFileComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7327
- 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 }); }
7328
- }
7329
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DownloadFileComponent, decorators: [{
7330
- type: Component,
7331
- 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"] }]
7332
- }], propDecorators: { event: [{
7333
- type: Input
7334
- }], displayError: [{
7335
- type: Input
7336
- }], diameter: [{
7337
- type: Input
7338
- }], mode: [{
7339
- type: Input
7340
- }], isPending: [{
7341
- type: Input
7342
- }], active: [{
7343
- type: Input
7344
- }], disabled: [{
7345
- type: Input
7346
- }], error: [{
7347
- type: Output
7348
- }], progress: [{
7349
- type: Input
7350
- }], hasError: [{
7351
- type: Input
7352
- }] } });
7353
-
7354
- class DownloadLabels {
7355
- constructor(error = '', action = '', icon = 'error') {
7356
- this.error = error;
7357
- this.action = action;
7358
- this.icon = icon;
7359
- }
7360
- static adapt(item) {
7361
- return new DownloadLabels(item?.error, item?.action, item?.icon);
7362
- }
7363
- }
7364
-
7365
- class FileDownloaderComponent extends HTTPManagerService {
7366
- set labels(value) {
7367
- this._labels = (value) ? DownloadLabels.adapt(value) : DownloadLabels.adapt();
7368
- }
7369
- get labels() {
7370
- return this._labels;
7371
- }
7372
- constructor() {
7373
- super();
7374
- this.delayError = 3;
7375
- this.apiRequest = ApiRequest.adapt();
7376
- this.displayErrorMessage = false;
7377
- this._labels = DownloadLabels.adapt();
7378
- this.active = false;
7379
- this.subscription = new Subscription();
7380
- this.completed = new EventEmitter();
7381
- this.failed = new EventEmitter();
7382
- this.disabled = false;
7383
- }
7384
- ngOnInit() {
7385
- }
7386
- onDownloadStreaming() {
7387
- if (this.active)
7388
- return;
7389
- this.active = true;
7390
- return this.downloadRequest(this.apiRequest, [])
7391
- .pipe(distinctUntilChanged(), catchError((err) => {
7392
- this.onError(err.message);
7393
- this.active = false;
7394
- this.failed.emit(err);
7395
- return throwError(() => err);
7396
- }), finalize(() => {
7397
- if (!this.active)
7398
- return;
7399
- this.active = false;
7400
- this.completed.emit();
7401
- }))
7402
- .subscribe();
7403
- }
7404
- onError(message) {
7405
- if (!message || !this.displayErrorMessage)
7406
- return;
7407
- const display = ToastDisplay.adapt({
7408
- message,
7409
- action: 'Ok',
7410
- color: ToastColors.ERROR,
7411
- icon: 'error',
7412
- });
7413
- this.active = false;
7414
- this.toastMessage.toastMessage(display);
7415
- }
7416
- OnDestroy() {
7417
- this.subscription.unsubscribe();
7418
- }
7419
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FileDownloaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7420
- 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 }); }
7421
- }
7422
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FileDownloaderComponent, decorators: [{
7423
- type: Component,
7424
- 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"] }]
7425
- }], ctorParameters: () => [], propDecorators: { delayError: [{
7426
- type: Input
7427
- }], apiRequest: [{
7428
- type: Input
7429
- }], displayErrorMessage: [{
7430
- type: Input
7431
- }], saveFileAs: [{
7432
- type: Input
7433
- }], labels: [{
7434
- type: Input
7435
- }], completed: [{
7436
- type: Output
7437
- }], failed: [{
7438
- type: Output
7439
- }], disabled: [{
7440
- type: Input
7441
- }] } });
7442
-
7443
- class SpinnerComponent {
7444
- constructor() {
7445
- this.value = 0;
7446
- }
7447
- ngOnInit() {
7448
- }
7449
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SpinnerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7450
- 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"] }] }); }
7451
- }
7452
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SpinnerComponent, decorators: [{
7453
- type: Component,
7454
- 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"] }]
7455
- }], ctorParameters: () => [], propDecorators: { color: [{
7456
- type: Input
7457
- }], diameter: [{
7458
- type: Input
7459
- }], display: [{
7460
- type: Input
7461
- }], mode: [{
7462
- type: Input
7463
- }], strokeWidth: [{
7464
- type: Input
7465
- }], value: [{
7466
- type: Input
7467
- }] } });
7468
-
7469
- class FileDownloaderModule {
7470
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FileDownloaderModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
7471
- static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.14", ngImport: i0, type: FileDownloaderModule, declarations: [SpinnerComponent,
7472
- DownloadFileComponent,
7473
- FileDownloaderComponent], imports: [CommonModule,
7474
- MatIconModule,
7475
- MatProgressSpinnerModule,
7476
- MatButtonModule], exports: [FileDownloaderComponent] }); }
7477
- static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FileDownloaderModule, imports: [CommonModule,
7478
- MatIconModule,
7479
- MatProgressSpinnerModule,
7480
- MatButtonModule] }); }
7481
- }
7482
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FileDownloaderModule, decorators: [{
7483
- type: NgModule,
7484
- args: [{
7485
- imports: [
7486
- CommonModule,
7487
- MatIconModule,
7488
- MatProgressSpinnerModule,
7489
- MatButtonModule,
7490
- ],
7491
- declarations: [
7492
- SpinnerComponent,
7493
- DownloadFileComponent,
7494
- FileDownloaderComponent,
7495
- ],
7496
- exports: [
7497
- FileDownloaderComponent
7498
- ]
7499
- }]
7500
- }] });
7501
-
7502
7240
  class DisplayConfig {
7503
7241
  constructor(type = 'snackbar', supportsMarkdown, stackable, queueBehavior, autoDismiss, width, height) {
7504
7242
  this.type = type;
@@ -7705,3284 +7443,37 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
7705
7443
  args: [{ providedIn: 'root' }]
7706
7444
  }] });
7707
7445
 
7708
- let ClientInfo$2 = class ClientInfo {
7709
- constructor(domain = '', service = '', id = 0, name = '') {
7710
- this.domain = domain;
7711
- this.service = service;
7712
- this.id = id;
7713
- this.name = name;
7714
- }
7715
- static adapt(item) {
7716
- return new ClientInfo(item?.domain, item?.service, item?.id, (item?.first_name || item?.last_name) ? `${item?.first_name} ${item?.last_name}` : '');
7717
- }
7718
- };
7719
-
7720
- let ClientInfoMapper$2 = class ClientInfoMapper {
7721
- constructor(id = 0, first_name = '', last_name = '', email = '') {
7722
- this.id = id;
7723
- this.first_name = first_name;
7724
- this.last_name = last_name;
7725
- this.email = email;
7726
- }
7727
- static adapt(item) {
7728
- const first_name = (item?.name) ? item.name.split(' ')[0] : '';
7729
- const last_name = (item?.name) ? item.name.split(' ')[1] : '';
7730
- return new ClientInfoMapper(item?.id, first_name, last_name, item?.email);
7731
- }
7732
- };
7733
-
7734
- let AIPrompt$2 = class AIPrompt {
7735
- constructor(response = '') {
7736
- this.response = response;
7737
- }
7738
- static adapt(item) {
7739
- return new AIPrompt(item?.response);
7740
- }
7741
- };
7742
-
7743
- class RequestManagerBasicDemoComponent {
7744
- // Dynamic columns based on data structure
7745
- getColumnsFromData(data) {
7746
- if (!data || data.length === 0) {
7747
- return [];
7748
- }
7749
- const firstRecord = data[0];
7750
- if (!firstRecord || typeof firstRecord !== 'object') {
7751
- return [];
7752
- }
7753
- // Extract all keys from the first record, excluding null/undefined
7754
- return Object.keys(firstRecord).filter(key => firstRecord[key] !== null && firstRecord[key] !== undefined);
7755
- }
7756
- // Update displayed columns when data changes
7757
- updateDisplayedColumns(data) {
7758
- this.displayedColumns = this.getColumnsFromData(data);
7759
- console.log('[DEMO] Updated columns:', this.displayedColumns);
7760
- }
7761
- // Helper to check if value is an object
7762
- isObject(value) {
7763
- return value !== null && typeof value === 'object' && !Array.isArray(value);
7764
- }
7765
- get retry() {
7766
- return this.requestForm.get('retry')?.value;
7767
- }
7768
- get headers() {
7769
- return this.requestForm.get('headers');
7770
- }
7771
- get isValid() {
7772
- this.requestForm.markAllAsTouched();
7773
- return this.requestForm.valid;
7774
- }
7775
- constructor() {
7776
- this.server = 'http://localhost:8080';
7777
- this.displayedColumns = [];
7778
- this.fb = inject(FormBuilder);
7779
- this.toastMessage = inject(ToastMessageDisplayService);
7780
- this.questionControl = this.fb.control("", [Validators.required]);
7781
- this.httpManagerService = inject(HTTPManagerService);
7782
- this.isPending$ = this.httpManagerService.isPending$;
7783
- this.countdown$ = this.httpManagerService.countdown$;
7784
- this.GET_error$ = new BehaviorSubject('');
7785
- this.POST_error$ = new BehaviorSubject('');
7786
- this.PUT_error$ = new BehaviorSubject('');
7787
- this.DELETE_error$ = new BehaviorSubject('');
7788
- this.STREAM_error$ = new BehaviorSubject('');
7789
- this.STREAM_AI_error$ = new BehaviorSubject('');
7790
- this.requestParams = {
7791
- GET: ApiRequest.adapt(),
7792
- POST: ApiRequest.adapt(),
7793
- PUT: ApiRequest.adapt(),
7794
- DELETE: ApiRequest.adapt(),
7795
- STREAM: ApiRequest.adapt(),
7796
- };
7797
- this.streamTypes = [
7798
- { id: 'JSON', value: 'json' },
7799
- { id: 'NDJSON', value: 'ndjson' },
7800
- { id: 'AI Streaming', value: 'ai_streaming' },
7801
- { id: 'Event Stream', value: 'event_stream' },
7802
- { id: 'Auto', value: 'auto' },
7803
- ];
7804
- this.streamType = 'Auto';
7805
- this.downloadRequest = ApiRequest.adapt({
7806
- server: 'assets/images',
7807
- path: ['lego.png'],
7808
- saveAs: 'john.jpg', // Optional
7809
- headers: { 'Authorization': 'Bearer Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6InJ0c0ZULWItN0x1WTdEVlllU05LY0lKN1ZuYyJ9.eyJhdWQiOiI4ODhhMzFjZS01OWQzLTRhMTItYTU5Ni04ZDYyZjY0MWI1MDUiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vNmY0MTE5ODItY2YyMy00ZTQ1LTk0NDktNGI2MDdiN2E4OGVjL3YyLjAiLCJpYXQiOjE3NjU4MzQxNDksIm5iZiI6MTc2NTgzNDE0OSwiZXhwIjoxNzY1ODM5NzQ2LCJhY2N0IjowLCJhaW8iOiJBV1FBbS84YUFBQUFVUEZOYUhSZkZ4ZVkwRWthcFBzdWZaNG1QYXV6YVRoODRRZUtQRzkrbEFIVE5XanVwQy9ZSjEwK2IrMktWVlJSSXZRNFpwS2xyWTFGd0xRZmtXOTNLbkRNckxSeEMzWTVOOGlQREZ4b1liZExucW1QL1N1ZE1pRW1Va05tTklSWSIsImF6cCI6Ijg4OGEzMWNlLTU5ZDMtNGExMi1hNTk2LThkNjJmNjQxYjUwNSIsImF6cGFjciI6IjAiLCJncm91cHMiOlsiZWE2ZDk5YjAtNDgyNC00MDU0LTk0MTQtZDNhOTZkZDA3MjRiIl0sIm5hbWUiOiJHVExDIEFwcCBTdXBlciBBZG1pbiIsIm9pZCI6IjI2NzUxY2I2LWNlMDEtNDMzMC05OTc0LWZjMzgxMjQ3YTEzYyIsInByZWZlcnJlZF91c2VybmFtZSI6Imd0bGMwMDNAZ3RsYy5jYSIsInJoIjoiMS5BUzRBZ2hsQmJ5UFBSVTZVU1V0Z2UzcUk3TTR4aW9qVFdSSktwWmFOWXZaQnRRVm1BYTR1QUEuIiwicm9sZXMiOlsiZ3RsYy5hY2wuc3VwZXJhZG1pbiJdLCJzY3AiOiJHVExDX0FwcGxpY2F0aW9uIiwic2lkIjoiMDAzZjVhZDktZmJmNi1jZTkyLTg3MjItMWEzNDExYjMzMDJiIiwic3ViIjoiS0RYQ3drVlhUbTRUUmcwaTkxbXZDZzgtQ29uLXpWbk5FQ2Y5LVg0dkpzUSIsInRpZCI6IjZmNDExOTgyLWNmMjMtNGU0NS05NDQ5LTRiNjA3YjdhODhlYyIsInVwbiI6Imd0bGMwMDNAZ3RsYy5jYSIsInV0aSI6InpBaTJoMmpVUUVHbVY0RTJXMWxqQUEiLCJ2ZXIiOiIyLjAiLCJ3aWRzIjpbImI3OWZiZjRkLTNlZjktNDY4OS04MTQzLTc2YjE5NGU4NTUwOSJdLCJ4bXNfZnRkIjoiUDJraW1ZM0tqc1BTRndxNHlZdmJ0bGhrSUk3d2tXNmFpcW1FQ1BaNEdja0JkWE5sWVhOMExXUnpiWE0ifQ.LzppoggMm27smSAy9SamtPN95vCzdELCAfhtOj5n_T_H6g9xCmNRLS9FaUFQMau6Qvl0lROKl7WDklTswLFkfxbIxCBWtXdL-LTqT5cDURSJAll8vC3zlN3Hg9pAFBUVZFRolt6Z7LvPdI3pvUOQs0yFwVzp9k6cLF8aemKdwKQrMX3XXua1MfBWZcqQ4WiBVNmKh8w6yQB35I4u5WqdFnu33nUGb-kvc18SOpoUfiJnlV-PudaEzFXdU3CjAaMEcuPFv5xLwWJKuhU73dNH4EyQDFMVGtcIHNnieOfiY_nK2_0-5DM6aI40UIRK6Bt-HmMQpnbhLps5y3ep6Z7RNw' } // Optional
7810
- });
7811
- // downloadRequest = ApiRequest.adapt({
7812
- // server: 'oidc/ai/file'
7813
- // })
7814
- this.sampleClientData = {
7815
- id: 0,
7816
- name: "Old School Dates",
7817
- domain: "osd.com",
7818
- service: "osd",
7819
- spiffe: "osd.com/osd",
7820
- secret: "SMOPECXP-OS4P-USOG-X2II-3XMD1FQDR3IJX",
7821
- created: 1693003138,
7822
- modified: 1693003138,
7823
- icon: "",
7824
- imageFile: "",
7825
- email: "wavecoders@gmail.com"
7826
- };
7827
- this.requestForm = this.fb.group({
7828
- path: this.fb.control("ai/"),
7829
- headers: this.fb.array([]),
7830
- adapter: [null],
7831
- mapper: [null],
7832
- retry: this.fb.group({
7833
- times: [3],
7834
- delay: [3],
7835
- }),
7836
- polling: [3],
7837
- });
7838
- this.AIType = 0;
7839
- this.sampleAdaptors = [
7840
- { label: "ClientInfo Basic", value: ClientInfo$2.adapt },
7841
- { label: "AI Prompt", value: AIPrompt$2.adapt },
7842
- ];
7843
- this.sampleMappers = [
7844
- { label: "Mapper Basic", value: ClientInfoMapper$2.adapt },
7845
- { label: "AI Prompt", value: AIPrompt$2.adapt },
7846
- ];
7847
- this.hasId = (arr) => {
7848
- if (arr.length === 0)
7849
- return false;
7850
- return !isNaN(arr[arr.length - 1]);
7851
- };
7852
- this.props = (adapter) => {
7853
- return (adapter) ? adapter() : null;
7854
- };
7855
- // server = `http://sample-endpoint/as/authorization.oauth2`
7856
- this.arrayObjectsToObjects = (arr) => {
7857
- return Array.isArray(arr) ? arr.reduce((obj, item) => Object.assign(obj, { [item.key]: item.value }), {}) : {};
7858
- };
7859
- }
7860
- ngOnInit() {
7861
- // const reqGet2 = ApiRequest.adapt({
7862
- // server,
7863
- // path: ['clients'],
7864
- // headers: { authentication: "Bearer <KEY>" },
7865
- // adapter: ClientInfo,
7866
- // dataType: DataType.OBJECT,
7867
- // // concurrent: false,
7868
- // // polling: 3, //seconds
7869
- // })
7870
- // const req2 = [1024,1025,1026].map(item => {
7871
- // return this.httpManagerService.getRequest<ClientInfo[]>(reqGet2, [item])
7872
- // .pipe(
7873
- // catchError(error => {
7874
- // return throwError(() => new Error(error.error.message))
7875
- // })
7876
- // )
7877
- // })
7878
- // forkJoin(req2)
7879
- // .subscribe(res => console.log(res))
7880
- }
7881
- onStreamType(type) {
7882
- this.streamType = type;
7883
- }
7884
- addHeader() {
7885
- const header = this.fb.group({
7886
- key: ['', Validators.required],
7887
- value: ['']
7888
- });
7889
- this.headers.push(header);
7890
- }
7891
- removeHeader(index) {
7892
- this.headers.removeAt(index);
7893
- }
7894
- compileRequest() {
7895
- const requestParams = this.requestForm.value;
7896
- requestParams.headers = this.arrayObjectsToObjects(requestParams.headers || []);
7897
- const pathReq = (requestParams.path === "") ? [] : (requestParams.path || "").split("/");
7898
- if (!this.pollingState.checked)
7899
- requestParams.polling = 0;
7900
- if (!this.failedState.checked) {
7901
- requestParams.retry = { times: 0, delay: 0 };
7902
- }
7903
- const apiOptions = ApiRequest.adapt(requestParams);
7904
- apiOptions.path = [];
7905
- apiOptions.server = this.server;
7906
- apiOptions.adapter = this.adapter;
7907
- apiOptions.mapper = this.mapper;
7908
- return { apiOptions: apiOptions, path: pathReq };
7909
- }
7910
- onGetRequest() {
7911
- if (!this.isValid)
7912
- return;
7913
- const reqParams = this.compileRequest();
7914
- this.requestParams.GET = reqParams.apiOptions;
7915
- this.GET$ = EMPTY; //Cancels Previous
7916
- this.GET_error$.next('');
7917
- this.GET$ = this.httpManagerService.getRequest({ ...reqParams.apiOptions }, reqParams.path)
7918
- .pipe(
7919
- // tap((data) => console.log("API GET response", data)),
7920
- catchError(error => {
7921
- return throwError(() => this.errorHandling(error, 'GET'));
7922
- }));
7923
- }
7924
- onCreateRequest() {
7925
- if (!this.isValid)
7926
- return;
7927
- const reqParams = this.compileRequest();
7928
- this.requestParams.POST = reqParams.apiOptions;
7929
- this.POST$ = EMPTY; //Cancels Previous
7930
- this.POST_error$.next('');
7931
- console.log("POST", this.sampleClientData);
7932
- console.log("POST", reqParams.apiOptions);
7933
- console.log("POST", reqParams.path);
7934
- this.POST$ = this.httpManagerService.postRequest(this.sampleClientData, reqParams.apiOptions, reqParams.path)
7935
- .pipe(
7936
- // tap((data) => console.log("API POST response", data)),
7937
- catchError(error => {
7938
- return throwError(() => this.errorHandling(error, 'POST'));
7939
- }));
7940
- }
7941
- onUpdateRequest() {
7942
- if (!this.isValid)
7943
- return;
7944
- const reqParams = this.compileRequest();
7945
- if (!this.hasId(reqParams.path)) {
7946
- console.log("Missing ID");
7947
- return;
7948
- }
7949
- this.sampleClientData.id = parseInt(reqParams.path[reqParams.path.length - 1]);
7950
- this.requestParams.PUT = reqParams.apiOptions;
7951
- this.PUT$ = EMPTY; //Cancels Previous
7952
- this.PUT_error$.next('');
7953
- this.PUT$ = this.httpManagerService.putRequest(this.sampleClientData, reqParams.apiOptions, reqParams.path)
7954
- .pipe(
7955
- // tap((data) => console.log("API PUT response", data)),
7956
- catchError(error => {
7957
- return throwError(() => this.errorHandling(error, 'PUT'));
7958
- }));
7959
- }
7960
- onDeleteRequest() {
7961
- if (!this.isValid)
7962
- return;
7963
- const reqParams = this.compileRequest();
7964
- this.requestParams.DELETE = reqParams.apiOptions;
7965
- if (!this.hasId(reqParams.path)) {
7966
- console.log("Missing ID");
7967
- return;
7968
- }
7969
- this.sampleClientData.id = parseInt(reqParams.path[reqParams.path.length - 1]);
7970
- this.requestParams.DELETE = reqParams.apiOptions;
7971
- this.DELETE$ = EMPTY; //Cancels Previous
7972
- this.DELETE_error$.next('');
7973
- this.DELETE$ = this.httpManagerService.deleteRequest(reqParams.apiOptions, reqParams.path)
7974
- .pipe(
7975
- // tap((data) => console.log("API DELETE response", data)),
7976
- catchError(error => {
7977
- return throwError(() => this.errorHandling(error, 'DELETE'));
7978
- }));
7979
- }
7980
- onStreamPostRequest() {
7981
- if (!this.isValid)
7982
- return;
7983
- const reqParams = this.compileRequest();
7984
- let payload = {};
7985
- let apiPath = reqParams.path;
7986
- let apiOptions = reqParams.apiOptions;
7987
- let responseMapper = (items) => items.response;
7988
- if (this.AIType === 0) {
7989
- // API request
7990
- payload = { prompt: this.questionControl.value };
7991
- }
7992
- else {
7993
- // Local Ollama request
7994
- apiOptions.server = "api";
7995
- apiPath = ["generate"];
7996
- apiOptions.stream = true;
7997
- apiOptions.streamType = this.streamType;
7998
- payload = {
7999
- model: "phi3:latest",
8000
- prompt: this.questionControl.value,
8001
- stream: true,
8002
- };
8003
- responseMapper = (items) => items.map((word) => word.response).flat().join('');
8004
- }
8005
- this.requestParams.STREAM = apiOptions;
8006
- this.STREAM_AI$ = EMPTY;
8007
- this.STREAM_AI_error$.next('');
8008
- this.STREAM_AI$ = this.httpManagerService.postRequest(payload, apiOptions, apiPath).pipe(map(responseMapper), tap(() => this.questionControl.reset()), catchError(error => throwError(() => this.errorHandling(error, 'STREAM'))));
8009
- }
8010
- onStreamRequest() {
8011
- if (!this.isValid)
8012
- return;
8013
- const reqParams = this.compileRequest();
8014
- reqParams.apiOptions.stream = true;
8015
- reqParams.apiOptions.streamType = StreamType.NDJSON;
8016
- this.requestParams.GET = reqParams.apiOptions;
8017
- this.STREAM$ = this.httpManagerService.getRequest(reqParams.apiOptions, reqParams.path)
8018
- .pipe(tap((data) => {
8019
- console.log("API STREAM response", data);
8020
- if (data && data.length > 0) {
8021
- this.updateDisplayedColumns(data);
8022
- }
8023
- }), catchError(error => {
8024
- return throwError(() => this.errorHandling(error, 'STREAM'));
8025
- }));
8026
- }
8027
- onDownloadCompleted() {
8028
- const message = "Download Completed";
8029
- const display = ToastDisplay.adapt({
8030
- message,
8031
- action: 'Ok',
8032
- color: ToastColors.SUCCESS,
8033
- icon: 'sentiment_satisfied_alt',
8034
- });
8035
- this.toastMessage.toastMessage(display);
8036
- }
8037
- onDownloadFailed(err) {
8038
- const message = "Download Failed";
8039
- const display = ToastDisplay.adapt({
8040
- message,
8041
- action: 'Ok',
8042
- color: ToastColors.ERROR,
8043
- icon: 'warning',
8044
- });
8045
- this.toastMessage.toastMessage(display);
8046
- }
8047
- errorHandling(err, type) {
8048
- if (type === 'GET')
8049
- this.GET_error$.next(err.message);
8050
- if (type === 'POST')
8051
- this.POST_error$.next(err.message);
8052
- if (type === 'PUT')
8053
- this.PUT_error$.next(err.message);
8054
- if (type === 'DELETE')
8055
- this.DELETE_error$.next(err.message);
8056
- if (type === 'STREAM')
8057
- this.STREAM_error$.next(err.message);
8058
- }
8059
- onSelectAIType(type) {
8060
- this.AIType = type;
8061
- }
8062
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerBasicDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8063
- 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" }] }); }
8064
- }
8065
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerBasicDemoComponent, decorators: [{
8066
- type: Component,
8067
- 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"] }]
8068
- }], ctorParameters: () => [], propDecorators: { failedState: [{
8069
- type: ViewChild,
8070
- args: ["failedState", { static: true }]
8071
- }], pollingState: [{
8072
- type: ViewChild,
8073
- args: ["pollingState", { static: true }]
8074
- }] } });
8075
-
8076
- let ClientInfo$1 = class ClientInfo {
8077
- constructor(domain = '', service = '', id = 0, name = '') {
8078
- this.domain = domain;
8079
- this.service = service;
8080
- this.id = id;
8081
- this.name = name;
8082
- }
8083
- static adapt(item) {
8084
- return new ClientInfo(item?.domain, item?.service, item?.id, (item?.first_name || item?.last_name) ? `${item?.first_name} ${item?.last_name}` : '');
8085
- }
8086
- };
8087
-
8088
- let ClientInfoMapper$1 = class ClientInfoMapper {
8089
- constructor(id = 0, first_name = '', last_name = '', email = '') {
8090
- this.id = id;
8091
- this.first_name = first_name;
8092
- this.last_name = last_name;
8093
- this.email = email;
8094
- }
8095
- static adapt(item) {
8096
- const first_name = (item?.name) ? item.name.split(' ')[0] : '';
8097
- const last_name = (item?.name) ? item.name.split(' ')[1] : '';
8098
- return new ClientInfoMapper(item?.id, first_name, last_name, item?.email);
8099
- }
8100
- };
8101
-
8102
- class StateManagerDemoService extends HTTPManagerStateService {
8103
- constructor() {
8104
- super(ApiRequest.adapt({
8105
- server: "",
8106
- path: [],
8107
- headers: {},
8108
- adapter: ClientInfo$1.adapt,
8109
- mapper: ClientInfoMapper$1.adapt,
8110
- stream: false,
8111
- }), DataType.ARRAY, DatabaseStorage.adapt());
8112
- }
8113
- setAPIOptions(apiOptions, dataType, database) {
8114
- this.setApiRequestOptions(apiOptions, dataType, database);
8115
- }
8116
- getClients() {
8117
- // const headers = {
8118
- // auth: "sample-auth-token"
8119
- // }
8120
- // const sampleOptions = RequestOptions.adapt({ path: ["id", 12], headers, sample: true })
8121
- this.fetchRecords();
8122
- }
8123
- createClient(data) {
8124
- // const headers = {
8125
- // auth: "sample-auth-token"
8126
- // }
8127
- // const sampleOptions = RequestOptions.adapt({ path: ["id", 12], headers, sample: true })
8128
- this.createRecord(data);
8129
- }
8130
- updateClient(data) {
8131
- // const headers = {
8132
- // auth: "sample-auth-token"
8133
- // }
8134
- data.id = 1031;
8135
- const sampleOptions = RequestOptions.adapt({ path: [data.id] });
8136
- this.updateRecord(data, sampleOptions);
8137
- }
8138
- deleteClient(data) {
8139
- // const headers = {
8140
- // auth: "sample-auth-token"
8141
- // }
8142
- data.id = 1031;
8143
- const sampleOptions = RequestOptions.adapt({ path: [data.id] });
8144
- this.deleteRecord(sampleOptions);
8145
- }
8146
- streamRequest() {
8147
- console.log('[DEMO SERVICE] streamRequest called');
8148
- const headers = {
8149
- auth: "sample-auth-token"
8150
- };
8151
- console.log('[DEMO SERVICE] Calling fetchStream with headers:', headers);
8152
- this.fetchStream();
8153
- }
8154
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateManagerDemoService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
8155
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateManagerDemoService }); }
8156
- }
8157
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateManagerDemoService, decorators: [{
8158
- type: Injectable
8159
- }], ctorParameters: () => [] });
8160
-
8161
- let AIPrompt$1 = class AIPrompt {
8162
- constructor(response = '') {
8163
- this.response = response;
8164
- }
8165
- static adapt(item) {
8166
- return new AIPrompt(item?.response);
8167
- }
8168
- };
8169
-
8170
- class RequestManagerStateDemoComponent {
8171
- // Dynamic columns based on data structure
8172
- getColumnsFromData(data) {
8173
- if (!data || data.length === 0) {
8174
- return [];
8175
- }
8176
- const firstRecord = data[0];
8177
- if (!firstRecord || typeof firstRecord !== 'object') {
8178
- return [];
8179
- }
8180
- // Extract all keys from the first record, excluding null/undefined
8181
- return Object.keys(firstRecord).filter(key => firstRecord[key] !== null && firstRecord[key] !== undefined);
8182
- }
8183
- // Update displayed columns when data changes
8184
- updateDisplayedColumns(data) {
8185
- this.displayedColumns = this.getColumnsFromData(data);
8186
- console.log('[STATE DEMO] Updated columns:', this.displayedColumns);
8187
- }
8188
- // Helper to check if value is an object
8189
- isObject(value) {
8190
- return value !== null && typeof value === 'object' && !Array.isArray(value);
8191
- }
8192
- // Helper to safely get data length
8193
- getDataLength(data) {
8194
- if (Array.isArray(data)) {
8195
- return data.length;
8196
- }
8197
- return 0;
8198
- }
8199
- get dataObservable$() {
8200
- switch (this.requestType) {
8201
- case 'GET':
8202
- return this.GET$;
8203
- case 'PUT':
8204
- return this.PUT$;
8205
- case 'POST':
8206
- return this.POST$;
8207
- case 'DELETE':
8208
- return this.DELETE$;
8209
- case 'STREAM':
8210
- return this.STREAM;
8211
- case 'STREAM_AI':
8212
- return this.STREAM_AI;
8213
- default:
8214
- return this.GET$;
8215
- break;
8216
- }
8217
- }
8218
- get hasChanged() {
8219
- return this.requestForm.dirty || this.requestForm.untouched;
8220
- }
8221
- get dataType() {
8222
- return this.requestForm.get("datatype")?.value;
8223
- }
8224
- get database() {
8225
- return this.requestForm.get("database")?.value;
8226
- }
8227
- get retry() {
8228
- return this.requestForm.get("retry")?.value;
8229
- }
8230
- get headers() {
8231
- return this.requestForm.get('headers');
8232
- }
8233
- get isValid() {
8234
- this.requestForm.markAllAsTouched();
8235
- return this.requestForm.valid;
8236
- }
8237
- constructor() {
8238
- this.server = 'http://localhost:8080';
8239
- this.stateManagerDemoService = inject(StateManagerDemoService);
8240
- this.displayedColumns = [];
8241
- this.fb = inject(FormBuilder);
8242
- this.streamTypes = [
8243
- { id: 'JSON', value: 'json' },
8244
- { id: 'NDJSON', value: 'ndjson' },
8245
- { id: 'AI Streaming', value: 'ai_streaming' },
8246
- { id: 'Event Stream', value: 'event_stream' },
8247
- { id: 'Auto', value: 'auto' },
8248
- ];
8249
- this.streamType = 'Auto';
8250
- this.httpManagerService = inject(HTTPManagerService);
8251
- this.isPending$ = this.stateManagerDemoService.isPending$;
8252
- this.error$ = this.stateManagerDemoService.error$;
8253
- this.countdown$ = this.httpManagerService.countdown$;
8254
- this.GET_error$ = new BehaviorSubject('');
8255
- this.POST_error$ = new BehaviorSubject('');
8256
- this.PUT_error$ = new BehaviorSubject('');
8257
- this.DELETE_error$ = new BehaviorSubject('');
8258
- this.STREAM_error$ = new BehaviorSubject('');
8259
- this.STREAM_AI_error$ = new BehaviorSubject('');
8260
- this.GET$ = new BehaviorSubject(null);
8261
- this.POST$ = new BehaviorSubject(null);
8262
- this.PUT$ = new BehaviorSubject(null);
8263
- this.DELETE$ = new BehaviorSubject(null);
8264
- this.STREAM = new BehaviorSubject(null);
8265
- this.STREAM$ = this.STREAM.asObservable();
8266
- this.STREAM_AI = new BehaviorSubject([]);
8267
- this.STREAM_AI$ = this.STREAM_AI.asObservable()
8268
- .pipe(map$1((items) => (items) ? items.map((item) => item.response) : []), map$1((items) => items.join('\n').trim()));
8269
- this.questionControl = this.fb.control("", [Validators.required]);
8270
- this.requestType = '';
8271
- this.prompts = [];
8272
- this.AIType = 0;
8273
- this.sampleClientData = {
8274
- id: 0,
8275
- name: "Old School Dates",
8276
- domain: "osd.com",
8277
- service: "osd",
8278
- spiffe: "osd.com/osd",
8279
- secret: "SMOPECXP-OS4P-USOG-X2II-3XMD1FQDR3IJX",
8280
- created: 1693003138,
8281
- modified: 1693003138,
8282
- icon: "",
8283
- imageFile: "",
8284
- };
8285
- this.selectedRecord = this.fb.control(null);
8286
- this.requestForm = this.fb.group({
8287
- datatype: this.fb.control('ARRAY'),
8288
- path: this.fb.control("ai/"),
8289
- headers: this.fb.array([]),
8290
- adapter: [null],
8291
- mapper: [null],
8292
- retry: this.fb.group({
8293
- times: [3],
8294
- delay: [3],
8295
- }),
8296
- polling: [3],
8297
- database: this.fb.group({
8298
- table: [''],
8299
- expiresIn: ['1m'],
8300
- })
8301
- });
8302
- this.sampleAdaptors = [
8303
- { label: "ClientInfo Basic", value: ClientInfo$1.adapt },
8304
- { label: "AI Prompt", value: AIPrompt$1.adapt },
8305
- ];
8306
- this.sampleMappers = [
8307
- { label: "Mapper Basic", value: ClientInfoMapper$1.adapt },
8308
- { label: "AI Prompt", value: AIPrompt$1.adapt },
8309
- ];
8310
- // server = `http://sample-endpoint/as/authorization.oauth2`
8311
- this.arrayObjectsToObjects = (arr) => {
8312
- return Array.isArray(arr) ? arr.reduce((obj, item) => Object.assign(obj, { [item.key]: item.value }), {}) : {};
8313
- };
8314
- this.props = (adapter) => {
8315
- 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
+ ],
8316
7455
  };
8317
7456
  }
8318
- ngOnInit() {
8319
- this.stateManagerDemoService.data$.pipe(tap$1((data) => console.log("API STREAM_AI response", data)));
8320
- this.error$.pipe(tap$1((data) => {
8321
- debugger;
8322
- console.log("API STREAM response", data);
8323
- }), catchError$1(error => {
8324
- return throwError(() => this.errorHandling(error, 'STREAM'));
8325
- }));
8326
- this.selectedRecord.valueChanges
8327
- .subscribe((data) => {
8328
- this.selectedRecord$ = (data) ? this.stateManagerDemoService.selectRecord$(data.id) : EMPTY;
8329
- });
8330
- this.stateManagerDemoService.data$
8331
- .pipe(tap$1((data) => {
8332
- console.log('[COMPONENT] State data received:', data);
8333
- switch (this.requestType) {
8334
- case 'GET':
8335
- console.log('[COMPONENT] Updating GET$ with data:', data);
8336
- this.GET$.next(data);
8337
- break;
8338
- case 'PUT':
8339
- console.log('[COMPONENT] Updating PUT$ with data:', data);
8340
- this.PUT$.next(data);
8341
- break;
8342
- case 'POST':
8343
- console.log('[COMPONENT] Updating POST$ with data:', data);
8344
- this.POST$.next(data);
8345
- break;
8346
- case 'DELETE':
8347
- console.log('[COMPONENT] Updating DELETE$ with data:', data);
8348
- this.DELETE$.next(data);
8349
- break;
8350
- case 'STREAM':
8351
- console.log('[COMPONENT] Updating STREAM$ with data:', data);
8352
- this.STREAM.next(data);
8353
- // Update table columns dynamically based on streaming data
8354
- if (data && Array.isArray(data) && data.length > 0) {
8355
- this.updateDisplayedColumns(data);
8356
- }
8357
- break;
8358
- case 'STREAM_AI':
8359
- console.log('[COMPONENT] Updating STREAM_AI$ with data:', data);
8360
- this.STREAM_AI.next(data);
8361
- break;
8362
- default:
8363
- console.log('[COMPONENT] No requestType set, ignoring data');
8364
- break;
8365
- }
8366
- })).subscribe();
8367
- }
8368
- onStreamType(type) {
8369
- this.streamType = type;
8370
- }
8371
- addHeader() {
8372
- const header = this.fb.group({
8373
- key: ['', Validators.required],
8374
- value: ['']
8375
- });
8376
- this.headers.push(header);
8377
- }
8378
- removeHeader(index) {
8379
- this.headers.removeAt(index);
8380
- }
8381
- compileRequest() {
8382
- const requestParams = this.requestForm.value;
8383
- requestParams.headers = this.arrayObjectsToObjects(requestParams.headers || []);
8384
- const pathReq = (requestParams.path === "") ? [] : (requestParams.path || "").split("/");
8385
- if (!this.pollingState.checked)
8386
- requestParams.polling = 0;
8387
- if (!this.failedState.checked) {
8388
- requestParams.retry = { times: 0, delay: 0 };
8389
- }
8390
- const currentOptions = ApiRequest.adapt(requestParams);
8391
- currentOptions.path = [];
8392
- currentOptions.server = this.server;
8393
- currentOptions.adapter = this.adapter;
8394
- currentOptions.mapper = this.mapper;
8395
- const apiOptions = ApiRequest.adapt({ ...currentOptions, path: pathReq });
8396
- return { apiOptions: apiOptions, path: pathReq };
8397
- }
8398
- onSetStateOptions() {
8399
- if (!this.isValid)
8400
- return;
8401
- const reqParams = this.compileRequest();
8402
- const db = DatabaseStorage.adapt(this.database);
8403
- const type = this.dataType === "ARRAY" ? DataType.ARRAY : DataType.OBJECT;
8404
- this.stateManagerDemoService.setAPIOptions(reqParams.apiOptions, type, db);
8405
- this.requestForm.markAsPristine();
8406
- }
8407
- onClearRecords() {
8408
- this.stateManagerDemoService.clearRecords();
8409
- }
8410
- onGetRequest() {
8411
- this.requestType = 'GET';
8412
- this.stateManagerDemoService.getClients();
8413
- }
8414
- onCreateRequest() {
8415
- this.requestType = 'POST';
8416
- this.stateManagerDemoService.createClient(this.sampleClientData);
8417
- }
8418
- onUpdateRequest() {
8419
- this.requestType = 'PUT';
8420
- this.stateManagerDemoService.updateClient(this.sampleClientData);
8421
- }
8422
- onDeleteRequest() {
8423
- this.requestType = 'DELETE';
8424
- this.stateManagerDemoService.deleteClient(this.sampleClientData);
8425
- }
8426
- onStreamRequest() {
8427
- console.log('[COMPONENT] onStreamRequest called');
8428
- if (!this.isValid) {
8429
- console.log('[COMPONENT] Form invalid, aborting');
8430
- return;
8431
- }
8432
- console.log('[COMPONENT] Compiling request...');
8433
- const reqParams = this.compileRequest();
8434
- console.log('[COMPONENT] Setting stream options:', reqParams.apiOptions);
8435
- reqParams.apiOptions.stream = true;
8436
- reqParams.apiOptions.streamType = this.streamType;
8437
- this.requestType = 'STREAM';
8438
- console.log('[COMPONENT] Calling streamRequest...');
8439
- this.stateManagerDemoService.streamRequest();
8440
- }
8441
- errorHandling(err, type) {
8442
- console.log(err, type);
8443
- if (type === 'GET')
8444
- this.GET_error$.next(err.message);
8445
- if (type === 'POST')
8446
- this.POST_error$.next(err.message);
8447
- if (type === 'PUT')
8448
- this.PUT_error$.next(err.message);
8449
- if (type === 'DELETE')
8450
- this.DELETE_error$.next(err.message);
8451
- if (type === 'STREAM')
8452
- this.STREAM_error$.next(err.message);
8453
- if (type === 'STREAM_AI')
8454
- this.STREAM_AI_error$.next(err.message);
8455
- }
8456
- onSelectAIType(type) {
8457
- this.AIType = type;
8458
- }
8459
- onClearHistory() {
8460
- this.prompts = [];
8461
- }
8462
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerStateDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8463
- 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" }] }); }
8464
- }
8465
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerStateDemoComponent, decorators: [{
8466
- type: Component,
8467
- 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"] }]
8468
- }], ctorParameters: () => [], propDecorators: { server: [{
8469
- type: Input
8470
- }], adapter: [{
8471
- type: Input
8472
- }], mapper: [{
8473
- type: Input
8474
- }], failedState: [{
8475
- type: ViewChild,
8476
- args: ["failedState", { static: true }]
8477
- }], pollingState: [{
8478
- type: ViewChild,
8479
- args: ["pollingState", { static: true }]
8480
- }] } });
8481
-
8482
- class RequestManagerDemoComponent {
8483
- // Dynamic columns based on data structure
8484
- getColumnsFromData(data) {
8485
- if (!data || data.length === 0) {
8486
- return [];
8487
- }
8488
- const firstRecord = data[0];
8489
- if (!firstRecord || typeof firstRecord !== 'object') {
8490
- return [];
8491
- }
8492
- // Extract all keys from the first record, excluding null/undefined
8493
- return Object.keys(firstRecord).filter(key => firstRecord[key] !== null && firstRecord[key] !== undefined);
8494
- }
8495
- // Update displayed columns when data changes
8496
- updateDisplayedColumns(data) {
8497
- this.displayedColumns = this.getColumnsFromData(data);
8498
- console.log('[DEMO] Updated columns:', this.displayedColumns);
8499
- }
8500
- // Helper to check if value is an object
8501
- isObject(value) {
8502
- return value !== null && typeof value === 'object' && !Array.isArray(value);
8503
- }
8504
- // Computed properties for filtered states (removes undefined)
8505
- get filteredParallelStates() {
8506
- return this.parallelBatchStates.filter(s => s !== undefined);
8507
- }
8508
- get filteredSequentialStates() {
8509
- return this.sequentialBatchStates.filter(s => s !== undefined);
8510
- }
8511
- get filteredStreamStates() {
8512
- return this.streamBatchStates.filter(s => s !== undefined);
8513
- }
8514
- get retry() {
8515
- return this.requestForm.get('retry')?.value;
8516
- }
8517
- get headers() {
8518
- return this.requestForm.get('headers');
8519
- }
8520
- get isValid() {
8521
- this.requestForm.markAllAsTouched();
8522
- return this.requestForm.valid;
8523
- }
8524
- constructor() {
8525
- this.server = 'http://localhost:8080';
8526
- this.displayedColumns = [];
8527
- this.fb = inject(FormBuilder);
8528
- this.toastMessage = inject(ToastMessageDisplayService);
8529
- this.questionControl = this.fb.control("", [Validators.required]);
8530
- this.httpManagerService = inject(HTTPManagerService);
8531
- this.isPending$ = this.httpManagerService.isPending$;
8532
- this.countdown$ = this.httpManagerService.countdown$;
8533
- this.GET_error$ = new BehaviorSubject('');
8534
- this.POST_error$ = new BehaviorSubject('');
8535
- this.PUT_error$ = new BehaviorSubject('');
8536
- this.DELETE_error$ = new BehaviorSubject('');
8537
- this.STREAM_error$ = new BehaviorSubject('');
8538
- this.STREAM_AI_error$ = new BehaviorSubject('');
8539
- this.isParallelLoading = false;
8540
- this.isSequentialLoading = false;
8541
- // Parallel batch states for individual request tracking
8542
- this.parallelBatchStates = [];
8543
- // Sequential batch states for individual request tracking
8544
- this.sequentialBatchStates = [];
8545
- // Stream batch request properties
8546
- this.streamBatchStates = [];
8547
- this.isStreamLoading = false;
8548
- this.requestParams = {
8549
- GET: ApiRequest.adapt(),
8550
- POST: ApiRequest.adapt(),
8551
- PUT: ApiRequest.adapt(),
8552
- DELETE: ApiRequest.adapt(),
8553
- STREAM: ApiRequest.adapt(),
8554
- };
8555
- this.streamTypes = [
8556
- { id: 'JSON', value: 'json' },
8557
- { id: 'NDJSON', value: 'ndjson' },
8558
- { id: 'AI Streaming', value: 'ai_streaming' },
8559
- { id: 'Event Stream', value: 'event_stream' },
8560
- { id: 'Auto', value: 'auto' },
8561
- ];
8562
- this.streamType = 'Auto';
8563
- this.downloadRequest = ApiRequest.adapt({
8564
- server: 'assets/images',
8565
- path: ['lego.png'],
8566
- saveAs: 'john.jpg', // Optional
8567
- headers: { 'Authorization': 'Bearer Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6InJ0c0ZULWItN0x1WTdEVlllU05LY0lKN1ZuYyJ9.eyJhdWQiOiI4ODhhMzFjZS01OWQzLTRhMTItYTU5Ni04ZDYyZjY0MWI1MDUiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vNmY0MTE5ODItY2YyMy00ZTQ1LTk0NDktNGI2MDdiN2E4OGVjL3YyLjAiLCJpYXQiOjE3NjU4MzQxNDksIm5iZiI6MTc2NTgzNDE0OSwiZXhwIjoxNzY1ODM5NzQ2LCJhY2N0IjowLCJhaW8iOiJBV1FBbS84YUFBQUFVUEZOYUhSZkZ4ZVkwRWthcFBzdWZaNG1QYXV6YVRoODRRZUtQRzkrbEFIVE5XanVwQy9ZSjEwK2IrMktWVlJSSXZRNFpwS2xyWTFGd0xRZmtXOTNLbkRNckxSeEMzWTVOOGlQREZ4b1liZExucW1QL1N1ZE1pRW1Va05tTklSWSIsImF6cCI6Ijg4OGEzMWNlLTU5ZDMtNGExMi1hNTk2LThkNjJmNjQxYjUwNSIsImF6cGFjciI6IjAiLCJncm91cHMiOlsiZWE2ZDk5YjAtNDgyNC00MDU0LTk0MTQtZDNhOTZkZDA3MjRiIl0sIm5hbWUiOiJHVExDIEFwcCBTdXBlciBBZG1pbiIsIm9pZCI6IjI2NzUxY2I2LWNlMDEtNDMzMC05OTc0LWZjMzgxMjQ3YTEzYyIsInByZWZlcnJlZF91c2VybmFtZSI6Imd0bGMwMDNAZ3RsYy5jYSIsInJoIjoiMS5BUzRBZ2hsQmJ5UFBSVTZVU1V0Z2UzcUk3TTR4aW9qVFdSSktwWmFOWXZaQnRRVm1BYTR1QUEuIiwicm9sZXMiOlsiZ3RsYy5hY2wuc3VwZXJhZG1pbiJdLCJzY3AiOiJHVExDX0FwcGxpY2F0aW9uIiwic2lkIjoiMDAzZjVhZDktZmJmNi1jZTkyLTg3MjItMWEzNDExYjMzMDJiIiwic3ViIjoiS0RYQ3drVlhUbTRUUmcwaTkxbXZDZzgtQ29uLXpWbk5FQ2Y5LVg0dkpzUSIsInRpZCI6IjZmNDExOTgyLWNmMjMtNGU0NS05NDQ5LTRiNjA3YjdhODhlYyIsInVwbiI6Imd0bGMwMDNAZ3RsYy5jYSIsInV0aSI6InpBaTJoMmpVUUVHbVY0RTJXMWxqQUEiLCJ2ZXIiOiIyLjAiLCJ3aWRzIjpbImI3OWZiZjRkLTNlZjktNDY4OS04MTQzLTc2YjE5NGU4NTUwOSJdLCJ4bXNfZnRkIjoiUDJraW1ZM0tqc1BTRndxNHlZdmJ0bGhrSUk3d2tXNmFpcW1FQ1BaNEdja0JkWE5sWVhOMExXUnpiWE0ifQ.LzppoggMm27smSAy9SamtPN95vCzdELCAfhtOj5n_T_H6g9xCmNRLS9FaUFQMau6Qvl0lROKl7WDklTswLFkfxbIxCBWtXdL-LTqT5cDURSJAll8vC3zlN3Hg9pAFBUVZFRolt6Z7LvPdI3pvUOQs0yFwVzp9k6cLF8aemKdwKQrMX3XXua1MfBWZcqQ4WiBVNmKh8w6yQB35I4u5WqdFnu33nUGb-kvc18SOpoUfiJnlV-PudaEzFXdU3CjAaMEcuPFv5xLwWJKuhU73dNH4EyQDFMVGtcIHNnieOfiY_nK2_0-5DM6aI40UIRK6Bt-HmMQpnbhLps5y3ep6Z7RNw' } // Optional
8568
- });
8569
- // downloadRequest = ApiRequest.adapt({
8570
- // server: 'oidc/ai/file'
8571
- // })
8572
- this.sampleClientData = {
8573
- id: 0,
8574
- name: "Old School Dates",
8575
- domain: "osd.com",
8576
- service: "osd",
8577
- spiffe: "osd.com/osd",
8578
- secret: "SMOPECXP-OS4P-USOG-X2II-3XMD1FQDR3IJX",
8579
- created: 1693003138,
8580
- modified: 1693003138,
8581
- icon: "",
8582
- imageFile: "",
8583
- email: "wavecoders@gmail.com"
8584
- };
8585
- this.requestForm = this.fb.group({
8586
- path: this.fb.control("ai/"),
8587
- headers: this.fb.array([]),
8588
- adapter: [null],
8589
- mapper: [null],
8590
- retry: this.fb.group({
8591
- times: [3],
8592
- delay: [3],
8593
- }),
8594
- polling: [3],
8595
- });
8596
- this.AIType = 0;
8597
- this.sampleAdaptors = [
8598
- { label: "ClientInfo Basic", value: ClientInfo$1.adapt },
8599
- { label: "AI Prompt", value: AIPrompt$1.adapt },
8600
- ];
8601
- this.sampleMappers = [
8602
- { label: "Mapper Basic", value: ClientInfoMapper$1.adapt },
8603
- { label: "AI Prompt", value: AIPrompt$1.adapt },
8604
- ];
8605
- this.hasId = (arr) => {
8606
- if (arr.length === 0)
8607
- return false;
8608
- return !isNaN(arr[arr.length - 1]);
8609
- };
8610
- this.props = (adapter) => {
8611
- return (adapter) ? adapter() : null;
8612
- };
8613
- // server = `http://sample-endpoint/as/authorization.oauth2`
8614
- this.arrayObjectsToObjects = (arr) => {
8615
- return Array.isArray(arr) ? arr.reduce((obj, item) => Object.assign(obj, { [item.key]: item.value }), {}) : {};
8616
- };
8617
- }
8618
- ngOnInit() {
8619
- // const reqGet2 = ApiRequest.adapt({
8620
- // server,
8621
- // path: ['clients'],
8622
- // headers: { authentication: "Bearer <KEY>" },
8623
- // adapter: ClientInfo,
8624
- // dataType: DataType.OBJECT,
8625
- // // concurrent: false,
8626
- // // polling: 3, //seconds
8627
- // })
8628
- // const req2 = [1024,1025,1026].map(item => {
8629
- // return this.httpManagerService.getRequest<ClientInfo[]>(reqGet2, [item])
8630
- // .pipe(
8631
- // catchError(error => {
8632
- // return throwError(() => new Error(error.error.message))
8633
- // })
8634
- // )
8635
- // })
8636
- // forkJoin(req2)
8637
- // .subscribe(res => console.log(res))
8638
- }
8639
- onStreamType(type) {
8640
- this.streamType = type;
8641
- }
8642
- addHeader() {
8643
- const header = this.fb.group({
8644
- key: ['', Validators.required],
8645
- value: ['']
8646
- });
8647
- this.headers.push(header);
8648
- }
8649
- removeHeader(index) {
8650
- this.headers.removeAt(index);
8651
- }
8652
- compileRequest() {
8653
- const requestParams = this.requestForm.value;
8654
- requestParams.headers = this.arrayObjectsToObjects(requestParams.headers || []);
8655
- const pathReq = (requestParams.path === "") ? [] : (requestParams.path || "").split("/");
8656
- if (!this.pollingState.checked)
8657
- requestParams.polling = 0;
8658
- if (!this.failedState.checked) {
8659
- requestParams.retry = { times: 0, delay: 0 };
8660
- }
8661
- const apiOptions = ApiRequest.adapt(requestParams);
8662
- apiOptions.path = [];
8663
- apiOptions.server = this.server;
8664
- apiOptions.adapter = this.adapter;
8665
- apiOptions.mapper = this.mapper;
8666
- return { apiOptions: apiOptions, path: pathReq };
8667
- }
8668
- onGetRequest() {
8669
- if (!this.isValid)
8670
- return;
8671
- const reqParams = this.compileRequest();
8672
- this.requestParams.GET = reqParams.apiOptions;
8673
- this.GET$ = EMPTY; //Cancels Previous
8674
- this.GET_error$.next('');
8675
- this.GET$ = this.httpManagerService.getRequest({ ...reqParams.apiOptions }, reqParams.path)
8676
- .pipe(
8677
- // tap((data) => console.log("API GET response", data)),
8678
- catchError(error => {
8679
- return throwError(() => this.errorHandling(error, 'GET'));
8680
- }));
8681
- }
8682
- onCreateRequest() {
8683
- if (!this.isValid)
8684
- return;
8685
- const reqParams = this.compileRequest();
8686
- this.requestParams.POST = reqParams.apiOptions;
8687
- this.POST$ = EMPTY; //Cancels Previous
8688
- this.POST_error$.next('');
8689
- console.log("POST", this.sampleClientData);
8690
- console.log("POST", reqParams.apiOptions);
8691
- console.log("POST", reqParams.path);
8692
- this.POST$ = this.httpManagerService.postRequest(this.sampleClientData, reqParams.apiOptions, reqParams.path)
8693
- .pipe(
8694
- // tap((data) => console.log("API POST response", data)),
8695
- catchError(error => {
8696
- return throwError(() => this.errorHandling(error, 'POST'));
8697
- }));
8698
- }
8699
- onUpdateRequest() {
8700
- if (!this.isValid)
8701
- return;
8702
- const reqParams = this.compileRequest();
8703
- if (!this.hasId(reqParams.path)) {
8704
- console.log("Missing ID");
8705
- return;
8706
- }
8707
- this.sampleClientData.id = parseInt(reqParams.path[reqParams.path.length - 1]);
8708
- this.requestParams.PUT = reqParams.apiOptions;
8709
- this.PUT$ = EMPTY; //Cancels Previous
8710
- this.PUT_error$.next('');
8711
- this.PUT$ = this.httpManagerService.putRequest(this.sampleClientData, reqParams.apiOptions, reqParams.path)
8712
- .pipe(
8713
- // tap((data) => console.log("API PUT response", data)),
8714
- catchError(error => {
8715
- return throwError(() => this.errorHandling(error, 'PUT'));
8716
- }));
8717
- }
8718
- onDeleteRequest() {
8719
- if (!this.isValid)
8720
- return;
8721
- const reqParams = this.compileRequest();
8722
- this.requestParams.DELETE = reqParams.apiOptions;
8723
- if (!this.hasId(reqParams.path)) {
8724
- console.log("Missing ID");
8725
- return;
8726
- }
8727
- this.sampleClientData.id = parseInt(reqParams.path[reqParams.path.length - 1]);
8728
- this.requestParams.DELETE = reqParams.apiOptions;
8729
- this.DELETE$ = EMPTY; //Cancels Previous
8730
- this.DELETE_error$.next('');
8731
- this.DELETE$ = this.httpManagerService.deleteRequest(reqParams.apiOptions, reqParams.path)
8732
- .pipe(
8733
- // tap((data) => console.log("API DELETE response", data)),
8734
- catchError(error => {
8735
- return throwError(() => this.errorHandling(error, 'DELETE'));
8736
- }));
8737
- }
8738
- onStreamPostRequest() {
8739
- if (!this.isValid)
8740
- return;
8741
- const reqParams = this.compileRequest();
8742
- let payload = {};
8743
- let apiPath = reqParams.path;
8744
- let apiOptions = reqParams.apiOptions;
8745
- let responseMapper = (items) => items.response;
8746
- if (this.AIType === 0) {
8747
- // API request
8748
- payload = { prompt: this.questionControl.value };
8749
- }
8750
- else {
8751
- // Local Ollama request
8752
- apiOptions.server = "api";
8753
- apiPath = ["generate"];
8754
- apiOptions.stream = true;
8755
- apiOptions.streamType = this.streamType;
8756
- payload = {
8757
- model: "phi3:latest",
8758
- prompt: this.questionControl.value,
8759
- stream: true,
8760
- };
8761
- responseMapper = (items) => items.map((word) => word.response).flat().join('');
8762
- }
8763
- this.requestParams.STREAM = apiOptions;
8764
- this.STREAM_AI$ = EMPTY;
8765
- this.STREAM_AI_error$.next('');
8766
- this.STREAM_AI$ = this.httpManagerService.postRequest(payload, apiOptions, apiPath).pipe(map(responseMapper), tap(() => this.questionControl.reset()), catchError(error => throwError(() => this.errorHandling(error, 'STREAM'))));
8767
- }
8768
- onStreamRequest() {
8769
- if (!this.isValid)
8770
- return;
8771
- const reqParams = this.compileRequest();
8772
- reqParams.apiOptions.stream = true;
8773
- reqParams.apiOptions.streamType = StreamType.NDJSON;
8774
- this.requestParams.GET = reqParams.apiOptions;
8775
- this.STREAM$ = this.httpManagerService.getRequest(reqParams.apiOptions, reqParams.path)
8776
- .pipe(tap((data) => {
8777
- console.log("API STREAM response", data);
8778
- if (data && data.length > 0) {
8779
- this.updateDisplayedColumns(data);
8780
- }
8781
- }), catchError(error => {
8782
- return throwError(() => this.errorHandling(error, 'STREAM'));
8783
- }));
8784
- }
8785
- onDownloadCompleted() {
8786
- const message = "Download Completed";
8787
- const display = ToastDisplay.adapt({
8788
- message,
8789
- action: 'Ok',
8790
- color: ToastColors.SUCCESS,
8791
- icon: 'sentiment_satisfied_alt',
8792
- });
8793
- this.toastMessage.toastMessage(display);
8794
- }
8795
- onDownloadFailed(err) {
8796
- const message = "Download Failed";
8797
- const display = ToastDisplay.adapt({
8798
- message,
8799
- action: 'Ok',
8800
- color: ToastColors.ERROR,
8801
- icon: 'warning',
8802
- });
8803
- this.toastMessage.toastMessage(display);
8804
- }
8805
- errorHandling(err, type) {
8806
- if (type === 'GET')
8807
- this.GET_error$.next(err.message);
8808
- if (type === 'POST')
8809
- this.POST_error$.next(err.message);
8810
- if (type === 'PUT')
8811
- this.PUT_error$.next(err.message);
8812
- if (type === 'DELETE')
8813
- this.DELETE_error$.next(err.message);
8814
- if (type === 'STREAM')
8815
- this.STREAM_error$.next(err.message);
8816
- }
8817
- onSelectAIType(type) {
8818
- this.AIType = type;
8819
- }
8820
- // ═══════════════════════════════════════════════════════════════════════════
8821
- // BATCH REQUEST DEMO METHODS
8822
- // ═══════════════════════════════════════════════════════════════════════════
8823
- /**
8824
- * Execute parallel batch request - fetch 5 users from JSONPlaceholder with individual states
8825
- */
8826
- onExecuteParallelBatch() {
8827
- this.isParallelLoading = true;
8828
- this.parallelError = undefined;
8829
- this.parallelExecutionTime = undefined;
8830
- this.parallelResults = undefined;
8831
- this.parallelBatchStates = [];
8832
- const requests = this.createBatchRequests('https://jsonplaceholder.typicode.com', ['users'], [1, 2, 3, 4, 5]);
8833
- const startTime = performance.now();
8834
- this.httpManagerService.getBatchRequestsStream(requests, {
8835
- mode: 'parallel',
8836
- concurrency: 3,
8837
- stopOnError: false,
8838
- ignoreErrors: true,
8839
- logErrors: true
8840
- }).pipe(tap(state => {
8841
- this.parallelBatchStates[state.index] = state;
8842
- if (!state.isPending && this.parallelBatchStates.every(s => s && !s.isPending)) {
8843
- this.parallelExecutionTime = performance.now() - startTime;
8844
- this.isParallelLoading = false;
8845
- }
8846
- }), catchError(error => {
8847
- this.parallelError = error.message || 'Parallel batch failed';
8848
- this.isParallelLoading = false;
8849
- return throwError(() => error);
8850
- })).subscribe();
8851
- }
8852
- /**
8853
- * Execute sequential batch request - fetch 5 todos from JSONPlaceholder with individual states
8854
- */
8855
- onExecuteSequentialBatch() {
8856
- this.isSequentialLoading = true;
8857
- this.sequentialError = undefined;
8858
- this.sequentialExecutionTime = undefined;
8859
- this.sequentialResults = undefined;
8860
- this.sequentialBatchStates = [];
8861
- const requests = this.createBatchRequests('https://jsonplaceholder.typicode.com', ['todos'], [1, 2, 3, 4, 5]);
8862
- const startTime = performance.now();
8863
- this.httpManagerService.getBatchRequestsStream(requests, {
8864
- mode: 'sequential',
8865
- stopOnError: false,
8866
- concurrency: 3,
8867
- ignoreErrors: true,
8868
- logErrors: true
8869
- }).pipe(tap(state => {
8870
- this.sequentialBatchStates[state.index] = state;
8871
- if (!state.isPending && this.sequentialBatchStates.every(s => s && !s.isPending)) {
8872
- this.sequentialExecutionTime = performance.now() - startTime;
8873
- this.isSequentialLoading = false;
8874
- }
8875
- }), catchError(error => {
8876
- this.sequentialError = error.message || 'Sequential batch failed';
8877
- this.isSequentialLoading = false;
8878
- return throwError(() => error);
8879
- })).subscribe();
8880
- }
8881
- /**
8882
- * Execute stream batch request - fetch 5 posts with real-time state updates
8883
- */
8884
- onExecuteStreamBatch() {
8885
- this.isStreamLoading = true;
8886
- this.streamError = undefined;
8887
- this.streamExecutionTime = undefined;
8888
- this.streamBatchStates = [];
8889
- this.streamProgress = undefined;
8890
- const requests = this.createBatchRequests('https://jsonplaceholder.typicode.com', ['posts'], [1, 2, 3, 4, 5]);
8891
- const startTime = performance.now();
8892
- this.httpManagerService.getBatchRequestsStream(requests, {
8893
- mode: 'parallel',
8894
- concurrency: 3,
8895
- stopOnError: false,
8896
- ignoreErrors: true,
8897
- logErrors: true
8898
- }).pipe(tap(state => {
8899
- this.streamBatchStates[state.index] = state;
8900
- this.streamProgress = calculateBatchProgress(this.streamBatchStates.filter(s => s !== undefined));
8901
- if (!state.isPending) {
8902
- this.streamExecutionTime = performance.now() - startTime;
8903
- }
8904
- this.isStreamLoading = this.streamBatchStates.some(s => s && s.isPending);
8905
- }), catchError(error => {
8906
- this.streamError = error.message || 'Stream batch failed';
8907
- this.isStreamLoading = false;
8908
- return throwError(() => error);
8909
- })).subscribe();
8910
- }
8911
- /**
8912
- * Type guard for pending state
8913
- */
8914
- isPendingState(state) {
8915
- return state.isPending === true;
8916
- }
8917
- /**
8918
- * Type guard for success state
8919
- */
8920
- isSuccessState(state) {
8921
- return state.isPending === false && state.data !== undefined;
8922
- }
8923
- /**
8924
- * Type guard for error state
8925
- */
8926
- isErrorState(state) {
8927
- return state.isPending === false && state.error !== undefined;
8928
- }
8929
- /**
8930
- * Helper to create batch requests from IDs
8931
- */
8932
- createBatchRequests(baseUrl, basePath, ids) {
8933
- return ids.map(id => ApiRequest.adapt({
8934
- server: baseUrl,
8935
- path: [...basePath, id]
8936
- }));
8937
- }
8938
- /**
8939
- * Check if result is successful (not undefined)
8940
- */
8941
- isSuccess(result) {
8942
- return result !== undefined && result !== null;
8943
- }
8944
- /**
8945
- * Format execution time for display
8946
- */
8947
- formatTime(ms) {
8948
- if (ms < 1000) {
8949
- return `${Math.round(ms)}ms`;
8950
- }
8951
- return `${(ms / 1000).toFixed(2)}s`;
8952
- }
8953
- /**
8954
- * Get success count from results array
8955
- */
8956
- getSuccessCount(results) {
8957
- if (!results)
8958
- return 0;
8959
- return results.filter(r => r !== undefined).length;
8960
- }
8961
- /**
8962
- * Get failure count from results array
8963
- */
8964
- getFailureCount(results) {
8965
- if (!results)
8966
- return 0;
8967
- return results.filter(r => r === undefined).length;
8968
- }
8969
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8970
- 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" }] }); }
8971
- }
8972
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerDemoComponent, decorators: [{
8973
- type: Component,
8974
- 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"] }]
8975
- }], ctorParameters: () => [], propDecorators: { server: [{
8976
- type: Input
8977
- }], adapter: [{
8978
- type: Input
8979
- }], mapper: [{
8980
- type: Input
8981
- }], failedState: [{
8982
- type: ViewChild,
8983
- args: ["failedState", { static: true }]
8984
- }], pollingState: [{
8985
- type: ViewChild,
8986
- args: ["pollingState", { static: true }]
8987
- }] } });
8988
-
8989
- class LocalStorageDemoComponent {
8990
- get type() {
8991
- return (this.typeControl.value) ? +this.typeControl.value : 0;
8992
- }
8993
- get isValid() {
8994
- return this.newStoreForm.valid;
8995
- }
8996
- get isValidData() {
8997
- return this.storageForm.valid;
8998
- }
8999
- constructor(configOptions) {
9000
- this.configOptions = configOptions;
9001
- this.fb = inject(FormBuilder);
9002
- this.utils = inject(UtilsService);
9003
- this.type$ = new BehaviorSubject(StorageType.GLOBAL);
9004
- this.typeControl = this.fb.control(StorageType.GLOBAL.toString());
9005
- this.localStorageManagerService = inject(LocalStorageManagerService);
9006
- this.settings$ = this.localStorageManagerService.settings$;
9007
- this.setting$ = (store) => this.localStorageManagerService.setting$(store);
9008
- this.storageForm = this.fb.group({
9009
- store: this.fb.control(null),
9010
- type: 'local',
9011
- settingType: 'local',
9012
- encrypted: false,
9013
- data: this.fb.control('', Validators.required),
9014
- });
9015
- this.newStoreForm = this.fb.group({
9016
- name: this.fb.control(null, Validators.required),
9017
- storage: 'local',
9018
- encrypted: false,
9019
- data: this.fb.control('', Validators.required),
9020
- expiresIn: this.fb.control('0')
9021
- });
9022
- this.storeData$ = this.storageForm.get('store')?.valueChanges
9023
- .pipe(switchMap((data) => {
9024
- return data
9025
- ? this.localStorageManagerService.store$(data.name)
9026
- : of('');
9027
- }), tap(data => {
9028
- this.storageForm.get('data')?.patchValue(data, { emitEvent: false });
9029
- }));
9030
- this.expiresIn = (epoch) => this.utils.expiresIn(epoch);
9031
- this.isValidJSON = (str) => {
9032
- try {
9033
- JSON.parse(str);
9034
- return true;
9035
- }
9036
- catch (e) {
9037
- return false;
9038
- }
9039
- };
9040
- this.displayedColumns = ['name', 'id', 'encrypted', 'expires', "option"];
9041
- this.filterData = (values) => {
9042
- if (!values)
9043
- return [];
9044
- return values.filter((item) => item.options.storage === +this.type);
9045
- };
9046
- this.create = false;
9047
- }
9048
- ngOnInit() {
9049
- this.storeProps = this.configOptions?.LocalStorageOptions;
9050
- this.options = this.storeProps?.options;
9051
- if (this.options?.storage) {
9052
- this.typeControl.patchValue(this.options.storage.toString());
9053
- this.typeControl.disable();
9054
- }
9055
- else {
9056
- this.typeControl.enable();
9057
- }
9058
- if (this.options?.expiresIn) {
9059
- this.newStoreForm.get('expiresIn')?.patchValue(this.options.expiresIn);
9060
- this.newStoreForm.get('expiresIn')?.disable();
9061
- }
9062
- else {
9063
- this.newStoreForm.get('expiresIn')?.enable();
9064
- }
9065
- if (this.options?.encrypted) {
9066
- this.newStoreForm.get('encrypted')?.patchValue(this.options.encrypted);
9067
- this.newStoreForm.get('encrypted')?.disable();
9068
- }
9069
- else {
9070
- this.newStoreForm.get('encrypted')?.enable();
9071
- }
9072
- }
9073
- onCreateStore() {
9074
- if (!this.isValid)
9075
- return;
9076
- const store = this.newStoreForm.value;
9077
- if (!store.name || store.name === '')
9078
- return;
9079
- const options = { storage: this.type, encrypted: store.encrypted, expiresIn: store.expiresIn };
9080
- this.localStorageManagerService.createStore({
9081
- name: store.name,
9082
- data: store.data,
9083
- options: SettingOptions.adapt(options)
9084
- });
9085
- this.newStoreForm.reset();
9086
- this.create = false;
9087
- }
9088
- onUpdateStore(store) {
9089
- if (!this.storageForm.valid)
9090
- return;
9091
- const storeData = this.storageForm.value;
9092
- const data = JSON.parse(storeData.data || '');
9093
- const type = (storeData.type === 'local') ? StorageType.GLOBAL : StorageType.SESSION;
9094
- this.localStorageManagerService.updateStore({
9095
- name: store.name,
9096
- data
9097
- });
9098
- }
9099
- onSelectedRow(store) {
9100
- this.store = store;
9101
- this.data$ = this.localStorageManagerService.store$(store.name).pipe(map(item => JSON.stringify(item)));
9102
- this.create = false;
9103
- }
9104
- onCreate() {
9105
- this.onCancel();
9106
- this.create = true;
9107
- }
9108
- onDelete(store) {
9109
- this.localStorageManagerService.deleteStore({
9110
- name: store.name,
9111
- });
9112
- this.onCancel();
9113
- }
9114
- onCancel() {
9115
- this.data$ = EMPTY;
9116
- this.store = null;
9117
- this.create = false;
9118
- }
9119
- onUpdate(store, data) {
9120
- this.localStorageManagerService.updateStore({
9121
- name: store.name,
9122
- data: JSON.parse(data)
9123
- });
9124
- this.onCancel();
9125
- }
9126
- onReset() {
9127
- this.localStorageManagerService.resetStore();
9128
- }
9129
- 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 }); }
9130
- 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 }); }
9131
- }
9132
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LocalStorageDemoComponent, decorators: [{
9133
- type: Component,
9134
- 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"] }]
9135
- }], ctorParameters: () => [{ type: ConfigOptions, decorators: [{
9136
- type: Inject,
9137
- args: [CONFIG_SETTINGS_TOKEN]
9138
- }] }] });
9139
-
9140
- class OIDCClient {
9141
- constructor(spiffe = '', id = 0, first_name = '', last_name = '', email = '') {
9142
- this.spiffe = spiffe;
9143
- this.id = id;
9144
- this.first_name = first_name;
9145
- this.last_name = last_name;
9146
- this.email = email;
9147
- }
9148
- static adapt(item) {
9149
- return new OIDCClient(item?.spiffe, item?.id, item?.first_name, item?.last_name, item?.email);
9150
- }
9151
- }
9152
-
9153
- /**
9154
- * StateServiceDemo - Core state management and WebSocket connection service
9155
- *
9156
- * Handles:
9157
- * - WebSocket connection setup
9158
- * - Base HTTP state management
9159
- * - Connection status and retry logic
9160
- */
9161
- class StateServiceDemo extends HTTPManagerStateService {
9162
- constructor() {
9163
- super(ApiRequest.adapt({
9164
- server: '',
9165
- retry: RetryOptions.adapt({
9166
- times: 3,
9167
- delay: 1,
9168
- }),
9169
- adapter: OIDCClient.adapt,
9170
- }), DataType.ARRAY, DatabaseStorage.adapt({
9171
- table: 'ws_demo_data',
9172
- expiresIn: '5mn',
9173
- }));
9174
- this.attempts$ = this.wsRetryAttempts$;
9175
- this.nextRetry$ = this.wsNextRetry$;
9176
- }
9177
- /**
9178
- * Initialize WebSocket connection with server configuration
9179
- * @param server - Backend server URL
9180
- * @param wsServer - WebSocket server URL
9181
- * @param jwtToken - JWT authentication token
9182
- * @param user - User information
9183
- * @param path - Path for constructing channel name (e.g., ['ai','tests'])
9184
- */
9185
- updateConnection(server, wsServer, jwtToken, user, path = ['ai', 'tests']) {
9186
- // Construct channel name from path: ['ai','tests'] → 'ai/tests'
9187
- const channelId = path.join('/');
9188
- this.setApiRequestOptions({
9189
- server,
9190
- path, // Set the path for HTTP requests
9191
- retry: RetryOptions.adapt({
9192
- times: 3,
9193
- delay: 1,
9194
- }),
9195
- adapter: OIDCClient.adapt,
9196
- ws: {
9197
- id: channelId, // Use path-based channel ID instead of hardcoded 'USERS123'
9198
- wsServer,
9199
- jwtToken,
9200
- user,
9201
- retry: RetryOptions.adapt({
9202
- times: 10,
9203
- delay: 5,
9204
- }),
9205
- }
9206
- });
9207
- this.userAction$.subscribe(user => {
9208
- console.log('User Action:', user);
9209
- });
9210
- }
9211
- /**
9212
- * Get all available channels
9213
- */
9214
- getAllChannels() {
9215
- this.httpManagerService.getAllChannels();
9216
- }
9217
- /**
9218
- * Subscribe to a channel
9219
- */
9220
- subscribeToChannel(channel) {
9221
- super.subscribeToChannel(channel);
9222
- }
9223
- /**
9224
- * Unsubscribe from a channel
9225
- */
9226
- unsubscribeFromChannel(channel) {
9227
- super.unsubscribeFromChannel(channel);
9228
- }
9229
- // =====================
9230
- // Notification Channel Methods (MES- prefix)
9231
- // These override methods from HTTPManagerStateService
9232
- // =====================
9233
- /**
9234
- * Create a notification channel (MES- prefix)
9235
- */
9236
- createNotificationChannel(channel) {
9237
- super.createNotificationChannel(channel);
9238
- }
9239
- /**
9240
- * Get all notification channels list (in-memory)
9241
- */
9242
- getNotificationChannels() {
9243
- super.getNotificationChannels();
9244
- }
9245
- /**
9246
- * Get today's notification channels from database
9247
- * Returns unique channels that have notifications posted today
9248
- */
9249
- getTodaysNotificationChannels() {
9250
- super.getTodaysNotificationChannels();
9251
- }
9252
- /**
9253
- * Define and load previous day's notification channels from database
9254
- * Creates channels in memory and broadcasts updated list
9255
- */
9256
- definePreviousNotificationChannels() {
9257
- const options = ApiRequest.adapt({
9258
- path: ['notifications', 'channels', 'define-previous']
9259
- });
9260
- this.httpManagerService.postRequest({}, options).subscribe({
9261
- next: (response) => {
9262
- console.log('📋 Previous day channels loaded:', response);
9263
- // The server will broadcast updated channel list to all WebSocket clients
9264
- },
9265
- error: (error) => {
9266
- console.error('❌ Failed to load previous day channels:', error);
9267
- }
9268
- });
9269
- }
9270
- /**
9271
- * Subscribe to notification channel with optional date filters
9272
- */
9273
- subscribeToNotificationChannel(channel, options, user) {
9274
- super.subscribeToNotificationChannel(channel, options, user);
9275
- }
9276
- /**
9277
- * Unsubscribe from notification channel
9278
- */
9279
- unsubscribeFromNotificationChannel(channel) {
9280
- super.unsubscribeFromNotificationChannel(channel);
9281
- }
9282
- /**
9283
- * Send a notification to a channel
9284
- */
9285
- sendNotification(channel, content) {
9286
- super.sendNotification(channel, content);
9287
- }
9288
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateServiceDemo, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
9289
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateServiceDemo, providedIn: 'root' }); }
9290
- }
9291
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateServiceDemo, decorators: [{
9292
- type: Injectable,
9293
- args: [{
9294
- providedIn: 'root'
9295
- }]
9296
- }], ctorParameters: () => [] });
9297
-
9298
- /**
9299
- * MessageServiceDemo - Channel messaging service (PUB- prefix channels)
9300
- *
9301
- * Handles:
9302
- * - Sending messages to channels
9303
- * - Channel subscription for messaging
9304
- * - Real-time message broadcast
9305
- */
9306
- class MessageServiceDemo {
9307
- constructor() {
9308
- this.stateService = inject(StateServiceDemo);
9309
- // Expose observables from state service
9310
- this.channels$ = this.stateService.channels$;
9311
- this.subscribedChannels$ = this.stateService.subscribedChannels$;
9312
- this.communicationMessages$ = this.stateService.communicationMessages$;
9313
- this.latestCommunicationMessages$ = this.stateService.latestCommunicationMessages$;
9314
- this.connectionStatus$ = this.stateService.connectionStatus$;
9315
- this.user$ = this.stateService.user$;
9316
- this.data$ = this.stateService.data$;
9317
- }
9318
- /**
9319
- * Helper to ensure channel has PUB- prefix
9320
- */
9321
- toPublicChannel(channel) {
9322
- return channel.startsWith('PUB-') ? channel : `PUB-${channel}`;
9323
- }
9324
- /**
9325
- * Helper to strip PUB- prefix for display
9326
- */
9327
- fromPublicChannel(channel) {
9328
- return channel.startsWith('PUB-') ? channel.replace('PUB-', '') : channel;
9329
- }
9330
- /**
9331
- * Create a new public channel
9332
- */
9333
- createChannel(channel) {
9334
- const publicChannel = this.toPublicChannel(channel);
9335
- this.stateService.createChannel(publicChannel);
9336
- }
9337
- /**
9338
- * Get all available channels
9339
- */
9340
- getAllChannels() {
9341
- this.stateService.getAllChannels();
9342
- }
9343
- /**
9344
- * Subscribe to a channel to receive messages
9345
- */
9346
- subscribeToChannel(channel) {
9347
- const publicChannel = this.toPublicChannel(channel);
9348
- this.stateService.subscribeToChannel(publicChannel);
9349
- }
9350
- /**
9351
- * Unsubscribe from a channel
9352
- */
9353
- unsubscribeFromChannel(channel) {
9354
- const publicChannel = this.toPublicChannel(channel);
9355
- this.stateService.unsubscribeFromChannel(publicChannel);
9356
- }
9357
- /**
9358
- * Send a message to one or more channels
9359
- */
9360
- sendMessage(data, channels) {
9361
- const publicChannels = channels?.map(ch => this.toPublicChannel(ch));
9362
- console.log('sendMessage', data, 'to channels:', publicChannels);
9363
- this.stateService.wsMessaging(data, publicChannels);
9364
- }
9365
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageServiceDemo, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
9366
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageServiceDemo, providedIn: 'root' }); }
9367
- }
9368
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageServiceDemo, decorators: [{
9369
- type: Injectable,
9370
- args: [{
9371
- providedIn: 'root'
9372
- }]
9373
- }] });
9374
-
9375
- /**
9376
- * NotificationServiceDemo - Notification channel service (MES- prefix channels)
9377
- *
9378
- * Handles:
9379
- * - Creating notification channels
9380
- * - Subscribing to notifications with date filters
9381
- * - Sending notifications (persisted to database)
9382
- * - Receiving real-time and historical notifications
9383
- */
9384
- class NotificationServiceDemo {
9385
- constructor() {
9386
- this.stateService = inject(StateServiceDemo);
9387
- // Notification channels from state service (in-memory - contains MES- channels from server)
9388
- this.notificationChannels$ = this.stateService.notificationChannels$;
9389
- // Today's notification channels from database (channels with data for today)
9390
- this.todaysNotificationChannels$ = this.stateService.todaysNotificationChannels$;
9391
- this.subscribedNotificationChannelsSubject = new BehaviorSubject(new Set());
9392
- this.subscribedNotificationChannels$ = this.subscribedNotificationChannelsSubject.asObservable();
9393
- // Notification messages from state service
9394
- this.notificationMessages$ = this.stateService.notificationMessages$;
9395
- // Latest notification from state service
9396
- this.latestNotification$ = this.stateService.latestNotification$;
9397
- // Expose connection status from state service
9398
- this.connectionStatus$ = this.stateService.connectionStatus$;
9399
- }
9400
- /**
9401
- * Create a notification channel
9402
- * NOTE: MES- prefix is added automatically by HTTPManagerStateService
9403
- */
9404
- createNotificationChannel(channel) {
9405
- console.log('📢 createNotificationChannel:', channel);
9406
- this.stateService.createNotificationChannel(channel);
9407
- }
9408
- /**
9409
- * Get all notification channels (in-memory)
9410
- */
9411
- getNotificationChannels() {
9412
- console.log('📢 getNotificationChannels');
9413
- this.stateService.getNotificationChannels();
9414
- }
9415
- /**
9416
- * Get today's notification channels from database
9417
- * Returns unique channels that have notifications posted today
9418
- */
9419
- getTodaysNotificationChannels() {
9420
- console.log('📢 getTodaysNotificationChannels (from DB)');
9421
- this.stateService.getTodaysNotificationChannels();
9422
- }
9423
- /**
9424
- * Define and load previous day's notification channels from database
9425
- * Creates channels in memory and broadcasts updated list
9426
- */
9427
- definePreviousNotificationChannels() {
9428
- console.log('📢 definePreviousNotificationChannels');
9429
- this.stateService.definePreviousNotificationChannels();
9430
- }
9431
- /**
9432
- * Subscribe to a notification channel with optional date filter
9433
- * NOTE: MES- prefix is added automatically by HTTPManagerStateService
9434
- * @param channel Base channel name (without MES- prefix)
9435
- * @param options { startEpoch?, endEpoch? }
9436
- * @param user User info for subscription
9437
- */
9438
- subscribeToNotificationChannel(channel, options, user) {
9439
- // Add MES- prefix for local tracking (to match what state service sends)
9440
- const prefixedChannel = channel.startsWith('MES-') ? channel : `MES-${channel}`;
9441
- console.log('📢 subscribeToNotificationChannel:', prefixedChannel, options);
9442
- // Add to local subscribed channels immediately for UI feedback
9443
- const currentSubs = this.subscribedNotificationChannelsSubject.value;
9444
- currentSubs.add(prefixedChannel);
9445
- this.subscribedNotificationChannelsSubject.next(new Set(currentSubs));
9446
- // Send subscription request to server (state service adds MES- prefix)
9447
- this.stateService.subscribeToNotificationChannel(channel, options, user);
9448
- }
9449
- /**
9450
- * Unsubscribe from a notification channel
9451
- * NOTE: MES- prefix is added automatically by HTTPManagerStateService
9452
- */
9453
- unsubscribeFromNotificationChannel(channel) {
9454
- // Add MES- prefix for local tracking (to match what state service sends)
9455
- const prefixedChannel = channel.startsWith('MES-') ? channel : `MES-${channel}`;
9456
- console.log('📢 unsubscribeFromNotificationChannel:', prefixedChannel);
9457
- // Remove from local subscribed channels immediately for UI feedback
9458
- const currentSubs = this.subscribedNotificationChannelsSubject.value;
9459
- currentSubs.delete(prefixedChannel);
9460
- this.subscribedNotificationChannelsSubject.next(new Set(currentSubs));
9461
- // Send unsubscription request to server (state service adds MES- prefix)
9462
- this.stateService.unsubscribeFromNotificationChannel(channel);
9463
- }
9464
- /**
9465
- * Send a notification to a channel
9466
- * NOTE: MES- prefix is added automatically by HTTPManagerStateService
9467
- */
9468
- sendNotification(channel, content, user) {
9469
- const notificationContent = {
9470
- sessionId: {
9471
- id: user?.id,
9472
- ldap: user?.ldap,
9473
- name: user?.name,
9474
- email: user?.email,
9475
- color: user?.color,
9476
- },
9477
- ...content
9478
- };
9479
- console.log('📢 sendNotification:', channel, notificationContent);
9480
- // State service adds MES- prefix
9481
- this.stateService.sendNotification(channel, notificationContent);
9482
- }
9483
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NotificationServiceDemo, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
9484
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NotificationServiceDemo, providedIn: 'root' }); }
9485
- }
9486
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NotificationServiceDemo, decorators: [{
9487
- type: Injectable,
9488
- args: [{
9489
- providedIn: 'root'
9490
- }]
9491
- }] });
9492
-
9493
- class StateDataRequestService extends HTTPManagerStateService {
9494
- constructor() {
9495
- super(ApiRequest.adapt({
9496
- server: '',
9497
- retry: RetryOptions.adapt({
9498
- times: 3,
9499
- delay: 1,
9500
- }),
9501
- adapter: OIDCClient.adapt,
9502
- ws: {
9503
- id: ``,
9504
- // wsChannels: ['allChannels'],
9505
- // access: ['read,write,delete,update'],
9506
- wsServer: '',
9507
- jwtToken: '',
9508
- user: UserData.adapt(),
9509
- retry: RetryOptions.adapt({
9510
- times: 3,
9511
- delay: 10,
9512
- }),
9513
- }
9514
- }), DataType.ARRAY, DatabaseStorage.adapt({
9515
- table: 'ws_demo_data',
9516
- expiresIn: '5mn',
9517
- }));
9518
- this.attempts$ = this.wsRetryAttempts$;
9519
- this.nextRetry$ = this.wsNextRetry$;
9520
- this.path = ['ai', 'tests'];
9521
- }
9522
- updateConnection(server, wsServer, jwtToken, user, path = []) {
9523
- this.path = path;
9524
- this.setApiRequestOptions({
9525
- server,
9526
- retry: RetryOptions.adapt({
9527
- times: 3,
9528
- delay: 1,
9529
- }),
9530
- adapter: OIDCClient.adapt,
9531
- ws: {
9532
- id: this.path.join('/'),
9533
- wsServer,
9534
- jwtToken,
9535
- user, // general info about user
9536
- retry: RetryOptions.adapt({
9537
- times: 3,
9538
- delay: 10,
9539
- }),
9540
- }
9541
- });
9542
- }
9543
- addData() {
9544
- const num = RandomNumber(1000, 9999);
9545
- const newData = {
9546
- "spiffe": `sample.com/developer/${num}`,
9547
- "last_name": "boni",
9548
- "email": "mikeboni@hotmail.com",
9549
- "first_name": "mike"
9550
- };
9551
- this.createRecord(newData, RequestOptions.adapt({ path: this.path }));
9552
- }
9553
- sendMessage(data) {
9554
- console.log('sendMessage', data);
9555
- this.wsMessaging(data);
9556
- }
9557
- getData() {
9558
- this.fetchRecords(RequestOptions.adapt({ path: this.path }));
9559
- }
9560
- updateData(data) {
9561
- const sampleOptions = RequestOptions.adapt({ path: [...this.path, data.id] });
9562
- console.log('updateData', sampleOptions);
9563
- this.updateRecord(data, sampleOptions);
9564
- }
9565
- deleteData(data) {
9566
- const sampleOptions = RequestOptions.adapt({ path: [...this.path, data.id] });
9567
- console.log('deleteData', sampleOptions);
9568
- this.deleteRecord(sampleOptions);
9569
- }
9570
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateDataRequestService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
9571
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateDataRequestService, providedIn: 'root' }); }
9572
- }
9573
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateDataRequestService, decorators: [{
9574
- type: Injectable,
9575
- args: [{
9576
- providedIn: 'root'
9577
- }]
9578
- }], ctorParameters: () => [] });
9579
-
9580
- class WsDataControlComponent {
9581
- constructor() {
9582
- this.path = ['ai', 'tests'];
9583
- this.stateDataRequestService = inject(StateDataRequestService);
9584
- this.webSocketMessageService = inject(WebSocketMessageService);
9585
- this.user$ = this.stateDataRequestService.user$;
9586
- this.users$ = this.stateDataRequestService.userList$;
9587
- this.userAction$ = this.stateDataRequestService.userAction$
9588
- .pipe(switchMap$1((action) => timer(3 * 1000).pipe(map$1(() => null), startWith$1(action))));
9589
- this.data$ = this.stateDataRequestService.data$;
9590
- this.isUser = (user, userItem) => {
9591
- return user.sessionId === userItem.sessionId;
9592
- };
9593
- }
9594
- ngOnInit() {
9595
- this.stateDataRequestService.updateConnection(this.server, this.wsServer, this.jwtToken, this.user, this.path);
9596
- this.stateDataRequestService.getData();
9597
- }
9598
- onGetData() {
9599
- this.stateDataRequestService.getData();
9600
- }
9601
- onAddData() {
9602
- this.stateDataRequestService.addData();
9603
- }
9604
- onUpdateData(data) {
9605
- const num = RandomNumber(1000, 9999);
9606
- const newData = {
9607
- "spiffe": `wavecoders.com/developer/${num}`,
9608
- "id": 63,
9609
- "last_name": "boni",
9610
- "email": "mikeboni@hotmail.com",
9611
- "first_name": "mike"
9612
- };
9613
- this.stateDataRequestService.updateData(newData);
9614
- }
9615
- onRemoveData(data) {
9616
- const lastRec = data[data.length - 1];
9617
- this.stateDataRequestService.deleteData(lastRec);
9618
- }
9619
- /**
9620
- * Test direct state message via WebSocketMessageService
9621
- * Sends an UPDATE message that should trigger fetchRecord() in the state manager
9622
- *
9623
- * @param recordId - The record ID to update (default: 63)
9624
- * @param useFakeSessionId - If true, uses a fake sessionId to avoid filtering (default: true)
9625
- * @param customSessionId - Optional custom sessionId (overrides useFakeSessionId if provided)
9626
- */
9627
- onTestDirectStateMessage(recordId = 63, useFakeSessionId = true, customSessionId) {
9628
- // Get actual sessionId from sessionStorage (stored by HTTPManagerStateService)
9629
- const actualSessionId = sessionStorage.getItem('WSID') || 'unknown';
9630
- // Determine sessionId to use
9631
- let sessionId;
9632
- if (customSessionId) {
9633
- sessionId = customSessionId;
9634
- }
9635
- else if (useFakeSessionId) {
9636
- sessionId = `test-${Date.now()}`;
9637
- }
9638
- else {
9639
- // Use actual sessionId (will be filtered out by receiver)
9640
- sessionId = actualSessionId;
9641
- }
9642
- // Build StateMessage with same structure as CRUD operations
9643
- const stateMessage = {
9644
- sessionId: {
9645
- id: sessionId,
9646
- name: useFakeSessionId ? 'Test Sender' : (this.user?.name || 'Current User')
9647
- },
9648
- content: {
9649
- method: 'UPDATE',
9650
- path: ['ai', 'tests', recordId],
9651
- user: {
9652
- id: sessionId,
9653
- name: useFakeSessionId ? 'Test Sender' : (this.user?.name || 'Current User')
9654
- }
9655
- }
9656
- };
9657
- console.log('🧪 [TEST] Sending direct state message:', {
9658
- recordId,
9659
- sessionId,
9660
- actualSessionId,
9661
- useFakeSessionId,
9662
- stateMessage
9663
- });
9664
- const success = this.webSocketMessageService.sendStateMessage(['ai', 'tests'], stateMessage);
9665
- console.log('🧪 [TEST] Message sent:', {
9666
- success,
9667
- actualSessionId,
9668
- sentSessionId: sessionId,
9669
- expectedBehavior: success
9670
- ? 'Server will broadcast to SYS-ai/tests subscribers'
9671
- : 'Failed to send (check connection)',
9672
- receiverBehavior: sessionId !== actualSessionId
9673
- ? 'Receiver will process message and call fetchRecord()'
9674
- : 'Receiver will filter out (sessionId match)'
9675
- });
9676
- }
9677
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsDataControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9678
- 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" }] }); }
9679
- }
9680
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsDataControlComponent, decorators: [{
9681
- type: Component,
9682
- 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"] }]
9683
- }], propDecorators: { server: [{
9684
- type: Input
9685
- }], wsServer: [{
9686
- type: Input
9687
- }], jwtToken: [{
9688
- type: Input
9689
- }], user: [{
9690
- type: Input
9691
- }], path: [{
9692
- type: Input
9693
- }] } });
9694
-
9695
- class WsMessagingComponent {
9696
- constructor() {
9697
- this.path = ['ai', 'tests']; // Default path for channel name
9698
- this.destroy$ = new Subject();
9699
- this.fb = inject(FormBuilder);
9700
- this.messageService = inject(MessageServiceDemo);
9701
- this.stateService = inject(StateServiceDemo);
9702
- this.messageDisplayService = inject(MessageDisplayRouterService);
9703
- // Only show public channels (starting with "PUB-"), strip prefix for display
9704
- // SYS- channels are private/internal and hidden from users
9705
- this.channels$ = this.messageService.channels$.pipe(map$1(channels => channels
9706
- ?.filter(channel => channel.startsWith('PUB-'))
9707
- .map(channel => channel.replace('PUB-', '')) || []), startWith$1([]), distinctUntilChanged$1((prev, curr) => prev.length === curr.length && prev.every((v, i) => v === curr[i])));
9708
- // Track subscribed public channels only, strip prefix for display
9709
- this.subscribedChannels$ = this.messageService.subscribedChannels$.pipe(map$1(set => Array.from(set)
9710
- .filter(channel => channel.startsWith('PUB-'))
9711
- .map(channel => channel.replace('PUB-', ''))), startWith$1([]), distinctUntilChanged$1((prev, curr) => prev.length === curr.length && prev.every((v, i) => v === curr[i])));
9712
- this.user$ = this.stateService.user$;
9713
- this.data$ = this.stateService.data$;
9714
- this.connectionStatus$ = this.stateService.connectionStatus$;
9715
- // Form for creating new channels
9716
- this.newChannelName = this.fb.control('', [Validators.required, Validators.minLength(2)]);
9717
- // Form for selecting channels to send to and message content
9718
- this.messages = this.fb.group({
9719
- selectedChannels: this.fb.control([], Validators.required),
9720
- content: this.fb.control(null, Validators.required),
9721
- });
9722
- this.communicationMessages$ = this.messageService.communicationMessages$;
9723
- this.latestCommunicationMessages$ = this.messageService.latestCommunicationMessages$;
9724
- this.chat$ = combineLatest([this.user$, this.communicationMessages$])
9725
- .pipe(map$1(([user, messages]) => ({ user, messages })), map$1(obj => {
9726
- if (!obj.user)
9727
- return EMPTY;
9728
- const mainUser = '';
9729
- const messages = obj.messages.map((item) => {
9730
- // Message transformation logic
9731
- });
9732
- return { user: mainUser, messages };
9733
- }));
9734
- }
9735
- get selectedChannels() {
9736
- return this.messages.get('selectedChannels');
9737
- }
9738
- get content() {
9739
- return this.messages.get('content');
9740
- }
9741
- ngOnInit() {
9742
- this.stateService.updateConnection(this.server, this.wsServer, this.jwtToken, this.user, this.path);
9743
- // Only trigger once when connection becomes true
9744
- this.connectionStatus$.pipe(filter$1(status => status === true), take$1(1), takeUntil$1(this.destroy$)).subscribe(() => {
9745
- // Wait a moment for subscription to be processed, then fetch channels
9746
- setTimeout(() => {
9747
- console.log('📋 Fetching channels after connection...');
9748
- this.messageService.getAllChannels();
9749
- }, 500); // 500ms delay to ensure subscription is processed
9750
- });
9751
- // Subscribe to latest messages and display using rule-based routing
9752
- this.latestCommunicationMessages$.pipe(filter$1(message => !!message), takeUntil$1(this.destroy$)).subscribe((message) => {
9753
- console.log('🔔 Message received, routing to display:', message);
9754
- // NEW: Delegate to MessageDisplayRouterService
9755
- // The message payload determines the display type via rules
9756
- this.messageDisplayService.display(message);
9757
- // OLD: Hardcoded snackbar (removed)
9758
- // const messageContent = message.content?.content?.message || message.content?.message || '';
9759
- // this.toastService.toastMessage(...);
9760
- });
9761
- }
9762
- ngOnDestroy() {
9763
- this.destroy$.next();
9764
- this.destroy$.complete();
9765
- }
9766
- /**
9767
- * Create a new public channel without auto-subscribing
9768
- * NOTE: PUB- prefix is added by messageService.createChannel
9769
- */
9770
- onCreateChannel() {
9771
- if (this.newChannelName.invalid)
9772
- return;
9773
- const channelName = this.newChannelName.value?.trim();
9774
- if (channelName) {
9775
- // Pass base channel name - service adds PUB- prefix
9776
- this.messageService.createChannel(channelName);
9777
- this.newChannelName.reset();
9778
- // Server will broadcast updated channel list to all clients automatically
9779
- }
9780
- }
9781
- /**
9782
- * Subscribe to a channel to receive messages
9783
- * NOTE: PUB- prefix is added by messageService.subscribeToChannel
9784
- */
9785
- onSubscribeToChannel(channel) {
9786
- // Pass base channel name - service adds PUB- prefix
9787
- this.messageService.subscribeToChannel(channel);
9788
- // Reset channel selection when subscriptions change
9789
- this.selectedChannels.reset([]);
9790
- }
9791
- /**
9792
- * Unsubscribe from a channel
9793
- * NOTE: PUB- prefix is added by messageService.unsubscribeFromChannel
9794
- */
9795
- onUnsubscribeFromChannel(channel) {
9796
- // Pass base channel name - service adds PUB- prefix
9797
- this.messageService.unsubscribeFromChannel(channel);
9798
- // Reset channel selection when subscriptions change
9799
- this.selectedChannels.reset([]);
9800
- }
9801
- /**
9802
- * Handle chip toggle for subscribe/unsubscribe
9803
- */
9804
- onChipToggle(event, channel) {
9805
- const isSelected = event.selected;
9806
- console.log('🎯 Chip toggle event:', {
9807
- channel,
9808
- isSelected,
9809
- option: event.source
9810
- });
9811
- if (isSelected) {
9812
- this.onSubscribeToChannel(channel);
9813
- }
9814
- else {
9815
- this.onUnsubscribeFromChannel(channel);
9816
- }
9817
- }
9818
- /**
9819
- * Check if currently subscribed to a channel
9820
- * Compares display names (without PUB- prefix)
9821
- */
9822
- isSubscribed(channel, subscribedChannels) {
9823
- return subscribedChannels.includes(channel);
9824
- }
9825
- onSendMessage() {
9826
- this.messages.markAllAsTouched();
9827
- if (this.messages.invalid)
9828
- return;
9829
- const channelsToSend = this.selectedChannels.value;
9830
- if (channelsToSend.length === 0)
9831
- return;
9832
- // Get user from observable - subscribe once
9833
- this.user$.pipe(take$1(1)).subscribe(user => {
9834
- if (!user) {
9835
- console.error('❌ No user found');
9836
- return;
9837
- }
9838
- // Pass base channel names - service adds PUB- prefix
9839
- const message = ChannelMessage.adapt({
9840
- sessionId: {
9841
- id: user.id,
9842
- ldap: user.ldap,
9843
- name: user.name,
9844
- email: user.email,
9845
- },
9846
- content: { message: this.messages.value.content },
9847
- });
9848
- this.messageService.sendMessage(message, channelsToSend);
9849
- this.content.reset();
9850
- });
9851
- }
9852
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsMessagingComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9853
- 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" }] }); }
9854
- }
9855
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsMessagingComponent, decorators: [{
9856
- type: Component,
9857
- 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"] }]
9858
- }], propDecorators: { server: [{
9859
- type: Input
9860
- }], wsServer: [{
9861
- type: Input
9862
- }], jwtToken: [{
9863
- type: Input
9864
- }], user: [{
9865
- type: Input
9866
- }], path: [{
9867
- type: Input
9868
- }] } });
9869
-
9870
- class WsNotificationsComponent {
9871
- constructor() {
9872
- this.destroy$ = new Subject();
9873
- this.fb = inject(FormBuilder);
9874
- this.notificationService = inject(NotificationServiceDemo);
9875
- this.stateService = inject(StateServiceDemo);
9876
- // Combine in-memory channels and today's DB channels, dedupe and strip prefix for display
9877
- this.todaysNotificationChannels$ = combineLatest([
9878
- this.notificationService.notificationChannels$.pipe(startWith$1([])),
9879
- this.notificationService.todaysNotificationChannels$.pipe(startWith$1([]))
9880
- ]).pipe(tap$1(([inMemory, fromDb]) => console.log('📢 In-memory channels:', inMemory, 'DB channels:', fromDb)), map$1(([inMemory, fromDb]) => {
9881
- // Merge both sources and dedupe
9882
- const allChannels = [...(inMemory || []), ...(fromDb || [])];
9883
- const uniqueChannels = [...new Set(allChannels)]
9884
- .filter(channel => channel.startsWith('MES-'))
9885
- .map(channel => channel.replace('MES-', ''));
9886
- return uniqueChannels.sort();
9887
- }), 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])));
9888
- // Track subscribed notification channels, strip prefix for display
9889
- this.subscribedNotificationChannels$ = this.notificationService.subscribedNotificationChannels$.pipe(map$1(set => Array.from(set)
9890
- .filter(channel => channel.startsWith('MES-'))
9891
- .map(channel => channel.replace('MES-', ''))), startWith$1([]), distinctUntilChanged$1((prev, curr) => prev.length === curr.length && prev.every((v, i) => v === curr[i])));
9892
- // Notification messages
9893
- this.notificationMessages$ = this.notificationService.notificationMessages$;
9894
- this.connectionStatus$ = this.stateService.connectionStatus$;
9895
- // Form for creating new notification channels
9896
- this.newChannelName = this.fb.control('', [Validators.required, Validators.minLength(2)]);
9897
- // Channel connection selection
9898
- this.selectedConnectionChannel = this.fb.control(null);
9899
- this.connectedChannel = null;
9900
- // Table columns for notifications display
9901
- this.displayedColumns = ['date', 'user', 'message'];
9902
- // Form for date filter when subscribing
9903
- this.dateFilter = this.fb.group({
9904
- startDate: this.fb.control(null),
9905
- endDate: this.fb.control(null),
9906
- });
9907
- // Form for sending notifications
9908
- this.notificationForm = this.fb.group({
9909
- content: this.fb.control(null, Validators.required),
9910
- });
9911
- }
9912
- /**
9913
- * Helper to ensure channel has MES- prefix for outgoing communication
9914
- */
9915
- toNotificationChannel(channel) {
9916
- return channel.startsWith('MES-') ? channel : `MES-${channel}`;
9917
- }
9918
- get content() {
9919
- return this.notificationForm.get('content');
9920
- }
9921
- /**
9922
- * Check if currently connected to a channel
9923
- */
9924
- isChannelConnected(subscribedChannels) {
9925
- return this.connectedChannel !== null && subscribedChannels.includes(this.connectedChannel);
9926
- }
9927
- /**
9928
- * Connect to the selected channel
9929
- */
9930
- onConnectToChannel() {
9931
- const channel = this.selectedConnectionChannel.value;
9932
- if (!channel)
9933
- return;
9934
- this.connectedChannel = channel;
9935
- this.onSubscribeToChannel(channel);
9936
- }
9937
- /**
9938
- * Disconnect from the current channel
9939
- */
9940
- onDisconnectFromChannel() {
9941
- if (this.connectedChannel) {
9942
- this.onUnsubscribeFromChannel(this.connectedChannel);
9943
- this.connectedChannel = null;
9944
- }
9945
- }
9946
- ngOnInit() {
9947
- // Request today's notification channels when connection is established
9948
- this.connectionStatus$.pipe(filter$1(isConnected => isConnected), takeUntil$1(this.destroy$)).subscribe(() => {
9949
- // Request the list of today's notification channels from database
9950
- this.notificationService.getTodaysNotificationChannels();
9951
- });
9952
- }
9953
- ngOnDestroy() {
9954
- this.destroy$.next();
9955
- this.destroy$.complete();
9956
- }
9957
- /**
9958
- * Create a new notification channel
9959
- */
9960
- onCreateChannel() {
9961
- if (this.newChannelName.invalid)
9962
- return;
9963
- const channelName = this.newChannelName.value?.trim();
9964
- if (channelName) {
9965
- this.notificationService.createNotificationChannel(channelName);
9966
- this.newChannelName.reset();
9967
- }
9968
- }
9969
- /**
9970
- * Define and load previous channels
9971
- */
9972
- onDefinePreviousChannels() {
9973
- this.notificationService.definePreviousNotificationChannels();
9974
- }
9975
- /**
9976
- * Subscribe to a notification channel with optional date filter
9977
- */
9978
- onSubscribeToChannel(channel) {
9979
- const startDate = this.dateFilter.get('startDate')?.value;
9980
- const endDate = this.dateFilter.get('endDate')?.value;
9981
- const options = {};
9982
- if (startDate) {
9983
- // Set to start of day
9984
- const startOfDay = new Date(startDate);
9985
- startOfDay.setHours(0, 0, 0, 0);
9986
- options.startEpoch = Math.floor(startOfDay.getTime() / 1000);
9987
- }
9988
- if (endDate) {
9989
- // Set to end of day
9990
- const endOfDay = new Date(endDate);
9991
- endOfDay.setHours(23, 59, 59, 999);
9992
- options.endEpoch = Math.floor(endOfDay.getTime() / 1000);
9993
- }
9994
- this.notificationService.subscribeToNotificationChannel(channel, options, this.user);
9995
- }
9996
- /**
9997
- * Unsubscribe from a notification channel
9998
- */
9999
- onUnsubscribeFromChannel(channel) {
10000
- this.notificationService.unsubscribeFromNotificationChannel(channel);
10001
- }
10002
- /**
10003
- * Check if currently subscribed to a channel
10004
- */
10005
- isSubscribed(channel, subscribedChannels) {
10006
- return subscribedChannels.includes(channel);
10007
- }
10008
- /**
10009
- * Handle chip click - toggle subscription state
10010
- */
10011
- onChipClick(channel, subscribedChannels) {
10012
- const isCurrentlySubscribed = this.isSubscribed(channel, subscribedChannels);
10013
- if (isCurrentlySubscribed) {
10014
- this.onUnsubscribeFromChannel(channel);
10015
- }
10016
- else {
10017
- this.onSubscribeToChannel(channel);
10018
- }
10019
- }
10020
- /**
10021
- * Send a notification
10022
- */
10023
- onSendNotification() {
10024
- this.notificationForm.markAllAsTouched();
10025
- if (this.notificationForm.invalid)
10026
- return;
10027
- const channel = this.connectedChannel;
10028
- if (!channel)
10029
- return;
10030
- const messageContent = {
10031
- message: this.content.value
10032
- };
10033
- this.notificationService.sendNotification(channel, messageContent, this.user);
10034
- this.content.reset();
10035
- }
10036
- /**
10037
- * Format epoch timestamp to readable date/time
10038
- */
10039
- formatTimestamp(epoch) {
10040
- return new Date(epoch * 1000).toLocaleString();
10041
- }
10042
- /**
10043
- * Get today's date in YYYY-MM-DD format for date input default
10044
- */
10045
- getTodayDate() {
10046
- return new Date().toISOString().split('T')[0];
10047
- }
10048
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsNotificationsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10049
- 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" }] }); }
10050
- }
10051
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsNotificationsComponent, decorators: [{
10052
- type: Component,
10053
- 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"] }]
10054
- }], propDecorators: { server: [{
10055
- type: Input
10056
- }], wsServer: [{
10057
- type: Input
10058
- }], jwtToken: [{
10059
- type: Input
10060
- }], user: [{
10061
- type: Input
10062
- }] } });
10063
-
10064
- class WsAiMessagingComponent {
10065
- constructor() { }
10066
- ngOnInit() {
10067
- }
10068
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsAiMessagingComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10069
- 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: [""] }); }
10070
- }
10071
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsAiMessagingComponent, decorators: [{
10072
- type: Component,
10073
- args: [{ selector: 'app-ws-ai-messaging', standalone: false, template: "<p>\n ws-ai-messaging coming soon!\n</p>\n" }]
10074
- }], ctorParameters: () => [] });
10075
-
10076
- class WsChatsComponent {
10077
- constructor() { }
10078
- ngOnInit() {
10079
- }
10080
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsChatsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10081
- 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: [""] }); }
10082
- }
10083
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsChatsComponent, decorators: [{
10084
- type: Component,
10085
- args: [{ selector: 'app-ws-chats', standalone: false, template: "<p>\n ws-chats coming soon!\n</p>\n" }]
10086
- }], ctorParameters: () => [] });
10087
-
10088
- class RequestManagerWsDemoComponent {
10089
- constructor() {
10090
- this.httpManagerService = inject(HTTPManagerService);
10091
- this.stateService = inject(StateServiceDemo);
10092
- this.fb = inject(FormBuilder);
10093
- this.path = ['ai', 'tests'];
10094
- this.user$ = this.stateService.user$;
10095
- this.attempts$ = this.stateService.wsRetryAttempts$;
10096
- this.nextRetry$ = this.stateService.wsNextRetry$;
10097
- this.connectionStatus$ = this.stateService.connectionStatus$;
10098
- this.data$ = this.stateService.data$;
10099
- this.isPending$ = this.stateService.isPending$;
10100
- }
10101
- ngOnInit() {
10102
- this.stateService.updateConnection(this.server, this.wsServer, this.jwtToken, this.user, this.path);
10103
- }
10104
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerWsDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10105
- 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" }] }); }
10106
- }
10107
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerWsDemoComponent, decorators: [{
10108
- type: Component,
10109
- 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" }]
10110
- }], propDecorators: { server: [{
10111
- type: Input
10112
- }], wsServer: [{
10113
- type: Input
10114
- }], jwtToken: [{
10115
- type: Input
10116
- }], user: [{
10117
- type: Input
10118
- }], path: [{
10119
- type: Input
10120
- }] } });
10121
-
10122
- class Settings {
10123
- constructor(enum_1 = [], enum_2 = [], enum_3 = [], enum_4 = []) {
10124
- this.enum_1 = enum_1;
10125
- this.enum_2 = enum_2;
10126
- this.enum_3 = enum_3;
10127
- this.enum_4 = enum_4;
10128
- }
10129
- static adapt(item) {
10130
- return new Settings(item?.enum_1, item?.enum_2, item?.enum_3, item?.enum_4);
10131
- }
10132
- }
10133
-
10134
- class SettingsStateService extends StoreStateManagerService {
10135
- constructor() {
10136
- super(StateStorageOptions.adapt({
10137
- store: 'sampleStore',
10138
- options: SettingOptions.adapt({
10139
- storage: StorageType.SESSION,
10140
- encrypted: false
10141
- }),
10142
- model: Settings.adapt,
10143
- }));
10144
- }
10145
- updateEnum_1(value) {
10146
- this.updateData(value);
10147
- }
10148
- updateEnum_2(value) {
10149
- this.updateData(value);
10150
- }
10151
- getEnum(key) {
10152
- return this.data$.pipe(map$1(item => item[key]));
10153
- }
10154
- getEnum_1() {
10155
- return this.data$.pipe(map$1(item => item?.enum_1));
10156
- }
10157
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SettingsStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
10158
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SettingsStateService }); }
10159
- }
10160
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SettingsStateService, decorators: [{
10161
- type: Injectable
10162
- }], ctorParameters: () => [] });
10163
-
10164
- class StoreStateManagerDemoComponent {
10165
- constructor() {
10166
- this.settingsStateService = inject(SettingsStateService);
10167
- this.dataState$ = this.settingsStateService.data$;
10168
- this.dataEnum$ = this.settingsStateService.getEnum_1();
10169
- }
10170
- ngOnInit() {
10171
- }
10172
- onUpdateEnum_1() {
10173
- const rnd = RandomNumber(1000, 9999);
10174
- this.settingsStateService.updateEnum_1({ enum_1: [{ users: [1, 2, 3, rnd], random: rnd }] });
10175
- }
10176
- onUpdateEnum_2() {
10177
- const rnd = RandomNumber(1000, 9999);
10178
- this.settingsStateService.updateEnum_2({ enum_2: [{ users: [1, 2, 3, rnd], random: rnd }] });
10179
- }
10180
- onGetEnum_1() {
10181
- this.settingsStateService.getEnum_1().subscribe(data => console.log('DUMP:', data));
10182
- }
10183
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StoreStateManagerDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10184
- 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" }] }); }
10185
- }
10186
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StoreStateManagerDemoComponent, decorators: [{
10187
- type: Component,
10188
- 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" }]
10189
- }] });
10190
-
10191
- class DatabaseDataDemoComponent {
10192
- constructor() {
10193
- this.db = inject(DatabaseManagerService);
10194
- this.destroy$ = new Subject();
10195
- this.dataToDisplay = [];
10196
- this.dataSource = new DatabaseDataSource(this.dataToDisplay);
10197
- this.displayedColumns = ['id', 'last_name', 'age', 'amount'];
10198
- this.names = ['Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Garcia', 'Miller', 'Davis'];
10199
- }
10200
- ngOnDestroy() {
10201
- this.destroy$.next();
10202
- this.destroy$.complete();
10203
- }
10204
- addData() {
10205
- const newRecord = {
10206
- id: Math.max(...this.dataToDisplay.map(r => r.id), 0) + 1,
10207
- last_name: this.names[Math.floor(Math.random() * this.names.length)],
10208
- age: Math.floor(Math.random() * 80) + 18,
10209
- amount: Math.floor(Math.random() * 1000)
10210
- };
10211
- this.dataToDisplay = [...this.dataToDisplay, newRecord];
10212
- this.db.createTableRecord('sample_table', newRecord).subscribe(() => this.refreshData());
10213
- }
10214
- removeData() {
10215
- if (this.dataToDisplay.length > 0) {
10216
- const removedRecord = this.dataToDisplay[this.dataToDisplay.length - 1];
10217
- this.dataToDisplay = this.dataToDisplay.slice(0, -1);
10218
- this.db.deleteTableRecord('sample_table', removedRecord.id).subscribe(() => this.refreshData());
10219
- }
10220
- }
10221
- updateData() {
10222
- if (this.dataToDisplay.length === 0)
10223
- return;
10224
- const randomIndex = Math.floor(Math.random() * this.dataToDisplay.length);
10225
- const recordToUpdate = this.dataToDisplay[randomIndex];
10226
- const updatedRecord = {
10227
- ...recordToUpdate,
10228
- last_name: this.names[Math.floor(Math.random() * this.names.length)],
10229
- age: Math.floor(Math.random() * 80) + 18,
10230
- amount: Math.floor(Math.random() * 1000)
10231
- };
10232
- this.dataToDisplay = this.dataToDisplay.map((r, index) => index === randomIndex ? updatedRecord : r);
10233
- this.db.updateTableRecord('sample_table', updatedRecord).subscribe(() => this.refreshData());
10234
- }
10235
- clearAllData() {
10236
- if (this.dataToDisplay.length === 0)
10237
- return;
10238
- const idsToDelete = this.dataToDisplay.map(r => r.id);
10239
- this.dataToDisplay = [];
10240
- this.db.deleteTableRecords('sample_table', idsToDelete).subscribe(() => this.refreshData());
10241
- }
10242
- refreshData() {
10243
- this.db.getTableRecords('sample_table').subscribe((records) => {
10244
- this.dataToDisplay = records || [];
10245
- this.dataSource.setData(this.dataToDisplay);
10246
- });
10247
- }
10248
- ngOnInit() {
10249
- const tableDef = TableSchemaDef.adapt({
10250
- table: 'sample_table',
10251
- schema: '++id, last_name, age, amount'
10252
- });
10253
- this.db.createDatabaseTable(tableDef).subscribe(() => {
10254
- console.log('Database Table Initialized');
10255
- // hasTable
10256
- this.db.hasDatabaseTable('sample_table')
10257
- .subscribe(data => console.log('hasDatabaseTable:', data));
10258
- // Tables
10259
- this.db.getDatabaseTables()
10260
- .subscribe(data => console.log('getDatabaseTables:', data));
10261
- // getTable
10262
- this.db.getDatabaseTable('sample_table')
10263
- .subscribe(data => console.log('getDatabaseTable:', data));
10264
- // Table Schema
10265
- this.db.getDatabaseTableSchema('sample_table')
10266
- .subscribe(data => console.log('getDatabaseTableSchema:', data));
10267
- // Insert Table Records
10268
- const records = [
10269
- { id: 1, age: 12, last_name: 'Bonifacio', amount: 80 },
10270
- { id: 2, age: 15, last_name: 'Johns', amount: 700 },
10271
- { id: 3, age: 22, last_name: 'Kims', amount: 234 },
10272
- { id: 4, age: 45, last_name: 'Harrys', amount: 1200 },
10273
- ];
10274
- this.db.createTableRecords('sample_table', records).subscribe(() => {
10275
- this.refreshData();
10276
- });
10277
- });
10278
- }
10279
- createTable(tableDef) {
10280
- return this.db.createDatabaseTable(tableDef);
10281
- }
10282
- // CRUD
10283
- findTableRecords(table, column, value) {
10284
- return this.db.findTableRecords(table, column, value)
10285
- .pipe(tap((data) => console.log('findTableRecords:', data)));
10286
- }
10287
- findTableRecord(table, column, value) {
10288
- return this.db.findTableRecord(table, column, value)
10289
- .pipe(tap((data) => console.log('findTableRecord:', data)));
10290
- }
10291
- getTableRecords(table) {
10292
- return this.db.getTableRecords(table)
10293
- .pipe(tap((data) => console.log('getTableRecords:', data)));
10294
- }
10295
- getTableRecord(table, id) {
10296
- return this.db.getTableRecord(table, id)
10297
- .pipe(tap((data) => console.log('getTableRecord:', data)));
10298
- }
10299
- createTableRecord(table, record) {
10300
- return this.db.createTableRecord(table, record)
10301
- .pipe(tap((data) => console.log('createTableRecord:', data)));
10302
- }
10303
- updateTableRecord(table, record) {
10304
- return this.db.updateTableRecord(table, record)
10305
- .pipe(tap((data) => console.log('updateTableRecord:', data)));
10306
- }
10307
- deleteTableRecord(table, id) {
10308
- return this.db.deleteTableRecord(table, id)
10309
- .pipe(tap((data) => console.log('deleteTableRecord:', data)));
10310
- }
10311
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DatabaseDataDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10312
- 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" }] }); }
10313
- }
10314
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DatabaseDataDemoComponent, decorators: [{
10315
- type: Component,
10316
- 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" }]
10317
- }], ctorParameters: () => [] });
10318
- class DatabaseDataSource extends DataSource {
10319
- constructor(initialData) {
10320
- super();
10321
- this._dataStream = new ReplaySubject();
10322
- this.setData(initialData);
10323
- }
10324
- connect() {
10325
- return this._dataStream;
10326
- }
10327
- disconnect() { }
10328
- setData(data) {
10329
- this._dataStream.next(data);
10330
- }
10331
- }
10332
-
10333
- class HttpRequestServicesDemoComponent {
10334
- constructor(configOptions) {
10335
- this.configOptions = configOptions;
10336
- this.wsServer = 'ws:';
10337
- this.jwtToken = '';
10338
- this.server = 'http:';
10339
- this.path = ['ai', 'tests'];
10340
- this.requestTypes = [
10341
- { name: "Http Service", value: 'http_service' },
10342
- // { name: "Http Signals Service", value: 'http_signals_service', new: true },
10343
- { name: "Http State Service", value: 'http_state_service' },
10344
- { name: "Http State Service - Websockets", value: 'http_state_service_ws', new: false },
10345
- { name: "Database Service", value: 'database_service', divider: true, disabled: false },
10346
- { name: "Local Storage Service", value: 'local_storage_service' },
10347
- // { name: "Local Signals Storage Service", value: 'local_storage_signals_service', new: true },
10348
- { name: "Store State Manager Service", value: 'store_state_manager', new: true },
10349
- { name: "Basic Http Service", value: 'basic_http_service', divider: true },
10350
- ];
10351
- this.selectedService = this.requestTypes[1].value; //menu selection default
10352
- }
10353
- ngOnInit() {
10354
- if (this.configOptions)
10355
- this.injectionOptions = this.configOptions;
10356
- }
10357
- onSelected(type) {
10358
- this.selectedService = this.requestTypes[type].value;
10359
- }
10360
- 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 }); }
10361
- 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" }] }); }
10362
- }
10363
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: HttpRequestServicesDemoComponent, decorators: [{
10364
- type: Component,
10365
- 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"] }]
10366
- }], ctorParameters: () => [{ type: ConfigOptions, decorators: [{
10367
- type: Inject,
10368
- args: [CONFIG_SETTINGS_TOKEN]
10369
- }] }], propDecorators: { wsServer: [{
10370
- type: Input
10371
- }], jwtToken: [{
10372
- type: Input
10373
- }], server: [{
10374
- type: Input
10375
- }], user: [{
10376
- type: Input
10377
- }], path: [{
10378
- type: Input
10379
- }], adapter: [{
10380
- type: Input
10381
- }], mapper: [{
10382
- type: Input
10383
- }] } });
10384
-
10385
- class LocalStorageSignalsDemoComponent {
10386
- // store type is driven by the form control
10387
- get type() {
10388
- return (this.typeControl.value) ? +this.typeControl.value : 0;
10389
- }
10390
- get isValid() {
10391
- return this.newStoreForm.valid;
10392
- }
10393
- get isValidData() {
10394
- return this.storageForm.valid;
10395
- }
10396
- settingFor(name) {
10397
- const c = this.localStorageManagerService.setting(name);
10398
- return c ? c() : null;
10399
- }
10400
- constructor(configOptions) {
10401
- this.configOptions = configOptions;
10402
- this.fb = inject(FormBuilder);
10403
- this.utils = inject(UtilsService);
10404
- this.typeControl = this.fb.control(StorageType.GLOBAL.toString());
10405
- this.localStorageManagerService = inject(LocalStorageSignalsManagerService);
10406
- // Use signals directly and computed values for template binding
10407
- this.settings = computed(() => this.localStorageManagerService.settings());
10408
- // selected store (signal) and its JSON data for template
10409
- this.storeSelected = signal(null);
10410
- this.selectedStoreData = computed(() => {
10411
- const s = this.storeSelected();
10412
- if (!s)
10413
- return '';
10414
- const computed = this.localStorageManagerService.store(s.name);
10415
- return computed ? JSON.stringify(computed()) : '';
10416
- });
10417
- // filtered settings by selected type
10418
- this.selectedType = signal(StorageType.GLOBAL);
10419
- this.filteredSettings = computed(() => {
10420
- const values = this.settings() || [];
10421
- return values.filter((item) => item.options.storage === +this.selectedType());
10422
- });
10423
- this.storageForm = this.fb.group({
10424
- store: this.fb.control(null),
10425
- type: 'local',
10426
- settingType: 'local',
10427
- encrypted: false,
10428
- data: this.fb.control('', Validators.required),
10429
- });
10430
- this.newStoreForm = this.fb.group({
10431
- name: this.fb.control(null, Validators.required),
10432
- storage: 'local',
10433
- encrypted: false,
10434
- data: this.fb.control('', Validators.required),
10435
- expiresIn: this.fb.control('0')
10436
- });
10437
- // no RxJS Observables here; template uses signals/computed directly
10438
- this.expiresIn = (epoch) => this.utils.expiresIn(epoch);
10439
- this.isValidJSON = (str) => {
10440
- try {
10441
- JSON.parse(str);
10442
- return true;
10443
- }
10444
- catch (e) {
10445
- return false;
10446
- }
10447
- };
10448
- this.displayedColumns = ['name', 'id', 'encrypted', 'expires', "option"];
10449
- this.filterData = (values) => {
10450
- if (!values)
10451
- return [];
10452
- return values.filter((item) => item.options && item.options.storage === +this.type);
10453
- };
10454
- this.create = false;
10455
- }
10456
- ngOnInit() {
10457
- this.storeProps = this.configOptions?.LocalStorageOptions;
10458
- this.options = this.storeProps?.options;
10459
- if (this.options?.storage) {
10460
- this.typeControl.patchValue(this.options.storage.toString());
10461
- this.typeControl.disable();
10462
- }
10463
- else {
10464
- this.typeControl.enable();
10465
- }
10466
- if (this.options?.expiresIn) {
10467
- this.newStoreForm.get('expiresIn')?.patchValue(this.options.expiresIn);
10468
- this.newStoreForm.get('expiresIn')?.disable();
10469
- }
10470
- else {
10471
- this.newStoreForm.get('expiresIn')?.enable();
10472
- }
10473
- if (this.options?.encrypted) {
10474
- this.newStoreForm.get('encrypted')?.patchValue(this.options.encrypted);
10475
- this.newStoreForm.get('encrypted')?.disable();
10476
- }
10477
- else {
10478
- this.newStoreForm.get('encrypted')?.enable();
10479
- }
10480
- // nothing to synchronize - templates read computed signals directly
10481
- }
10482
- onCreateStore() {
10483
- if (!this.isValid)
10484
- return;
10485
- const store = this.newStoreForm.value;
10486
- if (!store.name || store.name === '')
10487
- return;
10488
- const options = { storage: this.type, encrypted: store.encrypted, expiresIn: store.expiresIn };
10489
- this.localStorageManagerService.createStore({
10490
- name: store.name,
10491
- data: store.data,
10492
- options: SettingOptions.adapt(options)
10493
- });
10494
- this.newStoreForm.reset();
10495
- this.create = false;
10496
- }
10497
- onUpdateStore(store) {
10498
- if (!this.storageForm.valid)
10499
- return;
10500
- const storeData = this.storageForm.value;
10501
- const data = JSON.parse(storeData.data || '');
10502
- const type = (storeData.type === 'local') ? StorageType.GLOBAL : StorageType.SESSION;
10503
- this.localStorageManagerService.updateStore({
10504
- name: store.name,
10505
- data
10506
- });
10507
- }
10508
- onSelectedRow(store) {
10509
- this.store = store;
10510
- this.storeSelected.set(store);
10511
- this.create = false;
10512
- }
10513
- onCreate() {
10514
- this.onCancel();
10515
- this.create = true;
10516
- }
10517
- onDelete(store) {
10518
- this.localStorageManagerService.deleteStore({
10519
- name: store.name,
10520
- });
10521
- this.onCancel();
10522
- }
10523
- onCancel() {
10524
- this.storeSelected.set(null);
10525
- this.store = null;
10526
- this.create = false;
10527
- }
10528
- onUpdate(store, data) {
10529
- this.localStorageManagerService.updateStore({
10530
- name: store.name,
10531
- data: JSON.parse(data)
10532
- });
10533
- this.onCancel();
10534
- }
10535
- onReset() {
10536
- this.localStorageManagerService.resetStore();
10537
- }
10538
- 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 }); }
10539
- 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 }); }
10540
- }
10541
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LocalStorageSignalsDemoComponent, decorators: [{
10542
- type: Component,
10543
- 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"] }]
10544
- }], ctorParameters: () => [{ type: ConfigOptions, decorators: [{
10545
- type: Inject,
10546
- args: [CONFIG_SETTINGS_TOKEN]
10547
- }] }] });
10548
-
10549
- class ClientInfo {
10550
- constructor(domain = '', service = '', id = 0, name = '') {
10551
- this.domain = domain;
10552
- this.service = service;
10553
- this.id = id;
10554
- this.name = name;
10555
- }
10556
- static adapt(item) {
10557
- return new ClientInfo(item?.domain, item?.service, item?.id, (item?.first_name || item?.last_name) ? `${item?.first_name} ${item?.last_name}` : '');
10558
- }
10559
- }
10560
-
10561
- class ClientInfoMapper {
10562
- constructor(id = 0, first_name = '', last_name = '', email = '') {
10563
- this.id = id;
10564
- this.first_name = first_name;
10565
- this.last_name = last_name;
10566
- this.email = email;
10567
- }
10568
- static adapt(item) {
10569
- const first_name = (item?.name) ? item.name.split(' ')[0] : '';
10570
- const last_name = (item?.name) ? item.name.split(' ')[1] : '';
10571
- return new ClientInfoMapper(item?.id, first_name, last_name, item?.email);
10572
- }
10573
- }
10574
-
10575
- class AIPrompt {
10576
- constructor(response = '') {
10577
- this.response = response;
10578
- }
10579
- static adapt(item) {
10580
- return new AIPrompt(item?.response);
10581
- }
10582
- }
10583
-
10584
- class RequestSignalsManagerDemoComponent {
10585
- get retry() {
10586
- return this.requestForm.get('retry')?.value;
10587
- }
10588
- get headers() {
10589
- return this.requestForm.get('headers');
10590
- }
10591
- get isValid() {
10592
- this.requestForm.markAllAsTouched();
10593
- return this.requestForm.valid;
10594
- }
10595
- constructor() {
10596
- this.displayedColumns = ['id', 'name', 'lastName', 'age'];
10597
- this.fb = inject(FormBuilder);
10598
- this.toastMessage = inject(ToastMessageDisplayService);
10599
- this.httpManagerSignalsService = inject(HTTPManagerSignalsService);
10600
- // Using signals service: signals are callable in templates (e.g. isPending())
10601
- this.isPending = this.httpManagerSignalsService.isPending;
10602
- this.countdown = this.httpManagerSignalsService.countdown;
10603
- // per-operation plain results and error messages (no rxjs in component)
10604
- this.GET_result = null;
10605
- this.POST_result = null;
10606
- this.PUT_result = null;
10607
- this.DELETE_result = null;
10608
- this.STREAM_result = null;
10609
- this.STREAM_AI_result = null;
10610
- this.GET_error = '';
10611
- this.POST_error = '';
10612
- this.PUT_error = '';
10613
- this.DELETE_error = '';
10614
- this.STREAM_error = '';
10615
- this.STREAM_AI_error = '';
10616
- this.requestParams = {
10617
- GET: ApiRequest.adapt(),
10618
- POST: ApiRequest.adapt(),
10619
- PUT: ApiRequest.adapt(),
10620
- DELETE: ApiRequest.adapt(),
10621
- STREAM: ApiRequest.adapt(),
10622
- };
10623
- this.questionControl = this.fb.control("", [Validators.required]);
10624
- this.downloadRequest = ApiRequest.adapt({
10625
- server: 'assets/images',
10626
- path: ['lego.png'],
10627
- // saveAs: 'john.jpg' // Optional
10628
- });
10629
- // downloadRequest = ApiRequest.adapt({
10630
- // server: 'oidc/ai/file'
10631
- // })
10632
- this.sampleClientData = {
10633
- id: 0,
10634
- name: "Old School Dates",
10635
- domain: "osd.com",
10636
- service: "osd",
10637
- spiffe: "osd.com/osd",
10638
- secret: "SMOPECXP-OS4P-USOG-X2II-3XMD1FQDR3IJX",
10639
- created: 1693003138,
10640
- modified: 1693003138,
10641
- icon: "",
10642
- imageFile: "",
10643
- email: "wavecoders@gmail.com"
10644
- };
10645
- this.requestForm = this.fb.group({
10646
- path: this.fb.control("ai/"),
10647
- headers: this.fb.array([]),
10648
- adapter: [null],
10649
- mapper: [null],
10650
- retry: this.fb.group({
10651
- times: [3],
10652
- delay: [3],
10653
- }),
10654
- polling: [3],
10655
- });
10656
- this.AIType = 0;
10657
- this.sampleAdaptors = [
10658
- { label: "ClientInfo Basic", value: ClientInfo.adapt },
10659
- { label: "AI Prompt", value: AIPrompt.adapt },
10660
- ];
10661
- this.sampleMappers = [
10662
- { label: "Mapper Basic", value: ClientInfoMapper.adapt },
10663
- { label: "AI Prompt", value: AIPrompt.adapt },
10664
- ];
10665
- this.hasId = (arr) => {
10666
- if (arr.length === 0)
10667
- return false;
10668
- return !isNaN(arr[arr.length - 1]);
10669
- };
10670
- this.props = (adapter) => {
10671
- return (adapter) ? adapter() : null;
10672
- };
10673
- // server = `http://sample-endpoint/as/authorization.oauth2`
10674
- this.arrayObjectsToObjects = (arr) => {
10675
- return Array.isArray(arr) ? arr.reduce((obj, item) => Object.assign(obj, { [item.key]: item.value }), {}) : {};
10676
- };
10677
- }
10678
- ngOnInit() {
10679
- // const reqGet2 = ApiRequest.adapt({
10680
- // server,
10681
- // path: ['clients'],
10682
- // headers: { authentication: "Bearer <KEY>" },
10683
- // adapter: ClientInfo,
10684
- // dataType: DataType.OBJECT,
10685
- // // concurrent: false,
10686
- // // polling: 3, //seconds
10687
- // })
10688
- // const req2 = [1024,1025,1026].map(item => {
10689
- // return this.httpManagerService.getRequest<ClientInfo[]>(reqGet2, [item])
10690
- // .pipe(
10691
- // catchError(error => {
10692
- // return throwError(() => new Error(error.error.message))
10693
- // })
10694
- // )
10695
- // })
10696
- // forkJoin(req2)
10697
- // .subscribe(res => console.log(res))
10698
- }
10699
- addHeader() {
10700
- const header = this.fb.group({
10701
- key: ['', Validators.required],
10702
- value: ['']
10703
- });
10704
- this.headers.push(header);
10705
- }
10706
- removeHeader(index) {
10707
- this.headers.removeAt(index);
10708
- }
10709
- compileRequest() {
10710
- const requestParams = this.requestForm.value;
10711
- requestParams.headers = this.arrayObjectsToObjects(requestParams.headers || []);
10712
- const pathReq = (requestParams.path === "") ? [] : (requestParams.path || "").split("/");
10713
- if (!this.pollingState.checked)
10714
- requestParams.polling = 0;
10715
- if (!this.failedState.checked) {
10716
- requestParams.retry = { times: 0, delay: 0 };
10717
- }
10718
- const apiOptions = ApiRequest.adapt(requestParams);
10719
- apiOptions.path = [];
10720
- return { apiOptions: apiOptions, path: pathReq };
10721
- }
10722
- onGetRequest() {
10723
- if (!this.isValid)
10724
- return;
10725
- const reqParams = this.compileRequest();
10726
- this.requestParams.GET = reqParams.apiOptions;
10727
- // reset local state
10728
- this.GET_result = null;
10729
- this.GET_error = '';
10730
- this.httpManagerSignalsService.getRequest(reqParams.apiOptions, reqParams.path)
10731
- .subscribe({
10732
- next: (res) => this.GET_result = res,
10733
- error: (err) => this.errorHandling(err, 'GET')
10734
- });
10735
- }
10736
- onCreateRequest() {
10737
- if (!this.isValid)
10738
- return;
10739
- const reqParams = this.compileRequest();
10740
- this.requestParams.POST = reqParams.apiOptions;
10741
- // reset local state
10742
- this.POST_result = null;
10743
- this.POST_error = '';
10744
- this.httpManagerSignalsService.postRequest(this.sampleClientData, reqParams.apiOptions, reqParams.path)
10745
- .subscribe({
10746
- next: (res) => this.POST_result = res,
10747
- error: (err) => this.errorHandling(err, 'POST')
10748
- });
10749
- }
10750
- onUpdateRequest() {
10751
- if (!this.isValid)
10752
- return;
10753
- const reqParams = this.compileRequest();
10754
- if (!this.hasId(reqParams.path)) {
10755
- console.log("Missing ID");
10756
- return;
10757
- }
10758
- this.sampleClientData.id = parseInt(reqParams.path[reqParams.path.length - 1]);
10759
- this.requestParams.PUT = reqParams.apiOptions;
10760
- // reset local state
10761
- this.PUT_result = null;
10762
- this.PUT_error = '';
10763
- this.httpManagerSignalsService.putRequest(this.sampleClientData, reqParams.apiOptions, reqParams.path)
10764
- .subscribe({
10765
- next: (res) => this.PUT_result = res,
10766
- error: (err) => this.errorHandling(err, 'PUT')
10767
- });
10768
- }
10769
- onDeleteRequest() {
10770
- if (!this.isValid)
10771
- return;
10772
- const reqParams = this.compileRequest();
10773
- this.requestParams.DELETE = reqParams.apiOptions;
10774
- if (!this.hasId(reqParams.path)) {
10775
- console.log("Missing ID");
10776
- return;
10777
- }
10778
- this.sampleClientData.id = parseInt(reqParams.path[reqParams.path.length - 1]);
10779
- this.requestParams.DELETE = reqParams.apiOptions;
10780
- // reset local state
10781
- this.DELETE_result = null;
10782
- this.DELETE_error = '';
10783
- this.httpManagerSignalsService.deleteRequest(reqParams.apiOptions, reqParams.path)
10784
- .subscribe({
10785
- next: (res) => this.DELETE_result = res,
10786
- error: (err) => this.errorHandling(err, 'DELETE')
10787
- });
10788
- }
10789
- onStreamPostRequest() {
10790
- if (!this.isValid)
10791
- return;
10792
- const reqParams = this.compileRequest();
10793
- let payload = {};
10794
- let apiPath = reqParams.path;
10795
- let apiOptions = reqParams.apiOptions;
10796
- let responseMapper = (items) => items.response;
10797
- if (this.AIType === 0) {
10798
- // API request
10799
- payload = { prompt: this.questionControl.value };
10800
- }
10801
- else {
10802
- // Local Ollama request
10803
- apiOptions.server = "api";
10804
- apiPath = ["generate"];
10805
- apiOptions.stream = true;
10806
- payload = {
10807
- model: "phi3:latest",
10808
- prompt: this.questionControl.value,
10809
- stream: true,
10810
- };
10811
- responseMapper = (items) => items.map((word) => word.response).flat().join('');
10812
- }
10813
- this.requestParams.STREAM = apiOptions;
10814
- this.STREAM_AI_result = null;
10815
- this.STREAM_AI_error = '';
10816
- this.httpManagerSignalsService.postRequest(payload, apiOptions, apiPath)
10817
- .subscribe({
10818
- next: (res) => {
10819
- try {
10820
- this.STREAM_AI_result = responseMapper(res);
10821
- }
10822
- catch (e) {
10823
- this.STREAM_AI_result = res;
10824
- }
10825
- this.questionControl.reset();
10826
- },
10827
- error: (err) => this.errorHandling(err, 'STREAM')
10828
- });
10829
- }
10830
- onStreamRequest() {
10831
- if (!this.isValid)
10832
- return;
10833
- const reqParams = this.compileRequest();
10834
- reqParams.apiOptions.stream = true;
10835
- this.requestParams.GET = reqParams.apiOptions;
10836
- this.STREAM_result = null;
10837
- this.STREAM_error = '';
10838
- this.httpManagerSignalsService.getRequest(reqParams.apiOptions, reqParams.path)
10839
- .subscribe({
10840
- next: (res) => this.STREAM_result = res,
10841
- error: (err) => this.errorHandling(err, 'STREAM')
10842
- });
10843
- }
10844
- onDownloadCompleted() {
10845
- const message = "Download Completed";
10846
- const display = ToastDisplay.adapt({
10847
- message,
10848
- action: 'Ok',
10849
- color: ToastColors.SUCCESS,
10850
- icon: 'sentiment_satisfied_alt',
10851
- });
10852
- this.toastMessage.toastMessage(display);
10853
- }
10854
- onDownloadFailed(err) {
10855
- const message = "Download Failed";
10856
- const display = ToastDisplay.adapt({
10857
- message,
10858
- action: 'Ok',
10859
- color: ToastColors.ERROR,
10860
- icon: 'warning',
10861
- });
10862
- this.toastMessage.toastMessage(display);
10863
- }
10864
- errorHandling(err, type) {
10865
- const message = err?.message || String(err);
10866
- // set local error state
10867
- if (type === 'GET')
10868
- this.GET_error = message;
10869
- if (type === 'POST')
10870
- this.POST_error = message;
10871
- if (type === 'PUT')
10872
- this.PUT_error = message;
10873
- if (type === 'DELETE')
10874
- this.DELETE_error = message;
10875
- if (type === 'STREAM')
10876
- this.STREAM_error = message;
10877
- // also set the shared service error signal
10878
- this.httpManagerSignalsService.error.set(message);
10879
- return err;
10880
- }
10881
- onSelectAIType(type) {
10882
- this.AIType = type;
10883
- }
10884
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestSignalsManagerDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10885
- 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" }] }); }
10886
- }
10887
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestSignalsManagerDemoComponent, decorators: [{
10888
- type: Component,
10889
- 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"] }]
10890
- }], ctorParameters: () => [], propDecorators: { failedState: [{
10891
- type: ViewChild,
10892
- args: ["failedState", { static: true }]
10893
- }], pollingState: [{
10894
- type: ViewChild,
10895
- args: ["pollingState", { static: true }]
10896
- }] } });
10897
-
10898
- // import { MessengerChatModule } from 'src/app/components/messenger-chat/messenger-chat.module';
10899
- // import { StoreStateManagerModule } from "src/app/beta_components/store-state-manager/store-state-manager.module";
10900
- class HttpRequestManagerModule {
10901
- static forRoot(config = ConfigOptions.adapt()) {
10902
- return {
10903
- ngModule: HttpRequestManagerModule,
10904
- providers: [
10905
- { provide: CONFIG_SETTINGS_TOKEN, useValue: ConfigOptions.adapt(config) },
10906
- HTTPManagerService, LocalStorageManagerService //all services that need access to config
10907
- ],
10908
- };
10909
- }
10910
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: HttpRequestManagerModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
10911
- static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.14", ngImport: i0, type: HttpRequestManagerModule, declarations: [RequestManagerBasicDemoComponent,
10912
- HttpRequestServicesDemoComponent,
10913
- RequestManagerStateDemoComponent,
10914
- RequestManagerDemoComponent,
10915
- RequestSignalsManagerDemoComponent,
10916
- LocalStorageDemoComponent,
10917
- LocalStorageSignalsDemoComponent,
10918
- RequestManagerWsDemoComponent,
10919
- StoreStateManagerDemoComponent,
10920
- DatabaseDataDemoComponent,
10921
- WsDataControlComponent,
10922
- WsMessagingComponent,
10923
- WsNotificationsComponent,
10924
- WsAiMessagingComponent,
10925
- WsChatsComponent], imports: [CommonModule,
10926
- ToastMessageDisplayModule,
10927
- FormsModule,
10928
- ReactiveFormsModule,
10929
- MatButtonModule,
10930
- MatTabsModule,
10931
- MatSelectModule,
10932
- MatChipsModule,
10933
- MatMenuModule,
10934
- MatIconModule,
10935
- MatTableModule,
10936
- MatButtonToggleModule,
10937
- MatAutocompleteModule,
10938
- MatProgressBarModule,
10939
- MatProgressSpinnerModule,
10940
- MatSlideToggleModule,
10941
- MatDividerModule,
10942
- MatFormFieldModule,
10943
- MatInputModule,
10944
- MatToolbarModule,
10945
- MatSlideToggleModule, i1.TranslateModule, MatSidenavModule,
10946
- MatDatepickerModule,
10947
- MatNativeDateModule,
10948
- MatCardModule,
10949
- FileDownloaderModule], exports: [HttpRequestServicesDemoComponent] }); }
10950
- static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: HttpRequestManagerModule, providers: [
10951
- { provide: HTTP_INTERCEPTORS, useClass: WithCredentialsInterceptor, multi: true },
10952
- { provide: HTTP_INTERCEPTORS, useClass: RequestHeadersInterceptor, multi: true },
10953
- { provide: HTTP_INTERCEPTORS, useClass: ProxyDebuggerInterceptor, multi: true },
10954
- { provide: CONFIG_SETTINGS_TOKEN, useValue: ConfigOptions.adapt() },
10955
- HTTPManagerService, LocalStorageManagerService,
10956
- ToastMessageDisplayService,
10957
- MessageDisplayRouterService,
10958
- SnackbarStrategy
10959
- ], imports: [CommonModule,
10960
- ToastMessageDisplayModule,
10961
- FormsModule,
10962
- ReactiveFormsModule,
10963
- MatButtonModule,
10964
- MatTabsModule,
10965
- MatSelectModule,
10966
- MatChipsModule,
10967
- MatMenuModule,
10968
- MatIconModule,
10969
- MatTableModule,
10970
- MatButtonToggleModule,
10971
- MatAutocompleteModule,
10972
- MatProgressBarModule,
10973
- MatProgressSpinnerModule,
10974
- MatSlideToggleModule,
10975
- MatDividerModule,
10976
- MatFormFieldModule,
10977
- MatInputModule,
10978
- MatToolbarModule,
10979
- MatSlideToggleModule,
10980
- TranslateModule.forRoot(),
10981
- MatSidenavModule,
10982
- MatDatepickerModule,
10983
- MatNativeDateModule,
10984
- MatCardModule,
10985
- 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()] }); }
10986
7477
  }
10987
7478
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: HttpRequestManagerModule, decorators: [{
10988
7479
  type: NgModule,
@@ -10992,200 +7483,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
10992
7483
  ToastMessageDisplayModule,
10993
7484
  FormsModule,
10994
7485
  ReactiveFormsModule,
10995
- MatButtonModule,
10996
- MatTabsModule,
10997
- MatSelectModule,
10998
- MatChipsModule,
10999
- MatMenuModule,
11000
- MatIconModule,
11001
- MatTableModule,
11002
- MatButtonToggleModule,
11003
- MatAutocompleteModule,
11004
- MatProgressBarModule,
11005
- MatProgressSpinnerModule,
11006
- MatSlideToggleModule,
11007
- MatDividerModule,
11008
- MatFormFieldModule,
11009
- MatInputModule,
11010
- MatToolbarModule,
11011
- MatSlideToggleModule,
11012
7486
  TranslateModule.forRoot(),
11013
- MatSidenavModule,
11014
- MatDatepickerModule,
11015
- MatNativeDateModule,
11016
- MatCardModule,
11017
- FileDownloaderModule,
11018
- // MessengerChatModule,
11019
- // StoreStateManagerModule
11020
- ],
11021
- declarations: [
11022
- RequestManagerBasicDemoComponent,
11023
- HttpRequestServicesDemoComponent,
11024
- RequestManagerStateDemoComponent,
11025
- RequestManagerDemoComponent,
11026
- RequestSignalsManagerDemoComponent,
11027
- LocalStorageDemoComponent,
11028
- LocalStorageSignalsDemoComponent,
11029
- RequestManagerWsDemoComponent,
11030
- StoreStateManagerDemoComponent,
11031
- DatabaseDataDemoComponent,
11032
- WsDataControlComponent,
11033
- WsMessagingComponent,
11034
- WsNotificationsComponent,
11035
- WsAiMessagingComponent,
11036
- WsChatsComponent,
11037
- ],
11038
- exports: [
11039
- HttpRequestServicesDemoComponent,
11040
7487
  ],
7488
+ declarations: [],
7489
+ exports: [],
11041
7490
  providers: [
11042
7491
  { provide: HTTP_INTERCEPTORS, useClass: WithCredentialsInterceptor, multi: true },
11043
7492
  { provide: HTTP_INTERCEPTORS, useClass: RequestHeadersInterceptor, multi: true },
11044
7493
  { provide: HTTP_INTERCEPTORS, useClass: ProxyDebuggerInterceptor, multi: true },
11045
7494
  { provide: CONFIG_SETTINGS_TOKEN, useValue: ConfigOptions.adapt() },
11046
- HTTPManagerService, LocalStorageManagerService,
7495
+ HTTPManagerService,
7496
+ LocalStorageManagerService,
11047
7497
  ToastMessageDisplayService,
11048
7498
  MessageDisplayRouterService,
11049
- SnackbarStrategy
7499
+ SnackbarStrategy,
11050
7500
  ],
11051
7501
  }]
11052
7502
  }] });
11053
7503
 
11054
- const defaultCounterState = () => ({
11055
- count: 0,
11056
- lastUpdated: new Date().toISOString(),
11057
- history: []
11058
- });
11059
- class StoreStateSignalsDemoComponent {
11060
- constructor() {
11061
- // Signals from service
11062
- this.stateJson = signal('');
11063
- this.historyLength = signal(0);
11064
- this.history = signal([]);
11065
- }
11066
- ngOnInit() {
11067
- const options = StateStorageOptions.adapt({
11068
- store: 'counter-state',
11069
- model: defaultCounterState,
11070
- options: {
11071
- storage: 'global',
11072
- encrypted: false
11073
- }
11074
- });
11075
- this.stateManager = new StoreStateManagerSignalsService();
11076
- this.stateManager.init(options);
11077
- // Update signals when state changes
11078
- this.updateSignals();
11079
- }
11080
- updateSignals() {
11081
- const state = this.stateManager.data();
11082
- if (state) {
11083
- this.stateJson.set(JSON.stringify(state, null, 2));
11084
- this.historyLength.set(state.history.length);
11085
- this.history.set([...state.history]);
11086
- }
11087
- }
11088
- increment() {
11089
- const current = this.stateManager.data();
11090
- if (current) {
11091
- this.stateManager.updateState({
11092
- count: current.count + 1,
11093
- lastUpdated: new Date().toISOString()
11094
- });
11095
- this.updateSignals();
11096
- }
11097
- }
11098
- decrement() {
11099
- const current = this.stateManager.data();
11100
- if (current) {
11101
- this.stateManager.updateState({
11102
- count: current.count - 1,
11103
- lastUpdated: new Date().toISOString()
11104
- });
11105
- this.updateSignals();
11106
- }
11107
- }
11108
- reset() {
11109
- this.stateManager.resetState();
11110
- this.updateSignals();
11111
- }
11112
- addToHistory() {
11113
- const current = this.stateManager.data();
11114
- if (current) {
11115
- this.stateManager.updateState({
11116
- history: [...current.history, current.count],
11117
- lastUpdated: new Date().toISOString()
11118
- });
11119
- this.updateSignals();
11120
- }
11121
- }
11122
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StoreStateSignalsDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
11123
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: StoreStateSignalsDemoComponent, selector: "app-store-state-signals-demo", ngImport: i0, template: `
11124
- <div class="demo-container">
11125
- <h2>Store State Manager Signals Demo</h2>
11126
- <p>Signal-based state management with localStorage persistence</p>
11127
-
11128
- <div class="state-display">
11129
- <h3>Current State (Signal)</h3>
11130
- <pre>{{ stateJson() }}</pre>
11131
- </div>
11132
-
11133
- <div class="actions">
11134
- <button (click)="increment()">Increment</button>
11135
- <button (click)="decrement()">Decrement</button>
11136
- <button (click)="reset()">Reset</button>
11137
- <button (click)="addToHistory()">Add to History</button>
11138
- </div>
11139
-
11140
- <div class="history">
11141
- <h3>History: {{ historyLength() }} items</h3>
11142
- <ul>
11143
- @for (item of history(); track item; let i = $index) {
11144
- <li>{{ i + 1 }}. {{ item }}</li>
11145
- }
11146
- </ul>
11147
- </div>
11148
- </div>
11149
- `, 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"] }); }
11150
- }
11151
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StoreStateSignalsDemoComponent, decorators: [{
11152
- type: Component,
11153
- args: [{ selector: 'app-store-state-signals-demo', template: `
11154
- <div class="demo-container">
11155
- <h2>Store State Manager Signals Demo</h2>
11156
- <p>Signal-based state management with localStorage persistence</p>
11157
-
11158
- <div class="state-display">
11159
- <h3>Current State (Signal)</h3>
11160
- <pre>{{ stateJson() }}</pre>
11161
- </div>
11162
-
11163
- <div class="actions">
11164
- <button (click)="increment()">Increment</button>
11165
- <button (click)="decrement()">Decrement</button>
11166
- <button (click)="reset()">Reset</button>
11167
- <button (click)="addToHistory()">Add to History</button>
11168
- </div>
11169
-
11170
- <div class="history">
11171
- <h3>History: {{ historyLength() }} items</h3>
11172
- <ul>
11173
- @for (item of history(); track item; let i = $index) {
11174
- <li>{{ i + 1 }}. {{ item }}</li>
11175
- }
11176
- </ul>
11177
- </div>
11178
- </div>
11179
- `, 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"] }]
11180
- }] });
11181
-
11182
7504
  /*
11183
7505
  * Public API Surface of http-request-manager
11184
7506
  */
7507
+ // Core module and services
11185
7508
 
11186
7509
  /**
11187
7510
  * Generated bundle index. Do not edit.
11188
7511
  */
11189
7512
 
11190
- export { ApiRequest, AppService, AsymmetricalEncryptionService, BatchOptions, BatchRequestResultModel, 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 };
11191
7514
  //# sourceMappingURL=http-request-manager.mjs.map