turbogui-angular 17.0.1 → 19.0.0

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,6 +1,6 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { EventEmitter, Output, Directive, HostListener, NgModule, inject, Injectable, Component, Inject, HostBinding, Input } from '@angular/core';
3
- import { Subject } from 'rxjs';
3
+ import { Subject, BehaviorSubject } from 'rxjs';
4
4
  import { ArrayUtils, NumericUtils, HTTPManager, StringUtils, HTTPManagerGetRequest, HTTPManagerPostRequest, BrowserManager, ConversionUtils } from 'turbocommons-ts';
5
5
  import * as i1 from '@angular/material/dialog';
6
6
  import { MAT_DIALOG_DATA } from '@angular/material/dialog';
@@ -14,6 +14,10 @@ import * as i2$1 from '@angular/material/datepicker';
14
14
  import { MatDatepickerModule } from '@angular/material/datepicker';
15
15
  import { MatNativeDateModule } from '@angular/material/core';
16
16
  import * as i1$1 from '@angular/material/snack-bar';
17
+ import * as i1$2 from '@angular/router';
18
+ import { NavigationEnd } from '@angular/router';
19
+ import { filter } from 'rxjs/operators';
20
+ import * as i2$2 from '@angular/platform-browser';
17
21
  import { MatFormFieldModule } from '@angular/material/form-field';
18
22
  import * as i4 from '@angular/material/input';
19
23
  import { MatInputModule } from '@angular/material/input';
@@ -379,487 +383,595 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImpor
379
383
  * CopyRight : -> Copyright 2018 Edertone Advanded Solutions. https://www.edertone.com
380
384
  */
381
385
  /**
382
- * Manages application text translations and languages
386
+ * This is the base class for all the dialog components that can be loaded by the dialog service class
383
387
  */
