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