384
- class LocalesService extends SingletoneStrictClass {
385
- /**
386
- * Fully featured translation manager to be used with any application that requires text internationalization.
387
- */
388
- constructor() {
389
- super(LocalesService);
390
- /**
391
- * if the class has been correctly initialized and translations have been correctly loaded
392
- */
393
- this._isInitialized = false;
394
- /**
395
- * @see getLocales()
396
- */
397
- this._locales = [];
398
- /**
399
- * @see getLanguages()
400
- */
401
- this._languages = [];
402
- /**
403
- * Stores all the loaded localization data by library name, bundle name, key and locales
404
- */
405
- this._loadedTranslations = {};
406
- /**
407
- * Stores a memory cache to improve performance when outputing translations
408
- */
409
- this._keyValuesCache = {};
410
- /**
411
- * @see setWildCardsFormat()
412
- */
413
- this._wildCardsFormat = '{N}';
414
- /**
415
- * @see setMissingKeyFormat()
416
- */
417
- this._missingKeyFormat = '$exception';
418
- /**
419
- * Stores a hash value that is used to improve the performance for translation t() methods.
420
- * This is computed based on _wildCardsFormat plus _missingKeyFormat plus the current primary locale
421
- * Methods that change these values will recalculate the hash string, so when calling translation methods, the
422
- * performance will be as fast as possible.
423
- */
424
- this._cacheHashBaseString = '';
425
- }
426
- /**
427
- * Wildcards are string fragments that are placed inside the translated texts. Their main purpose is to be replaced at
428
- * runtime by custom values like for example a user name, a date, a numeric value, etc..
429
- *
430
- * This class helps with this process by including a parameter called 'toReplace' on all ->t methods which allows us
431
- * to specify a string or list of strings that will replace the respective wildcards on the translated text. Each wildcard
432
- * must follow the format specified here, and contain a numeric digit that will be used to find the replacement text at the
433
- * 'toReplace' list. For example, if we define $N as the wildcard format, and we have a translation that contains $0, $1, $2,
434
- * $0 will be replaced with the first element on toReplace, $1 with the second and so.
435
- *
436
- * We usually set this before initializing the class translation data
437
- *
438
- * Notice that N is mandayory on the wildcards format and the first index value is 0.
388
+ class DialogBaseComponent {
389
+ /*
390
+ * The name of the superclass must be set into this constant as it is required by the dialog service to identify dialogs as different.
439
391
  *
440
- * @param value The wildcards format we want to set
392
+ * When you extend the dialog base class, simply declare this static constant with the exact same name as your class and you're done.
393
+ * If this value is not set on the extended dialog component, a runtime exception will happen when trying to show the dialog.
441
394
  *
442
- * @returns The value that's been set
395
+ * The root cause of this requirement is that when apps are compiled for production, class names are minified and this causes problems
396
+ * when creating a dialog hash to uniquely identify a dialog instance. Therefore, a hardcoded class name is necesary.
443
397
  */
444
- setWildCardsFormat(value) {
445
- if (!value.includes('N')) {
446
- throw new Error("N is mandatory to replace wildcards");
398
+ static { this.DIALOG_CLASS_NAME = ''; }
399
+ constructor(elementRef, dialogRef) {
400
+ this.elementRef = elementRef;
401
+ this.dialogRef = dialogRef;
402
+ }
403
+ ngAfterViewInit() {
404
+ // Assign the component HTML identifier if it is specifically assigned to the dialogref instance
405
+ if (this.dialogRef.id !== undefined && this.dialogRef.id !== '') {
406
+ this.elementRef.nativeElement.id = this.dialogRef.id;
447
407
  }
448
- this._cacheHashBaseString = value + this._missingKeyFormat + ((this._locales.length > 0) ? this._locales[0] : '');
449
- this._wildCardsFormat = value;
450
- return value;
451
408
  }
452
409
  /**
453
- * Defines the behaviour for get(), getStartCase(), etc... methods when a key is not found on
454
- * a bundle or the bundle does not exist
455
- *
456
- * If missingKeyFormat is an empty string, all missing keys will return an empty value (not recommended)
457
- *
458
- * If missingKeyFormat contains a string, that string will be always returned for missing keys
459
- *
460
- * If missingKeyFormat contains a string with one of the following predefined wildcards:<br>
461
- * - $key will be replaced with key name. Example: get("NAME") will output [NAME] if key is not found and missingKeyFormat = '[$key]'<br>
462
- * - $exception (default value) will throw an exception with the problem cause description.
410
+ * Method to be called by the dialogs that extend this base component when they want to close themselves.
411
+ * It will perform the close of that dialog and also send an object to the addDialog() callback with the index and value
412
+ * that the user may have selected.
463
413
  *
464
- * @param value The missing key format we want to set
414
+ * @param index The numeric index of the user option selection. It will be specific for each dialog and it's different available options
415
+ * @param value Any value that may be provided to the callback regarding the user selected option.
465
416
  *
466
- * @returns The value that's been set
467
- */
468
- setMissingKeyFormat(value) {
469
- this._cacheHashBaseString = this._wildCardsFormat + value + ((this._locales.length > 0) ? this._locales[0] : '');
470
- this._missingKeyFormat = value;
471
- return value;
472
- }
473
- /**
474
- * @see setMissingKeyFormat()
417
+ * @return void
475
418
  */
476
- getMissingKeyFormat() {
477
- return this._missingKeyFormat;
419
+ closeDialog(index, value = null) {
420
+ this.dialogRef.close({ index: index, value: value });
478
421
  }
479
- /**
480
- * Initializes the translation system by loading and parsing bundle files from the specified translations path.
481
- * After the promise finishes, the class will contain all the translation data and will be ready to translate any
482
- * provided key.
483
- *
484
- * @param translationsPath - Url where the translations Json structure of libraries/bundles/locales/keys is available.
485
- * @param locales An array of locale codes (e.g., ['en_US', 'es_ES', 'fr_FR']) to load. These will be added to the translation
486
- * path using the following format: translationsPath/en_US-es_ES-fr_FR. The order of this array will determine the
487
- * translation priority
488
- * @param parameters Any extra parameters to be attached to the translationsPath after the locales like /param1/param2/ etc
489
- *
490
- * @return A promise that will resolve if the translations get correctly loaded, or reject with an error if load fails
491
- */
492
- initialize(translationsPath, locales, parameters) {
493
- this._isInitialized = false;
494
- this._loadedTranslations = {};
495
- // Validate received locales are correct
496
- for (const locale of locales) {
497
- this._validateLocaleString(locale);
422
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DialogBaseComponent, deps: [{ token: i0.ElementRef }, { token: i1.MatDialogRef }], target: i0.ɵɵFactoryTarget.Component }); }
423
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.5", type: DialogBaseComponent, isStandalone: false, selector: "ng-component", ngImport: i0, template: '', isInline: true }); }
424
+ }
425
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DialogBaseComponent, decorators: [{
426
+ type: Component,
427
+ args: [{
428
+ template: '',
429
+ standalone: false
430
+ }]
431
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1.MatDialogRef }] });
432
+
433
+ /**
434
+ * TurboGUI is A library that helps with the most common and generic UI elements and functionalities
435
+ *
436
+ * Website : -> http://www.turbogui.org
437
+ * License : -> Licensed under the Apache License, Version 2.0. You may not use this file except in compliance with the License.
438
+ * License Url : -> http://www.apache.org/licenses/LICENSE-2.0
439
+ * CopyRight : -> Copyright 2018 Edertone Advanded Solutions. https://www.edertone.com
440
+ */
441
+ /**
442
+ * A dialog component with a single option button, to be used for error notifications
443
+ */
444
+ class DialogErrorComponent extends DialogBaseComponent {
445
+ static { this.DIALOG_CLASS_NAME = 'DialogErrorComponent'; }
446
+ constructor(elementRef, dialogRef, data) {
447
+ super(elementRef, dialogRef);
448
+ this.elementRef = elementRef;
449
+ this.dialogRef = dialogRef;
450
+ this.data = data;
451
+ if (data.texts.length < 1) {
452
+ throw new Error('DialogErrorComponent expects 2 texts: The title and optionally a description');
498
453
  }
499
- let translationsFullPath = translationsPath + '/' + locales.join('-') + '/' + parameters.join('/');
500
- return new Promise((resolve, reject) => {
501
- fetch(translationsFullPath).then(response => {
502
- if (!response.ok) {
503
- throw new Error(`HTTP error! status: ${response.status}`);
504
- }
505
- return response.json();
506
- }).then(data => {
507
- this._loadedTranslations = data;
508
- this._isInitialized = true;
509
- this._locales = locales;
510
- this._languages = locales.map((l) => l.substring(0, 2));
511
- this._cacheHashBaseString = this._wildCardsFormat + this._missingKeyFormat + this._locales[0];
512
- resolve(undefined);
513
- }).catch(error => {
514
- reject(new Error(`ERROR LOADING LOCALES FROM: ${translationsFullPath}\n` + error));
515
- });
516
- });
517
- }
518
- /**
519
- * Check if the class has been correctly initialized and translations have been correctly loaded
520
- */
521
- isInitialized() {
522
- return this._isInitialized;
523
- }
524
- /**
525
- * Aux method to verify that this class is correctly initialized with translation data
526
- */
527
- _validateInitialized() {
528
- if (!this._isInitialized) {
529
- throw new Error('LocalesManager not initialized');
454
+ if (data.options.length !== 1) {
455
+ throw new Error('DialogErrorComponent expects only one option');
456
+ }
457
+ if (data.texts.length > 1) {
458
+ data.texts[1] = data.texts[1].replace(/\n/g, "<br/>");
530
459
  }
531
460
  }
461
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DialogErrorComponent, deps: [{ token: i0.ElementRef }, { token: i1.MatDialogRef }, { token: MAT_DIALOG_DATA }], target: i0.ɵɵFactoryTarget.Component }); }
462
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.5", type: DialogErrorComponent, isStandalone: true, selector: "tg-dialog-error", providers: [], usesInheritance: true, ngImport: i0, template: "<div class=\"titleAndIconContainer\">\r\n <h3>\r\n {{data.texts[0]}}\r\n </h3>\r\n \r\n <!-- error icon -->\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24\" viewBox=\"0 0 24 24\" width=\"24\">\r\n <path d=\"M0 0h24v24H0z\" fill=\"none\"/>\r\n <path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\" fill=\"#ff0000\" />\r\n </svg>\r\n \r\n</div>\r\n\r\n<div class=\"textContainer\">\r\n <p *ngIf=\"data.texts.length > 1\" [innerHTML]=\"data.texts[1]\">\r\n </p>\r\n</div>\r\n\r\n<button mat-raised-button color=\"warn\"\r\n (click)=\"closeDialog(0)\">\r\n \r\n {{data.options[0]}}\r\n \r\n</button>", styles: [":host{position:relative;min-height:300px}.titleAndIconContainer{display:flex;flex-direction:row;align-items:center;justify-content:space-between}svg{height:10vh;width:auto}.textContainer{margin-top:5px;max-height:70vh;overflow:auto}p{-webkit-user-select:text;user-select:text}h3{margin-top:0;margin-bottom:25px;width:82%}button{width:100%;margin-top:5px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }] }); }
463
+ }
464
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DialogErrorComponent, decorators: [{
465
+ type: Component,
466
+ args: [{ selector: 'tg-dialog-error', imports: [CommonModule, MatButtonModule], providers: [], template: "<div class=\"titleAndIconContainer\">\r\n <h3>\r\n {{data.texts[0]}}\r\n </h3>\r\n \r\n <!-- error icon -->\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24\" viewBox=\"0 0 24 24\" width=\"24\">\r\n <path d=\"M0 0h24v24H0z\" fill=\"none\"/>\r\n <path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\" fill=\"#ff0000\" />\r\n </svg>\r\n \r\n</div>\r\n\r\n<div class=\"textContainer\">\r\n <p *ngIf=\"data.texts.length > 1\" [innerHTML]=\"data.texts[1]\">\r\n </p>\r\n</div>\r\n\r\n<button mat-raised-button color=\"warn\"\r\n (click)=\"closeDialog(0)\">\r\n \r\n {{data.options[0]}}\r\n \r\n</button>", styles: [":host{position:relative;min-height:300px}.titleAndIconContainer{display:flex;flex-direction:row;align-items:center;justify-content:space-between}svg{height:10vh;width:auto}.textContainer{margin-top:5px;max-height:70vh;overflow:auto}p{-webkit-user-select:text;user-select:text}h3{margin-top:0;margin-bottom:25px;width:82%}button{width:100%;margin-top:5px}\n"] }]
467
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1.MatDialogRef }, { type: undefined, decorators: [{
468
+ type: Inject,
469
+ args: [MAT_DIALOG_DATA]
470
+ }] }] });
471
+
472
+ /**
473
+ * TurboGUI is A library that helps with the most common and generic UI elements and functionalities
474
+ *
475
+ * Website : -> http://www.turbogui.org
476
+ * License : -> Licensed under the Apache License, Version 2.0. You may not use this file except in compliance with the License.
477
+ * License Url : -> http://www.apache.org/licenses/LICENSE-2.0
478
+ * CopyRight : -> Copyright 2018 Edertone Advanded Solutions. https://www.edertone.com
479
+ */
480
+ /**
481
+ * Fade animations
482
+ */
483
+ class FadeAnimationClass {
532
484
  /**
533
- * Checks if the specified locale is currently loaded for the currently defined bundles and paths.
534
- *
535
- * @param locale A locale to check. For example 'en_US'
485
+ * Get a custom trigger to create fade animations when components are added or removed from the application
536
486
  *
537
- * @return True if the locale is currently loaded on the class, false if not.
487
+ * @param triggerName The name for the trigger we want to create
488
+ * @param enter The time and easing that we want to use for the enter state
489
+ * @param leave The time and easing that we want to use for the leave state
538
490
  */
539
- isLocaleLoaded(locale) {
540
- this._validateLocaleString(locale);
541
- return this._locales.includes(locale);
491
+ static getTrigger(triggerName, enter = '1s ease', leave = '300ms ease') {
492
+ return trigger(triggerName, [
493
+ transition('void => *', [style({ opacity: 0 }), animate(enter, style({ opacity: 1 }))]),
494
+ transition('* => void', [animate(leave, style({ opacity: 0 }))])
495
+ ]);
542
496
  }
543
- /**
544
- * Aux method to validate that a locale string is correctly formatted
545
- *
546
- * @param string $locale A locale string
547
- */
548
- _validateLocaleString(locale) {
549
- if (!/^[a-z]{2}_[A-Z]{2}$/.test(locale)) {
550
- throw new Error('locale must be a valid xx_XX value');
551
- }
497
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: FadeAnimationClass, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
498
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: FadeAnimationClass }); }
499
+ }
500
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: FadeAnimationClass, decorators: [{
501
+ type: Injectable
502
+ }] });
503
+
504
+ /**
505
+ * TurboGUI is A library that helps with the most common and generic UI elements and functionalities
506
+ *
507
+ * Website : -> http://www.turbogui.org
508
+ * License : -> Licensed under the Apache License, Version 2.0. You may not use this file except in compliance with the License.
509
+ * License Url : -> http://www.apache.org/licenses/LICENSE-2.0
510
+ * CopyRight : -> Copyright 2018 Edertone Advanded Solutions. https://www.edertone.com
511
+ */
512
+ /**
513
+ * This component is used by turboGUI angular library to show the busy state to the user.
514
+ * It is used to block all the user input and progressively shows a busy cursor to notify that the application
515
+ * is waiting for something.
516
+ *
517
+ * We can (should) override this component with our own one to adapt its visual appearance to our application.
518
+ * We can then set dialogService.busyStateComponentClass to our component class at the application start to to
519
+ * override the default one.
520
+ */
521
+ class BusyStateBaseComponent {
522
+ constructor() {
523
+ /**
524
+ * This is used to attach the fade animation directly to this component so it fades in and out when created and removed from the app
525
+ */
526
+ this.busyStateBaseFade = true;
552
527
  }
553
- /**
554
- * Checks if the specified 2 digit language is currently loaded for the currently defined bundles and paths.
555
- *
556
- * @param language A language to check. For example 'en'
557
- *
558
- * @return True if the language is currently loaded on the class, false if not.
559
- */
560
- isLanguageLoaded(language) {
561
- this._validateLanguageString(language);
562
- return this._languages.includes(language);
528
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: BusyStateBaseComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
529
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.5", type: BusyStateBaseComponent, isStandalone: true, selector: "tg-busy-state-base", host: { properties: { "@busyStateBaseFade": "this.busyStateBaseFade" } }, providers: [], ngImport: i0, template: "<div class=\"darkBg\">\r\n\r\n</div>\r\n\r\n<svg width=\"38\" height=\"38\" viewBox=\"0 0 38 38\" xmlns=\"http://www.w3.org/2000/svg\" stroke=\"#fff\">\r\n <g fill=\"none\" fill-rule=\"evenodd\">\r\n <g transform=\"translate(1 1)\" stroke-width=\"1\">\r\n <circle stroke-opacity=\".2\" cx=\"18\" cy=\"18\" r=\"18\"/>\r\n <path d=\"M36 18c0-9.94-8.06-18-18-18\">\r\n <animateTransform\r\n attributeName=\"transform\"\r\n type=\"rotate\"\r\n from=\"0 18 18\"\r\n to=\"360 18 18\"\r\n dur=\"1s\"\r\n repeatCount=\"indefinite\"/>\r\n </path>\r\n </g>\r\n </g>\r\n</svg>\r\n", styles: [":host{position:fixed;inset:0;display:flex;justify-content:center;align-items:center;z-index:999999999}.darkBg{position:absolute;cursor:progress;width:100%;height:100%;display:flex;justify-content:center;align-items:center;background-color:#00000096;animation-name:bgfadein;animation-duration:30s;animation-timing-function:ease}svg{z-index:5000;width:40%;height:40%;max-width:130px;max-height:130px;animation-name:loadingfadein;animation-duration:3s;animation-timing-function:ease-in}@keyframes loadingfadein{0%{opacity:0}25%{opacity:0}to{opacity:1}}@keyframes bgfadein{0%{opacity:0}4%{opacity:0}14%{opacity:.2}35%{opacity:.5}to{opacity:1}}\n"], animations: [FadeAnimationClass.getTrigger('busyStateBaseFade', '1s ease', '400ms ease')] }); }
530
+ }
531
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: BusyStateBaseComponent, decorators: [{
532
+ type: Component,
533
+ args: [{ selector: 'tg-busy-state-base', imports: [], providers: [], animations: [FadeAnimationClass.getTrigger('busyStateBaseFade', '1s ease', '400ms ease')], template: "<div class=\"darkBg\">\r\n\r\n</div>\r\n\r\n<svg width=\"38\" height=\"38\" viewBox=\"0 0 38 38\" xmlns=\"http://www.w3.org/2000/svg\" stroke=\"#fff\">\r\n <g fill=\"none\" fill-rule=\"evenodd\">\r\n <g transform=\"translate(1 1)\" stroke-width=\"1\">\r\n <circle stroke-opacity=\".2\" cx=\"18\" cy=\"18\" r=\"18\"/>\r\n <path d=\"M36 18c0-9.94-8.06-18-18-18\">\r\n <animateTransform\r\n attributeName=\"transform\"\r\n type=\"rotate\"\r\n from=\"0 18 18\"\r\n to=\"360 18 18\"\r\n dur=\"1s\"\r\n repeatCount=\"indefinite\"/>\r\n </path>\r\n </g>\r\n </g>\r\n</svg>\r\n", styles: [":host{position:fixed;inset:0;display:flex;justify-content:center;align-items:center;z-index:999999999}.darkBg{position:absolute;cursor:progress;width:100%;height:100%;display:flex;justify-content:center;align-items:center;background-color:#00000096;animation-name:bgfadein;animation-duration:30s;animation-timing-function:ease}svg{z-index:5000;width:40%;height:40%;max-width:130px;max-height:130px;animation-name:loadingfadein;animation-duration:3s;animation-timing-function:ease-in}@keyframes loadingfadein{0%{opacity:0}25%{opacity:0}to{opacity:1}}@keyframes bgfadein{0%{opacity:0}4%{opacity:0}14%{opacity:.2}35%{opacity:.5}to{opacity:1}}\n"] }]
534
+ }], propDecorators: { busyStateBaseFade: [{
535
+ type: HostBinding,
536
+ args: ['@busyStateBaseFade']
537
+ }] } });
538
+
539
+ /**
540
+ * TurboGUI is A library that helps with the most common and generic UI elements and functionalities
541
+ *
542
+ * Website : -> http://www.turbogui.org
543
+ * License : -> Licensed under the Apache License, Version 2.0. You may not use this file except in compliance with the License.
544
+ * License Url : -> http://www.apache.org/licenses/LICENSE-2.0
545
+ * CopyRight : -> Copyright 2018 Edertone Advanded Solutions. https://www.edertone.com
546
+ */
547
+ /**
548
+ * A dialog component with a calendar that allows us to select a single date value
549
+ */
550
+ class DialogDateSelectionComponent extends DialogBaseComponent {
551
+ static { this.DIALOG_CLASS_NAME = 'DialogDateSelectionComponent'; }
552
+ constructor(elementRef, dialogRef, data) {
553
+ super(elementRef, dialogRef);
554
+ this.elementRef = elementRef;
555
+ this.dialogRef = dialogRef;
556
+ this.data = data;
563
557
  }
564
- /**
565
- * Aux method to validate that a language string is correctly formatted
566
- *
567
- * @param language A 2 digit language string
568
- */
569
- _validateLanguageString(language) {
570
- if (!/^[a-z]{2}$/.test(language)) {
571
- throw new Error('language must be a valid 2 digit value');
572
- }
558
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DialogDateSelectionComponent, deps: [{ token: i0.ElementRef }, { token: i1.MatDialogRef }, { token: MAT_DIALOG_DATA }], target: i0.ɵɵFactoryTarget.Component }); }
559
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.5", type: DialogDateSelectionComponent, isStandalone: true, selector: "tg-dialog-date-selection", providers: [], usesInheritance: true, ngImport: i0, template: "<h2>{{data.texts[0]}}</h2>\r\n\r\n<mat-calendar #calendar\r\n (selectedChange)=\"closeDialog(0, $event)\">\r\n</mat-calendar>", styles: [":host{position:relative;min-height:300px}h1{margin-top:0;margin-bottom:25px;width:82%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i2$1.MatCalendar, selector: "mat-calendar", inputs: ["headerComponent", "startAt", "startView", "selected", "minDate", "maxDate", "dateFilter", "dateClass", "comparisonStart", "comparisonEnd", "startDateAccessibleName", "endDateAccessibleName"], outputs: ["selectedChange", "yearSelected", "monthSelected", "viewChanged", "_userSelection", "_userDragDrop"], exportAs: ["matCalendar"] }, { kind: "ngmodule", type: MatNativeDateModule }] }); }
560
+ }
561
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DialogDateSelectionComponent, decorators: [{
562
+ type: Component,
563
+ args: [{ selector: 'tg-dialog-date-selection', imports: [CommonModule, MatDatepickerModule, MatNativeDateModule], providers: [], template: "<h2>{{data.texts[0]}}</h2>\r\n\r\n<mat-calendar #calendar\r\n (selectedChange)=\"closeDialog(0, $event)\">\r\n</mat-calendar>", styles: [":host{position:relative;min-height:300px}h1{margin-top:0;margin-bottom:25px;width:82%}\n"] }]
564
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1.MatDialogRef }, { type: undefined, decorators: [{
565
+ type: Inject,
566
+ args: [MAT_DIALOG_DATA]
567
+ }] }] });
568
+
569
+ /**
570
+ * TurboGUI is A library that helps with the most common and generic UI elements and functionalities
571
+ *
572
+ * Website : -> http://www.turbogui.org
573
+ * License : -> Licensed under the Apache License, Version 2.0. You may not use this file except in compliance with the License.
574
+ * License Url : -> http://www.apache.org/licenses/LICENSE-2.0
575
+ * CopyRight : -> Copyright 2018 Edertone Advanded Solutions. https://www.edertone.com
576
+ */
577
+ /**
578
+ * Manages the application modal and non modal floating elements
579
+ */
580
+ class DialogService extends SingletoneStrictClass {
581
+ constructor(rendererFactory, matSnackBar, matDialog, injector, applicationRef, componentFactoryResolver) {
582
+ super(DialogService);
583
+ this.matSnackBar = matSnackBar;
584
+ this.matDialog = matDialog;
585
+ this.injector = injector;
586
+ this.applicationRef = applicationRef;
587
+ this.componentFactoryResolver = componentFactoryResolver;
588
+ /**
589
+ * Used to modify the busy state component that is shown by default by the addModalBusyState() method.
590
+ *
591
+ * @see this.addModalBusyState()
592
+ */
593
+ this.customBusyStateComponentClass = BusyStateBaseComponent;
594
+ /**
595
+ * Check public getter for docs
596
+ */
597
+ this._isEnabled = true;
598
+ /**
599
+ * Tells if the main application is currently showing a busy state that blocks all user interaction
600
+ */
601
+ this._isShowingBusyState = false;
602
+ /**
603
+ * A reference to the modal busy state component that is initialized only the first time it is called.
604
+ *
605
+ * (To append the busy state dynamically, we use the Portal concept from the angular material library)
606
+ */
607
+ this._componentPortal = null;
608
+ /**
609
+ * A reference to the modal busy state container where the component will be added
610
+ */
611
+ this._modalBusyStateHost = null;
612
+ /**
613
+ * Tells if the manager is currently showing a snackbar element or not
614
+ */
615
+ this._isShowingSnackBar = false;
616
+ /**
617
+ * Contains a list of the dialogs that are currently visible to the user.
618
+ * Each item in this list is a hash that is computed when dialog is created to uniquely identify it.
619
+ *
620
+ * Empty list means no dialogs are currently visible
621
+ */
622
+ this._activeDialogs = [];
623
+ /**
624
+ * Contains a list with all the MatDialog instances that are currently visible to the user.
625
+ * The list uses the same order as the list of _activeDialogs hash values
626
+ */
627
+ this._activeDialogInstances = [];
628
+ /**
629
+ * Method that is used to delete the window beforeunload event listener once not used anymore
630
+ */
631
+ this._windowBeforeUnloadUnListen = null;
632
+ /**
633
+ * Method that is used to delete the document keydown event listener once not used anymore
634
+ */
635
+ this._documentKeydownUnlisten = null;
636
+ /**
637
+ * Method that is used to delete the document mousedown event listener once not used anymore
638
+ */
639
+ this._documentMousedownUnlisten = null;
640
+ /**
641
+ * Method that is used to delete the document pointerdown event listener once not used anymore
642
+ */
643
+ this._documentPointerdownUnlisten = null;
644
+ this._renderer = rendererFactory.createRenderer(null, null);
573
645
  }
574
646
  /**
575
- * Get the translation to the current primary locale for the given key, library and bundle
576
- *
577
- * @param string key The key we want to read from the specified resource bundle
578
- * @param string bundlePath A string with the format 'library_name/bundle_name' that is used to locate the bundle were the key to translate is found
579
- * @param array replaceWildcards A list of values that will replace wildcards that may be found on the translated text. Each wildcard
580
- * will be replaced with the element whose index on replaceWildcards matches it. Check the documentation for this.wildCardsFormat
581
- * property to know more about how to setup wildcards on your translations.
582
- *
583
- * @see setWildCardsFormat()
647
+ * Tells if this dialog service is enabled or not. If dialog service is disabled, none of it's features will work
648
+ * This is used to block all dialog features normally when shutting down the application
584
649
  *
585
- * @return The translated text
650
+ * Use with caution. When this is set to false, dialog service stops working.
586
651
  */
587
- t(key, bundlePath, replaceWildcards = []) {
588
- this._validateInitialized();
589
- // Create a cache key to improve performance when requesting the same key translation several times
590
- const cacheKey = `${this._cacheHashBaseString}${key}${bundlePath}${replaceWildcards.join('')}`;
591
- if (!this._keyValuesCache[cacheKey]) {
592
- this._forceNonEmptyString(key, '', 'key must be non empty string');
593
- this._forceNonEmptyString(bundlePath, '', 'bundlePath must be non empty string');
594
- const [library, bundle] = bundlePath.split('/');
595
- this._forceNonEmptyString(library, '', 'no library specified on bundlePath');
596
- this._forceNonEmptyString(bundle, '', 'no bundle specified on bundlePath');
597
- const replacementsCount = replaceWildcards.length;
598
- // Loop all the locales to find the first one with a value for the specified key
599
- for (const locale of this._locales) {
600
- if (this._loadedTranslations[library]?.[bundle]?.[locale]?.[key]) {
601
- let result = this._loadedTranslations[library][bundle][locale][key];
602
- // Replace all wildcards on the text with the specified replacements if any
603
- for (let i = 0; i < replacementsCount; i++) {
604
- result = this._replace(result, this._replace(this._wildCardsFormat, 'N', i.toString()), replaceWildcards[i]);
605
- }
606
- this._keyValuesCache[cacheKey] = result;
607
- return result;
608
- }
609
- }
610
- // Check if an exception needs to be thrown if the specified key is not found on this bundle
611
- if (this._missingKeyFormat.includes('$exception')) {
612
- throw new Error(`key <${key}> not found on ${bundlePath}`);
613
- }
614
- this._keyValuesCache[cacheKey] = this._replace(this._missingKeyFormat, '$key', key);
652
+ set isEnabled(v) {
653
+ if (v === this._isEnabled) {
654
+ return;
615
655
  }
616
- return this._keyValuesCache[cacheKey];
617
- }
618
- /**
619
- * Get the translation for the given key and bundle as a string with all words first character capitalized
620
- * and all the rest of the word with lower case
621
- *
622
- * @see t()
623
- *
624
- * @returns The localized and case formatted text
625
- */
626
- tStartCase(key, bundlePath, replaceWildcards = []) {
627
- return this.t(key, bundlePath, replaceWildcards).split(' ')
628
- .map((word) => word ? word[0].toUpperCase() + word.slice(1).toLowerCase() : '').join(' ');
629
- }
630
- /**
631
- * Get the translation for the given key and bundle as an all upper case string
632
- *
633
- * @see t()
634
- *
635
- * @returns The localized and case formatted text
636
- */
637
- tAllUpperCase(key, bundlePath, replaceWildcards = []) {
638
- return this.t(key, bundlePath, replaceWildcards).toUpperCase();
656
+ this._isEnabled = v;
639
657
  }
640
658
  /**
641
- * Get the translation for the given key and bundle as an all lower case string
642
- *
643
- * @see t()
659
+ * Enables a warning that will be shown to the user when he/she tries to close the application.
660
+ * This warning will prompt the user to continue with the exit process or cancel it.
661
+ * The specific texts of this message are a generic ones and cannot be changed.
644
662
  *
645
- * @returns The localized and case formatted text
663
+ * IMPORTANT: This method must be called after the main application has been initialized in order to work,
664
+ * otherwise it will do nothing.
646
665
  */
647
- tAllLowerCase(key, bundlePath, replaceWildcards = []) {
648
- return this.t(key, bundlePath, replaceWildcards).toLowerCase();
666
+ addCloseApplicationWarning() {
667
+ if (this._windowBeforeUnloadUnListen === null) {
668
+ this._windowBeforeUnloadUnListen = this._renderer.listen('window', 'beforeunload', (event) => event.returnValue = true);
669
+ }
649
670
  }
650
671
  /**
651
- * Get the translation for the given key and bundle as a string with the first character as Upper case
652
- * and all the rest as lower case
653
- *
654
- * @see t()
655
- *
656
- * @returns The localized and case formatted text
672
+ * Remove the close application warning message if previously assigned
657
673
  */
658
- tFirstUpperRestLower(key, bundlePath, replaceWildcards = []) {
659
- const string = this.t(key, bundlePath, replaceWildcards);
660
- return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
674
+ removeCloseApplicationWarning() {
675
+ if (this._windowBeforeUnloadUnListen !== null) {
676
+ this._windowBeforeUnloadUnListen();
677
+ this._windowBeforeUnloadUnListen = null;
678
+ }
661
679
  }
662
680
  /**
663
- * A list of strings containing the locales that are used by this class to translate the given keys, sorted by preference.
664
- * Each string is formatted as a standard locale code with language and country joined by an underscore, like: en_US, fr_FR
681
+ * Change the application visual appearance so the user perceives that the application is
682
+ * currently busy. While modal busy state is enabled, no user input is possible neither via
683
+ * keyboard, mouse or touch. Use this state when performing server requests or operations that
684
+ * must block the user interaction with the application. To allow user interaction again, you must
685
+ * call removeModalBusyState()
665
686
  *
666
- * When a key and bundle are requested for translation, the class will check on the first language of this
667
- * list for a translated text. If missing, the next one will be used, and so. This list is constructed after initialize
668
- * methods is called.
687
+ * Notice: We can modify the busy state visual component that is shown by this method. To do it, we must
688
+ * set this.customBusyStateComponentClass property with our own custom busy state component class. (We can do it at
689
+ * our main application component constructor for example). Our custom component must extend the
690
+ * BusyStateBaseComponent one to add its own visual appearance.
669
691
  *
670
- * @example: After loading the following list of locales ['en_US', 'es_ES', 'fr_FR'] if we call LocalesManager.t('HELLO', 'lib1/greetings')
671
- * the localization manager will try to locate the en_US value for the HELLO tag on the greetings bundle for the library lib1.
672
- * If the tag is not found for the specified locale and bundle, the same search will be performed for the es_ES locale, and so, till a
673
- * value is found or no more locales are defined.
692
+ * @see this.customBusyStateComponentClass
674
693
  */
675
- getLocales() {
676
- return this._locales;
694
+ addModalBusyState() {
695
+ if (!this._isEnabled || this._isShowingBusyState) {
696
+ return;
697
+ }
698
+ this._disableUserInteraction();
699
+ // Dynamically create the busy state component reference if this is the first time
700
+ if (this._componentPortal === null) {
701
+ this._componentPortal = new ComponentPortal(this.customBusyStateComponentClass);
702
+ // Create a PortalHost with document.body as its anchor element
703
+ this._modalBusyStateHost = new DomPortalOutlet(document.body, this.componentFactoryResolver, this.applicationRef, this.injector);
704
+ }
705
+ this._modalBusyStateHost.attach(this._componentPortal);
706
+ this._isShowingBusyState = true;
677
707
  }
678
708
  /**
679
- * A list of strings containing the languages that are used by this class to translate the given keys, sorted by preference.
680
- * Each string is formatted as a 2 digit language code, like: en, fr
681
- *
682
- * This list is the same as the locales() one, but containing only the language part of each locale (the first two digits)
683
- *
684
- * @see getLocales()
709
+ * Tells if the application is currently locked in a modal busy state (caused by an addModalBusyState() call)
685
710
  */
686
- getLanguages() {
687
- return this._languages;
711
+ get isShowingBusyState() {
712
+ return this._isShowingBusyState;
688
713
  }
689
714
  /**
690
- * Get the first locale from the list of loaded locales, which is the currently used to search for translated texts.
691
- *
692
- * @returns The locale that is defined as the primary one. For example: en_US, es_ES, ..
715
+ * Cancel the application busy state and restore it back to normal so user interaction is allowed again
693
716
  */
694
- getPrimaryLocale() {
695
- this._validateInitialized();
696
- return this._locales[0];
717
+ removeModalBusyState() {
718
+ if (!this._isEnabled || !this._isShowingBusyState) {
719
+ return;
720
+ }
721
+ if (this._componentPortal !== null) {
722
+ this._modalBusyStateHost.detach();
723
+ }
724
+ this._enableUserInteraction();
725
+ this._isShowingBusyState = false;
697
726
  }
698
727
  /**
699
- * Get the first language from the list of loaded locales, which is the currently used to search for translated texts.
700
- *
701
- * @returns The 2 digit language code that is defined as the primary one. For example: en, es, ..
728
+ * TODO - adapt from TS version
702
729
  */
703
- getPrimaryLanguage() {
704
- this._validateInitialized();
705
- return this._languages[0];
730
+ addToolTip() {
731
+ // TODO - adapt from TS version
706
732
  }
707
733
  /**
708
- * Define the locale that will be placed at the front of the currently loaded locales list (moving all the others one position to the right).
734
+ * Show a non modal snackbar notification to the user (Only one snack-bar can ever be opened at the same time).
709
735
  *
710
- * This will be the first locale to use when trying to get a translation.
736
+ * Snackbars inform users of a process that an app has performed or will perform. They appear temporarily, towards the bottom or top of the screen.
737
+ * They shouldn’t interrupt the user experience, and they don’t require user input to disappear.
711
738
  *
712
- * @param locale A currently loaded locale that will be moved to the first position of the loaded locales list. If the specified locale
713
- * is not currently loaded, an exception will happen.
739
+ * @param config A MatSnackBarConfig instance with the customizations we want for this snackbar
740
+ * @param message The message to show on the snackbar
741
+ * @param action If not empty, the text to place on the snackbar confirmation button
742
+ * @param actionCallback A method to execute once the user clicks into the action button.
714
743
  *
715
- * @returns void
744
+ * @return A promise that will be resolved once the snackbar is closed.
716
745
  */
717
- setPrimaryLocale(locale) {
718
- this._validateInitialized();
719
- if (!this.isLocaleLoaded(locale)) {
720
- throw new Error(locale + ' not loaded');
746
+ addSnackBar(config, message, action = '') {
747
+ if (!this._isEnabled) {
748
+ return Promise.reject(new Error('Dialog service is disabled'));
721
749
  }
722
- let result = [locale];
723
- for (let l of this._locales) {
724
- if (l !== locale) {
725
- result.push(l);
726
- }
750
+ if (this._isShowingSnackBar) {
751
+ throw new Error('Trying to show a snackbar while another one is still visible');
727
752
  }
728
- this._locales = result;
729
- this._languages = this._locales.map((l) => l.substring(0, 2));
730
- this._cacheHashBaseString = this._wildCardsFormat + this._missingKeyFormat + this._locales[0];
753
+ this._isShowingSnackBar = true;
754
+ return new Promise((resolve) => {
755
+ const snackBarRef = this.matSnackBar.open(message, action === '' ? undefined : action, config);
756
+ // Handle action button click
757
+ snackBarRef.onAction().subscribe(() => {
758
+ this._isShowingSnackBar = false;
759
+ resolve(true);
760
+ });
761
+ // Handle dismiss
762
+ snackBarRef.afterDismissed().subscribe(() => {
763
+ this._isShowingSnackBar = false;
764
+ resolve(false);
765
+ });
766
+ });
731
767
  }
732
768
  /**
733
- * Moves the specified locales to the beginning of the locales list. This also alters the translation priority by setting the first
734
- * provided locale as the most prioritary, the second as the next one and so.
735
- *
736
- * This method basically works exactly the same way as setPrimaryLocale but letting us add many locales at once.
737
- *
738
- * @see setPrimaryLocale()
739
- *
740
- * @param locales A list of locales to be moved to the beginning of the translation priority. First locales item will be the prefered
741
- * locale for translation, second will be the next one in case some key is not translated for the first one and so. If any of the
742
- * specified locales is not currently loaded, an exception will happen.
769
+ * Tells if the application is currently showing a snackbar or not
770
+ */
771
+ get isShowingSnackBar() {
772
+ return this._isShowingSnackBar;
773
+ }
774
+ /**
775
+ * Force the removal of the snack bar dialog if it exists.
743
776
  *
744
- * @returns void
777
+ * If no snackbar is currently visible, this method will do nothing
745
778
  */
746
- setPrimaryLocales(locales) {
747
- if (!Array.isArray(locales) ||
748
- (new Set(locales).size !== locales.length) ||
749
- locales.length === 0) {
750
- throw new Error('locales must be non empty string array with no duplicate elements');
751
- }
752
- for (let i = locales.length - 1; i >= 0; i--) {
753
- this.setPrimaryLocale(locales[i]);
779
+ removeSnackBar() {
780
+ if (!this._isEnabled || !this._isShowingSnackBar) {
781
+ return;
754
782
  }
783
+ this.matSnackBar.dismiss();
784
+ this._isShowingSnackBar = false;
755
785
  }
756
786
  /**
757
- * Define the 2 digit language that will be placed at the front of the currently loaded locales list (moving all the others one position to the right).
758
- *
759
- * This will be the first language to use when trying to get a translation.
787
+ * Show a dialog with one or more options that can be used to close it. We can use any of the predefined dialog types that are bundled with
788
+ * this library or extend DialogBaseComponent to create our own custom ones.
760
789
  *
761
- * @param language A 2 digit language code that matches with any of the currently loaded locales, which will
762
- * be moved to the first position of the loaded locales list. If the specified language does not match with
763
- * a locale that is currently loaded, an exception will happen.
790
+ * @param dialogComponentClass A class for a component that extends DialogBaseComponent, which will be the dialog that is shown to the user.
791
+ * @param properties An object containing the different visual and textual options that this dialog allows:
792
+ * - id: The html unique identifier that the dialog will have once created. If not specified, no id will be explicitly set
793
+ * - width: 50% by default. Specify the css value for the default dialog width. As the dialog is responsive, the value will be automatically
794
+ * reduced if the available screen is not enough, and will reach the desired value otherwise. We can set any css unit like pixels,
795
+ * %, vh, vw, or any other. For example: '400px', '50%', etc.
796
+ * - maxWidth: Defines the maximum width that the dialog will have regarding the viewport. We can specify it in % or vw, just like is done in
797
+ * css. By default it is defined as 96vw, which will fit 96% of the viewport on small devices
798
+ * - height: TODO docs
799
+ * - maxHeight: TODO docs
800
+ * - modal: True (default) if selecting an option is mandatory to close the dialog, false if the dialog can be closed
801
+ * by the user clicking outside it
802
+ * - texts: A list with strings containing the dialog texts, sorted by importance. When dialog has a title, this should
803
+ * be placed first, subtitle second and so (Each dialog may accept a different custom number of texts).
804
+ * - options: A list of strings that will be used as button captions for each one of the accepted dialog options
805
+ * - data: An object that we can use to pass any extra data that we want to the dialog
806
+ * - viewContainerRef: This is important if we want to propagate providers from a parent component to this dialog. We must specify
807
+ * this reference to make sure the same services injected on the parent are available too at the child dialog
764
808
  *
765
- * @returns void
809
+ * @return A promise that will be resolved once the dialog is closed.
810
+ * The promise will receive a selection object with two properties which will correspond to the index and value from the options
811
+ * array that's selected by the user. If no option selected, index will be -1 and value null
766
812
  */
767
- setPrimaryLanguage(language) {
768
- for (let locale of this._locales) {
769
- if (locale.substring(0, 2) === language) {
770
- this.setPrimaryLocale(locale);
771
- return;
813
+ addDialog(dialogComponentClass, properties) {
814
+ if (!this._isEnabled) {
815
+ return Promise.reject(new Error('Dialog service is disabled'));
816
+ }
817
+ return new Promise((resolve) => {
818
+ // Set the default values for non specified properties
819
+ properties.modal = properties.modal ?? true;
820
+ properties.texts = properties.texts ?? [];
821
+ properties.options = properties.options ?? [];
822
+ properties.data = properties.data ?? {};
823
+ // Generate a string to uniquely identify this dialog on the list of active dialogs
824
+ // A dialog is considered as unique if the dialog id and texts are exactly the same. We do not take options into consideration
825
+ // as there may be dialogs with a big amount of options available.
826
+ let className = dialogComponentClass.DIALOG_CLASS_NAME;
827
+ if (className === '') {
828
+ throw new Error(`The static property DIALOG_CLASS_NAME is not defined or is empty for this dialog component (${dialogComponentClass})`);
772
829
  }
773
- }
774
- throw new Error(language + ' not loaded');
830
+ const dialogHash = className + properties.texts.join('');
831
+ // identical dialogs won't be allowed at the same time
832
+ if (this._activeDialogs.includes(dialogHash)) {
833
+ return resolve({ index: -1 });
834
+ }
835
+ const dialogRef = this.matDialog.open(dialogComponentClass, {
836
+ width: properties.width ?? "50%",
837
+ maxWidth: properties.maxWidth ?? "96vw",
838
+ disableClose: properties.modal,
839
+ autoFocus: false,
840
+ closeOnNavigation: !properties.modal,
841
+ viewContainerRef: properties.viewContainerRef,
842
+ data: { texts: properties.texts, options: properties.options, data: properties.data }
843
+ });
844
+ // Assign the dialog ID only if specifically set on properties
845
+ if (properties.id && properties.id !== undefined) {
846
+ dialogRef.id = properties.id;
847
+ }
848
+ this._activeDialogs.push(dialogHash);
849
+ this._activeDialogInstances.push(dialogRef);
850
+ dialogRef.beforeClosed().subscribe((selection) => {
851
+ this._activeDialogs = ArrayUtils.removeElement(this._activeDialogs, dialogHash);
852
+ this._activeDialogInstances = ArrayUtils.removeElement(this._activeDialogInstances, dialogRef);
853
+ if (!properties.modal && selection === undefined) {
854
+ selection = { index: -1 };
855
+ }
856
+ else if (!NumericUtils.isInteger(selection.index)) {
857
+ throw new Error(`closeDialog() expects index to be an integer`);
858
+ }
859
+ if (selection.index >= 0 && selection.value === null) {
860
+ selection.value = properties.options[selection.index];
861
+ }
862
+ resolve(selection);
863
+ });
864
+ });
775
865
  }
776
866
  /**
777
- * Moves the locales that match the specified languages to the beginning of the locales list.
778
- * Works the same as setPrimaryLocales() but with a list of the 2 digit language codes that match the respective locales.
779
- *
780
- * @see setPrimaryLocale()
781
- * @see setPrimaryLanguage()
867
+ * Show a dialog with a calendar to let the user pick a date.
782
868
  *
783
- * @param languages A list of 2 digit language codes to be moved to the beginning of the translation priority. If any of the
784
- * specified languages does not match with a locale that is currently loaded, an exception will happen.
869
+ * @param properties An object containing the different visual and textual options that this dialog allows:
870
+ * - id: The html unique identifier that the dialog will have once created. If not specified, no id will be explicitly set
871
+ * - width: Specify the css value for the default dialog width. As the dialog is responsive, the value will be automatically
872
+ * reduced if the available screen is not enough, and will reach the desired value otherwise. We can set any css unit like pixels,
873
+ * %, vh, vw, or any other. For example: '400px', '50%', etc.
874
+ * - maxWidth: Defines the maximum width that the dialog will have regarding the viewport. We can specify it in % or vw, just like is done in
875
+ * css. By default it is defined as 96vw, which will fit 96% of the viewport on small devices
876
+ * - height: TODO docs
877
+ * - maxHeight: TODO docs
878
+ * - modal: True (default) if selecting an option is mandatory to close the dialog, false if the dialog can be closed
879
+ * by the user clicking outside it
880
+ * - title: An optional dialog title
881
+ * - viewContainerRef: This is important to propagate providers from a parent component to this dialog. We must specify
882
+ * this reference to make sure the same services injected on the parent are available too at the child dialog
785
883
  *
786
- * @returns void
884
+ * @returns A Promise that resolves to a Date() object selected by the user or null if no selection was made
787
885
  */
788
- setPrimaryLanguages(languages) {
789
- if (!Array.isArray(languages) ||
790
- (new Set(languages).size !== languages.length) ||
791
- languages.length === 0) {
792
- throw new Error('languages must be non empty string array with no duplicate elements');
793
- }
794
- for (let i = languages.length - 1; i >= 0; i--) {
795
- this.setPrimaryLanguage(languages[i]);
886
+ async addDateSelectionDialog(properties) {
887
+ if (!this._isEnabled) {
888
+ return null;
796
889
  }
890
+ const selection = await this.addDialog(DialogDateSelectionComponent, {
891
+ id: properties.id ?? undefined,
892
+ width: properties.width ?? "50%",
893
+ maxWidth: properties.maxWidth ?? "96vw",
894
+ height: properties.height ?? "50%",
895
+ maxHeight: properties.maxHeight ?? "92vw",
896
+ modal: properties.modal ?? false,
897
+ texts: [properties.title ?? ''],
898
+ viewContainerRef: properties.viewContainerRef
899
+ });
900
+ return selection.index === -1 ? null : selection.value;
797
901
  }
798
902
  /**
799
- * Change the loaded locales translation preference order. The same locales that are currently loaded must be passed
800
- * but with a different order to change the translation priority.
801
- *
802
- * @param locales A list with the new locales translation priority
903
+ * Force the removal of all the dialogs that are currently visible.
803
904
  *
804
- * @returns void
905
+ * If no dialogs are currently visible, this method will do nothing
805
906
  */
806
- setLocalesOrder(locales) {
807
- if (locales.length !== this._locales.length) {
808
- throw new Error('locales must contain all the currently loaded locales');
907
+ removeAllDialogs() {
908
+ if (!this._isEnabled) {
909
+ return;
809
910
  }
810
- this._validateInitialized();
811
- for (let locale of locales) {
812
- if (!this.isLocaleLoaded(locale)) {
813
- throw new Error(locale + ' not loaded');
814
- }
911
+ for (const dialogRef of this._activeDialogInstances) {
912
+ dialogRef.close({ index: -1 });
815
913
  }
816
- this._locales = locales;
817
- this._languages = this._locales.map((l) => l.substring(0, 2));
818
- this._cacheHashBaseString = this._wildCardsFormat + this._missingKeyFormat + this._locales[0];
914
+ this._activeDialogs = [];
915
+ this._activeDialogInstances = [];
819
916
  }
820
917
  /**
821
- * This is an aux method to implement the TurboCommons StringUtils replace method.
822
- * It is exactly the same as the one on the library, but we implement it here to avoid having a dependency with TurboCommons
918
+ * TODO - translate from TS version
823
919
  */
824
- _replace(string, search, replacement) {
825
- const escapedSearch = search.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
826
- return string.replace(new RegExp(escapedSearch, 'g'), replacement);
827
- }
920
+ // addSideNav(){
921
+ //
922
+ // }
828
923
  /**
829
- * This is an aux method to implement the TurboCommons StringUtils isEmpty method.
830
- * It is exactly the same as the one on the library, but we implement it here to avoid having a dependency with TurboCommons
924
+ * TODO - translate from TS version
831
925
  */
832
- _isEmpty(string) {
833
- let isString = (typeof string === 'string' || string instanceof String);
834
- // Throw exception if non string value was received
835
- if (!isString) {
836
- // Empty or null value is considered empty
837
- if (string == null || string == '') {
838
- return true;
839
- }
840
- throw new Error("value is not a string");
926
+ // get isShowingSideNav(){
927
+ //
928
+ // }
929
+ /**
930
+ * TODO - translate from TS version
931
+ */
932
+ // removeSideNav(){
933
+ //
934
+ // }
935
+ /**
936
+ * Block all the user interactions with the application (keyboard, touch, mouse, ...)
937
+ */
938
+ _disableUserInteraction() {
939
+ if (this._documentKeydownUnlisten === null) {
940
+ this._documentKeydownUnlisten = this._renderer.listen('document', 'keydown', (event) => event.preventDefault());
941
+ }
942
+ if (this._documentMousedownUnlisten === null) {
943
+ this._documentMousedownUnlisten = this._renderer.listen('document', 'mousedown', (event) => event.preventDefault());
944
+ }
945
+ if (this._documentPointerdownUnlisten === null) {
946
+ this._documentPointerdownUnlisten = this._renderer.listen('document', 'pointerdown', (event) => event.preventDefault());
841
947
  }
842
- return string.replace(/[ \n\r\t]/g, '') === '';
843
948
  }
844
949
  /**
845
- * This is an aux method to implement the TurboCommons StringUtils forceNonEmptyString method.
846
- * It is exactly the same as the one on the library, but we implement it here to avoid having a dependency with TurboCommons
950
+ * Restore the user interactions that were previously disabled with _disableUserInteraction method
847
951
  */
848
- _forceNonEmptyString(value, valueName = '', errorMessage = 'must be a non empty string') {
849
- let isString = (typeof value === 'string' || value instanceof String);
850
- if (!isString || this._isEmpty(value)) {
851
- throw new Error(valueName + ' ' + errorMessage);
952
+ _enableUserInteraction() {
953
+ if (this._documentKeydownUnlisten !== null) {
954
+ this._documentKeydownUnlisten();
955
+ this._documentKeydownUnlisten = null;
956
+ }
957
+ if (this._documentMousedownUnlisten !== null) {
958
+ this._documentMousedownUnlisten();
959
+ this._documentMousedownUnlisten = null;
960
+ }
961
+ if (this._documentPointerdownUnlisten !== null) {
962
+ this._documentPointerdownUnlisten();
963
+ this._documentMousedownUnlisten = null;
852
964
  }
853
965
  }
854
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: LocalesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
855
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: LocalesService, providedIn: 'root' }); }
966
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DialogService, deps: [{ token: i0.RendererFactory2 }, { token: i1$1.MatSnackBar }, { token: i1.MatDialog }, { token: i0.Injector }, { token: i0.ApplicationRef }, { token: i0.ComponentFactoryResolver }], target: i0.ɵɵFactoryTarget.Injectable }); }
967
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DialogService, providedIn: 'root' }); }
856
968
  }
857
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: LocalesService, decorators: [{
969
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DialogService, decorators: [{
858
970
  type: Injectable,
859
971
  args: [{
860
972
  providedIn: 'root',
861
973
  }]
862
- }], ctorParameters: () => [] });
974
+ }], ctorParameters: () => [{ type: i0.RendererFactory2 }, { type: i1$1.MatSnackBar }, { type: i1.MatDialog }, { type: i0.Injector }, { type: i0.ApplicationRef }, { type: i0.ComponentFactoryResolver }] });
863
975
 
864
976
  /**
865
977
  * TurboGUI is A library that helps with the most common and generic UI elements and functionalities
@@ -870,91 +982,62 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImpor
870
982
  * CopyRight : -> Copyright 2018 Edertone Advanded Solutions. https://www.edertone.com
871
983
  */
872
984
  /**
873
- * This is the base class for all the dialog components that can be loaded by the dialog service class
985
+ * Manages application http communications
874
986
  */
875
- class DialogBaseComponent {
876
- /*
877
- * The name of the superclass must be set into this constant as it is required by the dialog service to identify dialogs as different.
878
- *
879
- * When you extend the dialog base class, simply declare this static constant with the exact same name as your class and you're done.
880
- * If this value is not set on the extended dialog component, a runtime exception will happen when trying to show the dialog.
881
- *
882
- * The root cause of this requirement is that when apps are compiled for production, class names are minified and this causes problems
883
- * when creating a dialog hash to uniquely identify a dialog instance. Therefore, a hardcoded class name is necesary.
884
- */
885
- static { this.DIALOG_CLASS_NAME = ''; }
886
- constructor(elementRef, dialogRef) {
887
- this.elementRef = elementRef;
888
- this.dialogRef = dialogRef;
889
- }
890
- ngAfterViewInit() {
891
- // Assign the component HTML identifier if it is specifically assigned to the dialogref instance
892
- if (this.dialogRef.id !== undefined && this.dialogRef.id !== '') {
893
- this.elementRef.nativeElement.id = this.dialogRef.id;
894
- }
987
+ class HTTPService extends HTTPManager {
988
+ constructor(dialogService) {
989
+ super(true);
990
+ this.dialogService = dialogService;
895
991
  }
896
992
  /**
897
- * Method to be called by the dialogs that extend this base component when they want to close themselves.
898
- * It will perform the close of that dialog and also send an object to the addDialog() callback with the index and value
899
- * that the user may have selected.
993
+ * The same method as HTTPManager.execute but with the ability to enable several options which are specific to this service:
900
994
  *
901
- * @param index The numeric index of the user option selection. It will be specific for each dialog and it's different available options
902
- * @param value Any value that may be provided to the callback regarding the user selected option.
995
+ * - options:
996
+ * busyState: Set it to false to prevent the default behaviour of locking the UI while the request is running
997
+ * handleErrors: Set it to false to prevent the default behaviour of showing a detailed error dialog when a request fails
903
998
  *
904
- * @return void
999
+ * @see HTTPManager.execute()
905
1000
  */
906
- closeDialog(index, value = null) {
907
- this.dialogRef.close({ index: index, value: value });
908
- }
909
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DialogBaseComponent, deps: [{ token: i0.ElementRef }, { token: i1.MatDialogRef }], target: i0.ɵɵFactoryTarget.Component }); }
910
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.5", type: DialogBaseComponent, isStandalone: false, selector: "ng-component", ngImport: i0, template: '', isInline: true }); }
911
- }
912
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DialogBaseComponent, decorators: [{
913
- type: Component,
914
- args: [{
915
- template: '',
916
- standalone: false
917
- }]
918
- }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1.MatDialogRef }] });
919
-
920
- /**
921
- * TurboGUI is A library that helps with the most common and generic UI elements and functionalities
922
- *
923
- * Website : -> http://www.turbogui.org
924
- * License : -> Licensed under the Apache License, Version 2.0. You may not use this file except in compliance with the License.
925
- * License Url : -> http://www.apache.org/licenses/LICENSE-2.0
926
- * CopyRight : -> Copyright 2018 Edertone Advanded Solutions. https://www.edertone.com
927
- */
928
- /**
929
- * A dialog component with a single option button, to be used for error notifications
930
- */
931
- class DialogErrorComponent extends DialogBaseComponent {
932
- static { this.DIALOG_CLASS_NAME = 'DialogErrorComponent'; }
933
- constructor(elementRef, dialogRef, data) {
934
- super(elementRef, dialogRef);
935
- this.elementRef = elementRef;
936
- this.dialogRef = dialogRef;
937
- this.data = data;
938
- if (data.texts.length < 1) {
939
- throw new Error('DialogErrorComponent expects 2 texts: The title and optionally a description');
940
- }
941
- if (data.options.length !== 1) {
942
- throw new Error('DialogErrorComponent expects only one option');
943
- }
944
- if (data.texts.length > 1) {
945
- data.texts[1] = data.texts[1].replace(/\n/g, "<br/>");
1001
+ execute(requests, finishedCallback = null, progressCallback = null, options = {}) {
1002
+ // Set the default values for non specified properties
1003
+ options.busyState = options.busyState ?? true;
1004
+ options.handleErrors = options.handleErrors ?? true;
1005
+ if (options.busyState) {
1006
+ this.dialogService.addModalBusyState();
946
1007
  }
1008
+ super.execute(requests, (results, anyError) => {
1009
+ if (options.busyState) {
1010
+ this.dialogService.removeModalBusyState();
1011
+ }
1012
+ if (options.handleErrors && anyError) {
1013
+ for (let result of results) {
1014
+ if (result.isError) {
1015
+ let errorMsg = result.errorMsg + '\n\n' + result.response;
1016
+ if (StringUtils.isEmpty(errorMsg)) {
1017
+ errorMsg = 'Unknown error. Make sure Internet connection is available';
1018
+ }
1019
+ this.dialogService.addDialog(DialogErrorComponent, {
1020
+ width: '50vw',
1021
+ texts: ['Error: ' + result.code, errorMsg],
1022
+ options: ['Ok']
1023
+ });
1024
+ }
1025
+ }
1026
+ }
1027
+ if (finishedCallback !== null) {
1028
+ finishedCallback(results, anyError);
1029
+ }
1030
+ }, progressCallback);
947
1031
  }
948
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DialogErrorComponent, deps: [{ token: i0.ElementRef }, { token: i1.MatDialogRef }, { token: MAT_DIALOG_DATA }], target: i0.ɵɵFactoryTarget.Component }); }
949
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.5", type: DialogErrorComponent, isStandalone: true, selector: "tg-dialog-error", providers: [], usesInheritance: true, ngImport: i0, template: "<div class=\"titleAndIconContainer\">\r\n <h3>\r\n {{data.texts[0]}}\r\n </h3>\r\n \r\n <!-- error icon -->\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24\" viewBox=\"0 0 24 24\" width=\"24\">\r\n <path d=\"M0 0h24v24H0z\" fill=\"none\"/>\r\n <path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\" fill=\"#ff0000\" />\r\n </svg>\r\n \r\n</div>\r\n\r\n<div class=\"textContainer\">\r\n <p *ngIf=\"data.texts.length > 1\" [innerHTML]=\"data.texts[1]\">\r\n </p>\r\n</div>\r\n\r\n<button mat-raised-button color=\"warn\"\r\n (click)=\"closeDialog(0)\">\r\n \r\n {{data.options[0]}}\r\n \r\n</button>", styles: [":host{position:relative;min-height:300px}.titleAndIconContainer{display:flex;flex-direction:row;align-items:center;justify-content:space-between}svg{height:10vh;width:auto}.textContainer{margin-top:5px;max-height:70vh;overflow:auto}p{-webkit-user-select:text;user-select:text}h3{margin-top:0;margin-bottom:25px;width:82%}button{width:100%;margin-top:5px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }] }); }
1032
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: HTTPService, deps: [{ token: DialogService }], target: i0.ɵɵFactoryTarget.Injectable }); }
1033
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: HTTPService, providedIn: 'root' }); }
950
1034
  }
951
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DialogErrorComponent, decorators: [{
952
- type: Component,
953
- args: [{ selector: 'tg-dialog-error', imports: [CommonModule, MatButtonModule], providers: [], template: "<div class=\"titleAndIconContainer\">\r\n <h3>\r\n {{data.texts[0]}}\r\n </h3>\r\n \r\n <!-- error icon -->\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24\" viewBox=\"0 0 24 24\" width=\"24\">\r\n <path d=\"M0 0h24v24H0z\" fill=\"none\"/>\r\n <path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\" fill=\"#ff0000\" />\r\n </svg>\r\n \r\n</div>\r\n\r\n<div class=\"textContainer\">\r\n <p *ngIf=\"data.texts.length > 1\" [innerHTML]=\"data.texts[1]\">\r\n </p>\r\n</div>\r\n\r\n<button mat-raised-button color=\"warn\"\r\n (click)=\"closeDialog(0)\">\r\n \r\n {{data.options[0]}}\r\n \r\n</button>", styles: [":host{position:relative;min-height:300px}.titleAndIconContainer{display:flex;flex-direction:row;align-items:center;justify-content:space-between}svg{height:10vh;width:auto}.textContainer{margin-top:5px;max-height:70vh;overflow:auto}p{-webkit-user-select:text;user-select:text}h3{margin-top:0;margin-bottom:25px;width:82%}button{width:100%;margin-top:5px}\n"] }]
954
- }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1.MatDialogRef }, { type: undefined, decorators: [{
955
- type: Inject,
956
- args: [MAT_DIALOG_DATA]
957
- }] }] });
1035
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: HTTPService, decorators: [{
1036
+ type: Injectable,
1037
+ args: [{
1038
+ providedIn: 'root',
1039
+ }]
1040
+ }], ctorParameters: () => [{ type: DialogService }] });
958
1041
 
959
1042
  /**
960
1043
  * TurboGUI is A library that helps with the most common and generic UI elements and functionalities
@@ -965,28 +1048,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImpor
965
1048
  * CopyRight : -> Copyright 2018 Edertone Advanded Solutions. https://www.edertone.com
966
1049
  */
967
1050
  /**
968
- * Fade animations
1051
+ * Class that defines a GET http request, to be used by HttpService
969
1052
  */
970
- class FadeAnimationClass {
971
- /**
972
- * Get a custom trigger to create fade animations when components are added or removed from the application
973
- *
974
- * @param triggerName The name for the trigger we want to create
975
- * @param enter The time and easing that we want to use for the enter state
976
- * @param leave The time and easing that we want to use for the leave state
977
- */
978
- static getTrigger(triggerName, enter = '1s ease', leave = '300ms ease') {
979
- return trigger(triggerName, [
980
- transition('void => *', [style({ opacity: 0 }), animate(enter, style({ opacity: 1 }))]),
981
- transition('* => void', [animate(leave, style({ opacity: 0 }))])
982
- ]);
983
- }
984
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: FadeAnimationClass, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
985
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: FadeAnimationClass }); }
1053
+ class HTTPServiceGetRequest extends HTTPManagerGetRequest {
986
1054
  }
987
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: FadeAnimationClass, decorators: [{
988
- type: Injectable
989
- }] });
990
1055
 
991
1056
  /**
992
1057
  * TurboGUI is A library that helps with the most common and generic UI elements and functionalities
@@ -997,31 +1062,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImpor
997
1062
  * CopyRight : -> Copyright 2018 Edertone Advanded Solutions. https://www.edertone.com
998
1063
  */
999
1064
  /**
1000
- * This component is used by turboGUI angular library to show the busy state to the user.
1001
- * It is used to block all the user input and progressively shows a busy cursor to notify that the application
1002
- * is waiting for something.
1003
- *
1004
- * We can (should) override this component with our own one to adapt its visual appearance to our application.
1005
- * We can then set dialogService.busyStateComponentClass to our component class at the application start to to
1006
- * override the default one.
1065
+ * Class that defines a POST http request, to be used by HttpService
1007
1066
  */
1008
- class BusyStateBaseComponent {
1009
- constructor() {
1010
- /**
1011
- * This is used to attach the fade animation directly to this component so it fades in and out when created and removed from the app
1012
- */
1013
- this.busyStateBaseFade = true;
1014
- }
1015
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: BusyStateBaseComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1016
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.5", type: BusyStateBaseComponent, isStandalone: true, selector: "tg-busy-state-base", host: { properties: { "@busyStateBaseFade": "this.busyStateBaseFade" } }, providers: [], ngImport: i0, template: "<div class=\"darkBg\">\r\n\r\n</div>\r\n\r\n<svg width=\"38\" height=\"38\" viewBox=\"0 0 38 38\" xmlns=\"http://www.w3.org/2000/svg\" stroke=\"#fff\">\r\n <g fill=\"none\" fill-rule=\"evenodd\">\r\n <g transform=\"translate(1 1)\" stroke-width=\"1\">\r\n <circle stroke-opacity=\".2\" cx=\"18\" cy=\"18\" r=\"18\"/>\r\n <path d=\"M36 18c0-9.94-8.06-18-18-18\">\r\n <animateTransform\r\n attributeName=\"transform\"\r\n type=\"rotate\"\r\n from=\"0 18 18\"\r\n to=\"360 18 18\"\r\n dur=\"1s\"\r\n repeatCount=\"indefinite\"/>\r\n </path>\r\n </g>\r\n </g>\r\n</svg>\r\n", styles: [":host{position:fixed;inset:0;display:flex;justify-content:center;align-items:center;z-index:999999999}.darkBg{position:absolute;cursor:progress;width:100%;height:100%;display:flex;justify-content:center;align-items:center;background-color:#00000096;animation-name:bgfadein;animation-duration:30s;animation-timing-function:ease}svg{z-index:5000;width:40%;height:40%;max-width:130px;max-height:130px;animation-name:loadingfadein;animation-duration:3s;animation-timing-function:ease-in}@keyframes loadingfadein{0%{opacity:0}25%{opacity:0}to{opacity:1}}@keyframes bgfadein{0%{opacity:0}4%{opacity:0}14%{opacity:.2}35%{opacity:.5}to{opacity:1}}\n"], animations: [FadeAnimationClass.getTrigger('busyStateBaseFade', '1s ease', '400ms ease')] }); }
1067
+ class HTTPServicePostRequest extends HTTPManagerPostRequest {
1017
1068
  }
1018
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: BusyStateBaseComponent, decorators: [{
1019
- type: Component,
1020
- args: [{ selector: 'tg-busy-state-base', imports: [], providers: [], animations: [FadeAnimationClass.getTrigger('busyStateBaseFade', '1s ease', '400ms ease')], template: "<div class=\"darkBg\">\r\n\r\n</div>\r\n\r\n<svg width=\"38\" height=\"38\" viewBox=\"0 0 38 38\" xmlns=\"http://www.w3.org/2000/svg\" stroke=\"#fff\">\r\n <g fill=\"none\" fill-rule=\"evenodd\">\r\n <g transform=\"translate(1 1)\" stroke-width=\"1\">\r\n <circle stroke-opacity=\".2\" cx=\"18\" cy=\"18\" r=\"18\"/>\r\n <path d=\"M36 18c0-9.94-8.06-18-18-18\">\r\n <animateTransform\r\n attributeName=\"transform\"\r\n type=\"rotate\"\r\n from=\"0 18 18\"\r\n to=\"360 18 18\"\r\n dur=\"1s\"\r\n repeatCount=\"indefinite\"/>\r\n </path>\r\n </g>\r\n </g>\r\n</svg>\r\n", styles: [":host{position:fixed;inset:0;display:flex;justify-content:center;align-items:center;z-index:999999999}.darkBg{position:absolute;cursor:progress;width:100%;height:100%;display:flex;justify-content:center;align-items:center;background-color:#00000096;animation-name:bgfadein;animation-duration:30s;animation-timing-function:ease}svg{z-index:5000;width:40%;height:40%;max-width:130px;max-height:130px;animation-name:loadingfadein;animation-duration:3s;animation-timing-function:ease-in}@keyframes loadingfadein{0%{opacity:0}25%{opacity:0}to{opacity:1}}@keyframes bgfadein{0%{opacity:0}4%{opacity:0}14%{opacity:.2}35%{opacity:.5}to{opacity:1}}\n"] }]
1021
- }], propDecorators: { busyStateBaseFade: [{
1022
- type: HostBinding,
1023
- args: ['@busyStateBaseFade']
1024
- }] } });
1025
1069
 
1026
1070
  /**
1027
1071
  * TurboGUI is A library that helps with the most common and generic UI elements and functionalities
@@ -1032,26 +1076,43 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImpor
1032
1076
  * CopyRight : -> Copyright 2018 Edertone Advanded Solutions. https://www.edertone.com
1033
1077
  */
1034
1078
  /**
1035
- * A dialog component with a calendar that allows us to select a single date value
1079
+ * An abstraction of the browser entity an all its related operations and properties
1036
1080
  */
1037
- class DialogDateSelectionComponent extends DialogBaseComponent {
1038
- static { this.DIALOG_CLASS_NAME = 'DialogDateSelectionComponent'; }
1039
- constructor(elementRef, dialogRef, data) {
1040
- super(elementRef, dialogRef);
1041
- this.elementRef = elementRef;
1042
- this.dialogRef = dialogRef;
1043
- this.data = data;
1081
+ class BrowserService extends BrowserManager {
1082
+ constructor(location) {
1083
+ super();
1084
+ this.location = location;
1044
1085
  }
1045
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DialogDateSelectionComponent, deps: [{ token: i0.ElementRef }, { token: i1.MatDialogRef }, { token: MAT_DIALOG_DATA }], target: i0.ɵɵFactoryTarget.Component }); }
1046
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.5", type: DialogDateSelectionComponent, isStandalone: true, selector: "tg-dialog-date-selection", providers: [], usesInheritance: true, ngImport: i0, template: "<h2>{{data.texts[0]}}</h2>\r\n\r\n<mat-calendar #calendar\r\n (selectedChange)=\"closeDialog(0, $event)\">\r\n</mat-calendar>", styles: [":host{position:relative;min-height:300px}h1{margin-top:0;margin-bottom:25px;width:82%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i2$1.MatCalendar, selector: "mat-calendar", inputs: ["headerComponent", "startAt", "startView", "selected", "minDate", "maxDate", "dateFilter", "dateClass", "comparisonStart", "comparisonEnd", "startDateAccessibleName", "endDateAccessibleName"], outputs: ["selectedChange", "yearSelected", "monthSelected", "viewChanged", "_userSelection", "_userDragDrop"], exportAs: ["matCalendar"] }, { kind: "ngmodule", type: MatNativeDateModule }] }); }
1086
+ /**
1087
+ * Modify the current browser URI without reloading the current page document
1088
+ *
1089
+ * @param path The uri value we want to set
1090
+ * @param query The query url parameters part we want to specify if any
1091
+ *
1092
+ * @returns void
1093
+ */
1094
+ setCurrentUrlURI(path, query) {
1095
+ this.location.go(path, query);
1096
+ }
1097
+ /**
1098
+ * Obtain a subscription to get notified on any changes at the browser url
1099
+ *
1100
+ * @param onNext A method to be executed every time the url changes on the browser. The url will be available inside the value parameter
1101
+ *
1102
+ * @returns Subscribed events. Make sure to unsubscribe when not needed
1103
+ */
1104
+ subscribeToUrlChange(onNext) {
1105
+ return this.location.subscribe(onNext);
1106
+ }
1107
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: BrowserService, deps: [{ token: i2.Location }], target: i0.ɵɵFactoryTarget.Injectable }); }
1108
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: BrowserService, providedIn: 'root' }); }
1047
1109
  }
1048
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DialogDateSelectionComponent, decorators: [{
1049
- type: Component,
1050
- args: [{ selector: 'tg-dialog-date-selection', imports: [CommonModule, MatDatepickerModule, MatNativeDateModule], providers: [], template: "<h2>{{data.texts[0]}}</h2>\r\n\r\n<mat-calendar #calendar\r\n (selectedChange)=\"closeDialog(0, $event)\">\r\n</mat-calendar>", styles: [":host{position:relative;min-height:300px}h1{margin-top:0;margin-bottom:25px;width:82%}\n"] }]
1051
- }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1.MatDialogRef }, { type: undefined, decorators: [{
1052
- type: Inject,
1053
- args: [MAT_DIALOG_DATA]
1054
- }] }] });
1110
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: BrowserService, decorators: [{
1111
+ type: Injectable,
1112
+ args: [{
1113
+ providedIn: 'root',
1114
+ }]
1115
+ }], ctorParameters: () => [{ type: i2.Location }] });
1055
1116
 
1056
1117
  /**
1057
1118
  * TurboGUI is A library that helps with the most common and generic UI elements and functionalities
@@ -1062,489 +1123,428 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImpor
1062
1123
  * CopyRight : -> Copyright 2018 Edertone Advanded Solutions. https://www.edertone.com
1063
1124
  */
1064
1125
  /**
1065
- * Manages the application modal and non modal floating elements
1126
+ * Allows us to easily perform requests to a remote API that is developed with Turbo framework.
1066
1127
  */
1067
- class DialogService extends SingletoneStrictClass {
1068
- constructor(rendererFactory, matSnackBar, matDialog, injector, applicationRef, componentFactoryResolver) {
1069
- super(DialogService);
1070
- this.matSnackBar = matSnackBar;
1071
- this.matDialog = matDialog;
1072
- this.injector = injector;
1073
- this.applicationRef = applicationRef;
1074
- this.componentFactoryResolver = componentFactoryResolver;
1075
- /**
1076
- * Used to modify the busy state component that is shown by default by the addModalBusyState() method.
1077
- *
1078
- * @see this.addModalBusyState()
1079
- */
1080
- this.customBusyStateComponentClass = BusyStateBaseComponent;
1081
- /**
1082
- * Check public getter for docs
1083
- */
1084
- this._isEnabled = true;
1128
+ class TurboApiCallerService extends SingletoneStrictClass {
1129
+ constructor(dialogService, browserService) {
1130
+ super(TurboApiCallerService);
1131
+ this.dialogService = dialogService;
1132
+ this.browserService = browserService;
1085
1133
  /**
1086
- * Tells if the main application is currently showing a busy state that blocks all user interaction
1134
+ * URI Path to the web service that performs the user login
1087
1135
  */
1088
- this._isShowingBusyState = false;
1136
+ this.loginServiceURI = 'users/login';
1089
1137
  /**
1090
- * A reference to the modal busy state component that is initialized only the first time it is called.
1091
- *
1092
- * (To append the busy state dynamically, we use the Portal concept from the angular material library)
1138
+ * URI Path to the web service that allows us to create extra user tokens
1093
1139
  */
1094
- this._componentPortal = null;
1140
+ this.tokenCreationServiceURI = 'users/user-token-create';
1095
1141
  /**
1096
- * A reference to the modal busy state container where the component will be added
1142
+ * URI Path to the web service that performs the user log out
1097
1143
  */
1098
- this._modalBusyStateHost = null;
1144
+ this.logOutServiceURI = 'users/logout';
1099
1145
  /**
1100
- * Tells if the manager is currently showing a snackbar element or not
1146
+ * URI Path to the web service that performs the verification for a user email
1101
1147
  */
1102
- this._isShowingSnackBar = false;
1148
+ this.mailVerifyServiceURI = 'users/user-mail-verify';
1103
1149
  /**
1104
- * Contains a list of the dialogs that are currently visible to the user.
1105
- * Each item in this list is a hash that is computed when dialog is created to uniquely identify it.
1106
- *
1107
- * Empty list means no dialogs are currently visible
1150
+ * The username that is currently defined and will be used by the login methods
1108
1151
  */
1109
- this._activeDialogs = [];
1152
+ this._userName = '';
1110
1153
  /**
1111
- * Contains a list with all the MatDialog instances that are currently visible to the user.
1112
- * The list uses the same order as the list of _activeDialogs hash values
1154
+ * The password for the user that is currently defined and will be used by the login methods
1113
1155
  */
1114
- this._activeDialogInstances = [];
1156
+ this._password = '';
1115
1157
  /**
1116
- * Method that is used to delete the window beforeunload event listener once not used anymore
1158
+ * Contains the last email account that has been verified (or tried to verify) by this service, if any
1117
1159
  */
1118
- this._windowBeforeUnloadUnListen = null;
1160
+ this._lastVerifiedMail = '';
1119
1161
  /**
1120
- * Method that is used to delete the document keydown event listener once not used anymore
1162
+ * Check public getter for docs
1121
1163
  */
1122
- this._documentKeydownUnlisten = null;
1164
+ this._isLogged = false;
1123
1165
  /**
1124
- * Method that is used to delete the document mousedown event listener once not used anymore
1166
+ * @see token() getter for more info
1125
1167
  */
1126
- this._documentMousedownUnlisten = null;
1168
+ this._token = '';
1127
1169
  /**
1128
- * Method that is used to delete the document pointerdown event listener once not used anymore
1129
- */
1130
- this._documentPointerdownUnlisten = null;
1131
- this._renderer = rendererFactory.createRenderer(null, null);
1132
- }
1133
- /**
1134
- * Tells if this dialog service is enabled or not. If dialog service is disabled, none of it's features will work
1135
- * This is used to block all dialog features normally when shutting down the application
1136
- *
1137
- * Use with caution. When this is set to false, dialog service stops working.
1138
- */
1139
- set isEnabled(v) {
1140
- if (v === this._isEnabled) {
1141
- return;
1142
- }
1143
- this._isEnabled = v;
1144
- }
1145
- /**
1146
- * Enables a warning that will be shown to the user when he/she tries to close the application.
1147
- * This warning will prompt the user to continue with the exit process or cancel it.
1148
- * The specific texts of this message are a generic ones and cannot be changed.
1149
- *
1150
- * IMPORTANT: This method must be called after the main application has been initialized in order to work,
1151
- * otherwise it will do nothing.
1152
- */
1153
- addCloseApplicationWarning() {
1154
- if (this._windowBeforeUnloadUnListen === null) {
1155
- this._windowBeforeUnloadUnListen = this._renderer.listen('window', 'beforeunload', (event) => event.returnValue = true);
1156
- }
1157
- }
1158
- /**
1159
- * Remove the close application warning message if previously assigned
1160
- */
1161
- removeCloseApplicationWarning() {
1162
- if (this._windowBeforeUnloadUnListen !== null) {
1163
- this._windowBeforeUnloadUnListen();
1164
- this._windowBeforeUnloadUnListen = null;
1165
- }
1166
- }
1167
- /**
1168
- * Change the application visual appearance so the user perceives that the application is
1169
- * currently busy. While modal busy state is enabled, no user input is possible neither via
1170
- * keyboard, mouse or touch. Use this state when performing server requests or operations that
1171
- * must block the user interaction with the application. To allow user interaction again, you must
1172
- * call removeModalBusyState()
1173
- *
1174
- * Notice: We can modify the busy state visual component that is shown by this method. To do it, we must
1175
- * set this.customBusyStateComponentClass property with our own custom busy state component class. (We can do it at
1176
- * our main application component constructor for example). Our custom component must extend the
1177
- * BusyStateBaseComponent one to add its own visual appearance.
1178
- *
1179
- * @see this.customBusyStateComponentClass
1180
- */
1181
- addModalBusyState() {
1182
- if (!this._isEnabled || this._isShowingBusyState) {
1183
- return;
1184
- }
1185
- this._disableUserInteraction();
1186
- // Dynamically create the busy state component reference if this is the first time
1187
- if (this._componentPortal === null) {
1188
- this._componentPortal = new ComponentPortal(this.customBusyStateComponentClass);
1189
- // Create a PortalHost with document.body as its anchor element
1190
- this._modalBusyStateHost = new DomPortalOutlet(document.body, this.componentFactoryResolver, this.applicationRef, this.injector);
1191
- }
1192
- this._modalBusyStateHost.attach(this._componentPortal);
1193
- this._isShowingBusyState = true;
1170
+ * List of operations that are allowed to the currently loged user. It gets filled just after login is performed
1171
+ */
1172
+ this._operations = [];
1173
+ // Create a fresh instance of the http service so we can use it independently
1174
+ this.httpManager = new HTTPManager();
1175
+ this._clearUserAndToken();
1194
1176
  }
1195
1177
  /**
1196
- * Tells if the application is currently locked in a modal busy state (caused by an addModalBusyState() call)
1178
+ * The username that is currently defined and will be used by the login methods
1197
1179
  */
1198
- get isShowingBusyState() {
1199
- return this._isShowingBusyState;
1180
+ set userName(v) {
1181
+ this._isLogged = false;
1182
+ this._token = '';
1183
+ this._userName = v;
1200
1184
  }
1201
1185
  /**
1202
- * Cancel the application busy state and restore it back to normal so user interaction is allowed again
1186
+ * The username that is currently defined and will be used by the login methods
1203
1187
  */
1204
- removeModalBusyState() {
1205
- if (!this._isEnabled || !this._isShowingBusyState) {
1206
- return;
1207
- }
1208
- if (this._componentPortal !== null) {
1209
- this._modalBusyStateHost.detach();
1210
- }
1211
- this._enableUserInteraction();
1212
- this._isShowingBusyState = false;
1188
+ get userName() {
1189
+ return this._userName;
1213
1190
  }
1214
1191
  /**
1215
- * TODO - adapt from TS version
1192
+ * The password for the user that is currently defined and will be used by the login methods
1216
1193
  */
1217
- addToolTip() {
1218
- // TODO - adapt from TS version
1194
+ set password(v) {
1195
+ this._isLogged = false;
1196
+ this._token = '';
1197
+ this._password = v;
1219
1198
  }
1220
1199
  /**
1221
- * Show a non modal snackbar notification to the user (Only one snack-bar can ever be opened at the same time).
1222
- *
1223
- * Snackbars inform users of a process that an app has performed or will perform. They appear temporarily, towards the bottom or top of the screen.
1224
- * They shouldn’t interrupt the user experience, and they don’t require user input to disappear.
1225
- *
1226
- * @param config A MatSnackBarConfig instance with the customizations we want for this snackbar
1227
- * @param message The message to show on the snackbar
1228
- * @param action If not empty, the text to place on the snackbar confirmation button
1229
- * @param actionCallback A method to execute once the user clicks into the action button.
1230
- *
1231
- * @return void
1200
+ * The password for the user that is currently defined and will be used by the login methods
1232
1201
  */
1233
- addSnackBar(config, message, action = '', actionCallback = null) {
1234
- if (!this._isEnabled) {
1235
- return;
1236
- }
1237
- if (this._isShowingSnackBar) {
1238
- throw new Error('Trying to show a snackbar while another one is still visible');
1239
- }
1240
- this._isShowingSnackBar = true;
1241
- const snackBarRef = this.matSnackBar.open(message, action === '' ? undefined : action, config);
1242
- if (actionCallback !== null) {
1243
- snackBarRef.onAction().subscribe(() => {
1244
- actionCallback();
1245
- });
1246
- }
1202
+ get password() {
1203
+ return this._password;
1247
1204
  }
1248
1205
  /**
1249
- * Tells if the application is currently showing a snackbar or not
1206
+ * Define the root url that will be used for all the API calls
1250
1207
  */
1251
- get isShowingSnackBar() {
1252
- return this._isShowingSnackBar;
1208
+ set baseUrl(url) {
1209
+ this.httpManager.baseUrl = url;
1253
1210
  }
1254
1211
  /**
1255
- * Force the removal of the snack bar dialog if it exists.
1256
- *
1257
- * If no snackbar is currently visible, this method will do nothing
1212
+ * Define the root url that will be used for all the API calls
1258
1213
  */
1259
- removeSnackBar() {
1260
- if (!this._isEnabled || !this._isShowingSnackBar) {
1261
- return;
1262
- }
1263
- this.matSnackBar.dismiss();
1264
- this._isShowingSnackBar = false;
1214
+ get baseUrl() {
1215
+ return this.httpManager.baseUrl;
1265
1216
  }
1266
1217
  /**
1267
- * Show a dialog with one or more options that can be used to close it. We can use any of the predefined dialog types that are bundled with
1268
- * this library or extend DialogBaseComponent to create our own custom ones.
1218
+ * If the current browser URL contains a hash fragment (The part after the # character) that contains encoded user or password
1219
+ * values, this method will parse and automatically set them as the credentials to use.
1269
1220
  *
1270
- * @param dialogComponentClass A class for a component that extends DialogBaseComponent, which will be the dialog that is shown to the user.
1271
- * @param properties An object containing the different visual and textual options that this dialog allows:
1272
- * - id: The html unique identifier that the dialog will have once created. If not specified, no id will be explicitly set
1273
- * - width: 50% by default. Specify the css value for the default dialog width. As the dialog is responsive, the value will be automatically
1274
- * reduced if the available screen is not enough, and will reach the desired value otherwise. We can set any css unit like pixels,
1275
- * %, vh, vw, or any other. For example: '400px', '50%', etc.
1276
- * - maxWidth: Defines the maximum width that the dialog will have regarding the viewport. We can specify it in % or vw, just like is done in
1277
- * css. By default it is defined as 96vw, which will fit 96% of the viewport on small devices
1278
- * - height: TODO docs
1279
- * - maxHeight: TODO docs
1280
- * - modal: True (default) if selecting an option is mandatory to close the dialog, false if the dialog can be closed
1281
- * by the user clicking outside it
1282
- * - texts: A list with strings containing the dialog texts, sorted by importance. When dialog has a title, this should
1283
- * be placed first, subtitle second and so (Each dialog may accept a different custom number of texts).
1284
- * - options: A list of strings that will be used as button captions for each one of the accepted dialog options
1285
- * - data: An object that we can use to pass any extra data that we want to the dialog
1286
- * - viewContainerRef: This is important if we want to propagate providers from a parent component to this dialog. We must specify
1287
- * this reference to make sure the same services injected on the parent are available too at the child dialog
1221
+ * The URL hash is not altered in any way by this method.
1288
1222
  *
1289
- * @param callback A function that will be called after the dialog is closed. It will receive a selection object with two properties: index and value. Those
1290
- * will contain the index and value from the options array that's selected by the user. if no option selected, index will be -1 and value null
1223
+ * @returns True if any credentials were found and loaded, false if nothing happened
1291
1224
  */
1292
- addDialog(dialogComponentClass, properties, callback = null) {
1293
- if (!this._isEnabled) {
1294
- return;
1225
+ loadCredentialsFromURLHashFragment() {
1226
+ // If the hash fragment is empty, nothing to do
1227
+ if (!this.browserService.isCurrentUrlWithHashFragment()) {
1228
+ return false;
1295
1229
  }
1296
- // Set the default values for non specified properties
1297
- properties.modal = properties.modal ?? true;
1298
- properties.texts = properties.texts ?? [];
1299
- properties.options = properties.options ?? [];
1300
- properties.data = properties.data ?? {};
1301
- // Generate a string to uniquely identify this dialog on the list of active dialogs
1302
- // A dialog is considered as unique if the dialog id and texts are exactly the same. We do not take options into consideration
1303
- // as there may be dialogs with a big amount of options available.
1304
- let className = dialogComponentClass.DIALOG_CLASS_NAME;
1305
- if (className === '') {
1306
- throw new Error(`The static property DIALOG_CLASS_NAME is not defined or is empty for this dialog component (${dialogComponentClass})`);
1307
- }
1308
- const dialogHash = className + properties.texts.join('');
1309
- // identical dialogs won't be allowed at the same time
1310
- if (this._activeDialogs.includes(dialogHash)) {
1311
- return;
1230
+ let valuesFound = false;
1231
+ // Split the hash fragment to obtain the different values that contains
1232
+ let hashData = this.browserService.getCurrentUrlHashFragment().split('/');
1233
+ // User name is the first element of the hash fragment. If we found a value,
1234
+ // we will fill it automatically
1235
+ if (hashData.length > 0) {
1236
+ valuesFound = true;
1237
+ this.userName = ConversionUtils.base64ToString(hashData[0]);
1312
1238
  }
1313
- const dialogRef = this.matDialog.open(dialogComponentClass, {
1314
- width: properties.width ?? "50%",
1315
- maxWidth: properties.maxWidth ?? "96vw",
1316
- disableClose: properties.modal,
1317
- autoFocus: false,
1318
- closeOnNavigation: !properties.modal,
1319
- viewContainerRef: properties.viewContainerRef,
1320
- data: { texts: properties.texts, options: properties.options, data: properties.data }
1321
- });
1322
- // Assign the dialog ID only if specifically set on properties
1323
- if (properties.id && properties.id !== undefined) {
1324
- dialogRef.id = properties.id;
1325
- }
1326
- this._activeDialogs.push(dialogHash);
1327
- this._activeDialogInstances.push(dialogRef);
1328
- dialogRef.beforeClosed().subscribe((selection) => {
1329
- this._activeDialogs = ArrayUtils.removeElement(this._activeDialogs, dialogHash);
1330
- this._activeDialogInstances = ArrayUtils.removeElement(this._activeDialogInstances, dialogRef);
1331
- if (!properties.modal && selection === undefined) {
1332
- selection = { index: -1 };
1333
- }
1334
- else if (!NumericUtils.isInteger(selection.index)) {
1335
- throw new Error(`closeDialog() expects index to be an integer`);
1336
- }
1337
- if (callback !== null) {
1338
- if (selection.index >= 0 && selection.value === null) {
1339
- selection.value = properties.options[selection.index];
1239
+ // Auto fill the password if it is received
1240
+ if (hashData.length > 3) {
1241
+ valuesFound = true;
1242
+ this.password = ConversionUtils.base64ToString(hashData[3]);
1243
+ }
1244
+ return valuesFound;
1245
+ }
1246
+ /**
1247
+ * If the current browser URL contains a hash fragment (The part after the # character) that contains encoded user, mail and
1248
+ * has verification code values, this method will perform the request to server to verify that email account for the user.
1249
+ *
1250
+ * The URI path to the mail verification service must be correctly defined at this.mailVerifyServiceURI property.
1251
+ *
1252
+ * @returns A promise that resolves with the server response if the verification is successful,
1253
+ * or rejects with an error if the verification fails. Response will have 4 possible values:
1254
+ * undefined - meaning that the URL hash fragment didn't contain valid verification values
1255
+ * -1 - meaning verification failed
1256
+ * 0 - meaning verification succeeded
1257
+ * 1 - meaning the email was already previouly verified
1258
+ */
1259
+ verifyUserMailFromURLHashFragment() {
1260
+ // If the hash fragment is empty, nothing to do
1261
+ if (this.browserService.isCurrentUrlWithHashFragment()) {
1262
+ let hashData = this.browserService.getCurrentUrlHashFragment().split('/');
1263
+ if (hashData.length >= 3) {
1264
+ // Call for the user mail verification if user, mail and hash are received
1265
+ let user = ConversionUtils.base64ToString(hashData[0]);
1266
+ let mail = ConversionUtils.base64ToString(hashData[1]);
1267
+ let hash = ConversionUtils.base64ToString(hashData[2]);
1268
+ if (!StringUtils.isEmpty(user) && !StringUtils.isEmpty(mail) && !StringUtils.isEmpty(hash)) {
1269
+ this._lastVerifiedMail = mail;
1270
+ return this.call(this.mailVerifyServiceURI, { userName: user, mail: mail, hash: hash });
1340
1271
  }
1341
- callback(selection);
1342
1272
  }
1343
- });
1273
+ }
1274
+ return Promise.resolve(undefined);
1344
1275
  }
1345
1276
  /**
1346
- * Show a dialog with a calendar to let the user pick a date.
1277
+ * Obtain the lat user mail account that has been verified (or attempted to verify) by this service.
1278
+ */
1279
+ getLastVerifiedMail() {
1280
+ return this._lastVerifiedMail;
1281
+ }
1282
+ /**
1283
+ * Authenticates the userName and password that are currently defined at the respective properties of this service.
1284
+ * Returns a promise that resolves with the server's response or rejects with an error if the login fails.
1285
+ * Path to the login service must be correctly defined at this.loginWebService
1347
1286
  *
1348
- * @param properties An object containing the different visual and textual options that this dialog allows:
1349
- * - id: The html unique identifier that the dialog will have once created. If not specified, no id will be explicitly set
1350
- * - width: Specify the css value for the default dialog width. As the dialog is responsive, the value will be automatically
1351
- * reduced if the available screen is not enough, and will reach the desired value otherwise. We can set any css unit like pixels,
1352
- * %, vh, vw, or any other. For example: '400px', '50%', etc.
1353
- * - maxWidth: Defines the maximum width that the dialog will have regarding the viewport. We can specify it in % or vw, just like is done in
1354
- * css. By default it is defined as 96vw, which will fit 96% of the viewport on small devices
1355
- * - height: TODO docs
1356
- * - maxHeight: TODO docs
1357
- * - modal: True (default) if selecting an option is mandatory to close the dialog, false if the dialog can be closed
1358
- * by the user clicking outside it
1359
- * - title: An optional dialog title
1360
- * - viewContainerRef: This is important to propagate providers from a parent component to this dialog. We must specify
1361
- * this reference to make sure the same services injected on the parent are available too at the child dialog
1362
- * @param callback A function to be called after the dialog is closed. It will receive a Date() object selected by the user or null if no selection happened
1287
+ * The authentication process is performed by sending an encoded credentials request to the login web service using the
1288
+ * currently defined user name and psw.
1289
+ *
1290
+ * Here's an example of a call:
1291
+ *
1292
+ * login().then(response => {
1293
+ * console.log('Login successful:', response);
1294
+ * }).catch(() => {
1295
+ * console.error('Login failed:');
1296
+ * });
1297
+ *
1298
+ * @returns A promise that resolves with the server response if the login is successful,
1299
+ * or rejects with an error if the login fails.
1363
1300
  */
1364
- addDateSelectionDialog(properties, callback) {
1365
- if (!this._isEnabled) {
1366
- return;
1367
- }
1368
- this.addDialog(DialogDateSelectionComponent, {
1369
- id: properties.id ?? undefined,
1370
- width: properties.width ?? "50%",
1371
- maxWidth: properties.maxWidth ?? "96vw",
1372
- height: properties.height ?? "50%",
1373
- maxHeight: properties.maxHeight ?? "92vw",
1374
- modal: properties.modal ?? false,
1375
- texts: [properties.title ?? ''],
1376
- viewContainerRef: properties.viewContainerRef
1377
- }, (selection) => {
1378
- callback(selection.index === -1 ? null : selection.value);
1301
+ login() {
1302
+ this.dialogService.addModalBusyState();
1303
+ return new Promise((resolve, reject) => {
1304
+ const request = new HTTPManagerPostRequest(this.loginServiceURI);
1305
+ request.ignoreGlobalPostParams = true;
1306
+ const encodedCredentials = ConversionUtils.stringToBase64(ConversionUtils.stringToBase64(this._userName) + ',' + ConversionUtils.stringToBase64(this._password));
1307
+ request.parameters = { data: encodedCredentials };
1308
+ request.successCallback = (response) => {
1309
+ if (response !== '') {
1310
+ response = JSON.parse(response);
1311
+ this._isLogged = true;
1312
+ this._token = response.token;
1313
+ this._operations = response.operations;
1314
+ this.httpManager.setGlobalPostParam('token', response.token);
1315
+ resolve(response);
1316
+ }
1317
+ else {
1318
+ this._clearUserAndToken();
1319
+ reject(new Error());
1320
+ }
1321
+ };
1322
+ request.errorCallback = () => {
1323
+ this._clearUserAndToken();
1324
+ reject(new Error());
1325
+ };
1326
+ request.finallyCallback = () => {
1327
+ this.dialogService.removeModalBusyState();
1328
+ };
1329
+ this.httpManager.execute(request);
1379
1330
  });
1380
1331
  }
1381
1332
  /**
1382
- * Force the removal of all the dialogs that are currently visible.
1333
+ * Perform a request to the API service to create a new token for the user that is currently logged in.
1383
1334
  *
1384
- * If no dialogs are currently visible, this method will do nothing
1335
+ * @param options The parameters that will affect the token behaviour. To learn more about each option, please
1336
+ * refer to the turbodepot UsersManager class createToken method documentation
1337
+ *
1338
+ * @returns A promise that resolves with the created token string if the request is successful, or rejects
1339
+ * with an error (containing all the error response details) if the request fails.
1385
1340
  */
1386
- removeAllDialogs() {
1387
- if (!this._isEnabled) {
1388
- return;
1389
- }
1390
- for (const dialogRef of this._activeDialogInstances) {
1391
- dialogRef.close({ index: -1 });
1392
- }
1393
- this._activeDialogs = [];
1394
- this._activeDialogInstances = [];
1341
+ createUserToken(options) {
1342
+ return this.call(this.tokenCreationServiceURI, { options: options }, { resultFormat: 'STRING' });
1395
1343
  }
1396
1344
  /**
1397
- * TODO - translate from TS version
1345
+ * Checks if the user and password credentials are filled and not empty.
1346
+ * If any of the two values is empty, false will be returned
1398
1347
  */
1399
- // addSideNav(){
1400
- //
1401
- // }
1348
+ get isUserAndPswDefined() {
1349
+ return !StringUtils.isEmpty(this._userName) && !StringUtils.isEmpty(this._password);
1350
+ }
1402
1351
  /**
1403
- * TODO - translate from TS version
1352
+ * Tells if the user name and psw that are specified on this service are currently logged or not. This means
1353
+ * also a token is active.
1404
1354
  */
1405
- // get isShowingSideNav(){
1406
- //
1407
- // }
1355
+ get isLogged() {
1356
+ return this._isLogged;
1357
+ }
1408
1358
  /**
1409
- * TODO - translate from TS version
1359
+ * Gives the value for the currently active user authentication token or an empty string if no user logged
1410
1360
  */
1411
- // removeSideNav(){
1412
- //
1413
- // }
1361
+ get token() {
1362
+ return this._token;
1363
+ }
1414
1364
  /**
1415
- * Block all the user interactions with the application (keyboard, touch, mouse, ...)
1365
+ * Checks if the currently logged user is allowed to perform the specified operation.
1366
+ * This will check the user permisions as defined on server side and return true if the operation is allowed
1367
+ * or false if not. We can then decide what to do (hide an app section, block a button, etc)
1368
+ *
1369
+ * @param operation The name of the operation to check
1370
+ *
1371
+ * @returns True if the operation is allowed, false otherwise
1416
1372
  */
1417
- _disableUserInteraction() {
1418
- if (this._documentKeydownUnlisten === null) {
1419
- this._documentKeydownUnlisten = this._renderer.listen('document', 'keydown', (event) => event.preventDefault());
1420
- }
1421
- if (this._documentMousedownUnlisten === null) {
1422
- this._documentMousedownUnlisten = this._renderer.listen('document', 'mousedown', (event) => event.preventDefault());
1423
- }
1424
- if (this._documentPointerdownUnlisten === null) {
1425
- this._documentPointerdownUnlisten = this._renderer.listen('document', 'pointerdown', (event) => event.preventDefault());
1426
- }
1373
+ isUserAllowedTo(operation) {
1374
+ return this._operations.includes(operation);
1427
1375
  }
1428
1376
  /**
1429
- * Restore the user interactions that were previously disabled with _disableUserInteraction method
1377
+ * Performs a standard request to an API service and returns a promise that resolves with the response data.
1378
+ *
1379
+ * Following is an example of a call:
1380
+ *
1381
+ * this.apiService.call('users/user-create', this.userData, {handleErrors: false}).then(response => {
1382
+ *
1383
+ * console.log('Success:', response);
1384
+ *
1385
+ * }).catch(error => {
1386
+ *
1387
+ * console.error('Error:', error.message);
1388
+ * });
1389
+ *
1390
+ * @param apiPath - A relative URL (based on the defined base root url) that defines the path to the service to call.
1391
+ * For example: 'users/login'
1392
+ * @param parameters - An object containing key-value pairs that are sent as the request body.
1393
+ * token parameter is not necessary, it is automatically appended
1394
+ * @param options An object defining several options for the request:
1395
+ * resultFormat: 'JSON' by default. The expected format of the response.
1396
+ * busyState: Enabled by default. Enables or disables the busy state to lock user interaction while performing the http calls.
1397
+ * handleErrors: Enabled by default. If set to true, an error dialog will be automatically shown when the call fails.
1398
+ * If set to false, the promise will generate a reject error that must be handled by our code.
1399
+ *
1400
+ * @returns A promise that resolves with the response data correctly parsed if the request is successful, or rejects
1401
+ * with an error (containing all the error response details) if the request fails.
1430
1402
  */
1431
- _enableUserInteraction() {
1432
- if (this._documentKeydownUnlisten !== null) {
1433
- this._documentKeydownUnlisten();
1434
- this._documentKeydownUnlisten = null;
1435
- }
1436
- if (this._documentMousedownUnlisten !== null) {
1437
- this._documentMousedownUnlisten();
1438
- this._documentMousedownUnlisten = null;
1439
- }
1440
- if (this._documentPointerdownUnlisten !== null) {
1441
- this._documentPointerdownUnlisten();
1442
- this._documentMousedownUnlisten = null;
1403
+ call(apiPath, parameters = {}, options = {}) {
1404
+ // Set the default values for non specified properties
1405
+ options.resultFormat = options.resultFormat ?? 'JSON';
1406
+ options.busyState = options.busyState ?? true;
1407
+ options.handleErrors = options.handleErrors ?? true;
1408
+ if (options.busyState) {
1409
+ this.dialogService.addModalBusyState();
1443
1410
  }
1444
- }
1445
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DialogService, deps: [{ token: i0.RendererFactory2 }, { token: i1$1.MatSnackBar }, { token: i1.MatDialog }, { token: i0.Injector }, { token: i0.ApplicationRef }, { token: i0.ComponentFactoryResolver }], target: i0.ɵɵFactoryTarget.Injectable }); }
1446
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DialogService, providedIn: 'root' }); }
1447
- }
1448
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DialogService, decorators: [{
1449
- type: Injectable,
1450
- args: [{
1451
- providedIn: 'root',
1452
- }]
1453
- }], ctorParameters: () => [{ type: i0.RendererFactory2 }, { type: i1$1.MatSnackBar }, { type: i1.MatDialog }, { type: i0.Injector }, { type: i0.ApplicationRef }, { type: i0.ComponentFactoryResolver }] });
1454
-
1455
- /**
1456
- * TurboGUI is A library that helps with the most common and generic UI elements and functionalities
1457
- *
1458
- * Website : -> http://www.turbogui.org
1459
- * License : -> Licensed under the Apache License, Version 2.0. You may not use this file except in compliance with the License.
1460
- * License Url : -> http://www.apache.org/licenses/LICENSE-2.0
1461
- * CopyRight : -> Copyright 2018 Edertone Advanded Solutions. https://www.edertone.com
1462
- */
1463
- /**
1464
- * Manages application http communications
1465
- */
1466
- class HTTPService extends HTTPManager {
1467
- constructor(dialogService) {
1468
- super(true);
1469
- this.dialogService = dialogService;
1411
+ return new Promise((resolve, reject) => {
1412
+ // Create the request object
1413
+ const request = new HTTPManagerPostRequest(apiPath, options.resultFormat === 'STRING' ? HTTPManagerPostRequest.STRING : HTTPManagerPostRequest.JSON);
1414
+ request.parameters = parameters;
1415
+ request.successCallback = (response) => {
1416
+ resolve(response);
1417
+ };
1418
+ request.errorCallback = (errorMsg, errorCode, response) => {
1419
+ if (options.handleErrors) {
1420
+ this.showErrorDialog(errorMsg, errorCode, response);
1421
+ }
1422
+ else {
1423
+ reject(new Error(errorMsg + ' ' + errorCode + ' ' + response));
1424
+ }
1425
+ };
1426
+ request.finallyCallback = () => {
1427
+ if (options.busyState) {
1428
+ this.dialogService.removeModalBusyState();
1429
+ }
1430
+ };
1431
+ this.httpManager.execute(request);
1432
+ });
1470
1433
  }
1471
1434
  /**
1472
- * The same method as HTTPManager.execute but with the ability to enable several options which are specific to this service:
1435
+ * Performs a request to chain several api calls into a single http request, via the chain services mechanism of the turboframework API.
1436
+ * Returns a promise that resolves with an array of response objects. One for each of the request calls.
1473
1437
  *
1474
- * - options:
1475
- * busyState: Set it to false to prevent the default behaviour of locking the UI while the request is running
1476
- * handleErrors: Set it to false to prevent the default behaviour of showing a detailed error dialog when a request fails
1438
+ * @param apiPath - A relative URL (based on the defined base root url) that defines the path to the root of the chain services call.
1439
+ * For example: 'turbosite/chain/chain-services'
1440
+ * @param services - An array of objects, were each object contains a valid structure for the chain services call.
1441
+ * Token parameter is not necessary, it is automatically appended
1442
+ * @param options An object defining several options for the request:
1443
+ * busyState: Enables or disables the busy state to lock user interaction while performing the http calls. Enabled by default
1444
+ * handleErrors: Enabled by default. If set to true, an error dialog will be automatically shown when the call fails.
1445
+ * If set to false, the promise will generate a reject error that must be handled by our code.
1477
1446
  *
1478
- * @see HTTPManager.execute()
1447
+ * @returns A promise that resolves with the response data correctly parsed if the request is successful, or rejects
1448
+ * with an error (containing all the error response details) if the request fails.
1479
1449
  */
1480
- execute(requests, finishedCallback = null, progressCallback = null, options = {}) {
1450
+ callChain(apiPath, services, options = {}) {
1481
1451
  // Set the default values for non specified properties
1482
1452
  options.busyState = options.busyState ?? true;
1483
1453
  options.handleErrors = options.handleErrors ?? true;
1484
1454
  if (options.busyState) {
1485
1455
  this.dialogService.addModalBusyState();
1486
1456
  }
1487
- super.execute(requests, (results, anyError) => {
1488
- if (options.busyState) {
1489
- this.dialogService.removeModalBusyState();
1490
- }
1491
- if (options.handleErrors && anyError) {
1492
- for (let result of results) {
1493
- if (result.isError) {
1494
- let errorMsg = result.errorMsg + '\n\n' + result.response;
1495
- if (StringUtils.isEmpty(errorMsg)) {
1496
- errorMsg = 'Unknown error. Make sure Internet connection is available';
1497
- }
1498
- this.dialogService.addDialog(DialogErrorComponent, {
1499
- width: '50vw',
1500
- texts: ['Error: ' + result.code, errorMsg],
1501
- options: ['Ok']
1502
- });
1503
- }
1457
+ return new Promise((resolve, reject) => {
1458
+ const request = new HTTPManagerPostRequest(apiPath, HTTPManagerPostRequest.JSON);
1459
+ request.ignoreGlobalPostParams = true;
1460
+ request.parameters = { services: services };
1461
+ request.successCallback = (response) => {
1462
+ resolve(response);
1463
+ };
1464
+ request.errorCallback = (errorMsg, errorCode, response) => {
1465
+ if (options.handleErrors) {
1466
+ this.showErrorDialog(errorMsg, errorCode, response);
1504
1467
  }
1505
- }
1506
- if (finishedCallback !== null) {
1507
- finishedCallback(results, anyError);
1508
- }
1509
- }, progressCallback);
1468
+ else {
1469
+ reject(new Error(errorMsg + ' ' + errorCode + ' ' + response));
1470
+ }
1471
+ };
1472
+ request.finallyCallback = () => {
1473
+ if (options.busyState) {
1474
+ this.dialogService.removeModalBusyState();
1475
+ }
1476
+ };
1477
+ this.httpManager.execute(request);
1478
+ });
1479
+ }
1480
+ /**
1481
+ * Aux method to show an error dialog when a request fails and error handling is enabled
1482
+ */
1483
+ showErrorDialog(errorMsg, errorCode, response) {
1484
+ errorMsg = errorMsg + '\n\n' + response;
1485
+ if (StringUtils.isEmpty(errorMsg)) {
1486
+ errorMsg = 'Unknown error. Make sure Internet connection is available';
1487
+ }
1488
+ this.dialogService.addDialog(DialogErrorComponent, {
1489
+ width: '50vw',
1490
+ texts: ['Error: ' + errorCode, errorMsg],
1491
+ options: ['Ok']
1492
+ });
1493
+ }
1494
+ /**
1495
+ * Perform the logout for the currently logged user
1496
+ *
1497
+ * @param options An object defining several options for the request:
1498
+ * handleErrors: Enabled by default. If set to true, an error dialog will be automatically shown when the call fails.
1499
+ * If set to false, the promise will generate a reject error that must be handled by our code.
1500
+ *
1501
+ * @returns A promise that resolves correctly if the logout is correct, or rejects with an error (containing all the error
1502
+ * response details) if the request fails.
1503
+ */
1504
+ logout(options = {}) {
1505
+ // Set the default values for non specified properties
1506
+ options.handleErrors = options.handleErrors ?? true;
1507
+ this.dialogService.addModalBusyState();
1508
+ return new Promise((resolve, reject) => {
1509
+ const request = new HTTPManagerPostRequest(this.logOutServiceURI);
1510
+ request.parameters = { token: this._token };
1511
+ request.successCallback = () => {
1512
+ this._clearUserAndToken();
1513
+ resolve(undefined);
1514
+ };
1515
+ request.errorCallback = (errorMsg, errorCode, response) => {
1516
+ if (options.handleErrors) {
1517
+ this.showErrorDialog(errorMsg, errorCode, response);
1518
+ }
1519
+ else {
1520
+ reject(new Error(errorMsg + ' ' + errorCode + ' ' + response));
1521
+ }
1522
+ };
1523
+ request.finallyCallback = () => {
1524
+ this.dialogService.removeModalBusyState();
1525
+ };
1526
+ this.httpManager.execute(request);
1527
+ });
1528
+ }
1529
+ /**
1530
+ * Aux methot to clear all the currently logged user data
1531
+ */
1532
+ _clearUserAndToken() {
1533
+ this._userName = '';
1534
+ this._password = '';
1535
+ this._isLogged = false;
1536
+ this._token = '';
1537
+ this.httpManager.setGlobalPostParam('token', '-');
1510
1538
  }
1511
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: HTTPService, deps: [{ token: DialogService }], target: i0.ɵɵFactoryTarget.Injectable }); }
1512
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: HTTPService, providedIn: 'root' }); }
1539
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: TurboApiCallerService, deps: [{ token: DialogService }, { token: BrowserService }], target: i0.ɵɵFactoryTarget.Injectable }); }
1540
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: TurboApiCallerService, providedIn: 'root' }); }
1513
1541
  }
1514
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: HTTPService, decorators: [{
1542
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: TurboApiCallerService, decorators: [{
1515
1543
  type: Injectable,
1516
1544
  args: [{
1517
1545
  providedIn: 'root',
1518
1546
  }]
1519
- }], ctorParameters: () => [{ type: DialogService }] });
1520
-
1521
- /**
1522
- * TurboGUI is A library that helps with the most common and generic UI elements and functionalities
1523
- *
1524
- * Website : -> http://www.turbogui.org
1525
- * License : -> Licensed under the Apache License, Version 2.0. You may not use this file except in compliance with the License.
1526
- * License Url : -> http://www.apache.org/licenses/LICENSE-2.0
1527
- * CopyRight : -> Copyright 2018 Edertone Advanded Solutions. https://www.edertone.com
1528
- */
1529
- /**
1530
- * Class that defines a GET http request, to be used by HttpService
1531
- */
1532
- class HTTPServiceGetRequest extends HTTPManagerGetRequest {
1533
- }
1534
-
1535
- /**
1536
- * TurboGUI is A library that helps with the most common and generic UI elements and functionalities
1537
- *
1538
- * Website : -> http://www.turbogui.org
1539
- * License : -> Licensed under the Apache License, Version 2.0. You may not use this file except in compliance with the License.
1540
- * License Url : -> http://www.apache.org/licenses/LICENSE-2.0
1541
- * CopyRight : -> Copyright 2018 Edertone Advanded Solutions. https://www.edertone.com
1542
- */
1543
- /**
1544
- * Class that defines a POST http request, to be used by HttpService
1545
- */
1546
- class HTTPServicePostRequest extends HTTPManagerPostRequest {
1547
- }
1547
+ }], ctorParameters: () => [{ type: DialogService }, { type: BrowserService }] });
1548
1548
 
1549
1549
  /**
1550
1550
  * TurboGUI is A library that helps with the most common and generic UI elements and functionalities
@@ -1555,43 +1555,122 @@ class HTTPServicePostRequest extends HTTPManagerPostRequest {
1555
1555
  * CopyRight : -> Copyright 2018 Edertone Advanded Solutions. https://www.edertone.com
1556
1556
  */
1557
1557
  /**
1558
- * An abstraction of the browser entity an all its related operations and properties
1558
+ * Global service that helps with application routing and related functionalities.
1559
+ * It is defined as an abstract class so it must be extended in our application to be used.
1560
+ * We must declare a static array of routes that will be used to define the routes of the application.
1559
1561
  */
1560
- class BrowserService extends BrowserManager {
1561
- constructor(location) {
1562
- super();
1563
- this.location = location;
1562
+ class RouterBaseService {
1563
+ constructor(router, titleService) {
1564
+ this.router = router;
1565
+ this.titleService = titleService;
1566
+ /**
1567
+ * Indicates whether the title manager has been initialized.
1568
+ * This should only be done once, typically at application startup.
1569
+ */
1570
+ this._isTitleManagerInitialized = false;
1571
+ /**
1572
+ * BehaviorSubject that holds the current route URL.
1573
+ * This allows components to reactively subscribe to route changes.
1574
+ */
1575
+ this._currentRoute = new BehaviorSubject(this.router.url);
1576
+ // Initial update in case the service loads after the first NavigationEnd
1577
+ this._updateCurrentRoute();
1564
1578
  }
1565
1579
  /**
1566
- * Modify the current browser URI without reloading the current page document
1580
+ * Updates the current route URL in the BehaviorSubject.
1581
+ * This is called internally after each navigation event.
1582
+ */
1583
+ _updateCurrentRoute() {
1584
+ this._currentRoute.next(this.router.url);
1585
+ }
1586
+ /**
1587
+ * Checks if the current route matches the specified route.
1567
1588
  *
1568
- * @param path The uri value we want to set
1569
- * @param query The query url parameters part we want to specify if any
1589
+ * @param route The route to check against the current route.
1570
1590
  *
1571
- * @returns void
1591
+ * @returns True if we are actually at the specified route, false otherwise.
1572
1592
  */
1573
- setCurrentUrlURI(path, query) {
1574
- this.location.go(path, query);
1593
+ isRouteCurrent(route) {
1594
+ return this.getCurrentRoute() === '/' + route;
1575
1595
  }
1576
1596
  /**
1577
- * Obtain a subscription to get notified on any changes at the browser url
1597
+ * Gets the current value of the route URL synchronously.
1598
+ */
1599
+ getCurrentRoute() {
1600
+ return this._currentRoute.getValue();
1601
+ }
1602
+ /**
1603
+ * Initializes the title management feature to automatically refresh the browser title based on the current
1604
+ * URL route. It Must be called once, typically at application startup
1578
1605
  *
1579
- * @param onNext A method to be executed every time the url changes on the browser. The url will be available inside the value parameter
1606
+ * To correctly translate the route title, We expect the route definitions to have the following properties:
1580
1607
  *
1581
- * @returns Subscribed events. Make sure to unsubscribe when not needed
1608
+ * - titleKey: The key to be used to get the title from the translation bundle.
1609
+ * - titleBundle: The bundle to be used to get the title from the translation bundle.
1610
+ *
1611
+ * (Translations will be done using the LocalesService from this same library).
1612
+ *
1613
+ * Example of a Route using this feature:
1614
+ * // Home route
1615
+ * { path: '', component: HomePageComponent,
1616
+ * data: { titleKey: 'HOME', titleBundle: 'turbodepot/user-interface'} },
1617
+ *
1618
+ * @param prefix A text to be added before the computed title.
1619
+ * @param sufix A text to be added after the computed title.
1582
1620
  */
1583
- subscribeToUrlChange(onNext) {
1584
- return this.location.subscribe(onNext);
1621
+ initializeAutoTranslateTitleByRoute(localesService, prefix = '', sufix = '') {
1622
+ this._localesService = localesService;
1623
+ if (this._isTitleManagerInitialized) {
1624
+ throw new Error('Title refresh from routes has already been initialized. Can only be done once.');
1625
+ }
1626
+ // Set initial title based on current route data immediately
1627
+ this.updateTitleFromCurrentRoute(prefix, sufix);
1628
+ // Subscribe to future navigation events
1629
+ this._routerSubscription = this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(() => {
1630
+ this._updateCurrentRoute();
1631
+ this.updateTitleFromCurrentRoute(prefix, sufix);
1632
+ });
1633
+ this._isTitleManagerInitialized = true;
1585
1634
  }
1586
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: BrowserService, deps: [{ token: i2.Location }], target: i0.ɵɵFactoryTarget.Injectable }); }
1587
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: BrowserService, providedIn: 'root' }); }
1635
+ /**
1636
+ * Aux method: Updates the browser title based on the current route's data properties.
1637
+ * This is called after each navigation event to ensure the title is always up-to-date.
1638
+ *
1639
+ * @param prefix A text to be added before the computed title.
1640
+ * @param sufix A text to be added after the computed title.
1641
+ */
1642
+ updateTitleFromCurrentRoute(prefix, sufix) {
1643
+ let currentRoute = this.router.routerState.snapshot.root;
1644
+ while (currentRoute.firstChild) {
1645
+ currentRoute = currentRoute.firstChild;
1646
+ }
1647
+ const data = currentRoute.data;
1648
+ if (data['titleKey'] && data['titleBundle']) {
1649
+ this.titleService.setTitle(prefix + this._localesService.t(data['titleKey'], data['titleBundle']) + sufix);
1650
+ }
1651
+ }
1652
+ /**
1653
+ * Navigates to the specified route.
1654
+ *
1655
+ * @param route The route to navigate to.
1656
+ */
1657
+ navigateTo(route) {
1658
+ this.router.navigate(['/' + route]);
1659
+ }
1660
+ ngOnDestroy() {
1661
+ this._routerSubscription?.unsubscribe();
1662
+ // Clean up BehaviorSubject
1663
+ this._currentRoute.complete();
1664
+ }
1665
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: RouterBaseService, deps: [{ token: i1$2.Router }, { token: i2$2.Title }], target: i0.ɵɵFactoryTarget.Injectable }); }
1666
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: RouterBaseService, providedIn: 'root' }); }
1588
1667
  }
1589
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: BrowserService, decorators: [{
1668
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: RouterBaseService, decorators: [{
1590
1669
  type: Injectable,
1591
1670
  args: [{
1592
1671
  providedIn: 'root',
1593
1672
  }]
1594
- }], ctorParameters: () => [{ type: i2.Location }] });
1673
+ }], ctorParameters: () => [{ type: i1$2.Router }, { type: i2$2.Title }] });
1595
1674
 
1596
1675
  /**
1597
1676
  * TurboGUI is A library that helps with the most common and generic UI elements and functionalities
@@ -1602,428 +1681,513 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImpor
1602
1681
  * CopyRight : -> Copyright 2018 Edertone Advanded Solutions. https://www.edertone.com
1603
1682
  */
1604
1683
  /**
1605
- * Allows us to easily perform requests to a remote API that is developed with Turbo framework.
1684
+ * Fully featured translation manager to be used with any application that requires text internationalization.
1685
+ * It is defined as an abstract class so it must be extended in our application. This way we can
1686
+ * write custom methods to extend the functionality of this class if needed.
1606
1687
  */
1607
- class TurboApiCallerService extends SingletoneStrictClass {
1608
- constructor(dialogService, browserService) {
1609
- super(TurboApiCallerService);
1610
- this.dialogService = dialogService;
1611
- this.browserService = browserService;
1612
- /**
1613
- * URI Path to the web service that performs the user login
1614
- */
1615
- this.loginServiceURI = 'users/login';
1616
- /**
1617
- * URI Path to the web service that allows us to create extra user tokens
1618
- */
1619
- this.tokenCreationServiceURI = 'users/user-token-create';
1688
+ class LocalesBaseService {
1689
+ constructor() {
1620
1690
  /**
1621
- * URI Path to the web service that performs the user log out
1691
+ * if the class has been correctly initialized and translations have been correctly loaded
1622
1692
  */
1623
- this.logOutServiceURI = 'users/logout';
1693
+ this._isInitialized = false;
1624
1694
  /**
1625
- * URI Path to the web service that performs the verification for a user email
1695
+ * @see getLocales()
1626
1696
  */
1627
- this.mailVerifyServiceURI = 'users/user-mail-verify';
1697
+ this._locales = [];
1628
1698
  /**
1629
- * The username that is currently defined and will be used by the login methods
1699
+ * @see getLanguages()
1630
1700
  */
1631
- this._userName = '';
1701
+ this._languages = [];
1632
1702
  /**
1633
- * The password for the user that is currently defined and will be used by the login methods
1703
+ * Stores all the loaded localization data by library name, bundle name, key and locales
1634
1704
  */
1635
- this._password = '';
1705
+ this._loadedTranslations = {};
1636
1706
  /**
1637
- * Contains the last email account that has been verified (or tried to verify) by this service, if any
1707
+ * Stores a memory cache to improve performance when outputing translations
1638
1708
  */
1639
- this._lastVerifiedMail = '';
1709
+ this._keyValuesCache = {};
1640
1710
  /**
1641
- * Check public getter for docs
1711
+ * @see setWildCardsFormat()
1642
1712
  */
1643
- this._isLogged = false;
1713
+ this._wildCardsFormat = '{N}';
1644
1714
  /**
1645
- * @see token() getter for more info
1715
+ * @see setMissingKeyFormat()
1646
1716
  */
1647
- this._token = '';
1717
+ this._missingKeyFormat = '$exception';
1648
1718
  /**
1649
- * List of operations that are allowed to the currently loged user. It gets filled just after login is performed
1719
+ * Stores a hash value that is used to improve the performance for translation t() methods.
1720
+ * This is computed based on _wildCardsFormat plus _missingKeyFormat plus the current primary locale
1721
+ * Methods that change these values will recalculate the hash string, so when calling translation methods, the
1722
+ * performance will be as fast as possible.
1650
1723
  */
1651
- this._operations = [];
1652
- // Create a fresh instance of the http service so we can use it independently
1653
- this.httpManager = new HTTPManager();
1654
- this._clearUserAndToken();
1724
+ this._cacheHashBaseString = '';
1655
1725
  }
1656
1726
  /**
1657
- * The username that is currently defined and will be used by the login methods
1727
+ * Wildcards are string fragments that are placed inside the translated texts. Their main purpose is to be replaced at
1728
+ * runtime by custom values like for example a user name, a date, a numeric value, etc..
1729
+ *
1730
+ * This class helps with this process by including a parameter called 'toReplace' on all ->t methods which allows us
1731
+ * to specify a string or list of strings that will replace the respective wildcards on the translated text. Each wildcard
1732
+ * must follow the format specified here, and contain a numeric digit that will be used to find the replacement text at the
1733
+ * 'toReplace' list. For example, if we define $N as the wildcard format, and we have a translation that contains $0, $1, $2,
1734
+ * $0 will be replaced with the first element on toReplace, $1 with the second and so.
1735
+ *
1736
+ * We usually set this before initializing the class translation data
1737
+ *
1738
+ * Notice that N is mandayory on the wildcards format and the first index value is 0.
1739
+ *
1740
+ * @param value The wildcards format we want to set
1741
+ *
1742
+ * @returns The value that's been set
1658
1743
  */
1659
- set userName(v) {
1660
- this._isLogged = false;
1661
- this._token = '';
1662
- this._userName = v;
1744
+ setWildCardsFormat(value) {
1745
+ if (!value.includes('N')) {
1746
+ throw new Error("N is mandatory to replace wildcards");
1747
+ }
1748
+ this._cacheHashBaseString = value + this._missingKeyFormat + ((this._locales.length > 0) ? this._locales[0] : '');
1749
+ this._wildCardsFormat = value;
1750
+ return value;
1663
1751
  }
1664
1752
  /**
1665
- * The username that is currently defined and will be used by the login methods
1753
+ * Defines the behaviour for t(), tStartCase(), etc... methods when a key is not found on
1754
+ * a bundle or the bundle does not exist
1755
+ *
1756
+ * If missingKeyFormat is an empty string, all missing keys will return an empty value (not recommended)
1757
+ *
1758
+ * If missingKeyFormat contains a string, that string will be always returned for missing keys
1759
+ *
1760
+ * If missingKeyFormat contains a string with one of the following predefined wildcards:<br>
1761
+ * - $key will be replaced with key name. Example: get("NAME") will output [NAME] if key is not found and missingKeyFormat = '[$key]'<br>
1762
+ * - $exception (default value) will throw an exception with the problem cause description.
1763
+ *
1764
+ * @param value The missing key format we want to set
1765
+ *
1766
+ * @returns The value that's been set
1666
1767
  */
1667
- get userName() {
1668
- return this._userName;
1768
+ setMissingKeyFormat(value) {
1769
+ this._cacheHashBaseString = this._wildCardsFormat + value + ((this._locales.length > 0) ? this._locales[0] : '');
1770
+ this._missingKeyFormat = value;
1771
+ return value;
1669
1772
  }
1670
1773
  /**
1671
- * The password for the user that is currently defined and will be used by the login methods
1774
+ * @see setMissingKeyFormat()
1672
1775
  */
1673
- set password(v) {
1674
- this._isLogged = false;
1675
- this._token = '';
1676
- this._password = v;
1776
+ getMissingKeyFormat() {
1777
+ return this._missingKeyFormat;
1677
1778
  }
1678
1779
  /**
1679
- * The password for the user that is currently defined and will be used by the login methods
1780
+ * Initializes the translation system by loading and parsing bundle files from the specified JSON object.
1781
+ * After the method finishes, the class will contain all the translation data and will be ready to translate any provided key.
1782
+ *
1783
+ * @param translations A JSON object containing the translation data. The structure must be as follows:
1784
+ * { library_name: { bundle_name: { locale_code: { key1: "translation1", key2: "translation2" } } } ... }
1785
+ *
1786
+ * @param locales An array of locale codes (e.g., ['en_US', 'es_ES', 'fr_FR']) to load into this class. The order of this array
1787
+ * will determine the translation priority
1788
+ *
1789
+ * @return True if the translations get correctly loaded. Any unsuccessful initialization will throw an exception
1790
+ */
1791
+ initializeFromJson(translations, locales) {
1792
+ this._isInitialized = false;
1793
+ // Validate received locales are correct
1794
+ for (const locale of locales) {
1795
+ this._validateLocaleString(locale);
1796
+ }
1797
+ // Validate the translations object follows the right structure
1798
+ let isTranslationsValid = false;
1799
+ for (const library in translations) {
1800
+ for (const bundle in translations[library]) {
1801
+ for (const locale in translations[library][bundle]) {
1802
+ this._validateLocaleString(locale);
1803
+ isTranslationsValid = true;
1804
+ }
1805
+ }
1806
+ }
1807
+ if (!isTranslationsValid) {
1808
+ throw new Error('translations must be a non empty object with the structure: { library: { bundle: { xx_XX: { key: translation } } } }');
1809
+ }
1810
+ this._loadedTranslations = translations;
1811
+ this._locales = locales;
1812
+ this._languages = locales.map((l) => l.substring(0, 2));
1813
+ this._cacheHashBaseString = this._wildCardsFormat + this._missingKeyFormat + this._locales[0];
1814
+ return this._isInitialized = true;
1815
+ }
1816
+ /**
1817
+ * Initializes the translation system by loading and parsing bundle files from the specified translations path.
1818
+ * After the promise finishes, the class will contain all the translation data and will be ready to translate any
1819
+ * provided key.
1820
+ *
1821
+ * @param translationsPath - Url where the translations Json structure of libraries/bundles/locales/keys is available.
1822
+ * @param locales An array of locale codes (e.g., ['en_US', 'es_ES', 'fr_FR']) to load. These will be added to the translation
1823
+ * path using the following format: translationsPath/en_US-es_ES-fr_FR. The order of this array will determine the
1824
+ * translation priority
1825
+ * @param parameters Any extra parameters to be attached to the translationsPath after the locales like /param1/param2/ etc
1826
+ *
1827
+ * @return A promise that will resolve if the translations get correctly loaded, or reject with an error if load fails
1828
+ */
1829
+ initializeFromUrl(translationsPath, locales, parameters) {
1830
+ this._isInitialized = false;
1831
+ const translationsFullPath = translationsPath + '/' + locales.join('-') + '/' + parameters.join('/');
1832
+ return new Promise((resolve, reject) => {
1833
+ fetch(translationsFullPath).then(response => {
1834
+ if (!response.ok) {
1835
+ throw new Error(`HTTP error! status: ${response.status}`);
1836
+ }
1837
+ return response.json();
1838
+ }).then(data => {
1839
+ this.initializeFromJson(data, locales);
1840
+ resolve(undefined);
1841
+ }).catch(error => {
1842
+ reject(new Error(`ERROR LOADING LOCALES FROM: ${translationsFullPath}\n` + error));
1843
+ });
1844
+ });
1845
+ }
1846
+ /**
1847
+ * Check if the class has been correctly initialized and translations have been correctly loaded
1848
+ */
1849
+ isInitialized() {
1850
+ return this._isInitialized;
1851
+ }
1852
+ /**
1853
+ * Aux method to verify that this class is correctly initialized with translation data
1854
+ */
1855
+ _validateInitialized() {
1856
+ if (!this._isInitialized) {
1857
+ throw new Error('Translation service not initialized');
1858
+ }
1859
+ }
1860
+ /**
1861
+ * Checks if the specified locale is currently loaded for the currently defined bundles and paths.
1862
+ *
1863
+ * @param locale A locale to check. For example 'en_US'
1864
+ *
1865
+ * @return True if the locale is currently loaded on the class, false if not.
1680
1866
  */
1681
- get password() {
1682
- return this._password;
1867
+ isLocaleLoaded(locale) {
1868
+ this._validateLocaleString(locale);
1869
+ return this._locales.includes(locale);
1683
1870
  }
1684
1871
  /**
1685
- * Define the root url that will be used for all the API calls
1872
+ * Aux method to validate that a locale string is correctly formatted
1873
+ *
1874
+ * @param string $locale A locale string
1686
1875
  */
1687
- set baseUrl(url) {
1688
- this.httpManager.baseUrl = url;
1876
+ _validateLocaleString(locale) {
1877
+ if (!/^[a-z]{2}_[A-Z]{2}$/.test(locale)) {
1878
+ throw new Error('locale must be a valid xx_XX value');
1879
+ }
1689
1880
  }
1690
1881
  /**
1691
- * Define the root url that will be used for all the API calls
1882
+ * Checks if the specified 2 digit language is currently loaded for the currently defined bundles and paths.
1883
+ *
1884
+ * @param language A language to check. For example 'en'
1885
+ *
1886
+ * @return True if the language is currently loaded on the class, false if not.
1692
1887
  */
1693
- get baseUrl() {
1694
- return this.httpManager.baseUrl;
1888
+ isLanguageLoaded(language) {
1889
+ this._validateLanguageString(language);
1890
+ return this._languages.includes(language);
1695
1891
  }
1696
1892
  /**
1697
- * If the current browser URL contains a hash fragment (The part after the # character) that contains encoded user or password
1698
- * values, this method will parse and automatically set them as the credentials to use.
1699
- *
1700
- * The URL hash is not altered in any way by this method.
1893
+ * Aux method to validate that a language string is correctly formatted
1701
1894
  *
1702
- * @returns True if any credentials were found and loaded, false if nothing happened
1895
+ * @param language A 2 digit language string
1703
1896
  */
1704
- loadCredentialsFromURLHashFragment() {
1705
- // If the hash fragment is empty, nothing to do
1706
- if (!this.browserService.isCurrentUrlWithHashFragment()) {
1707
- return false;
1708
- }
1709
- let valuesFound = false;
1710
- // Split the hash fragment to obtain the different values that contains
1711
- let hashData = this.browserService.getCurrentUrlHashFragment().split('/');
1712
- // User name is the first element of the hash fragment. If we found a value,
1713
- // we will fill it automatically
1714
- if (hashData.length > 0) {
1715
- valuesFound = true;
1716
- this.userName = ConversionUtils.base64ToString(hashData[0]);
1717
- }
1718
- // Auto fill the password if it is received
1719
- if (hashData.length > 3) {
1720
- valuesFound = true;
1721
- this.password = ConversionUtils.base64ToString(hashData[3]);
1897
+ _validateLanguageString(language) {
1898
+ if (!/^[a-z]{2}$/.test(language)) {
1899
+ throw new Error('language must be a valid 2 digit value');
1722
1900
  }
1723
- return valuesFound;
1724
1901
  }
1725
1902
  /**
1726
- * If the current browser URL contains a hash fragment (The part after the # character) that contains encoded user, mail and
1727
- * has verification code values, this method will perform the request to server to verify that email account for the user.
1903
+ * Get the translation to the current primary locale for the given key, library and bundle
1728
1904
  *
1729
- * The URI path to the mail verification service must be correctly defined at this.mailVerifyServiceURI property.
1905
+ * @param string key The key we want to read from the specified resource bundle
1906
+ * @param string bundlePath A string with the format 'library_name/bundle_name' that is used to locate the bundle were the key to translate is found
1907
+ * @param array replaceWildcards A list of values that will replace wildcards that may be found on the translated text. Each wildcard
1908
+ * will be replaced with the element whose index on replaceWildcards matches it. Check the documentation for this.wildCardsFormat
1909
+ * property to know more about how to setup wildcards on your translations.
1730
1910
  *
1731
- * @returns A promise that resolves with the server response if the verification is successful,
1732
- * or rejects with an error if the verification fails. Response will have 4 possible values:
1733
- * undefined - meaning that the URL hash fragment didn't contain valid verification values
1734
- * -1 - meaning verification failed
1735
- * 0 - meaning verification succeeded
1736
- * 1 - meaning the email was already previouly verified
1911
+ * @see setWildCardsFormat()
1912
+ *
1913
+ * @return The translated text
1737
1914
  */
1738
- verifyUserMailFromURLHashFragment() {
1739
- // If the hash fragment is empty, nothing to do
1740
- if (this.browserService.isCurrentUrlWithHashFragment()) {
1741
- let hashData = this.browserService.getCurrentUrlHashFragment().split('/');
1742
- if (hashData.length >= 3) {
1743
- // Call for the user mail verification if user, mail and hash are received
1744
- let user = ConversionUtils.base64ToString(hashData[0]);
1745
- let mail = ConversionUtils.base64ToString(hashData[1]);
1746
- let hash = ConversionUtils.base64ToString(hashData[2]);
1747
- if (!StringUtils.isEmpty(user) && !StringUtils.isEmpty(mail) && !StringUtils.isEmpty(hash)) {
1748
- this._lastVerifiedMail = mail;
1749
- return this.call(this.mailVerifyServiceURI, { userName: user, mail: mail, hash: hash });
1915
+ t(key, bundlePath, replaceWildcards = []) {
1916
+ this._validateInitialized();
1917
+ // Create a cache key to improve performance when requesting the same key translation several times
1918
+ const cacheKey = `${this._cacheHashBaseString}${key}${bundlePath}${replaceWildcards.join('')}`;
1919
+ if (!this._keyValuesCache[cacheKey]) {
1920
+ this._forceNonEmptyString(key, '', 'key must be non empty string');
1921
+ this._forceNonEmptyString(bundlePath, '', 'bundlePath must be non empty string');
1922
+ const [library, bundle] = bundlePath.split('/');
1923
+ this._forceNonEmptyString(library, '', 'no library specified on bundlePath');
1924
+ this._forceNonEmptyString(bundle, '', 'no bundle specified on bundlePath');
1925
+ const replacementsCount = replaceWildcards.length;
1926
+ // Loop all the locales to find the first one with a value for the specified key
1927
+ for (const locale of this._locales) {
1928
+ if (this._loadedTranslations[library]?.[bundle]?.[locale]?.[key]) {
1929
+ let result = this._loadedTranslations[library][bundle][locale][key];
1930
+ // Replace all wildcards on the text with the specified replacements if any
1931
+ for (let i = 0; i < replacementsCount; i++) {
1932
+ result = this._replace(result, this._replace(this._wildCardsFormat, 'N', i.toString()), replaceWildcards[i]);
1933
+ }
1934
+ this._keyValuesCache[cacheKey] = result;
1935
+ return result;
1750
1936
  }
1751
1937
  }
1938
+ // Check if an exception needs to be thrown if the specified key is not found on this bundle
1939
+ if (this._missingKeyFormat.includes('$exception')) {
1940
+ throw new Error(`Translation key <${key}> not found on <${bundlePath}>`);
1941
+ }
1942
+ this._keyValuesCache[cacheKey] = this._replace(this._missingKeyFormat, '$key', key);
1752
1943
  }
1753
- return Promise.resolve(undefined);
1944
+ return this._keyValuesCache[cacheKey];
1754
1945
  }
1755
1946
  /**
1756
- * Obtain the lat user mail account that has been verified (or attempted to verify) by this service.
1947
+ * Get the translation for the given key and bundle as a string with all words first character capitalized
1948
+ * and all the rest of the word with lower case
1949
+ *
1950
+ * @see t()
1951
+ *
1952
+ * @returns The localized and case formatted text
1757
1953
  */
1758
- getLastVerifiedMail() {
1759
- return this._lastVerifiedMail;
1954
+ tStartCase(key, bundlePath, replaceWildcards = []) {
1955
+ return this.t(key, bundlePath, replaceWildcards).split(' ')
1956
+ .map((word) => word ? word[0].toUpperCase() + word.slice(1).toLowerCase() : '').join(' ');
1760
1957
  }
1761
1958
  /**
1762
- * Authenticates the userName and password that are currently defined at the respective properties of this service.
1763
- * Returns a promise that resolves with the server's response or rejects with an error if the login fails.
1764
- * Path to the login service must be correctly defined at this.loginWebService
1959
+ * Get the translation for the given key and bundle as an all upper case string
1765
1960
  *
1766
- * The authentication process is performed by sending an encoded credentials request to the login web service using the
1767
- * currently defined user name and psw.
1961
+ * @see t()
1768
1962
  *
1769
- * Here's an example of a call:
1963
+ * @returns The localized and case formatted text
1964
+ */
1965
+ tAllUpperCase(key, bundlePath, replaceWildcards = []) {
1966
+ return this.t(key, bundlePath, replaceWildcards).toUpperCase();
1967
+ }
1968
+ /**
1969
+ * Get the translation for the given key and bundle as an all lower case string
1770
1970
  *
1771
- * login().then(response => {
1772
- * console.log('Login successful:', response);
1773
- * }).catch(() => {
1774
- * console.error('Login failed:');
1775
- * });
1971
+ * @see t()
1776
1972
  *
1777
- * @returns A promise that resolves with the server response if the login is successful,
1778
- * or rejects with an error if the login fails.
1973
+ * @returns The localized and case formatted text
1779
1974
  */
1780
- login() {
1781
- this.dialogService.addModalBusyState();
1782
- return new Promise((resolve, reject) => {
1783
- const request = new HTTPManagerPostRequest(this.loginServiceURI);
1784
- request.ignoreGlobalPostParams = true;
1785
- const encodedCredentials = ConversionUtils.stringToBase64(ConversionUtils.stringToBase64(this._userName) + ',' + ConversionUtils.stringToBase64(this._password));
1786
- request.parameters = { data: encodedCredentials };
1787
- request.successCallback = (response) => {
1788
- if (response !== '') {
1789
- response = JSON.parse(response);
1790
- this._isLogged = true;
1791
- this._token = response.token;
1792
- this._operations = response.operations;
1793
- this.httpManager.setGlobalPostParam('token', response.token);
1794
- resolve(response);
1795
- }
1796
- else {
1797
- this._clearUserAndToken();
1798
- reject(new Error());
1799
- }
1800
- };
1801
- request.errorCallback = () => {
1802
- this._clearUserAndToken();
1803
- reject(new Error());
1804
- };
1805
- request.finallyCallback = () => {
1806
- this.dialogService.removeModalBusyState();
1807
- };
1808
- this.httpManager.execute(request);
1809
- });
1975
+ tAllLowerCase(key, bundlePath, replaceWildcards = []) {
1976
+ return this.t(key, bundlePath, replaceWildcards).toLowerCase();
1810
1977
  }
1811
1978
  /**
1812
- * Perform a request to the API service to create a new token for the user that is currently logged in.
1979
+ * Get the translation for the given key and bundle as a string with the first character as Upper case
1980
+ * and all the rest as lower case
1813
1981
  *
1814
- * @param options The parameters that will affect the token behaviour. To learn more about each option, please
1815
- * refer to the turbodepot UsersManager class createToken method documentation
1982
+ * @see t()
1816
1983
  *
1817
- * @returns A promise that resolves with the created token string if the request is successful, or rejects
1818
- * with an error (containing all the error response details) if the request fails.
1984
+ * @returns The localized and case formatted text
1819
1985
  */
1820
- createUserToken(options) {
1821
- return this.call(this.tokenCreationServiceURI, { options: options }, { resultFormat: 'STRING' });
1986
+ tFirstUpperRestLower(key, bundlePath, replaceWildcards = []) {
1987
+ const string = this.t(key, bundlePath, replaceWildcards);
1988
+ return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
1822
1989
  }
1823
1990
  /**
1824
- * Checks if the user and password credentials are filled and not empty.
1825
- * If any of the two values is empty, false will be returned
1991
+ * A list of strings containing the locales that are used by this class to translate the given keys, sorted by preference.
1992
+ * Each string is formatted as a standard locale code with language and country joined by an underscore, like: en_US, fr_FR
1993
+ *
1994
+ * When a key and bundle are requested for translation, the class will check on the first language of this
1995
+ * list for a translated text. If missing, the next one will be used, and so. This list is constructed after initialize
1996
+ * methods is called.
1997
+ *
1998
+ * @example: After loading the following list of locales ['en_US', 'es_ES', 'fr_FR'] if we call t('HELLO', 'lib1/greetings')
1999
+ * the localization manager will try to locate the en_US value for the HELLO tag on the greetings bundle for the library lib1.
2000
+ * If the tag is not found for the specified locale and bundle, the same search will be performed for the es_ES locale, and so, till a
2001
+ * value is found or no more locales are defined.
1826
2002
  */
1827
- get isUserAndPswDefined() {
1828
- return !StringUtils.isEmpty(this._userName) && !StringUtils.isEmpty(this._password);
2003
+ getLocales() {
2004
+ return this._locales;
1829
2005
  }
1830
2006
  /**
1831
- * Tells if the user name and psw that are specified on this service are currently logged or not. This means
1832
- * also a token is active.
2007
+ * A list of strings containing the languages that are used by this class to translate the given keys, sorted by preference.
2008
+ * Each string is formatted as a 2 digit language code, like: en, fr
2009
+ *
2010
+ * This list is the same as the locales() one, but containing only the language part of each locale (the first two digits)
2011
+ *
2012
+ * @see getLocales()
1833
2013
  */
1834
- get isLogged() {
1835
- return this._isLogged;
2014
+ getLanguages() {
2015
+ return this._languages;
1836
2016
  }
1837
2017
  /**
1838
- * Gives the value for the currently active user authentication token or an empty string if no user logged
2018
+ * Get the first locale from the list of loaded locales, which is the currently used to search for translated texts.
2019
+ *
2020
+ * @returns The locale that is defined as the primary one. For example: en_US, es_ES, ..
1839
2021
  */
1840
- get token() {
1841
- return this._token;
2022
+ getPrimaryLocale() {
2023
+ this._validateInitialized();
2024
+ return this._locales[0];
2025
+ }
2026
+ /**
2027
+ * Get the first language from the list of loaded locales, which is the currently used to search for translated texts.
2028
+ *
2029
+ * @returns The 2 digit language code that is defined as the primary one. For example: en, es, ..
2030
+ */
2031
+ getPrimaryLanguage() {
2032
+ this._validateInitialized();
2033
+ return this._languages[0];
2034
+ }
2035
+ /**
2036
+ * Define the locale that will be placed at the front of the currently loaded locales list (moving all the others one position to the right).
2037
+ *
2038
+ * This will be the first locale to use when trying to get a translation.
2039
+ *
2040
+ * @param locale A currently loaded locale that will be moved to the first position of the loaded locales list. If the specified locale
2041
+ * is not currently loaded, an exception will happen.
2042
+ *
2043
+ * @returns void
2044
+ */
2045
+ setPrimaryLocale(locale) {
2046
+ this._validateInitialized();
2047
+ if (!this.isLocaleLoaded(locale)) {
2048
+ throw new Error(locale + ' not loaded');
2049
+ }
2050
+ let result = [locale];
2051
+ for (let l of this._locales) {
2052
+ if (l !== locale) {
2053
+ result.push(l);
2054
+ }
2055
+ }
2056
+ this._locales = result;
2057
+ this._languages = this._locales.map((l) => l.substring(0, 2));
2058
+ this._cacheHashBaseString = this._wildCardsFormat + this._missingKeyFormat + this._locales[0];
1842
2059
  }
1843
2060
  /**
1844
- * Checks if the currently logged user is allowed to perform the specified operation.
1845
- * This will check the user permisions as defined on server side and return true if the operation is allowed
1846
- * or false if not. We can then decide what to do (hide an app section, block a button, etc)
2061
+ * Moves the specified locales to the beginning of the locales list. This also alters the translation priority by setting the first
2062
+ * provided locale as the most prioritary, the second as the next one and so.
1847
2063
  *
1848
- * @param operation The name of the operation to check
2064
+ * This method basically works exactly the same way as setPrimaryLocale but letting us add many locales at once.
1849
2065
  *
1850
- * @returns True if the operation is allowed, false otherwise
2066
+ * @see setPrimaryLocale()
2067
+ *
2068
+ * @param locales A list of locales to be moved to the beginning of the translation priority. First locales item will be the prefered
2069
+ * locale for translation, second will be the next one in case some key is not translated for the first one and so. If any of the
2070
+ * specified locales is not currently loaded, an exception will happen.
2071
+ *
2072
+ * @returns void
1851
2073
  */
1852
- isUserAllowedTo(operation) {
1853
- return this._operations.includes(operation);
2074
+ setPrimaryLocales(locales) {
2075
+ if (!Array.isArray(locales) ||
2076
+ (new Set(locales).size !== locales.length) ||
2077
+ locales.length === 0) {
2078
+ throw new Error('locales must be non empty string array with no duplicate elements');
2079
+ }
2080
+ for (let i = locales.length - 1; i >= 0; i--) {
2081
+ this.setPrimaryLocale(locales[i]);
2082
+ }
1854
2083
  }
1855
2084
  /**
1856
- * Performs a standard request to an API service and returns a promise that resolves with the response data.
1857
- *
1858
- * Following is an example of a call:
2085
+ * Define the 2 digit language that will be placed at the front of the currently loaded locales list (moving all the others one position to the right).
1859
2086
  *
1860
- * this.apiService.call('users/user-create', this.userData, {handleErrors: false}).then(response => {
2087
+ * This will be the first language to use when trying to get a translation.
1861
2088
  *
1862
- * console.log('Success:', response);
2089
+ * @param language A 2 digit language code that matches with any of the currently loaded locales, which will
2090
+ * be moved to the first position of the loaded locales list. If the specified language does not match with
2091
+ * a locale that is currently loaded, an exception will happen.
1863
2092
  *
1864
- * }).catch(error => {
2093
+ * @returns void
2094
+ */
2095
+ setPrimaryLanguage(language) {
2096
+ for (let locale of this._locales) {
2097
+ if (locale.substring(0, 2) === language) {
2098
+ this.setPrimaryLocale(locale);
2099
+ return;
2100
+ }
2101
+ }
2102
+ throw new Error(language + ' not loaded');
2103
+ }
2104
+ /**
2105
+ * Moves the locales that match the specified languages to the beginning of the locales list.
2106
+ * Works the same as setPrimaryLocales() but with a list of the 2 digit language codes that match the respective locales.
1865
2107
  *
1866
- * console.error('Error:', error.message);
1867
- * });
2108
+ * @see setPrimaryLocale()
2109
+ * @see setPrimaryLanguage()
1868
2110
  *
1869
- * @param apiPath - A relative URL (based on the defined base root url) that defines the path to the service to call.
1870
- * For example: 'users/login'
1871
- * @param parameters - An object containing key-value pairs that are sent as the request body.
1872
- * token parameter is not necessary, it is automatically appended
1873
- * @param options An object defining several options for the request:
1874
- * resultFormat: 'JSON' by default. The expected format of the response.
1875
- * busyState: Enabled by default. Enables or disables the busy state to lock user interaction while performing the http calls.
1876
- * handleErrors: Enabled by default. If set to true, an error dialog will be automatically shown when the call fails.
1877
- * If set to false, the promise will generate a reject error that must be handled by our code.
2111
+ * @param languages A list of 2 digit language codes to be moved to the beginning of the translation priority. If any of the
2112
+ * specified languages does not match with a locale that is currently loaded, an exception will happen.
1878
2113
  *
1879
- * @returns A promise that resolves with the response data correctly parsed if the request is successful, or rejects
1880
- * with an error (containing all the error response details) if the request fails.
2114
+ * @returns void
1881
2115
  */
1882
- call(apiPath, parameters = {}, options = {}) {
1883
- // Set the default values for non specified properties
1884
- options.resultFormat = options.resultFormat ?? 'JSON';
1885
- options.busyState = options.busyState ?? true;
1886
- options.handleErrors = options.handleErrors ?? true;
1887
- if (options.busyState) {
1888
- this.dialogService.addModalBusyState();
2116
+ setPrimaryLanguages(languages) {
2117
+ if (!Array.isArray(languages) ||
2118
+ (new Set(languages).size !== languages.length) ||
2119
+ languages.length === 0) {
2120
+ throw new Error('languages must be non empty string array with no duplicate elements');
2121
+ }
2122
+ for (let i = languages.length - 1; i >= 0; i--) {
2123
+ this.setPrimaryLanguage(languages[i]);
1889
2124
  }
1890
- return new Promise((resolve, reject) => {
1891
- // Create the request object
1892
- const request = new HTTPManagerPostRequest(apiPath, options.resultFormat === 'STRING' ? HTTPManagerPostRequest.STRING : HTTPManagerPostRequest.JSON);
1893
- request.parameters = parameters;
1894
- request.successCallback = (response) => {
1895
- resolve(response);
1896
- };
1897
- request.errorCallback = (errorMsg, errorCode, response) => {
1898
- if (options.handleErrors) {
1899
- this.showErrorDialog(errorMsg, errorCode, response);
1900
- }
1901
- else {
1902
- reject(new Error(errorMsg + ' ' + errorCode + ' ' + response));
1903
- }
1904
- };
1905
- request.finallyCallback = () => {
1906
- if (options.busyState) {
1907
- this.dialogService.removeModalBusyState();
1908
- }
1909
- };
1910
- this.httpManager.execute(request);
1911
- });
1912
2125
  }
1913
2126
  /**
1914
- * Performs a request to chain several api calls into a single http request, via the chain services mechanism of the turboframework API.
1915
- * Returns a promise that resolves with an array of response objects. One for each of the request calls.
2127
+ * Change the loaded locales translation preference order. The same locales that are currently loaded must be passed
2128
+ * but with a different order to change the translation priority.
1916
2129
  *
1917
- * @param apiPath - A relative URL (based on the defined base root url) that defines the path to the root of the chain services call.
1918
- * For example: 'turbosite/chain/chain-services'
1919
- * @param services - An array of objects, were each object contains a valid structure for the chain services call.
1920
- * Token parameter is not necessary, it is automatically appended
1921
- * @param options An object defining several options for the request:
1922
- * busyState: Enables or disables the busy state to lock user interaction while performing the http calls. Enabled by default
1923
- * handleErrors: Enabled by default. If set to true, an error dialog will be automatically shown when the call fails.
1924
- * If set to false, the promise will generate a reject error that must be handled by our code.
2130
+ * @param locales A list with the new locales translation priority
1925
2131
  *
1926
- * @returns A promise that resolves with the response data correctly parsed if the request is successful, or rejects
1927
- * with an error (containing all the error response details) if the request fails.
2132
+ * @returns void
1928
2133
  */
1929
- callChain(apiPath, services, options = {}) {
1930
- // Set the default values for non specified properties
1931
- options.busyState = options.busyState ?? true;
1932
- options.handleErrors = options.handleErrors ?? true;
1933
- if (options.busyState) {
1934
- this.dialogService.addModalBusyState();
2134
+ setLocalesOrder(locales) {
2135
+ if (locales.length !== this._locales.length) {
2136
+ throw new Error('locales must contain all the currently loaded locales');
1935
2137
  }
1936
- return new Promise((resolve, reject) => {
1937
- const request = new HTTPManagerPostRequest(apiPath, HTTPManagerPostRequest.JSON);
1938
- request.ignoreGlobalPostParams = true;
1939
- request.parameters = { services: services };
1940
- request.successCallback = (response) => {
1941
- resolve(response);
1942
- };
1943
- request.errorCallback = (errorMsg, errorCode, response) => {
1944
- if (options.handleErrors) {
1945
- this.showErrorDialog(errorMsg, errorCode, response);
1946
- }
1947
- else {
1948
- reject(new Error(errorMsg + ' ' + errorCode + ' ' + response));
1949
- }
1950
- };
1951
- request.finallyCallback = () => {
1952
- if (options.busyState) {
1953
- this.dialogService.removeModalBusyState();
1954
- }
1955
- };
1956
- this.httpManager.execute(request);
1957
- });
2138
+ this._validateInitialized();
2139
+ for (let locale of locales) {
2140
+ if (!this.isLocaleLoaded(locale)) {
2141
+ throw new Error(locale + ' not loaded');
2142
+ }
2143
+ }
2144
+ this._locales = locales;
2145
+ this._languages = this._locales.map((l) => l.substring(0, 2));
2146
+ this._cacheHashBaseString = this._wildCardsFormat + this._missingKeyFormat + this._locales[0];
1958
2147
  }
1959
2148
  /**
1960
- * Aux method to show an error dialog when a request fails and error handling is enabled
2149
+ * This is an aux method to implement the TurboCommons StringUtils replace method.
2150
+ * It is exactly the same as the one on the library, but we implement it here to avoid having a dependency with TurboCommons
1961
2151
  */
1962
- showErrorDialog(errorMsg, errorCode, response) {
1963
- errorMsg = errorMsg + '\n\n' + response;
1964
- if (StringUtils.isEmpty(errorMsg)) {
1965
- errorMsg = 'Unknown error. Make sure Internet connection is available';
1966
- }
1967
- this.dialogService.addDialog(DialogErrorComponent, {
1968
- width: '50vw',
1969
- texts: ['Error: ' + errorCode, errorMsg],
1970
- options: ['Ok']
1971
- });
2152
+ _replace(string, search, replacement) {
2153
+ const escapedSearch = search.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
2154
+ return string.replace(new RegExp(escapedSearch, 'g'), replacement);
1972
2155
  }
1973
2156
  /**
1974
- * Perform the logout for the currently logged user
1975
- *
1976
- * @param options An object defining several options for the request:
1977
- * handleErrors: Enabled by default. If set to true, an error dialog will be automatically shown when the call fails.
1978
- * If set to false, the promise will generate a reject error that must be handled by our code.
1979
- *
1980
- * @returns A promise that resolves correctly if the logout is correct, or rejects with an error (containing all the error
1981
- * response details) if the request fails.
2157
+ * This is an aux method to implement the TurboCommons StringUtils isEmpty method.
2158
+ * It is exactly the same as the one on the library, but we implement it here to avoid having a dependency with TurboCommons
1982
2159
  */
1983
- logout(options = {}) {
1984
- // Set the default values for non specified properties
1985
- options.handleErrors = options.handleErrors ?? true;
1986
- this.dialogService.addModalBusyState();
1987
- return new Promise((resolve, reject) => {
1988
- const request = new HTTPManagerPostRequest(this.logOutServiceURI);
1989
- request.parameters = { token: this._token };
1990
- request.successCallback = () => {
1991
- this._clearUserAndToken();
1992
- resolve(undefined);
1993
- };
1994
- request.errorCallback = (errorMsg, errorCode, response) => {
1995
- if (options.handleErrors) {
1996
- this.showErrorDialog(errorMsg, errorCode, response);
1997
- }
1998
- else {
1999
- reject(new Error(errorMsg + ' ' + errorCode + ' ' + response));
2000
- }
2001
- };
2002
- request.finallyCallback = () => {
2003
- this.dialogService.removeModalBusyState();
2004
- };
2005
- this.httpManager.execute(request);
2006
- });
2160
+ _isEmpty(string) {
2161
+ let isString = (typeof string === 'string' || string instanceof String);
2162
+ // Throw exception if non string value was received
2163
+ if (!isString) {
2164
+ // Empty or null value is considered empty
2165
+ if (string == null || string == '') {
2166
+ return true;
2167
+ }
2168
+ throw new Error("value is not a string");
2169
+ }
2170
+ return string.replace(/[ \n\r\t]/g, '') === '';
2007
2171
  }
2008
2172
  /**
2009
- * Aux methot to clear all the currently logged user data
2173
+ * This is an aux method to implement the TurboCommons StringUtils forceNonEmptyString method.
2174
+ * It is exactly the same as the one on the library, but we implement it here to avoid having a dependency with TurboCommons
2010
2175
  */
2011
- _clearUserAndToken() {
2012
- this._userName = '';
2013
- this._password = '';
2014
- this._isLogged = false;
2015
- this._token = '';
2016
- this.httpManager.setGlobalPostParam('token', '-');
2176
+ _forceNonEmptyString(value, valueName = '', errorMessage = 'must be a non empty string') {
2177
+ let isString = (typeof value === 'string' || value instanceof String);
2178
+ if (!isString || this._isEmpty(value)) {
2179
+ throw new Error(valueName + ' ' + errorMessage);
2180
+ }
2017
2181
  }
2018
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: TurboApiCallerService, deps: [{ token: DialogService }, { token: BrowserService }], target: i0.ɵɵFactoryTarget.Injectable }); }
2019
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: TurboApiCallerService, providedIn: 'root' }); }
2182
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: LocalesBaseService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2183
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: LocalesBaseService, providedIn: 'root' }); }
2020
2184
  }
2021
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: TurboApiCallerService, decorators: [{
2185
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: LocalesBaseService, decorators: [{
2022
2186
  type: Injectable,
2023
2187
  args: [{
2024
2188
  providedIn: 'root',
2025
2189
  }]
2026
- }], ctorParameters: () => [{ type: DialogService }, { type: BrowserService }] });
2190
+ }] });
2027
2191
 
2028
2192
  /**
2029
2193
  * TurboGUI is A library that helps with the most common and generic UI elements and functionalities
@@ -2557,6 +2721,7 @@ class ButtonContainerComponent extends ButtonBaseComponent {
2557
2721
  super(...arguments);
2558
2722
  /**
2559
2723
  * This is used to attach the fade animation directly to this component so it fades in and out when created and removed from the app
2724
+ * TODO: THIS IS NOT A GOOD IDEA
2560
2725
  */
2561
2726
  this.buttonFade = true;
2562
2727
  }
@@ -2612,18 +2777,29 @@ class GUINotification {
2612
2777
  * CopyRight : -> Copyright 2018 Edertone Advanded Solutions. https://www.edertone.com
2613
2778
  */
2614
2779
  /**
2615
- * Defines an application view.
2616
- * This is a base class that helps managing our application views. We should extend this class to create our views, as it forces us
2617
- * to set the viewContainerRef and the viewService, which are both used when working with views.
2780
+ * This is a base class that helps managing our application pages.
2781
+ * We should extend this class to create our application pages, as it forces us to set the viewContainerRef and the viewService,
2782
+ * which are both used when working with pages.
2618
2783
  *
2619
- * When using view services, it is important to specify the view service as a provider for the view component to ensure that it will
2620
- * be created and destroyed when the view is created and destroyed.
2784
+ * IMPORTANT!!! View services are created and destroyed when the page is created and destroyed. It is vital to specify the view service
2785
+ * as a provider for the page to ensure that it will be created and destroyed when the view is created and destroyed.
2786
+ *
2787
+ * Example:
2788
+ *
2789
+ * @Component({
2790
+ * ...
2791
+ * selector: 'app-my-view-page',
2792
+ * providers: [MyViewService]
2793
+ * ...
2794
+ *})
2621
2795
  */
2622
2796
  class View {
2623
2797
  /**
2624
- * This constructor is specifically designed to force the view to set the viewContainerRef which will be automatically assigned
2625
- * to the respective view service. If the view has no service, we can set it to null, but it is recommended to always use a service with a view,
2626
- * to store the view state and global methods that can be used by other components loaded in the view.
2798
+ * This constructor is specifically designed to force the following:
2799
+ *
2800
+ * - Set the viewContainerRef which will be automatically assigned to the respective view service.
2801
+ *
2802
+ * - If the view has no service, we can set it to null, but it is recommended to always use a service with a view, to store the view state and global methods
2627
2803
  */
2628
2804
  constructor(viewContainerRef, viewService) {
2629
2805
  this.viewContainerRef = viewContainerRef;
@@ -2764,5 +2940,5 @@ class ValidatorsPlus extends Validators {
2764
2940
  * Generated bundle index. Do not edit.
2765
2941
  */
2766
2942
 
2767
- export { AutoFocusOnDisplayDirective, AutoSelectTextOnFocusDirective, BrowserService, BusyStateBaseComponent, ButtonContainerComponent, ButtonImageComponent, DelayedMethodCallManager, DialogBaseComponent, DialogDateSelectionComponent, DialogErrorComponent, DialogMultipleOptionComponent, DialogService, DialogSingleInputComponent, DialogSingleOptionComponent, DialogSingleSelectionListComponent, DialogTwoOptionComponent, ElementCreatedDirective, ElementDestroyedDirective, FadeAnimationClass, GUINotification, GlobalErrorService, HTTPService, HTTPServiceGetRequest, HTTPServicePostRequest, LocalesService, NotificationService, SingletoneStrictClass, TurboApiCallerService, TurboGuiAngularModule, ValidatorsPlus, View, ViewService };
2943
+ export { AutoFocusOnDisplayDirective, AutoSelectTextOnFocusDirective, BrowserService, BusyStateBaseComponent, ButtonContainerComponent, ButtonImageComponent, DelayedMethodCallManager, DialogBaseComponent, DialogDateSelectionComponent, DialogErrorComponent, DialogMultipleOptionComponent, DialogService, DialogSingleInputComponent, DialogSingleOptionComponent, DialogSingleSelectionListComponent, DialogTwoOptionComponent, ElementCreatedDirective, ElementDestroyedDirective, FadeAnimationClass, GUINotification, GlobalErrorService, HTTPService, HTTPServiceGetRequest, HTTPServicePostRequest, LocalesBaseService, NotificationService, RouterBaseService, SingletoneStrictClass, TurboApiCallerService, TurboGuiAngularModule, ValidatorsPlus, View, ViewService };
2768
2944
  //# sourceMappingURL=turbogui-angular.mjs.map