@sumaris-net/ngx-components 18.26.14-alpha1 → 18.26.14-alpha2

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.
Files changed (65) hide show
  1. package/doc/changelog.md +41 -1
  2. package/esm2022/public_api.mjs +2 -1
  3. package/esm2022/src/app/admin/users/users.mjs +2 -2
  4. package/esm2022/src/app/core/about/about.modal.mjs +74 -13
  5. package/esm2022/src/app/core/account/account.page.mjs +1 -1
  6. package/esm2022/src/app/core/form/entity/entity-editor.class.mjs +2 -2
  7. package/esm2022/src/app/core/form/properties/properties.form.mjs +20 -3
  8. package/esm2022/src/app/core/form/properties/properties.table.mjs +3 -3
  9. package/esm2022/src/app/core/menu/menu.component.mjs +3 -3
  10. package/esm2022/src/app/core/register/register.form.mjs +1 -1
  11. package/esm2022/src/app/core/services/account.service.mjs +10 -5
  12. package/esm2022/src/app/core/services/config/core.config.mjs +6 -1
  13. package/esm2022/src/app/core/services/local-settings.service.mjs +3 -7
  14. package/esm2022/src/app/core/services/network.service.mjs +7 -1
  15. package/esm2022/src/app/core/services/platform.service.mjs +37 -7
  16. package/esm2022/src/app/core/settings/settings.page.mjs +55 -22
  17. package/esm2022/src/app/core/table/async-table.class.mjs +28 -15
  18. package/esm2022/src/app/core/table/column/row-field.component.mjs +8 -6
  19. package/esm2022/src/app/core/table/entities-async-table-datasource.class.mjs +1 -1
  20. package/esm2022/src/app/core/table/table.class.mjs +20 -14
  21. package/esm2022/src/app/shared/alerts.mjs +42 -8
  22. package/esm2022/src/app/shared/capacitor/plugins.mjs +2 -1
  23. package/esm2022/src/app/shared/form/field.component.mjs +7 -3
  24. package/esm2022/src/app/shared/gitlab/gitlab.utils.mjs +170 -0
  25. package/esm2022/src/app/shared/image/gallery/image-gallery.component.mjs +5 -5
  26. package/esm2022/src/app/shared/image/gallery/slideshow/image-gallery-slideshow.component.mjs +2 -2
  27. package/esm2022/src/app/shared/image/image.service.mjs +3 -3
  28. package/esm2022/src/app/shared/markdown/markdown.component.mjs +6 -5
  29. package/esm2022/src/app/shared/markdown/markdown.utils.mjs +3 -12
  30. package/esm2022/src/app/shared/markdown/testing/markdown.test.mjs +2 -2
  31. package/esm2022/src/app/shared/material/material.animations.mjs +7 -6
  32. package/esm2022/src/app/shared/material/paginator/material.paginator-i18n.mjs +25 -14
  33. package/esm2022/src/app/shared/platforms.mjs +23 -1
  34. package/esm2022/src/app/shared/version/versions.mjs +10 -3
  35. package/esm2022/src/app/social/feed/feed.component.mjs +3 -3
  36. package/esm2022/src/environments/environment.class.mjs +2 -1
  37. package/esm2022/src/environments/environment.mjs +2 -1
  38. package/fesm2022/sumaris-net.ngx-components.mjs +547 -139
  39. package/fesm2022/sumaris-net.ngx-components.mjs.map +1 -1
  40. package/package.json +14 -13
  41. package/public_api.d.ts +1 -0
  42. package/src/app/core/about/about.modal.d.ts +9 -2
  43. package/src/app/core/form/entity/entity-editor.class.d.ts +4 -1
  44. package/src/app/core/form/properties/properties.form.d.ts +1 -0
  45. package/src/app/core/services/account.service.d.ts +1 -1
  46. package/src/app/core/services/config/core.config.d.ts +1 -0
  47. package/src/app/core/services/platform.service.d.ts +2 -0
  48. package/src/app/core/settings/settings.page.d.ts +2 -1
  49. package/src/app/core/table/async-table.class.d.ts +15 -3
  50. package/src/app/core/table/column/row-field.component.d.ts +3 -2
  51. package/src/app/core/table/table.class.d.ts +13 -3
  52. package/src/app/shared/alerts.d.ts +39 -4
  53. package/src/app/shared/capacitor/plugins.d.ts +1 -0
  54. package/src/app/shared/form/field.component.d.ts +2 -1
  55. package/src/app/shared/gitlab/gitlab.utils.d.ts +109 -0
  56. package/src/app/shared/image/gallery/image-gallery.component.d.ts +4 -0
  57. package/src/app/shared/markdown/markdown.utils.d.ts +0 -2
  58. package/src/app/shared/material/paginator/material.paginator-i18n.d.ts +5 -1
  59. package/src/app/shared/platforms.d.ts +1 -0
  60. package/src/assets/i18n/en-US.json +2 -1
  61. package/src/assets/i18n/en.json +2 -1
  62. package/src/assets/i18n/fr.json +2 -1
  63. package/src/assets/manifest.json +1 -1
  64. package/src/environments/environment.class.d.ts +1 -0
  65. package/src/theme/_ngx-components.table.scss +5 -0
@@ -149,6 +149,7 @@ import { Style, StatusBar } from '@capacitor/status-bar';
149
149
  import { Browser } from '@capacitor/browser';
150
150
  import { Clipboard } from '@capacitor/clipboard';
151
151
  import { EdgeToEdge } from '@capawesome/capacitor-android-edge-to-edge-support';
152
+ import { Device } from '@capacitor/device';
152
153
  import * as i2$6 from '@angular/cdk/platform';
153
154
  import * as i3$5 from '@angular/cdk/clipboard';
154
155
  import { select } from '@rx-angular/state/selections';
@@ -215,6 +216,7 @@ class Environment {
215
216
  // About
216
217
  sourceUrl;
217
218
  reportIssueUrl;
219
+ changelogUrl;
218
220
  forumUrl; // TODO allow a map by locale ? ex: { [lang: string]: string };
219
221
  helpUrl; // TODO allow a map by locale ?
220
222
  privacyPolicyUrl; // TODO allow a map by locale ?
@@ -7018,6 +7020,7 @@ class CapacitorPlugins {
7018
7020
  static StatusBar = 'StatusBar';
7019
7021
  static Keyboard = 'Keyboard';
7020
7022
  static EdgeToEdge = 'EdgeToEdge';
7023
+ static Device = 'Device';
7021
7024
  //static readonly BarcodeScanner = 'BarcodeScanner';
7022
7025
  static isAvailable = Capacitor.isPluginAvailable;
7023
7026
  }
@@ -8289,6 +8292,28 @@ function isMobile(win) {
8289
8292
  function isPrint(win) {
8290
8293
  return matchMedia(win, 'print');
8291
8294
  }
8295
+ function isWebAnimationsSupported() {
8296
+ if (typeof document === 'undefined' || typeof Element === 'undefined') {
8297
+ return false;
8298
+ }
8299
+ if (typeof Element.prototype.animate !== 'function') {
8300
+ return false;
8301
+ }
8302
+ try {
8303
+ // Test minimal
8304
+ const el = document.createElement('div');
8305
+ // On n’append plus forcément au body
8306
+ const animation = el.animate([{ opacity: 0.5, offset: 0.5 }, { opacity: 1 }], { duration: 1 });
8307
+ animation.cancel();
8308
+ console.debug('[app] Web animations are well supported');
8309
+ return true;
8310
+ }
8311
+ catch {
8312
+ // Continue
8313
+ }
8314
+ console.warn('[app] Web animations are not supported');
8315
+ return false;
8316
+ }
8292
8317
 
8293
8318
  class TreeItemEntityUtils {
8294
8319
  static forward(node, filterFn, loopCount) {
@@ -14624,9 +14649,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
14624
14649
  class MatPaginatorI18n extends MatPaginatorIntl {
14625
14650
  translate;
14626
14651
  ofLabel;
14652
+ _subscription = new Subscription();
14627
14653
  constructor(translate) {
14628
14654
  super();
14629
14655
  this.translate = translate;
14656
+ // Initial load
14657
+ this._updateTranslations();
14658
+ // Re-translate on language change
14659
+ this._subscription.add(this.translate.onLangChange.subscribe(() => this._updateTranslations()));
14660
+ }
14661
+ ngOnDestroy() {
14662
+ this._subscription.unsubscribe();
14663
+ }
14664
+ getRangeLabel = (page, pageSize, length) => {
14665
+ if (length === 0 || pageSize === 0) {
14666
+ return '0 ' + this.ofLabel + ' ' + length;
14667
+ }
14668
+ length = Math.max(length, 0);
14669
+ const startIndex = page * pageSize;
14670
+ // If the start index exceeds the list length, do not try and fix the end index to the end.
14671
+ const endIndex = startIndex < length ? Math.min(startIndex + pageSize, length) : startIndex + pageSize;
14672
+ return startIndex + 1 + ' - ' + endIndex + ' ' + this.ofLabel + ' ' + length;
14673
+ };
14674
+ _getTranslationValue(translations, key, defaultValue) {
14675
+ return translations[key] !== key ? translations[key] : defaultValue;
14676
+ }
14677
+ _updateTranslations() {
14630
14678
  this.translate
14631
14679
  .get([
14632
14680
  'COMMON.PAGINATOR.ITEMS_PER_PAGE',
@@ -14647,19 +14695,6 @@ class MatPaginatorI18n extends MatPaginatorIntl {
14647
14695
  this.changes.next();
14648
14696
  });
14649
14697
  }
14650
- getRangeLabel = (page, pageSize, length) => {
14651
- if (length === 0 || pageSize === 0) {
14652
- return '0 ' + this.ofLabel + ' ' + length;
14653
- }
14654
- length = Math.max(length, 0);
14655
- const startIndex = page * pageSize;
14656
- // If the start index exceeds the list length, do not try and fix the end index to the end.
14657
- const endIndex = startIndex < length ? Math.min(startIndex + pageSize, length) : startIndex + pageSize;
14658
- return startIndex + 1 + ' - ' + endIndex + ' ' + this.ofLabel + ' ' + length;
14659
- };
14660
- _getTranslationValue(translations, key, defaultValue) {
14661
- return translations[key] !== key ? translations[key] : defaultValue;
14662
- }
14663
14698
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MatPaginatorI18n, deps: [{ token: i1$1.TranslateService }], target: i0.ɵɵFactoryTarget.Injectable });
14664
14699
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MatPaginatorI18n, providedIn: 'root' });
14665
14700
  }
@@ -14894,6 +14929,7 @@ class AppFormField {
14894
14929
  panelClass;
14895
14930
  panelWidth;
14896
14931
  onKeyupEnter = new EventEmitter();
14932
+ definitionChanged = new EventEmitter();
14897
14933
  get value() {
14898
14934
  return this.formControl.value;
14899
14935
  }
@@ -15112,6 +15148,7 @@ class AppFormField {
15112
15148
  }
15113
15149
  // Recompute tab index, because if type changed, then matInput can have changed
15114
15150
  this.updateTabIndex(100);
15151
+ this.definitionChanged.emit(definition);
15115
15152
  }
15116
15153
  updateTabIndex(timeout) {
15117
15154
  if (this.tabindex && this.tabindex !== -1) {
@@ -15128,7 +15165,7 @@ class AppFormField {
15128
15165
  }
15129
15166
  }
15130
15167
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AppFormField, deps: [{ token: i1$1.TranslateService }, { token: i0.ChangeDetectorRef }, { token: i0.Injector }, { token: i1$3.FormGroupDirective, optional: true }], target: i0.ɵɵFactoryTarget.Component });
15131
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AppFormField, selector: "app-form-field", inputs: { definition: "definition", readonly: ["readonly", "readonly", booleanAttribute], disabled: ["disabled", "disabled", booleanAttribute], formControl: "formControl", formControlName: "formControlName", placeholder: "placeholder", compact: ["compact", "compact", booleanAttribute], required: ["required", "required", booleanAttribute], hideRequiredMarker: ["hideRequiredMarker", "hideRequiredMarker", booleanAttribute], floatLabel: "floatLabel", label: "label", appearance: "appearance", subscriptSizing: "subscriptSizing", tabindex: ["tabindex", "tabindex", numberAttribute], autofocus: ["autofocus", "autofocus", booleanAttribute], clearable: ["clearable", "clearable", booleanAttribute], chipColor: "chipColor", classList: ["class", "classList"], debug: ["debug", "debug", booleanAttribute], panelClass: "panelClass", panelWidth: "panelWidth" }, outputs: { onKeyupEnter: "keyup.enter" }, providers: [
15168
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AppFormField, selector: "app-form-field", inputs: { definition: "definition", readonly: ["readonly", "readonly", booleanAttribute], disabled: ["disabled", "disabled", booleanAttribute], formControl: "formControl", formControlName: "formControlName", placeholder: "placeholder", compact: ["compact", "compact", booleanAttribute], required: ["required", "required", booleanAttribute], hideRequiredMarker: ["hideRequiredMarker", "hideRequiredMarker", booleanAttribute], floatLabel: "floatLabel", label: "label", appearance: "appearance", subscriptSizing: "subscriptSizing", tabindex: ["tabindex", "tabindex", numberAttribute], autofocus: ["autofocus", "autofocus", booleanAttribute], clearable: ["clearable", "clearable", booleanAttribute], chipColor: "chipColor", classList: ["class", "classList"], debug: ["debug", "debug", booleanAttribute], panelClass: "panelClass", panelWidth: "panelWidth" }, outputs: { onKeyupEnter: "keyup.enter", definitionChanged: "definitionChanged" }, providers: [
15132
15169
  {
15133
15170
  provide: NG_VALUE_ACCESSOR,
15134
15171
  useExisting: forwardRef(() => AppFormField),
@@ -15144,7 +15181,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
15144
15181
  useExisting: forwardRef(() => AppFormField),
15145
15182
  multi: true,
15146
15183
  },
15147
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container [ngSwitch]=\"type\">\n <!-- integer -->\n <mat-form-field\n *ngSwitchCase=\"'integer'\"\n [floatLabel]=\"floatLabel | asFloatLabelType\"\n [appearance]=\"appearance\"\n [subscriptSizing]=\"subscriptSizing\"\n [class]=\"classList\"\n >\n @if (placeholder && floatLabel !== 'never') {\n <mat-label>{{ placeholder }}</mat-label>\n }\n\n <input\n matInput\n #matInput\n type=\"number\"\n autocomplete=\"off\"\n [min]=\"definition.minValue\"\n [max]=\"definition.maxValue\"\n pattern=\"-?[0-9]*\"\n step=\"1\"\n [appAutofocus]=\"autofocus\"\n [placeholder]=\"floatLabel === 'never' ? placeholder : null\"\n (keyup.enter)=\"onKeyupEnter.emit($event)\"\n (keypress)=\"filterNumberInput($event, false)\"\n [formControl]=\"formControl\"\n [required]=\"required\"\n [readonly]=\"readonly\"\n [tabIndex]=\"tabindex\"\n (click)=\"selectInputContentFromEvent($event)\"\n />\n <mat-error *ngIf=\"formControl.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngIf=\"formControl.hasError('min')\">\n {{ (compact ? 'ERROR.FIELD_MIN_COMPACT' : 'ERROR.FIELD_MIN') | translate: formControl.errors['min'] }}\n </mat-error>\n <mat-error *ngIf=\"formControl.hasError('max')\">\n {{ (compact ? 'ERROR.FIELD_MAX_COMPACT' : 'ERROR.FIELD_MAX') | translate: formControl.errors['max'] }}\n </mat-error>\n <mat-error *ngIf=\"formControl.hasError('pattern')\">\n {{ 'ERROR.FIELD_NOT_VALID_INTEGER' | translate }}\n </mat-error>\n <mat-error *ngIf=\"formControl.hasError('integer')\">\n {{ 'ERROR.FIELD_NOT_VALID_INTEGER' | translate }}\n </mat-error>\n\n <button\n matSuffix\n *ngIf=\"clearable\"\n mat-icon-button\n tabindex=\"-1\"\n type=\"button\"\n (click)=\"formControl.reset()\"\n [hidden]=\"formControl.disabled || (formControl | formGetValue | isNilOrBlank)\"\n >\n <mat-icon>close</mat-icon>\n </button>\n\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n <ng-content select=\"mat-error\"></ng-content>\n </mat-form-field>\n\n <!-- double -->\n <mat-form-field\n *ngSwitchCase=\"'double'\"\n [floatLabel]=\"floatLabel | asFloatLabelType\"\n [appearance]=\"appearance\"\n [subscriptSizing]=\"subscriptSizing\"\n [class]=\"classList\"\n >\n @if (placeholder && floatLabel !== 'never') {\n <mat-label>{{ placeholder }}</mat-label>\n }\n\n <input\n matInput\n #matInput\n type=\"number\"\n decimal=\"true\"\n autocomplete=\"off\"\n [min]=\"definition.minValue\"\n [max]=\"definition.maxValue\"\n [appAutofocus]=\"autofocus\"\n [placeholder]=\"floatLabel === 'never' ? placeholder : null\"\n [step]=\"numberInputStep\"\n (keyup.enter)=\"onKeyupEnter.emit($event)\"\n (keypress)=\"filterNumberInput($event, true)\"\n [formControl]=\"formControl\"\n [required]=\"required\"\n [readonly]=\"readonly\"\n [tabIndex]=\"tabindex\"\n (click)=\"selectInputContentFromEvent($event)\"\n />\n <mat-error *ngIf=\"formControl.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngIf=\"formControl.hasError('decimal')\" translate>ERROR.FIELD_NOT_VALID_DECIMAL</mat-error>\n <mat-error *ngIf=\"formControl.hasError('min')\">\n {{ (compact ? 'ERROR.FIELD_MIN_COMPACT' : 'ERROR.FIELD_MIN') | translate: formControl.errors['min'] }}\n </mat-error>\n <mat-error *ngIf=\"formControl.hasError('max')\">\n {{ (compact ? 'ERROR.FIELD_MAX_COMPACT' : 'ERROR.FIELD_MAX') | translate: formControl.errors['max'] }}\n </mat-error>\n <mat-error *ngIf=\"formControl.hasError('maxDecimals')\">\n {{\n (compact ? 'ERROR.FIELD_MAXIMUM_DECIMALS_COMPACT' : 'ERROR.FIELD_MAXIMUM_DECIMALS')\n | translate: { maxDecimals: definition.maximumNumberDecimals }\n }}\n </mat-error>\n\n <button\n matSuffix\n *ngIf=\"clearable\"\n mat-icon-button\n tabindex=\"-1\"\n type=\"button\"\n (click)=\"formControl.reset()\"\n [hidden]=\"formControl.disabled || (formControl | formGetValue | isNilOrBlank)\"\n >\n <mat-icon>close</mat-icon>\n </button>\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n <ng-content select=\"mat-error\"></ng-content>\n </mat-form-field>\n\n <!-- boolean -->\n <mat-boolean-field\n *ngSwitchCase=\"'boolean'\"\n #matInput\n [class]=\"classList\"\n [formControl]=\"formControl\"\n [placeholder]=\"placeholder\"\n [floatLabel]=\"floatLabel\"\n [appearance]=\"appearance\"\n [subscriptSizing]=\"subscriptSizing\"\n [compact]=\"compact\"\n [required]=\"required\"\n [readonly]=\"readonly\"\n [tabindex]=\"tabindex\"\n >\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n <ng-content select=\"mat-error\"></ng-content>\n </mat-boolean-field>\n\n <!-- date -->\n <mat-date-field\n *ngSwitchCase=\"'date'\"\n #matInput\n [class]=\"classList\"\n [formControl]=\"formControl\"\n [placeholder]=\"compact ? ('COMMON.DATE_PLACEHOLDER' | translate) : placeholder\"\n [floatLabel]=\"floatLabel\"\n [appearance]=\"appearance\"\n [subscriptSizing]=\"subscriptSizing\"\n [required]=\"required\"\n [clearable]=\"clearable\"\n [readonly]=\"readonly\"\n [compact]=\"compact\"\n [tabindex]=\"tabindex\"\n [autofocus]=\"autofocus\"\n >\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n <ng-content select=\"mat-error\"></ng-content>\n </mat-date-field>\n\n <!-- date time -->\n <mat-date-time-field\n *ngSwitchCase=\"'dateTime'\"\n #matInput\n [class]=\"classList\"\n [formControl]=\"formControl\"\n [placeholder]=\"compact ? ('COMMON.DATE_TIME_PLACEHOLDER' | translate) : placeholder\"\n [floatLabel]=\"floatLabel\"\n [appearance]=\"appearance\"\n [subscriptSizing]=\"subscriptSizing\"\n [required]=\"required\"\n [clearable]=\"clearable\"\n [readonly]=\"readonly\"\n [compact]=\"compact\"\n [tabindex]=\"tabindex\"\n [autofocus]=\"autofocus\"\n >\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n <ng-content select=\"mat-error\"></ng-content>\n </mat-date-time-field>\n\n <!-- enum -->\n <mat-form-field\n *ngSwitchCase=\"'enum'\"\n [floatLabel]=\"floatLabel | asFloatLabelType\"\n [appearance]=\"appearance\"\n [subscriptSizing]=\"subscriptSizing\"\n [class]=\"classList\"\n [class.mat-form-field-disabled]=\"readonly\"\n >\n <ng-content select=\"[matPrefix]\"></ng-content>\n\n @if (placeholder && floatLabel !== 'never') {\n <mat-label>{{ placeholder }}</mat-label>\n }\n @if (readonly) {\n <input matInput hidden type=\"text\" readonly=\"true\" [formControl]=\"formControl\" />\n <ion-text>{{ formControl.value }}</ion-text>\n } @else {\n <mat-select\n [formControl]=\"formControl\"\n [placeholder]=\"floatLabel === 'never' ? placeholder : null\"\n [required]=\"required\"\n [tabindex]=\"tabindex\"\n (keyup.enter)=\"onKeyupEnter.emit($event)\"\n >\n <mat-option *ngFor=\"let item of _values\" [value]=\"item.key\">{{ item.value | translate }}</mat-option>\n </mat-select>\n }\n\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n\n <mat-error *ngIf=\"formControl.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <ng-content select=\"mat-error\"></ng-content>\n </mat-form-field>\n\n <!-- multi enum -->\n <mat-chips-field\n *ngSwitchCase=\"'enums'\"\n [class]=\"classList\"\n [placeholder]=\"placeholder\"\n [floatLabel]=\"floatLabel\"\n [appearance]=\"appearance\"\n [formControl]=\"formControl\"\n [clearable]=\"clearable\"\n [items]=\"_values\"\n [displayAttributes]=\"definition.autocomplete?.attributes || ['key', 'value']\"\n [config]=\"definition.autocomplete\"\n [showAllOnFocus]=\"true\"\n [required]=\"required\"\n [readonly]=\"readonly\"\n [chipColor]=\"chipColor | matColor\"\n >\n <ng-content select=\"[matPrefix]\"></ng-content>\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n <ng-content select=\"mat-error\"></ng-content>\n </mat-chips-field>\n\n <!-- color -->\n <mat-form-field\n *ngSwitchCase=\"'color'\"\n [floatLabel]=\"floatLabel | asFloatLabelType\"\n [appearance]=\"appearance\"\n [subscriptSizing]=\"subscriptSizing\"\n [class]=\"classList\"\n >\n <ion-icon margin-right name=\"color-fill\" matPrefix></ion-icon>\n\n <input\n matInput\n autocomplete=\"off\"\n [appAutofocus]=\"autofocus\"\n [formControl]=\"formControl\"\n [style.color]=\"getColorContrast(formControl.value)\"\n [style.background]=\"formControl.value\"\n [colorPicker]=\"formControl.value\"\n (colorPickerChange)=\"writeValue($event)\"\n [cpSaveClickOutside]=\"true\"\n cpPosition=\"top\"\n cpOutputFormat=\"hex\"\n [cpOKButton]=\"false\"\n [required]=\"required\"\n [readonly]=\"readonly\"\n [tabIndex]=\"tabindex\"\n />\n <mat-error *ngIf=\"formControl.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <ng-content select=\"mat-error\"></ng-content>\n </mat-form-field>\n\n <!-- string -->\n <mat-form-field\n *ngSwitchCase=\"'string'\"\n [floatLabel]=\"floatLabel | asFloatLabelType\"\n [appearance]=\"appearance\"\n [subscriptSizing]=\"subscriptSizing\"\n [class]=\"classList\"\n >\n @if (placeholder && floatLabel !== 'never') {\n <mat-label>{{ placeholder }}</mat-label>\n }\n <input\n matInput\n #matInput\n autocomplete=\"off\"\n [appAutofocus]=\"autofocus\"\n [formControl]=\"formControl\"\n [placeholder]=\"floatLabel === 'never' ? placeholder : null\"\n [required]=\"required\"\n [readonly]=\"readonly\"\n [tabIndex]=\"tabindex\"\n (click)=\"selectInputContentFromEvent($event)\"\n (keyup.enter)=\"onKeyupEnter.emit($event)\"\n (keypress)=\"filterAlphanumericalInput($event)\"\n />\n <mat-error *ngIf=\"formControl.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n\n <button\n matSuffix\n *ngIf=\"clearable\"\n mat-icon-button\n tabindex=\"-1\"\n type=\"button\"\n (click)=\"formControl.reset()\"\n [hidden]=\"formControl.disabled || (formControl | formGetValue | isNilOrBlank)\"\n >\n <mat-icon>close</mat-icon>\n </button>\n\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n <ng-content select=\"mat-error\"></ng-content>\n </mat-form-field>\n\n <!-- auto-complete -->\n <mat-autocomplete-field\n *ngSwitchCase=\"'entity'\"\n #autocompleteField\n [class]=\"classList\"\n [autofocus]=\"autofocus\"\n [placeholder]=\"placeholder\"\n [floatLabel]=\"floatLabel\"\n [appearance]=\"appearance\"\n [subscriptSizing]=\"subscriptSizing\"\n [formControl]=\"formControl\"\n [config]=\"definition.autocomplete\"\n [hideRequiredMarker]=\"hideRequiredMarker\"\n [clearable]=\"clearable\"\n [required]=\"required\"\n [readonly]=\"readonly\"\n [tabindex]=\"tabindex\"\n (keyup.enter)=\"onKeyupEnter.emit($event)\"\n [displayWith]=\"getDisplayValueFn(definition)\"\n [panelClass]=\"panelClass\"\n [panelWidth]=\"panelWidth\"\n >\n <ng-content select=\"[matPrefix]\"></ng-content>\n <ng-content select=\"[matSuffix]\"></ng-content>\n <ng-content select=\"mat-error\"></ng-content>\n </mat-autocomplete-field>\n\n <!-- multi auto-complete -->\n <mat-chips-field\n *ngSwitchCase=\"'entities'\"\n [class]=\"classList\"\n [placeholder]=\"placeholder\"\n [floatLabel]=\"floatLabel\"\n [appearance]=\"appearance\"\n [subscriptSizing]=\"subscriptSizing\"\n [formControl]=\"formControl\"\n [config]=\"definition.autocomplete\"\n [chipColor]=\"chipColor | matColor\"\n [clearable]=\"clearable\"\n [displayWith]=\"getDisplayValueFn(definition)\"\n [required]=\"required\"\n [readonly]=\"readonly\"\n [tabindex]=\"tabindex\"\n [panelClass]=\"panelClass\"\n [panelWidth]=\"panelWidth\"\n >\n <ng-content select=\"[matPrefix]\"></ng-content>\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n <ng-content select=\"mat-error\"></ng-content>\n </mat-chips-field>\n\n <!-- other -->\n <div *ngSwitchDefault>\n <mat-error *ngIf=\"type\">\n Unknown type {{ type }} for option {{ definition.key }}. Please report this error.\n </mat-error>\n <mat-error *ngIf=\"!type\">Error on option field. Please check console log for details.</mat-error>\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n <ng-content select=\"mat-error\"></ng-content>\n </div>\n</ng-container>\n", styles: [":host{display:inline-block;position:relative}button[hidden]{display:none}\n"] }]
15184
+ ], changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<ng-container [ngSwitch]=\"type\">\n <!-- integer -->\n <mat-form-field\n *ngSwitchCase=\"'integer'\"\n [floatLabel]=\"floatLabel | asFloatLabelType\"\n [appearance]=\"appearance\"\n [subscriptSizing]=\"subscriptSizing\"\n [class]=\"classList\"\n >\n @if (placeholder && floatLabel !== 'never') {\n <mat-label>{{ placeholder }}</mat-label>\n }\n\n <input\n matInput\n #matInput\n type=\"number\"\n autocomplete=\"off\"\n [min]=\"definition.minValue\"\n [max]=\"definition.maxValue\"\n pattern=\"-?[0-9]*\"\n step=\"1\"\n [appAutofocus]=\"autofocus\"\n [placeholder]=\"floatLabel === 'never' ? placeholder : null\"\n (keyup.enter)=\"onKeyupEnter.emit($event)\"\n (keypress)=\"filterNumberInput($event, false)\"\n [formControl]=\"formControl\"\n [required]=\"required\"\n [readonly]=\"readonly\"\n [tabIndex]=\"tabindex\"\n (click)=\"selectInputContentFromEvent($event)\"\n />\n <mat-error *ngIf=\"formControl.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngIf=\"formControl.hasError('min')\">\n {{ (compact ? 'ERROR.FIELD_MIN_COMPACT' : 'ERROR.FIELD_MIN') | translate: formControl.errors['min'] }}\n </mat-error>\n <mat-error *ngIf=\"formControl.hasError('max')\">\n {{ (compact ? 'ERROR.FIELD_MAX_COMPACT' : 'ERROR.FIELD_MAX') | translate: formControl.errors['max'] }}\n </mat-error>\n <mat-error *ngIf=\"formControl.hasError('pattern')\">\n {{ 'ERROR.FIELD_NOT_VALID_INTEGER' | translate }}\n </mat-error>\n <mat-error *ngIf=\"formControl.hasError('integer')\">\n {{ 'ERROR.FIELD_NOT_VALID_INTEGER' | translate }}\n </mat-error>\n\n <button\n matSuffix\n *ngIf=\"clearable\"\n mat-icon-button\n tabindex=\"-1\"\n type=\"button\"\n (click)=\"formControl.reset()\"\n [hidden]=\"formControl.disabled || (formControl | formGetValue | isNilOrBlank)\"\n >\n <mat-icon>close</mat-icon>\n </button>\n\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n <ng-content select=\"mat-error\"></ng-content>\n </mat-form-field>\n\n <!-- double -->\n <mat-form-field\n *ngSwitchCase=\"'double'\"\n [floatLabel]=\"floatLabel | asFloatLabelType\"\n [appearance]=\"appearance\"\n [subscriptSizing]=\"subscriptSizing\"\n [class]=\"classList\"\n >\n @if (placeholder && floatLabel !== 'never') {\n <mat-label>{{ placeholder }}</mat-label>\n }\n\n <input\n matInput\n #matInput\n type=\"number\"\n decimal=\"true\"\n autocomplete=\"off\"\n [min]=\"definition.minValue\"\n [max]=\"definition.maxValue\"\n [appAutofocus]=\"autofocus\"\n [placeholder]=\"floatLabel === 'never' ? placeholder : null\"\n [step]=\"numberInputStep\"\n (keyup.enter)=\"onKeyupEnter.emit($event)\"\n (keypress)=\"filterNumberInput($event, true)\"\n [formControl]=\"formControl\"\n [required]=\"required\"\n [readonly]=\"readonly\"\n [tabIndex]=\"tabindex\"\n (click)=\"selectInputContentFromEvent($event)\"\n />\n <mat-error *ngIf=\"formControl.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngIf=\"formControl.hasError('decimal')\" translate>ERROR.FIELD_NOT_VALID_DECIMAL</mat-error>\n <mat-error *ngIf=\"formControl.hasError('min')\">\n {{ (compact ? 'ERROR.FIELD_MIN_COMPACT' : 'ERROR.FIELD_MIN') | translate: formControl.errors['min'] }}\n </mat-error>\n <mat-error *ngIf=\"formControl.hasError('max')\">\n {{ (compact ? 'ERROR.FIELD_MAX_COMPACT' : 'ERROR.FIELD_MAX') | translate: formControl.errors['max'] }}\n </mat-error>\n <mat-error *ngIf=\"formControl.hasError('maxDecimals')\">\n {{\n (compact ? 'ERROR.FIELD_MAXIMUM_DECIMALS_COMPACT' : 'ERROR.FIELD_MAXIMUM_DECIMALS')\n | translate: { maxDecimals: definition.maximumNumberDecimals }\n }}\n </mat-error>\n\n <button\n matSuffix\n *ngIf=\"clearable\"\n mat-icon-button\n tabindex=\"-1\"\n type=\"button\"\n (click)=\"formControl.reset()\"\n [hidden]=\"formControl.disabled || (formControl | formGetValue | isNilOrBlank)\"\n >\n <mat-icon>close</mat-icon>\n </button>\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n <ng-content select=\"mat-error\"></ng-content>\n </mat-form-field>\n\n <!-- boolean -->\n <mat-boolean-field\n *ngSwitchCase=\"'boolean'\"\n #matInput\n [class]=\"classList\"\n [formControl]=\"formControl\"\n [placeholder]=\"placeholder\"\n [floatLabel]=\"floatLabel\"\n [appearance]=\"appearance\"\n [subscriptSizing]=\"subscriptSizing\"\n [compact]=\"compact\"\n [required]=\"required\"\n [readonly]=\"readonly\"\n [tabindex]=\"tabindex\"\n >\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n <ng-content select=\"mat-error\"></ng-content>\n </mat-boolean-field>\n\n <!-- date -->\n <mat-date-field\n *ngSwitchCase=\"'date'\"\n #matInput\n [class]=\"classList\"\n [formControl]=\"formControl\"\n [placeholder]=\"compact ? ('COMMON.DATE_PLACEHOLDER' | translate) : placeholder\"\n [floatLabel]=\"floatLabel\"\n [appearance]=\"appearance\"\n [subscriptSizing]=\"subscriptSizing\"\n [required]=\"required\"\n [clearable]=\"clearable\"\n [readonly]=\"readonly\"\n [compact]=\"compact\"\n [tabindex]=\"tabindex\"\n [autofocus]=\"autofocus\"\n >\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n <ng-content select=\"mat-error\"></ng-content>\n </mat-date-field>\n\n <!-- date time -->\n <mat-date-time-field\n *ngSwitchCase=\"'dateTime'\"\n #matInput\n [class]=\"classList\"\n [formControl]=\"formControl\"\n [placeholder]=\"compact ? ('COMMON.DATE_TIME_PLACEHOLDER' | translate) : placeholder\"\n [floatLabel]=\"floatLabel\"\n [appearance]=\"appearance\"\n [subscriptSizing]=\"subscriptSizing\"\n [required]=\"required\"\n [clearable]=\"clearable\"\n [readonly]=\"readonly\"\n [compact]=\"compact\"\n [tabindex]=\"tabindex\"\n [autofocus]=\"autofocus\"\n >\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n <ng-content select=\"mat-error\"></ng-content>\n </mat-date-time-field>\n\n <!-- enum -->\n <mat-form-field\n *ngSwitchCase=\"'enum'\"\n [floatLabel]=\"floatLabel | asFloatLabelType\"\n [appearance]=\"appearance\"\n [subscriptSizing]=\"subscriptSizing\"\n [class]=\"classList\"\n [class.mat-form-field-disabled]=\"readonly\"\n >\n <ng-content select=\"[matPrefix]\"></ng-content>\n\n @if (placeholder && floatLabel !== 'never') {\n <mat-label>{{ placeholder }}</mat-label>\n }\n @if (readonly) {\n <input matInput hidden type=\"text\" readonly=\"true\" [formControl]=\"formControl\" />\n <ion-text>{{ formControl.value }}</ion-text>\n } @else {\n <mat-select\n [formControl]=\"formControl\"\n [placeholder]=\"floatLabel === 'never' ? placeholder : null\"\n [required]=\"required\"\n [tabindex]=\"tabindex\"\n (keyup.enter)=\"onKeyupEnter.emit($event)\"\n >\n <mat-option *ngFor=\"let item of _values\" [value]=\"item.key\">{{ item.value | translate }}</mat-option>\n </mat-select>\n }\n\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n\n <mat-error *ngIf=\"formControl.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <ng-content select=\"mat-error\"></ng-content>\n </mat-form-field>\n\n <!-- multi enum -->\n <mat-chips-field\n *ngSwitchCase=\"'enums'\"\n [class]=\"classList\"\n [placeholder]=\"placeholder\"\n [floatLabel]=\"floatLabel\"\n [appearance]=\"appearance\"\n [formControl]=\"formControl\"\n [clearable]=\"clearable\"\n [items]=\"_values\"\n [displayAttributes]=\"definition.autocomplete?.attributes || ['key', 'value']\"\n [config]=\"definition.autocomplete\"\n [showAllOnFocus]=\"true\"\n [required]=\"required\"\n [readonly]=\"readonly\"\n [chipColor]=\"chipColor | matColor\"\n >\n <ng-content select=\"[matPrefix]\"></ng-content>\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n <ng-content select=\"mat-error\"></ng-content>\n </mat-chips-field>\n\n <!-- color -->\n <mat-form-field\n *ngSwitchCase=\"'color'\"\n [floatLabel]=\"floatLabel | asFloatLabelType\"\n [appearance]=\"appearance\"\n [subscriptSizing]=\"subscriptSizing\"\n [class]=\"classList\"\n >\n <ion-icon margin-right name=\"color-fill\" matPrefix></ion-icon>\n\n <input\n matInput\n autocomplete=\"off\"\n [appAutofocus]=\"autofocus\"\n [formControl]=\"formControl\"\n [style.color]=\"getColorContrast(formControl.value)\"\n [style.background]=\"formControl.value\"\n [colorPicker]=\"formControl.value\"\n (colorPickerChange)=\"writeValue($event)\"\n [cpSaveClickOutside]=\"true\"\n cpPosition=\"top\"\n cpOutputFormat=\"hex\"\n [cpOKButton]=\"false\"\n [required]=\"required\"\n [readonly]=\"readonly\"\n [tabIndex]=\"tabindex\"\n />\n <mat-error *ngIf=\"formControl.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <ng-content select=\"mat-error\"></ng-content>\n </mat-form-field>\n\n <!-- string -->\n <mat-form-field\n *ngSwitchCase=\"'string'\"\n [floatLabel]=\"floatLabel | asFloatLabelType\"\n [appearance]=\"appearance\"\n [subscriptSizing]=\"subscriptSizing\"\n [class]=\"classList\"\n >\n @if (placeholder && floatLabel !== 'never') {\n <mat-label>{{ placeholder }}</mat-label>\n }\n <input\n matInput\n #matInput\n autocomplete=\"off\"\n [appAutofocus]=\"autofocus\"\n [formControl]=\"formControl\"\n [placeholder]=\"floatLabel === 'never' ? placeholder : null\"\n [required]=\"required\"\n [readonly]=\"readonly\"\n [tabIndex]=\"tabindex\"\n (click)=\"selectInputContentFromEvent($event)\"\n (keyup.enter)=\"onKeyupEnter.emit($event)\"\n (keypress)=\"filterAlphanumericalInput($event)\"\n />\n <mat-error *ngIf=\"formControl.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n\n <button\n matSuffix\n *ngIf=\"clearable\"\n mat-icon-button\n tabindex=\"-1\"\n type=\"button\"\n (click)=\"formControl.reset()\"\n [hidden]=\"formControl.disabled || (formControl | formGetValue | isNilOrBlank)\"\n >\n <mat-icon>close</mat-icon>\n </button>\n\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n <ng-content select=\"mat-error\"></ng-content>\n </mat-form-field>\n\n <!-- auto-complete -->\n <mat-autocomplete-field\n *ngSwitchCase=\"'entity'\"\n #autocompleteField\n [class]=\"classList\"\n [autofocus]=\"autofocus\"\n [placeholder]=\"placeholder\"\n [floatLabel]=\"floatLabel\"\n [appearance]=\"appearance\"\n [subscriptSizing]=\"subscriptSizing\"\n [formControl]=\"formControl\"\n [config]=\"definition.autocomplete\"\n [hideRequiredMarker]=\"hideRequiredMarker\"\n [clearable]=\"clearable\"\n [required]=\"required\"\n [readonly]=\"readonly\"\n [tabindex]=\"tabindex\"\n (keyup.enter)=\"onKeyupEnter.emit($event)\"\n [displayWith]=\"getDisplayValueFn(definition)\"\n [panelClass]=\"panelClass\"\n [panelWidth]=\"panelWidth\"\n >\n <ng-content select=\"[matPrefix]\"></ng-content>\n <ng-content select=\"[matSuffix]\"></ng-content>\n <ng-content select=\"mat-error\"></ng-content>\n </mat-autocomplete-field>\n\n <!-- multi auto-complete -->\n <mat-chips-field\n *ngSwitchCase=\"'entities'\"\n [class]=\"classList\"\n [placeholder]=\"placeholder\"\n [floatLabel]=\"floatLabel\"\n [appearance]=\"appearance\"\n [subscriptSizing]=\"subscriptSizing\"\n [formControl]=\"formControl\"\n [config]=\"definition.autocomplete\"\n [chipColor]=\"chipColor | matColor\"\n [clearable]=\"clearable\"\n [displayWith]=\"getDisplayValueFn(definition)\"\n [required]=\"required\"\n [readonly]=\"readonly\"\n [tabindex]=\"tabindex\"\n [panelClass]=\"panelClass\"\n [panelWidth]=\"panelWidth\"\n >\n <ng-content select=\"[matPrefix]\"></ng-content>\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n <ng-content select=\"mat-error\"></ng-content>\n </mat-chips-field>\n\n <!-- other -->\n <div *ngSwitchDefault>\n <mat-error *ngIf=\"type\">\n Unknown type {{ type }} for option {{ definition.key }}. Please report this error.\n </mat-error>\n <mat-error *ngIf=\"!type\">Error on option field. Please check console log for details.</mat-error>\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n <ng-content select=\"mat-error\"></ng-content>\n </div>\n</ng-container>\n", styles: [":host{display:inline-block;position:relative}button[hidden]{display:none}\n"] }]
15148
15185
  }], ctorParameters: () => [{ type: i1$1.TranslateService }, { type: i0.ChangeDetectorRef }, { type: i0.Injector }, { type: i1$3.FormGroupDirective, decorators: [{
15149
15186
  type: Optional
15150
15187
  }] }], propDecorators: { definition: [{
@@ -15202,6 +15239,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
15202
15239
  }], onKeyupEnter: [{
15203
15240
  type: Output,
15204
15241
  args: ['keyup.enter']
15242
+ }], definitionChanged: [{
15243
+ type: Output
15205
15244
  }], matInput: [{
15206
15245
  type: ViewChild,
15207
15246
  args: ['matInput']
@@ -17150,6 +17189,7 @@ const environment = Object.freeze({
17150
17189
  // About
17151
17190
  sourceUrl: 'https://gitlab.ifremer.fr/sih-public/sumaris/ngx-sumaris-components',
17152
17191
  reportIssueUrl: 'https://gitlab.ifremer.fr/sih-public/sumaris/ngx-sumaris-components/-/issues',
17192
+ changelogUrl: 'https://gitlab.ifremer.fr/sih-public/sumaris/ngx-sumaris-components/-/blob/develop/doc/changelog.md',
17153
17193
  forumUrl: null, //'https://forum.sumaris.net',
17154
17194
  helpUrl: 'https://gitlab.ifremer.fr/sih-public/sumaris/sumaris-doc/-/blob/master/user-manual/index_fr.md',
17155
17195
  privacyPolicyUrl: 'https://gitlab.ifremer.fr/sih-public/sumaris/sumaris-app/-/raw/master/doc/privacy_policy_fr.md',
@@ -17600,15 +17640,11 @@ class LocalSettingsService extends StartableService {
17600
17640
  if (opts?.sanitize) {
17601
17641
  settings.properties = AppPropertiesUtils.sanitize(settings.properties, { definitions: this.optionDefs });
17602
17642
  }
17603
- const data = {
17604
- ...this._data,
17605
- ...settings,
17606
- };
17607
- if (equals(data, this._data))
17643
+ if (equals(settings, this._data))
17608
17644
  return; // Skip if no changes
17609
17645
  // DEBUG
17610
17646
  //console.debug('[platform] Applying local settings...', settings);
17611
- this._data = data;
17647
+ this._data = { ...this._data, ...settings };
17612
17648
  // Save locally
17613
17649
  if (opts && opts.persistImmediate) {
17614
17650
  await this.storeLocally(true);
@@ -19426,24 +19462,25 @@ const expansionAnimation = trigger('expansionAnimation', [
19426
19462
  height: '0px',
19427
19463
  minHeight: '0px',
19428
19464
  opacity: 0,
19465
+ // Applied statically via state styles: `overflow` is NOT animatable, and
19466
+ // setting it in a transition/style() keyframe triggers the Angular warning
19467
+ // "attempting to animate the not animatable property: overflow".
19468
+ overflow: 'hidden',
19429
19469
  })),
19430
19470
  // État visible (quand showSamplingStrata est true)
19431
19471
  state('true', style({
19432
19472
  height: '*',
19433
19473
  opacity: 1,
19474
+ overflow: 'hidden',
19434
19475
  })),
19435
19476
  // Transition entre les états
19436
- transition('false <=> true', [
19437
- style({ overflow: 'hidden' }),
19438
- animate('300ms ease-in-out')
19439
- ]),
19477
+ transition('false <=> true', [animate('300ms ease-in-out')]),
19440
19478
  // Transition initiale (depuis void)
19441
19479
  transition('void => *', [
19442
19480
  style({
19443
19481
  height: '0px',
19444
19482
  minHeight: '0px',
19445
19483
  opacity: 0,
19446
- overflow: 'hidden',
19447
19484
  }),
19448
19485
  animate('300ms ease-in-out'),
19449
19486
  ]),
@@ -19547,8 +19584,15 @@ class VersionUtils {
19547
19584
  * @returns {number} -1 if v1 < v2, 0 if equal, 1 if v1 > v2
19548
19585
  */
19549
19586
  function compareVersionNumbers(v1, v2, comparePrerelease = false) {
19550
- const m1 = v1.match(SEMVER_2_REGEXP);
19551
- const m2 = v2.match(SEMVER_2_REGEXP);
19587
+ const padVersion = (v) => {
19588
+ const parts = v.split('.');
19589
+ while (parts.length < 3) {
19590
+ parts.push('0');
19591
+ }
19592
+ return parts.join('.');
19593
+ };
19594
+ const m1 = padVersion(v1).match(SEMVER_2_REGEXP);
19595
+ const m2 = padVersion(v2).match(SEMVER_2_REGEXP);
19552
19596
  if (!m1 || !m2) {
19553
19597
  return NaN;
19554
19598
  }
@@ -21330,6 +21374,12 @@ class NetworkService extends StartableObservableService {
21330
21374
  return Promise.resolve(peers);
21331
21375
  }
21332
21376
  showToast(opts) {
21377
+ // Make sure local has been load
21378
+ if (!this.started) {
21379
+ return this.ready().then(() => {
21380
+ return Toasts.show(this.toastController, this.translate, opts);
21381
+ });
21382
+ }
21333
21383
  return Toasts.show(this.toastController, this.translate, opts);
21334
21384
  }
21335
21385
  addListener(name, callback) {
@@ -22481,6 +22531,11 @@ const CORE_CONFIG_OPTIONS = Object.freeze({
22481
22531
  label: 'CONFIGURATION.OPTIONS.REPORT_ISSUE_URL',
22482
22532
  type: 'string',
22483
22533
  },
22534
+ CHANGELOG_URL: {
22535
+ key: 'sumaris.changelog.url',
22536
+ label: 'CONFIGURATION.OPTIONS.CHANGELOG_URL',
22537
+ type: 'string',
22538
+ },
22484
22539
  PRIVACY_POLICY_URL: {
22485
22540
  key: 'sumaris.policy.url',
22486
22541
  label: 'CONFIGURATION.OPTIONS.PRIVACY_POLICY_URL',
@@ -24414,7 +24469,10 @@ class AccountService extends BaseGraphqlService {
24414
24469
  // Initialize GraphQL operations (allow overrides)
24415
24470
  this.queries = { ...Queries$1, ...(accountOptions?.queries || {}) };
24416
24471
  this.mutations = { ...Mutations$1, ...(accountOptions?.mutations || {}) };
24417
- this.subscriptions = { ...AccountSubscriptions, ...(accountOptions?.subscriptions || {}) };
24472
+ this.subscriptions = {
24473
+ ...AccountSubscriptions,
24474
+ ...(accountOptions?.subscriptions || {}),
24475
+ };
24418
24476
  this._enableListenChanges = !environment.account || environment.account.enableListenChanges !== false; // True by default
24419
24477
  this._listenIntervalInSeconds = toNumber(environment.account && environment.account.listenIntervalInSeconds, 0); // no timer by default
24420
24478
  this._debug = !environment.production;
@@ -25249,14 +25307,14 @@ class AccountService extends BaseGraphqlService {
25249
25307
  console.error('[account] Error while saving account locally: ' + ((err && err.message) || err), err);
25250
25308
  }
25251
25309
  }
25252
- async saveLocalSettingsRemotely(source) {
25310
+ async saveLocalSettingsRemotely(source, force) {
25253
25311
  if (!source || !this.isLogin() || source.accountInheritance === false)
25254
25312
  return; // Skip
25255
25313
  const account = this.account;
25256
25314
  // Merge local settings into account's settings
25257
25315
  const settings = UserSettings.fromObject(account.settings) || new UserSettings();
25258
25316
  const changed = settings.merge(source, this._remoteLocalSettingsKeys);
25259
- if (!changed)
25317
+ if (!changed && !force)
25260
25318
  return; // Skip if unchanged
25261
25319
  // Save remotely
25262
25320
  this.account.settings = settings;
@@ -25521,7 +25579,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
25521
25579
  }] }] });
25522
25580
  function provideAccountService() {
25523
25581
  return {
25524
- provide: APP_ACCOUNT_SERVICE, useClass: AccountService, deps: [ENVIRONMENT]
25582
+ provide: APP_ACCOUNT_SERVICE,
25583
+ useClass: AccountService,
25584
+ deps: [ENVIRONMENT],
25525
25585
  };
25526
25586
  }
25527
25587
 
@@ -26236,7 +26296,7 @@ class PlatformService extends StartableService {
26236
26296
  }
26237
26297
  download(request) {
26238
26298
  if (!request || !request.uri)
26239
- throw new Error("Missing argument 'request' or 'request.uri'");
26299
+ throw new Error('Missing argument \'request\' or \'request.uri\'');
26240
26300
  if (this.canDownload) {
26241
26301
  // Check if not already downloading file
26242
26302
  let job$ = this._downloadingJobs.get(request.uri);
@@ -26486,7 +26546,7 @@ class PlatformService extends StartableService {
26486
26546
  }
26487
26547
  showToast(opts) {
26488
26548
  if (!this.toastController)
26489
- throw new Error("Missing toastController in component's constructor");
26549
+ throw new Error('Missing toastController in component\'s constructor');
26490
26550
  return new Promise((resolve) => Toasts.show(this.toastController, this.translate, {
26491
26551
  ...opts,
26492
26552
  onWillPresent: (t) => resolve(t),
@@ -26495,6 +26555,18 @@ class PlatformService extends StartableService {
26495
26555
  async closeToast(id) {
26496
26556
  return this.toastController?.dismiss(null, null, id);
26497
26557
  }
26558
+ async getDeviceInfo() {
26559
+ if (!this.isApp())
26560
+ return null;
26561
+ try {
26562
+ return await Device.getInfo();
26563
+ }
26564
+ catch (err) {
26565
+ const logMessage = `Error while calling Device plugin: ${err.message || err}\n${err?.originalStack || JSON.stringify(err)}`;
26566
+ console.error('[platform] ' + logMessage);
26567
+ this._logger?.error('deviceInfo', logMessage);
26568
+ }
26569
+ }
26498
26570
  /* -- protected methods -- */
26499
26571
  async configureTheme(mobile, win = window) {
26500
26572
  // Listen for changes to settings dark mode
@@ -26523,19 +26595,36 @@ class PlatformService extends StartableService {
26523
26595
  console.info('[platform] Configuring Capacitor plugins...');
26524
26596
  let pluginName;
26525
26597
  try {
26598
+ // Get device info
26599
+ pluginName = CapacitorPlugins.Device;
26600
+ const deviceInfo = await this.getDeviceInfo();
26601
+ // Enable Edge-to-edge plugin on Android 15+
26602
+ // - disable for Android <=15 (fix issue sumaris-app#2076)
26603
+ // - disable for Android >=17 (edge to edge mode)
26526
26604
  pluginName = CapacitorPlugins.EdgeToEdge;
26527
26605
  if (this.isApp() && this.isAndroid()) {
26528
- await EdgeToEdge.enable();
26606
+ const enabled = VersionUtils.isCompatible('15', deviceInfo?.osVersion)
26607
+ && !VersionUtils.isCompatible('17', deviceInfo?.osVersion);
26608
+ console.debug(`[platform] Android EdgeToEdge ${enabled ? 'enabled' : 'disabled'} - OS version: ${deviceInfo?.osVersion}`);
26609
+ if (enabled) {
26610
+ await EdgeToEdge.enable();
26611
+ }
26612
+ else {
26613
+ await EdgeToEdge.disable();
26614
+ }
26529
26615
  }
26616
+ // Avoid statusbar overlays on iOS and Android 14+
26530
26617
  pluginName = CapacitorPlugins.StatusBar;
26531
26618
  if (this.isApp()) {
26532
- // Avoid overlays (Android 15 or iOS)
26533
- await StatusBar.setOverlaysWebView({ overlay: false });
26619
+ const statusBarOverlay = this.isAndroid() && VersionUtils.isCompatible('17', deviceInfo?.osVersion);
26620
+ console.debug(`[platform] StatusBar overlay ${statusBarOverlay ? 'enabled' : 'disabled'} - OS version: ${deviceInfo?.osVersion}`);
26621
+ await StatusBar.setOverlaysWebView({ overlay: statusBarOverlay });
26534
26622
  }
26535
26623
  // Hide accessory bar (iOS only)
26536
26624
  if (this.isApp() && this.isIOS()) {
26537
26625
  pluginName = CapacitorPlugins.Keyboard;
26538
26626
  // Let Capacitor config handle accessory bar
26627
+ // Comment out, because we sometime we need the accessory bar
26539
26628
  //await Keyboard.setAccessoryBarVisible({ isVisible: false });
26540
26629
  }
26541
26630
  }
@@ -26615,7 +26704,7 @@ class PlatformService extends StartableService {
26615
26704
  console.info('[platform] Checking remote app version...');
26616
26705
  const actualVersion = this.environment.version;
26617
26706
  if (isNilOrBlank(actualVersion)) {
26618
- console.error("[platform] Missing required value for 'environment.version'. Cannot check remote app version.");
26707
+ console.error('[platform] Missing required value for \'environment.version\'. Cannot check remote app version.');
26619
26708
  this.checkAppVersionTimer?.unsubscribe(); // Stop timer if exists
26620
26709
  return; // Skip
26621
26710
  }
@@ -27779,8 +27868,11 @@ class Alerts {
27779
27868
  * @param alertCtrl
27780
27869
  * @param translate
27781
27870
  * @param event
27871
+ * @param opts
27872
+ * @param opts.backdropDismiss Whether the alert can be dismissed by clicking the backdrop (default: true)
27873
+ * @param opts.keyboardClose Whether the alert can be dismissed by pressing the back button (default: true)
27782
27874
  */
27783
- static async askSaveBeforeLeave(alertCtrl, translate, event) {
27875
+ static async askSaveBeforeLeave(alertCtrl, translate, event, opts) {
27784
27876
  let confirm = false;
27785
27877
  let cancel = false;
27786
27878
  const translations = translate.instant([
@@ -27814,6 +27906,8 @@ class Alerts {
27814
27906
  },
27815
27907
  },
27816
27908
  ],
27909
+ backdropDismiss: opts?.backdropDismiss,
27910
+ keyboardClose: opts?.keyboardClose,
27817
27911
  });
27818
27912
  await alert.present();
27819
27913
  await alert.onDidDismiss();
@@ -27835,10 +27929,13 @@ class Alerts {
27835
27929
  * @param immediate is action has an immediate effect ?
27836
27930
  * @param event
27837
27931
  * @param interpolateParams
27932
+ * @param opts
27933
+ * @param opts.backdropDismiss Whether the alert can be dismissed by clicking the backdrop (default: true)
27934
+ * @param opts.keyboardClose Whether the alert can be dismissed by pressing the back button (default: true)
27838
27935
  */
27839
- static async askActionConfirmation(alertCtrl, translate, immediate, event, interpolateParams) {
27936
+ static async askActionConfirmation(alertCtrl, translate, immediate, event, opts, interpolateParams) {
27840
27937
  const messageKey = immediate === true ? 'CONFIRM.ACTION_IMMEDIATE' : 'CONFIRM.ACTION';
27841
- return Alerts.askConfirmation(messageKey, alertCtrl, translate, event, interpolateParams);
27938
+ return Alerts.askConfirmation(messageKey, alertCtrl, translate, event, opts, interpolateParams);
27842
27939
  }
27843
27940
  /**
27844
27941
  * Ask the user to confirm. If return undefined: user has cancelled
@@ -27849,13 +27946,21 @@ class Alerts {
27849
27946
  * @param translate
27850
27947
  * @param event
27851
27948
  * @param interpolateParams
27949
+ * @param opts
27950
+ * @param opts.backdropDismiss Whether the alert can be dismissed by clicking the backdrop (default: true)
27951
+ * @param opts.keyboardClose Whether the alert can be dismissed by pressing the back button (default: true)
27952
+ *
27953
+ * Note: interpolateParams and opts are merged for translation for compatibility
27852
27954
  */
27853
- static async askConfirmation(messageKey, alertCtrl, translate, event, interpolateParams) {
27955
+ static async askConfirmation(messageKey, alertCtrl, translate, event, opts, interpolateParams) {
27854
27956
  if (!alertCtrl || !translate)
27855
27957
  throw new Error("Missing required argument 'alertCtrl' or 'translate'");
27856
27958
  let confirm = false;
27857
27959
  let cancel = false;
27858
- const translations = translate.instant(['COMMON.BTN_YES_CONTINUE', 'COMMON.BTN_CANCEL', messageKey, 'CONFIRM.ALERT_HEADER'], interpolateParams);
27960
+ const translations = translate.instant(['COMMON.BTN_YES_CONTINUE', 'COMMON.BTN_CANCEL', messageKey, 'CONFIRM.ALERT_HEADER'], {
27961
+ ...interpolateParams,
27962
+ ...opts,
27963
+ });
27859
27964
  const alert = await alertCtrl.create({
27860
27965
  header: translations['CONFIRM.ALERT_HEADER'],
27861
27966
  message: translations[messageKey],
@@ -27875,6 +27980,8 @@ class Alerts {
27875
27980
  },
27876
27981
  },
27877
27982
  ],
27983
+ backdropDismiss: opts?.backdropDismiss,
27984
+ keyboardClose: opts?.keyboardClose,
27878
27985
  });
27879
27986
  await alert.present();
27880
27987
  await alert.onDidDismiss();
@@ -27895,11 +28002,19 @@ class Alerts {
27895
28002
  * @param translate
27896
28003
  * @param event
27897
28004
  * @param interpolateParams
28005
+ * @param opts
28006
+ * @param opts.backdropDismiss Whether the alert can be dismissed by clicking the backdrop (default: true)
28007
+ * @param opts.keyboardClose Whether the alert can be dismissed by pressing the back button (default: true)
28008
+ *
28009
+ * Note: interpolateParams and opts are merged for translation for compatibility
27898
28010
  */
27899
- static async askDeleteConfirmation(alertCtrl, translate, event, interpolateParams) {
28011
+ static async askDeleteConfirmation(alertCtrl, translate, event, opts, interpolateParams) {
27900
28012
  let confirm = false;
27901
28013
  let cancel = false;
27902
- const translations = translate.instant(['COMMON.BTN_YES_DELETE', 'COMMON.BTN_CANCEL', 'CONFIRM.DELETE', 'CONFIRM.ALERT_HEADER'], interpolateParams);
28014
+ const translations = translate.instant(['COMMON.BTN_YES_DELETE', 'COMMON.BTN_CANCEL', 'CONFIRM.DELETE', 'CONFIRM.ALERT_HEADER'], {
28015
+ ...interpolateParams,
28016
+ ...opts,
28017
+ });
27903
28018
  const alert = await alertCtrl.create({
27904
28019
  header: translations['CONFIRM.ALERT_HEADER'],
27905
28020
  message: translations['CONFIRM.DELETE'],
@@ -27919,6 +28034,8 @@ class Alerts {
27919
28034
  },
27920
28035
  },
27921
28036
  ],
28037
+ backdropDismiss: opts?.backdropDismiss,
28038
+ keyboardClose: opts?.keyboardClose,
27922
28039
  });
27923
28040
  await alert.present();
27924
28041
  await alert.onDidDismiss();
@@ -27966,6 +28083,8 @@ class Alerts {
27966
28083
  },
27967
28084
  },
27968
28085
  ],
28086
+ backdropDismiss: opts?.backdropDismiss,
28087
+ keyboardClose: opts?.keyboardClose,
27969
28088
  });
27970
28089
  }
27971
28090
  else {
@@ -27987,6 +28106,8 @@ class Alerts {
27987
28106
  handler: () => { },
27988
28107
  },
27989
28108
  ],
28109
+ backdropDismiss: opts?.backdropDismiss,
28110
+ keyboardClose: opts?.keyboardClose,
27990
28111
  });
27991
28112
  }
27992
28113
  await alert.present();
@@ -28007,6 +28128,8 @@ class Alerts {
28007
28128
  role: 'cancel',
28008
28129
  },
28009
28130
  ],
28131
+ backdropDismiss: opts?.backdropDismiss,
28132
+ keyboardClose: opts?.keyboardClose,
28010
28133
  });
28011
28134
  await alert.present();
28012
28135
  await alert.onDidDismiss();
@@ -29352,11 +29475,11 @@ class AppImageGallerySlideshowComponent {
29352
29475
  this.cd.markForCheck();
29353
29476
  }
29354
29477
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AppImageGallerySlideshowComponent, deps: [{ token: i2$1.AlertController }, { token: i1$1.TranslateService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
29355
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AppImageGallerySlideshowComponent, selector: "app-image-gallery-slideshow", inputs: { rows: "rows", selectedIndex: "selectedIndex", mobile: ["mobile", "mobile", booleanAttribute], readOnly: ["readOnly", "readOnly", booleanAttribute], disabled: ["disabled", "disabled", booleanAttribute], debug: ["debug", "debug", booleanAttribute], enableMouseZoom: ["enableMouseZoom", "enableMouseZoom", booleanAttribute], wheelZoomStep: "wheelZoomStep", maxZoomRatio: "maxZoomRatio", enableRotate: ["enableRotate", "enableRotate", booleanAttribute], enableCrop: ["enableCrop", "enableCrop", booleanAttribute], editionFormat: "editionFormat", editionQuality: "editionQuality", imageSizeQueryParam: "imageSizeQueryParam", modalImageSize: "modalImageSize", enableLoop: ["enableLoop", "enableLoop", booleanAttribute], canFetchMore: ["canFetchMore", "canFetchMore", booleanAttribute] }, outputs: { deleteRequested: "deleteRequested", closeRequested: "closeRequested", afterEditImage: "afterEditImage", afterEditRow: "afterEditRow", previousSlide: "previousSlide", nextSlide: "nextSlide", fetchMore: "fetchMore" }, host: { listeners: { "window:resize": "onWindowResize()" } }, viewQueries: [{ propertyName: "swiperRef", first: true, predicate: ["swiperContainer"], descendants: true }], ngImport: i0, template: "<ion-content\n class=\"ion-no-padding ion-no-margin\"\n (mousedown)=\"onPointerDown($event, swiperContainer)\"\n (mousemove)=\"onPointerMove($event, swiperContainer)\"\n (mouseup)=\"onPointerUp($event, swiperContainer)\"\n (mouseleave)=\"onPointerUp($event, swiperContainer)\"\n (touchmove)=\"onCropPointerMove($event, swiperContainer)\"\n (touchend)=\"onCropPointerUp($event, swiperContainer)\"\n (touchcancel)=\"onCropPointerUp($event, swiperContainer)\"\n>\n @let count = rows?.length || 0;\n <!-- Counter -->\n <div class=\"top-left\">\n <ion-label color=\"light\">{{ _activeIndex + 1 }} / {{ count }}</ion-label>\n </div>\n\n <!-- zoom / rotation / crop buttons: all shown together, no \"enter edit mode\" step needed -->\n <div class=\"top-right\">\n @if (!mobile) {\n <ion-fab-button size=\"small\" (click)=\"zoom(swiperContainer, true)\" color=\"light\">\n <ion-icon name=\"search\"></ion-icon>\n <ion-icon class=\"icon-secondary\" name=\"add\"></ion-icon>\n </ion-fab-button>\n <ion-fab-button size=\"small\" (click)=\"zoom(swiperContainer, false)\" color=\"light\">\n <ion-icon name=\"search\"></ion-icon>\n <ion-icon class=\"icon-secondary\" name=\"remove\"></ion-icon>\n </ion-fab-button>\n }\n @if (!readOnly && enableRotate) {\n <ion-fab-button\n size=\"small\"\n (click)=\"rotate(swiperContainer, 'left')\"\n color=\"light\"\n [title]=\"!showTooltip ? ('IMAGE.GALLERY.BTN_ROTATE_LEFT' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('IMAGE.GALLERY.BTN_ROTATE_LEFT' | translate) : ''\"\n >\n <mat-icon>rotate_left</mat-icon>\n </ion-fab-button>\n <ion-fab-button\n size=\"small\"\n (click)=\"rotate(swiperContainer, 'right')\"\n color=\"light\"\n [title]=\"!showTooltip ? ('IMAGE.GALLERY.BTN_ROTATE_RIGHT' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('IMAGE.GALLERY.BTN_ROTATE_RIGHT' | translate) : ''\"\n >\n <mat-icon>rotate_right</mat-icon>\n </ion-fab-button>\n }\n @if (!readOnly && enableCrop) {\n <ion-fab-button\n size=\"small\"\n (click)=\"toggleCropTool(swiperContainer)\"\n [color]=\"_cropToolActive ? 'tertiary' : 'light'\"\n [title]=\"\n !showTooltip\n ? ((_cropToolActive ? 'IMAGE.GALLERY.BTN_CROP_DEACTIVATE' : 'IMAGE.GALLERY.BTN_CROP_ACTIVATE') | translate)\n : ''\n \"\n [matTooltip]=\"\n showTooltip\n ? ((_cropToolActive ? 'IMAGE.GALLERY.BTN_CROP_DEACTIVATE' : 'IMAGE.GALLERY.BTN_CROP_ACTIVATE') | translate)\n : ''\n \"\n >\n <mat-icon>crop</mat-icon>\n </ion-fab-button>\n @if (_cropToolActive) {\n <ion-fab-button\n size=\"small\"\n (click)=\"resetCrop(swiperContainer)\"\n color=\"light\"\n [title]=\"!showTooltip ? ('IMAGE.GALLERY.BTN_CROP_RESET' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('IMAGE.GALLERY.BTN_CROP_RESET' | translate) : ''\"\n >\n <mat-icon>crop_free</mat-icon>\n </ion-fab-button>\n }\n }\n </div>\n\n <!-- navigation side buttons next/back: hidden while a crop/rotation edit is pending, to keep the focus on validating/cancelling it -->\n <ng-container *ngIf=\"!mobile && !hasPendingEdit && (rows | isArrayLength: { greaterThan: 1 })\">\n <div class=\"side-buttons prev\" [class.disabled]=\"isPrevDisabled\" (click)=\"slidePrev(swiperContainer)\">\n <ion-icon name=\"chevron-back\"></ion-icon>\n </div>\n <div class=\"side-buttons next\" [class.disabled]=\"isNextDisabled\" (click)=\"slideNext(swiperContainer)\">\n <ion-icon name=\"chevron-forward\"></ion-icon>\n </div>\n </ng-container>\n\n <!-- image slides -->\n <swiper-container\n #swiperContainer\n [modules]=\"swiperModules\"\n [zoom]=\"!enableMouseZoom\"\n [initialSlide]=\"selectedIndex\"\n (swiperslidechange)=\"onSlideChange(swiperContainer)\"\n (swiperinit)=\"onSwiperInit(swiperContainer)\"\n >\n @for (row of rows; track row.id; let index = $index) {\n <ng-container *ngTemplateOutlet=\"slideTemplate; context: { image: row.currentData, index }\"></ng-container>\n }\n </swiper-container>\n</ion-content>\n\n<ion-footer>\n <ion-toolbar color=\"light\">\n @if (!hasPendingEdit) {\n <ion-row>\n <ion-col size=\"4\" class=\"ion-text-start\">\n @if (!readOnly) {\n <ion-button\n (click)=\"requestDelete(swiperContainer)\"\n [disabled]=\"disabled\"\n fill=\"clear\"\n color=\"danger\"\n >\n <mat-icon slot=\"start\">delete</mat-icon>\n {{ 'COMMON.BTN_DELETE' | translate }}\n </ion-button>\n }\n </ion-col>\n <ion-col size=\"4\"></ion-col>\n <ion-col size=\"4\" class=\"ion-text-end\">\n <ion-button (click)=\"closeRequested.emit()\" color=\"tertiary\" fill=\"clear\" *ngIf=\"mobile; else desktop\">\n <ion-icon slot=\"start\" name=\"close\"></ion-icon>\n {{ 'COMMON.BTN_CLOSE' | translate }}\n </ion-button>\n <ng-template #desktop>\n <ion-button (click)=\"closeRequested.emit()\" color=\"tertiary\">\n {{ 'COMMON.BTN_CLOSE' | translate }}\n </ion-button>\n </ng-template>\n </ion-col>\n </ion-row>\n } @else {\n <!-- A crop or rotation change is pending: ask for an explicit validation before exporting the image -->\n <ion-row>\n <ion-col size=\"12\" class=\"ion-text-end\">\n <ion-button\n (click)=\"discardEdit(swiperContainer)\"\n color=\"dark\"\n fill=\"clear\"\n [title]=\"!showTooltip ? ('IMAGE.GALLERY.BTN_CANCEL_EDIT' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('IMAGE.GALLERY.BTN_CANCEL_EDIT' | translate) : ''\"\n >\n <ion-icon slot=\"start\" name=\"arrow-undo\"></ion-icon>\n {{ 'COMMON.BTN_CANCEL' | translate }}\n </ion-button>\n <ion-button (click)=\"validateEdit(swiperContainer)\" color=\"danger\">\n <ion-icon slot=\"start\" name=\"checkmark\"></ion-icon>\n {{ 'COMMON.BTN_VALIDATE' | translate }}\n </ion-button>\n </ion-col>\n </ion-row>\n }\n </ion-toolbar>\n</ion-footer>\n\n<ng-template #slideTemplate let-image=\"image\" let-index=\"index\">\n <swiper-slide>\n <div class=\"swiper-zoom-container\" [class.cdk-visually-hidden]=\"!_swiper && index !== selectedIndex\">\n\n <!-- the image -->\n @if (image?.url) {\n <ion-img class=\"swiper-zoom-target\" [src]=\"image.url | appendQueryParams: getImageSizeQueryParams()\" />\n } @else {\n <img [src]=\"image.dataUrl\" [alt]=\"image.title\" />\n }\n\n <!-- crop overlay: only rendered for the active slide -->\n @if (!readOnly && enableCrop && _cropToolActive && index === _activeIndex) {\n <div\n class=\"crop-overlay\"\n (mousedown)=\"onCropPointerDown($event, swiperContainer, 'move')\"\n (touchstart)=\"onCropPointerDown($event, swiperContainer, 'move')\"\n >\n <!-- darkened mask around the selection: clipped to the image bounds -->\n <div class=\"crop-mask\">\n <div\n class=\"crop-mask-rect\"\n [style.left.%]=\"_cropRect.x * 100\"\n [style.top.%]=\"_cropRect.y * 100\"\n [style.width.%]=\"_cropRect.width * 100\"\n [style.height.%]=\"_cropRect.height * 100\"\n ></div>\n </div>\n\n <!-- interactive selection border, grid & drag handles: never clipped, so corner/edge handles\n remain fully grabbable even when the selection touches the image bounds -->\n <div\n class=\"crop-rect\"\n [style.left.%]=\"_cropRect.x * 100\"\n [style.top.%]=\"_cropRect.y * 100\"\n [style.width.%]=\"_cropRect.width * 100\"\n [style.height.%]=\"_cropRect.height * 100\"\n >\n <div class=\"crop-grid\">\n <span class=\"line v\" style=\"left: 33.333%\"></span>\n <span class=\"line v\" style=\"left: 66.666%\"></span>\n <span class=\"line h\" style=\"top: 33.333%\"></span>\n <span class=\"line h\" style=\"top: 66.666%\"></span>\n </div>\n @for (handle of _cropHandles; track handle) {\n <div\n class=\"crop-handle {{ handle }}\"\n (mousedown)=\"onCropPointerDown($event, swiperContainer, handle)\"\n (touchstart)=\"onCropPointerDown($event, swiperContainer, handle)\"\n ></div>\n }\n </div>\n </div>\n }\n </div>\n </swiper-slide>\n</ng-template>\n", styles: [":host{display:flex;flex-direction:column;height:100%;-webkit-user-select:none;user-select:none}:host ion-content{flex:1 1 auto}:host .top-left{z-index:14;position:absolute;display:inline-grid;left:var(--ion-padding-start, 8px);top:var(--ion-padding-start, 8px)}:host .top-right{z-index:14;position:absolute;display:inline-grid;right:var(--ion-padding-start, 8px);top:var(--ion-padding-start, 8px)}:host .side-buttons{--side-buttons-width: 120px;--side-buttons-margin: calc(var(--side-buttons-width) / 3);z-index:13;position:absolute;top:0;bottom:0;width:var(--side-buttons-width)}:host .side-buttons ion-icon{top:50%;color:#fff;background:#00000080;padding:4px;border-radius:50%;font-size:2.5rem;position:absolute;opacity:0;transition-duration:.15s;transition-timing-function:ease-in}:host .side-buttons:hover{cursor:pointer}:host .side-buttons:hover ion-icon{opacity:1}:host .side-buttons.prev{left:0}:host .side-buttons.prev ion-icon{left:var(--side-buttons-margin)}:host .side-buttons.next{right:0}:host .side-buttons.next ion-icon{right:var(--side-buttons-margin)}:host .side-buttons.disabled{pointer-events:none}:host swiper-container{height:100%;background:#000}:host .swiper-zoom-container{position:relative}:host ion-fab-button .icon-secondary{top:11px;left:6px;height:14px}:host .crop-overlay{position:absolute;z-index:15;cursor:move;touch-action:none;animation:crop-overlay-fade-in .15s ease-in}:host .crop-overlay .crop-mask{position:absolute;inset:0;overflow:hidden;pointer-events:none}:host .crop-overlay .crop-mask .crop-mask-rect{position:absolute;box-shadow:0 0 0 2000px #0009}:host .crop-overlay .crop-rect{position:absolute;box-sizing:border-box;border:1px solid rgba(255,255,255,.95);cursor:move;touch-action:none}:host .crop-overlay .crop-rect .crop-grid{position:absolute;inset:0;pointer-events:none}:host .crop-overlay .crop-rect .crop-grid .line{position:absolute;background:#ffffff73}:host .crop-overlay .crop-rect .crop-grid .line.v{top:0;bottom:0;width:1px}:host .crop-overlay .crop-rect .crop-grid .line.h{left:0;right:0;height:1px}:host .crop-overlay .crop-rect .crop-handle{position:absolute;width:16px;height:16px;margin:-8px;background:#fff;border:1px solid rgba(0,0,0,.45);border-radius:50%;box-shadow:0 1px 4px #00000080;transition:transform .12s ease;touch-action:none}:host .crop-overlay .crop-rect .crop-handle:hover{transform:scale(1.3)}:host .crop-overlay .crop-rect .crop-handle.nw{left:0;top:0;cursor:nwse-resize}:host .crop-overlay .crop-rect .crop-handle.n{left:50%;top:0;cursor:ns-resize}:host .crop-overlay .crop-rect .crop-handle.ne{left:100%;top:0;cursor:nesw-resize}:host .crop-overlay .crop-rect .crop-handle.e{left:100%;top:50%;cursor:ew-resize}:host .crop-overlay .crop-rect .crop-handle.se{left:100%;top:100%;cursor:nwse-resize}:host .crop-overlay .crop-rect .crop-handle.s{left:50%;top:100%;cursor:ns-resize}:host .crop-overlay .crop-rect .crop-handle.sw{left:0;top:100%;cursor:nesw-resize}:host .crop-overlay .crop-rect .crop-handle.w{left:0;top:50%;cursor:ew-resize}@keyframes crop-overlay-fade-in{0%{opacity:0}to{opacity:1}}\n"], dependencies: [{ kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonFabButton, selector: "ion-fab-button", inputs: ["activated", "closeIcon", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "show", "size", "target", "translucent", "type"] }, { kind: "component", type: i2$1.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonImg, selector: "ion-img", inputs: ["alt", "src"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "component", type: i2$1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: i6$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i1$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "pipe", type: ArrayLengthPipe, name: "isArrayLength" }, { kind: "pipe", type: AppendQueryParamsPipePipe, name: "appendQueryParams" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
29478
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AppImageGallerySlideshowComponent, selector: "app-image-gallery-slideshow", inputs: { rows: "rows", selectedIndex: "selectedIndex", mobile: ["mobile", "mobile", booleanAttribute], readOnly: ["readOnly", "readOnly", booleanAttribute], disabled: ["disabled", "disabled", booleanAttribute], debug: ["debug", "debug", booleanAttribute], enableMouseZoom: ["enableMouseZoom", "enableMouseZoom", booleanAttribute], wheelZoomStep: "wheelZoomStep", maxZoomRatio: "maxZoomRatio", enableRotate: ["enableRotate", "enableRotate", booleanAttribute], enableCrop: ["enableCrop", "enableCrop", booleanAttribute], editionFormat: "editionFormat", editionQuality: "editionQuality", imageSizeQueryParam: "imageSizeQueryParam", modalImageSize: "modalImageSize", enableLoop: ["enableLoop", "enableLoop", booleanAttribute], canFetchMore: ["canFetchMore", "canFetchMore", booleanAttribute] }, outputs: { deleteRequested: "deleteRequested", closeRequested: "closeRequested", afterEditImage: "afterEditImage", afterEditRow: "afterEditRow", previousSlide: "previousSlide", nextSlide: "nextSlide", fetchMore: "fetchMore" }, host: { listeners: { "window:resize": "onWindowResize()" } }, viewQueries: [{ propertyName: "swiperRef", first: true, predicate: ["swiperContainer"], descendants: true }], ngImport: i0, template: "<ion-content\n class=\"ion-no-padding ion-no-margin\"\n (mousedown)=\"onPointerDown($event, swiperContainer)\"\n (mousemove)=\"onPointerMove($event, swiperContainer)\"\n (mouseup)=\"onPointerUp($event, swiperContainer)\"\n (mouseleave)=\"onPointerUp($event, swiperContainer)\"\n (touchmove)=\"onCropPointerMove($event, swiperContainer)\"\n (touchend)=\"onCropPointerUp($event, swiperContainer)\"\n (touchcancel)=\"onCropPointerUp($event, swiperContainer)\"\n>\n @let count = rows?.length || 0;\n <!-- Counter -->\n <div class=\"top-left\">\n <ion-label color=\"light\">{{ _activeIndex + 1 }} / {{ count }}</ion-label>\n </div>\n\n <!-- zoom / rotation / crop buttons: all shown together, no \"enter edit mode\" step needed -->\n <div class=\"top-right\">\n @if (!mobile) {\n <ion-fab-button size=\"small\" (click)=\"zoom(swiperContainer, true)\" color=\"light\">\n <ion-icon name=\"search\"></ion-icon>\n <ion-icon class=\"icon-secondary\" name=\"add\"></ion-icon>\n </ion-fab-button>\n <ion-fab-button size=\"small\" (click)=\"zoom(swiperContainer, false)\" color=\"light\">\n <ion-icon name=\"search\"></ion-icon>\n <ion-icon class=\"icon-secondary\" name=\"remove\"></ion-icon>\n </ion-fab-button>\n }\n @if (!readOnly && enableRotate) {\n <ion-fab-button\n size=\"small\"\n (click)=\"rotate(swiperContainer, 'left')\"\n color=\"light\"\n [title]=\"!showTooltip ? ('IMAGE.GALLERY.BTN_ROTATE_LEFT' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('IMAGE.GALLERY.BTN_ROTATE_LEFT' | translate) : ''\"\n >\n <mat-icon>rotate_left</mat-icon>\n </ion-fab-button>\n <ion-fab-button\n size=\"small\"\n (click)=\"rotate(swiperContainer, 'right')\"\n color=\"light\"\n [title]=\"!showTooltip ? ('IMAGE.GALLERY.BTN_ROTATE_RIGHT' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('IMAGE.GALLERY.BTN_ROTATE_RIGHT' | translate) : ''\"\n >\n <mat-icon>rotate_right</mat-icon>\n </ion-fab-button>\n }\n @if (!readOnly && enableCrop) {\n <ion-fab-button\n size=\"small\"\n (click)=\"toggleCropTool(swiperContainer)\"\n [color]=\"_cropToolActive ? 'tertiary' : 'light'\"\n [title]=\"\n !showTooltip\n ? ((_cropToolActive ? 'IMAGE.GALLERY.BTN_CROP_DEACTIVATE' : 'IMAGE.GALLERY.BTN_CROP_ACTIVATE') | translate)\n : ''\n \"\n [matTooltip]=\"\n showTooltip\n ? ((_cropToolActive ? 'IMAGE.GALLERY.BTN_CROP_DEACTIVATE' : 'IMAGE.GALLERY.BTN_CROP_ACTIVATE') | translate)\n : ''\n \"\n >\n <mat-icon>crop</mat-icon>\n </ion-fab-button>\n @if (_cropToolActive) {\n <ion-fab-button\n size=\"small\"\n (click)=\"resetCrop(swiperContainer)\"\n color=\"light\"\n [title]=\"!showTooltip ? ('IMAGE.GALLERY.BTN_CROP_RESET' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('IMAGE.GALLERY.BTN_CROP_RESET' | translate) : ''\"\n >\n <mat-icon>crop_free</mat-icon>\n </ion-fab-button>\n }\n }\n </div>\n\n <!-- navigation side buttons next/back: hidden while a crop/rotation edit is pending, to keep the focus on validating/cancelling it -->\n <ng-container *ngIf=\"!mobile && !hasPendingEdit && (rows | isArrayLength: { greaterThan: 1 })\">\n <div class=\"side-buttons prev\" [class.disabled]=\"isPrevDisabled\" (click)=\"slidePrev(swiperContainer)\">\n <ion-icon name=\"chevron-back\"></ion-icon>\n </div>\n <div class=\"side-buttons next\" [class.disabled]=\"isNextDisabled\" (click)=\"slideNext(swiperContainer)\">\n <ion-icon name=\"chevron-forward\"></ion-icon>\n </div>\n </ng-container>\n\n <!-- image slides -->\n <swiper-container\n #swiperContainer\n [modules]=\"swiperModules\"\n [zoom]=\"!enableMouseZoom\"\n [initialSlide]=\"selectedIndex\"\n (swiperslidechange)=\"onSlideChange(swiperContainer)\"\n (swiperinit)=\"onSwiperInit(swiperContainer)\"\n >\n @for (row of rows; track row.id; let index = $index) {\n <ng-container *ngTemplateOutlet=\"slideTemplate; context: { image: row.currentData, index }\"></ng-container>\n }\n </swiper-container>\n</ion-content>\n\n<ion-footer>\n <ion-toolbar color=\"light\">\n @if (!hasPendingEdit) {\n <ion-row>\n <ion-col size=\"4\" class=\"ion-text-start\">\n @if (!readOnly) {\n <ion-button\n (click)=\"requestDelete(swiperContainer)\"\n [disabled]=\"disabled\"\n fill=\"clear\"\n color=\"danger\"\n >\n <mat-icon slot=\"start\">delete</mat-icon>\n {{ 'COMMON.BTN_DELETE' | translate }}\n </ion-button>\n }\n </ion-col>\n <ion-col size=\"4\"></ion-col>\n <ion-col size=\"4\" class=\"ion-text-end\">\n <ion-button (click)=\"closeRequested.emit()\" color=\"tertiary\" fill=\"clear\" *ngIf=\"mobile; else desktop\">\n <ion-icon slot=\"start\" name=\"close\"></ion-icon>\n {{ 'COMMON.BTN_CLOSE' | translate }}\n </ion-button>\n <ng-template #desktop>\n <ion-button (click)=\"closeRequested.emit()\" color=\"tertiary\">\n {{ 'COMMON.BTN_CLOSE' | translate }}\n </ion-button>\n </ng-template>\n </ion-col>\n </ion-row>\n } @else {\n <!-- A crop or rotation change is pending: ask for an explicit validation before exporting the image -->\n <ion-row>\n <ion-col size=\"12\" class=\"ion-text-end\">\n <ion-button\n (click)=\"discardEdit(swiperContainer)\"\n color=\"dark\"\n fill=\"clear\"\n [title]=\"!showTooltip ? ('IMAGE.GALLERY.BTN_CANCEL_EDIT' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('IMAGE.GALLERY.BTN_CANCEL_EDIT' | translate) : ''\"\n >\n <ion-icon slot=\"start\" name=\"arrow-undo\"></ion-icon>\n {{ 'COMMON.BTN_CANCEL' | translate }}\n </ion-button>\n <ion-button (click)=\"validateEdit(swiperContainer)\" color=\"danger\">\n <ion-icon slot=\"start\" name=\"checkmark\"></ion-icon>\n {{ 'COMMON.BTN_VALIDATE' | translate }}\n </ion-button>\n </ion-col>\n </ion-row>\n }\n </ion-toolbar>\n</ion-footer>\n\n<ng-template #slideTemplate let-image=\"image\" let-index=\"index\">\n <swiper-slide>\n <div class=\"swiper-zoom-container\" [class.cdk-visually-hidden]=\"!_swiper && index !== selectedIndex\">\n\n <!-- the image -->\n @if (image?.url) {\n <ion-img class=\"swiper-zoom-target\" [src]=\"image.url | appendQueryParams: getImageSizeQueryParams()\" />\n } @else {\n <img [src]=\"image.dataUrl\" [alt]=\"image.title\" />\n }\n\n <!-- crop overlay: only rendered for the active slide -->\n @if (!readOnly && enableCrop && _cropToolActive && index === _activeIndex) {\n <div\n class=\"crop-overlay\"\n (mousedown)=\"onCropPointerDown($event, swiperContainer, 'move')\"\n (touchstart)=\"onCropPointerDown($event, swiperContainer, 'move')\"\n >\n <!-- darkened mask around the selection: clipped to the image bounds -->\n <div class=\"crop-mask\">\n <div\n class=\"crop-mask-rect\"\n [style.left.%]=\"_cropRect.x * 100\"\n [style.top.%]=\"_cropRect.y * 100\"\n [style.width.%]=\"_cropRect.width * 100\"\n [style.height.%]=\"_cropRect.height * 100\"\n ></div>\n </div>\n\n <!-- interactive selection border, grid & drag handles: never clipped, so corner/edge handles\n remain fully grabbable even when the selection touches the image bounds -->\n <div\n class=\"crop-rect\"\n [style.left.%]=\"_cropRect.x * 100\"\n [style.top.%]=\"_cropRect.y * 100\"\n [style.width.%]=\"_cropRect.width * 100\"\n [style.height.%]=\"_cropRect.height * 100\"\n >\n <div class=\"crop-grid\">\n <span class=\"line v\" style=\"left: 33.333%\"></span>\n <span class=\"line v\" style=\"left: 66.666%\"></span>\n <span class=\"line h\" style=\"top: 33.333%\"></span>\n <span class=\"line h\" style=\"top: 66.666%\"></span>\n </div>\n @for (handle of _cropHandles; track handle) {\n <div\n class=\"crop-handle {{ handle }}\"\n (mousedown)=\"onCropPointerDown($event, swiperContainer, handle)\"\n (touchstart)=\"onCropPointerDown($event, swiperContainer, handle)\"\n ></div>\n }\n </div>\n </div>\n }\n </div>\n </swiper-slide>\n</ng-template>\n", styles: [":host{display:flex;flex-direction:column;height:100%;-webkit-user-select:none;user-select:none}:host ion-content{flex:1 1 auto}:host .top-left{z-index:14;position:absolute;display:inline-grid;left:var(--ion-padding-start, 8px);top:var(--ion-padding-start, 8px)}:host .top-right{z-index:14;position:absolute;display:inline-grid;right:var(--ion-padding-start, 8px);top:var(--ion-padding-start, 8px)}:host .side-buttons{--side-buttons-width: 120px;--side-buttons-margin: calc(var(--side-buttons-width) / 3);z-index:13;position:absolute;top:0;bottom:0;width:var(--side-buttons-width)}:host .side-buttons ion-icon{top:50%;color:#fff;background:rgba(0,0,0,.5);padding:4px;border-radius:50%;font-size:2.5rem;position:absolute;opacity:0;transition-duration:.15s;transition-timing-function:ease-in}:host .side-buttons:hover{cursor:pointer}:host .side-buttons:hover ion-icon{opacity:1}:host .side-buttons.prev{left:0}:host .side-buttons.prev ion-icon{left:var(--side-buttons-margin)}:host .side-buttons.next{right:0}:host .side-buttons.next ion-icon{right:var(--side-buttons-margin)}:host .side-buttons.disabled{pointer-events:none}:host swiper-container{height:100%;background:#000}:host .swiper-zoom-container{position:relative}:host ion-fab-button .icon-secondary{top:11px;left:6px;height:14px}:host .crop-overlay{position:absolute;z-index:15;cursor:move;touch-action:none;animation:crop-overlay-fade-in .15s ease-in}:host .crop-overlay .crop-mask{position:absolute;top:0;right:0;bottom:0;left:0;overflow:hidden;pointer-events:none}:host .crop-overlay .crop-mask .crop-mask-rect{position:absolute;box-shadow:0 0 0 2000px rgba(0,0,0,.6)}:host .crop-overlay .crop-rect{position:absolute;box-sizing:border-box;border:1px solid rgba(255,255,255,.95);cursor:move;touch-action:none}:host .crop-overlay .crop-rect .crop-grid{position:absolute;top:0;right:0;bottom:0;left:0;pointer-events:none}:host .crop-overlay .crop-rect .crop-grid .line{position:absolute;background:rgba(255,255,255,.45)}:host .crop-overlay .crop-rect .crop-grid .line.v{top:0;bottom:0;width:1px}:host .crop-overlay .crop-rect .crop-grid .line.h{left:0;right:0;height:1px}:host .crop-overlay .crop-rect .crop-handle{position:absolute;width:16px;height:16px;margin:-8px;background:#fff;border:1px solid rgba(0,0,0,.45);border-radius:50%;box-shadow:0 1px 4px rgba(0,0,0,.5);transition:transform .12s ease;touch-action:none}:host .crop-overlay .crop-rect .crop-handle:hover{transform:scale(1.3)}:host .crop-overlay .crop-rect .crop-handle.nw{left:0;top:0;cursor:nwse-resize}:host .crop-overlay .crop-rect .crop-handle.n{left:50%;top:0;cursor:ns-resize}:host .crop-overlay .crop-rect .crop-handle.ne{left:100%;top:0;cursor:nesw-resize}:host .crop-overlay .crop-rect .crop-handle.e{left:100%;top:50%;cursor:ew-resize}:host .crop-overlay .crop-rect .crop-handle.se{left:100%;top:100%;cursor:nwse-resize}:host .crop-overlay .crop-rect .crop-handle.s{left:50%;top:100%;cursor:ns-resize}:host .crop-overlay .crop-rect .crop-handle.sw{left:0;top:100%;cursor:nesw-resize}:host .crop-overlay .crop-rect .crop-handle.w{left:0;top:50%;cursor:ew-resize}@keyframes crop-overlay-fade-in{0%{opacity:0}to{opacity:1}}\n"], dependencies: [{ kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonFabButton, selector: "ion-fab-button", inputs: ["activated", "closeIcon", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "show", "size", "target", "translucent", "type"] }, { kind: "component", type: i2$1.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonImg, selector: "ion-img", inputs: ["alt", "src"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "component", type: i2$1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: i6$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i1$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "pipe", type: ArrayLengthPipe, name: "isArrayLength" }, { kind: "pipe", type: AppendQueryParamsPipePipe, name: "appendQueryParams" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
29356
29479
  }
29357
29480
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AppImageGallerySlideshowComponent, decorators: [{
29358
29481
  type: Component,
29359
- args: [{ selector: 'app-image-gallery-slideshow', changeDetection: ChangeDetectionStrategy.OnPush, template: "<ion-content\n class=\"ion-no-padding ion-no-margin\"\n (mousedown)=\"onPointerDown($event, swiperContainer)\"\n (mousemove)=\"onPointerMove($event, swiperContainer)\"\n (mouseup)=\"onPointerUp($event, swiperContainer)\"\n (mouseleave)=\"onPointerUp($event, swiperContainer)\"\n (touchmove)=\"onCropPointerMove($event, swiperContainer)\"\n (touchend)=\"onCropPointerUp($event, swiperContainer)\"\n (touchcancel)=\"onCropPointerUp($event, swiperContainer)\"\n>\n @let count = rows?.length || 0;\n <!-- Counter -->\n <div class=\"top-left\">\n <ion-label color=\"light\">{{ _activeIndex + 1 }} / {{ count }}</ion-label>\n </div>\n\n <!-- zoom / rotation / crop buttons: all shown together, no \"enter edit mode\" step needed -->\n <div class=\"top-right\">\n @if (!mobile) {\n <ion-fab-button size=\"small\" (click)=\"zoom(swiperContainer, true)\" color=\"light\">\n <ion-icon name=\"search\"></ion-icon>\n <ion-icon class=\"icon-secondary\" name=\"add\"></ion-icon>\n </ion-fab-button>\n <ion-fab-button size=\"small\" (click)=\"zoom(swiperContainer, false)\" color=\"light\">\n <ion-icon name=\"search\"></ion-icon>\n <ion-icon class=\"icon-secondary\" name=\"remove\"></ion-icon>\n </ion-fab-button>\n }\n @if (!readOnly && enableRotate) {\n <ion-fab-button\n size=\"small\"\n (click)=\"rotate(swiperContainer, 'left')\"\n color=\"light\"\n [title]=\"!showTooltip ? ('IMAGE.GALLERY.BTN_ROTATE_LEFT' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('IMAGE.GALLERY.BTN_ROTATE_LEFT' | translate) : ''\"\n >\n <mat-icon>rotate_left</mat-icon>\n </ion-fab-button>\n <ion-fab-button\n size=\"small\"\n (click)=\"rotate(swiperContainer, 'right')\"\n color=\"light\"\n [title]=\"!showTooltip ? ('IMAGE.GALLERY.BTN_ROTATE_RIGHT' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('IMAGE.GALLERY.BTN_ROTATE_RIGHT' | translate) : ''\"\n >\n <mat-icon>rotate_right</mat-icon>\n </ion-fab-button>\n }\n @if (!readOnly && enableCrop) {\n <ion-fab-button\n size=\"small\"\n (click)=\"toggleCropTool(swiperContainer)\"\n [color]=\"_cropToolActive ? 'tertiary' : 'light'\"\n [title]=\"\n !showTooltip\n ? ((_cropToolActive ? 'IMAGE.GALLERY.BTN_CROP_DEACTIVATE' : 'IMAGE.GALLERY.BTN_CROP_ACTIVATE') | translate)\n : ''\n \"\n [matTooltip]=\"\n showTooltip\n ? ((_cropToolActive ? 'IMAGE.GALLERY.BTN_CROP_DEACTIVATE' : 'IMAGE.GALLERY.BTN_CROP_ACTIVATE') | translate)\n : ''\n \"\n >\n <mat-icon>crop</mat-icon>\n </ion-fab-button>\n @if (_cropToolActive) {\n <ion-fab-button\n size=\"small\"\n (click)=\"resetCrop(swiperContainer)\"\n color=\"light\"\n [title]=\"!showTooltip ? ('IMAGE.GALLERY.BTN_CROP_RESET' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('IMAGE.GALLERY.BTN_CROP_RESET' | translate) : ''\"\n >\n <mat-icon>crop_free</mat-icon>\n </ion-fab-button>\n }\n }\n </div>\n\n <!-- navigation side buttons next/back: hidden while a crop/rotation edit is pending, to keep the focus on validating/cancelling it -->\n <ng-container *ngIf=\"!mobile && !hasPendingEdit && (rows | isArrayLength: { greaterThan: 1 })\">\n <div class=\"side-buttons prev\" [class.disabled]=\"isPrevDisabled\" (click)=\"slidePrev(swiperContainer)\">\n <ion-icon name=\"chevron-back\"></ion-icon>\n </div>\n <div class=\"side-buttons next\" [class.disabled]=\"isNextDisabled\" (click)=\"slideNext(swiperContainer)\">\n <ion-icon name=\"chevron-forward\"></ion-icon>\n </div>\n </ng-container>\n\n <!-- image slides -->\n <swiper-container\n #swiperContainer\n [modules]=\"swiperModules\"\n [zoom]=\"!enableMouseZoom\"\n [initialSlide]=\"selectedIndex\"\n (swiperslidechange)=\"onSlideChange(swiperContainer)\"\n (swiperinit)=\"onSwiperInit(swiperContainer)\"\n >\n @for (row of rows; track row.id; let index = $index) {\n <ng-container *ngTemplateOutlet=\"slideTemplate; context: { image: row.currentData, index }\"></ng-container>\n }\n </swiper-container>\n</ion-content>\n\n<ion-footer>\n <ion-toolbar color=\"light\">\n @if (!hasPendingEdit) {\n <ion-row>\n <ion-col size=\"4\" class=\"ion-text-start\">\n @if (!readOnly) {\n <ion-button\n (click)=\"requestDelete(swiperContainer)\"\n [disabled]=\"disabled\"\n fill=\"clear\"\n color=\"danger\"\n >\n <mat-icon slot=\"start\">delete</mat-icon>\n {{ 'COMMON.BTN_DELETE' | translate }}\n </ion-button>\n }\n </ion-col>\n <ion-col size=\"4\"></ion-col>\n <ion-col size=\"4\" class=\"ion-text-end\">\n <ion-button (click)=\"closeRequested.emit()\" color=\"tertiary\" fill=\"clear\" *ngIf=\"mobile; else desktop\">\n <ion-icon slot=\"start\" name=\"close\"></ion-icon>\n {{ 'COMMON.BTN_CLOSE' | translate }}\n </ion-button>\n <ng-template #desktop>\n <ion-button (click)=\"closeRequested.emit()\" color=\"tertiary\">\n {{ 'COMMON.BTN_CLOSE' | translate }}\n </ion-button>\n </ng-template>\n </ion-col>\n </ion-row>\n } @else {\n <!-- A crop or rotation change is pending: ask for an explicit validation before exporting the image -->\n <ion-row>\n <ion-col size=\"12\" class=\"ion-text-end\">\n <ion-button\n (click)=\"discardEdit(swiperContainer)\"\n color=\"dark\"\n fill=\"clear\"\n [title]=\"!showTooltip ? ('IMAGE.GALLERY.BTN_CANCEL_EDIT' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('IMAGE.GALLERY.BTN_CANCEL_EDIT' | translate) : ''\"\n >\n <ion-icon slot=\"start\" name=\"arrow-undo\"></ion-icon>\n {{ 'COMMON.BTN_CANCEL' | translate }}\n </ion-button>\n <ion-button (click)=\"validateEdit(swiperContainer)\" color=\"danger\">\n <ion-icon slot=\"start\" name=\"checkmark\"></ion-icon>\n {{ 'COMMON.BTN_VALIDATE' | translate }}\n </ion-button>\n </ion-col>\n </ion-row>\n }\n </ion-toolbar>\n</ion-footer>\n\n<ng-template #slideTemplate let-image=\"image\" let-index=\"index\">\n <swiper-slide>\n <div class=\"swiper-zoom-container\" [class.cdk-visually-hidden]=\"!_swiper && index !== selectedIndex\">\n\n <!-- the image -->\n @if (image?.url) {\n <ion-img class=\"swiper-zoom-target\" [src]=\"image.url | appendQueryParams: getImageSizeQueryParams()\" />\n } @else {\n <img [src]=\"image.dataUrl\" [alt]=\"image.title\" />\n }\n\n <!-- crop overlay: only rendered for the active slide -->\n @if (!readOnly && enableCrop && _cropToolActive && index === _activeIndex) {\n <div\n class=\"crop-overlay\"\n (mousedown)=\"onCropPointerDown($event, swiperContainer, 'move')\"\n (touchstart)=\"onCropPointerDown($event, swiperContainer, 'move')\"\n >\n <!-- darkened mask around the selection: clipped to the image bounds -->\n <div class=\"crop-mask\">\n <div\n class=\"crop-mask-rect\"\n [style.left.%]=\"_cropRect.x * 100\"\n [style.top.%]=\"_cropRect.y * 100\"\n [style.width.%]=\"_cropRect.width * 100\"\n [style.height.%]=\"_cropRect.height * 100\"\n ></div>\n </div>\n\n <!-- interactive selection border, grid & drag handles: never clipped, so corner/edge handles\n remain fully grabbable even when the selection touches the image bounds -->\n <div\n class=\"crop-rect\"\n [style.left.%]=\"_cropRect.x * 100\"\n [style.top.%]=\"_cropRect.y * 100\"\n [style.width.%]=\"_cropRect.width * 100\"\n [style.height.%]=\"_cropRect.height * 100\"\n >\n <div class=\"crop-grid\">\n <span class=\"line v\" style=\"left: 33.333%\"></span>\n <span class=\"line v\" style=\"left: 66.666%\"></span>\n <span class=\"line h\" style=\"top: 33.333%\"></span>\n <span class=\"line h\" style=\"top: 66.666%\"></span>\n </div>\n @for (handle of _cropHandles; track handle) {\n <div\n class=\"crop-handle {{ handle }}\"\n (mousedown)=\"onCropPointerDown($event, swiperContainer, handle)\"\n (touchstart)=\"onCropPointerDown($event, swiperContainer, handle)\"\n ></div>\n }\n </div>\n </div>\n }\n </div>\n </swiper-slide>\n</ng-template>\n", styles: [":host{display:flex;flex-direction:column;height:100%;-webkit-user-select:none;user-select:none}:host ion-content{flex:1 1 auto}:host .top-left{z-index:14;position:absolute;display:inline-grid;left:var(--ion-padding-start, 8px);top:var(--ion-padding-start, 8px)}:host .top-right{z-index:14;position:absolute;display:inline-grid;right:var(--ion-padding-start, 8px);top:var(--ion-padding-start, 8px)}:host .side-buttons{--side-buttons-width: 120px;--side-buttons-margin: calc(var(--side-buttons-width) / 3);z-index:13;position:absolute;top:0;bottom:0;width:var(--side-buttons-width)}:host .side-buttons ion-icon{top:50%;color:#fff;background:#00000080;padding:4px;border-radius:50%;font-size:2.5rem;position:absolute;opacity:0;transition-duration:.15s;transition-timing-function:ease-in}:host .side-buttons:hover{cursor:pointer}:host .side-buttons:hover ion-icon{opacity:1}:host .side-buttons.prev{left:0}:host .side-buttons.prev ion-icon{left:var(--side-buttons-margin)}:host .side-buttons.next{right:0}:host .side-buttons.next ion-icon{right:var(--side-buttons-margin)}:host .side-buttons.disabled{pointer-events:none}:host swiper-container{height:100%;background:#000}:host .swiper-zoom-container{position:relative}:host ion-fab-button .icon-secondary{top:11px;left:6px;height:14px}:host .crop-overlay{position:absolute;z-index:15;cursor:move;touch-action:none;animation:crop-overlay-fade-in .15s ease-in}:host .crop-overlay .crop-mask{position:absolute;inset:0;overflow:hidden;pointer-events:none}:host .crop-overlay .crop-mask .crop-mask-rect{position:absolute;box-shadow:0 0 0 2000px #0009}:host .crop-overlay .crop-rect{position:absolute;box-sizing:border-box;border:1px solid rgba(255,255,255,.95);cursor:move;touch-action:none}:host .crop-overlay .crop-rect .crop-grid{position:absolute;inset:0;pointer-events:none}:host .crop-overlay .crop-rect .crop-grid .line{position:absolute;background:#ffffff73}:host .crop-overlay .crop-rect .crop-grid .line.v{top:0;bottom:0;width:1px}:host .crop-overlay .crop-rect .crop-grid .line.h{left:0;right:0;height:1px}:host .crop-overlay .crop-rect .crop-handle{position:absolute;width:16px;height:16px;margin:-8px;background:#fff;border:1px solid rgba(0,0,0,.45);border-radius:50%;box-shadow:0 1px 4px #00000080;transition:transform .12s ease;touch-action:none}:host .crop-overlay .crop-rect .crop-handle:hover{transform:scale(1.3)}:host .crop-overlay .crop-rect .crop-handle.nw{left:0;top:0;cursor:nwse-resize}:host .crop-overlay .crop-rect .crop-handle.n{left:50%;top:0;cursor:ns-resize}:host .crop-overlay .crop-rect .crop-handle.ne{left:100%;top:0;cursor:nesw-resize}:host .crop-overlay .crop-rect .crop-handle.e{left:100%;top:50%;cursor:ew-resize}:host .crop-overlay .crop-rect .crop-handle.se{left:100%;top:100%;cursor:nwse-resize}:host .crop-overlay .crop-rect .crop-handle.s{left:50%;top:100%;cursor:ns-resize}:host .crop-overlay .crop-rect .crop-handle.sw{left:0;top:100%;cursor:nesw-resize}:host .crop-overlay .crop-rect .crop-handle.w{left:0;top:50%;cursor:ew-resize}@keyframes crop-overlay-fade-in{0%{opacity:0}to{opacity:1}}\n"] }]
29482
+ args: [{ selector: 'app-image-gallery-slideshow', changeDetection: ChangeDetectionStrategy.OnPush, template: "<ion-content\n class=\"ion-no-padding ion-no-margin\"\n (mousedown)=\"onPointerDown($event, swiperContainer)\"\n (mousemove)=\"onPointerMove($event, swiperContainer)\"\n (mouseup)=\"onPointerUp($event, swiperContainer)\"\n (mouseleave)=\"onPointerUp($event, swiperContainer)\"\n (touchmove)=\"onCropPointerMove($event, swiperContainer)\"\n (touchend)=\"onCropPointerUp($event, swiperContainer)\"\n (touchcancel)=\"onCropPointerUp($event, swiperContainer)\"\n>\n @let count = rows?.length || 0;\n <!-- Counter -->\n <div class=\"top-left\">\n <ion-label color=\"light\">{{ _activeIndex + 1 }} / {{ count }}</ion-label>\n </div>\n\n <!-- zoom / rotation / crop buttons: all shown together, no \"enter edit mode\" step needed -->\n <div class=\"top-right\">\n @if (!mobile) {\n <ion-fab-button size=\"small\" (click)=\"zoom(swiperContainer, true)\" color=\"light\">\n <ion-icon name=\"search\"></ion-icon>\n <ion-icon class=\"icon-secondary\" name=\"add\"></ion-icon>\n </ion-fab-button>\n <ion-fab-button size=\"small\" (click)=\"zoom(swiperContainer, false)\" color=\"light\">\n <ion-icon name=\"search\"></ion-icon>\n <ion-icon class=\"icon-secondary\" name=\"remove\"></ion-icon>\n </ion-fab-button>\n }\n @if (!readOnly && enableRotate) {\n <ion-fab-button\n size=\"small\"\n (click)=\"rotate(swiperContainer, 'left')\"\n color=\"light\"\n [title]=\"!showTooltip ? ('IMAGE.GALLERY.BTN_ROTATE_LEFT' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('IMAGE.GALLERY.BTN_ROTATE_LEFT' | translate) : ''\"\n >\n <mat-icon>rotate_left</mat-icon>\n </ion-fab-button>\n <ion-fab-button\n size=\"small\"\n (click)=\"rotate(swiperContainer, 'right')\"\n color=\"light\"\n [title]=\"!showTooltip ? ('IMAGE.GALLERY.BTN_ROTATE_RIGHT' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('IMAGE.GALLERY.BTN_ROTATE_RIGHT' | translate) : ''\"\n >\n <mat-icon>rotate_right</mat-icon>\n </ion-fab-button>\n }\n @if (!readOnly && enableCrop) {\n <ion-fab-button\n size=\"small\"\n (click)=\"toggleCropTool(swiperContainer)\"\n [color]=\"_cropToolActive ? 'tertiary' : 'light'\"\n [title]=\"\n !showTooltip\n ? ((_cropToolActive ? 'IMAGE.GALLERY.BTN_CROP_DEACTIVATE' : 'IMAGE.GALLERY.BTN_CROP_ACTIVATE') | translate)\n : ''\n \"\n [matTooltip]=\"\n showTooltip\n ? ((_cropToolActive ? 'IMAGE.GALLERY.BTN_CROP_DEACTIVATE' : 'IMAGE.GALLERY.BTN_CROP_ACTIVATE') | translate)\n : ''\n \"\n >\n <mat-icon>crop</mat-icon>\n </ion-fab-button>\n @if (_cropToolActive) {\n <ion-fab-button\n size=\"small\"\n (click)=\"resetCrop(swiperContainer)\"\n color=\"light\"\n [title]=\"!showTooltip ? ('IMAGE.GALLERY.BTN_CROP_RESET' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('IMAGE.GALLERY.BTN_CROP_RESET' | translate) : ''\"\n >\n <mat-icon>crop_free</mat-icon>\n </ion-fab-button>\n }\n }\n </div>\n\n <!-- navigation side buttons next/back: hidden while a crop/rotation edit is pending, to keep the focus on validating/cancelling it -->\n <ng-container *ngIf=\"!mobile && !hasPendingEdit && (rows | isArrayLength: { greaterThan: 1 })\">\n <div class=\"side-buttons prev\" [class.disabled]=\"isPrevDisabled\" (click)=\"slidePrev(swiperContainer)\">\n <ion-icon name=\"chevron-back\"></ion-icon>\n </div>\n <div class=\"side-buttons next\" [class.disabled]=\"isNextDisabled\" (click)=\"slideNext(swiperContainer)\">\n <ion-icon name=\"chevron-forward\"></ion-icon>\n </div>\n </ng-container>\n\n <!-- image slides -->\n <swiper-container\n #swiperContainer\n [modules]=\"swiperModules\"\n [zoom]=\"!enableMouseZoom\"\n [initialSlide]=\"selectedIndex\"\n (swiperslidechange)=\"onSlideChange(swiperContainer)\"\n (swiperinit)=\"onSwiperInit(swiperContainer)\"\n >\n @for (row of rows; track row.id; let index = $index) {\n <ng-container *ngTemplateOutlet=\"slideTemplate; context: { image: row.currentData, index }\"></ng-container>\n }\n </swiper-container>\n</ion-content>\n\n<ion-footer>\n <ion-toolbar color=\"light\">\n @if (!hasPendingEdit) {\n <ion-row>\n <ion-col size=\"4\" class=\"ion-text-start\">\n @if (!readOnly) {\n <ion-button\n (click)=\"requestDelete(swiperContainer)\"\n [disabled]=\"disabled\"\n fill=\"clear\"\n color=\"danger\"\n >\n <mat-icon slot=\"start\">delete</mat-icon>\n {{ 'COMMON.BTN_DELETE' | translate }}\n </ion-button>\n }\n </ion-col>\n <ion-col size=\"4\"></ion-col>\n <ion-col size=\"4\" class=\"ion-text-end\">\n <ion-button (click)=\"closeRequested.emit()\" color=\"tertiary\" fill=\"clear\" *ngIf=\"mobile; else desktop\">\n <ion-icon slot=\"start\" name=\"close\"></ion-icon>\n {{ 'COMMON.BTN_CLOSE' | translate }}\n </ion-button>\n <ng-template #desktop>\n <ion-button (click)=\"closeRequested.emit()\" color=\"tertiary\">\n {{ 'COMMON.BTN_CLOSE' | translate }}\n </ion-button>\n </ng-template>\n </ion-col>\n </ion-row>\n } @else {\n <!-- A crop or rotation change is pending: ask for an explicit validation before exporting the image -->\n <ion-row>\n <ion-col size=\"12\" class=\"ion-text-end\">\n <ion-button\n (click)=\"discardEdit(swiperContainer)\"\n color=\"dark\"\n fill=\"clear\"\n [title]=\"!showTooltip ? ('IMAGE.GALLERY.BTN_CANCEL_EDIT' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('IMAGE.GALLERY.BTN_CANCEL_EDIT' | translate) : ''\"\n >\n <ion-icon slot=\"start\" name=\"arrow-undo\"></ion-icon>\n {{ 'COMMON.BTN_CANCEL' | translate }}\n </ion-button>\n <ion-button (click)=\"validateEdit(swiperContainer)\" color=\"danger\">\n <ion-icon slot=\"start\" name=\"checkmark\"></ion-icon>\n {{ 'COMMON.BTN_VALIDATE' | translate }}\n </ion-button>\n </ion-col>\n </ion-row>\n }\n </ion-toolbar>\n</ion-footer>\n\n<ng-template #slideTemplate let-image=\"image\" let-index=\"index\">\n <swiper-slide>\n <div class=\"swiper-zoom-container\" [class.cdk-visually-hidden]=\"!_swiper && index !== selectedIndex\">\n\n <!-- the image -->\n @if (image?.url) {\n <ion-img class=\"swiper-zoom-target\" [src]=\"image.url | appendQueryParams: getImageSizeQueryParams()\" />\n } @else {\n <img [src]=\"image.dataUrl\" [alt]=\"image.title\" />\n }\n\n <!-- crop overlay: only rendered for the active slide -->\n @if (!readOnly && enableCrop && _cropToolActive && index === _activeIndex) {\n <div\n class=\"crop-overlay\"\n (mousedown)=\"onCropPointerDown($event, swiperContainer, 'move')\"\n (touchstart)=\"onCropPointerDown($event, swiperContainer, 'move')\"\n >\n <!-- darkened mask around the selection: clipped to the image bounds -->\n <div class=\"crop-mask\">\n <div\n class=\"crop-mask-rect\"\n [style.left.%]=\"_cropRect.x * 100\"\n [style.top.%]=\"_cropRect.y * 100\"\n [style.width.%]=\"_cropRect.width * 100\"\n [style.height.%]=\"_cropRect.height * 100\"\n ></div>\n </div>\n\n <!-- interactive selection border, grid & drag handles: never clipped, so corner/edge handles\n remain fully grabbable even when the selection touches the image bounds -->\n <div\n class=\"crop-rect\"\n [style.left.%]=\"_cropRect.x * 100\"\n [style.top.%]=\"_cropRect.y * 100\"\n [style.width.%]=\"_cropRect.width * 100\"\n [style.height.%]=\"_cropRect.height * 100\"\n >\n <div class=\"crop-grid\">\n <span class=\"line v\" style=\"left: 33.333%\"></span>\n <span class=\"line v\" style=\"left: 66.666%\"></span>\n <span class=\"line h\" style=\"top: 33.333%\"></span>\n <span class=\"line h\" style=\"top: 66.666%\"></span>\n </div>\n @for (handle of _cropHandles; track handle) {\n <div\n class=\"crop-handle {{ handle }}\"\n (mousedown)=\"onCropPointerDown($event, swiperContainer, handle)\"\n (touchstart)=\"onCropPointerDown($event, swiperContainer, handle)\"\n ></div>\n }\n </div>\n </div>\n }\n </div>\n </swiper-slide>\n</ng-template>\n", styles: [":host{display:flex;flex-direction:column;height:100%;-webkit-user-select:none;user-select:none}:host ion-content{flex:1 1 auto}:host .top-left{z-index:14;position:absolute;display:inline-grid;left:var(--ion-padding-start, 8px);top:var(--ion-padding-start, 8px)}:host .top-right{z-index:14;position:absolute;display:inline-grid;right:var(--ion-padding-start, 8px);top:var(--ion-padding-start, 8px)}:host .side-buttons{--side-buttons-width: 120px;--side-buttons-margin: calc(var(--side-buttons-width) / 3);z-index:13;position:absolute;top:0;bottom:0;width:var(--side-buttons-width)}:host .side-buttons ion-icon{top:50%;color:#fff;background:rgba(0,0,0,.5);padding:4px;border-radius:50%;font-size:2.5rem;position:absolute;opacity:0;transition-duration:.15s;transition-timing-function:ease-in}:host .side-buttons:hover{cursor:pointer}:host .side-buttons:hover ion-icon{opacity:1}:host .side-buttons.prev{left:0}:host .side-buttons.prev ion-icon{left:var(--side-buttons-margin)}:host .side-buttons.next{right:0}:host .side-buttons.next ion-icon{right:var(--side-buttons-margin)}:host .side-buttons.disabled{pointer-events:none}:host swiper-container{height:100%;background:#000}:host .swiper-zoom-container{position:relative}:host ion-fab-button .icon-secondary{top:11px;left:6px;height:14px}:host .crop-overlay{position:absolute;z-index:15;cursor:move;touch-action:none;animation:crop-overlay-fade-in .15s ease-in}:host .crop-overlay .crop-mask{position:absolute;top:0;right:0;bottom:0;left:0;overflow:hidden;pointer-events:none}:host .crop-overlay .crop-mask .crop-mask-rect{position:absolute;box-shadow:0 0 0 2000px rgba(0,0,0,.6)}:host .crop-overlay .crop-rect{position:absolute;box-sizing:border-box;border:1px solid rgba(255,255,255,.95);cursor:move;touch-action:none}:host .crop-overlay .crop-rect .crop-grid{position:absolute;top:0;right:0;bottom:0;left:0;pointer-events:none}:host .crop-overlay .crop-rect .crop-grid .line{position:absolute;background:rgba(255,255,255,.45)}:host .crop-overlay .crop-rect .crop-grid .line.v{top:0;bottom:0;width:1px}:host .crop-overlay .crop-rect .crop-grid .line.h{left:0;right:0;height:1px}:host .crop-overlay .crop-rect .crop-handle{position:absolute;width:16px;height:16px;margin:-8px;background:#fff;border:1px solid rgba(0,0,0,.45);border-radius:50%;box-shadow:0 1px 4px rgba(0,0,0,.5);transition:transform .12s ease;touch-action:none}:host .crop-overlay .crop-rect .crop-handle:hover{transform:scale(1.3)}:host .crop-overlay .crop-rect .crop-handle.nw{left:0;top:0;cursor:nwse-resize}:host .crop-overlay .crop-rect .crop-handle.n{left:50%;top:0;cursor:ns-resize}:host .crop-overlay .crop-rect .crop-handle.ne{left:100%;top:0;cursor:nesw-resize}:host .crop-overlay .crop-rect .crop-handle.e{left:100%;top:50%;cursor:ew-resize}:host .crop-overlay .crop-rect .crop-handle.se{left:100%;top:100%;cursor:nwse-resize}:host .crop-overlay .crop-rect .crop-handle.s{left:50%;top:100%;cursor:ns-resize}:host .crop-overlay .crop-rect .crop-handle.sw{left:0;top:100%;cursor:nesw-resize}:host .crop-overlay .crop-rect .crop-handle.w{left:0;top:50%;cursor:ew-resize}@keyframes crop-overlay-fade-in{0%{opacity:0}to{opacity:1}}\n"] }]
29360
29483
  }], ctorParameters: () => [{ type: i2$1.AlertController }, { type: i1$1.TranslateService }, { type: i0.ChangeDetectorRef }], propDecorators: { rows: [{
29361
29484
  type: Input
29362
29485
  }], selectedIndex: [{
@@ -29428,8 +29551,8 @@ class ImageService extends StartableService {
29428
29551
  translate;
29429
29552
  popoverController;
29430
29553
  environment;
29431
- defaultMaxHeight = 1024;
29432
- defaultMaxWidth = 1024;
29554
+ defaultMaxHeight = 2048;
29555
+ defaultMaxWidth = 2048;
29433
29556
  constructor(platform, settings, translate, popoverController, environment) {
29434
29557
  super(platform);
29435
29558
  this.platform = platform;
@@ -29721,7 +29844,7 @@ class AppImageGalleryComponent {
29721
29844
  event?.preventDefault();
29722
29845
  event?.stopPropagation();
29723
29846
  // Check using emitter
29724
- const confirmed = await this.canDeleteRows([row]);
29847
+ const confirmed = await this.canDeleteRows([row], opts);
29725
29848
  if (!confirmed)
29726
29849
  return false;
29727
29850
  let deleted;
@@ -29912,7 +30035,7 @@ class AppImageGalleryComponent {
29912
30035
  }
29913
30036
  }
29914
30037
  if (this.confirmBeforeDelete) {
29915
- const confirmed = await Alerts.askDeleteConfirmation(this.alterCtrl, this.translate, event);
30038
+ const confirmed = await Alerts.askDeleteConfirmation(this.alterCtrl, this.translate, undefined, opts);
29916
30039
  if (!confirmed)
29917
30040
  return false; // Cancelled
29918
30041
  }
@@ -29930,11 +30053,11 @@ class AppImageGalleryComponent {
29930
30053
  this.cd.markForCheck();
29931
30054
  }
29932
30055
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AppImageGalleryComponent, deps: [{ token: ImageService }, { token: i2$1.Platform }, { token: i2$1.AlertController }, { token: i1$1.TranslateService }, { token: i0.ChangeDetectorRef }, { token: ENVIRONMENT, optional: true }], target: i0.ɵɵFactoryTarget.Component });
29933
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AppImageGalleryComponent, selector: "app-image-gallery", inputs: { cardColor: "cardColor", debug: ["debug", "debug", booleanAttribute], disabled: ["disabled", "disabled", booleanAttribute], readOnly: ["readOnly", "readOnly", booleanAttribute], mobile: ["mobile", "mobile", booleanAttribute], mode: "mode", confirmBeforeDelete: "confirmBeforeDelete", showToolbar: ["showToolbar", "showToolbar", booleanAttribute], showFabButton: ["showFabButton", "showFabButton", booleanAttribute], showTitle: ["showTitle", "showTitle", booleanAttribute], showAddToolbarButton: ["showAddToolbarButton", "showAddToolbarButton", booleanAttribute], showAddTextButton: ["showAddTextButton", "showAddTextButton", booleanAttribute], showAddCardButton: ["showAddCardButton", "showAddCardButton", booleanAttribute], showCardToolbar: ["showCardToolbar", "showCardToolbar", booleanAttribute], addButtonColor: "addButtonColor", addButtonText: "addButtonText", cardTemplate: "cardTemplate", imageSizes: "imageSizes", imageSizeQueryParam: "imageSizeQueryParam", enableMouseZoom: ["enableMouseZoom", "enableMouseZoom", booleanAttribute], wheelZoomStep: "wheelZoomStep", maxZoomRatio: "maxZoomRatio", enableRotate: ["enableRotate", "enableRotate", booleanAttribute], enableCrop: ["enableCrop", "enableCrop", booleanAttribute], editionFormat: "editionFormat", editionQuality: "editionQuality", slideshowMode: "slideshowMode", inlineZoomHeight: "inlineZoomHeight", enableSlideshowLoop: ["enableSlideshowLoop", "enableSlideshowLoop", booleanAttribute], canFetchMore: ["canFetchMore", "canFetchMore", booleanAttribute], dataSource: "dataSource" }, outputs: { onBeforeDeleteRows: "onBeforeDeleteRows", onAfterAddRows: "onAfterAddRows", onAfterEditRow: "onAfterEditRow", onAfterEditImage: "onAfterEditImage", cardClicked: "clickCard", previousSlide: "previousSlide", nextSlide: "nextSlide", fetchMore: "fetchMore", slideshowOpened: "slideshowOpened", slideshowClosed: "slideshowClosed" }, viewQueries: [{ propertyName: "zoomModal", first: true, predicate: ["zoomModal"], descendants: true }, { propertyName: "titlePopover", first: true, predicate: ["titlePopover"], descendants: true }, { propertyName: "inlineSlideshow", first: true, predicate: ["inlineSlideshow"], descendants: true }], ngImport: i0, template: "@if (showToolbar && !isInlineSlideshowOpen) {\n <ion-toolbar color=\"light\" class=\"gallery-toolbar\">\n <ng-content select=\"ion-buttons[slot=start]\"></ng-content>\n\n <!-- Add -->\n @if (!readOnly && showAddToolbarButton && enabled) {\n <button\n mat-icon-button\n [title]=\"!_showTooltip ? ('COMMON.BTN_ADD' | translate) : ''\"\n [matTooltip]=\"_showTooltip ? ('COMMON.BTN_ADD' | translate) : ''\"\n (click)=\"add()\"\n >\n <mat-icon>add</mat-icon>\n </button>\n }\n\n <ng-content select=\"ion-buttons[slot=end]\"></ng-content>\n\n <ion-buttons slot=\"end\">\n <!-- Toggle view mode -->\n <ion-segment (ionChange)=\"toggleViewMode($event)\" color=\"accent\" [value]=\"_colSize\">\n <ion-segment-button [value]=\"4\">\n <mat-icon>view_module</mat-icon>\n </ion-segment-button>\n <ion-segment-button [value]=\"12\">\n <mat-icon>view_list</mat-icon>\n </ion-segment-button>\n </ion-segment>\n </ion-buttons>\n </ion-toolbar>\n}\n\n<div\n class=\"gallery-container ion-padding-bottom {{ mode }}\"\n [class.inline-zoom-active]=\"isInlineSlideshowOpen\"\n [style.--inline-zoom-height]=\"inlineZoomHeight\"\n>\n @if (mobile && zoomActive) {\n <div class=\"backdrop\" [style.opacity]=\"zoomScale\"></div>\n }\n\n <ion-grid class=\"ion-no-padding\" [class.cdk-visually-hidden]=\"isInlineSlideshowOpen\">\n <ion-row>\n <ion-col *rxFor=\"let row of _data$; trackBy: trackByFn\" [size]=\"_colSize\">\n @let image = row | propertyGet: 'currentData';\n @if (cardTemplate) {\n <ng-container\n *ngTemplateOutlet=\"cardTemplate; context: { $implicit: row, mode: mode, image: image }\"\n ></ng-container>\n } @else {\n <ion-card\n #card\n class=\"image-card\"\n [class.zoom-hover]=\"!mobile\"\n [color]=\"cardColor\"\n (click)=\"clickCard($event, row)\"\n tappable\n @fadeInAnimation\n >\n <figure class=\"card-thumbnail\">\n @if (image.url) {\n <ion-img [src]=\"image.url | appendQueryParams: getImageSizeQueryParams()\" />\n } @else {\n <ion-img [src]=\"image.dataUrl\" />\n }\n </figure>\n\n <!-- toolbar for title (when not hover) -->\n @if (showTitle && image.title) {\n <ion-toolbar color=\"transparent\" class=\"title\">\n <ion-label class=\"card-title\">{{ image.title }}</ion-label>\n </ion-toolbar>\n }\n\n <!-- action toolbar shown when hover image -->\n @if (!readOnly && !mobile && showCardToolbar) {\n <ion-toolbar #cardToolbar color=\"light\" class=\"buttons\">\n <!-- edit title button -->\n @if (showTitle) {\n <button\n mat-button\n slot=\"start\"\n [disabled]=\"disabled\"\n (click)=\"editTitle($event, row, cardToolbar)\"\n (focusin)=\"focusCardToolbar(cardToolbar)\"\n >\n <mat-icon>edit</mat-icon>\n <mat-label>\n @if (row.currentData.title | isNotNilOrBlank) {\n <span translate>IMAGE.GALLERY.BTN_EDIT_TITLE</span>\n } @else {\n <span translate>IMAGE.GALLERY.BTN_ADD_TITLE</span>\n }\n </mat-label>\n </button>\n }\n\n <div class=\"flex-spacer\"></div>\n\n <!-- delete button -->\n <button\n mat-icon-button\n slot=\"end\"\n class=\"ion-float-end\"\n [title]=\"!_showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n [matTooltip]=\"_showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n [disabled]=\"disabled\"\n (click)=\"delete($event, row)\"\n >\n <mat-icon>delete</mat-icon>\n </button>\n </ion-toolbar>\n }\n </ion-card>\n }\n </ion-col>\n\n <!-- add button -->\n @if ((showAddTextButton || showAddCardButton) && !readOnly) {\n <ion-col [size]=\"_colSize\" @fadeInAnimation>\n @if (showAddTextButton) {\n <ion-button (click)=\"add($event)\" [disabled]=\"disabled\" [color]=\"addButtonColor\">\n <mat-label>{{ addButtonText | translate }}</mat-label>\n <ion-icon slot=\"end\" name=\"camera\"></ion-icon>\n </ion-button>\n } @else if (showAddCardButton) {\n <ion-card\n class=\"image-card-button\"\n color=\"light\"\n (click)=\"add($event)\"\n [disabled]=\"disabled\"\n [title]=\"!_showTooltip ? (addButtonText | translate) : ''\"\n [matTooltip]=\"_showTooltip ? (addButtonText | translate) : ''\"\n tappable\n >\n <ion-card-content>\n <div class=\"icon-container\">\n <ion-icon name=\"camera\" [color]=\"addButtonColor\"></ion-icon>\n <ion-icon name=\"add\" [color]=\"addButtonColor\" class=\"icon-secondary\"></ion-icon>\n </div>\n </ion-card-content>\n </ion-card>\n }\n </ion-col>\n }\n </ion-row>\n </ion-grid>\n\n <!-- Inline slideshow: shown as an overlay directly within the gallery, instead of a full-screen modal.\n The rest of the page (outside the gallery) stays fully visible and usable, unlike the modal's backdrop. -->\n @if (isInlineSlideshowOpen) {\n <app-image-gallery-slideshow #inlineSlideshow\n class=\"inline-overlay\"\n [rows]=\"rows\"\n [selectedIndex]=\"_selectedRowIndex\"\n [mobile]=\"mobile\"\n [readOnly]=\"readOnly\"\n [disabled]=\"disabled\"\n [enableMouseZoom]=\"enableMouseZoom\"\n [wheelZoomStep]=\"wheelZoomStep\"\n [maxZoomRatio]=\"maxZoomRatio\"\n [enableRotate]=\"enableRotate\"\n [enableCrop]=\"enableCrop\"\n [enableLoop]=\"enableSlideshowLoop\"\n [editionFormat]=\"editionFormat\"\n [editionQuality]=\"editionQuality\"\n [imageSizeQueryParam]=\"imageSizeQueryParam\"\n [modalImageSize]=\"imageSizes?.modal\"\n (deleteRequested)=\"onSlideshowDeleteRequested($event)\"\n (closeRequested)=\"closeSlideshow()\"\n (afterEditImage)=\"onAfterEditImage.emit($event)\"\n (afterEditRow)=\"onAfterEditRow.emit($event)\"\n (previousSlide)=\"previousSlide.emit($event)\"\n (nextSlide)=\"nextSlide.emit($event)\"\n [canFetchMore]=\"canFetchMore\"\n (fetchMore)=\"fetchMore.emit($event)\"\n [debug]=\"debug\"\n ></app-image-gallery-slideshow>\n }\n</div>\n\n<!-- FAB button: add -->\n@if (showFabButton && !readOnly) {\n <ion-fab slot=\"fixed\" vertical=\"bottom\" horizontal=\"end\" [class.cdk-visually-hidden]=\"disabled\" @fadeInOutAnimation>\n <ion-fab-button color=\"tertiary\" (click)=\"add($event)\">\n <ion-icon name=\"camera\"></ion-icon>\n <ion-icon name=\"add\" class=\"icon-secondary\" style=\"left: 33px; top: 9px; font-size: 20px\"></ion-icon>\n </ion-fab-button>\n </ion-fab>\n}\n\n<!-- Zoom modal -->\n@if (slideshowMode === 'modal') {\n <ion-modal #zoomModal class=\"stack-modal zoom-modal\" [class.modal-fullscreen]=\"mobile\" [class.modal-large]=\"!mobile\">\n <ng-template>\n <app-image-gallery-slideshow\n [rows]=\"rows\"\n [selectedIndex]=\"_selectedRowIndex\"\n [mobile]=\"mobile\"\n [readOnly]=\"readOnly\"\n [disabled]=\"disabled\"\n [debug]=\"debug\"\n [enableMouseZoom]=\"enableMouseZoom\"\n [wheelZoomStep]=\"wheelZoomStep\"\n [maxZoomRatio]=\"maxZoomRatio\"\n [enableRotate]=\"enableRotate\"\n [enableCrop]=\"enableCrop\"\n [enableLoop]=\"enableSlideshowLoop\"\n [editionFormat]=\"editionFormat\"\n [editionQuality]=\"editionQuality\"\n [imageSizeQueryParam]=\"imageSizeQueryParam\"\n [modalImageSize]=\"imageSizes?.modal\"\n (deleteRequested)=\"onSlideshowDeleteRequested($event)\"\n (closeRequested)=\"closeSlideshow()\"\n (afterEditImage)=\"onAfterEditImage.emit($event)\"\n (afterEditRow)=\"onAfterEditRow.emit($event)\"\n (previousSlide)=\"previousSlide.emit($event)\"\n (nextSlide)=\"nextSlide.emit($event)\"\n [canFetchMore]=\"canFetchMore\"\n (fetchMore)=\"fetchMore.emit($event)\"\n ></app-image-gallery-slideshow>\n </ng-template>\n </ion-modal>\n}\n\n<ion-popover #titlePopover>\n <ng-template>\n <ion-content class=\"ion-padding\" cdkTrapFocus [cdkTrapFocusAutoCapture]=\"true\">\n <form [formGroup]=\"_titleForm\">\n <mat-form-field floatLabel=\"auto\">\n <mat-label translate>IMAGE.GALLERY.TITLE</mat-label>\n <input\n matInput\n formControlName=\"title\"\n [readonly]=\"disabled\"\n [appAutofocus]=\"_titleAutofocus\"\n (keydown.control.enter)=\"titlePopover.dismiss(_titleForm.controls.title.value)\"\n />\n </mat-form-field>\n </form>\n </ion-content>\n\n <ion-footer>\n <ion-toolbar>\n <ion-row class=\"ion-no-padding\" nowrap>\n <ion-col></ion-col>\n\n <ion-col size=\"auto\">\n <ion-button fill=\"clear\" color=\"dark\" (click)=\"titlePopover.dismiss(null, 'CANCEL')\">\n <ion-label translate>COMMON.BTN_CANCEL</ion-label>\n </ion-button>\n <ion-button\n [fill]=\"disabled ? 'clear' : 'solid'\"\n (click)=\"titlePopover.dismiss(_titleForm.controls.title.value)\"\n (keyup.enter)=\"titlePopover.dismiss(_titleForm.controls.title.value)\"\n color=\"tertiary\"\n [disabled]=\"disabled\"\n [title]=\"!_showTooltip ? ('COMMON.BTN_VALIDATE_WITH_SHORTCUT_HELP' | translate) : ''\"\n [matTooltip]=\"_showTooltip ? ('COMMON.BTN_VALIDATE_WITH_SHORTCUT_HELP' | translate) : ''\"\n >\n <ion-label translate>COMMON.BTN_VALIDATE</ion-label>\n </ion-button>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n </ion-footer>\n </ng-template>\n</ion-popover>\n", styles: [":host{--image-margin-inline: 10px;--image-margin-safe-area: var(--image-margin-inline, 0px) * 2 - 3px;--segment-max-width: 150px}ion-toolbar{position:sticky;top:0}ion-toolbar ion-segment{max-width:var(--segment-max-width)}mat-toolbar,.mat-toolbar-single-row{padding:0;height:42px}ion-card{margin-left:unset;margin-right:unset;margin-inline:var(--image-margin-inline, 10px)}.image-slide{overflow:visible}.image-card{background-color:var(--ion-color-base);z-index:9;--img-scale: 1}.image-card .card-thumbnail{position:relative;overflow:hidden;display:flex;justify-content:center;align-items:center;margin:0;padding:0;width:100%;height:100%;max-height:var(--img-max-height, calc(100% - var(--image-margin-safe-area, 0px)))}.image-card .card-thumbnail>ion-img{min-height:100%;height:100%;object-fit:cover;object-position:center;transition:scale .2s ease-in-out;scale:var(--img-scale)}.image-card .card-thumbnail>ion-img img{max-height:var(--img-max-height)}.image-card.zoom-hover:hover{--img-scale: 1.2}.image-card ion-toolbar{--top-offset: calc(-1 * var(--ion-toolbar-height));position:absolute}.image-card ion-toolbar.title{--padding-start: 16px;--padding-end: 16px;--ion-color-base: rgba(0, 0, 0, .45) !important;position:absolute;top:calc(100% + var(--top-offset));transition:opacity .2s ease-in-out}.image-card ion-toolbar.title ion-label{color:#fff}.image-card ion-toolbar.buttons{--padding-start: 4px;--padding-end: 4px;--ion-color-base: var(--ion-color-light) !important;position:absolute;top:100%;transition:transform .2s ease-in-out}.image-card:hover ion-toolbar.buttons,.image-card ion-toolbar.buttons.focused{transform:translateY(var(--top-offset))}.image-card-button{height:calc(100% - var(--image-margin-safe-area, 0px));max-height:max(min(30vh,200px),var(--img-max-height, 0px));transition:.2s ease-in-out;max-width:250px}.image-card-button ion-card-content{height:100%}.image-card-button .icon-container{height:auto;position:relative;top:calc(50% - 75px);display:flex;align-items:center;justify-content:center}.image-card-button ion-icon:first-of-type{font-size:150px;height:150px;position:relative;top:0}.image-card-button ion-icon.icon-secondary{font-size:50px;position:absolute;top:0;left:max(min(50% + 25px,100% - 50px),0px)}.image-card-button ion-button{float:right}.image-card-button:hover{--ion-color-base: var(--ion-color-shade) !important}.image-card-button:hover ion-icon{--ion-color-base: var(--ion-color-shade) !important}.backdrop{height:100vh;width:100vw;background:#000;position:absolute;z-index:10}.gallery-container{position:relative}.gallery-container.inline-zoom-active{min-height:var(--inline-zoom-height, 60vh)}.inline-overlay{position:absolute;inset:0;z-index:20;background:#000}\n"], dependencies: [{ kind: "directive", type: i3$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$1.IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: i2$1.IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonFab, selector: "ion-fab", inputs: ["activated", "edge", "horizontal", "vertical"] }, { kind: "component", type: i2$1.IonFabButton, selector: "ion-fab-button", inputs: ["activated", "closeIcon", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "show", "size", "target", "translucent", "type"] }, { kind: "component", type: i2$1.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonImg, selector: "ion-img", inputs: ["alt", "src"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "component", type: i2$1.IonSegment, selector: "ion-segment", inputs: ["color", "disabled", "mode", "scrollable", "selectOnFocus", "swipeGesture", "value"] }, { kind: "component", type: i2$1.IonSegmentButton, selector: "ion-segment-button", inputs: ["disabled", "layout", "mode", "type", "value"] }, { kind: "component", type: i2$1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonModal, selector: "ion-modal" }, { kind: "component", type: i2$1.IonPopover, selector: "ion-popover" }, { kind: "directive", type: i2$1.SelectValueAccessor, selector: "ion-select, ion-radio-group, ion-segment, ion-datetime" }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i6$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i11$1.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i11$1.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "directive", type: i1$6.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "directive", type: i1$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: AutofocusDirective, selector: "[autofocus], input[appAutofocus]", inputs: ["appAutofocus", "autofocusDelay"] }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "directive", type: i13$3.RxFor, selector: "[rxFor][rxForOf]", inputs: ["rxForOf", "rxForTemplate", "rxForStrategy", "rxForParent", "rxForPatchZone", "rxForTrackBy", "rxForRenderCallback"] }, { kind: "component", type: AppImageGallerySlideshowComponent, selector: "app-image-gallery-slideshow", inputs: ["rows", "selectedIndex", "mobile", "readOnly", "disabled", "debug", "enableMouseZoom", "wheelZoomStep", "maxZoomRatio", "enableRotate", "enableCrop", "editionFormat", "editionQuality", "imageSizeQueryParam", "modalImageSize", "enableLoop", "canFetchMore"], outputs: ["deleteRequested", "closeRequested", "afterEditImage", "afterEditRow", "previousSlide", "nextSlide", "fetchMore"] }, { kind: "pipe", type: PropertyGetPipe, name: "propertyGet" }, { kind: "pipe", type: IsNotNilOrBlankPipe, name: "isNotNilOrBlank" }, { kind: "pipe", type: AppendQueryParamsPipePipe, name: "appendQueryParams" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], animations: [fadeInAnimation, fadeInOutAnimation], changeDetection: i0.ChangeDetectionStrategy.OnPush });
30056
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AppImageGalleryComponent, selector: "app-image-gallery", inputs: { cardColor: "cardColor", debug: ["debug", "debug", booleanAttribute], disabled: ["disabled", "disabled", booleanAttribute], readOnly: ["readOnly", "readOnly", booleanAttribute], mobile: ["mobile", "mobile", booleanAttribute], mode: "mode", confirmBeforeDelete: "confirmBeforeDelete", showToolbar: ["showToolbar", "showToolbar", booleanAttribute], showFabButton: ["showFabButton", "showFabButton", booleanAttribute], showTitle: ["showTitle", "showTitle", booleanAttribute], showAddToolbarButton: ["showAddToolbarButton", "showAddToolbarButton", booleanAttribute], showAddTextButton: ["showAddTextButton", "showAddTextButton", booleanAttribute], showAddCardButton: ["showAddCardButton", "showAddCardButton", booleanAttribute], showCardToolbar: ["showCardToolbar", "showCardToolbar", booleanAttribute], addButtonColor: "addButtonColor", addButtonText: "addButtonText", cardTemplate: "cardTemplate", imageSizes: "imageSizes", imageSizeQueryParam: "imageSizeQueryParam", enableMouseZoom: ["enableMouseZoom", "enableMouseZoom", booleanAttribute], wheelZoomStep: "wheelZoomStep", maxZoomRatio: "maxZoomRatio", enableRotate: ["enableRotate", "enableRotate", booleanAttribute], enableCrop: ["enableCrop", "enableCrop", booleanAttribute], editionFormat: "editionFormat", editionQuality: "editionQuality", slideshowMode: "slideshowMode", inlineZoomHeight: "inlineZoomHeight", enableSlideshowLoop: ["enableSlideshowLoop", "enableSlideshowLoop", booleanAttribute], canFetchMore: ["canFetchMore", "canFetchMore", booleanAttribute], dataSource: "dataSource" }, outputs: { onBeforeDeleteRows: "onBeforeDeleteRows", onAfterAddRows: "onAfterAddRows", onAfterEditRow: "onAfterEditRow", onAfterEditImage: "onAfterEditImage", cardClicked: "clickCard", previousSlide: "previousSlide", nextSlide: "nextSlide", fetchMore: "fetchMore", slideshowOpened: "slideshowOpened", slideshowClosed: "slideshowClosed" }, viewQueries: [{ propertyName: "zoomModal", first: true, predicate: ["zoomModal"], descendants: true }, { propertyName: "titlePopover", first: true, predicate: ["titlePopover"], descendants: true }, { propertyName: "inlineSlideshow", first: true, predicate: ["inlineSlideshow"], descendants: true }], ngImport: i0, template: "@if (showToolbar && !isInlineSlideshowOpen) {\n <ion-toolbar color=\"light\" class=\"gallery-toolbar\">\n <ng-content select=\"ion-buttons[slot=start]\"></ng-content>\n\n <!-- Add -->\n @if (!readOnly && showAddToolbarButton && enabled) {\n <button\n mat-icon-button\n [title]=\"!_showTooltip ? ('COMMON.BTN_ADD' | translate) : ''\"\n [matTooltip]=\"_showTooltip ? ('COMMON.BTN_ADD' | translate) : ''\"\n (click)=\"add()\"\n >\n <mat-icon>add</mat-icon>\n </button>\n }\n\n <ng-content select=\"ion-buttons[slot=end]\"></ng-content>\n\n <ion-buttons slot=\"end\">\n <!-- Toggle view mode -->\n <ion-segment (ionChange)=\"toggleViewMode($event)\" color=\"accent\" [value]=\"_colSize\">\n <ion-segment-button [value]=\"4\">\n <mat-icon>view_module</mat-icon>\n </ion-segment-button>\n <ion-segment-button [value]=\"12\">\n <mat-icon>view_list</mat-icon>\n </ion-segment-button>\n </ion-segment>\n </ion-buttons>\n </ion-toolbar>\n}\n\n<div\n class=\"gallery-container ion-padding-bottom {{ mode }}\"\n [class.inline-zoom-active]=\"isInlineSlideshowOpen\"\n [style.--inline-zoom-height]=\"inlineZoomHeight\"\n>\n @if (mobile && zoomActive) {\n <div class=\"backdrop\" [style.opacity]=\"zoomScale\"></div>\n }\n\n <ion-grid class=\"ion-no-padding\" [class.cdk-visually-hidden]=\"isInlineSlideshowOpen\">\n <ion-row>\n <ion-col *rxFor=\"let row of _data$; trackBy: trackByFn\" [size]=\"_colSize\">\n @let image = row | propertyGet: 'currentData';\n @if (cardTemplate) {\n <ng-container\n *ngTemplateOutlet=\"cardTemplate; context: { $implicit: row, mode: mode, image: image }\"\n ></ng-container>\n } @else {\n <ion-card\n #card\n class=\"image-card\"\n [class.zoom-hover]=\"!mobile\"\n [color]=\"cardColor\"\n (click)=\"clickCard($event, row)\"\n tappable\n @fadeInAnimation\n >\n <figure class=\"card-thumbnail\">\n @if (image.url) {\n <ion-img [src]=\"image.url | appendQueryParams: getImageSizeQueryParams()\" />\n } @else {\n <ion-img [src]=\"image.dataUrl\" />\n }\n </figure>\n\n <!-- toolbar for title (when not hover) -->\n @if (showTitle && image.title) {\n <ion-toolbar color=\"transparent\" class=\"title\">\n <ion-label class=\"card-title\">{{ image.title }}</ion-label>\n </ion-toolbar>\n }\n\n <!-- action toolbar shown when hover image -->\n @if (!readOnly && !mobile && showCardToolbar) {\n <ion-toolbar #cardToolbar color=\"light\" class=\"buttons\">\n <!-- edit title button -->\n @if (showTitle) {\n <button\n mat-button\n slot=\"start\"\n [disabled]=\"disabled\"\n (click)=\"editTitle($event, row, cardToolbar)\"\n (focusin)=\"focusCardToolbar(cardToolbar)\"\n >\n <mat-icon>edit</mat-icon>\n <mat-label>\n @if (row.currentData.title | isNotNilOrBlank) {\n <span translate>IMAGE.GALLERY.BTN_EDIT_TITLE</span>\n } @else {\n <span translate>IMAGE.GALLERY.BTN_ADD_TITLE</span>\n }\n </mat-label>\n </button>\n }\n\n <div class=\"flex-spacer\"></div>\n\n <!-- delete button -->\n <button\n mat-icon-button\n slot=\"end\"\n class=\"ion-float-end\"\n [title]=\"!_showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n [matTooltip]=\"_showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n [disabled]=\"disabled\"\n (click)=\"delete($event, row)\"\n >\n <mat-icon>delete</mat-icon>\n </button>\n </ion-toolbar>\n }\n </ion-card>\n }\n </ion-col>\n\n <!-- add button -->\n @if ((showAddTextButton || showAddCardButton) && !readOnly) {\n <ion-col [size]=\"_colSize\" @fadeInAnimation>\n @if (showAddTextButton) {\n <ion-button (click)=\"add($event)\" [disabled]=\"disabled\" [color]=\"addButtonColor\">\n <mat-label>{{ addButtonText | translate }}</mat-label>\n <ion-icon slot=\"end\" name=\"camera\"></ion-icon>\n </ion-button>\n } @else if (showAddCardButton) {\n <ion-card\n class=\"image-card-button\"\n color=\"light\"\n (click)=\"add($event)\"\n [disabled]=\"disabled\"\n [title]=\"!_showTooltip ? (addButtonText | translate) : ''\"\n [matTooltip]=\"_showTooltip ? (addButtonText | translate) : ''\"\n tappable\n >\n <ion-card-content>\n <div class=\"icon-container\">\n <ion-icon name=\"camera\" [color]=\"addButtonColor\"></ion-icon>\n <ion-icon name=\"add\" [color]=\"addButtonColor\" class=\"icon-secondary\"></ion-icon>\n </div>\n </ion-card-content>\n </ion-card>\n }\n </ion-col>\n }\n </ion-row>\n </ion-grid>\n\n <!-- Inline slideshow: shown as an overlay directly within the gallery, instead of a full-screen modal.\n The rest of the page (outside the gallery) stays fully visible and usable, unlike the modal's backdrop. -->\n @if (isInlineSlideshowOpen) {\n <app-image-gallery-slideshow #inlineSlideshow\n class=\"inline-overlay\"\n [rows]=\"rows\"\n [selectedIndex]=\"_selectedRowIndex\"\n [mobile]=\"mobile\"\n [readOnly]=\"readOnly\"\n [disabled]=\"disabled\"\n [enableMouseZoom]=\"enableMouseZoom\"\n [wheelZoomStep]=\"wheelZoomStep\"\n [maxZoomRatio]=\"maxZoomRatio\"\n [enableRotate]=\"enableRotate\"\n [enableCrop]=\"enableCrop\"\n [enableLoop]=\"enableSlideshowLoop\"\n [editionFormat]=\"editionFormat\"\n [editionQuality]=\"editionQuality\"\n [imageSizeQueryParam]=\"imageSizeQueryParam\"\n [modalImageSize]=\"imageSizes?.modal\"\n (deleteRequested)=\"onSlideshowDeleteRequested($event)\"\n (closeRequested)=\"closeSlideshow()\"\n (afterEditImage)=\"onAfterEditImage.emit($event)\"\n (afterEditRow)=\"onAfterEditRow.emit($event)\"\n (previousSlide)=\"previousSlide.emit($event)\"\n (nextSlide)=\"nextSlide.emit($event)\"\n [canFetchMore]=\"canFetchMore\"\n (fetchMore)=\"fetchMore.emit($event)\"\n [debug]=\"debug\"\n ></app-image-gallery-slideshow>\n }\n</div>\n\n<!-- FAB button: add -->\n@if (showFabButton && !readOnly) {\n <ion-fab slot=\"fixed\" vertical=\"bottom\" horizontal=\"end\" [class.cdk-visually-hidden]=\"disabled\" @fadeInOutAnimation>\n <ion-fab-button color=\"tertiary\" (click)=\"add($event)\">\n <ion-icon name=\"camera\"></ion-icon>\n <ion-icon name=\"add\" class=\"icon-secondary\" style=\"left: 33px; top: 9px; font-size: 20px\"></ion-icon>\n </ion-fab-button>\n </ion-fab>\n}\n\n<!-- Zoom modal -->\n@if (slideshowMode === 'modal') {\n <ion-modal #zoomModal class=\"stack-modal zoom-modal\" [class.modal-fullscreen]=\"mobile\" [class.modal-large]=\"!mobile\">\n <ng-template>\n <app-image-gallery-slideshow\n [rows]=\"rows\"\n [selectedIndex]=\"_selectedRowIndex\"\n [mobile]=\"mobile\"\n [readOnly]=\"readOnly\"\n [disabled]=\"disabled\"\n [debug]=\"debug\"\n [enableMouseZoom]=\"enableMouseZoom\"\n [wheelZoomStep]=\"wheelZoomStep\"\n [maxZoomRatio]=\"maxZoomRatio\"\n [enableRotate]=\"enableRotate\"\n [enableCrop]=\"enableCrop\"\n [enableLoop]=\"enableSlideshowLoop\"\n [editionFormat]=\"editionFormat\"\n [editionQuality]=\"editionQuality\"\n [imageSizeQueryParam]=\"imageSizeQueryParam\"\n [modalImageSize]=\"imageSizes?.modal\"\n (deleteRequested)=\"onSlideshowDeleteRequested($event)\"\n (closeRequested)=\"closeSlideshow()\"\n (afterEditImage)=\"onAfterEditImage.emit($event)\"\n (afterEditRow)=\"onAfterEditRow.emit($event)\"\n (previousSlide)=\"previousSlide.emit($event)\"\n (nextSlide)=\"nextSlide.emit($event)\"\n [canFetchMore]=\"canFetchMore\"\n (fetchMore)=\"fetchMore.emit($event)\"\n ></app-image-gallery-slideshow>\n </ng-template>\n </ion-modal>\n}\n\n<ion-popover #titlePopover>\n <ng-template>\n <ion-content class=\"ion-padding\" cdkTrapFocus [cdkTrapFocusAutoCapture]=\"true\">\n <form [formGroup]=\"_titleForm\">\n <mat-form-field floatLabel=\"auto\">\n <mat-label translate>IMAGE.GALLERY.TITLE</mat-label>\n <input\n matInput\n formControlName=\"title\"\n [readonly]=\"disabled\"\n [appAutofocus]=\"_titleAutofocus\"\n (keydown.control.enter)=\"titlePopover.dismiss(_titleForm.controls.title.value)\"\n />\n </mat-form-field>\n </form>\n </ion-content>\n\n <ion-footer>\n <ion-toolbar>\n <ion-row class=\"ion-no-padding\" nowrap>\n <ion-col></ion-col>\n\n <ion-col size=\"auto\">\n <ion-button fill=\"clear\" color=\"dark\" (click)=\"titlePopover.dismiss(null, 'CANCEL')\">\n <ion-label translate>COMMON.BTN_CANCEL</ion-label>\n </ion-button>\n <ion-button\n [fill]=\"disabled ? 'clear' : 'solid'\"\n (click)=\"titlePopover.dismiss(_titleForm.controls.title.value)\"\n (keyup.enter)=\"titlePopover.dismiss(_titleForm.controls.title.value)\"\n color=\"tertiary\"\n [disabled]=\"disabled\"\n [title]=\"!_showTooltip ? ('COMMON.BTN_VALIDATE_WITH_SHORTCUT_HELP' | translate) : ''\"\n [matTooltip]=\"_showTooltip ? ('COMMON.BTN_VALIDATE_WITH_SHORTCUT_HELP' | translate) : ''\"\n >\n <ion-label translate>COMMON.BTN_VALIDATE</ion-label>\n </ion-button>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n </ion-footer>\n </ng-template>\n</ion-popover>\n", styles: [":host{--image-margin-inline: 10px;--image-margin-safe-area: var(--image-margin-inline, 0px) * 2 - 3px;--segment-max-width: 150px}ion-toolbar{position:sticky;top:0}ion-toolbar ion-segment{max-width:var(--segment-max-width)}mat-toolbar,.mat-toolbar-single-row{padding:0;height:42px}ion-card{margin-left:unset;margin-right:unset;margin-inline:var(--image-margin-inline, 10px)}.image-slide{overflow:visible}.image-card{background-color:var(--ion-color-base);z-index:9;--img-scale: 1}.image-card .card-thumbnail{position:relative;overflow:hidden;display:flex;justify-content:center;align-items:center;margin:0;padding:0;width:100%;height:100%;max-height:var(--img-max-height, calc(100% - var(--image-margin-safe-area, 0px)))}.image-card .card-thumbnail>ion-img{min-height:100%;height:100%;object-fit:cover;object-position:center;transition:scale .2s ease-in-out;scale:var(--img-scale)}.image-card .card-thumbnail>ion-img img{max-height:var(--img-max-height)}.image-card.zoom-hover:hover{--img-scale: 1.2}.image-card ion-toolbar{--top-offset: calc(-1 * var(--ion-toolbar-height));position:absolute}.image-card ion-toolbar.title{--padding-start: 16px;--padding-end: 16px;--ion-color-base: rgba(0, 0, 0, .45) !important;position:absolute;top:calc(100% + var(--top-offset));transition:opacity .2s ease-in-out}.image-card ion-toolbar.title ion-label{color:#fff}.image-card ion-toolbar.buttons{--padding-start: 4px;--padding-end: 4px;--ion-color-base: var(--ion-color-light) !important;position:absolute;top:100%;transition:transform .2s ease-in-out}.image-card:hover ion-toolbar.buttons,.image-card ion-toolbar.buttons.focused{transform:translateY(var(--top-offset))}.image-card-button{height:calc(100% - var(--image-margin-safe-area, 0px));max-height:max(min(30vh,200px),var(--img-max-height, 0px));transition:.2s ease-in-out;max-width:250px}.image-card-button ion-card-content{height:100%}.image-card-button .icon-container{height:auto;position:relative;top:calc(50% - 75px);display:flex;align-items:center;justify-content:center}.image-card-button ion-icon:first-of-type{font-size:150px;height:150px;position:relative;top:0}.image-card-button ion-icon.icon-secondary{font-size:50px;position:absolute;top:0;left:max(min(50% + 25px,100% - 50px),0px)}.image-card-button ion-button{float:right}.image-card-button:hover{--ion-color-base: var(--ion-color-shade) !important}.image-card-button:hover ion-icon{--ion-color-base: var(--ion-color-shade) !important}.backdrop{height:100vh;width:100vw;background:#000;position:absolute;z-index:10}.gallery-container{position:relative}.gallery-container.inline-zoom-active{min-height:var(--inline-zoom-height, 60vh)}.inline-overlay{position:absolute;top:0;right:0;bottom:0;left:0;z-index:20;background:#000}\n"], dependencies: [{ kind: "directive", type: i3$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$1.IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: i2$1.IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonFab, selector: "ion-fab", inputs: ["activated", "edge", "horizontal", "vertical"] }, { kind: "component", type: i2$1.IonFabButton, selector: "ion-fab-button", inputs: ["activated", "closeIcon", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "show", "size", "target", "translucent", "type"] }, { kind: "component", type: i2$1.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonImg, selector: "ion-img", inputs: ["alt", "src"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "component", type: i2$1.IonSegment, selector: "ion-segment", inputs: ["color", "disabled", "mode", "scrollable", "selectOnFocus", "swipeGesture", "value"] }, { kind: "component", type: i2$1.IonSegmentButton, selector: "ion-segment-button", inputs: ["disabled", "layout", "mode", "type", "value"] }, { kind: "component", type: i2$1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonModal, selector: "ion-modal" }, { kind: "component", type: i2$1.IonPopover, selector: "ion-popover" }, { kind: "directive", type: i2$1.SelectValueAccessor, selector: "ion-select, ion-radio-group, ion-segment, ion-datetime" }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i6$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i11$1.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i11$1.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "directive", type: i1$6.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "directive", type: i1$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: AutofocusDirective, selector: "[autofocus], input[appAutofocus]", inputs: ["appAutofocus", "autofocusDelay"] }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "directive", type: i13$3.RxFor, selector: "[rxFor][rxForOf]", inputs: ["rxForOf", "rxForTemplate", "rxForStrategy", "rxForParent", "rxForPatchZone", "rxForTrackBy", "rxForRenderCallback"] }, { kind: "component", type: AppImageGallerySlideshowComponent, selector: "app-image-gallery-slideshow", inputs: ["rows", "selectedIndex", "mobile", "readOnly", "disabled", "debug", "enableMouseZoom", "wheelZoomStep", "maxZoomRatio", "enableRotate", "enableCrop", "editionFormat", "editionQuality", "imageSizeQueryParam", "modalImageSize", "enableLoop", "canFetchMore"], outputs: ["deleteRequested", "closeRequested", "afterEditImage", "afterEditRow", "previousSlide", "nextSlide", "fetchMore"] }, { kind: "pipe", type: PropertyGetPipe, name: "propertyGet" }, { kind: "pipe", type: IsNotNilOrBlankPipe, name: "isNotNilOrBlank" }, { kind: "pipe", type: AppendQueryParamsPipePipe, name: "appendQueryParams" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], animations: [fadeInAnimation, fadeInOutAnimation], changeDetection: i0.ChangeDetectionStrategy.OnPush });
29934
30057
  }
29935
30058
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AppImageGalleryComponent, decorators: [{
29936
30059
  type: Component,
29937
- args: [{ selector: 'app-image-gallery', animations: [fadeInAnimation, fadeInOutAnimation], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (showToolbar && !isInlineSlideshowOpen) {\n <ion-toolbar color=\"light\" class=\"gallery-toolbar\">\n <ng-content select=\"ion-buttons[slot=start]\"></ng-content>\n\n <!-- Add -->\n @if (!readOnly && showAddToolbarButton && enabled) {\n <button\n mat-icon-button\n [title]=\"!_showTooltip ? ('COMMON.BTN_ADD' | translate) : ''\"\n [matTooltip]=\"_showTooltip ? ('COMMON.BTN_ADD' | translate) : ''\"\n (click)=\"add()\"\n >\n <mat-icon>add</mat-icon>\n </button>\n }\n\n <ng-content select=\"ion-buttons[slot=end]\"></ng-content>\n\n <ion-buttons slot=\"end\">\n <!-- Toggle view mode -->\n <ion-segment (ionChange)=\"toggleViewMode($event)\" color=\"accent\" [value]=\"_colSize\">\n <ion-segment-button [value]=\"4\">\n <mat-icon>view_module</mat-icon>\n </ion-segment-button>\n <ion-segment-button [value]=\"12\">\n <mat-icon>view_list</mat-icon>\n </ion-segment-button>\n </ion-segment>\n </ion-buttons>\n </ion-toolbar>\n}\n\n<div\n class=\"gallery-container ion-padding-bottom {{ mode }}\"\n [class.inline-zoom-active]=\"isInlineSlideshowOpen\"\n [style.--inline-zoom-height]=\"inlineZoomHeight\"\n>\n @if (mobile && zoomActive) {\n <div class=\"backdrop\" [style.opacity]=\"zoomScale\"></div>\n }\n\n <ion-grid class=\"ion-no-padding\" [class.cdk-visually-hidden]=\"isInlineSlideshowOpen\">\n <ion-row>\n <ion-col *rxFor=\"let row of _data$; trackBy: trackByFn\" [size]=\"_colSize\">\n @let image = row | propertyGet: 'currentData';\n @if (cardTemplate) {\n <ng-container\n *ngTemplateOutlet=\"cardTemplate; context: { $implicit: row, mode: mode, image: image }\"\n ></ng-container>\n } @else {\n <ion-card\n #card\n class=\"image-card\"\n [class.zoom-hover]=\"!mobile\"\n [color]=\"cardColor\"\n (click)=\"clickCard($event, row)\"\n tappable\n @fadeInAnimation\n >\n <figure class=\"card-thumbnail\">\n @if (image.url) {\n <ion-img [src]=\"image.url | appendQueryParams: getImageSizeQueryParams()\" />\n } @else {\n <ion-img [src]=\"image.dataUrl\" />\n }\n </figure>\n\n <!-- toolbar for title (when not hover) -->\n @if (showTitle && image.title) {\n <ion-toolbar color=\"transparent\" class=\"title\">\n <ion-label class=\"card-title\">{{ image.title }}</ion-label>\n </ion-toolbar>\n }\n\n <!-- action toolbar shown when hover image -->\n @if (!readOnly && !mobile && showCardToolbar) {\n <ion-toolbar #cardToolbar color=\"light\" class=\"buttons\">\n <!-- edit title button -->\n @if (showTitle) {\n <button\n mat-button\n slot=\"start\"\n [disabled]=\"disabled\"\n (click)=\"editTitle($event, row, cardToolbar)\"\n (focusin)=\"focusCardToolbar(cardToolbar)\"\n >\n <mat-icon>edit</mat-icon>\n <mat-label>\n @if (row.currentData.title | isNotNilOrBlank) {\n <span translate>IMAGE.GALLERY.BTN_EDIT_TITLE</span>\n } @else {\n <span translate>IMAGE.GALLERY.BTN_ADD_TITLE</span>\n }\n </mat-label>\n </button>\n }\n\n <div class=\"flex-spacer\"></div>\n\n <!-- delete button -->\n <button\n mat-icon-button\n slot=\"end\"\n class=\"ion-float-end\"\n [title]=\"!_showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n [matTooltip]=\"_showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n [disabled]=\"disabled\"\n (click)=\"delete($event, row)\"\n >\n <mat-icon>delete</mat-icon>\n </button>\n </ion-toolbar>\n }\n </ion-card>\n }\n </ion-col>\n\n <!-- add button -->\n @if ((showAddTextButton || showAddCardButton) && !readOnly) {\n <ion-col [size]=\"_colSize\" @fadeInAnimation>\n @if (showAddTextButton) {\n <ion-button (click)=\"add($event)\" [disabled]=\"disabled\" [color]=\"addButtonColor\">\n <mat-label>{{ addButtonText | translate }}</mat-label>\n <ion-icon slot=\"end\" name=\"camera\"></ion-icon>\n </ion-button>\n } @else if (showAddCardButton) {\n <ion-card\n class=\"image-card-button\"\n color=\"light\"\n (click)=\"add($event)\"\n [disabled]=\"disabled\"\n [title]=\"!_showTooltip ? (addButtonText | translate) : ''\"\n [matTooltip]=\"_showTooltip ? (addButtonText | translate) : ''\"\n tappable\n >\n <ion-card-content>\n <div class=\"icon-container\">\n <ion-icon name=\"camera\" [color]=\"addButtonColor\"></ion-icon>\n <ion-icon name=\"add\" [color]=\"addButtonColor\" class=\"icon-secondary\"></ion-icon>\n </div>\n </ion-card-content>\n </ion-card>\n }\n </ion-col>\n }\n </ion-row>\n </ion-grid>\n\n <!-- Inline slideshow: shown as an overlay directly within the gallery, instead of a full-screen modal.\n The rest of the page (outside the gallery) stays fully visible and usable, unlike the modal's backdrop. -->\n @if (isInlineSlideshowOpen) {\n <app-image-gallery-slideshow #inlineSlideshow\n class=\"inline-overlay\"\n [rows]=\"rows\"\n [selectedIndex]=\"_selectedRowIndex\"\n [mobile]=\"mobile\"\n [readOnly]=\"readOnly\"\n [disabled]=\"disabled\"\n [enableMouseZoom]=\"enableMouseZoom\"\n [wheelZoomStep]=\"wheelZoomStep\"\n [maxZoomRatio]=\"maxZoomRatio\"\n [enableRotate]=\"enableRotate\"\n [enableCrop]=\"enableCrop\"\n [enableLoop]=\"enableSlideshowLoop\"\n [editionFormat]=\"editionFormat\"\n [editionQuality]=\"editionQuality\"\n [imageSizeQueryParam]=\"imageSizeQueryParam\"\n [modalImageSize]=\"imageSizes?.modal\"\n (deleteRequested)=\"onSlideshowDeleteRequested($event)\"\n (closeRequested)=\"closeSlideshow()\"\n (afterEditImage)=\"onAfterEditImage.emit($event)\"\n (afterEditRow)=\"onAfterEditRow.emit($event)\"\n (previousSlide)=\"previousSlide.emit($event)\"\n (nextSlide)=\"nextSlide.emit($event)\"\n [canFetchMore]=\"canFetchMore\"\n (fetchMore)=\"fetchMore.emit($event)\"\n [debug]=\"debug\"\n ></app-image-gallery-slideshow>\n }\n</div>\n\n<!-- FAB button: add -->\n@if (showFabButton && !readOnly) {\n <ion-fab slot=\"fixed\" vertical=\"bottom\" horizontal=\"end\" [class.cdk-visually-hidden]=\"disabled\" @fadeInOutAnimation>\n <ion-fab-button color=\"tertiary\" (click)=\"add($event)\">\n <ion-icon name=\"camera\"></ion-icon>\n <ion-icon name=\"add\" class=\"icon-secondary\" style=\"left: 33px; top: 9px; font-size: 20px\"></ion-icon>\n </ion-fab-button>\n </ion-fab>\n}\n\n<!-- Zoom modal -->\n@if (slideshowMode === 'modal') {\n <ion-modal #zoomModal class=\"stack-modal zoom-modal\" [class.modal-fullscreen]=\"mobile\" [class.modal-large]=\"!mobile\">\n <ng-template>\n <app-image-gallery-slideshow\n [rows]=\"rows\"\n [selectedIndex]=\"_selectedRowIndex\"\n [mobile]=\"mobile\"\n [readOnly]=\"readOnly\"\n [disabled]=\"disabled\"\n [debug]=\"debug\"\n [enableMouseZoom]=\"enableMouseZoom\"\n [wheelZoomStep]=\"wheelZoomStep\"\n [maxZoomRatio]=\"maxZoomRatio\"\n [enableRotate]=\"enableRotate\"\n [enableCrop]=\"enableCrop\"\n [enableLoop]=\"enableSlideshowLoop\"\n [editionFormat]=\"editionFormat\"\n [editionQuality]=\"editionQuality\"\n [imageSizeQueryParam]=\"imageSizeQueryParam\"\n [modalImageSize]=\"imageSizes?.modal\"\n (deleteRequested)=\"onSlideshowDeleteRequested($event)\"\n (closeRequested)=\"closeSlideshow()\"\n (afterEditImage)=\"onAfterEditImage.emit($event)\"\n (afterEditRow)=\"onAfterEditRow.emit($event)\"\n (previousSlide)=\"previousSlide.emit($event)\"\n (nextSlide)=\"nextSlide.emit($event)\"\n [canFetchMore]=\"canFetchMore\"\n (fetchMore)=\"fetchMore.emit($event)\"\n ></app-image-gallery-slideshow>\n </ng-template>\n </ion-modal>\n}\n\n<ion-popover #titlePopover>\n <ng-template>\n <ion-content class=\"ion-padding\" cdkTrapFocus [cdkTrapFocusAutoCapture]=\"true\">\n <form [formGroup]=\"_titleForm\">\n <mat-form-field floatLabel=\"auto\">\n <mat-label translate>IMAGE.GALLERY.TITLE</mat-label>\n <input\n matInput\n formControlName=\"title\"\n [readonly]=\"disabled\"\n [appAutofocus]=\"_titleAutofocus\"\n (keydown.control.enter)=\"titlePopover.dismiss(_titleForm.controls.title.value)\"\n />\n </mat-form-field>\n </form>\n </ion-content>\n\n <ion-footer>\n <ion-toolbar>\n <ion-row class=\"ion-no-padding\" nowrap>\n <ion-col></ion-col>\n\n <ion-col size=\"auto\">\n <ion-button fill=\"clear\" color=\"dark\" (click)=\"titlePopover.dismiss(null, 'CANCEL')\">\n <ion-label translate>COMMON.BTN_CANCEL</ion-label>\n </ion-button>\n <ion-button\n [fill]=\"disabled ? 'clear' : 'solid'\"\n (click)=\"titlePopover.dismiss(_titleForm.controls.title.value)\"\n (keyup.enter)=\"titlePopover.dismiss(_titleForm.controls.title.value)\"\n color=\"tertiary\"\n [disabled]=\"disabled\"\n [title]=\"!_showTooltip ? ('COMMON.BTN_VALIDATE_WITH_SHORTCUT_HELP' | translate) : ''\"\n [matTooltip]=\"_showTooltip ? ('COMMON.BTN_VALIDATE_WITH_SHORTCUT_HELP' | translate) : ''\"\n >\n <ion-label translate>COMMON.BTN_VALIDATE</ion-label>\n </ion-button>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n </ion-footer>\n </ng-template>\n</ion-popover>\n", styles: [":host{--image-margin-inline: 10px;--image-margin-safe-area: var(--image-margin-inline, 0px) * 2 - 3px;--segment-max-width: 150px}ion-toolbar{position:sticky;top:0}ion-toolbar ion-segment{max-width:var(--segment-max-width)}mat-toolbar,.mat-toolbar-single-row{padding:0;height:42px}ion-card{margin-left:unset;margin-right:unset;margin-inline:var(--image-margin-inline, 10px)}.image-slide{overflow:visible}.image-card{background-color:var(--ion-color-base);z-index:9;--img-scale: 1}.image-card .card-thumbnail{position:relative;overflow:hidden;display:flex;justify-content:center;align-items:center;margin:0;padding:0;width:100%;height:100%;max-height:var(--img-max-height, calc(100% - var(--image-margin-safe-area, 0px)))}.image-card .card-thumbnail>ion-img{min-height:100%;height:100%;object-fit:cover;object-position:center;transition:scale .2s ease-in-out;scale:var(--img-scale)}.image-card .card-thumbnail>ion-img img{max-height:var(--img-max-height)}.image-card.zoom-hover:hover{--img-scale: 1.2}.image-card ion-toolbar{--top-offset: calc(-1 * var(--ion-toolbar-height));position:absolute}.image-card ion-toolbar.title{--padding-start: 16px;--padding-end: 16px;--ion-color-base: rgba(0, 0, 0, .45) !important;position:absolute;top:calc(100% + var(--top-offset));transition:opacity .2s ease-in-out}.image-card ion-toolbar.title ion-label{color:#fff}.image-card ion-toolbar.buttons{--padding-start: 4px;--padding-end: 4px;--ion-color-base: var(--ion-color-light) !important;position:absolute;top:100%;transition:transform .2s ease-in-out}.image-card:hover ion-toolbar.buttons,.image-card ion-toolbar.buttons.focused{transform:translateY(var(--top-offset))}.image-card-button{height:calc(100% - var(--image-margin-safe-area, 0px));max-height:max(min(30vh,200px),var(--img-max-height, 0px));transition:.2s ease-in-out;max-width:250px}.image-card-button ion-card-content{height:100%}.image-card-button .icon-container{height:auto;position:relative;top:calc(50% - 75px);display:flex;align-items:center;justify-content:center}.image-card-button ion-icon:first-of-type{font-size:150px;height:150px;position:relative;top:0}.image-card-button ion-icon.icon-secondary{font-size:50px;position:absolute;top:0;left:max(min(50% + 25px,100% - 50px),0px)}.image-card-button ion-button{float:right}.image-card-button:hover{--ion-color-base: var(--ion-color-shade) !important}.image-card-button:hover ion-icon{--ion-color-base: var(--ion-color-shade) !important}.backdrop{height:100vh;width:100vw;background:#000;position:absolute;z-index:10}.gallery-container{position:relative}.gallery-container.inline-zoom-active{min-height:var(--inline-zoom-height, 60vh)}.inline-overlay{position:absolute;inset:0;z-index:20;background:#000}\n"] }]
30060
+ args: [{ selector: 'app-image-gallery', animations: [fadeInAnimation, fadeInOutAnimation], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (showToolbar && !isInlineSlideshowOpen) {\n <ion-toolbar color=\"light\" class=\"gallery-toolbar\">\n <ng-content select=\"ion-buttons[slot=start]\"></ng-content>\n\n <!-- Add -->\n @if (!readOnly && showAddToolbarButton && enabled) {\n <button\n mat-icon-button\n [title]=\"!_showTooltip ? ('COMMON.BTN_ADD' | translate) : ''\"\n [matTooltip]=\"_showTooltip ? ('COMMON.BTN_ADD' | translate) : ''\"\n (click)=\"add()\"\n >\n <mat-icon>add</mat-icon>\n </button>\n }\n\n <ng-content select=\"ion-buttons[slot=end]\"></ng-content>\n\n <ion-buttons slot=\"end\">\n <!-- Toggle view mode -->\n <ion-segment (ionChange)=\"toggleViewMode($event)\" color=\"accent\" [value]=\"_colSize\">\n <ion-segment-button [value]=\"4\">\n <mat-icon>view_module</mat-icon>\n </ion-segment-button>\n <ion-segment-button [value]=\"12\">\n <mat-icon>view_list</mat-icon>\n </ion-segment-button>\n </ion-segment>\n </ion-buttons>\n </ion-toolbar>\n}\n\n<div\n class=\"gallery-container ion-padding-bottom {{ mode }}\"\n [class.inline-zoom-active]=\"isInlineSlideshowOpen\"\n [style.--inline-zoom-height]=\"inlineZoomHeight\"\n>\n @if (mobile && zoomActive) {\n <div class=\"backdrop\" [style.opacity]=\"zoomScale\"></div>\n }\n\n <ion-grid class=\"ion-no-padding\" [class.cdk-visually-hidden]=\"isInlineSlideshowOpen\">\n <ion-row>\n <ion-col *rxFor=\"let row of _data$; trackBy: trackByFn\" [size]=\"_colSize\">\n @let image = row | propertyGet: 'currentData';\n @if (cardTemplate) {\n <ng-container\n *ngTemplateOutlet=\"cardTemplate; context: { $implicit: row, mode: mode, image: image }\"\n ></ng-container>\n } @else {\n <ion-card\n #card\n class=\"image-card\"\n [class.zoom-hover]=\"!mobile\"\n [color]=\"cardColor\"\n (click)=\"clickCard($event, row)\"\n tappable\n @fadeInAnimation\n >\n <figure class=\"card-thumbnail\">\n @if (image.url) {\n <ion-img [src]=\"image.url | appendQueryParams: getImageSizeQueryParams()\" />\n } @else {\n <ion-img [src]=\"image.dataUrl\" />\n }\n </figure>\n\n <!-- toolbar for title (when not hover) -->\n @if (showTitle && image.title) {\n <ion-toolbar color=\"transparent\" class=\"title\">\n <ion-label class=\"card-title\">{{ image.title }}</ion-label>\n </ion-toolbar>\n }\n\n <!-- action toolbar shown when hover image -->\n @if (!readOnly && !mobile && showCardToolbar) {\n <ion-toolbar #cardToolbar color=\"light\" class=\"buttons\">\n <!-- edit title button -->\n @if (showTitle) {\n <button\n mat-button\n slot=\"start\"\n [disabled]=\"disabled\"\n (click)=\"editTitle($event, row, cardToolbar)\"\n (focusin)=\"focusCardToolbar(cardToolbar)\"\n >\n <mat-icon>edit</mat-icon>\n <mat-label>\n @if (row.currentData.title | isNotNilOrBlank) {\n <span translate>IMAGE.GALLERY.BTN_EDIT_TITLE</span>\n } @else {\n <span translate>IMAGE.GALLERY.BTN_ADD_TITLE</span>\n }\n </mat-label>\n </button>\n }\n\n <div class=\"flex-spacer\"></div>\n\n <!-- delete button -->\n <button\n mat-icon-button\n slot=\"end\"\n class=\"ion-float-end\"\n [title]=\"!_showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n [matTooltip]=\"_showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n [disabled]=\"disabled\"\n (click)=\"delete($event, row)\"\n >\n <mat-icon>delete</mat-icon>\n </button>\n </ion-toolbar>\n }\n </ion-card>\n }\n </ion-col>\n\n <!-- add button -->\n @if ((showAddTextButton || showAddCardButton) && !readOnly) {\n <ion-col [size]=\"_colSize\" @fadeInAnimation>\n @if (showAddTextButton) {\n <ion-button (click)=\"add($event)\" [disabled]=\"disabled\" [color]=\"addButtonColor\">\n <mat-label>{{ addButtonText | translate }}</mat-label>\n <ion-icon slot=\"end\" name=\"camera\"></ion-icon>\n </ion-button>\n } @else if (showAddCardButton) {\n <ion-card\n class=\"image-card-button\"\n color=\"light\"\n (click)=\"add($event)\"\n [disabled]=\"disabled\"\n [title]=\"!_showTooltip ? (addButtonText | translate) : ''\"\n [matTooltip]=\"_showTooltip ? (addButtonText | translate) : ''\"\n tappable\n >\n <ion-card-content>\n <div class=\"icon-container\">\n <ion-icon name=\"camera\" [color]=\"addButtonColor\"></ion-icon>\n <ion-icon name=\"add\" [color]=\"addButtonColor\" class=\"icon-secondary\"></ion-icon>\n </div>\n </ion-card-content>\n </ion-card>\n }\n </ion-col>\n }\n </ion-row>\n </ion-grid>\n\n <!-- Inline slideshow: shown as an overlay directly within the gallery, instead of a full-screen modal.\n The rest of the page (outside the gallery) stays fully visible and usable, unlike the modal's backdrop. -->\n @if (isInlineSlideshowOpen) {\n <app-image-gallery-slideshow #inlineSlideshow\n class=\"inline-overlay\"\n [rows]=\"rows\"\n [selectedIndex]=\"_selectedRowIndex\"\n [mobile]=\"mobile\"\n [readOnly]=\"readOnly\"\n [disabled]=\"disabled\"\n [enableMouseZoom]=\"enableMouseZoom\"\n [wheelZoomStep]=\"wheelZoomStep\"\n [maxZoomRatio]=\"maxZoomRatio\"\n [enableRotate]=\"enableRotate\"\n [enableCrop]=\"enableCrop\"\n [enableLoop]=\"enableSlideshowLoop\"\n [editionFormat]=\"editionFormat\"\n [editionQuality]=\"editionQuality\"\n [imageSizeQueryParam]=\"imageSizeQueryParam\"\n [modalImageSize]=\"imageSizes?.modal\"\n (deleteRequested)=\"onSlideshowDeleteRequested($event)\"\n (closeRequested)=\"closeSlideshow()\"\n (afterEditImage)=\"onAfterEditImage.emit($event)\"\n (afterEditRow)=\"onAfterEditRow.emit($event)\"\n (previousSlide)=\"previousSlide.emit($event)\"\n (nextSlide)=\"nextSlide.emit($event)\"\n [canFetchMore]=\"canFetchMore\"\n (fetchMore)=\"fetchMore.emit($event)\"\n [debug]=\"debug\"\n ></app-image-gallery-slideshow>\n }\n</div>\n\n<!-- FAB button: add -->\n@if (showFabButton && !readOnly) {\n <ion-fab slot=\"fixed\" vertical=\"bottom\" horizontal=\"end\" [class.cdk-visually-hidden]=\"disabled\" @fadeInOutAnimation>\n <ion-fab-button color=\"tertiary\" (click)=\"add($event)\">\n <ion-icon name=\"camera\"></ion-icon>\n <ion-icon name=\"add\" class=\"icon-secondary\" style=\"left: 33px; top: 9px; font-size: 20px\"></ion-icon>\n </ion-fab-button>\n </ion-fab>\n}\n\n<!-- Zoom modal -->\n@if (slideshowMode === 'modal') {\n <ion-modal #zoomModal class=\"stack-modal zoom-modal\" [class.modal-fullscreen]=\"mobile\" [class.modal-large]=\"!mobile\">\n <ng-template>\n <app-image-gallery-slideshow\n [rows]=\"rows\"\n [selectedIndex]=\"_selectedRowIndex\"\n [mobile]=\"mobile\"\n [readOnly]=\"readOnly\"\n [disabled]=\"disabled\"\n [debug]=\"debug\"\n [enableMouseZoom]=\"enableMouseZoom\"\n [wheelZoomStep]=\"wheelZoomStep\"\n [maxZoomRatio]=\"maxZoomRatio\"\n [enableRotate]=\"enableRotate\"\n [enableCrop]=\"enableCrop\"\n [enableLoop]=\"enableSlideshowLoop\"\n [editionFormat]=\"editionFormat\"\n [editionQuality]=\"editionQuality\"\n [imageSizeQueryParam]=\"imageSizeQueryParam\"\n [modalImageSize]=\"imageSizes?.modal\"\n (deleteRequested)=\"onSlideshowDeleteRequested($event)\"\n (closeRequested)=\"closeSlideshow()\"\n (afterEditImage)=\"onAfterEditImage.emit($event)\"\n (afterEditRow)=\"onAfterEditRow.emit($event)\"\n (previousSlide)=\"previousSlide.emit($event)\"\n (nextSlide)=\"nextSlide.emit($event)\"\n [canFetchMore]=\"canFetchMore\"\n (fetchMore)=\"fetchMore.emit($event)\"\n ></app-image-gallery-slideshow>\n </ng-template>\n </ion-modal>\n}\n\n<ion-popover #titlePopover>\n <ng-template>\n <ion-content class=\"ion-padding\" cdkTrapFocus [cdkTrapFocusAutoCapture]=\"true\">\n <form [formGroup]=\"_titleForm\">\n <mat-form-field floatLabel=\"auto\">\n <mat-label translate>IMAGE.GALLERY.TITLE</mat-label>\n <input\n matInput\n formControlName=\"title\"\n [readonly]=\"disabled\"\n [appAutofocus]=\"_titleAutofocus\"\n (keydown.control.enter)=\"titlePopover.dismiss(_titleForm.controls.title.value)\"\n />\n </mat-form-field>\n </form>\n </ion-content>\n\n <ion-footer>\n <ion-toolbar>\n <ion-row class=\"ion-no-padding\" nowrap>\n <ion-col></ion-col>\n\n <ion-col size=\"auto\">\n <ion-button fill=\"clear\" color=\"dark\" (click)=\"titlePopover.dismiss(null, 'CANCEL')\">\n <ion-label translate>COMMON.BTN_CANCEL</ion-label>\n </ion-button>\n <ion-button\n [fill]=\"disabled ? 'clear' : 'solid'\"\n (click)=\"titlePopover.dismiss(_titleForm.controls.title.value)\"\n (keyup.enter)=\"titlePopover.dismiss(_titleForm.controls.title.value)\"\n color=\"tertiary\"\n [disabled]=\"disabled\"\n [title]=\"!_showTooltip ? ('COMMON.BTN_VALIDATE_WITH_SHORTCUT_HELP' | translate) : ''\"\n [matTooltip]=\"_showTooltip ? ('COMMON.BTN_VALIDATE_WITH_SHORTCUT_HELP' | translate) : ''\"\n >\n <ion-label translate>COMMON.BTN_VALIDATE</ion-label>\n </ion-button>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n </ion-footer>\n </ng-template>\n</ion-popover>\n", styles: [":host{--image-margin-inline: 10px;--image-margin-safe-area: var(--image-margin-inline, 0px) * 2 - 3px;--segment-max-width: 150px}ion-toolbar{position:sticky;top:0}ion-toolbar ion-segment{max-width:var(--segment-max-width)}mat-toolbar,.mat-toolbar-single-row{padding:0;height:42px}ion-card{margin-left:unset;margin-right:unset;margin-inline:var(--image-margin-inline, 10px)}.image-slide{overflow:visible}.image-card{background-color:var(--ion-color-base);z-index:9;--img-scale: 1}.image-card .card-thumbnail{position:relative;overflow:hidden;display:flex;justify-content:center;align-items:center;margin:0;padding:0;width:100%;height:100%;max-height:var(--img-max-height, calc(100% - var(--image-margin-safe-area, 0px)))}.image-card .card-thumbnail>ion-img{min-height:100%;height:100%;object-fit:cover;object-position:center;transition:scale .2s ease-in-out;scale:var(--img-scale)}.image-card .card-thumbnail>ion-img img{max-height:var(--img-max-height)}.image-card.zoom-hover:hover{--img-scale: 1.2}.image-card ion-toolbar{--top-offset: calc(-1 * var(--ion-toolbar-height));position:absolute}.image-card ion-toolbar.title{--padding-start: 16px;--padding-end: 16px;--ion-color-base: rgba(0, 0, 0, .45) !important;position:absolute;top:calc(100% + var(--top-offset));transition:opacity .2s ease-in-out}.image-card ion-toolbar.title ion-label{color:#fff}.image-card ion-toolbar.buttons{--padding-start: 4px;--padding-end: 4px;--ion-color-base: var(--ion-color-light) !important;position:absolute;top:100%;transition:transform .2s ease-in-out}.image-card:hover ion-toolbar.buttons,.image-card ion-toolbar.buttons.focused{transform:translateY(var(--top-offset))}.image-card-button{height:calc(100% - var(--image-margin-safe-area, 0px));max-height:max(min(30vh,200px),var(--img-max-height, 0px));transition:.2s ease-in-out;max-width:250px}.image-card-button ion-card-content{height:100%}.image-card-button .icon-container{height:auto;position:relative;top:calc(50% - 75px);display:flex;align-items:center;justify-content:center}.image-card-button ion-icon:first-of-type{font-size:150px;height:150px;position:relative;top:0}.image-card-button ion-icon.icon-secondary{font-size:50px;position:absolute;top:0;left:max(min(50% + 25px,100% - 50px),0px)}.image-card-button ion-button{float:right}.image-card-button:hover{--ion-color-base: var(--ion-color-shade) !important}.image-card-button:hover ion-icon{--ion-color-base: var(--ion-color-shade) !important}.backdrop{height:100vh;width:100vw;background:#000;position:absolute;z-index:10}.gallery-container{position:relative}.gallery-container.inline-zoom-active{min-height:var(--inline-zoom-height, 60vh)}.inline-overlay{position:absolute;top:0;right:0;bottom:0;left:0;z-index:20;background:#000}\n"] }]
29938
30061
  }], ctorParameters: () => [{ type: ImageService }, { type: i2$1.Platform }, { type: i2$1.AlertController }, { type: i1$1.TranslateService }, { type: i0.ChangeDetectorRef }, { type: Environment, decorators: [{
29939
30062
  type: Optional
29940
30063
  }, {
@@ -32091,6 +32214,174 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
32091
32214
  }]
32092
32215
  }] });
32093
32216
 
32217
+ class GitlabUtils {
32218
+ static API_V4_PATH = '/api/v4/';
32219
+ static API_V4_PROJECTS_PATH = '/api/v4/projects/';
32220
+ /**
32221
+ * Checks whether a URL points to a Gitlab instance.
32222
+ *
32223
+ * A URL is considered a Gitlab URL either when its host starts with 'gitlab.'
32224
+ * (e.g. 'https://gitlab.ifremer.fr/...'), or when it is a repository file URL
32225
+ * (see {@link isRepoFileUrl}) - in which case the host is NOT checked, since
32226
+ * a self-hosted Gitlab instance may use a custom domain name.
32227
+ *
32228
+ * @example
32229
+ * GitlabUtils.isGitlabUrl('https://gitlab.ifremer.fr/sih-public/sumaris/sumaris-app'); // true
32230
+ * GitlabUtils.isGitlabUrl('https://my-custom-domain.com/foo/-/raw/master/README.md'); // true (repo file URL)
32231
+ * GitlabUtils.isGitlabUrl('https://github.com/foo/bar'); // false
32232
+ */
32233
+ static isGitlabUrl(url) {
32234
+ if (!url)
32235
+ return false;
32236
+ return url.startsWith('https://gitlab.') || GitlabUtils.isRepoFileUrl(url);
32237
+ }
32238
+ /**
32239
+ * Checks whether a URL is a Gitlab repository file URL, i.e. a URL used to
32240
+ * view a single file in a repository ('/-/blob/') or to download its raw
32241
+ * content ('/-/raw/').
32242
+ *
32243
+ * @example
32244
+ * GitlabUtils.isRepoFileUrl('https://gitlab.ifremer.fr/a/b/-/raw/master/README.md'); // true
32245
+ * GitlabUtils.isRepoFileUrl('https://gitlab.ifremer.fr/a/b/-/blob/master/README.md'); // true
32246
+ * GitlabUtils.isRepoFileUrl('https://gitlab.ifremer.fr/api/v4/projects/a%2Fb'); // false
32247
+ */
32248
+ static isRepoFileUrl(url) {
32249
+ if (!url)
32250
+ return false;
32251
+ return url.includes('/-/raw/') || url.includes('/-/blob/');
32252
+ }
32253
+ /**
32254
+ * Converts a repository file URL into its 'raw content' equivalent, by
32255
+ * replacing the '/-/blob/' (file viewer) or '/-/tree/' (directory viewer)
32256
+ * segment with '/-/raw/'.
32257
+ *
32258
+ * If the URL is not a repository file URL (see {@link isRepoFileUrl}), it is
32259
+ * returned unchanged.
32260
+ *
32261
+ * @example
32262
+ * GitlabUtils.getRawRepoFileUrl('https://gitlab.ifremer.fr/a/b/-/blob/master/README.md');
32263
+ * // 'https://gitlab.ifremer.fr/a/b/-/raw/master/README.md'
32264
+ */
32265
+ static getRawRepoFileUrl(url) {
32266
+ if (!GitlabUtils.isRepoFileUrl(url))
32267
+ return url;
32268
+ return url.replace('/-/blob/', '/-/raw/').replace('/-/tree/', '/-/raw/');
32269
+ }
32270
+ /**
32271
+ * Checks whether a URL targets the Gitlab REST API (any endpoint under
32272
+ * '/api/v4/'). Repository file URLs (see {@link isRepoFileUrl}) are always
32273
+ * excluded, even if they happen to contain '/api/v4/' somewhere in their path.
32274
+ */
32275
+ static isApiUrl(url) {
32276
+ if (!url || GitlabUtils.isRepoFileUrl(url))
32277
+ return false;
32278
+ return url.includes(GitlabUtils.API_V4_PATH);
32279
+ }
32280
+ /**
32281
+ * Checks whether a URL targets the Gitlab 'projects' REST API (any endpoint
32282
+ * under '/api/v4/projects/'), e.g. project releases, branches, etc.
32283
+ */
32284
+ static isApiProjectsUrl(url) {
32285
+ if (!url || GitlabUtils.isRepoFileUrl(url))
32286
+ return false;
32287
+ return url.includes(GitlabUtils.API_V4_PROJECTS_PATH);
32288
+ }
32289
+ /**
32290
+ * Checks whether a URL targets the Gitlab project releases REST API, e.g.
32291
+ * 'https://gitlab.ifremer.fr/api/v4/projects/{id}/releases' (all releases)
32292
+ * or 'https://gitlab.ifremer.fr/api/v4/projects/{id}/releases/{tag_name}'
32293
+ * (a single release).
32294
+ */
32295
+ static isProjectReleasesApiUrl(url) {
32296
+ return GitlabUtils.isApiProjectsUrl(url) && url.includes('/releases');
32297
+ }
32298
+ /**
32299
+ * Fetches releases from the Gitlab project releases REST API.
32300
+ *
32301
+ * The given URL can either target the collection endpoint
32302
+ * ('.../releases', returning all releases), or a single release endpoint
32303
+ * ('.../releases/{tag_name}', returning one release, wrapped into an array).
32304
+ *
32305
+ * @return the list of releases, an empty array if the API call failed for a
32306
+ * single release, or `null` if the URL is not a project releases API URL.
32307
+ */
32308
+ static async getProjectReleases(http, url) {
32309
+ if (!GitlabUtils.isProjectReleasesApiUrl(url))
32310
+ return null;
32311
+ // Many releases API urls end with /releases
32312
+ if (url.trim().endsWith('/releases')) {
32313
+ return await HttpUtils.getJson(http, url);
32314
+ }
32315
+ // One release API url
32316
+ try {
32317
+ const release = await HttpUtils.getJson(http, url);
32318
+ return release ? [release] : [];
32319
+ }
32320
+ catch (err) {
32321
+ console.error('Failed to fetch release from the Gitlab API ' + url, err);
32322
+ return null;
32323
+ }
32324
+ }
32325
+ /**
32326
+ * Extracts the URL-encoded project path (e.g. 'group%2Fsubgroup%2Fproject')
32327
+ * from a Gitlab 'projects' API URL. The returned value is NOT decoded, so it
32328
+ * still contains '%2F' in place of '/'.
32329
+ *
32330
+ * @example
32331
+ * GitlabUtils.getProjectNameFromApiUrl('https://gitlab.ifremer.fr/api/v4/projects/sih-public%2Fsumaris%2Fsumaris-app/releases');
32332
+ * // 'sih-public%2Fsumaris%2Fsumaris-app'
32333
+ */
32334
+ static getProjectNameFromApiUrl(url) {
32335
+ if (!url?.includes(GitlabUtils.API_V4_PROJECTS_PATH))
32336
+ return null;
32337
+ return url.slice(url.indexOf(GitlabUtils.API_V4_PROJECTS_PATH) + GitlabUtils.API_V4_PROJECTS_PATH.length)
32338
+ .split('/', 2)[0];
32339
+ }
32340
+ /**
32341
+ * Resolves the Gitlab web page associated to a URL:
32342
+ * - for an API 'projects' URL (see {@link isApiUrl}), this is the project's
32343
+ * home page, e.g. 'https://gitlab.ifremer.fr/sih-public/sumaris/sumaris-app';
32344
+ * - for a repository file URL (see {@link isRepoFileUrl}), this is the
32345
+ * project's raw content base path (e.g. '.../my-project/-/raw/'), since
32346
+ * the project path cannot be reliably distinguished from the file path
32347
+ * once slashes are un-encoded.
32348
+ *
32349
+ * @return `null` if the project page cannot be resolved from the given URL.
32350
+ */
32351
+ static getProjectPage(url) {
32352
+ if (GitlabUtils.isRepoFileUrl(url)) {
32353
+ const fixUrl = GitlabUtils.getRawRepoFileUrl(url);
32354
+ const index = fixUrl.indexOf('/-/raw/');
32355
+ if (index >= 0)
32356
+ return fixUrl.slice(0, index + '/-/raw/'.length);
32357
+ }
32358
+ else if (GitlabUtils.isApiUrl(url)) {
32359
+ const index = url.indexOf(GitlabUtils.API_V4_PROJECTS_PATH);
32360
+ if (index >= 0) {
32361
+ const baseUrl = url.slice(0, index);
32362
+ const projectName = GitlabUtils.getProjectNameFromApiUrl(url);
32363
+ return UrlUtils.concat(baseUrl, projectName.replace(/%2F/g, '/'));
32364
+ }
32365
+ }
32366
+ return null;
32367
+ }
32368
+ /**
32369
+ * Builds the URL of the Gitlab web page listing a project's releases (e.g.
32370
+ * 'https://gitlab.ifremer.fr/sih-public/sumaris/sumaris-app/-/releases'),
32371
+ * optionally anchored on a specific version/tag.
32372
+ *
32373
+ * @param url any URL from which the project page can be resolved (see {@link getProjectPage})
32374
+ * @param version optional tag name (e.g. '2.16.2') appended to the releases page
32375
+ * @return `null` if the project page cannot be resolved from the given URL.
32376
+ */
32377
+ static getProjectReleasePage(url, version) {
32378
+ const basePath = GitlabUtils.getProjectPage(url);
32379
+ if (!basePath)
32380
+ return null;
32381
+ return UrlUtils.concat(UrlUtils.concat(basePath, '/-/releases'), version);
32382
+ }
32383
+ }
32384
+
32094
32385
  class MarkdownUtils {
32095
32386
  static HTML_ENTITIES_MAP = {
32096
32387
  '&#39;': "'", // Single quote
@@ -32151,18 +32442,8 @@ class MarkdownUtils {
32151
32442
  }
32152
32443
  return UrlUtils.normalizeUrl(UrlUtils.concat(baseUrl, relativePath));
32153
32444
  }
32154
- static isGitlabUrl(url) {
32155
- if (!url)
32156
- return false;
32157
- return url.startsWith('https://gitlab.') || url.includes('/-/raw/') || url.includes('/-/blob/');
32158
- }
32159
- static fixGitlabUrlToRaw(url) {
32160
- if (!this.isGitlabUrl(url))
32161
- return url;
32162
- return url.replace('/-/blob/', '/-/raw/').replace('/-/tree/', '/-/raw/');
32163
- }
32164
32445
  static getRootUrl(url) {
32165
- if (this.isGitlabUrl(url)) {
32446
+ if (GitlabUtils.isRepoFileUrl(url)) {
32166
32447
  console.debug('[markdown] Gitlab URL detected: ' + url);
32167
32448
  url = url.replace('/-/blob/', '/-/raw/');
32168
32449
  if (url.includes('/-/raw/')) {
@@ -32861,9 +33142,9 @@ class AppMarkdownContent {
32861
33142
  // Clean URL (remove fragment and query)
32862
33143
  const anchor = opts?.anchor || UrlUtils.getFragment(src);
32863
33144
  src = UrlUtils.stripFragmentAndQuery(src);
32864
- src = MarkdownUtils.isGitlabUrl(src) ? MarkdownUtils.fixGitlabUrlToRaw(src) : src;
33145
+ src = GitlabUtils.isRepoFileUrl(src) ? GitlabUtils.getRawRepoFileUrl(src) : src;
32865
33146
  let fallbackSrc = opts?.fallbackSrc ? UrlUtils.stripFragmentAndQuery(opts.fallbackSrc) : null;
32866
- fallbackSrc = MarkdownUtils.isGitlabUrl(fallbackSrc) ? MarkdownUtils.fixGitlabUrlToRaw(fallbackSrc) : fallbackSrc;
33147
+ fallbackSrc = GitlabUtils.isRepoFileUrl(fallbackSrc) ? GitlabUtils.getRawRepoFileUrl(fallbackSrc) : fallbackSrc;
32867
33148
  if (this._src !== src || this._fallbackSrc !== fallbackSrc || this._anchor !== anchor) {
32868
33149
  const url = src ? src + (isNotNilOrBlank(anchor) ? `#${anchor}` : '') : null;
32869
33150
  if (url)
@@ -32957,11 +33238,11 @@ class AppMarkdownContent {
32957
33238
  }));
32958
33239
  }
32959
33240
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AppMarkdownContent, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.ElementRef }, { token: DOCUMENT }, { token: i1$7.ActivatedRoute, optional: true }], target: i0.ɵɵFactoryTarget.Component });
32960
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AppMarkdownContent, selector: "app-markdown-content", inputs: { data: "data", src: "src", anchor: "anchor", debug: ["debug", "debug", booleanAttribute], showError: ["showError", "showError", booleanAttribute], animated: ["animated", "animated", booleanAttribute], enableNavigationHistory: ["enableNavigationHistory", "enableNavigationHistory", booleanAttribute], updateRouteLocation: ["updateRouteLocation", "updateRouteLocation", booleanAttribute] }, outputs: { markdownUrlChange: "markdownUrlChange" }, ngImport: i0, template: "<app-debug *ngIf=\"debug\">\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col>\n loading: {{ loading }}\n <br />\n error: {{ error }}\n <br />\n <br />\n baseUrl: {{ baseUrl }}\n <br />\n markdownUrl: {{ src }}\n <br />\n markdownFragment: {{ _anchor }}\n <br />\n markdownContent: {{ data }}\n <br />\n </ion-col>\n </ion-row>\n </ion-grid>\n</app-debug>\n\n<!-- error -->\n@if (showError && error; as error) {\n <ion-item lines=\"none\" @slideDownAnimation>\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n} @else {\n @if (data) {\n <markdown [data]=\"data\" (ready)=\"onReady()\" emoji></markdown>\n } @else if (src) {\n <markdown [src]=\"src\" (error)=\"onLoadError($event)\" (ready)=\"onReady()\" emoji></markdown>\n }\n}\n\n<!-- loading spinner -->\n@if (loading) {\n <div class=\"loading-spinner\">\n <ng-container *ngTemplateOutlet=\"loadingParagraph\"></ng-container>\n <ng-container *ngTemplateOutlet=\"loadingParagraph\"></ng-container>\n <ng-container *ngTemplateOutlet=\"loadingParagraph\"></ng-container>\n </div>\n}\n\n<ng-template #loadingParagraph>\n <p>\n <ion-skeleton-text [animated]=\"animated\" style=\"width: 75%; margin-inline-start: 5%\"></ion-skeleton-text>\n <ion-skeleton-text [animated]=\"animated\" style=\"width: 80%\"></ion-skeleton-text>\n <ion-skeleton-text [animated]=\"animated\" style=\"width: 80%\"></ion-skeleton-text>\n <ion-skeleton-text [animated]=\"animated\" style=\"width: 80%\"></ion-skeleton-text>\n <ion-skeleton-text [animated]=\"animated\" style=\"width: 80%\"></ion-skeleton-text>\n <ion-skeleton-text [animated]=\"animated\" style=\"width: 80%\"></ion-skeleton-text>\n </p>\n</ng-template>\n", styles: [":host ::ng-deep markdown div.logo{display:none;visibility:hidden}:host ::ng-deep markdown hr{border-top:1px solid var(--ion-color-medium-tint, #dcdcde);margin:10px 0}:host ::ng-deep markdown blockquote{--block-margin: 16px 0;--block-padding: 4px 16px;--box-shadow: -4px 0px 0px rgba(var(--ion-text-color-rgb), .4);--ion-padding: 4px;display:block;position:relative;margin:var(--block-margin);padding:var(--block-padding);font-style:italic;background:var(--ion-color-light);box-shadow:var(--box-shadow)}:host ::ng-deep markdown code{color:rgba(var(--ion-text-color-rgb),.7);background:rgba(var(--ion-text-color-rgb),.03);font-size:.9rem;letter-spacing:-.5px}.loading-spinner ion-skeleton-text{width:100%;height:1.5em}.loading-spinner p{margin-top:var(ion-padding, 16px)}@media print{@page{size:portrait;margin:1.1cm}body{font-family:Roboto,Helvetica,Arial,sans-serif;margin:0;padding:0;overflow:hidden}markdown hr{page-break-before:always;border:none;margin:0;padding:0}markdown img{max-width:100%;height:auto;page-break-inside:avoid}}\n"], dependencies: [{ kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "component", type: i2$1.IonSkeletonText, selector: "ion-skeleton-text", inputs: ["animated"] }, { kind: "component", type: i4$2.MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }, { kind: "component", type: DebugComponent, selector: "app-debug", inputs: ["titlePrefix", "title", "enable", "expanded"] }, { kind: "directive", type: MarkdownDirective, selector: "markdown,[markdown]" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], animations: [slideDownAnimation], changeDetection: i0.ChangeDetectionStrategy.OnPush });
33241
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AppMarkdownContent, selector: "app-markdown-content", inputs: { data: "data", src: "src", anchor: "anchor", debug: ["debug", "debug", booleanAttribute], showError: ["showError", "showError", booleanAttribute], animated: ["animated", "animated", booleanAttribute], enableNavigationHistory: ["enableNavigationHistory", "enableNavigationHistory", booleanAttribute], updateRouteLocation: ["updateRouteLocation", "updateRouteLocation", booleanAttribute] }, outputs: { markdownUrlChange: "markdownUrlChange" }, ngImport: i0, template: "<app-debug *ngIf=\"debug\">\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col>\n loading: {{ loading }}\n <br />\n error: {{ error }}\n <br />\n <br />\n baseUrl: {{ baseUrl }}\n <br />\n markdownUrl: {{ src }}\n <br />\n markdownFragment: {{ _anchor }}\n <br />\n markdownContent: {{ data }}\n <br />\n </ion-col>\n </ion-row>\n </ion-grid>\n</app-debug>\n\n<!-- error -->\n@if (showError && error; as error) {\n <ion-item lines=\"none\" @slideDownAnimation>\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n} @else {\n @if (data) {\n <markdown [data]=\"data\" (ready)=\"onReady()\" emoji></markdown>\n } @else if (src) {\n <markdown [src]=\"src\" (error)=\"onLoadError($event)\" (ready)=\"onReady()\" emoji></markdown>\n }\n}\n\n<!-- loading spinner -->\n@if (loading) {\n <div class=\"loading-spinner\">\n <ng-container *ngTemplateOutlet=\"loadingParagraph\"></ng-container>\n <ng-container *ngTemplateOutlet=\"loadingParagraph\"></ng-container>\n <ng-container *ngTemplateOutlet=\"loadingParagraph\"></ng-container>\n </div>\n}\n\n<ng-template #loadingParagraph>\n <p>\n <ion-skeleton-text [animated]=\"animated\" style=\"width: 75%; margin-inline-start: 5%\"></ion-skeleton-text>\n <ion-skeleton-text [animated]=\"animated\" style=\"width: 80%\"></ion-skeleton-text>\n <ion-skeleton-text [animated]=\"animated\" style=\"width: 80%\"></ion-skeleton-text>\n <ion-skeleton-text [animated]=\"animated\" style=\"width: 80%\"></ion-skeleton-text>\n <ion-skeleton-text [animated]=\"animated\" style=\"width: 80%\"></ion-skeleton-text>\n <ion-skeleton-text [animated]=\"animated\" style=\"width: 80%\"></ion-skeleton-text>\n </p>\n</ng-template>\n", styles: [":host ::ng-deep markdown div.logo{display:none;visibility:hidden}:host ::ng-deep markdown hr{border-top:1px solid var(--ion-color-medium-tint, #dcdcde);margin:10px 0}:host ::ng-deep markdown blockquote{--block-margin: 16px 0;--block-padding: 4px 16px;--box-shadow: -4px 0px 0px rgba(var(--ion-text-color-rgb), .4);--ion-padding: 4px;display:block;position:relative;margin:var(--block-margin);padding:var(--block-padding);font-style:italic;background:rgba(var(--ion-color-light-rgb),.87);box-shadow:var(--box-shadow)}:host ::ng-deep markdown code{background:rgba(var(--ion-text-color-rgb),.03);font-size:.9rem;letter-spacing:-.5px}:host ::ng-deep markdown div[style*=\"float: right\"]{position:relative;z-index:999;margin-left:var(--ion-padding, 16px)}.loading-spinner ion-skeleton-text{width:100%;height:1.5em}.loading-spinner p{margin-top:var(ion-padding, 16px)}@media print{@page{size:portrait;margin:1.1cm}body{font-family:Roboto,Helvetica,Arial,sans-serif;margin:0;padding:0;overflow:hidden}markdown hr{page-break-before:always;border:none;margin:0;padding:0}markdown img{max-width:100%;height:auto;page-break-inside:avoid}}\n"], dependencies: [{ kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "component", type: i2$1.IonSkeletonText, selector: "ion-skeleton-text", inputs: ["animated"] }, { kind: "component", type: i4$2.MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }, { kind: "component", type: DebugComponent, selector: "app-debug", inputs: ["titlePrefix", "title", "enable", "expanded"] }, { kind: "directive", type: MarkdownDirective, selector: "markdown,[markdown]" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], animations: [slideDownAnimation], changeDetection: i0.ChangeDetectionStrategy.OnPush });
32961
33242
  }
32962
33243
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AppMarkdownContent, decorators: [{
32963
33244
  type: Component,
32964
- args: [{ selector: 'app-markdown-content', changeDetection: ChangeDetectionStrategy.OnPush, animations: [slideDownAnimation], template: "<app-debug *ngIf=\"debug\">\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col>\n loading: {{ loading }}\n <br />\n error: {{ error }}\n <br />\n <br />\n baseUrl: {{ baseUrl }}\n <br />\n markdownUrl: {{ src }}\n <br />\n markdownFragment: {{ _anchor }}\n <br />\n markdownContent: {{ data }}\n <br />\n </ion-col>\n </ion-row>\n </ion-grid>\n</app-debug>\n\n<!-- error -->\n@if (showError && error; as error) {\n <ion-item lines=\"none\" @slideDownAnimation>\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n} @else {\n @if (data) {\n <markdown [data]=\"data\" (ready)=\"onReady()\" emoji></markdown>\n } @else if (src) {\n <markdown [src]=\"src\" (error)=\"onLoadError($event)\" (ready)=\"onReady()\" emoji></markdown>\n }\n}\n\n<!-- loading spinner -->\n@if (loading) {\n <div class=\"loading-spinner\">\n <ng-container *ngTemplateOutlet=\"loadingParagraph\"></ng-container>\n <ng-container *ngTemplateOutlet=\"loadingParagraph\"></ng-container>\n <ng-container *ngTemplateOutlet=\"loadingParagraph\"></ng-container>\n </div>\n}\n\n<ng-template #loadingParagraph>\n <p>\n <ion-skeleton-text [animated]=\"animated\" style=\"width: 75%; margin-inline-start: 5%\"></ion-skeleton-text>\n <ion-skeleton-text [animated]=\"animated\" style=\"width: 80%\"></ion-skeleton-text>\n <ion-skeleton-text [animated]=\"animated\" style=\"width: 80%\"></ion-skeleton-text>\n <ion-skeleton-text [animated]=\"animated\" style=\"width: 80%\"></ion-skeleton-text>\n <ion-skeleton-text [animated]=\"animated\" style=\"width: 80%\"></ion-skeleton-text>\n <ion-skeleton-text [animated]=\"animated\" style=\"width: 80%\"></ion-skeleton-text>\n </p>\n</ng-template>\n", styles: [":host ::ng-deep markdown div.logo{display:none;visibility:hidden}:host ::ng-deep markdown hr{border-top:1px solid var(--ion-color-medium-tint, #dcdcde);margin:10px 0}:host ::ng-deep markdown blockquote{--block-margin: 16px 0;--block-padding: 4px 16px;--box-shadow: -4px 0px 0px rgba(var(--ion-text-color-rgb), .4);--ion-padding: 4px;display:block;position:relative;margin:var(--block-margin);padding:var(--block-padding);font-style:italic;background:var(--ion-color-light);box-shadow:var(--box-shadow)}:host ::ng-deep markdown code{color:rgba(var(--ion-text-color-rgb),.7);background:rgba(var(--ion-text-color-rgb),.03);font-size:.9rem;letter-spacing:-.5px}.loading-spinner ion-skeleton-text{width:100%;height:1.5em}.loading-spinner p{margin-top:var(ion-padding, 16px)}@media print{@page{size:portrait;margin:1.1cm}body{font-family:Roboto,Helvetica,Arial,sans-serif;margin:0;padding:0;overflow:hidden}markdown hr{page-break-before:always;border:none;margin:0;padding:0}markdown img{max-width:100%;height:auto;page-break-inside:avoid}}\n"] }]
33245
+ args: [{ selector: 'app-markdown-content', changeDetection: ChangeDetectionStrategy.OnPush, animations: [slideDownAnimation], template: "<app-debug *ngIf=\"debug\">\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col>\n loading: {{ loading }}\n <br />\n error: {{ error }}\n <br />\n <br />\n baseUrl: {{ baseUrl }}\n <br />\n markdownUrl: {{ src }}\n <br />\n markdownFragment: {{ _anchor }}\n <br />\n markdownContent: {{ data }}\n <br />\n </ion-col>\n </ion-row>\n </ion-grid>\n</app-debug>\n\n<!-- error -->\n@if (showError && error; as error) {\n <ion-item lines=\"none\" @slideDownAnimation>\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n} @else {\n @if (data) {\n <markdown [data]=\"data\" (ready)=\"onReady()\" emoji></markdown>\n } @else if (src) {\n <markdown [src]=\"src\" (error)=\"onLoadError($event)\" (ready)=\"onReady()\" emoji></markdown>\n }\n}\n\n<!-- loading spinner -->\n@if (loading) {\n <div class=\"loading-spinner\">\n <ng-container *ngTemplateOutlet=\"loadingParagraph\"></ng-container>\n <ng-container *ngTemplateOutlet=\"loadingParagraph\"></ng-container>\n <ng-container *ngTemplateOutlet=\"loadingParagraph\"></ng-container>\n </div>\n}\n\n<ng-template #loadingParagraph>\n <p>\n <ion-skeleton-text [animated]=\"animated\" style=\"width: 75%; margin-inline-start: 5%\"></ion-skeleton-text>\n <ion-skeleton-text [animated]=\"animated\" style=\"width: 80%\"></ion-skeleton-text>\n <ion-skeleton-text [animated]=\"animated\" style=\"width: 80%\"></ion-skeleton-text>\n <ion-skeleton-text [animated]=\"animated\" style=\"width: 80%\"></ion-skeleton-text>\n <ion-skeleton-text [animated]=\"animated\" style=\"width: 80%\"></ion-skeleton-text>\n <ion-skeleton-text [animated]=\"animated\" style=\"width: 80%\"></ion-skeleton-text>\n </p>\n</ng-template>\n", styles: [":host ::ng-deep markdown div.logo{display:none;visibility:hidden}:host ::ng-deep markdown hr{border-top:1px solid var(--ion-color-medium-tint, #dcdcde);margin:10px 0}:host ::ng-deep markdown blockquote{--block-margin: 16px 0;--block-padding: 4px 16px;--box-shadow: -4px 0px 0px rgba(var(--ion-text-color-rgb), .4);--ion-padding: 4px;display:block;position:relative;margin:var(--block-margin);padding:var(--block-padding);font-style:italic;background:rgba(var(--ion-color-light-rgb),.87);box-shadow:var(--box-shadow)}:host ::ng-deep markdown code{background:rgba(var(--ion-text-color-rgb),.03);font-size:.9rem;letter-spacing:-.5px}:host ::ng-deep markdown div[style*=\"float: right\"]{position:relative;z-index:999;margin-left:var(--ion-padding, 16px)}.loading-spinner ion-skeleton-text{width:100%;height:1.5em}.loading-spinner p{margin-top:var(ion-padding, 16px)}@media print{@page{size:portrait;margin:1.1cm}body{font-family:Roboto,Helvetica,Arial,sans-serif;margin:0;padding:0;overflow:hidden}markdown hr{page-break-before:always;border:none;margin:0;padding:0}markdown img{max-width:100%;height:auto;page-break-inside:avoid}}\n"] }]
32965
33246
  }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: Document, decorators: [{
32966
33247
  type: Inject,
32967
33248
  args: [DOCUMENT]
@@ -33172,6 +33453,8 @@ class AboutModal {
33172
33453
  modalController;
33173
33454
  configService;
33174
33455
  networkService;
33456
+ platformService;
33457
+ http;
33175
33458
  cd;
33176
33459
  environment;
33177
33460
  developers;
@@ -33185,17 +33468,20 @@ class AboutModal {
33185
33468
  reportIssueUrl;
33186
33469
  forumUrl;
33187
33470
  helpUrl;
33471
+ changelogUrl;
33188
33472
  showVersionDetails = false;
33189
33473
  nodeInfo;
33190
33474
  buildDate;
33191
33475
  get allowVersionDetails() {
33192
33476
  return !!this.buildDate || !!this.nodeInfo?.softwareVersion;
33193
33477
  }
33194
- constructor(translate, modalController, configService, networkService, cd, environment, developers, partners) {
33478
+ constructor(translate, modalController, configService, networkService, platformService, http, cd, environment, developers, partners) {
33195
33479
  this.translate = translate;
33196
33480
  this.modalController = modalController;
33197
33481
  this.configService = configService;
33198
33482
  this.networkService = networkService;
33483
+ this.platformService = platformService;
33484
+ this.http = http;
33199
33485
  this.cd = cd;
33200
33486
  this.environment = environment;
33201
33487
  this.developers = developers;
@@ -33210,6 +33496,7 @@ class AboutModal {
33210
33496
  this.reportIssueUrl = environment?.reportIssueUrl;
33211
33497
  this.forumUrl = environment?.forumUrl;
33212
33498
  this.helpUrl = environment?.helpUrl;
33499
+ this.changelogUrl = environment?.changelogUrl;
33213
33500
  this.config$ = configService.config;
33214
33501
  this._subscription.add(this.config$.subscribe((config) => this.setConfig(config)));
33215
33502
  }
@@ -33228,8 +33515,58 @@ class AboutModal {
33228
33515
  this.forumUrl = config?.getProperty(CORE_CONFIG_OPTIONS.FORUM_URL) || this.environment?.forumUrl;
33229
33516
  this.helpUrl = config?.getProperty(CORE_CONFIG_OPTIONS.HELP_URL) || this.environment?.helpUrl;
33230
33517
  this.reportIssueUrl = config?.getProperty(CORE_CONFIG_OPTIONS.REPORT_ISSUE_URL) || this.environment?.reportIssueUrl;
33518
+ this.changelogUrl = this.getChangelogUrl(config);
33231
33519
  this.buildDate = this.environment?.buildDate;
33232
33520
  }
33521
+ getChangelogUrl(config) {
33522
+ let changelogUrl = config?.getProperty(CORE_CONFIG_OPTIONS.CHANGELOG_URL) || this.environment?.changelogUrl;
33523
+ // Replace {version} placeholder
33524
+ if (isNotNilOrBlank(changelogUrl) && isNotNilOrBlank(this.environment?.version)) {
33525
+ changelogUrl = changelogUrl.replace('{version}', this.environment.version);
33526
+ }
33527
+ return changelogUrl;
33528
+ }
33529
+ async openChangelog(event) {
33530
+ event.preventDefault();
33531
+ event.stopPropagation();
33532
+ if (!this.changelogUrl)
33533
+ return;
33534
+ const title = this.translate.instant('ABOUT.BTN_CHANGELOG');
33535
+ const canPrint = !this.platformService.mobile;
33536
+ const version = this.environment?.version;
33537
+ // Detect gitlab API
33538
+ if (GitlabUtils.isProjectReleasesApiUrl(this.changelogUrl)) {
33539
+ let releases = await GitlabUtils.getProjectReleases(this.http, this.changelogUrl);
33540
+ // Filter releases by version compatibility
33541
+ if (isNotNilOrBlank(version)) {
33542
+ const index = releases.findIndex(r => r.tag_name == version);
33543
+ if (index >= 0)
33544
+ releases = releases.slice(index);
33545
+ releases = releases.filter(r => VersionUtils.isCompatible(r.tag_name, version));
33546
+ }
33547
+ const content = releases.map(r => `### ${r.name ?? r.tag_name}\n\n${r.description}`)
33548
+ .join('\n\n---\n');
33549
+ if (isNotNilOrBlank(content)) {
33550
+ return await AppMarkdownModal.show(this.modalController, {
33551
+ title,
33552
+ data: content,
33553
+ canPrint
33554
+ });
33555
+ }
33556
+ console.warn(`[about] No compatible release found, from the Gitlab release API: ${this.changelogUrl}`);
33557
+ // Open the gitlab release page
33558
+ const releasePage = GitlabUtils.getProjectReleasePage(this.changelogUrl, version);
33559
+ if (isNotNilOrBlank(releasePage)) {
33560
+ console.info(`[about] Redirecting to the page ${releasePage}`);
33561
+ return this.platformService.open(releasePage);
33562
+ }
33563
+ }
33564
+ await AppMarkdownModal.show(this.modalController, {
33565
+ title,
33566
+ src: this.changelogUrl,
33567
+ canPrint
33568
+ });
33569
+ }
33233
33570
  async loadNodeInfo() {
33234
33571
  this.nodeInfo = await this.getNodeInfo();
33235
33572
  this.cd.markForCheck();
@@ -33245,13 +33582,13 @@ class AboutModal {
33245
33582
  return null;
33246
33583
  }
33247
33584
  }
33248
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AboutModal, deps: [{ token: i1$1.TranslateService }, { token: i2$1.ModalController }, { token: ConfigService }, { token: NetworkService }, { token: i0.ChangeDetectorRef }, { token: ENVIRONMENT, optional: true }, { token: APP_ABOUT_DEVELOPERS, optional: true }, { token: APP_ABOUT_PARTNERS, optional: true }], target: i0.ɵɵFactoryTarget.Component });
33249
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AboutModal, selector: "app-about-modal", ngImport: i0, template: "<ion-header>\n <ion-toolbar color=\"light\">\n <ion-title>\n {{ 'ABOUT.TITLE' | translate }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <ion-button (click)=\"close()\" visible-xs visible-sm visible-mobile>\n {{ 'COMMON.BTN_CLOSE' | translate }}\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n <ion-list lines=\"none\">\n <!-- Peer info -->\n @if (config$ | async; as config) {\n <ion-item>\n <span slot=\"start\">&nbsp;</span>\n <ion-label class=\"ion-text-wrap\">\n <h2>\n <b>{{ config.label | capitalize }}</b>\n @if (config.name) {\n -\n <span [innerHTML]=\"config.name\"></span>\n }\n </h2>\n\n @if (config.description) {\n <p>\n <markdown [data]=\"config.description\" emoji></markdown>\n </p>\n }\n </ion-label>\n </ion-item>\n }\n\n <!-- Version info -->\n <ion-item>\n <span slot=\"start\">&nbsp;</span>\n <ion-label class=\"ion-text-wrap\">\n <h2>\n @if (name) {\n <small>{{ 'ABOUT.POWERED_BY' | translate }}</small>\n {{ name }}\n }\n @if (version) {\n <span [innerHTML]=\"'ABOUT.VERSION' | translate: { version: version }\"></span>\n }\n @if(allowVersionDetails) {\n &nbsp;\n <small>\n <a (click)=\"showVersionDetails=!showVersionDetails\" tappable>\n {{'COMMON.BTN_SHOW_MORE' | translate}}\n <ion-icon [name]=\"!showVersionDetails ? 'chevron-down' : 'chevron-up'\"></ion-icon>\n </a>\n </small>\n }\n </h2>\n\n <!-- version details -->\n @if (showVersionDetails) {\n <p class=\"version-details\">\n <!-- App version -->\n @if (buildDate) {\n <small>\n <ion-icon name=\"logo-angular\"></ion-icon>\n <span [innerHTML]=\"'ABOUT.APP_VERSION' | translate: { version: version }\"></span>\n &nbsp;\n <ion-icon name=\"time-outline\"></ion-icon>\n {{ buildDate | dateFormat: { time: true, seconds: false } }}\n </small>\n <br/>\n }\n <!-- Pod version -->\n @if (nodeInfo?.softwareVersion && nodeInfo.buildDate) {\n <small>\n <ion-icon name=\"server\"></ion-icon>\n <span [innerHTML]=\"'ABOUT.POD_VERSION' | translate: { version: nodeInfo.softwareVersion }\"></span>\n &nbsp;\n <ion-icon name=\"time-outline\"></ion-icon>\n {{ nodeInfo.buildDate | dateFormat: { time: true, seconds: false } }}\n </small>\n }\n </p>\n }\n\n <!-- license-->\n <p [innerHTML]=\"'ABOUT.LICENSE' | translate\"></p>\n </ion-label>\n </ion-item>\n\n <!-- Help -->\n <ion-item *ngIf=\"forumUrl || helpUrl\">\n <mat-icon slot=\"start\">help_outline</mat-icon>\n <ion-label>\n <p>\n {{ 'ABOUT.HELP' | translate }}\n </p>\n <p>\n <ion-button fill=\"solid\" color=\"accent\" href=\"{{ helpUrl }}\" target=\"help\" size=\"default\">\n <mat-icon slot=\"start\">description</mat-icon>\n <ion-text translate>ABOUT.USER_MANUAL</ion-text>\n </ion-button>\n <ion-button fill=\"solid\" color=\"secondary\" href=\"{{ forumUrl }}\" target=\"forum\" size=\"default\">\n <ion-icon slot=\"start\" name=\"chatbubbles\"></ion-icon>\n <ion-text translate>ABOUT.FORUM</ion-text>\n </ion-button>\n </p>\n </ion-label>\n </ion-item>\n\n <!-- Report issue -->\n <ion-item *ngIf=\"reportIssueUrl\">\n <ion-icon slot=\"start\" name=\"bug\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{ 'ABOUT.REPORT_ISSUE' | translate }}\n </p>\n <p>\n <a color=\"primary\" href=\"{{ reportIssueUrl }}\" target=\"_system\" translate>ABOUT.BTN_REPORT_ISSUE</a>\n </p>\n </ion-label>\n </ion-item>\n\n <!-- Source code -->\n <ion-item *ngIf=\"sourceUrl\">\n <ion-icon slot=\"start\" name=\"code\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{ 'ABOUT.SOURCE_CODE' | translate }}\n </p>\n <p>\n <a color=\"primary\" href=\"{{ sourceUrl }}\" target=\"_system\">{{ sourceUrl }}</a>\n </p>\n </ion-label>\n </ion-item>\n\n <!-- Developers -->\n <ion-item *ngIf=\"developers | isNotEmptyArray\">\n <ion-icon slot=\"start\" name=\"people\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{ 'ABOUT.DEVELOPED_BY' | translate }}\n </p>\n <ion-list lines=\"none\">\n <ion-item *ngFor=\"let item of developers\" class=\"ion-no-padding\">\n <ion-label>\n <p>\n <a color=\"primary\" href=\"{{ item.siteUrl }}\" target=\"_system\">{{ item.name || item.label }}</a>\n </p>\n </ion-label>\n <ion-img slot=\"end\" [src]=\"item.logo\" />\n </ion-item>\n </ion-list>\n </ion-label>\n </ion-item>\n\n <!-- Partners -->\n <ion-item class=\"item-partners\" *ngIf=\"partners | isNotEmptyArray\">\n <ion-icon slot=\"start\" name=\"megaphone\" color=\"dark\"></ion-icon>\n <ion-text>\n <p>{{ 'ABOUT.PARTNERS' | translate }}</p>\n <p>\n <a *ngFor=\"let item of partners\" href=\"{{ item.siteUrl }}\" class=\"partners\" target=\"_system\">\n <img *ngIf=\"item.logo; else partnerName\" [src]=\"item.logo\" [alt]=\"item.label\" />\n <ng-template #partnerName>\n <ion-text>{{ item.label }}</ion-text>\n </ng-template>\n </a>\n </p>\n </ion-text>\n </ion-item>\n </ion-list>\n</ion-content>\n\n<ion-footer hidden-xs hidden-sm hidden-mobile>\n <ion-toolbar>\n <ion-row class=\"ion-no-padding\" nowrap>\n <ion-col></ion-col>\n <ion-col size=\"auto\">\n <ion-button fill=\"solid\" color=\"tertiary\" (click)=\"close()\">{{ 'COMMON.BTN_CLOSE' | translate }}</ion-button>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n</ion-footer>\n", styles: [".item-partners ion-text{height:auto}.item-partners ion-text p:last-child{display:flex;flex-wrap:wrap;flex-direction:row;justify-content:space-between}.item-partners ion-text p:last-child a{padding:2px}.item-partners ion-text p:last-child a img{text-align:center;display:inline-block;max-height:40px!important}\n"], dependencies: [{ kind: "directive", type: i3$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonImg, selector: "ion-img", inputs: ["alt", "src"] }, { kind: "component", type: i2$1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "component", type: i2$1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: i2$1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: i4$2.MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }, { kind: "component", type: i6$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "pipe", type: i3$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }, { kind: "pipe", type: DateFormatPipe, name: "dateFormat" }, { kind: "pipe", type: NotEmptyArrayPipe, name: "isNotEmptyArray" }, { kind: "pipe", type: CapitalizePipe, name: "capitalize" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
33585
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AboutModal, deps: [{ token: i1$1.TranslateService }, { token: i2$1.ModalController }, { token: ConfigService }, { token: NetworkService }, { token: PlatformService }, { token: i2$3.HttpClient }, { token: i0.ChangeDetectorRef }, { token: ENVIRONMENT, optional: true }, { token: APP_ABOUT_DEVELOPERS, optional: true }, { token: APP_ABOUT_PARTNERS, optional: true }], target: i0.ɵɵFactoryTarget.Component });
33586
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AboutModal, selector: "app-about-modal", ngImport: i0, template: "<ion-header>\n <ion-toolbar color=\"light\">\n <ion-title>\n {{ 'ABOUT.TITLE' | translate }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <ion-button (click)=\"close()\" visible-xs visible-sm visible-mobile>\n {{ 'COMMON.BTN_CLOSE' | translate }}\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n <ion-list lines=\"none\">\n <!-- Peer info -->\n @if (config$ | async; as config) {\n <ion-item>\n <span slot=\"start\">&nbsp;</span>\n <ion-label class=\"ion-text-wrap\">\n <h2>\n <b>{{ config.label | capitalize }}</b>\n @if (config.name) {\n -\n <span [innerHTML]=\"config.name\"></span>\n }\n </h2>\n\n @if (config.description) {\n <p>\n <markdown [data]=\"config.description\" emoji></markdown>\n </p>\n }\n </ion-label>\n </ion-item>\n }\n\n <!-- Version info -->\n <ion-item>\n <span slot=\"start\">&nbsp;</span>\n <ion-label class=\"ion-text-wrap\">\n <h2>\n @if (name) {\n <small>{{ 'ABOUT.POWERED_BY' | translate }}</small>\n {{ name }}\n }\n @if (version) {\n <span [innerHTML]=\"'ABOUT.VERSION' | translate: { version: version }\"></span>\n }\n @if(allowVersionDetails) {\n &nbsp;\n <small>\n <a (click)=\"showVersionDetails=!showVersionDetails\" tappable>\n {{'COMMON.BTN_SHOW_MORE' | translate}}\n <ion-icon [name]=\"!showVersionDetails ? 'chevron-down' : 'chevron-up'\"></ion-icon>\n </a>\n @if (changelogUrl) {\n &nbsp;\n <a (click)=\"openChangelog($event)\"\n [href]=\"changelogUrl\"\n tappable translate>ABOUT.BTN_CHANGELOG</a>\n }\n </small>\n }\n </h2>\n\n <!-- version details -->\n @if (showVersionDetails) {\n <p class=\"version-details\">\n <!-- App version -->\n @if (buildDate) {\n <small>\n <ion-icon name=\"logo-angular\"></ion-icon>\n <span [innerHTML]=\"'ABOUT.APP_VERSION' | translate: { version: version }\"></span>\n &nbsp;\n <ion-icon name=\"time-outline\"></ion-icon>\n {{ buildDate | dateFormat: { time: true, seconds: false } }}\n </small>\n <br/>\n }\n <!-- Pod version -->\n @if (nodeInfo?.softwareVersion && nodeInfo.buildDate) {\n <small>\n <ion-icon name=\"server\"></ion-icon>\n <span [innerHTML]=\"'ABOUT.POD_VERSION' | translate: { version: nodeInfo.softwareVersion }\"></span>\n &nbsp;\n <ion-icon name=\"time-outline\"></ion-icon>\n {{ nodeInfo.buildDate | dateFormat: { time: true, seconds: false } }}\n </small>\n }\n </p>\n }\n\n <!-- license-->\n <p [innerHTML]=\"'ABOUT.LICENSE' | translate\"></p>\n </ion-label>\n </ion-item>\n\n <!-- Help -->\n <ion-item *ngIf=\"forumUrl || helpUrl\">\n <mat-icon slot=\"start\">help_outline</mat-icon>\n <ion-label>\n <p>\n {{ 'ABOUT.HELP' | translate }}\n </p>\n <p>\n <ion-button fill=\"solid\" color=\"accent\" href=\"{{ helpUrl }}\" target=\"help\" size=\"default\">\n <mat-icon slot=\"start\">description</mat-icon>\n <ion-text translate>ABOUT.USER_MANUAL</ion-text>\n </ion-button>\n <ion-button fill=\"solid\" color=\"secondary\" href=\"{{ forumUrl }}\" target=\"forum\" size=\"default\">\n <ion-icon slot=\"start\" name=\"chatbubbles\"></ion-icon>\n <ion-text translate>ABOUT.FORUM</ion-text>\n </ion-button>\n </p>\n </ion-label>\n </ion-item>\n\n <!-- Report issue -->\n <ion-item *ngIf=\"reportIssueUrl\">\n <ion-icon slot=\"start\" name=\"bug\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{ 'ABOUT.REPORT_ISSUE' | translate }}\n </p>\n <p>\n <a color=\"primary\" href=\"{{ reportIssueUrl }}\" target=\"_system\" translate>ABOUT.BTN_REPORT_ISSUE</a>\n </p>\n </ion-label>\n </ion-item>\n\n <!-- Source code -->\n <ion-item *ngIf=\"sourceUrl\">\n <ion-icon slot=\"start\" name=\"code\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{ 'ABOUT.SOURCE_CODE' | translate }}\n </p>\n <p>\n <a color=\"primary\" href=\"{{ sourceUrl }}\" target=\"_system\">{{ sourceUrl }}</a>\n </p>\n </ion-label>\n </ion-item>\n\n <!-- Developers -->\n <ion-item *ngIf=\"developers | isNotEmptyArray\">\n <ion-icon slot=\"start\" name=\"people\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{ 'ABOUT.DEVELOPED_BY' | translate }}\n </p>\n <ion-list lines=\"none\">\n <ion-item *ngFor=\"let item of developers\" class=\"ion-no-padding\">\n <ion-label>\n <p>\n <a color=\"primary\" href=\"{{ item.siteUrl }}\" target=\"_system\">{{ item.name || item.label }}</a>\n </p>\n </ion-label>\n <ion-img slot=\"end\" [src]=\"item.logo\" />\n </ion-item>\n </ion-list>\n </ion-label>\n </ion-item>\n\n <!-- Partners -->\n <ion-item class=\"item-partners\" *ngIf=\"partners | isNotEmptyArray\">\n <ion-icon slot=\"start\" name=\"megaphone\" color=\"dark\"></ion-icon>\n <ion-text>\n <p>{{ 'ABOUT.PARTNERS' | translate }}</p>\n <p>\n <a *ngFor=\"let item of partners\" href=\"{{ item.siteUrl }}\" class=\"partners\" target=\"_system\">\n <img *ngIf=\"item.logo; else partnerName\" [src]=\"item.logo\" [alt]=\"item.label\" />\n <ng-template #partnerName>\n <ion-text>{{ item.label }}</ion-text>\n </ng-template>\n </a>\n </p>\n </ion-text>\n </ion-item>\n </ion-list>\n</ion-content>\n\n<ion-footer hidden-xs hidden-sm hidden-mobile>\n <ion-toolbar>\n <ion-row class=\"ion-no-padding\" nowrap>\n <ion-col></ion-col>\n <ion-col size=\"auto\">\n <ion-button fill=\"solid\" color=\"tertiary\" (click)=\"close()\">{{ 'COMMON.BTN_CLOSE' | translate }}</ion-button>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n</ion-footer>\n", styles: [".item-partners ion-text{height:auto}.item-partners ion-text p:last-child{display:flex;flex-wrap:wrap;flex-direction:row;justify-content:space-between}.item-partners ion-text p:last-child a{padding:2px}.item-partners ion-text p:last-child a img{text-align:center;display:inline-block;max-height:40px!important}\n"], dependencies: [{ kind: "directive", type: i3$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonImg, selector: "ion-img", inputs: ["alt", "src"] }, { kind: "component", type: i2$1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "component", type: i2$1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: i2$1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: i4$2.MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }, { kind: "component", type: i6$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "pipe", type: i3$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }, { kind: "pipe", type: DateFormatPipe, name: "dateFormat" }, { kind: "pipe", type: NotEmptyArrayPipe, name: "isNotEmptyArray" }, { kind: "pipe", type: CapitalizePipe, name: "capitalize" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
33250
33587
  }
33251
33588
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AboutModal, decorators: [{
33252
33589
  type: Component,
33253
- args: [{ selector: 'app-about-modal', changeDetection: ChangeDetectionStrategy.OnPush, template: "<ion-header>\n <ion-toolbar color=\"light\">\n <ion-title>\n {{ 'ABOUT.TITLE' | translate }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <ion-button (click)=\"close()\" visible-xs visible-sm visible-mobile>\n {{ 'COMMON.BTN_CLOSE' | translate }}\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n <ion-list lines=\"none\">\n <!-- Peer info -->\n @if (config$ | async; as config) {\n <ion-item>\n <span slot=\"start\">&nbsp;</span>\n <ion-label class=\"ion-text-wrap\">\n <h2>\n <b>{{ config.label | capitalize }}</b>\n @if (config.name) {\n -\n <span [innerHTML]=\"config.name\"></span>\n }\n </h2>\n\n @if (config.description) {\n <p>\n <markdown [data]=\"config.description\" emoji></markdown>\n </p>\n }\n </ion-label>\n </ion-item>\n }\n\n <!-- Version info -->\n <ion-item>\n <span slot=\"start\">&nbsp;</span>\n <ion-label class=\"ion-text-wrap\">\n <h2>\n @if (name) {\n <small>{{ 'ABOUT.POWERED_BY' | translate }}</small>\n {{ name }}\n }\n @if (version) {\n <span [innerHTML]=\"'ABOUT.VERSION' | translate: { version: version }\"></span>\n }\n @if(allowVersionDetails) {\n &nbsp;\n <small>\n <a (click)=\"showVersionDetails=!showVersionDetails\" tappable>\n {{'COMMON.BTN_SHOW_MORE' | translate}}\n <ion-icon [name]=\"!showVersionDetails ? 'chevron-down' : 'chevron-up'\"></ion-icon>\n </a>\n </small>\n }\n </h2>\n\n <!-- version details -->\n @if (showVersionDetails) {\n <p class=\"version-details\">\n <!-- App version -->\n @if (buildDate) {\n <small>\n <ion-icon name=\"logo-angular\"></ion-icon>\n <span [innerHTML]=\"'ABOUT.APP_VERSION' | translate: { version: version }\"></span>\n &nbsp;\n <ion-icon name=\"time-outline\"></ion-icon>\n {{ buildDate | dateFormat: { time: true, seconds: false } }}\n </small>\n <br/>\n }\n <!-- Pod version -->\n @if (nodeInfo?.softwareVersion && nodeInfo.buildDate) {\n <small>\n <ion-icon name=\"server\"></ion-icon>\n <span [innerHTML]=\"'ABOUT.POD_VERSION' | translate: { version: nodeInfo.softwareVersion }\"></span>\n &nbsp;\n <ion-icon name=\"time-outline\"></ion-icon>\n {{ nodeInfo.buildDate | dateFormat: { time: true, seconds: false } }}\n </small>\n }\n </p>\n }\n\n <!-- license-->\n <p [innerHTML]=\"'ABOUT.LICENSE' | translate\"></p>\n </ion-label>\n </ion-item>\n\n <!-- Help -->\n <ion-item *ngIf=\"forumUrl || helpUrl\">\n <mat-icon slot=\"start\">help_outline</mat-icon>\n <ion-label>\n <p>\n {{ 'ABOUT.HELP' | translate }}\n </p>\n <p>\n <ion-button fill=\"solid\" color=\"accent\" href=\"{{ helpUrl }}\" target=\"help\" size=\"default\">\n <mat-icon slot=\"start\">description</mat-icon>\n <ion-text translate>ABOUT.USER_MANUAL</ion-text>\n </ion-button>\n <ion-button fill=\"solid\" color=\"secondary\" href=\"{{ forumUrl }}\" target=\"forum\" size=\"default\">\n <ion-icon slot=\"start\" name=\"chatbubbles\"></ion-icon>\n <ion-text translate>ABOUT.FORUM</ion-text>\n </ion-button>\n </p>\n </ion-label>\n </ion-item>\n\n <!-- Report issue -->\n <ion-item *ngIf=\"reportIssueUrl\">\n <ion-icon slot=\"start\" name=\"bug\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{ 'ABOUT.REPORT_ISSUE' | translate }}\n </p>\n <p>\n <a color=\"primary\" href=\"{{ reportIssueUrl }}\" target=\"_system\" translate>ABOUT.BTN_REPORT_ISSUE</a>\n </p>\n </ion-label>\n </ion-item>\n\n <!-- Source code -->\n <ion-item *ngIf=\"sourceUrl\">\n <ion-icon slot=\"start\" name=\"code\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{ 'ABOUT.SOURCE_CODE' | translate }}\n </p>\n <p>\n <a color=\"primary\" href=\"{{ sourceUrl }}\" target=\"_system\">{{ sourceUrl }}</a>\n </p>\n </ion-label>\n </ion-item>\n\n <!-- Developers -->\n <ion-item *ngIf=\"developers | isNotEmptyArray\">\n <ion-icon slot=\"start\" name=\"people\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{ 'ABOUT.DEVELOPED_BY' | translate }}\n </p>\n <ion-list lines=\"none\">\n <ion-item *ngFor=\"let item of developers\" class=\"ion-no-padding\">\n <ion-label>\n <p>\n <a color=\"primary\" href=\"{{ item.siteUrl }}\" target=\"_system\">{{ item.name || item.label }}</a>\n </p>\n </ion-label>\n <ion-img slot=\"end\" [src]=\"item.logo\" />\n </ion-item>\n </ion-list>\n </ion-label>\n </ion-item>\n\n <!-- Partners -->\n <ion-item class=\"item-partners\" *ngIf=\"partners | isNotEmptyArray\">\n <ion-icon slot=\"start\" name=\"megaphone\" color=\"dark\"></ion-icon>\n <ion-text>\n <p>{{ 'ABOUT.PARTNERS' | translate }}</p>\n <p>\n <a *ngFor=\"let item of partners\" href=\"{{ item.siteUrl }}\" class=\"partners\" target=\"_system\">\n <img *ngIf=\"item.logo; else partnerName\" [src]=\"item.logo\" [alt]=\"item.label\" />\n <ng-template #partnerName>\n <ion-text>{{ item.label }}</ion-text>\n </ng-template>\n </a>\n </p>\n </ion-text>\n </ion-item>\n </ion-list>\n</ion-content>\n\n<ion-footer hidden-xs hidden-sm hidden-mobile>\n <ion-toolbar>\n <ion-row class=\"ion-no-padding\" nowrap>\n <ion-col></ion-col>\n <ion-col size=\"auto\">\n <ion-button fill=\"solid\" color=\"tertiary\" (click)=\"close()\">{{ 'COMMON.BTN_CLOSE' | translate }}</ion-button>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n</ion-footer>\n", styles: [".item-partners ion-text{height:auto}.item-partners ion-text p:last-child{display:flex;flex-wrap:wrap;flex-direction:row;justify-content:space-between}.item-partners ion-text p:last-child a{padding:2px}.item-partners ion-text p:last-child a img{text-align:center;display:inline-block;max-height:40px!important}\n"] }]
33254
- }], ctorParameters: () => [{ type: i1$1.TranslateService }, { type: i2$1.ModalController }, { type: ConfigService }, { type: NetworkService }, { type: i0.ChangeDetectorRef }, { type: undefined, decorators: [{
33590
+ args: [{ selector: 'app-about-modal', changeDetection: ChangeDetectionStrategy.OnPush, template: "<ion-header>\n <ion-toolbar color=\"light\">\n <ion-title>\n {{ 'ABOUT.TITLE' | translate }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <ion-button (click)=\"close()\" visible-xs visible-sm visible-mobile>\n {{ 'COMMON.BTN_CLOSE' | translate }}\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n <ion-list lines=\"none\">\n <!-- Peer info -->\n @if (config$ | async; as config) {\n <ion-item>\n <span slot=\"start\">&nbsp;</span>\n <ion-label class=\"ion-text-wrap\">\n <h2>\n <b>{{ config.label | capitalize }}</b>\n @if (config.name) {\n -\n <span [innerHTML]=\"config.name\"></span>\n }\n </h2>\n\n @if (config.description) {\n <p>\n <markdown [data]=\"config.description\" emoji></markdown>\n </p>\n }\n </ion-label>\n </ion-item>\n }\n\n <!-- Version info -->\n <ion-item>\n <span slot=\"start\">&nbsp;</span>\n <ion-label class=\"ion-text-wrap\">\n <h2>\n @if (name) {\n <small>{{ 'ABOUT.POWERED_BY' | translate }}</small>\n {{ name }}\n }\n @if (version) {\n <span [innerHTML]=\"'ABOUT.VERSION' | translate: { version: version }\"></span>\n }\n @if(allowVersionDetails) {\n &nbsp;\n <small>\n <a (click)=\"showVersionDetails=!showVersionDetails\" tappable>\n {{'COMMON.BTN_SHOW_MORE' | translate}}\n <ion-icon [name]=\"!showVersionDetails ? 'chevron-down' : 'chevron-up'\"></ion-icon>\n </a>\n @if (changelogUrl) {\n &nbsp;\n <a (click)=\"openChangelog($event)\"\n [href]=\"changelogUrl\"\n tappable translate>ABOUT.BTN_CHANGELOG</a>\n }\n </small>\n }\n </h2>\n\n <!-- version details -->\n @if (showVersionDetails) {\n <p class=\"version-details\">\n <!-- App version -->\n @if (buildDate) {\n <small>\n <ion-icon name=\"logo-angular\"></ion-icon>\n <span [innerHTML]=\"'ABOUT.APP_VERSION' | translate: { version: version }\"></span>\n &nbsp;\n <ion-icon name=\"time-outline\"></ion-icon>\n {{ buildDate | dateFormat: { time: true, seconds: false } }}\n </small>\n <br/>\n }\n <!-- Pod version -->\n @if (nodeInfo?.softwareVersion && nodeInfo.buildDate) {\n <small>\n <ion-icon name=\"server\"></ion-icon>\n <span [innerHTML]=\"'ABOUT.POD_VERSION' | translate: { version: nodeInfo.softwareVersion }\"></span>\n &nbsp;\n <ion-icon name=\"time-outline\"></ion-icon>\n {{ nodeInfo.buildDate | dateFormat: { time: true, seconds: false } }}\n </small>\n }\n </p>\n }\n\n <!-- license-->\n <p [innerHTML]=\"'ABOUT.LICENSE' | translate\"></p>\n </ion-label>\n </ion-item>\n\n <!-- Help -->\n <ion-item *ngIf=\"forumUrl || helpUrl\">\n <mat-icon slot=\"start\">help_outline</mat-icon>\n <ion-label>\n <p>\n {{ 'ABOUT.HELP' | translate }}\n </p>\n <p>\n <ion-button fill=\"solid\" color=\"accent\" href=\"{{ helpUrl }}\" target=\"help\" size=\"default\">\n <mat-icon slot=\"start\">description</mat-icon>\n <ion-text translate>ABOUT.USER_MANUAL</ion-text>\n </ion-button>\n <ion-button fill=\"solid\" color=\"secondary\" href=\"{{ forumUrl }}\" target=\"forum\" size=\"default\">\n <ion-icon slot=\"start\" name=\"chatbubbles\"></ion-icon>\n <ion-text translate>ABOUT.FORUM</ion-text>\n </ion-button>\n </p>\n </ion-label>\n </ion-item>\n\n <!-- Report issue -->\n <ion-item *ngIf=\"reportIssueUrl\">\n <ion-icon slot=\"start\" name=\"bug\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{ 'ABOUT.REPORT_ISSUE' | translate }}\n </p>\n <p>\n <a color=\"primary\" href=\"{{ reportIssueUrl }}\" target=\"_system\" translate>ABOUT.BTN_REPORT_ISSUE</a>\n </p>\n </ion-label>\n </ion-item>\n\n <!-- Source code -->\n <ion-item *ngIf=\"sourceUrl\">\n <ion-icon slot=\"start\" name=\"code\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{ 'ABOUT.SOURCE_CODE' | translate }}\n </p>\n <p>\n <a color=\"primary\" href=\"{{ sourceUrl }}\" target=\"_system\">{{ sourceUrl }}</a>\n </p>\n </ion-label>\n </ion-item>\n\n <!-- Developers -->\n <ion-item *ngIf=\"developers | isNotEmptyArray\">\n <ion-icon slot=\"start\" name=\"people\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{ 'ABOUT.DEVELOPED_BY' | translate }}\n </p>\n <ion-list lines=\"none\">\n <ion-item *ngFor=\"let item of developers\" class=\"ion-no-padding\">\n <ion-label>\n <p>\n <a color=\"primary\" href=\"{{ item.siteUrl }}\" target=\"_system\">{{ item.name || item.label }}</a>\n </p>\n </ion-label>\n <ion-img slot=\"end\" [src]=\"item.logo\" />\n </ion-item>\n </ion-list>\n </ion-label>\n </ion-item>\n\n <!-- Partners -->\n <ion-item class=\"item-partners\" *ngIf=\"partners | isNotEmptyArray\">\n <ion-icon slot=\"start\" name=\"megaphone\" color=\"dark\"></ion-icon>\n <ion-text>\n <p>{{ 'ABOUT.PARTNERS' | translate }}</p>\n <p>\n <a *ngFor=\"let item of partners\" href=\"{{ item.siteUrl }}\" class=\"partners\" target=\"_system\">\n <img *ngIf=\"item.logo; else partnerName\" [src]=\"item.logo\" [alt]=\"item.label\" />\n <ng-template #partnerName>\n <ion-text>{{ item.label }}</ion-text>\n </ng-template>\n </a>\n </p>\n </ion-text>\n </ion-item>\n </ion-list>\n</ion-content>\n\n<ion-footer hidden-xs hidden-sm hidden-mobile>\n <ion-toolbar>\n <ion-row class=\"ion-no-padding\" nowrap>\n <ion-col></ion-col>\n <ion-col size=\"auto\">\n <ion-button fill=\"solid\" color=\"tertiary\" (click)=\"close()\">{{ 'COMMON.BTN_CLOSE' | translate }}</ion-button>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n</ion-footer>\n", styles: [".item-partners ion-text{height:auto}.item-partners ion-text p:last-child{display:flex;flex-wrap:wrap;flex-direction:row;justify-content:space-between}.item-partners ion-text p:last-child a{padding:2px}.item-partners ion-text p:last-child a img{text-align:center;display:inline-block;max-height:40px!important}\n"] }]
33591
+ }], ctorParameters: () => [{ type: i1$1.TranslateService }, { type: i2$1.ModalController }, { type: ConfigService }, { type: NetworkService }, { type: PlatformService }, { type: i2$3.HttpClient }, { type: i0.ChangeDetectorRef }, { type: undefined, decorators: [{
33255
33592
  type: Optional
33256
33593
  }, {
33257
33594
  type: Inject,
@@ -34443,11 +34780,11 @@ class MenuComponent {
34443
34780
  this.menuService.togglePinned(item);
34444
34781
  }
34445
34782
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MenuComponent, deps: [{ token: AccountService }, { token: i2$1.NavController }, { token: i2$1.MenuController }, { token: i2$1.ModalController }, { token: i1$1.TranslateService }, { token: i0.ChangeDetectorRef }, { token: MenuService }, { token: i1$7.Router }, { token: ENVIRONMENT }, { token: APP_BASE_HREF, optional: true }, { token: i1$7.ActivatedRoute, optional: true }], target: i0.ɵɵFactoryTarget.Component });
34446
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: MenuComponent, selector: "app-menu", inputs: { id: "id", menuId: "menuId", side: "side", contentId: "contentId", logo: "logo", appName: "appName", rxStrategy: "rxStrategy", appVersion: "appVersion" }, viewQueries: [{ propertyName: "splitPane", first: true, predicate: ["splitPane"], descendants: true, static: true }], ngImport: i0, template: "<ion-split-pane #splitPane [contentId]=\"contentId\" (swiperight)=\"onSwipeRight($event)\">\n <ion-menu [id]=\"id\" [menuId]=\"menuId\" [contentId]=\"contentId\">\n <ion-header>\n <ion-toolbar *ngIf=\"!loading && isLogin; else notLogin\" @fadeInSlowAnimation class=\"user-toolbar\">\n <ion-grid>\n <ion-row>\n <ion-col size=\"4\">\n <button\n type=\"button\"\n mat-flat-button\n class=\"user-avatar\"\n [class.primary]=\"!accountAvatar\"\n [style.background-image]=\"'url(' + (accountAvatar || defaultAvatarImage) + ')'\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"ion-color-primary\"\n (click)=\"close()\"\n [title]=\"!showTooltip ? ('MENU.BTN_MY_ACCOUNT' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('MENU.BTN_MY_ACCOUNT' | translate) : ''\"\n ></button>\n </ion-col>\n <ion-col size=\"8\" class=\"user-logo\">\n <img\n *ngIf=\"logo; else noLogo\"\n [attr.src]=\"logo\"\n [title]=\"!showTooltip ? ('APP_NAME' | translate: { appName: appName }) : ''\"\n [matTooltip]=\"showTooltip ? ('APP_NAME' | translate: { appName: appName }) : ''\"\n alt=\"Logo\"\n width=\"108px\"\n />\n <ng-template #noLogo>\n <span style=\"width: 108px\">{{ appName }}</span>\n </ng-template>\n </ion-col>\n </ion-row>\n <ion-row class=\"ion-no-padding\">\n <ion-col>\n <button\n mat-button\n type=\"button\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"selected\"\n (click)=\"close()\"\n [title]=\"!showTooltip ? ('MENU.BTN_MY_ACCOUNT' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('MENU.BTN_MY_ACCOUNT' | translate) : ''\"\n >\n <ion-label class=\"ion-text-wrap ion-text-start\">\n <h3 class=\"no-margin username\">\n <b>{{ accountName }}</b>\n </h3>\n <h4>{{ accountEmail }}</h4>\n </ion-label>\n </button>\n </ion-col>\n\n <!-- Insertion headerBottomRight -->\n <ion-col size=\"auto\">\n <ng-container *ngTemplateOutlet=\"headerBottomRight\"></ng-container>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-toolbar>\n\n <!-- User not logged -->\n <ng-template #notLogin>\n <mat-toolbar\n class=\"ion-padding\"\n *ngIf=\"!loading\"\n style=\"height: unset; display: block; margin: auto; text-align: center\"\n >\n <img\n *ngIf=\"logo\"\n [attr.src]=\"logo\"\n [title]=\"!showTooltip ? ('APP_NAME' | translate: { appName: appName }) : ''\"\n [matTooltip]=\"showTooltip ? ('APP_NAME' | translate: { appName: appName }) : ''\"\n width=\"150px;\"\n alt=\"logo\"\n />\n <span *ngIf=\"!logo\" style=\"width: 150px\">{{ appName }}</span>\n </mat-toolbar>\n </ng-template>\n </ion-header>\n\n <ion-content [class.has-user-header]=\"isLogin\">\n <!-- will close the menu, after a click, in mobile -->\n <ion-menu-toggle auto-hide=\"false\">\n <ion-list lines=\"none\" *ngIf=\"!loading\">\n <ng-container *rxFor=\"let item of menuService.dataSubject; strategy: rxStrategy; trackBy: trackByFn\">\n <ng-container *ngTemplateOutlet=\"menuItem; context: { $implicit: item, level: 0 }\"></ng-container>\n </ng-container>\n </ion-list>\n </ion-menu-toggle>\n </ion-content>\n\n <ion-footer class=\"hidden-xxs hidden-xs hidden-sm hidden-md\">\n <ion-toolbar>\n <ion-buttons slot=\"start\">\n <ion-button mat-icon-button color=\"accent\" (click)=\"menuService.openAboutModal()\">\n <mat-icon slot=\"icon-only\">help_outline</mat-icon>\n </ion-button>\n </ion-buttons>\n\n <ion-title (click)=\"menuService.openAboutModal()\" color=\"medium\">\n {{ 'MENU.FOOTER_VERSION_ABOUT' | translate: { version: appVersion } }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <button\n mat-icon-button\n color=\"accent\"\n (click)=\"toggleSplitPaneShow($event)\"\n class=\"hidden-xs hidden-sm hidden-md\"\n [title]=\"!showTooltip ? ((splitPane.when ? 'COMMON.BTN_HIDE_MENU' : 'COMMON.BTN_SHOW_MENU') | translate) : ''\"\n [matTooltip]=\"showTooltip ? ((splitPane.when ? 'COMMON.BTN_HIDE_MENU' : 'COMMON.BTN_SHOW_MENU') | translate) : ''\"\n >\n <mat-icon>\n <span>{{ splitPane.when ? '&#xab;' : '&#xbb;' }}</span>\n </mat-icon>\n </button>\n </ion-buttons>\n </ion-toolbar>\n </ion-footer>\n </ion-menu>\n\n <ng-content></ng-content>\n</ion-split-pane>\n\n<ng-template #headerBottomRight>\n <ng-content select=\"[headerBottomRight]\"></ng-content>\n</ng-template>\n\n<ng-template #menuItem let-item let-level=\"level\">\n @if (item.divider) {\n <!-- divider -->\n <ion-item-divider class=\"{{ item.cssClass }} {{ item.color }}\" @fadeInSlowAnimation>\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{ item.matIcon }}</mat-icon>\n <ion-label *ngIf=\"item.cssClass !== 'flex-spacer'\">\n <span *rxIf=\"item.$title; let title; else: skeletonText\" [innerHTML]=\"title | translate\"></span>\n </ion-label>\n </ion-item-divider>\n } @else if (item.path && !item.action) {\n <!-- tappable link -->\n <ion-item\n @fadeInSlowAnimation\n class=\"{{ item.cssClass }} {{ item.color }}\"\n [class.menu-item-sub]=\"level !== 0\"\n tappable\n [routerLink]=\"item.path\"\n [queryParams]=\"item.pathParams\"\n routerDirection=\"root\"\n routerLinkActive=\"selected\"\n [routerLinkActiveOptions]=\"{\n paths: item.path === '/' || level !== 0 ? 'exact' : 'subset',\n queryParams: item.pathParams?.tab || level === 0 ? 'subset' : 'exact',\n matrixParams: 'exact',\n fragment: 'ignored',\n }\"\n >\n @if (item.icon) {\n <ion-icon slot=\"start\" [name]=\"item.icon\"></ion-icon>\n } @else if (item.matIcon) {\n <mat-icon slot=\"start\">{{ item.matIcon }}</mat-icon>\n } @else if (item.matSvgIcon) {\n <mat-icon slot=\"start\" [svgIcon]=\"item.matSvgIcon\"></mat-icon>\n }\n <ion-label *rxLet=\"item.$title; let title\">\n <span *ngIf=\"title; else skeletonText\" [innerHTML]=\"title | translate\"></span>\n <!--<span *ngIf=\"_debug\"> {{item.id}}</span>-->\n </ion-label>\n\n <ion-badge *rxIf=\"item.$badge; let badge\" [color]=\"item.$badgeColor | push\">\n {{badge}}\n </ion-badge>\n\n <!-- pin button -->\n <ng-container *ngTemplateOutlet=\"pinButton; context: { $implicit: item, level: level }\"></ng-container>\n </ion-item>\n } @else if (item.action) {\n <!-- action -->\n <ion-item\n @fadeInSlowAnimation\n class=\"{{ item.cssClass }} {{ item.color }} text-1x\"\n [class.menu-item-sub]=\"level !== 0\"\n tappable\n (click)=\"executeAction($event, item)\"\n >\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{ item.matIcon }}</mat-icon>\n <ion-label>\n <span *rxIf=\"item.$title; let title; else: skeletonText\" [innerHTML]=\"title | translate\"></span>\n <!--<span *ngIf=\"_debug\"> {{item.id}}</span>-->\n </ion-label>\n\n <ion-badge *rxIf=\"item.$badge; let badge\" [color]=\"item.$badgeColor | push\">\n {{badge}}\n </ion-badge>\n\n <!-- pin button -->\n <ng-container *ngTemplateOutlet=\"pinButton; context: { $implicit: item, level: level }\"></ng-container>\n </ion-item>\n }\n\n <!-- children -->\n @if (item.$children) {\n <div class=\"sub-menu-container\">\n <ng-container *rxFor=\"let child of item.$children; strategy: rxStrategy; trackBy: trackByFn\">\n <ng-container *ngTemplateOutlet=\"menuItem; context: { $implicit: child, level: level + 1 }\"></ng-container>\n </ng-container>\n </div>\n }\n</ng-template>\n\n<ng-template #pinButton let-item let-level=\"level\">\n <!-- pin button -->\n @if (level && item.pinnable) {\n <ion-button\n slot=\"end\"\n shape=\"round\"\n fill=\"clear\"\n size=\"small\"\n [class.visible-hover]=\"!item.pinned\"\n (click)=\"togglePinned($event, item)\"\n >\n <!--<mat-icon slot=\"icon-only\" [color]=\"item.pinned ? 'primary' : undefined\">push_pin</mat-icon>-->\n <ion-icon slot=\"icon-only\" [name]=\"item.pinned ? 'pin' : 'pin-outline'\"></ion-icon>\n </ion-button>\n }\n</ng-template>\n\n<ng-template #skeletonText let-width let-animated>\n <ion-skeleton-text [animated]=\"animated\" [style.width.%]=\"width || 60\"></ion-skeleton-text>\n</ng-template>\n", styles: [":host{--menu-item-margin: 0;--ion-item-background: transparent;--ion-item-divider-background: transparent;--ion-item-icon-color: var(--ion-color-primary-tint);--ion-item-text-color: var(--ion-color-primary-tint);--ion-item-background-selected: var(--ion-color-secondary100);--ion-item-text-color-selected: var(--ion-color-primary);--ion-item-icon-color-selected: var(--ion-color-primary);--ion-item-text-color-disable: var(--ion-color-medium);--ion-item-icon-color-disable: var(--ion-color-medium);--menu-item-background-selected-sub: var(--ion-color-secondary50);--menu-item-border-width-sub: 0 0 1px 0;--menu-item-margin-start-sub: 16px}@keyframes fadeinout{0%{opacity:0;display:none}75%{opacity:0;display:none}to{opacity:1;display:flex}}ion-menu ion-header ion-text{color:var(--ion-color-primary)}ion-menu ion-header ion-toolbar.user-toolbar{--ion-toolbar-height: 128px}ion-menu ion-header .user-avatar{-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;background-size:cover;background-repeat:no-repeat;background-position:center;background-color:var(--ion-color-secondary);border:solid 1px rgba(var(--ion-color-secondary-rgb),.5);overflow:hidden!important;font-size:var(--avatar-size, 60px)!important;line-height:var(--avatar-size, 60px);height:var(--avatar-size, 60px)!important;width:var(--avatar-size, 60px)!important;border-radius:50%;display:inline-block}ion-menu ion-header .user-avatar:hover{background-color:var(--ion-color-secondary-shade);border:solid 2px var(--ion-color-secondary-shade)}ion-menu ion-header .user-logo{text-align:right}ion-menu ion-header .user-logo img{max-width:120px;max-height:var(--avatar-size, 60px);width:auto}ion-menu ion-header .username{padding-top:0;margin-top:0;margin-bottom:0;width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}ion-menu ion-header button[mat-button]{padding:0;width:100%;justify-content:flex-start;text-align:start!important;color:var(--ion-item-text-color)}ion-menu ion-header button[mat-button].selected{background-color:var(--ion-item-background-selected);color:var(--ion-item-text-color-selected)!important}ion-menu ion-content ion-list{min-height:100%;display:flex;flex-direction:column;justify-content:flex-start}ion-menu ion-content ion-list ion-menu-toggle.flex-spacer{flex:1 1 auto;display:flex;flex-direction:column;justify-content:flex-end}ion-menu ion-content ion-list ion-item{display:block}ion-menu ion-content ion-list ion-item.primary{--ion-item-icon-color: var(--ion-color-primary-tint);--ion-item-text-color: var(--ion-color-primary-tint)}ion-menu ion-content ion-list ion-item.secondary{--ion-item-icon-color: var(--ion-color-secondary-tint);--ion-item-text-color: var(--ion-color-secondary-tint)}ion-menu ion-content ion-list ion-item.tertiary{--ion-item-icon-color: var(--ion-color-tertiary-tint);--ion-item-text-color: var(--ion-color-tertiary-tint)}ion-menu ion-content ion-list ion-item.danger{--ion-item-icon-color: var(--ion-color-danger-tint);--ion-item-text-color: var(--ion-color-danger-tint)}ion-menu ion-content ion-list ion-item.medium{--ion-item-icon-color: var(--ion-color-medium-shade);--ion-item-text-color: var(--ion-color-medium-shade)}ion-menu ion-content ion-list ion-item.dark{--ion-item-icon-color: var(--ion-color-dark-tint);--ion-item-text-color: var(--ion-color-dark-tint)}ion-menu ion-content ion-list ion-item mat-icon,ion-menu ion-content ion-list ion-item ion-icon{color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor}ion-menu ion-content ion-list ion-item ::ng-deep mat-icon svg{fill:currentColor;stroke:currentColor}ion-menu ion-content ion-list ion-item ion-text,ion-menu ion-content ion-list ion-item ion-label{color:var(--ion-item-text-color)!important}ion-menu ion-content ion-list ion-item span{margin-left:var(--menu-item-margin)}ion-menu ion-content ion-list ion-item.selected{background-color:var(--ion-item-background-selected)!important;--color-hover: var(--ion-item-background-selected) !important}ion-menu ion-content ion-list ion-item.selected mat-icon,ion-menu ion-content ion-list ion-item.selected ion-icon{color:var(--ion-item-icon-color-selected)!important;fill:currentColor;stroke:currentColor}ion-menu ion-content ion-list ion-item.selected ion-text,ion-menu ion-content ion-list ion-item.selected ion-label{color:var(--ion-item-text-color-selected)!important}ion-menu ion-content ion-list ion-item.selected:hover{--color-hover: var(--ion-item-background-selected) !important;--ion-item-icon-color-selected: var(--ion-item-icon-color) !important;--ion-item-text-color-selected: var(--ion-item-text-color) !important}ion-menu ion-content ion-list ion-item.selected.menu-item-sub{background-color:var(--menu-item-background-selected-sub)!important}ion-menu ion-content ion-list ion-item.menu-item-sub{--min-height: 34px;--padding-vertical: 8px;--padding-start: 8px;--padding-horizontal: 8px;--inner-border-width: var(--menu-item-border-width-sub);font-size:.8em;overflow:unset}ion-menu ion-content ion-list ion-item.menu-item-sub ion-icon[slot],ion-menu ion-content ion-list ion-item.menu-item-sub mat-icon[slot]{font-size:calc(var(--min-height) / 2 - 1px);height:calc(var(--min-height) / 2 - 1px);margin-top:calc(var(--padding-vertical) + 2px);margin-inline-end:calc(var(--padding-horizontal) + 2px);margin-bottom:calc(var(--padding-vertical) + 2px)}ion-menu ion-content ion-list ion-item.menu-item-sub ion-label{margin-top:var(--padding-vertical);margin-bottom:var(--padding-vertical)}ion-menu ion-content ion-list ion-item.menu-item-sub .item-inner{border-width:var(--inner-border-width);border-style:var(--border-style);border-color:var(--border-color);box-shadow:var(--inner-box-shadow)}ion-menu ion-content ion-list ion-item.menu-item-sub ion-button[slot=end]{-webkit-margin-start:unset;margin-inline-start:unset}ion-menu ion-content ion-list ion-item.menu-item-sub ion-button[slot=end] mat-icon[slot=icon-only],ion-menu ion-content ion-list ion-item.menu-item-sub ion-button[slot=end] ion-icon[slot=icon-only]{margin:0}ion-menu ion-content ion-list ion-item.menu-item-sub ion-button[slot=end].visible-hover{opacity:0;display:none;animation:fadeinout 1s linear 1 backwards}ion-menu ion-content ion-list ion-item.menu-item-sub:hover ion-button.visible-hover{opacity:1;display:flex}ion-menu ion-content ion-list .sub-menu-container{margin-inline-start:var(--menu-item-margin-start-sub, 16px)}ion-menu ion-footer{display:block}ion-menu ion-footer ion-toolbar ion-title{cursor:pointer;font-size:12pt;font-weight:400;text-align:center;padding:0 8px}ion-menu ion-footer ion-toolbar ion-button{--width: 40px}@media screen and (max-width: 767px){ion-menu ion-footer{display:none!important;visibility:hidden!important}}@media screen and (min-width: 768px){ion-menu ion-scroll{overflow-y:auto!important}ion-menu .user-avatar{font-size:var(--avatar-size, 80px)!important;line-height:var(--avatar-size, 80px);height:var(--avatar-size, 80px)!important;width:var(--avatar-size, 80px)!important}ion-menu .user-logo img{max-height:var(--avatar-size, 80px)}}\n"], dependencies: [{ kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i2$1.IonBadge, selector: "ion-badge", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i2$1.IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i2$1.IonItemDivider, selector: "ion-item-divider", inputs: ["color", "mode", "sticky"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: i2$1.IonMenu, selector: "ion-menu", inputs: ["contentId", "disabled", "maxEdgeStart", "menuId", "side", "swipeGesture", "type"] }, { kind: "component", type: i2$1.IonMenuToggle, selector: "ion-menu-toggle", inputs: ["autoHide", "menu"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "component", type: i2$1.IonSkeletonText, selector: "ion-skeleton-text", inputs: ["animated"] }, { kind: "component", type: i2$1.IonSplitPane, selector: "ion-split-pane", inputs: ["contentId", "disabled", "when"] }, { kind: "component", type: i2$1.IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: i2$1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "directive", type: i2$1.RouterLinkDelegate, selector: ":not(a):not(area)[routerLink]" }, { kind: "directive", type: i13$3.RxFor, selector: "[rxFor][rxForOf]", inputs: ["rxForOf", "rxForTemplate", "rxForStrategy", "rxForParent", "rxForPatchZone", "rxForTrackBy", "rxForRenderCallback"] }, { kind: "directive", type: i8$1.RxIf, selector: "[rxIf]", inputs: ["rxIf", "rxIfStrategy", "rxIfElse", "rxIfThen", "rxIfSuspense", "rxIfComplete", "rxIfError", "rxIfContextTrigger", "rxIfNextTrigger", "rxIfSuspenseTrigger", "rxIfErrorTrigger", "rxIfCompleteTrigger", "rxIfParent", "rxIfPatchZone", "rxIfRenderCallback"] }, { kind: "directive", type: i9$1.RxLet, selector: "[rxLet]", inputs: ["rxLet", "rxLetStrategy", "rxLetComplete", "rxLetError", "rxLetSuspense", "rxLetContextTrigger", "rxLetCompleteTrigger", "rxLetErrorTrigger", "rxLetSuspenseTrigger", "rxLetNextTrigger", "rxLetRenderCallback", "rxLetParent", "rxLetPatchZone"], outputs: ["rendered"] }, { kind: "component", type: i10$1.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "component", type: i6$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i11$1.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i11$1.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "directive", type: i1$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: i1$7.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i1$7.RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }, { kind: "pipe", type: i20.RxPush, name: "push" }], animations: [fadeInSlowAnimation], changeDetection: i0.ChangeDetectionStrategy.OnPush });
34783
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: MenuComponent, selector: "app-menu", inputs: { id: "id", menuId: "menuId", side: "side", contentId: "contentId", logo: "logo", appName: "appName", rxStrategy: "rxStrategy", appVersion: "appVersion" }, viewQueries: [{ propertyName: "splitPane", first: true, predicate: ["splitPane"], descendants: true, static: true }], ngImport: i0, template: "<ion-split-pane #splitPane [contentId]=\"contentId\" (swiperight)=\"onSwipeRight($event)\">\n <ion-menu [id]=\"id\" [menuId]=\"menuId\" [contentId]=\"contentId\">\n <ion-header>\n <ion-toolbar *ngIf=\"!loading && isLogin; else notLogin\" @fadeInSlowAnimation class=\"user-toolbar\">\n <ion-grid>\n <ion-row>\n <ion-col size=\"4\">\n <button\n type=\"button\"\n mat-flat-button\n class=\"user-avatar\"\n [class.primary]=\"!accountAvatar\"\n [style.background-image]=\"'url(' + (accountAvatar || defaultAvatarImage) + ')'\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"ion-color-primary\"\n (click)=\"close()\"\n [title]=\"!showTooltip ? ('MENU.BTN_MY_ACCOUNT' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('MENU.BTN_MY_ACCOUNT' | translate) : ''\"\n ></button>\n </ion-col>\n <ion-col size=\"8\" class=\"user-logo\">\n <img\n *ngIf=\"logo; else noLogo\"\n [attr.src]=\"logo\"\n [title]=\"!showTooltip ? ('APP_NAME' | translate: { appName: appName }) : ''\"\n [matTooltip]=\"showTooltip ? ('APP_NAME' | translate: { appName: appName }) : ''\"\n alt=\"Logo\"\n width=\"108px\"\n />\n <ng-template #noLogo>\n <span style=\"width: 108px\">{{ appName }}</span>\n </ng-template>\n </ion-col>\n </ion-row>\n <ion-row class=\"ion-no-padding\">\n <ion-col>\n <button\n mat-button\n type=\"button\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"selected\"\n (click)=\"close()\"\n [title]=\"!showTooltip ? ('MENU.BTN_MY_ACCOUNT' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('MENU.BTN_MY_ACCOUNT' | translate) : ''\"\n >\n <ion-label class=\"ion-text-wrap ion-text-start\">\n <h3 class=\"no-margin username\">\n <b>{{ accountName }}</b>\n </h3>\n <h4>{{ accountEmail }}</h4>\n </ion-label>\n </button>\n </ion-col>\n\n <!-- Insertion headerBottomRight -->\n <ion-col size=\"auto\">\n <ng-container *ngTemplateOutlet=\"headerBottomRight\"></ng-container>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-toolbar>\n\n <!-- User not logged -->\n <ng-template #notLogin>\n <mat-toolbar\n class=\"ion-padding\"\n *ngIf=\"!loading\"\n style=\"height: unset; display: block; margin: auto; text-align: center\"\n >\n <img\n *ngIf=\"logo\"\n [attr.src]=\"logo\"\n [title]=\"!showTooltip ? ('APP_NAME' | translate: { appName: appName }) : ''\"\n [matTooltip]=\"showTooltip ? ('APP_NAME' | translate: { appName: appName }) : ''\"\n width=\"150px;\"\n alt=\"logo\"\n />\n <span *ngIf=\"!logo\" style=\"width: 150px\">{{ appName }}</span>\n </mat-toolbar>\n </ng-template>\n </ion-header>\n\n <ion-content [class.has-user-header]=\"isLogin\">\n <!-- will close the menu, after a click, in mobile -->\n <ion-menu-toggle auto-hide=\"false\">\n <ion-list lines=\"none\" *ngIf=\"!loading\">\n <ng-container *rxFor=\"let item of menuService.dataSubject; strategy: rxStrategy; trackBy: trackByFn\">\n <ng-container *ngTemplateOutlet=\"menuItem; context: { $implicit: item, level: 0 }\"></ng-container>\n </ng-container>\n </ion-list>\n </ion-menu-toggle>\n </ion-content>\n\n <ion-footer class=\"hidden-xxs hidden-xs hidden-sm hidden-md\">\n <ion-toolbar>\n <ion-buttons slot=\"start\">\n <ion-button mat-icon-button color=\"accent\" (click)=\"menuService.openAboutModal()\">\n <mat-icon slot=\"icon-only\">help_outline</mat-icon>\n </ion-button>\n </ion-buttons>\n\n <ion-title (click)=\"menuService.openAboutModal()\" color=\"medium\">\n {{ 'MENU.FOOTER_VERSION_ABOUT' | translate: { version: appVersion } }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <button\n mat-icon-button\n color=\"accent\"\n (click)=\"toggleSplitPaneShow($event)\"\n class=\"hidden-xs hidden-sm hidden-md\"\n [title]=\"!showTooltip ? ((splitPane.when ? 'COMMON.BTN_HIDE_MENU' : 'COMMON.BTN_SHOW_MENU') | translate) : ''\"\n [matTooltip]=\"showTooltip ? ((splitPane.when ? 'COMMON.BTN_HIDE_MENU' : 'COMMON.BTN_SHOW_MENU') | translate) : ''\"\n >\n <mat-icon>\n <span>{{ splitPane.when ? '&#xab;' : '&#xbb;' }}</span>\n </mat-icon>\n </button>\n </ion-buttons>\n </ion-toolbar>\n </ion-footer>\n </ion-menu>\n\n <ng-content></ng-content>\n</ion-split-pane>\n\n<ng-template #headerBottomRight>\n <ng-content select=\"[headerBottomRight]\"></ng-content>\n</ng-template>\n\n<ng-template #menuItem let-item let-level=\"level\">\n @if (item.divider) {\n <!-- divider -->\n <ion-item-divider class=\"{{ item.cssClass }} {{ item.color }}\" @fadeInSlowAnimation>\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{ item.matIcon }}</mat-icon>\n <ion-label *ngIf=\"item.cssClass !== 'flex-spacer'\">\n <span *rxIf=\"item.$title; let title; else: skeletonText\" [innerHTML]=\"title | translate\"></span>\n </ion-label>\n </ion-item-divider>\n } @else if (item.path && !item.action) {\n <!-- tappable link -->\n <ion-item\n @fadeInSlowAnimation\n class=\"{{ item.cssClass }} {{ item.color }}\"\n [class.menu-item-sub]=\"level !== 0\"\n tappable\n [routerLink]=\"item.path\"\n [queryParams]=\"item.pathParams\"\n routerDirection=\"root\"\n routerLinkActive=\"selected\"\n [routerLinkActiveOptions]=\"{\n paths: item.path === '/' || level !== 0 ? 'exact' : 'subset',\n queryParams: item.pathParams?.tab || level === 0 ? 'subset' : 'exact',\n matrixParams: 'exact',\n fragment: 'ignored',\n }\"\n >\n @if (item.icon) {\n <ion-icon slot=\"start\" [name]=\"item.icon\"></ion-icon>\n } @else if (item.matIcon) {\n <mat-icon slot=\"start\">{{ item.matIcon }}</mat-icon>\n } @else if (item.matSvgIcon) {\n <mat-icon slot=\"start\" [svgIcon]=\"item.matSvgIcon\"></mat-icon>\n }\n <ion-label *rxLet=\"item.$title; let title\">\n <span *ngIf=\"title; else skeletonText\" [innerHTML]=\"title | translate\"></span>\n <!--<span *ngIf=\"_debug\"> {{item.id}}</span>-->\n </ion-label>\n\n <ion-badge *rxIf=\"item.$badge; let badge\" [color]=\"item.$badgeColor | push\">\n {{badge}}\n </ion-badge>\n\n <!-- pin button -->\n <ng-container *ngTemplateOutlet=\"pinButton; context: { $implicit: item, level: level }\"></ng-container>\n </ion-item>\n } @else if (item.action) {\n <!-- action -->\n <ion-item\n @fadeInSlowAnimation\n class=\"{{ item.cssClass }} {{ item.color }} text-1x\"\n [class.menu-item-sub]=\"level !== 0\"\n tappable\n (click)=\"executeAction($event, item)\"\n >\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{ item.matIcon }}</mat-icon>\n <ion-label>\n <span *rxIf=\"item.$title; let title; else: skeletonText\" [innerHTML]=\"title | translate\"></span>\n <!--<span *ngIf=\"_debug\"> {{item.id}}</span>-->\n </ion-label>\n\n <ion-badge *rxIf=\"item.$badge; let badge\" [color]=\"item.$badgeColor | push\">\n {{badge}}\n </ion-badge>\n\n <!-- pin button -->\n <ng-container *ngTemplateOutlet=\"pinButton; context: { $implicit: item, level: level }\"></ng-container>\n </ion-item>\n }\n\n <!-- children -->\n @if (item.$children) {\n <div class=\"sub-menu-container\">\n <ng-container *rxFor=\"let child of item.$children; strategy: rxStrategy; trackBy: trackByFn\">\n <ng-container *ngTemplateOutlet=\"menuItem; context: { $implicit: child, level: level + 1 }\"></ng-container>\n </ng-container>\n </div>\n }\n</ng-template>\n\n<ng-template #pinButton let-item let-level=\"level\">\n <!-- pin button -->\n @if (level && item.pinnable) {\n <ion-button\n slot=\"end\"\n shape=\"round\"\n fill=\"clear\"\n size=\"small\"\n [class.visible-hover]=\"!item.pinned\"\n (click)=\"togglePinned($event, item)\"\n >\n <!--<mat-icon slot=\"icon-only\" [color]=\"item.pinned ? 'primary' : undefined\">push_pin</mat-icon>-->\n <ion-icon slot=\"icon-only\" [name]=\"item.pinned ? 'pin' : 'pin-outline'\"></ion-icon>\n </ion-button>\n }\n</ng-template>\n\n<ng-template #skeletonText let-width let-animated>\n <ion-skeleton-text [animated]=\"animated\" [style.width.%]=\"width || 60\"></ion-skeleton-text>\n</ng-template>\n", styles: [":host{--menu-item-margin: 0;--ion-item-background: transparent;--ion-item-divider-background: transparent;--ion-item-icon-color: var(--ion-color-primary-tint);--ion-item-text-color: var(--ion-color-primary-tint);--ion-item-background-selected: var(--ion-color-secondary100);--ion-item-text-color-selected: var(--ion-color-primary);--ion-item-icon-color-selected: var(--ion-color-primary);--ion-item-text-color-disable: var(--ion-color-medium);--ion-item-icon-color-disable: var(--ion-color-medium);--menu-item-background-selected-sub: var(--ion-color-secondary50);--menu-item-border-width-sub: 0 0 1px 0;--menu-item-margin-start-sub: 16px}@keyframes fadeinout{0%{opacity:0;display:none}75%{opacity:0;display:none}to{opacity:1;display:flex}}ion-menu ion-header ion-text{color:var(--ion-color-primary)}ion-menu ion-header ion-toolbar.user-toolbar{--ion-toolbar-height: 128px}ion-menu ion-header .user-avatar{-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;background-size:cover;background-repeat:no-repeat;background-position:center;background-color:var(--ion-color-secondary);border:solid 1px rgba(var(--ion-color-secondary-rgb),.5);overflow:hidden!important;font-size:var(--avatar-size, 60px)!important;line-height:var(--avatar-size, 60px);height:var(--avatar-size, 60px)!important;width:var(--avatar-size, 60px)!important;border-radius:50%;display:inline-block}ion-menu ion-header .user-avatar:hover{background-color:var(--ion-color-secondary-shade);border:solid 2px var(--ion-color-secondary-shade)}ion-menu ion-header .user-logo{text-align:right}ion-menu ion-header .user-logo img{max-width:120px;max-height:var(--avatar-size, 60px);width:auto}ion-menu ion-header .username{padding-top:0;margin-top:0;margin-bottom:0;width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}ion-menu ion-header button[mat-button]{padding:0;width:100%;justify-content:flex-start;text-align:start!important;color:var(--ion-item-text-color)}ion-menu ion-header button[mat-button].selected{background-color:var(--ion-item-background-selected);color:var(--ion-item-text-color-selected)!important}ion-menu ion-content ion-list{min-height:100%;display:flex;flex-direction:column;justify-content:flex-start}ion-menu ion-content ion-list ion-menu-toggle.flex-spacer{flex:1 1 auto;display:flex;flex-direction:column;justify-content:flex-end}ion-menu ion-content ion-list ion-item{display:block}ion-menu ion-content ion-list ion-item.primary{--ion-item-icon-color: var(--ion-color-primary-tint);--ion-item-text-color: var(--ion-color-primary-tint)}ion-menu ion-content ion-list ion-item.secondary{--ion-item-icon-color: var(--ion-color-secondary-tint);--ion-item-text-color: var(--ion-color-secondary-tint)}ion-menu ion-content ion-list ion-item.tertiary{--ion-item-icon-color: var(--ion-color-tertiary-tint);--ion-item-text-color: var(--ion-color-tertiary-tint)}ion-menu ion-content ion-list ion-item.danger{--ion-item-icon-color: var(--ion-color-danger-tint);--ion-item-text-color: var(--ion-color-danger-tint)}ion-menu ion-content ion-list ion-item.medium{--ion-item-icon-color: var(--ion-color-medium-shade);--ion-item-text-color: var(--ion-color-medium-shade)}ion-menu ion-content ion-list ion-item.dark{--ion-item-icon-color: var(--ion-color-dark-tint);--ion-item-text-color: var(--ion-color-dark-tint)}ion-menu ion-content ion-list ion-item mat-icon,ion-menu ion-content ion-list ion-item ion-icon{color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor}ion-menu ion-content ion-list ion-item ::ng-deep mat-icon svg{fill:currentColor;stroke:currentColor}ion-menu ion-content ion-list ion-item ion-text,ion-menu ion-content ion-list ion-item ion-label{color:var(--ion-item-text-color)!important}ion-menu ion-content ion-list ion-item span{margin-left:var(--menu-item-margin)}ion-menu ion-content ion-list ion-item.selected{background-color:var(--ion-item-background-selected)!important;--color-hover: var(--ion-item-background-selected) !important}ion-menu ion-content ion-list ion-item.selected mat-icon,ion-menu ion-content ion-list ion-item.selected ion-icon{color:var(--ion-item-icon-color-selected)!important;fill:currentColor;stroke:currentColor}ion-menu ion-content ion-list ion-item.selected ion-text,ion-menu ion-content ion-list ion-item.selected ion-label{color:var(--ion-item-text-color-selected)!important}ion-menu ion-content ion-list ion-item.selected:hover{--color-hover: var(--ion-item-background-selected) !important;--ion-item-icon-color-selected: var(--ion-item-icon-color) !important;--ion-item-text-color-selected: var(--ion-item-text-color) !important}ion-menu ion-content ion-list ion-item.selected.menu-item-sub{background-color:var(--menu-item-background-selected-sub)!important}ion-menu ion-content ion-list ion-item.menu-item-sub{--min-height: 34px;--padding-vertical: 8px;--padding-start: 8px;--padding-horizontal: 8px;--inner-border-width: var(--menu-item-border-width-sub);font-size:.8em;overflow:unset}ion-menu ion-content ion-list ion-item.menu-item-sub ion-icon[slot],ion-menu ion-content ion-list ion-item.menu-item-sub mat-icon[slot]{font-size:calc(var(--min-height) / 2 - 1px);height:calc(var(--min-height) / 2 - 1px);margin-top:calc(var(--padding-vertical) + 2px);margin-inline-end:calc(var(--padding-horizontal) + 2px);margin-bottom:calc(var(--padding-vertical) + 2px)}ion-menu ion-content ion-list ion-item.menu-item-sub ion-label{margin-top:var(--padding-vertical);margin-bottom:var(--padding-vertical)}ion-menu ion-content ion-list ion-item.menu-item-sub .item-inner{border-width:var(--inner-border-width);border-style:var(--border-style);border-color:var(--border-color);box-shadow:var(--inner-box-shadow)}ion-menu ion-content ion-list ion-item.menu-item-sub ion-button[slot=end]{-webkit-margin-start:unset;margin-inline-start:unset}ion-menu ion-content ion-list ion-item.menu-item-sub ion-button[slot=end] mat-icon[slot=icon-only],ion-menu ion-content ion-list ion-item.menu-item-sub ion-button[slot=end] ion-icon[slot=icon-only]{margin:0}ion-menu ion-content ion-list ion-item.menu-item-sub ion-button[slot=end].visible-hover{opacity:0;display:none;animation:fadeinout 1s linear 1 backwards}ion-menu ion-content ion-list ion-item.menu-item-sub:hover ion-button.visible-hover{opacity:1;display:flex}ion-menu ion-content ion-list .sub-menu-container{margin-inline-start:var(--menu-item-margin-start-sub, 16px)}ion-menu ion-footer{display:block}ion-menu ion-footer ion-toolbar{--ion-safe-area-bottom: 0}ion-menu ion-footer ion-toolbar ion-title{cursor:pointer;font-size:12pt;font-weight:400;text-align:center;padding:0 8px}ion-menu ion-footer ion-toolbar ion-button{--width: 40px}@media screen and (max-width: 767px){ion-menu ion-footer{display:none!important;visibility:hidden!important}}@media screen and (min-width: 768px){ion-menu ion-scroll{overflow-y:auto!important}ion-menu .user-avatar{font-size:var(--avatar-size, 80px)!important;line-height:var(--avatar-size, 80px);height:var(--avatar-size, 80px)!important;width:var(--avatar-size, 80px)!important}ion-menu .user-logo img{max-height:var(--avatar-size, 80px)}}\n"], dependencies: [{ kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i2$1.IonBadge, selector: "ion-badge", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i2$1.IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i2$1.IonItemDivider, selector: "ion-item-divider", inputs: ["color", "mode", "sticky"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: i2$1.IonMenu, selector: "ion-menu", inputs: ["contentId", "disabled", "maxEdgeStart", "menuId", "side", "swipeGesture", "type"] }, { kind: "component", type: i2$1.IonMenuToggle, selector: "ion-menu-toggle", inputs: ["autoHide", "menu"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "component", type: i2$1.IonSkeletonText, selector: "ion-skeleton-text", inputs: ["animated"] }, { kind: "component", type: i2$1.IonSplitPane, selector: "ion-split-pane", inputs: ["contentId", "disabled", "when"] }, { kind: "component", type: i2$1.IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: i2$1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "directive", type: i2$1.RouterLinkDelegate, selector: ":not(a):not(area)[routerLink]" }, { kind: "directive", type: i13$3.RxFor, selector: "[rxFor][rxForOf]", inputs: ["rxForOf", "rxForTemplate", "rxForStrategy", "rxForParent", "rxForPatchZone", "rxForTrackBy", "rxForRenderCallback"] }, { kind: "directive", type: i8$1.RxIf, selector: "[rxIf]", inputs: ["rxIf", "rxIfStrategy", "rxIfElse", "rxIfThen", "rxIfSuspense", "rxIfComplete", "rxIfError", "rxIfContextTrigger", "rxIfNextTrigger", "rxIfSuspenseTrigger", "rxIfErrorTrigger", "rxIfCompleteTrigger", "rxIfParent", "rxIfPatchZone", "rxIfRenderCallback"] }, { kind: "directive", type: i9$1.RxLet, selector: "[rxLet]", inputs: ["rxLet", "rxLetStrategy", "rxLetComplete", "rxLetError", "rxLetSuspense", "rxLetContextTrigger", "rxLetCompleteTrigger", "rxLetErrorTrigger", "rxLetSuspenseTrigger", "rxLetNextTrigger", "rxLetRenderCallback", "rxLetParent", "rxLetPatchZone"], outputs: ["rendered"] }, { kind: "component", type: i10$1.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "component", type: i6$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i11$1.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i11$1.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "directive", type: i1$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: i1$7.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i1$7.RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }, { kind: "pipe", type: i20.RxPush, name: "push" }], animations: [fadeInSlowAnimation], changeDetection: i0.ChangeDetectionStrategy.OnPush });
34447
34784
  }
34448
34785
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MenuComponent, decorators: [{
34449
34786
  type: Component,
34450
- args: [{ selector: 'app-menu', animations: [fadeInSlowAnimation], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ion-split-pane #splitPane [contentId]=\"contentId\" (swiperight)=\"onSwipeRight($event)\">\n <ion-menu [id]=\"id\" [menuId]=\"menuId\" [contentId]=\"contentId\">\n <ion-header>\n <ion-toolbar *ngIf=\"!loading && isLogin; else notLogin\" @fadeInSlowAnimation class=\"user-toolbar\">\n <ion-grid>\n <ion-row>\n <ion-col size=\"4\">\n <button\n type=\"button\"\n mat-flat-button\n class=\"user-avatar\"\n [class.primary]=\"!accountAvatar\"\n [style.background-image]=\"'url(' + (accountAvatar || defaultAvatarImage) + ')'\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"ion-color-primary\"\n (click)=\"close()\"\n [title]=\"!showTooltip ? ('MENU.BTN_MY_ACCOUNT' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('MENU.BTN_MY_ACCOUNT' | translate) : ''\"\n ></button>\n </ion-col>\n <ion-col size=\"8\" class=\"user-logo\">\n <img\n *ngIf=\"logo; else noLogo\"\n [attr.src]=\"logo\"\n [title]=\"!showTooltip ? ('APP_NAME' | translate: { appName: appName }) : ''\"\n [matTooltip]=\"showTooltip ? ('APP_NAME' | translate: { appName: appName }) : ''\"\n alt=\"Logo\"\n width=\"108px\"\n />\n <ng-template #noLogo>\n <span style=\"width: 108px\">{{ appName }}</span>\n </ng-template>\n </ion-col>\n </ion-row>\n <ion-row class=\"ion-no-padding\">\n <ion-col>\n <button\n mat-button\n type=\"button\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"selected\"\n (click)=\"close()\"\n [title]=\"!showTooltip ? ('MENU.BTN_MY_ACCOUNT' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('MENU.BTN_MY_ACCOUNT' | translate) : ''\"\n >\n <ion-label class=\"ion-text-wrap ion-text-start\">\n <h3 class=\"no-margin username\">\n <b>{{ accountName }}</b>\n </h3>\n <h4>{{ accountEmail }}</h4>\n </ion-label>\n </button>\n </ion-col>\n\n <!-- Insertion headerBottomRight -->\n <ion-col size=\"auto\">\n <ng-container *ngTemplateOutlet=\"headerBottomRight\"></ng-container>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-toolbar>\n\n <!-- User not logged -->\n <ng-template #notLogin>\n <mat-toolbar\n class=\"ion-padding\"\n *ngIf=\"!loading\"\n style=\"height: unset; display: block; margin: auto; text-align: center\"\n >\n <img\n *ngIf=\"logo\"\n [attr.src]=\"logo\"\n [title]=\"!showTooltip ? ('APP_NAME' | translate: { appName: appName }) : ''\"\n [matTooltip]=\"showTooltip ? ('APP_NAME' | translate: { appName: appName }) : ''\"\n width=\"150px;\"\n alt=\"logo\"\n />\n <span *ngIf=\"!logo\" style=\"width: 150px\">{{ appName }}</span>\n </mat-toolbar>\n </ng-template>\n </ion-header>\n\n <ion-content [class.has-user-header]=\"isLogin\">\n <!-- will close the menu, after a click, in mobile -->\n <ion-menu-toggle auto-hide=\"false\">\n <ion-list lines=\"none\" *ngIf=\"!loading\">\n <ng-container *rxFor=\"let item of menuService.dataSubject; strategy: rxStrategy; trackBy: trackByFn\">\n <ng-container *ngTemplateOutlet=\"menuItem; context: { $implicit: item, level: 0 }\"></ng-container>\n </ng-container>\n </ion-list>\n </ion-menu-toggle>\n </ion-content>\n\n <ion-footer class=\"hidden-xxs hidden-xs hidden-sm hidden-md\">\n <ion-toolbar>\n <ion-buttons slot=\"start\">\n <ion-button mat-icon-button color=\"accent\" (click)=\"menuService.openAboutModal()\">\n <mat-icon slot=\"icon-only\">help_outline</mat-icon>\n </ion-button>\n </ion-buttons>\n\n <ion-title (click)=\"menuService.openAboutModal()\" color=\"medium\">\n {{ 'MENU.FOOTER_VERSION_ABOUT' | translate: { version: appVersion } }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <button\n mat-icon-button\n color=\"accent\"\n (click)=\"toggleSplitPaneShow($event)\"\n class=\"hidden-xs hidden-sm hidden-md\"\n [title]=\"!showTooltip ? ((splitPane.when ? 'COMMON.BTN_HIDE_MENU' : 'COMMON.BTN_SHOW_MENU') | translate) : ''\"\n [matTooltip]=\"showTooltip ? ((splitPane.when ? 'COMMON.BTN_HIDE_MENU' : 'COMMON.BTN_SHOW_MENU') | translate) : ''\"\n >\n <mat-icon>\n <span>{{ splitPane.when ? '&#xab;' : '&#xbb;' }}</span>\n </mat-icon>\n </button>\n </ion-buttons>\n </ion-toolbar>\n </ion-footer>\n </ion-menu>\n\n <ng-content></ng-content>\n</ion-split-pane>\n\n<ng-template #headerBottomRight>\n <ng-content select=\"[headerBottomRight]\"></ng-content>\n</ng-template>\n\n<ng-template #menuItem let-item let-level=\"level\">\n @if (item.divider) {\n <!-- divider -->\n <ion-item-divider class=\"{{ item.cssClass }} {{ item.color }}\" @fadeInSlowAnimation>\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{ item.matIcon }}</mat-icon>\n <ion-label *ngIf=\"item.cssClass !== 'flex-spacer'\">\n <span *rxIf=\"item.$title; let title; else: skeletonText\" [innerHTML]=\"title | translate\"></span>\n </ion-label>\n </ion-item-divider>\n } @else if (item.path && !item.action) {\n <!-- tappable link -->\n <ion-item\n @fadeInSlowAnimation\n class=\"{{ item.cssClass }} {{ item.color }}\"\n [class.menu-item-sub]=\"level !== 0\"\n tappable\n [routerLink]=\"item.path\"\n [queryParams]=\"item.pathParams\"\n routerDirection=\"root\"\n routerLinkActive=\"selected\"\n [routerLinkActiveOptions]=\"{\n paths: item.path === '/' || level !== 0 ? 'exact' : 'subset',\n queryParams: item.pathParams?.tab || level === 0 ? 'subset' : 'exact',\n matrixParams: 'exact',\n fragment: 'ignored',\n }\"\n >\n @if (item.icon) {\n <ion-icon slot=\"start\" [name]=\"item.icon\"></ion-icon>\n } @else if (item.matIcon) {\n <mat-icon slot=\"start\">{{ item.matIcon }}</mat-icon>\n } @else if (item.matSvgIcon) {\n <mat-icon slot=\"start\" [svgIcon]=\"item.matSvgIcon\"></mat-icon>\n }\n <ion-label *rxLet=\"item.$title; let title\">\n <span *ngIf=\"title; else skeletonText\" [innerHTML]=\"title | translate\"></span>\n <!--<span *ngIf=\"_debug\"> {{item.id}}</span>-->\n </ion-label>\n\n <ion-badge *rxIf=\"item.$badge; let badge\" [color]=\"item.$badgeColor | push\">\n {{badge}}\n </ion-badge>\n\n <!-- pin button -->\n <ng-container *ngTemplateOutlet=\"pinButton; context: { $implicit: item, level: level }\"></ng-container>\n </ion-item>\n } @else if (item.action) {\n <!-- action -->\n <ion-item\n @fadeInSlowAnimation\n class=\"{{ item.cssClass }} {{ item.color }} text-1x\"\n [class.menu-item-sub]=\"level !== 0\"\n tappable\n (click)=\"executeAction($event, item)\"\n >\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{ item.matIcon }}</mat-icon>\n <ion-label>\n <span *rxIf=\"item.$title; let title; else: skeletonText\" [innerHTML]=\"title | translate\"></span>\n <!--<span *ngIf=\"_debug\"> {{item.id}}</span>-->\n </ion-label>\n\n <ion-badge *rxIf=\"item.$badge; let badge\" [color]=\"item.$badgeColor | push\">\n {{badge}}\n </ion-badge>\n\n <!-- pin button -->\n <ng-container *ngTemplateOutlet=\"pinButton; context: { $implicit: item, level: level }\"></ng-container>\n </ion-item>\n }\n\n <!-- children -->\n @if (item.$children) {\n <div class=\"sub-menu-container\">\n <ng-container *rxFor=\"let child of item.$children; strategy: rxStrategy; trackBy: trackByFn\">\n <ng-container *ngTemplateOutlet=\"menuItem; context: { $implicit: child, level: level + 1 }\"></ng-container>\n </ng-container>\n </div>\n }\n</ng-template>\n\n<ng-template #pinButton let-item let-level=\"level\">\n <!-- pin button -->\n @if (level && item.pinnable) {\n <ion-button\n slot=\"end\"\n shape=\"round\"\n fill=\"clear\"\n size=\"small\"\n [class.visible-hover]=\"!item.pinned\"\n (click)=\"togglePinned($event, item)\"\n >\n <!--<mat-icon slot=\"icon-only\" [color]=\"item.pinned ? 'primary' : undefined\">push_pin</mat-icon>-->\n <ion-icon slot=\"icon-only\" [name]=\"item.pinned ? 'pin' : 'pin-outline'\"></ion-icon>\n </ion-button>\n }\n</ng-template>\n\n<ng-template #skeletonText let-width let-animated>\n <ion-skeleton-text [animated]=\"animated\" [style.width.%]=\"width || 60\"></ion-skeleton-text>\n</ng-template>\n", styles: [":host{--menu-item-margin: 0;--ion-item-background: transparent;--ion-item-divider-background: transparent;--ion-item-icon-color: var(--ion-color-primary-tint);--ion-item-text-color: var(--ion-color-primary-tint);--ion-item-background-selected: var(--ion-color-secondary100);--ion-item-text-color-selected: var(--ion-color-primary);--ion-item-icon-color-selected: var(--ion-color-primary);--ion-item-text-color-disable: var(--ion-color-medium);--ion-item-icon-color-disable: var(--ion-color-medium);--menu-item-background-selected-sub: var(--ion-color-secondary50);--menu-item-border-width-sub: 0 0 1px 0;--menu-item-margin-start-sub: 16px}@keyframes fadeinout{0%{opacity:0;display:none}75%{opacity:0;display:none}to{opacity:1;display:flex}}ion-menu ion-header ion-text{color:var(--ion-color-primary)}ion-menu ion-header ion-toolbar.user-toolbar{--ion-toolbar-height: 128px}ion-menu ion-header .user-avatar{-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;background-size:cover;background-repeat:no-repeat;background-position:center;background-color:var(--ion-color-secondary);border:solid 1px rgba(var(--ion-color-secondary-rgb),.5);overflow:hidden!important;font-size:var(--avatar-size, 60px)!important;line-height:var(--avatar-size, 60px);height:var(--avatar-size, 60px)!important;width:var(--avatar-size, 60px)!important;border-radius:50%;display:inline-block}ion-menu ion-header .user-avatar:hover{background-color:var(--ion-color-secondary-shade);border:solid 2px var(--ion-color-secondary-shade)}ion-menu ion-header .user-logo{text-align:right}ion-menu ion-header .user-logo img{max-width:120px;max-height:var(--avatar-size, 60px);width:auto}ion-menu ion-header .username{padding-top:0;margin-top:0;margin-bottom:0;width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}ion-menu ion-header button[mat-button]{padding:0;width:100%;justify-content:flex-start;text-align:start!important;color:var(--ion-item-text-color)}ion-menu ion-header button[mat-button].selected{background-color:var(--ion-item-background-selected);color:var(--ion-item-text-color-selected)!important}ion-menu ion-content ion-list{min-height:100%;display:flex;flex-direction:column;justify-content:flex-start}ion-menu ion-content ion-list ion-menu-toggle.flex-spacer{flex:1 1 auto;display:flex;flex-direction:column;justify-content:flex-end}ion-menu ion-content ion-list ion-item{display:block}ion-menu ion-content ion-list ion-item.primary{--ion-item-icon-color: var(--ion-color-primary-tint);--ion-item-text-color: var(--ion-color-primary-tint)}ion-menu ion-content ion-list ion-item.secondary{--ion-item-icon-color: var(--ion-color-secondary-tint);--ion-item-text-color: var(--ion-color-secondary-tint)}ion-menu ion-content ion-list ion-item.tertiary{--ion-item-icon-color: var(--ion-color-tertiary-tint);--ion-item-text-color: var(--ion-color-tertiary-tint)}ion-menu ion-content ion-list ion-item.danger{--ion-item-icon-color: var(--ion-color-danger-tint);--ion-item-text-color: var(--ion-color-danger-tint)}ion-menu ion-content ion-list ion-item.medium{--ion-item-icon-color: var(--ion-color-medium-shade);--ion-item-text-color: var(--ion-color-medium-shade)}ion-menu ion-content ion-list ion-item.dark{--ion-item-icon-color: var(--ion-color-dark-tint);--ion-item-text-color: var(--ion-color-dark-tint)}ion-menu ion-content ion-list ion-item mat-icon,ion-menu ion-content ion-list ion-item ion-icon{color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor}ion-menu ion-content ion-list ion-item ::ng-deep mat-icon svg{fill:currentColor;stroke:currentColor}ion-menu ion-content ion-list ion-item ion-text,ion-menu ion-content ion-list ion-item ion-label{color:var(--ion-item-text-color)!important}ion-menu ion-content ion-list ion-item span{margin-left:var(--menu-item-margin)}ion-menu ion-content ion-list ion-item.selected{background-color:var(--ion-item-background-selected)!important;--color-hover: var(--ion-item-background-selected) !important}ion-menu ion-content ion-list ion-item.selected mat-icon,ion-menu ion-content ion-list ion-item.selected ion-icon{color:var(--ion-item-icon-color-selected)!important;fill:currentColor;stroke:currentColor}ion-menu ion-content ion-list ion-item.selected ion-text,ion-menu ion-content ion-list ion-item.selected ion-label{color:var(--ion-item-text-color-selected)!important}ion-menu ion-content ion-list ion-item.selected:hover{--color-hover: var(--ion-item-background-selected) !important;--ion-item-icon-color-selected: var(--ion-item-icon-color) !important;--ion-item-text-color-selected: var(--ion-item-text-color) !important}ion-menu ion-content ion-list ion-item.selected.menu-item-sub{background-color:var(--menu-item-background-selected-sub)!important}ion-menu ion-content ion-list ion-item.menu-item-sub{--min-height: 34px;--padding-vertical: 8px;--padding-start: 8px;--padding-horizontal: 8px;--inner-border-width: var(--menu-item-border-width-sub);font-size:.8em;overflow:unset}ion-menu ion-content ion-list ion-item.menu-item-sub ion-icon[slot],ion-menu ion-content ion-list ion-item.menu-item-sub mat-icon[slot]{font-size:calc(var(--min-height) / 2 - 1px);height:calc(var(--min-height) / 2 - 1px);margin-top:calc(var(--padding-vertical) + 2px);margin-inline-end:calc(var(--padding-horizontal) + 2px);margin-bottom:calc(var(--padding-vertical) + 2px)}ion-menu ion-content ion-list ion-item.menu-item-sub ion-label{margin-top:var(--padding-vertical);margin-bottom:var(--padding-vertical)}ion-menu ion-content ion-list ion-item.menu-item-sub .item-inner{border-width:var(--inner-border-width);border-style:var(--border-style);border-color:var(--border-color);box-shadow:var(--inner-box-shadow)}ion-menu ion-content ion-list ion-item.menu-item-sub ion-button[slot=end]{-webkit-margin-start:unset;margin-inline-start:unset}ion-menu ion-content ion-list ion-item.menu-item-sub ion-button[slot=end] mat-icon[slot=icon-only],ion-menu ion-content ion-list ion-item.menu-item-sub ion-button[slot=end] ion-icon[slot=icon-only]{margin:0}ion-menu ion-content ion-list ion-item.menu-item-sub ion-button[slot=end].visible-hover{opacity:0;display:none;animation:fadeinout 1s linear 1 backwards}ion-menu ion-content ion-list ion-item.menu-item-sub:hover ion-button.visible-hover{opacity:1;display:flex}ion-menu ion-content ion-list .sub-menu-container{margin-inline-start:var(--menu-item-margin-start-sub, 16px)}ion-menu ion-footer{display:block}ion-menu ion-footer ion-toolbar ion-title{cursor:pointer;font-size:12pt;font-weight:400;text-align:center;padding:0 8px}ion-menu ion-footer ion-toolbar ion-button{--width: 40px}@media screen and (max-width: 767px){ion-menu ion-footer{display:none!important;visibility:hidden!important}}@media screen and (min-width: 768px){ion-menu ion-scroll{overflow-y:auto!important}ion-menu .user-avatar{font-size:var(--avatar-size, 80px)!important;line-height:var(--avatar-size, 80px);height:var(--avatar-size, 80px)!important;width:var(--avatar-size, 80px)!important}ion-menu .user-logo img{max-height:var(--avatar-size, 80px)}}\n"] }]
34787
+ args: [{ selector: 'app-menu', animations: [fadeInSlowAnimation], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ion-split-pane #splitPane [contentId]=\"contentId\" (swiperight)=\"onSwipeRight($event)\">\n <ion-menu [id]=\"id\" [menuId]=\"menuId\" [contentId]=\"contentId\">\n <ion-header>\n <ion-toolbar *ngIf=\"!loading && isLogin; else notLogin\" @fadeInSlowAnimation class=\"user-toolbar\">\n <ion-grid>\n <ion-row>\n <ion-col size=\"4\">\n <button\n type=\"button\"\n mat-flat-button\n class=\"user-avatar\"\n [class.primary]=\"!accountAvatar\"\n [style.background-image]=\"'url(' + (accountAvatar || defaultAvatarImage) + ')'\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"ion-color-primary\"\n (click)=\"close()\"\n [title]=\"!showTooltip ? ('MENU.BTN_MY_ACCOUNT' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('MENU.BTN_MY_ACCOUNT' | translate) : ''\"\n ></button>\n </ion-col>\n <ion-col size=\"8\" class=\"user-logo\">\n <img\n *ngIf=\"logo; else noLogo\"\n [attr.src]=\"logo\"\n [title]=\"!showTooltip ? ('APP_NAME' | translate: { appName: appName }) : ''\"\n [matTooltip]=\"showTooltip ? ('APP_NAME' | translate: { appName: appName }) : ''\"\n alt=\"Logo\"\n width=\"108px\"\n />\n <ng-template #noLogo>\n <span style=\"width: 108px\">{{ appName }}</span>\n </ng-template>\n </ion-col>\n </ion-row>\n <ion-row class=\"ion-no-padding\">\n <ion-col>\n <button\n mat-button\n type=\"button\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"selected\"\n (click)=\"close()\"\n [title]=\"!showTooltip ? ('MENU.BTN_MY_ACCOUNT' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('MENU.BTN_MY_ACCOUNT' | translate) : ''\"\n >\n <ion-label class=\"ion-text-wrap ion-text-start\">\n <h3 class=\"no-margin username\">\n <b>{{ accountName }}</b>\n </h3>\n <h4>{{ accountEmail }}</h4>\n </ion-label>\n </button>\n </ion-col>\n\n <!-- Insertion headerBottomRight -->\n <ion-col size=\"auto\">\n <ng-container *ngTemplateOutlet=\"headerBottomRight\"></ng-container>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-toolbar>\n\n <!-- User not logged -->\n <ng-template #notLogin>\n <mat-toolbar\n class=\"ion-padding\"\n *ngIf=\"!loading\"\n style=\"height: unset; display: block; margin: auto; text-align: center\"\n >\n <img\n *ngIf=\"logo\"\n [attr.src]=\"logo\"\n [title]=\"!showTooltip ? ('APP_NAME' | translate: { appName: appName }) : ''\"\n [matTooltip]=\"showTooltip ? ('APP_NAME' | translate: { appName: appName }) : ''\"\n width=\"150px;\"\n alt=\"logo\"\n />\n <span *ngIf=\"!logo\" style=\"width: 150px\">{{ appName }}</span>\n </mat-toolbar>\n </ng-template>\n </ion-header>\n\n <ion-content [class.has-user-header]=\"isLogin\">\n <!-- will close the menu, after a click, in mobile -->\n <ion-menu-toggle auto-hide=\"false\">\n <ion-list lines=\"none\" *ngIf=\"!loading\">\n <ng-container *rxFor=\"let item of menuService.dataSubject; strategy: rxStrategy; trackBy: trackByFn\">\n <ng-container *ngTemplateOutlet=\"menuItem; context: { $implicit: item, level: 0 }\"></ng-container>\n </ng-container>\n </ion-list>\n </ion-menu-toggle>\n </ion-content>\n\n <ion-footer class=\"hidden-xxs hidden-xs hidden-sm hidden-md\">\n <ion-toolbar>\n <ion-buttons slot=\"start\">\n <ion-button mat-icon-button color=\"accent\" (click)=\"menuService.openAboutModal()\">\n <mat-icon slot=\"icon-only\">help_outline</mat-icon>\n </ion-button>\n </ion-buttons>\n\n <ion-title (click)=\"menuService.openAboutModal()\" color=\"medium\">\n {{ 'MENU.FOOTER_VERSION_ABOUT' | translate: { version: appVersion } }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <button\n mat-icon-button\n color=\"accent\"\n (click)=\"toggleSplitPaneShow($event)\"\n class=\"hidden-xs hidden-sm hidden-md\"\n [title]=\"!showTooltip ? ((splitPane.when ? 'COMMON.BTN_HIDE_MENU' : 'COMMON.BTN_SHOW_MENU') | translate) : ''\"\n [matTooltip]=\"showTooltip ? ((splitPane.when ? 'COMMON.BTN_HIDE_MENU' : 'COMMON.BTN_SHOW_MENU') | translate) : ''\"\n >\n <mat-icon>\n <span>{{ splitPane.when ? '&#xab;' : '&#xbb;' }}</span>\n </mat-icon>\n </button>\n </ion-buttons>\n </ion-toolbar>\n </ion-footer>\n </ion-menu>\n\n <ng-content></ng-content>\n</ion-split-pane>\n\n<ng-template #headerBottomRight>\n <ng-content select=\"[headerBottomRight]\"></ng-content>\n</ng-template>\n\n<ng-template #menuItem let-item let-level=\"level\">\n @if (item.divider) {\n <!-- divider -->\n <ion-item-divider class=\"{{ item.cssClass }} {{ item.color }}\" @fadeInSlowAnimation>\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{ item.matIcon }}</mat-icon>\n <ion-label *ngIf=\"item.cssClass !== 'flex-spacer'\">\n <span *rxIf=\"item.$title; let title; else: skeletonText\" [innerHTML]=\"title | translate\"></span>\n </ion-label>\n </ion-item-divider>\n } @else if (item.path && !item.action) {\n <!-- tappable link -->\n <ion-item\n @fadeInSlowAnimation\n class=\"{{ item.cssClass }} {{ item.color }}\"\n [class.menu-item-sub]=\"level !== 0\"\n tappable\n [routerLink]=\"item.path\"\n [queryParams]=\"item.pathParams\"\n routerDirection=\"root\"\n routerLinkActive=\"selected\"\n [routerLinkActiveOptions]=\"{\n paths: item.path === '/' || level !== 0 ? 'exact' : 'subset',\n queryParams: item.pathParams?.tab || level === 0 ? 'subset' : 'exact',\n matrixParams: 'exact',\n fragment: 'ignored',\n }\"\n >\n @if (item.icon) {\n <ion-icon slot=\"start\" [name]=\"item.icon\"></ion-icon>\n } @else if (item.matIcon) {\n <mat-icon slot=\"start\">{{ item.matIcon }}</mat-icon>\n } @else if (item.matSvgIcon) {\n <mat-icon slot=\"start\" [svgIcon]=\"item.matSvgIcon\"></mat-icon>\n }\n <ion-label *rxLet=\"item.$title; let title\">\n <span *ngIf=\"title; else skeletonText\" [innerHTML]=\"title | translate\"></span>\n <!--<span *ngIf=\"_debug\"> {{item.id}}</span>-->\n </ion-label>\n\n <ion-badge *rxIf=\"item.$badge; let badge\" [color]=\"item.$badgeColor | push\">\n {{badge}}\n </ion-badge>\n\n <!-- pin button -->\n <ng-container *ngTemplateOutlet=\"pinButton; context: { $implicit: item, level: level }\"></ng-container>\n </ion-item>\n } @else if (item.action) {\n <!-- action -->\n <ion-item\n @fadeInSlowAnimation\n class=\"{{ item.cssClass }} {{ item.color }} text-1x\"\n [class.menu-item-sub]=\"level !== 0\"\n tappable\n (click)=\"executeAction($event, item)\"\n >\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{ item.matIcon }}</mat-icon>\n <ion-label>\n <span *rxIf=\"item.$title; let title; else: skeletonText\" [innerHTML]=\"title | translate\"></span>\n <!--<span *ngIf=\"_debug\"> {{item.id}}</span>-->\n </ion-label>\n\n <ion-badge *rxIf=\"item.$badge; let badge\" [color]=\"item.$badgeColor | push\">\n {{badge}}\n </ion-badge>\n\n <!-- pin button -->\n <ng-container *ngTemplateOutlet=\"pinButton; context: { $implicit: item, level: level }\"></ng-container>\n </ion-item>\n }\n\n <!-- children -->\n @if (item.$children) {\n <div class=\"sub-menu-container\">\n <ng-container *rxFor=\"let child of item.$children; strategy: rxStrategy; trackBy: trackByFn\">\n <ng-container *ngTemplateOutlet=\"menuItem; context: { $implicit: child, level: level + 1 }\"></ng-container>\n </ng-container>\n </div>\n }\n</ng-template>\n\n<ng-template #pinButton let-item let-level=\"level\">\n <!-- pin button -->\n @if (level && item.pinnable) {\n <ion-button\n slot=\"end\"\n shape=\"round\"\n fill=\"clear\"\n size=\"small\"\n [class.visible-hover]=\"!item.pinned\"\n (click)=\"togglePinned($event, item)\"\n >\n <!--<mat-icon slot=\"icon-only\" [color]=\"item.pinned ? 'primary' : undefined\">push_pin</mat-icon>-->\n <ion-icon slot=\"icon-only\" [name]=\"item.pinned ? 'pin' : 'pin-outline'\"></ion-icon>\n </ion-button>\n }\n</ng-template>\n\n<ng-template #skeletonText let-width let-animated>\n <ion-skeleton-text [animated]=\"animated\" [style.width.%]=\"width || 60\"></ion-skeleton-text>\n</ng-template>\n", styles: [":host{--menu-item-margin: 0;--ion-item-background: transparent;--ion-item-divider-background: transparent;--ion-item-icon-color: var(--ion-color-primary-tint);--ion-item-text-color: var(--ion-color-primary-tint);--ion-item-background-selected: var(--ion-color-secondary100);--ion-item-text-color-selected: var(--ion-color-primary);--ion-item-icon-color-selected: var(--ion-color-primary);--ion-item-text-color-disable: var(--ion-color-medium);--ion-item-icon-color-disable: var(--ion-color-medium);--menu-item-background-selected-sub: var(--ion-color-secondary50);--menu-item-border-width-sub: 0 0 1px 0;--menu-item-margin-start-sub: 16px}@keyframes fadeinout{0%{opacity:0;display:none}75%{opacity:0;display:none}to{opacity:1;display:flex}}ion-menu ion-header ion-text{color:var(--ion-color-primary)}ion-menu ion-header ion-toolbar.user-toolbar{--ion-toolbar-height: 128px}ion-menu ion-header .user-avatar{-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;background-size:cover;background-repeat:no-repeat;background-position:center;background-color:var(--ion-color-secondary);border:solid 1px rgba(var(--ion-color-secondary-rgb),.5);overflow:hidden!important;font-size:var(--avatar-size, 60px)!important;line-height:var(--avatar-size, 60px);height:var(--avatar-size, 60px)!important;width:var(--avatar-size, 60px)!important;border-radius:50%;display:inline-block}ion-menu ion-header .user-avatar:hover{background-color:var(--ion-color-secondary-shade);border:solid 2px var(--ion-color-secondary-shade)}ion-menu ion-header .user-logo{text-align:right}ion-menu ion-header .user-logo img{max-width:120px;max-height:var(--avatar-size, 60px);width:auto}ion-menu ion-header .username{padding-top:0;margin-top:0;margin-bottom:0;width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}ion-menu ion-header button[mat-button]{padding:0;width:100%;justify-content:flex-start;text-align:start!important;color:var(--ion-item-text-color)}ion-menu ion-header button[mat-button].selected{background-color:var(--ion-item-background-selected);color:var(--ion-item-text-color-selected)!important}ion-menu ion-content ion-list{min-height:100%;display:flex;flex-direction:column;justify-content:flex-start}ion-menu ion-content ion-list ion-menu-toggle.flex-spacer{flex:1 1 auto;display:flex;flex-direction:column;justify-content:flex-end}ion-menu ion-content ion-list ion-item{display:block}ion-menu ion-content ion-list ion-item.primary{--ion-item-icon-color: var(--ion-color-primary-tint);--ion-item-text-color: var(--ion-color-primary-tint)}ion-menu ion-content ion-list ion-item.secondary{--ion-item-icon-color: var(--ion-color-secondary-tint);--ion-item-text-color: var(--ion-color-secondary-tint)}ion-menu ion-content ion-list ion-item.tertiary{--ion-item-icon-color: var(--ion-color-tertiary-tint);--ion-item-text-color: var(--ion-color-tertiary-tint)}ion-menu ion-content ion-list ion-item.danger{--ion-item-icon-color: var(--ion-color-danger-tint);--ion-item-text-color: var(--ion-color-danger-tint)}ion-menu ion-content ion-list ion-item.medium{--ion-item-icon-color: var(--ion-color-medium-shade);--ion-item-text-color: var(--ion-color-medium-shade)}ion-menu ion-content ion-list ion-item.dark{--ion-item-icon-color: var(--ion-color-dark-tint);--ion-item-text-color: var(--ion-color-dark-tint)}ion-menu ion-content ion-list ion-item mat-icon,ion-menu ion-content ion-list ion-item ion-icon{color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor}ion-menu ion-content ion-list ion-item ::ng-deep mat-icon svg{fill:currentColor;stroke:currentColor}ion-menu ion-content ion-list ion-item ion-text,ion-menu ion-content ion-list ion-item ion-label{color:var(--ion-item-text-color)!important}ion-menu ion-content ion-list ion-item span{margin-left:var(--menu-item-margin)}ion-menu ion-content ion-list ion-item.selected{background-color:var(--ion-item-background-selected)!important;--color-hover: var(--ion-item-background-selected) !important}ion-menu ion-content ion-list ion-item.selected mat-icon,ion-menu ion-content ion-list ion-item.selected ion-icon{color:var(--ion-item-icon-color-selected)!important;fill:currentColor;stroke:currentColor}ion-menu ion-content ion-list ion-item.selected ion-text,ion-menu ion-content ion-list ion-item.selected ion-label{color:var(--ion-item-text-color-selected)!important}ion-menu ion-content ion-list ion-item.selected:hover{--color-hover: var(--ion-item-background-selected) !important;--ion-item-icon-color-selected: var(--ion-item-icon-color) !important;--ion-item-text-color-selected: var(--ion-item-text-color) !important}ion-menu ion-content ion-list ion-item.selected.menu-item-sub{background-color:var(--menu-item-background-selected-sub)!important}ion-menu ion-content ion-list ion-item.menu-item-sub{--min-height: 34px;--padding-vertical: 8px;--padding-start: 8px;--padding-horizontal: 8px;--inner-border-width: var(--menu-item-border-width-sub);font-size:.8em;overflow:unset}ion-menu ion-content ion-list ion-item.menu-item-sub ion-icon[slot],ion-menu ion-content ion-list ion-item.menu-item-sub mat-icon[slot]{font-size:calc(var(--min-height) / 2 - 1px);height:calc(var(--min-height) / 2 - 1px);margin-top:calc(var(--padding-vertical) + 2px);margin-inline-end:calc(var(--padding-horizontal) + 2px);margin-bottom:calc(var(--padding-vertical) + 2px)}ion-menu ion-content ion-list ion-item.menu-item-sub ion-label{margin-top:var(--padding-vertical);margin-bottom:var(--padding-vertical)}ion-menu ion-content ion-list ion-item.menu-item-sub .item-inner{border-width:var(--inner-border-width);border-style:var(--border-style);border-color:var(--border-color);box-shadow:var(--inner-box-shadow)}ion-menu ion-content ion-list ion-item.menu-item-sub ion-button[slot=end]{-webkit-margin-start:unset;margin-inline-start:unset}ion-menu ion-content ion-list ion-item.menu-item-sub ion-button[slot=end] mat-icon[slot=icon-only],ion-menu ion-content ion-list ion-item.menu-item-sub ion-button[slot=end] ion-icon[slot=icon-only]{margin:0}ion-menu ion-content ion-list ion-item.menu-item-sub ion-button[slot=end].visible-hover{opacity:0;display:none;animation:fadeinout 1s linear 1 backwards}ion-menu ion-content ion-list ion-item.menu-item-sub:hover ion-button.visible-hover{opacity:1;display:flex}ion-menu ion-content ion-list .sub-menu-container{margin-inline-start:var(--menu-item-margin-start-sub, 16px)}ion-menu ion-footer{display:block}ion-menu ion-footer ion-toolbar{--ion-safe-area-bottom: 0}ion-menu ion-footer ion-toolbar ion-title{cursor:pointer;font-size:12pt;font-weight:400;text-align:center;padding:0 8px}ion-menu ion-footer ion-toolbar ion-button{--width: 40px}@media screen and (max-width: 767px){ion-menu ion-footer{display:none!important;visibility:hidden!important}}@media screen and (min-width: 768px){ion-menu ion-scroll{overflow-y:auto!important}ion-menu .user-avatar{font-size:var(--avatar-size, 80px)!important;line-height:var(--avatar-size, 80px);height:var(--avatar-size, 80px)!important;width:var(--avatar-size, 80px)!important}ion-menu .user-logo img{max-height:var(--avatar-size, 80px)}}\n"] }]
34451
34788
  }], ctorParameters: () => [{ type: AccountService }, { type: i2$1.NavController }, { type: i2$1.MenuController }, { type: i2$1.ModalController }, { type: i1$1.TranslateService }, { type: i0.ChangeDetectorRef }, { type: MenuService }, { type: i1$7.Router }, { type: Environment, decorators: [{
34452
34789
  type: Inject,
34453
34790
  args: [ENVIRONMENT]
@@ -35559,7 +35896,7 @@ class RegisterForm {
35559
35896
  this.form.enable();
35560
35897
  }
35561
35898
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RegisterForm, deps: [{ token: AccountService }, { token: AccountValidatorService }, { token: i1$3.UntypedFormBuilder }, { token: ENVIRONMENT }, { token: LocalSettingsService }], target: i0.ɵɵFactoryTarget.Component });
35562
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: RegisterForm, selector: "app-register-form", outputs: { onCancel: "onCancel", onSubmit: "onSubmit" }, viewQueries: [{ propertyName: "stepper", first: true, predicate: ["stepper"], descendants: true, static: true }], ngImport: i0, template: "<mat-horizontal-stepper linear #stepper=\"matHorizontalStepper\">\n <!-- Step 1: Username -->\n <mat-step [stepControl]=\"forms[0]\" *ngIf=\"forms[0].controls; let controls\">\n <form [formGroup]=\"forms[0]\" class=\"form-container ion-padding\" (keyup.enter)=\"slideNext($event)\">\n <ng-template matStepLabel>{{ 'REGISTER.STEP_EMAIL' | translate }}</ng-template>\n\n <!-- Email -->\n <mat-form-field>\n <mat-label>{{ 'USER.EMAIL' | translate }}</mat-label>\n <input\n matInput\n [appAutofocus]=\"true\"\n [autofocusDelay]=\"500\"\n formControlName=\"email\"\n autocomplete=\"off\"\n required\n />\n <mat-error *ngIf=\"controls.email.hasError('required') && controls.email.touched\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error\n *ngIf=\"controls.email.hasError('email') && !controls.email.hasError('required') && controls.email.touched\"\n >\n <span>{{ 'ERROR.FIELD_NOT_VALID_EMAIL' | translate }}</span>\n </mat-error>\n <mat-error *ngIf=\"controls.email.hasError('availability') && controls.email.touched\" translate>\n REGISTER.ERROR.EMAIL_ALREADY_REGISTERED\n </mat-error>\n </mat-form-field>\n\n <!-- Confirm Email -->\n <mat-form-field>\n <mat-label>{{ 'REGISTER.CONFIRM_EMAIL' | translate }}</mat-label>\n <input matInput autocomplete=\"off\" matFormFieldControl formControlName=\"confirmEmail\"/>\n <mat-error *ngIf=\"controls.confirmEmail.hasError('required') && controls.confirmEmail.touched\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"controls.confirmEmail.hasError('equals') && controls.confirmEmail.touched\">\n <span>{{ 'REGISTER.ERROR.NOT_SAME_EMAIL' | translate }}</span>\n </mat-error>\n </mat-form-field>\n </form>\n </mat-step>\n\n <!-- Step 2: Password -->\n <mat-step [stepControl]=\"forms[1]\" *ngIf=\"forms[1].controls; let controls\">\n <form [formGroup]=\"forms[1]\" class=\"form-container ion-padding\" (keyup.enter)=\"slideNext($event)\">\n <ng-template matStepLabel>{{ 'REGISTER.STEP_PASSWORD' | translate }}</ng-template>\n\n <!-- Password -->\n <mat-form-field>\n <mat-label>{{ 'USER.PASSWORD' | translate }}</mat-label>\n <input\n matInput\n formControlName=\"password\"\n autocomplete=\"section-red new-password\"\n [type]=\"showPwd ? 'text' : 'password'\"\n required\n />\n <button matSuffix mat-icon-button type=\"button\" (click)=\"showPwd = !showPwd\" tabindex=\"-1\">\n <mat-icon>{{ showPwd ? 'visibility_off' : 'visibility' }}</mat-icon>\n </button>\n <mat-error *ngIf=\"controls.password.hasError('required') && controls.password.touched\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"controls.password.hasError('minlength') && controls.password.touched\">\n <span>{{ 'ERROR.FIELD_MIN_LENGTH' | translate: {requiredLength: 8} }}</span>\n </mat-error>\n </mat-form-field>\n\n <!-- Confirm password -->\n <mat-form-field>\n <mat-label>{{ 'REGISTER.CONFIRM_PASSWORD' | translate }}</mat-label>\n <input\n matInput\n formControlName=\"confirmPassword\"\n autocomplete=\"section-red new-password\"\n [type]=\"showConfirmPwd ? 'text' : 'password'\"\n required\n />\n <button mat-icon-button matSuffix (click)=\"showConfirmPwd = !showConfirmPwd\" tabindex=\"-1\">\n <mat-icon>{{ showConfirmPwd ? 'visibility_off' : 'visibility' }}</mat-icon>\n </button>\n <mat-error *ngIf=\"controls.confirmPassword.hasError('required') && controls.confirmPassword.touched\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"controls.confirmPassword.hasError('equals') && controls.confirmPassword.touched\">\n <span>{{ 'REGISTER.ERROR.NOT_SAME_PASSWORD' | translate }}</span>\n </mat-error>\n </mat-form-field>\n </form>\n </mat-step>\n\n <!-- Step 3: user details -->\n <mat-step [stepControl]=\"forms[2]\" *ngIf=\"forms[2].controls; let controls\">\n <form\n [formGroup]=\"forms[2]\"\n (ngSubmit)=\"doSubmit($event)\"\n class=\"form-container ion-padding\"\n (keyup.enter)=\"doSubmit()\"\n >\n <ng-template matStepLabel>{{ 'REGISTER.STEP_USER_DETAILS' | translate }}</ng-template>\n\n <!-- error -->\n <ion-item *ngIf=\"error && !sending\" lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n\n <!-- Last name -->\n <mat-form-field>\n <mat-label>{{ 'USER.LAST_NAME' | translate }}</mat-label>\n <input matInput formControlName=\"lastName\" autocomplete=\"section-blue family-name\" required/>\n <mat-error *ngIf=\"controls.lastName.hasError('required') && controls.lastName.dirty\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"controls.lastName.hasError('minlength') && controls.lastName.dirty\">\n <span>{{ 'ERROR.FIELD_MIN_LENGTH' | translate: {requiredLength: 2} }}</span>\n </mat-error>\n </mat-form-field>\n\n <!-- First name -->\n <mat-form-field>\n <mat-label>{{ 'USER.FIRST_NAME' | translate }}</mat-label>\n <input matInput formControlName=\"firstName\" autocomplete=\"section-blue given-name\" required/>\n <mat-error *ngIf=\"controls.firstName.hasError('required') && controls.firstName.dirty\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"controls.firstName.hasError('minlength') && controls.firstName.dirty\">\n <span>{{ 'ERROR.FIELD_MIN_LENGTH' | translate: {requiredLength: 2} }}</span>\n </mat-error>\n </mat-form-field>\n\n <!-- Additional fields -->\n @for (field of additionalFields; track field.key) {\n @if (field.extra.registration?.description; as description) {\n <ion-text>\n <p [innerHTML]=\"description | translate\"></p>\n </ion-text>\n }\n <app-form-field\n [definition]=\"field\"\n [formControlName]=\"field.key\"\n [required]=\"field.extra?.registration.required ?? field.extra?.account.required ?? false\"\n ></app-form-field>\n }\n </form>\n </mat-step>\n</mat-horizontal-stepper>\n", styles: ["form ion-toolbar.toolbar-buttons .toolbar-content{text-align:right}form ion-toolbar.toolbar-buttons .toolbar-background{border:0;background:inherit!important}\n"], dependencies: [{ kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i3.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i6$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i11$1.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i12$1.MatStep, selector: "mat-step", inputs: ["color"], exportAs: ["matStep"] }, { kind: "directive", type: i12$1.MatStepLabel, selector: "[matStepLabel]" }, { kind: "component", type: i12$1.MatStepper, selector: "mat-stepper, mat-vertical-stepper, mat-horizontal-stepper, [matStepper]", inputs: ["disableRipple", "color", "labelPosition", "headerPosition", "animationDuration"], outputs: ["animationDone"], exportAs: ["matStepper", "matVerticalStepper", "matHorizontalStepper"] }, { kind: "directive", type: AutofocusDirective, selector: "[autofocus], input[appAutofocus]", inputs: ["appAutofocus", "autofocusDelay"] }, { kind: "component", type: AppFormField, selector: "app-form-field", inputs: ["definition", "readonly", "disabled", "formControl", "formControlName", "placeholder", "compact", "required", "hideRequiredMarker", "floatLabel", "label", "appearance", "subscriptSizing", "tabindex", "autofocus", "clearable", "chipColor", "class", "debug", "panelClass", "panelWidth"], outputs: ["keyup.enter"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }] });
35899
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: RegisterForm, selector: "app-register-form", outputs: { onCancel: "onCancel", onSubmit: "onSubmit" }, viewQueries: [{ propertyName: "stepper", first: true, predicate: ["stepper"], descendants: true, static: true }], ngImport: i0, template: "<mat-horizontal-stepper linear #stepper=\"matHorizontalStepper\">\n <!-- Step 1: Username -->\n <mat-step [stepControl]=\"forms[0]\" *ngIf=\"forms[0].controls; let controls\">\n <form [formGroup]=\"forms[0]\" class=\"form-container ion-padding\" (keyup.enter)=\"slideNext($event)\">\n <ng-template matStepLabel>{{ 'REGISTER.STEP_EMAIL' | translate }}</ng-template>\n\n <!-- Email -->\n <mat-form-field>\n <mat-label>{{ 'USER.EMAIL' | translate }}</mat-label>\n <input\n matInput\n [appAutofocus]=\"true\"\n [autofocusDelay]=\"500\"\n formControlName=\"email\"\n autocomplete=\"off\"\n required\n />\n <mat-error *ngIf=\"controls.email.hasError('required') && controls.email.touched\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error\n *ngIf=\"controls.email.hasError('email') && !controls.email.hasError('required') && controls.email.touched\"\n >\n <span>{{ 'ERROR.FIELD_NOT_VALID_EMAIL' | translate }}</span>\n </mat-error>\n <mat-error *ngIf=\"controls.email.hasError('availability') && controls.email.touched\" translate>\n REGISTER.ERROR.EMAIL_ALREADY_REGISTERED\n </mat-error>\n </mat-form-field>\n\n <!-- Confirm Email -->\n <mat-form-field>\n <mat-label>{{ 'REGISTER.CONFIRM_EMAIL' | translate }}</mat-label>\n <input matInput autocomplete=\"off\" matFormFieldControl formControlName=\"confirmEmail\"/>\n <mat-error *ngIf=\"controls.confirmEmail.hasError('required') && controls.confirmEmail.touched\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"controls.confirmEmail.hasError('equals') && controls.confirmEmail.touched\">\n <span>{{ 'REGISTER.ERROR.NOT_SAME_EMAIL' | translate }}</span>\n </mat-error>\n </mat-form-field>\n </form>\n </mat-step>\n\n <!-- Step 2: Password -->\n <mat-step [stepControl]=\"forms[1]\" *ngIf=\"forms[1].controls; let controls\">\n <form [formGroup]=\"forms[1]\" class=\"form-container ion-padding\" (keyup.enter)=\"slideNext($event)\">\n <ng-template matStepLabel>{{ 'REGISTER.STEP_PASSWORD' | translate }}</ng-template>\n\n <!-- Password -->\n <mat-form-field>\n <mat-label>{{ 'USER.PASSWORD' | translate }}</mat-label>\n <input\n matInput\n formControlName=\"password\"\n autocomplete=\"section-red new-password\"\n [type]=\"showPwd ? 'text' : 'password'\"\n required\n />\n <button matSuffix mat-icon-button type=\"button\" (click)=\"showPwd = !showPwd\" tabindex=\"-1\">\n <mat-icon>{{ showPwd ? 'visibility_off' : 'visibility' }}</mat-icon>\n </button>\n <mat-error *ngIf=\"controls.password.hasError('required') && controls.password.touched\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"controls.password.hasError('minlength') && controls.password.touched\">\n <span>{{ 'ERROR.FIELD_MIN_LENGTH' | translate: {requiredLength: 8} }}</span>\n </mat-error>\n </mat-form-field>\n\n <!-- Confirm password -->\n <mat-form-field>\n <mat-label>{{ 'REGISTER.CONFIRM_PASSWORD' | translate }}</mat-label>\n <input\n matInput\n formControlName=\"confirmPassword\"\n autocomplete=\"section-red new-password\"\n [type]=\"showConfirmPwd ? 'text' : 'password'\"\n required\n />\n <button mat-icon-button matSuffix (click)=\"showConfirmPwd = !showConfirmPwd\" tabindex=\"-1\">\n <mat-icon>{{ showConfirmPwd ? 'visibility_off' : 'visibility' }}</mat-icon>\n </button>\n <mat-error *ngIf=\"controls.confirmPassword.hasError('required') && controls.confirmPassword.touched\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"controls.confirmPassword.hasError('equals') && controls.confirmPassword.touched\">\n <span>{{ 'REGISTER.ERROR.NOT_SAME_PASSWORD' | translate }}</span>\n </mat-error>\n </mat-form-field>\n </form>\n </mat-step>\n\n <!-- Step 3: user details -->\n <mat-step [stepControl]=\"forms[2]\" *ngIf=\"forms[2].controls; let controls\">\n <form\n [formGroup]=\"forms[2]\"\n (ngSubmit)=\"doSubmit($event)\"\n class=\"form-container ion-padding\"\n (keyup.enter)=\"doSubmit()\"\n >\n <ng-template matStepLabel>{{ 'REGISTER.STEP_USER_DETAILS' | translate }}</ng-template>\n\n <!-- error -->\n <ion-item *ngIf=\"error && !sending\" lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n\n <!-- Last name -->\n <mat-form-field>\n <mat-label>{{ 'USER.LAST_NAME' | translate }}</mat-label>\n <input matInput formControlName=\"lastName\" autocomplete=\"section-blue family-name\" required/>\n <mat-error *ngIf=\"controls.lastName.hasError('required') && controls.lastName.dirty\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"controls.lastName.hasError('minlength') && controls.lastName.dirty\">\n <span>{{ 'ERROR.FIELD_MIN_LENGTH' | translate: {requiredLength: 2} }}</span>\n </mat-error>\n </mat-form-field>\n\n <!-- First name -->\n <mat-form-field>\n <mat-label>{{ 'USER.FIRST_NAME' | translate }}</mat-label>\n <input matInput formControlName=\"firstName\" autocomplete=\"section-blue given-name\" required/>\n <mat-error *ngIf=\"controls.firstName.hasError('required') && controls.firstName.dirty\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"controls.firstName.hasError('minlength') && controls.firstName.dirty\">\n <span>{{ 'ERROR.FIELD_MIN_LENGTH' | translate: {requiredLength: 2} }}</span>\n </mat-error>\n </mat-form-field>\n\n <!-- Additional fields -->\n @for (field of additionalFields; track field.key) {\n @if (field.extra.registration?.description; as description) {\n <ion-text>\n <p [innerHTML]=\"description | translate\"></p>\n </ion-text>\n }\n <app-form-field\n [definition]=\"field\"\n [formControlName]=\"field.key\"\n [required]=\"field.extra?.registration.required ?? field.extra?.account.required ?? false\"\n ></app-form-field>\n }\n </form>\n </mat-step>\n</mat-horizontal-stepper>\n", styles: ["form ion-toolbar.toolbar-buttons .toolbar-content{text-align:right}form ion-toolbar.toolbar-buttons .toolbar-background{border:0;background:inherit!important}\n"], dependencies: [{ kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i3.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i6$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i11$1.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i12$1.MatStep, selector: "mat-step", inputs: ["color"], exportAs: ["matStep"] }, { kind: "directive", type: i12$1.MatStepLabel, selector: "[matStepLabel]" }, { kind: "component", type: i12$1.MatStepper, selector: "mat-stepper, mat-vertical-stepper, mat-horizontal-stepper, [matStepper]", inputs: ["disableRipple", "color", "labelPosition", "headerPosition", "animationDuration"], outputs: ["animationDone"], exportAs: ["matStepper", "matVerticalStepper", "matHorizontalStepper"] }, { kind: "directive", type: AutofocusDirective, selector: "[autofocus], input[appAutofocus]", inputs: ["appAutofocus", "autofocusDelay"] }, { kind: "component", type: AppFormField, selector: "app-form-field", inputs: ["definition", "readonly", "disabled", "formControl", "formControlName", "placeholder", "compact", "required", "hideRequiredMarker", "floatLabel", "label", "appearance", "subscriptSizing", "tabindex", "autofocus", "clearable", "chipColor", "class", "debug", "panelClass", "panelWidth"], outputs: ["keyup.enter", "definitionChanged"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }] });
35563
35900
  }
35564
35901
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RegisterForm, decorators: [{
35565
35902
  type: Component,
@@ -38452,11 +38789,11 @@ class FeedsComponent {
38452
38789
  }
38453
38790
  }
38454
38791
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FeedsComponent, deps: [{ token: LocalSettingsService }, { token: ENVIRONMENT }, { token: APP_FEED_SERVICE, optional: true }, { token: AccountService }, { token: i2$1.AlertController }], target: i0.ɵɵFactoryTarget.Component });
38455
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: FeedsComponent, selector: "app-feed", inputs: { debug: "debug", mobile: "mobile", showHeader: "showHeader", showReadMoreButton: "showReadMoreButton", headerColor: "headerColor", cardColor: "cardColor", shape: "shape", class: "class", itemId: "itemId", filterItem: "filterItem", feeds: "feeds", urls: "urls", maxAgeInMonths: "maxAgeInMonths", maxContentLength: "maxContentLength" }, outputs: { editItem: "editItem", deleteItem: "deleteItem" }, host: { properties: { "class": "this.hostClass" } }, providers: [RxState], viewQueries: [{ propertyName: "modal", first: true, predicate: ["modal"], descendants: true }], ngImport: i0, template: "<!-- debug -->\n@if (debug) {\n <app-debug [title]=\"'Feed'\">\n <p>\n hasFeeds?: {{ hasFeeds$ | async }}\n <br />\n urls: {{ urls$ | async | json }}\n <br />\n shape: {{ shape }}\n </p>\n </app-debug>\n}\n\n@let feeds = feeds$ | async;\n@let userId = userId$ | async;\n\n@if (feeds | isNotEmptyArray) {\n <!-- top header -->\n @if (showHeader && (feeds | arrayFirst); as feed) {\n <ion-item lines=\"none\" [color]=\"headerColor\" class=\"feed-header shape-{{ shape }}\">\n <ion-icon slot=\"start\" name=\"megaphone\"></ion-icon>\n <ion-label>\n <b>{{ feed.title || ('SOCIAL.FEED.NEWS' | translate) }}</b>\n </ion-label>\n <ion-button slot=\"end\" fill=\"clear\" (click)=\"openFeedHome()\" shape=\"\">\n <ion-label translate>SOCIAL.FEED.SHOW_ALL_FEED</ion-label>\n <ion-icon slot=\"end\" name=\"chevron-forward-outline\"></ion-icon>\n </ion-button>\n </ion-item>\n }\n\n <div class=\"feed-content shape-{{ shape }} ion-no-padding\" [class.has-header]=\"showHeader\">\n <!-- feeds -->\n @for (feed of feeds; track feed.feed_url; let firstFeed = $first; let lastFeed = $last) {\n <!-- items -->\n @for (item of feed.items | arrayFilter: filterItem; track item.id; let firstItem = $first; let lastItem = $last) {\n <ion-card\n [class.first]=\"firstFeed && firstItem\"\n [class.last]=\"lastFeed && lastItem\"\n [color]=\"cardColor !== 'light-transparent' ? cardColor : undefined\"\n class=\"feed-item-card\"\n >\n <ion-card-header>\n <ion-card-subtitle style=\"vertical-align: middle\">\n <!-- Authors -->\n @for (author of item.authors || feed.authors; track author) {\n @if (author.name || author.avatar) {\n <ion-chip (click)=\"openUrl(author.url)\" tappable>\n @if (author.avatar) {\n <ion-avatar>\n <ion-img [src]=\"author.avatar\" [alt]=\"author.name\"></ion-img>\n </ion-avatar>\n }\n @if (author.name) {\n <ion-label class=\"author\">{{ author.name }}</ion-label>\n }\n </ion-chip>\n }\n }\n <ion-note class=\"ion-float-end\">\n <small>{{ item.date_published | dateFromNow }}</small>\n </ion-note>\n </ion-card-subtitle>\n\n <!-- title -->\n <ion-card-title (click)=\"openFeedItem(item, feed)\" tappable>{{ item?.title }}</ion-card-title>\n\n <!-- tags -->\n @let tags = item | map: getTags;\n @if (tags | isNotEmptyArray) {\n <ion-text class=\"tags\">\n @for (tag of tags; track tag; let last = $last) {\n <a (click)=\"openTag(feed, tag)\" tappable>\n <ion-text>#{{ tag }}</ion-text>\n </a>\n @if (!last) {\n &nbsp;\n }\n }\n </ion-text>\n }\n </ion-card-header>\n\n <!-- Feed content -->\n <ion-card-content>\n <ion-text [feed]=\"item.url || feed.feed_url\">\n @if (item.content_html) {\n <p [innerHTML]=\"item.content_html\"></p>\n } @else if (item.content_text) {\n <p>\n <markdown [data]=\"item.content_text\" emoji></markdown>\n </p>\n }\n </ion-text>\n </ion-card-content>\n\n @let editable = canEditItem(item, userId, feed);\n @if (editable || showReadMoreButton) {\n @if (editable) {\n\n <!-- Delete button (visible hover)-->\n <button\n mat-icon-button\n (click)=\"onDeleteItem($event, item)\"\n class=\"visible-hover ion-float-start\"\n [title]=\"!showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n >\n <mat-icon>delete</mat-icon>\n </button>\n <!-- Edit button (visible hover) -->\n <ion-button\n (click)=\"onEditItem($event, item)\"\n class=\"visible-hover ion-float-start\" fill=\"clear\"\n >\n <mat-icon slot=\"start\">edit</mat-icon>\n<!-- <ion-icon name=\"pencil\" slot=\"start\"></ion-icon>-->\n <ion-label translate>COMMON.BTN_EDIT</ion-label>\n </ion-button>\n }\n @if (showReadMoreButton) {\n <ion-button (click)=\"openFeedItem(item, feed)\" class=\"ion-float-end\" fill=\"clear\">\n <ion-label>{{ (item.truncated ? 'SOCIAL.FEED.READ_MORE' : 'COMMON.BTN_SHOW') | translate }}</ion-label>\n <ion-icon slot=\"end\" name=\"chevron-forward-outline\"></ion-icon>\n </ion-button>\n }\n }\n </ion-card>\n }\n }\n </div>\n}\n\n<!-- Details modal -->\n<ion-modal #modal [showBackdrop]=\"false\"\n class=\"stack-modal\" [class.modal-large]=\"!mobile\">\n <ng-template>\n <ion-header>\n <ion-toolbar color=\"secondary\">\n <ion-title>{{ 'SOCIAL.FEED.NEWS' | translate }}</ion-title>\n <ion-buttons slot=\"end\">\n <ion-button (click)=\"modal.dismiss()\">\n <ion-icon slot=\"icon-only\" name=\"close\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </ion-header>\n <ion-content>\n <div class=\"ion-padding\">\n <app-feed\n [urls]=\"urls\"\n [showHeader]=\"false\"\n [showReadMoreButton]=\"false\"\n [maxContentLength]=\"-1\"\n [maxAgeInMonths]=\"-1\"\n [itemId]=\"modalItemId\"\n cardColor=\"light\"\n [debug]=\"debug\"\n ></app-feed>\n </div>\n </ion-content>\n </ng-template>\n</ion-modal>\n", styles: [":host{display:block;height:calc(100% - 10px);max-height:fit-content;overflow:hidden;--feed-header-height: 48px;-webkit-box-shadow:0 3px 1px -2px rgba(0,0,0,.2),0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12);box-shadow:0 3px 1px -2px #0003,0 2px 2px #00000024,0 1px 5px #0000001f;-webkit-margin-start:10px;margin-inline-start:10px;-webkit-margin-end:10px;margin-inline-end:10px;margin-top:0;margin-bottom:10px;--feed-border-radius: 4px;border-radius:var(--feed-border-radius);--ion-card-background: rgba(var(--ion-background-color-rgb), .6)}:host.shape-round{--feed-border-radius: 12px}ion-button{text-transform:unset;--color: rgba(var(--ion-color-contrast-rgb), .7)}ion-item.feed-header{height:var(--feed-header-height);margin:0;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);border-radius:var(--feed-border-radius) var(--feed-border-radius) 0 0}ion-item.feed-header ion-icon[slot=start]{-webkit-margin-end:8px;margin-inline-end:8px}.feed-content{height:auto;overflow-y:auto;--margin-bottom: 8px}.feed-content.has-header{height:calc(100% - var(--feed-header-height, 0))}.feed-content ion-card.feed-item-card{--top-radius: 4px;--bottom-radius: 4px;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);margin:0 0 var(--margin-bottom) 0;border-radius:var(--top-radius) var(--top-radius) var(--bottom-radius) var(--bottom-radius);--ion-card-color-contrast-rgb: var(--ion-color-contrast-rgb, var(--ion-color-dark-rgb, 0, 0, 0));--color: rgba(var(--ion-card-color-contrast-rgb), .87)}.feed-content ion-card.feed-item-card.first{--top-radius: 0}.feed-content ion-card.feed-item-card.last{--margin-bottom: 0;--bottom-radius: 0}.feed-content ion-card.feed-item-card ion-card-header ion-card-title{--color: rgba(var(--ion-card-color-contrast-rgb), .87)}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-chip{--background: transparent;--border-color: transparent;--border-width: 0}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-chip ion-avatar{--color: rgba(var(--ion-card-color-contrast-rgb), .6);border:1px solid var(--color)}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-chip ion-label{--color: rgba(var(--ion-card-color-contrast-rgb), .8);color:var(--color)}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-note{--color: rgba(var(--ion-card-color-contrast-rgb), .6);color:var(--color)}.feed-content ion-card.feed-item-card ion-card-header .tags{display:inline}.feed-content ion-card.feed-item-card ion-card-header .tags a{color:rgba(var(--ion-card-color-contrast-rgb),.6)!important}.feed-content ion-card.feed-item-card ion-card-content ion-text ::ng-deep img{max-width:100%;height:auto!important}.feed-content ion-card.feed-item-card .visible-hover{opacity:0;transition:opacity .2s ease-in-out}.feed-content ion-card.feed-item-card:hover .visible-hover{opacity:1}\n"], dependencies: [{ kind: "component", type: i2$1.IonAvatar, selector: "ion-avatar" }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$1.IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: i2$1.IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: i2$1.IonCardHeader, selector: "ion-card-header", inputs: ["color", "mode", "translucent"] }, { kind: "component", type: i2$1.IonCardSubtitle, selector: "ion-card-subtitle", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonCardTitle, selector: "ion-card-title", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonChip, selector: "ion-chip", inputs: ["color", "disabled", "mode", "outline"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonImg, selector: "ion-img", inputs: ["alt", "src"] }, { kind: "component", type: i2$1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: i2$1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonModal, selector: "ion-modal" }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: i4$2.MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }, { kind: "component", type: i6$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i11$1.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "directive", type: i1$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: DebugComponent, selector: "app-debug", inputs: ["titlePrefix", "title", "enable", "expanded"] }, { kind: "directive", type: MarkdownDirective, selector: "markdown,[markdown]" }, { kind: "component", type: FeedsComponent, selector: "app-feed", inputs: ["debug", "mobile", "showHeader", "showReadMoreButton", "headerColor", "cardColor", "shape", "class", "itemId", "filterItem", "feeds", "urls", "maxAgeInMonths", "maxContentLength"], outputs: ["editItem", "deleteItem"] }, { kind: "directive", type: FeedDirective, selector: "feed,[feed]", inputs: ["feed"] }, { kind: "pipe", type: i3$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.JsonPipe, name: "json" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }, { kind: "pipe", type: DateFromNowPipe, name: "dateFromNow" }, { kind: "pipe", type: NotEmptyArrayPipe, name: "isNotEmptyArray" }, { kind: "pipe", type: ArrayFirstPipe, name: "arrayFirst" }, { kind: "pipe", type: ArrayFilterPipe, name: "arrayFilter" }, { kind: "pipe", type: MapPipe, name: "map" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
38792
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: FeedsComponent, selector: "app-feed", inputs: { debug: "debug", mobile: "mobile", showHeader: "showHeader", showReadMoreButton: "showReadMoreButton", headerColor: "headerColor", cardColor: "cardColor", shape: "shape", class: "class", itemId: "itemId", filterItem: "filterItem", feeds: "feeds", urls: "urls", maxAgeInMonths: "maxAgeInMonths", maxContentLength: "maxContentLength" }, outputs: { editItem: "editItem", deleteItem: "deleteItem" }, host: { properties: { "class": "this.hostClass" } }, providers: [RxState], viewQueries: [{ propertyName: "modal", first: true, predicate: ["modal"], descendants: true }], ngImport: i0, template: "<!-- debug -->\n@if (debug) {\n <app-debug [title]=\"'Feed'\">\n <p>\n hasFeeds?: {{ hasFeeds$ | async }}\n <br />\n urls: {{ urls$ | async | json }}\n <br />\n shape: {{ shape }}\n </p>\n </app-debug>\n}\n\n@let feeds = feeds$ | async;\n@let userId = userId$ | async;\n\n@if (feeds | isNotEmptyArray) {\n <!-- top header -->\n @if (showHeader && (feeds | arrayFirst); as feed) {\n <ion-item lines=\"none\" [color]=\"headerColor\" class=\"feed-header shape-{{ shape }}\">\n <ion-icon slot=\"start\" name=\"megaphone\"></ion-icon>\n <ion-label>\n <b>{{ feed.title || ('SOCIAL.FEED.NEWS' | translate) }}</b>\n </ion-label>\n <ion-button slot=\"end\" fill=\"clear\" (click)=\"openFeedHome()\" shape=\"\">\n <ion-label translate>SOCIAL.FEED.SHOW_ALL_FEED</ion-label>\n <ion-icon slot=\"end\" name=\"chevron-forward-outline\"></ion-icon>\n </ion-button>\n </ion-item>\n }\n\n <div class=\"feed-content shape-{{ shape }} ion-no-padding\" [class.has-header]=\"showHeader\">\n <!-- feeds -->\n @for (feed of feeds; track feed.feed_url; let firstFeed = $first; let lastFeed = $last) {\n <!-- items -->\n @for (item of feed.items | arrayFilter: filterItem; track item.id; let firstItem = $first; let lastItem = $last) {\n <ion-card\n [class.first]=\"firstFeed && firstItem\"\n [class.last]=\"lastFeed && lastItem\"\n [color]=\"cardColor !== 'light-transparent' ? cardColor : undefined\"\n class=\"feed-item-card\"\n >\n <ion-card-header>\n <ion-card-subtitle style=\"vertical-align: middle\">\n <!-- Authors -->\n @for (author of item.authors || feed.authors; track author) {\n @if (author.name || author.avatar) {\n <ion-chip (click)=\"openUrl(author.url)\" tappable>\n @if (author.avatar) {\n <ion-avatar>\n <ion-img [src]=\"author.avatar\" [alt]=\"author.name\"></ion-img>\n </ion-avatar>\n }\n @if (author.name) {\n <ion-label class=\"author\">{{ author.name }}</ion-label>\n }\n </ion-chip>\n }\n }\n <ion-note class=\"ion-float-end\">\n <small>{{ item.date_published | dateFromNow }}</small>\n </ion-note>\n </ion-card-subtitle>\n\n <!-- title -->\n <ion-card-title (click)=\"openFeedItem(item, feed)\" tappable>{{ item?.title }}</ion-card-title>\n\n <!-- tags -->\n @let tags = item | map: getTags;\n @if (tags | isNotEmptyArray) {\n <ion-text class=\"tags\">\n @for (tag of tags; track tag; let last = $last) {\n <a (click)=\"openTag(feed, tag)\" tappable>\n <ion-text>#{{ tag }}</ion-text>\n </a>\n @if (!last) {\n &nbsp;\n }\n }\n </ion-text>\n }\n </ion-card-header>\n\n <!-- Feed content -->\n <ion-card-content>\n <ion-text [feed]=\"item.url || feed.feed_url\">\n @if (item.content_html) {\n <p [innerHTML]=\"item.content_html\"></p>\n } @else if (item.content_text) {\n <p>\n <markdown [data]=\"item.content_text\" emoji></markdown>\n </p>\n }\n </ion-text>\n </ion-card-content>\n\n @let editable = canEditItem(item, userId, feed);\n @if (editable || showReadMoreButton) {\n @if (editable) {\n\n <!-- Delete button (visible hover)-->\n <button\n mat-icon-button\n (click)=\"onDeleteItem($event, item)\"\n class=\"visible-hover ion-float-start\"\n [title]=\"!showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n >\n <mat-icon>delete</mat-icon>\n </button>\n <!-- Edit button (visible hover) -->\n <ion-button\n (click)=\"onEditItem($event, item)\"\n class=\"visible-hover ion-float-start\" fill=\"clear\"\n >\n <mat-icon slot=\"start\">edit</mat-icon>\n<!-- <ion-icon name=\"pencil\" slot=\"start\"></ion-icon>-->\n <ion-label translate>COMMON.BTN_EDIT</ion-label>\n </ion-button>\n }\n @if (showReadMoreButton) {\n <ion-button (click)=\"openFeedItem(item, feed)\" class=\"ion-float-end\" fill=\"clear\">\n <ion-label>{{ (item.truncated ? 'SOCIAL.FEED.READ_MORE' : 'COMMON.BTN_SHOW') | translate }}</ion-label>\n <ion-icon slot=\"end\" name=\"chevron-forward-outline\"></ion-icon>\n </ion-button>\n }\n }\n </ion-card>\n }\n }\n </div>\n}\n\n<!-- Details modal -->\n<ion-modal #modal [showBackdrop]=\"false\"\n class=\"stack-modal\" [class.modal-large]=\"!mobile\">\n <ng-template>\n <ion-header>\n <ion-toolbar color=\"secondary\">\n <ion-title>{{ 'SOCIAL.FEED.NEWS' | translate }}</ion-title>\n <ion-buttons slot=\"end\">\n <ion-button (click)=\"modal.dismiss()\">\n <ion-icon slot=\"icon-only\" name=\"close\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </ion-header>\n <ion-content>\n <div class=\"ion-padding\">\n <app-feed\n [urls]=\"urls\"\n [showHeader]=\"false\"\n [showReadMoreButton]=\"false\"\n [maxContentLength]=\"-1\"\n [maxAgeInMonths]=\"-1\"\n [itemId]=\"modalItemId\"\n cardColor=\"light\"\n [debug]=\"debug\"\n ></app-feed>\n </div>\n </ion-content>\n </ng-template>\n</ion-modal>\n", styles: [":host{display:block;height:calc(100% - 10px);max-height:fit-content;overflow:hidden;--feed-header-height: 48px;-webkit-box-shadow:0 3px 1px -2px rgba(0,0,0,.2),0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12);box-shadow:0 3px 1px -2px rgba(0,0,0,.2),0 2px 2px rgba(0,0,0,.14),0 1px 5px rgba(0,0,0,.12);-webkit-margin-start:10px;margin-inline-start:10px;-webkit-margin-end:10px;margin-inline-end:10px;margin-top:0;margin-bottom:10px;--feed-border-radius: 4px;border-radius:var(--feed-border-radius);--ion-card-background: rgba(var(--ion-background-color-rgb), .6)}:host.shape-round{--feed-border-radius: 12px}ion-button{text-transform:unset;--color: rgba(var(--ion-color-contrast-rgb), .7)}ion-item.feed-header{height:var(--feed-header-height);margin:0;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);border-radius:var(--feed-border-radius) var(--feed-border-radius) 0 0}ion-item.feed-header ion-icon[slot=start]{-webkit-margin-end:8px;margin-inline-end:8px}.feed-content{height:auto;overflow-y:auto;--margin-bottom: 8px}.feed-content.has-header{height:calc(100% - var(--feed-header-height, 0))}.feed-content ion-card.feed-item-card{--top-radius: 4px;--bottom-radius: 4px;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);margin:0 0 var(--margin-bottom) 0;border-radius:var(--top-radius) var(--top-radius) var(--bottom-radius) var(--bottom-radius);--ion-card-color-contrast-rgb: var(--ion-color-contrast-rgb, var(--ion-color-dark-rgb, 0, 0, 0));--color: rgba(var(--ion-card-color-contrast-rgb), .87)}.feed-content ion-card.feed-item-card.first{--top-radius: 0}.feed-content ion-card.feed-item-card.last{--margin-bottom: 0;--bottom-radius: 0}.feed-content ion-card.feed-item-card ion-card-header ion-card-title{--color: rgba(var(--ion-card-color-contrast-rgb), .87)}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-chip{--background: transparent;--border-color: transparent;--border-width: 0}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-chip ion-avatar{--color: rgba(var(--ion-card-color-contrast-rgb), .6);border:1px solid var(--color)}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-chip ion-label{--color: rgba(var(--ion-card-color-contrast-rgb), .8);color:var(--color)}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-note{--color: rgba(var(--ion-card-color-contrast-rgb), .6);color:var(--color)}.feed-content ion-card.feed-item-card ion-card-header .tags{display:inline}.feed-content ion-card.feed-item-card ion-card-header .tags a{color:rgba(var(--ion-card-color-contrast-rgb),.6)!important}.feed-content ion-card.feed-item-card ion-card-content ion-text ::ng-deep img{max-width:100%;height:auto!important}.feed-content ion-card.feed-item-card .visible-hover{opacity:0;transition:opacity .2s ease-in-out}.feed-content ion-card.feed-item-card:hover .visible-hover{opacity:1}\n"], dependencies: [{ kind: "component", type: i2$1.IonAvatar, selector: "ion-avatar" }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$1.IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: i2$1.IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: i2$1.IonCardHeader, selector: "ion-card-header", inputs: ["color", "mode", "translucent"] }, { kind: "component", type: i2$1.IonCardSubtitle, selector: "ion-card-subtitle", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonCardTitle, selector: "ion-card-title", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonChip, selector: "ion-chip", inputs: ["color", "disabled", "mode", "outline"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonImg, selector: "ion-img", inputs: ["alt", "src"] }, { kind: "component", type: i2$1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: i2$1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonModal, selector: "ion-modal" }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: i4$2.MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }, { kind: "component", type: i6$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i11$1.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "directive", type: i1$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: DebugComponent, selector: "app-debug", inputs: ["titlePrefix", "title", "enable", "expanded"] }, { kind: "directive", type: MarkdownDirective, selector: "markdown,[markdown]" }, { kind: "component", type: FeedsComponent, selector: "app-feed", inputs: ["debug", "mobile", "showHeader", "showReadMoreButton", "headerColor", "cardColor", "shape", "class", "itemId", "filterItem", "feeds", "urls", "maxAgeInMonths", "maxContentLength"], outputs: ["editItem", "deleteItem"] }, { kind: "directive", type: FeedDirective, selector: "feed,[feed]", inputs: ["feed"] }, { kind: "pipe", type: i3$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.JsonPipe, name: "json" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }, { kind: "pipe", type: DateFromNowPipe, name: "dateFromNow" }, { kind: "pipe", type: NotEmptyArrayPipe, name: "isNotEmptyArray" }, { kind: "pipe", type: ArrayFirstPipe, name: "arrayFirst" }, { kind: "pipe", type: ArrayFilterPipe, name: "arrayFilter" }, { kind: "pipe", type: MapPipe, name: "map" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
38456
38793
  }
38457
38794
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FeedsComponent, decorators: [{
38458
38795
  type: Component,
38459
- args: [{ selector: 'app-feed', providers: [RxState], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- debug -->\n@if (debug) {\n <app-debug [title]=\"'Feed'\">\n <p>\n hasFeeds?: {{ hasFeeds$ | async }}\n <br />\n urls: {{ urls$ | async | json }}\n <br />\n shape: {{ shape }}\n </p>\n </app-debug>\n}\n\n@let feeds = feeds$ | async;\n@let userId = userId$ | async;\n\n@if (feeds | isNotEmptyArray) {\n <!-- top header -->\n @if (showHeader && (feeds | arrayFirst); as feed) {\n <ion-item lines=\"none\" [color]=\"headerColor\" class=\"feed-header shape-{{ shape }}\">\n <ion-icon slot=\"start\" name=\"megaphone\"></ion-icon>\n <ion-label>\n <b>{{ feed.title || ('SOCIAL.FEED.NEWS' | translate) }}</b>\n </ion-label>\n <ion-button slot=\"end\" fill=\"clear\" (click)=\"openFeedHome()\" shape=\"\">\n <ion-label translate>SOCIAL.FEED.SHOW_ALL_FEED</ion-label>\n <ion-icon slot=\"end\" name=\"chevron-forward-outline\"></ion-icon>\n </ion-button>\n </ion-item>\n }\n\n <div class=\"feed-content shape-{{ shape }} ion-no-padding\" [class.has-header]=\"showHeader\">\n <!-- feeds -->\n @for (feed of feeds; track feed.feed_url; let firstFeed = $first; let lastFeed = $last) {\n <!-- items -->\n @for (item of feed.items | arrayFilter: filterItem; track item.id; let firstItem = $first; let lastItem = $last) {\n <ion-card\n [class.first]=\"firstFeed && firstItem\"\n [class.last]=\"lastFeed && lastItem\"\n [color]=\"cardColor !== 'light-transparent' ? cardColor : undefined\"\n class=\"feed-item-card\"\n >\n <ion-card-header>\n <ion-card-subtitle style=\"vertical-align: middle\">\n <!-- Authors -->\n @for (author of item.authors || feed.authors; track author) {\n @if (author.name || author.avatar) {\n <ion-chip (click)=\"openUrl(author.url)\" tappable>\n @if (author.avatar) {\n <ion-avatar>\n <ion-img [src]=\"author.avatar\" [alt]=\"author.name\"></ion-img>\n </ion-avatar>\n }\n @if (author.name) {\n <ion-label class=\"author\">{{ author.name }}</ion-label>\n }\n </ion-chip>\n }\n }\n <ion-note class=\"ion-float-end\">\n <small>{{ item.date_published | dateFromNow }}</small>\n </ion-note>\n </ion-card-subtitle>\n\n <!-- title -->\n <ion-card-title (click)=\"openFeedItem(item, feed)\" tappable>{{ item?.title }}</ion-card-title>\n\n <!-- tags -->\n @let tags = item | map: getTags;\n @if (tags | isNotEmptyArray) {\n <ion-text class=\"tags\">\n @for (tag of tags; track tag; let last = $last) {\n <a (click)=\"openTag(feed, tag)\" tappable>\n <ion-text>#{{ tag }}</ion-text>\n </a>\n @if (!last) {\n &nbsp;\n }\n }\n </ion-text>\n }\n </ion-card-header>\n\n <!-- Feed content -->\n <ion-card-content>\n <ion-text [feed]=\"item.url || feed.feed_url\">\n @if (item.content_html) {\n <p [innerHTML]=\"item.content_html\"></p>\n } @else if (item.content_text) {\n <p>\n <markdown [data]=\"item.content_text\" emoji></markdown>\n </p>\n }\n </ion-text>\n </ion-card-content>\n\n @let editable = canEditItem(item, userId, feed);\n @if (editable || showReadMoreButton) {\n @if (editable) {\n\n <!-- Delete button (visible hover)-->\n <button\n mat-icon-button\n (click)=\"onDeleteItem($event, item)\"\n class=\"visible-hover ion-float-start\"\n [title]=\"!showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n >\n <mat-icon>delete</mat-icon>\n </button>\n <!-- Edit button (visible hover) -->\n <ion-button\n (click)=\"onEditItem($event, item)\"\n class=\"visible-hover ion-float-start\" fill=\"clear\"\n >\n <mat-icon slot=\"start\">edit</mat-icon>\n<!-- <ion-icon name=\"pencil\" slot=\"start\"></ion-icon>-->\n <ion-label translate>COMMON.BTN_EDIT</ion-label>\n </ion-button>\n }\n @if (showReadMoreButton) {\n <ion-button (click)=\"openFeedItem(item, feed)\" class=\"ion-float-end\" fill=\"clear\">\n <ion-label>{{ (item.truncated ? 'SOCIAL.FEED.READ_MORE' : 'COMMON.BTN_SHOW') | translate }}</ion-label>\n <ion-icon slot=\"end\" name=\"chevron-forward-outline\"></ion-icon>\n </ion-button>\n }\n }\n </ion-card>\n }\n }\n </div>\n}\n\n<!-- Details modal -->\n<ion-modal #modal [showBackdrop]=\"false\"\n class=\"stack-modal\" [class.modal-large]=\"!mobile\">\n <ng-template>\n <ion-header>\n <ion-toolbar color=\"secondary\">\n <ion-title>{{ 'SOCIAL.FEED.NEWS' | translate }}</ion-title>\n <ion-buttons slot=\"end\">\n <ion-button (click)=\"modal.dismiss()\">\n <ion-icon slot=\"icon-only\" name=\"close\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </ion-header>\n <ion-content>\n <div class=\"ion-padding\">\n <app-feed\n [urls]=\"urls\"\n [showHeader]=\"false\"\n [showReadMoreButton]=\"false\"\n [maxContentLength]=\"-1\"\n [maxAgeInMonths]=\"-1\"\n [itemId]=\"modalItemId\"\n cardColor=\"light\"\n [debug]=\"debug\"\n ></app-feed>\n </div>\n </ion-content>\n </ng-template>\n</ion-modal>\n", styles: [":host{display:block;height:calc(100% - 10px);max-height:fit-content;overflow:hidden;--feed-header-height: 48px;-webkit-box-shadow:0 3px 1px -2px rgba(0,0,0,.2),0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12);box-shadow:0 3px 1px -2px #0003,0 2px 2px #00000024,0 1px 5px #0000001f;-webkit-margin-start:10px;margin-inline-start:10px;-webkit-margin-end:10px;margin-inline-end:10px;margin-top:0;margin-bottom:10px;--feed-border-radius: 4px;border-radius:var(--feed-border-radius);--ion-card-background: rgba(var(--ion-background-color-rgb), .6)}:host.shape-round{--feed-border-radius: 12px}ion-button{text-transform:unset;--color: rgba(var(--ion-color-contrast-rgb), .7)}ion-item.feed-header{height:var(--feed-header-height);margin:0;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);border-radius:var(--feed-border-radius) var(--feed-border-radius) 0 0}ion-item.feed-header ion-icon[slot=start]{-webkit-margin-end:8px;margin-inline-end:8px}.feed-content{height:auto;overflow-y:auto;--margin-bottom: 8px}.feed-content.has-header{height:calc(100% - var(--feed-header-height, 0))}.feed-content ion-card.feed-item-card{--top-radius: 4px;--bottom-radius: 4px;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);margin:0 0 var(--margin-bottom) 0;border-radius:var(--top-radius) var(--top-radius) var(--bottom-radius) var(--bottom-radius);--ion-card-color-contrast-rgb: var(--ion-color-contrast-rgb, var(--ion-color-dark-rgb, 0, 0, 0));--color: rgba(var(--ion-card-color-contrast-rgb), .87)}.feed-content ion-card.feed-item-card.first{--top-radius: 0}.feed-content ion-card.feed-item-card.last{--margin-bottom: 0;--bottom-radius: 0}.feed-content ion-card.feed-item-card ion-card-header ion-card-title{--color: rgba(var(--ion-card-color-contrast-rgb), .87)}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-chip{--background: transparent;--border-color: transparent;--border-width: 0}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-chip ion-avatar{--color: rgba(var(--ion-card-color-contrast-rgb), .6);border:1px solid var(--color)}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-chip ion-label{--color: rgba(var(--ion-card-color-contrast-rgb), .8);color:var(--color)}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-note{--color: rgba(var(--ion-card-color-contrast-rgb), .6);color:var(--color)}.feed-content ion-card.feed-item-card ion-card-header .tags{display:inline}.feed-content ion-card.feed-item-card ion-card-header .tags a{color:rgba(var(--ion-card-color-contrast-rgb),.6)!important}.feed-content ion-card.feed-item-card ion-card-content ion-text ::ng-deep img{max-width:100%;height:auto!important}.feed-content ion-card.feed-item-card .visible-hover{opacity:0;transition:opacity .2s ease-in-out}.feed-content ion-card.feed-item-card:hover .visible-hover{opacity:1}\n"] }]
38796
+ args: [{ selector: 'app-feed', providers: [RxState], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- debug -->\n@if (debug) {\n <app-debug [title]=\"'Feed'\">\n <p>\n hasFeeds?: {{ hasFeeds$ | async }}\n <br />\n urls: {{ urls$ | async | json }}\n <br />\n shape: {{ shape }}\n </p>\n </app-debug>\n}\n\n@let feeds = feeds$ | async;\n@let userId = userId$ | async;\n\n@if (feeds | isNotEmptyArray) {\n <!-- top header -->\n @if (showHeader && (feeds | arrayFirst); as feed) {\n <ion-item lines=\"none\" [color]=\"headerColor\" class=\"feed-header shape-{{ shape }}\">\n <ion-icon slot=\"start\" name=\"megaphone\"></ion-icon>\n <ion-label>\n <b>{{ feed.title || ('SOCIAL.FEED.NEWS' | translate) }}</b>\n </ion-label>\n <ion-button slot=\"end\" fill=\"clear\" (click)=\"openFeedHome()\" shape=\"\">\n <ion-label translate>SOCIAL.FEED.SHOW_ALL_FEED</ion-label>\n <ion-icon slot=\"end\" name=\"chevron-forward-outline\"></ion-icon>\n </ion-button>\n </ion-item>\n }\n\n <div class=\"feed-content shape-{{ shape }} ion-no-padding\" [class.has-header]=\"showHeader\">\n <!-- feeds -->\n @for (feed of feeds; track feed.feed_url; let firstFeed = $first; let lastFeed = $last) {\n <!-- items -->\n @for (item of feed.items | arrayFilter: filterItem; track item.id; let firstItem = $first; let lastItem = $last) {\n <ion-card\n [class.first]=\"firstFeed && firstItem\"\n [class.last]=\"lastFeed && lastItem\"\n [color]=\"cardColor !== 'light-transparent' ? cardColor : undefined\"\n class=\"feed-item-card\"\n >\n <ion-card-header>\n <ion-card-subtitle style=\"vertical-align: middle\">\n <!-- Authors -->\n @for (author of item.authors || feed.authors; track author) {\n @if (author.name || author.avatar) {\n <ion-chip (click)=\"openUrl(author.url)\" tappable>\n @if (author.avatar) {\n <ion-avatar>\n <ion-img [src]=\"author.avatar\" [alt]=\"author.name\"></ion-img>\n </ion-avatar>\n }\n @if (author.name) {\n <ion-label class=\"author\">{{ author.name }}</ion-label>\n }\n </ion-chip>\n }\n }\n <ion-note class=\"ion-float-end\">\n <small>{{ item.date_published | dateFromNow }}</small>\n </ion-note>\n </ion-card-subtitle>\n\n <!-- title -->\n <ion-card-title (click)=\"openFeedItem(item, feed)\" tappable>{{ item?.title }}</ion-card-title>\n\n <!-- tags -->\n @let tags = item | map: getTags;\n @if (tags | isNotEmptyArray) {\n <ion-text class=\"tags\">\n @for (tag of tags; track tag; let last = $last) {\n <a (click)=\"openTag(feed, tag)\" tappable>\n <ion-text>#{{ tag }}</ion-text>\n </a>\n @if (!last) {\n &nbsp;\n }\n }\n </ion-text>\n }\n </ion-card-header>\n\n <!-- Feed content -->\n <ion-card-content>\n <ion-text [feed]=\"item.url || feed.feed_url\">\n @if (item.content_html) {\n <p [innerHTML]=\"item.content_html\"></p>\n } @else if (item.content_text) {\n <p>\n <markdown [data]=\"item.content_text\" emoji></markdown>\n </p>\n }\n </ion-text>\n </ion-card-content>\n\n @let editable = canEditItem(item, userId, feed);\n @if (editable || showReadMoreButton) {\n @if (editable) {\n\n <!-- Delete button (visible hover)-->\n <button\n mat-icon-button\n (click)=\"onDeleteItem($event, item)\"\n class=\"visible-hover ion-float-start\"\n [title]=\"!showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n >\n <mat-icon>delete</mat-icon>\n </button>\n <!-- Edit button (visible hover) -->\n <ion-button\n (click)=\"onEditItem($event, item)\"\n class=\"visible-hover ion-float-start\" fill=\"clear\"\n >\n <mat-icon slot=\"start\">edit</mat-icon>\n<!-- <ion-icon name=\"pencil\" slot=\"start\"></ion-icon>-->\n <ion-label translate>COMMON.BTN_EDIT</ion-label>\n </ion-button>\n }\n @if (showReadMoreButton) {\n <ion-button (click)=\"openFeedItem(item, feed)\" class=\"ion-float-end\" fill=\"clear\">\n <ion-label>{{ (item.truncated ? 'SOCIAL.FEED.READ_MORE' : 'COMMON.BTN_SHOW') | translate }}</ion-label>\n <ion-icon slot=\"end\" name=\"chevron-forward-outline\"></ion-icon>\n </ion-button>\n }\n }\n </ion-card>\n }\n }\n </div>\n}\n\n<!-- Details modal -->\n<ion-modal #modal [showBackdrop]=\"false\"\n class=\"stack-modal\" [class.modal-large]=\"!mobile\">\n <ng-template>\n <ion-header>\n <ion-toolbar color=\"secondary\">\n <ion-title>{{ 'SOCIAL.FEED.NEWS' | translate }}</ion-title>\n <ion-buttons slot=\"end\">\n <ion-button (click)=\"modal.dismiss()\">\n <ion-icon slot=\"icon-only\" name=\"close\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </ion-header>\n <ion-content>\n <div class=\"ion-padding\">\n <app-feed\n [urls]=\"urls\"\n [showHeader]=\"false\"\n [showReadMoreButton]=\"false\"\n [maxContentLength]=\"-1\"\n [maxAgeInMonths]=\"-1\"\n [itemId]=\"modalItemId\"\n cardColor=\"light\"\n [debug]=\"debug\"\n ></app-feed>\n </div>\n </ion-content>\n </ng-template>\n</ion-modal>\n", styles: [":host{display:block;height:calc(100% - 10px);max-height:fit-content;overflow:hidden;--feed-header-height: 48px;-webkit-box-shadow:0 3px 1px -2px rgba(0,0,0,.2),0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12);box-shadow:0 3px 1px -2px rgba(0,0,0,.2),0 2px 2px rgba(0,0,0,.14),0 1px 5px rgba(0,0,0,.12);-webkit-margin-start:10px;margin-inline-start:10px;-webkit-margin-end:10px;margin-inline-end:10px;margin-top:0;margin-bottom:10px;--feed-border-radius: 4px;border-radius:var(--feed-border-radius);--ion-card-background: rgba(var(--ion-background-color-rgb), .6)}:host.shape-round{--feed-border-radius: 12px}ion-button{text-transform:unset;--color: rgba(var(--ion-color-contrast-rgb), .7)}ion-item.feed-header{height:var(--feed-header-height);margin:0;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);border-radius:var(--feed-border-radius) var(--feed-border-radius) 0 0}ion-item.feed-header ion-icon[slot=start]{-webkit-margin-end:8px;margin-inline-end:8px}.feed-content{height:auto;overflow-y:auto;--margin-bottom: 8px}.feed-content.has-header{height:calc(100% - var(--feed-header-height, 0))}.feed-content ion-card.feed-item-card{--top-radius: 4px;--bottom-radius: 4px;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);margin:0 0 var(--margin-bottom) 0;border-radius:var(--top-radius) var(--top-radius) var(--bottom-radius) var(--bottom-radius);--ion-card-color-contrast-rgb: var(--ion-color-contrast-rgb, var(--ion-color-dark-rgb, 0, 0, 0));--color: rgba(var(--ion-card-color-contrast-rgb), .87)}.feed-content ion-card.feed-item-card.first{--top-radius: 0}.feed-content ion-card.feed-item-card.last{--margin-bottom: 0;--bottom-radius: 0}.feed-content ion-card.feed-item-card ion-card-header ion-card-title{--color: rgba(var(--ion-card-color-contrast-rgb), .87)}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-chip{--background: transparent;--border-color: transparent;--border-width: 0}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-chip ion-avatar{--color: rgba(var(--ion-card-color-contrast-rgb), .6);border:1px solid var(--color)}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-chip ion-label{--color: rgba(var(--ion-card-color-contrast-rgb), .8);color:var(--color)}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-note{--color: rgba(var(--ion-card-color-contrast-rgb), .6);color:var(--color)}.feed-content ion-card.feed-item-card ion-card-header .tags{display:inline}.feed-content ion-card.feed-item-card ion-card-header .tags a{color:rgba(var(--ion-card-color-contrast-rgb),.6)!important}.feed-content ion-card.feed-item-card ion-card-content ion-text ::ng-deep img{max-width:100%;height:auto!important}.feed-content ion-card.feed-item-card .visible-hover{opacity:0;transition:opacity .2s ease-in-out}.feed-content ion-card.feed-item-card:hover .visible-hover{opacity:1}\n"] }]
38460
38797
  }], ctorParameters: () => [{ type: LocalSettingsService }, { type: Environment, decorators: [{
38461
38798
  type: Inject,
38462
38799
  args: [ENVIRONMENT]
@@ -39703,6 +40040,14 @@ class AppPropertiesForm extends AppForm {
39703
40040
  })
39704
40041
  .filter(isNotNil);
39705
40042
  this._definitionByKey = definitionsByKey;
40043
+ // Also update _definitionsByIndex: object used to display form in html template
40044
+ Object.keys(this._definitionsByIndex).forEach((index) => {
40045
+ const key = this._definitionsByIndex[index]?.key;
40046
+ if (isNotNil(key)) {
40047
+ const definition = this._definitionByKey[key];
40048
+ this._definitionsByIndex[index] = this._definitionByKey[key];
40049
+ }
40050
+ });
39706
40051
  }
39707
40052
  getDefinitionAt(index) {
39708
40053
  let definition = this._definitionsByIndex[index];
@@ -39749,6 +40094,15 @@ class AppPropertiesForm extends AppForm {
39749
40094
  this.markAsLoaded();
39750
40095
  }
39751
40096
  }
40097
+ getRawValueAsJson() {
40098
+ // Should get disabled options (when use ac translatecountInheritance)
40099
+ const data = this.formArray.getRawValue();
40100
+ const json = AppPropertiesUtils.arrayAsObject(data, this.definitions, {
40101
+ omitNilOrBlank: this.options?.allowDuplicatedValues !== true,
40102
+ });
40103
+ console.debug('[app-properties-form] Converting raw data as object', data, json);
40104
+ return json;
40105
+ }
39752
40106
  getValueAsJson() {
39753
40107
  const data = this.value;
39754
40108
  const json = AppPropertiesUtils.arrayAsObject(data, this.definitions, {
@@ -39851,11 +40205,11 @@ class AppPropertiesForm extends AppForm {
39851
40205
  return isNil(obj?.key) || isNil(obj.value);
39852
40206
  }
39853
40207
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AppPropertiesForm, deps: [{ token: i0.Injector }, { token: i1$3.UntypedFormBuilder }, { token: i2.DateAdapter }, { token: i0.ChangeDetectorRef }, { token: PropertyValidator }, { token: i1$3.FormGroupDirective, optional: true }], target: i0.ɵɵFactoryTarget.Component });
39854
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AppPropertiesForm, selector: "app-properties-form", inputs: { showToolbar: ["showToolbar", "showToolbar", booleanAttribute], formArrayName: "formArrayName", formArray: "formArray", options: "options", chipColor: "chipColor", mobile: ["mobile", "mobile", booleanAttribute], appearance: "appearance", subscriptSizing: "subscriptSizing", showHintKey: ["showHintKey", "showHintKey", booleanAttribute], hintKeyPrefix: "hintKeyPrefix", showMoreButton: ["showMoreButton", "showMoreButton", booleanAttribute], addButtonText: "addButtonText", addButtonTitle: "addButtonTitle", showAddButton: ["showAddButton", "showAddButton", booleanAttribute], panelClass: "panelClass", panelWidth: "panelWidth", canDownload: ["canDownload", "canDownload", booleanAttribute], canImport: ["canImport", "canImport", booleanAttribute], showMoreButtonTitle: "showMoreButtonTitle", definitions: "definitions", importPolicy: "importPolicy" }, providers: [{ provide: PropertyValidator, useClass: PropertyValidator }, RxState], usesInheritance: true, ngImport: i0, template: "@if (showToolbar) {\n <mat-toolbar>\n <div class=\"toolbar-spacer\"></div>\n\n <ion-item lines=\"none\" (click)=\"toggleShowHintKeys()\">\n <ion-label color=\"dark\">{{ 'FILE.PROPERTIES.BTN_SHOW_HINT_KEY' | translate }}</ion-label>\n <ion-toggle color=\"primary\" [checked]=\"showHintKey\"></ion-toggle>\n </ion-item>\n\n <!-- options sub menu -->\n @if (canDownload || canImport) {\n <button\n slot=\"end\"\n mat-icon-button\n [matMenuTriggerFor]=\"optionsMenu\"\n [title]=\"!showTooltip ? ('COMMON.BTN_OPTIONS' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_OPTIONS' | translate) : ''\"\n >\n <mat-icon>more_vert</mat-icon>\n </button>\n }\n </mat-toolbar>\n}\n\n<!-- Options menu -->\n<mat-menu #optionsMenu=\"matMenu\">\n <!-- Download as CSV -->\n @if (canDownload) {\n <button mat-menu-item [disabled]=\"loading\" [matMenuTriggerFor]=\"downloadMenu\">\n <mat-icon>download</mat-icon>\n <ion-label translate>COMMON.BTN_DOWNLOAD_DOTS</ion-label>\n </button>\n }\n\n <!-- Import from file -->\n @if (canImport) {\n <button mat-menu-item [disabled]=\"loading || disabled\" [matMenuTriggerFor]=\"importMenu\">\n <mat-icon>upload</mat-icon>\n <ion-label translate>COMMON.BTN_IMPORT_FROM_FILE_DOTS</ion-label>\n </button>\n }\n</mat-menu>\n\n<!-- Download menu -->\n<mat-menu #downloadMenu=\"matMenu\">\n <button mat-menu-item [disabled]=\"loading\" (click)=\"exportToCsv()\">\n <ion-label translate>FILE.PROPERTIES.BTN_DOWNLOAD_AS_CSV</ion-label>\n </button>\n</mat-menu>\n\n<!-- Import menu -->\n<mat-menu #importMenu=\"matMenu\">\n <!-- import from file -->\n <button mat-menu-item (click)=\"importFromCsv($event)\">\n <ion-label translate>FILE.PROPERTIES.BTN_IMPORT_FROM_CSV</ion-label>\n </button>\n\n <!-- import policy -->\n <mat-divider></mat-divider>\n <button mat-menu-item [matMenuTriggerFor]=\"importPolicyMenu\">\n <ion-label translate>FILE.UPLOAD.BTN_IMPORT_POLICY_DOTS</ion-label>\n </button>\n</mat-menu>\n\n<!-- Import policy menu -->\n<mat-menu #importPolicyMenu=\"matMenu\" class=\"ion-no-padding\">\n <ng-template matMenuContent>\n <!-- header-->\n <ion-row class=\"mat-menu-header ion-no-padding column\">\n <ion-col>\n <ion-label translate>FILE.UPLOAD.IMPORT_POLICY.TITLE</ion-label>\n </ion-col>\n </ion-row>\n @for (policy of _importPolicies; track policy) {\n <button mat-menu-item (click)=\"importPolicy = policy\">\n <mat-icon>{{ importPolicy === policy ? 'check_box' : 'check_box_outline_blank' }}</mat-icon>\n <ion-label>{{ 'FILE.UPLOAD.IMPORT_POLICY.' + policy | uppercase | translate }}</ion-label>\n </button>\n }\n </ng-template>\n</mat-menu>\n\n<!-- Properties -->\n<form [formGroup]=\"form\" class=\"form-container\">\n @if (loadingSubject | async) {\n <ion-list [inset]=\"mobile\">\n <ng-container *ngTemplateOutlet=\"propertyRowSkeleton\"></ng-container>\n <ng-container *ngTemplateOutlet=\"propertyRowSkeleton\"></ng-container>\n <ng-container *ngTemplateOutlet=\"propertyRowSkeleton\"></ng-container>\n </ion-list>\n } @else {\n <ion-list formArrayName=\"properties\" [inset]=\"mobile\">\n <!-- Show more options -->\n @if ((_definitionKeys$ | async | isNotEmptyArray) && formArray?.length === 0) {\n <ion-item lines=\"none\">\n <ion-button\n color=\"light\"\n [title]=\"!showTooltip ? (addButtonTitle | translate) : ''\"\n [matTooltip]=\"showTooltip ? (addButtonTitle | translate) : ''\"\n (click)=\"_helper.add()\"\n >\n <ion-label>{{ addButtonText | translate }}</ion-label>\n <mat-icon slot=\"end\">arrow_drop_down</mat-icon>\n </ion-button>\n </ion-item>\n }\n\n @for (fieldForm of fieldForms; track i; let i = $index) {\n <ion-item lines=\"none\" [class.outline]=\"appearance === 'outline'\">\n <ion-grid class=\"ion-no-padding\">\n <ion-row [formGroupName]=\"i\" class=\"ion-no-padding\">\n @let definition = getDefinitionAt(i);\n\n <!-- property key -->\n @if (fieldForm | formGetControl: 'key'; as control) {\n <ion-col\n size=\"12\"\n size-sm=\"6\"\n [title]=\"!showTooltip ? ((definition | propertyGet: 'label' | translate) || '') : ''\"\n [matTooltip]=\"showTooltip ? ((definition | propertyGet: 'label' | translate) || '') : ''\"\n class=\"ion-no-padding\"\n >\n <mat-autocomplete-field\n floatLabel=\"auto\"\n [formControl]=\"control\"\n [appearance]=\"appearance\"\n [subscriptSizing]=\"showHintKey ? 'fixed' : subscriptSizing\"\n (selectionChange)=\"updateDefinitionAt(i)\"\n [tabindex]=\"tabindex + i * 2\"\n [config]=\"_autocompleteConfig\"\n [placeholder]=\"'SETTINGS.PROPERTY_KEY' | translate\"\n [required]=\"true\"\n (focus)=\"_focusedControlIndex = i\"\n [readonly]=\"definition?.disabled\"\n [mobile]=\"mobile\"\n [panelClass]=\"panelClass\"\n [panelWidth]=\"panelWidth\"\n >\n <mat-hint [class.cdk-visually-hidden]=\"!showHintKey\">\n @let keyValue = control.value;\n {{ hintKeyPrefix || '' }}{{ keyValue?.key || keyValue || '' }}\n </mat-hint>\n </mat-autocomplete-field>\n </ion-col>\n }\n\n <!-- property value -->\n <ion-col size=\"\" size-sm=\"\" class=\"ion-no-padding ion-padding-start-xs\">\n @if (definition) {\n <app-form-field\n floatLabel=\"auto\"\n [label]=\"mobile ? ('SETTINGS.PROPERTY_VALUE' | translate) : ' '\"\n [appearance]=\"appearance\"\n [subscriptSizing]=\"subscriptSizing\"\n [definition]=\"definition\"\n [formControl]=\"fieldForm | formGetControl: 'value'\"\n [placeholder]=\"'SETTINGS.PROPERTY_VALUE' | translate\"\n [chipColor]=\"chipColor\"\n [required]=\"true\"\n [hideRequiredMarker]=\"true\"\n [tabindex]=\"tabindex + i * 2 + 1\"\n [readonly]=\"definition?.disabled\"\n [panelClass]=\"panelClass\"\n [panelWidth]=\"panelWidth\"\n ></app-form-field>\n } @else {\n <!-- unknown definition -->\n <mat-form-field [appearance]=\"appearance\" [subscriptSizing]=\"subscriptSizing\">\n @if (mobile) {\n <mat-label>{{ 'SETTINGS.PROPERTY_VALUE' | translate }}</mat-label>\n }\n <input\n type=\"text\"\n matInput\n [formControl]=\"fieldForm | formGetControl: 'value'\"\n [placeholder]=\"mobile ? '' : ('SETTINGS.PROPERTY_VALUE' | translate)\"\n [required]=\"true\"\n [tabindex]=\"20 + i * 2 + 1\"\n />\n </mat-form-field>\n }\n </ion-col>\n\n <ion-col size=\"auto\" class=\"ion-no-padding\">\n @if (_helper.isLast(i)) {\n <button\n type=\"button\"\n mat-icon-button\n color=\"light\"\n [disabled]=\"\n disabled || (fieldForm.value | asBoolean: isEmptyProperty) || (_definitionKeys | isEmptyArray)\n \"\n [title]=\"!showTooltip ? ('SETTINGS.BTN_ADD_PROPERTY' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('SETTINGS.BTN_ADD_PROPERTY' | translate) : ''\"\n (click)=\"_helper.add()\"\n [tabindex]=\"20 + i * 2 + 2\"\n >\n <mat-icon>add</mat-icon>\n </button>\n }\n <button\n type=\"button\"\n mat-icon-button\n color=\"light\"\n [disabled]=\"definition?.disabled || disabled\"\n [title]=\"!showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n (click)=\"removeAt(i)\"\n [tabindex]=\"-1\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-item>\n }\n </ion-list>\n }\n</form>\n\n<ng-template #propertyRowSkeleton>\n <ion-item lines=\"none\">\n <ion-grid class=\"ion-no-padding\">\n <ion-row class=\"ion-no-padding\">\n <!-- property key -->\n <ion-col size=\"6\" class=\"ion-no-padding\">\n <mat-form-field [subscriptSizing]=\"subscriptSizing\">\n <input matInput hidden disabled />\n <ion-skeleton-text [animated]=\"true\" style=\"width: 60%\"></ion-skeleton-text>\n <ion-icon name=\"arrow-dropdown\" matSuffix></ion-icon>\n </mat-form-field>\n </ion-col>\n <!-- value -->\n <ion-col class=\"ion-no-padding\">\n <mat-form-field [subscriptSizing]=\"subscriptSizing\">\n <input matInput hidden disabled />\n <ion-skeleton-text [animated]=\"true\" style=\"width: 60%\"></ion-skeleton-text>\n </mat-form-field>\n </ion-col>\n <!-- buttons -->\n <ion-col size=\"auto\">\n <button type=\"button\" mat-icon-button color=\"light\" disabled>\n <mat-icon>close</mat-icon>\n </button>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-item>\n</ng-template>\n", styles: [":host ion-item ion-row ion-col{--ion-padding-start: 0;min-width:48px}:host ion-item.outline{overflow:visible}:host ion-item.outline ion-grid,:host ion-item.outline ion-row,:host ion-item.outline ion-col{overflow:visible}\n"], dependencies: [{ kind: "directive", type: i3$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "component", type: i2$1.IonSkeletonText, selector: "ion-skeleton-text", inputs: ["animated"] }, { kind: "component", type: i2$1.IonToggle, selector: "ion-toggle", inputs: ["alignment", "checked", "color", "disabled", "enableOnOffLabels", "justify", "labelPlacement", "legacy", "mode", "name", "value"] }, { kind: "directive", type: i2$1.BooleanValueAccessor, selector: "ion-checkbox,ion-toggle" }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$3.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "directive", type: i1$3.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i3.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i10$1.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "component", type: i6$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i11$1.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i12.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i12.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i12.MatMenuContent, selector: "ng-template[matMenuContent]" }, { kind: "directive", type: i12.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i9.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i1$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: MatAutocompleteField, selector: "mat-autocomplete-field", inputs: ["equals", "logPrefix", "formControl", "formControlName", "floatLabel", "label", "appearance", "subscriptSizing", "placeholder", "suggestFn", "required", "hideRequiredMarker", "mobile", "showPanelHeader", "showPanelFooter", "clearable", "debounceTime", "displaySeparator", "displayWith", "displayAttributes", "displayColumnSizes", "displayColumnNames", "highlightAccent", "showAllOnFocus", "showPanelOnFocus", "reloadItemsOnFocus", "clearInvalidValueOnBlur", "autofocus", "config", "i18nPrefix", "noResultMessage", "panelClass", "panelWidth", "disableRipple", "matAutocompletePosition", "multiple", "fetchMoreThreshold", "suggestLengthThreshold", "showLoadingSpinner", "debug", "showSearchBar", "stickySearchBar", "applyImplicitValue", "dropButtonTitle", "clearButtonTitle", "trimSearchText", "splitSearchText", "selectInputContentOnFocus", "selectInputContentOnFocusDelay", "previewImplicitValue", "showFavorites", "toggleFavoriteTitle", "favoriteItems", "colSizes", "class", "filter", "readonly", "tabindex", "items"], outputs: ["click", "blur", "focus", "dropButtonClick", "keydown.escape", "keydown.backspace", "keyup.enter", "arrowUp", "arrowDown", "enter", "selectionChange", "openedChange", "toggleFavorite"] }, { kind: "component", type: AppFormField, selector: "app-form-field", inputs: ["definition", "readonly", "disabled", "formControl", "formControlName", "placeholder", "compact", "required", "hideRequiredMarker", "floatLabel", "label", "appearance", "subscriptSizing", "tabindex", "autofocus", "clearable", "chipColor", "class", "debug", "panelClass", "panelWidth"], outputs: ["keyup.enter"] }, { kind: "pipe", type: i3$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.UpperCasePipe, name: "uppercase" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }, { kind: "pipe", type: PropertyGetPipe, name: "propertyGet" }, { kind: "pipe", type: NotEmptyArrayPipe, name: "isNotEmptyArray" }, { kind: "pipe", type: EmptyArrayPipe, name: "isEmptyArray" }, { kind: "pipe", type: FormGetControlPipe, name: "formGetControl" }, { kind: "pipe", type: AsBooleanPipe, name: "asBoolean" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
40208
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AppPropertiesForm, selector: "app-properties-form", inputs: { showToolbar: ["showToolbar", "showToolbar", booleanAttribute], formArrayName: "formArrayName", formArray: "formArray", options: "options", chipColor: "chipColor", mobile: ["mobile", "mobile", booleanAttribute], appearance: "appearance", subscriptSizing: "subscriptSizing", showHintKey: ["showHintKey", "showHintKey", booleanAttribute], hintKeyPrefix: "hintKeyPrefix", showMoreButton: ["showMoreButton", "showMoreButton", booleanAttribute], addButtonText: "addButtonText", addButtonTitle: "addButtonTitle", showAddButton: ["showAddButton", "showAddButton", booleanAttribute], panelClass: "panelClass", panelWidth: "panelWidth", canDownload: ["canDownload", "canDownload", booleanAttribute], canImport: ["canImport", "canImport", booleanAttribute], showMoreButtonTitle: "showMoreButtonTitle", definitions: "definitions", importPolicy: "importPolicy" }, providers: [{ provide: PropertyValidator, useClass: PropertyValidator }, RxState], usesInheritance: true, ngImport: i0, template: "@if (showToolbar) {\n <mat-toolbar>\n <div class=\"toolbar-spacer\"></div>\n\n <ion-item lines=\"none\" (click)=\"toggleShowHintKeys()\">\n <ion-label color=\"dark\">{{ 'FILE.PROPERTIES.BTN_SHOW_HINT_KEY' | translate }}</ion-label>\n <ion-toggle color=\"primary\" [checked]=\"showHintKey\"></ion-toggle>\n </ion-item>\n\n <!-- options sub menu -->\n @if (canDownload || canImport) {\n <button\n slot=\"end\"\n mat-icon-button\n [matMenuTriggerFor]=\"optionsMenu\"\n [title]=\"!showTooltip ? ('COMMON.BTN_OPTIONS' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_OPTIONS' | translate) : ''\"\n >\n <mat-icon>more_vert</mat-icon>\n </button>\n }\n </mat-toolbar>\n}\n\n<!-- Options menu -->\n<mat-menu #optionsMenu=\"matMenu\">\n <!-- Download as CSV -->\n @if (canDownload) {\n <button mat-menu-item [disabled]=\"loading\" [matMenuTriggerFor]=\"downloadMenu\">\n <mat-icon>download</mat-icon>\n <ion-label translate>COMMON.BTN_DOWNLOAD_DOTS</ion-label>\n </button>\n }\n\n <!-- Import from file -->\n @if (canImport) {\n <button mat-menu-item [disabled]=\"loading || disabled\" [matMenuTriggerFor]=\"importMenu\">\n <mat-icon>upload</mat-icon>\n <ion-label translate>COMMON.BTN_IMPORT_FROM_FILE_DOTS</ion-label>\n </button>\n }\n</mat-menu>\n\n<!-- Download menu -->\n<mat-menu #downloadMenu=\"matMenu\">\n <button mat-menu-item [disabled]=\"loading\" (click)=\"exportToCsv()\">\n <ion-label translate>FILE.PROPERTIES.BTN_DOWNLOAD_AS_CSV</ion-label>\n </button>\n</mat-menu>\n\n<!-- Import menu -->\n<mat-menu #importMenu=\"matMenu\">\n <!-- import from file -->\n <button mat-menu-item (click)=\"importFromCsv($event)\">\n <ion-label translate>FILE.PROPERTIES.BTN_IMPORT_FROM_CSV</ion-label>\n </button>\n\n <!-- import policy -->\n <mat-divider></mat-divider>\n <button mat-menu-item [matMenuTriggerFor]=\"importPolicyMenu\">\n <ion-label translate>FILE.UPLOAD.BTN_IMPORT_POLICY_DOTS</ion-label>\n </button>\n</mat-menu>\n\n<!-- Import policy menu -->\n<mat-menu #importPolicyMenu=\"matMenu\" class=\"ion-no-padding\">\n <ng-template matMenuContent>\n <!-- header-->\n <ion-row class=\"mat-menu-header ion-no-padding column\">\n <ion-col>\n <ion-label translate>FILE.UPLOAD.IMPORT_POLICY.TITLE</ion-label>\n </ion-col>\n </ion-row>\n @for (policy of _importPolicies; track policy) {\n <button mat-menu-item (click)=\"importPolicy = policy\">\n <mat-icon>{{ importPolicy === policy ? 'check_box' : 'check_box_outline_blank' }}</mat-icon>\n <ion-label>{{ 'FILE.UPLOAD.IMPORT_POLICY.' + policy | uppercase | translate }}</ion-label>\n </button>\n }\n </ng-template>\n</mat-menu>\n\n<!-- Properties -->\n<form [formGroup]=\"form\" class=\"form-container\">\n @if (loadingSubject | async) {\n <ion-list [inset]=\"mobile\">\n <ng-container *ngTemplateOutlet=\"propertyRowSkeleton\"></ng-container>\n <ng-container *ngTemplateOutlet=\"propertyRowSkeleton\"></ng-container>\n <ng-container *ngTemplateOutlet=\"propertyRowSkeleton\"></ng-container>\n </ion-list>\n } @else {\n <ion-list formArrayName=\"properties\" [inset]=\"mobile\">\n <!-- Show more options -->\n @if ((_definitionKeys$ | async | isNotEmptyArray) && formArray?.length === 0) {\n <ion-item lines=\"none\">\n <ion-button\n color=\"light\"\n [title]=\"!showTooltip ? (addButtonTitle | translate) : ''\"\n [matTooltip]=\"showTooltip ? (addButtonTitle | translate) : ''\"\n (click)=\"_helper.add()\"\n >\n <ion-label>{{ addButtonText | translate }}</ion-label>\n <mat-icon slot=\"end\">arrow_drop_down</mat-icon>\n </ion-button>\n </ion-item>\n }\n\n @for (fieldForm of fieldForms; track i; let i = $index) {\n <ion-item lines=\"none\" [class.outline]=\"appearance === 'outline'\">\n <ion-grid class=\"ion-no-padding\">\n <ion-row [formGroupName]=\"i\" class=\"ion-no-padding\">\n @let definition = getDefinitionAt(i);\n\n <!-- property key -->\n @if (fieldForm | formGetControl: 'key'; as control) {\n <ion-col\n size=\"12\"\n size-sm=\"6\"\n [title]=\"!showTooltip ? (definition | propertyGet: 'label' | translate) || '' : ''\"\n [matTooltip]=\"showTooltip ? (definition | propertyGet: 'label' | translate) || '' : ''\"\n class=\"ion-no-padding\"\n >\n <mat-autocomplete-field\n floatLabel=\"auto\"\n [formControl]=\"control\"\n [appearance]=\"appearance\"\n [subscriptSizing]=\"showHintKey ? 'fixed' : subscriptSizing\"\n (selectionChange)=\"updateDefinitionAt(i)\"\n [tabindex]=\"tabindex + i * 2\"\n [config]=\"_autocompleteConfig\"\n [placeholder]=\"'SETTINGS.PROPERTY_KEY' | translate\"\n [required]=\"true\"\n (focus)=\"_focusedControlIndex = i\"\n [readonly]=\"definition?.disabled\"\n [mobile]=\"mobile\"\n [panelClass]=\"panelClass\"\n [panelWidth]=\"panelWidth\"\n >\n <mat-hint [class.cdk-visually-hidden]=\"!showHintKey\">\n @let keyValue = control.value;\n {{ hintKeyPrefix || '' }}{{ keyValue?.key || keyValue || '' }}\n </mat-hint>\n </mat-autocomplete-field>\n </ion-col>\n }\n\n <!-- property value -->\n <ion-col size=\"\" size-sm=\"\" class=\"ion-no-padding ion-padding-start-xs\">\n @if (definition) {\n <app-form-field\n floatLabel=\"auto\"\n [label]=\"mobile ? ('SETTINGS.PROPERTY_VALUE' | translate) : ' '\"\n [appearance]=\"appearance\"\n [subscriptSizing]=\"subscriptSizing\"\n [definition]=\"definition\"\n [formControl]=\"fieldForm | formGetControl: 'value'\"\n [placeholder]=\"'SETTINGS.PROPERTY_VALUE' | translate\"\n [chipColor]=\"chipColor\"\n [required]=\"definition?.required\"\n [hideRequiredMarker]=\"true\"\n [tabindex]=\"tabindex + i * 2 + 1\"\n [readonly]=\"definition?.disabled\"\n [panelClass]=\"panelClass\"\n [panelWidth]=\"panelWidth\"\n ></app-form-field>\n } @else {\n <!-- unknown definition -->\n <mat-form-field [appearance]=\"appearance\" [subscriptSizing]=\"subscriptSizing\">\n @if (mobile) {\n <mat-label>{{ 'SETTINGS.PROPERTY_VALUE' | translate }}</mat-label>\n }\n <input\n type=\"text\"\n matInput\n [formControl]=\"fieldForm | formGetControl: 'value'\"\n [placeholder]=\"mobile ? '' : ('SETTINGS.PROPERTY_VALUE' | translate)\"\n [required]=\"true\"\n [tabindex]=\"20 + i * 2 + 1\"\n />\n </mat-form-field>\n }\n </ion-col>\n\n <ion-col size=\"auto\" class=\"ion-no-padding\">\n @if (_helper.isLast(i)) {\n <button\n type=\"button\"\n mat-icon-button\n color=\"light\"\n [disabled]=\"\n disabled || (fieldForm.value | asBoolean: isEmptyProperty) || (_definitionKeys | isEmptyArray)\n \"\n [title]=\"!showTooltip ? ('SETTINGS.BTN_ADD_PROPERTY' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('SETTINGS.BTN_ADD_PROPERTY' | translate) : ''\"\n (click)=\"_helper.add()\"\n [tabindex]=\"20 + i * 2 + 2\"\n >\n <mat-icon>add</mat-icon>\n </button>\n }\n <button\n type=\"button\"\n mat-icon-button\n color=\"light\"\n [disabled]=\"definition?.disabled || disabled\"\n [title]=\"!showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n (click)=\"removeAt(i)\"\n [tabindex]=\"-1\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-item>\n }\n </ion-list>\n }\n</form>\n\n<ng-template #propertyRowSkeleton>\n <ion-item lines=\"none\">\n <ion-grid class=\"ion-no-padding\">\n <ion-row class=\"ion-no-padding\">\n <!-- property key -->\n <ion-col size=\"6\" class=\"ion-no-padding\">\n <mat-form-field [subscriptSizing]=\"subscriptSizing\">\n <input matInput hidden disabled />\n <ion-skeleton-text [animated]=\"true\" style=\"width: 60%\"></ion-skeleton-text>\n <ion-icon name=\"arrow-dropdown\" matSuffix></ion-icon>\n </mat-form-field>\n </ion-col>\n <!-- value -->\n <ion-col class=\"ion-no-padding\">\n <mat-form-field [subscriptSizing]=\"subscriptSizing\">\n <input matInput hidden disabled />\n <ion-skeleton-text [animated]=\"true\" style=\"width: 60%\"></ion-skeleton-text>\n </mat-form-field>\n </ion-col>\n <!-- buttons -->\n <ion-col size=\"auto\">\n <button type=\"button\" mat-icon-button color=\"light\" disabled>\n <mat-icon>close</mat-icon>\n </button>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-item>\n</ng-template>\n", styles: [":host ion-item ion-row ion-col{--ion-padding-start: 0;min-width:48px}:host ion-item.outline{overflow:visible}:host ion-item.outline ion-grid,:host ion-item.outline ion-row,:host ion-item.outline ion-col{overflow:visible}\n"], dependencies: [{ kind: "directive", type: i3$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "component", type: i2$1.IonSkeletonText, selector: "ion-skeleton-text", inputs: ["animated"] }, { kind: "component", type: i2$1.IonToggle, selector: "ion-toggle", inputs: ["alignment", "checked", "color", "disabled", "enableOnOffLabels", "justify", "labelPlacement", "legacy", "mode", "name", "value"] }, { kind: "directive", type: i2$1.BooleanValueAccessor, selector: "ion-checkbox,ion-toggle" }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$3.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "directive", type: i1$3.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i3.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i10$1.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "component", type: i6$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i11$1.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i12.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i12.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i12.MatMenuContent, selector: "ng-template[matMenuContent]" }, { kind: "directive", type: i12.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i9.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i1$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: MatAutocompleteField, selector: "mat-autocomplete-field", inputs: ["equals", "logPrefix", "formControl", "formControlName", "floatLabel", "label", "appearance", "subscriptSizing", "placeholder", "suggestFn", "required", "hideRequiredMarker", "mobile", "showPanelHeader", "showPanelFooter", "clearable", "debounceTime", "displaySeparator", "displayWith", "displayAttributes", "displayColumnSizes", "displayColumnNames", "highlightAccent", "showAllOnFocus", "showPanelOnFocus", "reloadItemsOnFocus", "clearInvalidValueOnBlur", "autofocus", "config", "i18nPrefix", "noResultMessage", "panelClass", "panelWidth", "disableRipple", "matAutocompletePosition", "multiple", "fetchMoreThreshold", "suggestLengthThreshold", "showLoadingSpinner", "debug", "showSearchBar", "stickySearchBar", "applyImplicitValue", "dropButtonTitle", "clearButtonTitle", "trimSearchText", "splitSearchText", "selectInputContentOnFocus", "selectInputContentOnFocusDelay", "previewImplicitValue", "showFavorites", "toggleFavoriteTitle", "favoriteItems", "colSizes", "class", "filter", "readonly", "tabindex", "items"], outputs: ["click", "blur", "focus", "dropButtonClick", "keydown.escape", "keydown.backspace", "keyup.enter", "arrowUp", "arrowDown", "enter", "selectionChange", "openedChange", "toggleFavorite"] }, { kind: "component", type: AppFormField, selector: "app-form-field", inputs: ["definition", "readonly", "disabled", "formControl", "formControlName", "placeholder", "compact", "required", "hideRequiredMarker", "floatLabel", "label", "appearance", "subscriptSizing", "tabindex", "autofocus", "clearable", "chipColor", "class", "debug", "panelClass", "panelWidth"], outputs: ["keyup.enter", "definitionChanged"] }, { kind: "pipe", type: i3$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.UpperCasePipe, name: "uppercase" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }, { kind: "pipe", type: PropertyGetPipe, name: "propertyGet" }, { kind: "pipe", type: NotEmptyArrayPipe, name: "isNotEmptyArray" }, { kind: "pipe", type: EmptyArrayPipe, name: "isEmptyArray" }, { kind: "pipe", type: FormGetControlPipe, name: "formGetControl" }, { kind: "pipe", type: AsBooleanPipe, name: "asBoolean" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
39855
40209
  }
39856
40210
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AppPropertiesForm, decorators: [{
39857
40211
  type: Component,
39858
- args: [{ selector: 'app-properties-form', providers: [{ provide: PropertyValidator, useClass: PropertyValidator }, RxState], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (showToolbar) {\n <mat-toolbar>\n <div class=\"toolbar-spacer\"></div>\n\n <ion-item lines=\"none\" (click)=\"toggleShowHintKeys()\">\n <ion-label color=\"dark\">{{ 'FILE.PROPERTIES.BTN_SHOW_HINT_KEY' | translate }}</ion-label>\n <ion-toggle color=\"primary\" [checked]=\"showHintKey\"></ion-toggle>\n </ion-item>\n\n <!-- options sub menu -->\n @if (canDownload || canImport) {\n <button\n slot=\"end\"\n mat-icon-button\n [matMenuTriggerFor]=\"optionsMenu\"\n [title]=\"!showTooltip ? ('COMMON.BTN_OPTIONS' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_OPTIONS' | translate) : ''\"\n >\n <mat-icon>more_vert</mat-icon>\n </button>\n }\n </mat-toolbar>\n}\n\n<!-- Options menu -->\n<mat-menu #optionsMenu=\"matMenu\">\n <!-- Download as CSV -->\n @if (canDownload) {\n <button mat-menu-item [disabled]=\"loading\" [matMenuTriggerFor]=\"downloadMenu\">\n <mat-icon>download</mat-icon>\n <ion-label translate>COMMON.BTN_DOWNLOAD_DOTS</ion-label>\n </button>\n }\n\n <!-- Import from file -->\n @if (canImport) {\n <button mat-menu-item [disabled]=\"loading || disabled\" [matMenuTriggerFor]=\"importMenu\">\n <mat-icon>upload</mat-icon>\n <ion-label translate>COMMON.BTN_IMPORT_FROM_FILE_DOTS</ion-label>\n </button>\n }\n</mat-menu>\n\n<!-- Download menu -->\n<mat-menu #downloadMenu=\"matMenu\">\n <button mat-menu-item [disabled]=\"loading\" (click)=\"exportToCsv()\">\n <ion-label translate>FILE.PROPERTIES.BTN_DOWNLOAD_AS_CSV</ion-label>\n </button>\n</mat-menu>\n\n<!-- Import menu -->\n<mat-menu #importMenu=\"matMenu\">\n <!-- import from file -->\n <button mat-menu-item (click)=\"importFromCsv($event)\">\n <ion-label translate>FILE.PROPERTIES.BTN_IMPORT_FROM_CSV</ion-label>\n </button>\n\n <!-- import policy -->\n <mat-divider></mat-divider>\n <button mat-menu-item [matMenuTriggerFor]=\"importPolicyMenu\">\n <ion-label translate>FILE.UPLOAD.BTN_IMPORT_POLICY_DOTS</ion-label>\n </button>\n</mat-menu>\n\n<!-- Import policy menu -->\n<mat-menu #importPolicyMenu=\"matMenu\" class=\"ion-no-padding\">\n <ng-template matMenuContent>\n <!-- header-->\n <ion-row class=\"mat-menu-header ion-no-padding column\">\n <ion-col>\n <ion-label translate>FILE.UPLOAD.IMPORT_POLICY.TITLE</ion-label>\n </ion-col>\n </ion-row>\n @for (policy of _importPolicies; track policy) {\n <button mat-menu-item (click)=\"importPolicy = policy\">\n <mat-icon>{{ importPolicy === policy ? 'check_box' : 'check_box_outline_blank' }}</mat-icon>\n <ion-label>{{ 'FILE.UPLOAD.IMPORT_POLICY.' + policy | uppercase | translate }}</ion-label>\n </button>\n }\n </ng-template>\n</mat-menu>\n\n<!-- Properties -->\n<form [formGroup]=\"form\" class=\"form-container\">\n @if (loadingSubject | async) {\n <ion-list [inset]=\"mobile\">\n <ng-container *ngTemplateOutlet=\"propertyRowSkeleton\"></ng-container>\n <ng-container *ngTemplateOutlet=\"propertyRowSkeleton\"></ng-container>\n <ng-container *ngTemplateOutlet=\"propertyRowSkeleton\"></ng-container>\n </ion-list>\n } @else {\n <ion-list formArrayName=\"properties\" [inset]=\"mobile\">\n <!-- Show more options -->\n @if ((_definitionKeys$ | async | isNotEmptyArray) && formArray?.length === 0) {\n <ion-item lines=\"none\">\n <ion-button\n color=\"light\"\n [title]=\"!showTooltip ? (addButtonTitle | translate) : ''\"\n [matTooltip]=\"showTooltip ? (addButtonTitle | translate) : ''\"\n (click)=\"_helper.add()\"\n >\n <ion-label>{{ addButtonText | translate }}</ion-label>\n <mat-icon slot=\"end\">arrow_drop_down</mat-icon>\n </ion-button>\n </ion-item>\n }\n\n @for (fieldForm of fieldForms; track i; let i = $index) {\n <ion-item lines=\"none\" [class.outline]=\"appearance === 'outline'\">\n <ion-grid class=\"ion-no-padding\">\n <ion-row [formGroupName]=\"i\" class=\"ion-no-padding\">\n @let definition = getDefinitionAt(i);\n\n <!-- property key -->\n @if (fieldForm | formGetControl: 'key'; as control) {\n <ion-col\n size=\"12\"\n size-sm=\"6\"\n [title]=\"!showTooltip ? ((definition | propertyGet: 'label' | translate) || '') : ''\"\n [matTooltip]=\"showTooltip ? ((definition | propertyGet: 'label' | translate) || '') : ''\"\n class=\"ion-no-padding\"\n >\n <mat-autocomplete-field\n floatLabel=\"auto\"\n [formControl]=\"control\"\n [appearance]=\"appearance\"\n [subscriptSizing]=\"showHintKey ? 'fixed' : subscriptSizing\"\n (selectionChange)=\"updateDefinitionAt(i)\"\n [tabindex]=\"tabindex + i * 2\"\n [config]=\"_autocompleteConfig\"\n [placeholder]=\"'SETTINGS.PROPERTY_KEY' | translate\"\n [required]=\"true\"\n (focus)=\"_focusedControlIndex = i\"\n [readonly]=\"definition?.disabled\"\n [mobile]=\"mobile\"\n [panelClass]=\"panelClass\"\n [panelWidth]=\"panelWidth\"\n >\n <mat-hint [class.cdk-visually-hidden]=\"!showHintKey\">\n @let keyValue = control.value;\n {{ hintKeyPrefix || '' }}{{ keyValue?.key || keyValue || '' }}\n </mat-hint>\n </mat-autocomplete-field>\n </ion-col>\n }\n\n <!-- property value -->\n <ion-col size=\"\" size-sm=\"\" class=\"ion-no-padding ion-padding-start-xs\">\n @if (definition) {\n <app-form-field\n floatLabel=\"auto\"\n [label]=\"mobile ? ('SETTINGS.PROPERTY_VALUE' | translate) : ' '\"\n [appearance]=\"appearance\"\n [subscriptSizing]=\"subscriptSizing\"\n [definition]=\"definition\"\n [formControl]=\"fieldForm | formGetControl: 'value'\"\n [placeholder]=\"'SETTINGS.PROPERTY_VALUE' | translate\"\n [chipColor]=\"chipColor\"\n [required]=\"true\"\n [hideRequiredMarker]=\"true\"\n [tabindex]=\"tabindex + i * 2 + 1\"\n [readonly]=\"definition?.disabled\"\n [panelClass]=\"panelClass\"\n [panelWidth]=\"panelWidth\"\n ></app-form-field>\n } @else {\n <!-- unknown definition -->\n <mat-form-field [appearance]=\"appearance\" [subscriptSizing]=\"subscriptSizing\">\n @if (mobile) {\n <mat-label>{{ 'SETTINGS.PROPERTY_VALUE' | translate }}</mat-label>\n }\n <input\n type=\"text\"\n matInput\n [formControl]=\"fieldForm | formGetControl: 'value'\"\n [placeholder]=\"mobile ? '' : ('SETTINGS.PROPERTY_VALUE' | translate)\"\n [required]=\"true\"\n [tabindex]=\"20 + i * 2 + 1\"\n />\n </mat-form-field>\n }\n </ion-col>\n\n <ion-col size=\"auto\" class=\"ion-no-padding\">\n @if (_helper.isLast(i)) {\n <button\n type=\"button\"\n mat-icon-button\n color=\"light\"\n [disabled]=\"\n disabled || (fieldForm.value | asBoolean: isEmptyProperty) || (_definitionKeys | isEmptyArray)\n \"\n [title]=\"!showTooltip ? ('SETTINGS.BTN_ADD_PROPERTY' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('SETTINGS.BTN_ADD_PROPERTY' | translate) : ''\"\n (click)=\"_helper.add()\"\n [tabindex]=\"20 + i * 2 + 2\"\n >\n <mat-icon>add</mat-icon>\n </button>\n }\n <button\n type=\"button\"\n mat-icon-button\n color=\"light\"\n [disabled]=\"definition?.disabled || disabled\"\n [title]=\"!showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n (click)=\"removeAt(i)\"\n [tabindex]=\"-1\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-item>\n }\n </ion-list>\n }\n</form>\n\n<ng-template #propertyRowSkeleton>\n <ion-item lines=\"none\">\n <ion-grid class=\"ion-no-padding\">\n <ion-row class=\"ion-no-padding\">\n <!-- property key -->\n <ion-col size=\"6\" class=\"ion-no-padding\">\n <mat-form-field [subscriptSizing]=\"subscriptSizing\">\n <input matInput hidden disabled />\n <ion-skeleton-text [animated]=\"true\" style=\"width: 60%\"></ion-skeleton-text>\n <ion-icon name=\"arrow-dropdown\" matSuffix></ion-icon>\n </mat-form-field>\n </ion-col>\n <!-- value -->\n <ion-col class=\"ion-no-padding\">\n <mat-form-field [subscriptSizing]=\"subscriptSizing\">\n <input matInput hidden disabled />\n <ion-skeleton-text [animated]=\"true\" style=\"width: 60%\"></ion-skeleton-text>\n </mat-form-field>\n </ion-col>\n <!-- buttons -->\n <ion-col size=\"auto\">\n <button type=\"button\" mat-icon-button color=\"light\" disabled>\n <mat-icon>close</mat-icon>\n </button>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-item>\n</ng-template>\n", styles: [":host ion-item ion-row ion-col{--ion-padding-start: 0;min-width:48px}:host ion-item.outline{overflow:visible}:host ion-item.outline ion-grid,:host ion-item.outline ion-row,:host ion-item.outline ion-col{overflow:visible}\n"] }]
40212
+ args: [{ selector: 'app-properties-form', providers: [{ provide: PropertyValidator, useClass: PropertyValidator }, RxState], changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "@if (showToolbar) {\n <mat-toolbar>\n <div class=\"toolbar-spacer\"></div>\n\n <ion-item lines=\"none\" (click)=\"toggleShowHintKeys()\">\n <ion-label color=\"dark\">{{ 'FILE.PROPERTIES.BTN_SHOW_HINT_KEY' | translate }}</ion-label>\n <ion-toggle color=\"primary\" [checked]=\"showHintKey\"></ion-toggle>\n </ion-item>\n\n <!-- options sub menu -->\n @if (canDownload || canImport) {\n <button\n slot=\"end\"\n mat-icon-button\n [matMenuTriggerFor]=\"optionsMenu\"\n [title]=\"!showTooltip ? ('COMMON.BTN_OPTIONS' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_OPTIONS' | translate) : ''\"\n >\n <mat-icon>more_vert</mat-icon>\n </button>\n }\n </mat-toolbar>\n}\n\n<!-- Options menu -->\n<mat-menu #optionsMenu=\"matMenu\">\n <!-- Download as CSV -->\n @if (canDownload) {\n <button mat-menu-item [disabled]=\"loading\" [matMenuTriggerFor]=\"downloadMenu\">\n <mat-icon>download</mat-icon>\n <ion-label translate>COMMON.BTN_DOWNLOAD_DOTS</ion-label>\n </button>\n }\n\n <!-- Import from file -->\n @if (canImport) {\n <button mat-menu-item [disabled]=\"loading || disabled\" [matMenuTriggerFor]=\"importMenu\">\n <mat-icon>upload</mat-icon>\n <ion-label translate>COMMON.BTN_IMPORT_FROM_FILE_DOTS</ion-label>\n </button>\n }\n</mat-menu>\n\n<!-- Download menu -->\n<mat-menu #downloadMenu=\"matMenu\">\n <button mat-menu-item [disabled]=\"loading\" (click)=\"exportToCsv()\">\n <ion-label translate>FILE.PROPERTIES.BTN_DOWNLOAD_AS_CSV</ion-label>\n </button>\n</mat-menu>\n\n<!-- Import menu -->\n<mat-menu #importMenu=\"matMenu\">\n <!-- import from file -->\n <button mat-menu-item (click)=\"importFromCsv($event)\">\n <ion-label translate>FILE.PROPERTIES.BTN_IMPORT_FROM_CSV</ion-label>\n </button>\n\n <!-- import policy -->\n <mat-divider></mat-divider>\n <button mat-menu-item [matMenuTriggerFor]=\"importPolicyMenu\">\n <ion-label translate>FILE.UPLOAD.BTN_IMPORT_POLICY_DOTS</ion-label>\n </button>\n</mat-menu>\n\n<!-- Import policy menu -->\n<mat-menu #importPolicyMenu=\"matMenu\" class=\"ion-no-padding\">\n <ng-template matMenuContent>\n <!-- header-->\n <ion-row class=\"mat-menu-header ion-no-padding column\">\n <ion-col>\n <ion-label translate>FILE.UPLOAD.IMPORT_POLICY.TITLE</ion-label>\n </ion-col>\n </ion-row>\n @for (policy of _importPolicies; track policy) {\n <button mat-menu-item (click)=\"importPolicy = policy\">\n <mat-icon>{{ importPolicy === policy ? 'check_box' : 'check_box_outline_blank' }}</mat-icon>\n <ion-label>{{ 'FILE.UPLOAD.IMPORT_POLICY.' + policy | uppercase | translate }}</ion-label>\n </button>\n }\n </ng-template>\n</mat-menu>\n\n<!-- Properties -->\n<form [formGroup]=\"form\" class=\"form-container\">\n @if (loadingSubject | async) {\n <ion-list [inset]=\"mobile\">\n <ng-container *ngTemplateOutlet=\"propertyRowSkeleton\"></ng-container>\n <ng-container *ngTemplateOutlet=\"propertyRowSkeleton\"></ng-container>\n <ng-container *ngTemplateOutlet=\"propertyRowSkeleton\"></ng-container>\n </ion-list>\n } @else {\n <ion-list formArrayName=\"properties\" [inset]=\"mobile\">\n <!-- Show more options -->\n @if ((_definitionKeys$ | async | isNotEmptyArray) && formArray?.length === 0) {\n <ion-item lines=\"none\">\n <ion-button\n color=\"light\"\n [title]=\"!showTooltip ? (addButtonTitle | translate) : ''\"\n [matTooltip]=\"showTooltip ? (addButtonTitle | translate) : ''\"\n (click)=\"_helper.add()\"\n >\n <ion-label>{{ addButtonText | translate }}</ion-label>\n <mat-icon slot=\"end\">arrow_drop_down</mat-icon>\n </ion-button>\n </ion-item>\n }\n\n @for (fieldForm of fieldForms; track i; let i = $index) {\n <ion-item lines=\"none\" [class.outline]=\"appearance === 'outline'\">\n <ion-grid class=\"ion-no-padding\">\n <ion-row [formGroupName]=\"i\" class=\"ion-no-padding\">\n @let definition = getDefinitionAt(i);\n\n <!-- property key -->\n @if (fieldForm | formGetControl: 'key'; as control) {\n <ion-col\n size=\"12\"\n size-sm=\"6\"\n [title]=\"!showTooltip ? (definition | propertyGet: 'label' | translate) || '' : ''\"\n [matTooltip]=\"showTooltip ? (definition | propertyGet: 'label' | translate) || '' : ''\"\n class=\"ion-no-padding\"\n >\n <mat-autocomplete-field\n floatLabel=\"auto\"\n [formControl]=\"control\"\n [appearance]=\"appearance\"\n [subscriptSizing]=\"showHintKey ? 'fixed' : subscriptSizing\"\n (selectionChange)=\"updateDefinitionAt(i)\"\n [tabindex]=\"tabindex + i * 2\"\n [config]=\"_autocompleteConfig\"\n [placeholder]=\"'SETTINGS.PROPERTY_KEY' | translate\"\n [required]=\"true\"\n (focus)=\"_focusedControlIndex = i\"\n [readonly]=\"definition?.disabled\"\n [mobile]=\"mobile\"\n [panelClass]=\"panelClass\"\n [panelWidth]=\"panelWidth\"\n >\n <mat-hint [class.cdk-visually-hidden]=\"!showHintKey\">\n @let keyValue = control.value;\n {{ hintKeyPrefix || '' }}{{ keyValue?.key || keyValue || '' }}\n </mat-hint>\n </mat-autocomplete-field>\n </ion-col>\n }\n\n <!-- property value -->\n <ion-col size=\"\" size-sm=\"\" class=\"ion-no-padding ion-padding-start-xs\">\n @if (definition) {\n <app-form-field\n floatLabel=\"auto\"\n [label]=\"mobile ? ('SETTINGS.PROPERTY_VALUE' | translate) : ' '\"\n [appearance]=\"appearance\"\n [subscriptSizing]=\"subscriptSizing\"\n [definition]=\"definition\"\n [formControl]=\"fieldForm | formGetControl: 'value'\"\n [placeholder]=\"'SETTINGS.PROPERTY_VALUE' | translate\"\n [chipColor]=\"chipColor\"\n [required]=\"definition?.required\"\n [hideRequiredMarker]=\"true\"\n [tabindex]=\"tabindex + i * 2 + 1\"\n [readonly]=\"definition?.disabled\"\n [panelClass]=\"panelClass\"\n [panelWidth]=\"panelWidth\"\n ></app-form-field>\n } @else {\n <!-- unknown definition -->\n <mat-form-field [appearance]=\"appearance\" [subscriptSizing]=\"subscriptSizing\">\n @if (mobile) {\n <mat-label>{{ 'SETTINGS.PROPERTY_VALUE' | translate }}</mat-label>\n }\n <input\n type=\"text\"\n matInput\n [formControl]=\"fieldForm | formGetControl: 'value'\"\n [placeholder]=\"mobile ? '' : ('SETTINGS.PROPERTY_VALUE' | translate)\"\n [required]=\"true\"\n [tabindex]=\"20 + i * 2 + 1\"\n />\n </mat-form-field>\n }\n </ion-col>\n\n <ion-col size=\"auto\" class=\"ion-no-padding\">\n @if (_helper.isLast(i)) {\n <button\n type=\"button\"\n mat-icon-button\n color=\"light\"\n [disabled]=\"\n disabled || (fieldForm.value | asBoolean: isEmptyProperty) || (_definitionKeys | isEmptyArray)\n \"\n [title]=\"!showTooltip ? ('SETTINGS.BTN_ADD_PROPERTY' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('SETTINGS.BTN_ADD_PROPERTY' | translate) : ''\"\n (click)=\"_helper.add()\"\n [tabindex]=\"20 + i * 2 + 2\"\n >\n <mat-icon>add</mat-icon>\n </button>\n }\n <button\n type=\"button\"\n mat-icon-button\n color=\"light\"\n [disabled]=\"definition?.disabled || disabled\"\n [title]=\"!showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n (click)=\"removeAt(i)\"\n [tabindex]=\"-1\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-item>\n }\n </ion-list>\n }\n</form>\n\n<ng-template #propertyRowSkeleton>\n <ion-item lines=\"none\">\n <ion-grid class=\"ion-no-padding\">\n <ion-row class=\"ion-no-padding\">\n <!-- property key -->\n <ion-col size=\"6\" class=\"ion-no-padding\">\n <mat-form-field [subscriptSizing]=\"subscriptSizing\">\n <input matInput hidden disabled />\n <ion-skeleton-text [animated]=\"true\" style=\"width: 60%\"></ion-skeleton-text>\n <ion-icon name=\"arrow-dropdown\" matSuffix></ion-icon>\n </mat-form-field>\n </ion-col>\n <!-- value -->\n <ion-col class=\"ion-no-padding\">\n <mat-form-field [subscriptSizing]=\"subscriptSizing\">\n <input matInput hidden disabled />\n <ion-skeleton-text [animated]=\"true\" style=\"width: 60%\"></ion-skeleton-text>\n </mat-form-field>\n </ion-col>\n <!-- buttons -->\n <ion-col size=\"auto\">\n <button type=\"button\" mat-icon-button color=\"light\" disabled>\n <mat-icon>close</mat-icon>\n </button>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-item>\n</ng-template>\n", styles: [":host ion-item ion-row ion-col{--ion-padding-start: 0;min-width:48px}:host ion-item.outline{overflow:visible}:host ion-item.outline ion-grid,:host ion-item.outline ion-row,:host ion-item.outline ion-col{overflow:visible}\n"] }]
39859
40213
  }], ctorParameters: () => [{ type: i0.Injector }, { type: i1$3.UntypedFormBuilder }, { type: i2.DateAdapter }, { type: i0.ChangeDetectorRef }, { type: PropertyValidator }, { type: i1$3.FormGroupDirective, decorators: [{
39860
40214
  type: Optional
39861
40215
  }] }], propDecorators: { showToolbar: [{
@@ -39979,9 +40333,11 @@ class SettingsPage extends AppForm {
39979
40333
  this.setAccountInheritance(this.accountInheritance);
39980
40334
  // Reload options definitions
39981
40335
  this.loadPropertyDefinitions();
40336
+ this.setAccountInheritance(this.accountInheritance, { updateInheritanceValues: true });
39982
40337
  }));
39983
40338
  }
39984
40339
  loadPropertyDefinitions() {
40340
+ console.debug('[settings] Loading properties definitions...');
39985
40341
  let definitions = [
39986
40342
  // Add account field (BEFORE local settings, because of removeDuplicatesFromArray() - see bellow)
39987
40343
  ...this.accountService.optionDefs.map((def) => ({
@@ -40098,41 +40454,72 @@ class SettingsPage extends AppForm {
40098
40454
  if (this.data.accountInheritance !== enable) {
40099
40455
  this.form.controls['accountInheritance'].markAsDirty({ onlySelf: false });
40100
40456
  }
40101
- if (enable) {
40102
- if (this.isLogin) {
40103
- // Force using account settings
40104
- const account = this.accountService.account;
40105
- // Copy values
40457
+ const accountOptionKeys = this.accountService.optionDefs.map((item) => item.key);
40458
+ const localOptionKeys = this.settings.optionDefs.map((item) => item.key);
40459
+ if (enable && this.isLogin) {
40460
+ // Force using account settings
40461
+ const account = this.accountService.account;
40462
+ // Restore values from account settings
40463
+ if (opts?.updateInheritanceValues) {
40106
40464
  if (account.settings) {
40107
40465
  if (account.settings.locale)
40108
40466
  this.form.get('locale').setValue(account.settings.locale, opts);
40109
40467
  if (account.settings.latLongFormat)
40110
40468
  this.form.get('latLongFormat').setValue(account.settings.latLongFormat, opts);
40469
+ const accountSettingsProperties = account.settings.content?.properties ?? {};
40470
+ this.propertiesForm.setValue(accountSettingsProperties, { emitEvent: false });
40111
40471
  }
40112
- // Disable fields
40113
- this.form.get('locale').disable(opts);
40114
- this.form.get('latLongFormat').disable(opts);
40115
- }
40116
- else {
40117
- // Enable fields
40118
- this.form.get('locale').enable(opts);
40119
- this.form.get('latLongFormat').enable(opts);
40120
40472
  }
40473
+ // Disable fields
40474
+ this.form.get('locale').disable(opts);
40475
+ this.form.get('latLongFormat').disable(opts);
40476
+ // Disable properties managed by account settings
40477
+ this.propertyDefinitions = this.propertyDefinitions.map((item) => {
40478
+ // Disable shared option (declared in both account and local settings)
40479
+ if (accountOptionKeys.includes(item.key)) {
40480
+ item.disabled = true;
40481
+ item.required = false;
40482
+ }
40483
+ return item;
40484
+ });
40121
40485
  }
40122
40486
  else {
40123
- // Restore previous values
40124
- this.form.get('locale').setValue(this.data.locale, opts);
40125
- this.form.get('latLongFormat').setValue(this.data.latLongFormat, opts);
40487
+ // If account inheritance is disabled or user is not logged
40488
+ // -> treat account settings as local settings
40126
40489
  // Enable fields
40127
40490
  this.form.get('locale').enable(opts);
40128
40491
  this.form.get('latLongFormat').enable(opts);
40492
+ this.propertyDefinitions = this.propertyDefinitions.map((item) => {
40493
+ if (accountOptionKeys.includes(item.key)) {
40494
+ const originalOptionDef = localOptionKeys.includes(item.key)
40495
+ ? this.settings.optionDefs.find((optDef) => optDef.key == item.key)
40496
+ : this.accountService.optionDefs.find((optDef) => optDef.key == item.key);
40497
+ if (isNil(originalOptionDef?.minProfile))
40498
+ originalOptionDef.minProfile = 'GUEST';
40499
+ const hasMinProfile = !this.accountService.hasMinProfile(originalOptionDef.minProfile);
40500
+ item.disabled = !originalOptionDef?.disabled && !hasMinProfile;
40501
+ item.required = originalOptionDef.required;
40502
+ }
40503
+ return item;
40504
+ });
40505
+ // Restore previous values
40506
+ if (opts?.updateInheritanceValues) {
40507
+ this.form.get('locale').setValue(this.data.locale, opts);
40508
+ this.form.get('latLongFormat').setValue(this.data.latLongFormat, opts);
40509
+ const localSettingsProperties = this.data.properties;
40510
+ this.propertiesForm.setValue(localSettingsProperties, { emitEvent: false });
40511
+ }
40129
40512
  }
40130
40513
  // Mark for check, if need
40131
40514
  if (!opts || opts.emitEvent) {
40132
40515
  this.markForCheck();
40133
40516
  }
40134
40517
  }
40135
- async showSelectPeerModal(opts) {
40518
+ async showSelectPeerModal(event, opts) {
40519
+ if (event) {
40520
+ event.preventDefault();
40521
+ event.stopPropagation();
40522
+ }
40136
40523
  const selectedPeer = this._form.get('peerUrl').value;
40137
40524
  const peer = await this.network.showSelectPeerModal({
40138
40525
  showSetManuallyButton: false, // Make no sense here, because same as cancel (already inside the settings page)
@@ -40184,7 +40571,7 @@ class SettingsPage extends AppForm {
40184
40571
  async getValue() {
40185
40572
  const json = this.form.value;
40186
40573
  // Serialize properties to map (from array)
40187
- json.properties = this.propertiesForm.getValueAsJson();
40574
+ json.properties = this.propertiesForm.getRawValueAsJson();
40188
40575
  // Override properties from account
40189
40576
  if (json.accountInheritance && this.isLogin) {
40190
40577
  const account = this.accountService.account;
@@ -40259,11 +40646,11 @@ class SettingsPage extends AppForm {
40259
40646
  this.cd.markForCheck();
40260
40647
  }
40261
40648
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SettingsPage, deps: [{ token: i0.Injector }, { token: PlatformService }, { token: i2$1.NavController }, { token: LocalSettingsValidatorService }, { token: i2$1.AlertController }, { token: i1$1.TranslateService }, { token: i1$3.UntypedFormBuilder }, { token: AccountService }, { token: LocalSettingsService }, { token: i0.ChangeDetectorRef }, { token: NetworkService }, { token: APP_LOCALES }, { token: APP_SETTINGS_MENU_ITEMS, optional: true }], target: i0.ɵɵFactoryTarget.Component });
40262
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SettingsPage, selector: "page-settings", host: { listeners: { "window:beforeunload": "handleRefresh($event)" } }, providers: [{ provide: ValidatorService, useExisting: LocalSettingsValidatorService }], viewQueries: [{ propertyName: "propertiesForm", first: true, predicate: ["propertiesForm"], descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: "@let hasValidate = !loading && (dirty || saving);\n<app-toolbar\n [title]=\"'SETTINGS.TITLE' | translate\"\n color=\"primary\"\n [hasValidate]=\"hasValidate\"\n [hasClose]=\"!hasValidate\"\n (onValidate)=\"save($event)\"\n (onClose)=\"close($event)\"\n>\n <ion-buttons slot=\"end\">\n <!-- options menu -->\n <ion-button [matMenuTriggerFor]=\"optionsMenu\" [disabled]=\"loading || saving\">\n <mat-icon slot=\"icon-only\">more_vert</mat-icon>\n </ion-button>\n </ion-buttons>\n</app-toolbar>\n\n<!-- Options menu -->\n<mat-menu #optionsMenu=\"matMenu\">\n <!-- Clear cache -->\n <button mat-menu-item [disabled]=\"saving\" (click)=\"clearCache($event)\">\n <mat-icon><ion-icon name=\"trash-outline\"></ion-icon></mat-icon>\n <ion-label translate>SETTINGS.BTN_CLEAR_CACHE</ion-label>\n </button>\n\n <!-- Reset -->\n <button mat-menu-item [disabled]=\"saving || !dirty\" (click)=\"cancel($event)\">\n <mat-icon><ion-icon name=\"refresh\"></ion-icon></mat-icon>\n <mat-label translate>COMMON.BTN_RESET</mat-label>\n </button>\n\n <!-- additional items -->\n @if (!loading) {\n @for (item of menuItems; track item) {\n @if (item.path) {\n <a mat-menu-item class=\"{{ item.cssClass }} {{ item.color }}\" (click)=\"executeAction($event, item)\">\n <mat-icon *ngIf=\"item.icon\"><ion-icon [name]=\"item.icon\"></ion-icon></mat-icon>\n <mat-icon *ngIf=\"item.matIcon\">{{ item.matIcon }}</mat-icon>\n <mat-label translate>{{ item.title }}</mat-label>\n </a>\n } @else if (!item.action) {\n <!-- divider -->\n <mat-divider class=\"{{ item.cssClass }} {{ item.color }}\">\n <mat-label translate>{{ item.title }}</mat-label>\n </mat-divider>\n }\n }\n }\n\n <!-- debug -->\n @if (isAdmin) {\n <mat-divider></mat-divider>\n\n <button\n mat-menu-item\n #menuTrigger=\"matMenuTrigger\"\n (mouseenter)=\"menuTrigger.openMenu()\"\n [matMenuTriggerFor]=\"debugMenu\"\n >\n <mat-icon><ion-icon name=\"bug\"></ion-icon></mat-icon>\n <ion-label translate>COMMON.DEBUG.BTN_DEBUG_DOTS</ion-label>\n </button>\n\n <mat-menu #debugMenu=\"matMenu\">\n <!-- enable debug -->\n <button mat-menu-item (click)=\"devToggleDebug()\">\n <mat-icon>{{ debug ? 'check_box' : 'check_box_outline_blank' }}</mat-icon>\n <mat-label translate>COMMON.DEBUG.BTN_ENABLE_DEBUG</mat-label>\n </button>\n\n <!-- show option key-->\n <button mat-menu-item (click)=\"toggleShowOptionKeys()\">\n <mat-icon>{{ showOptionKeys ? 'check_box' : 'check_box_outline_blank' }}</mat-icon>\n <mat-label translate>SETTINGS.BTN_SHOW_OPTION_KEY</mat-label>\n </button>\n </mat-menu>\n }\n</mat-menu>\n\n<ion-content>\n <form [formGroup]=\"form\" novalidate (ngSubmit)=\"save($event)\" class=\"form-container\">\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <!-- left margin -->\n <ion-col size=\"0\" size-lg=\"1\" size-xl=\"2\">&nbsp;</ion-col>\n\n <ion-col class=\"ion-padding\">\n <!-- error -->\n <ion-item *ngIf=\"errorSubject | async; let error\" lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n\n <ion-list-header class=\"hidden-xs hidden-xxs\" translate>SETTINGS.DESCRIPTION</ion-list-header>\n\n <ion-list [inset]=\"mobile\" [class.cdk-visually-hidden]=\"!isLogin\">\n <ion-item lines=\"none\">\n <!-- account inheritance (desktop) -->\n @if (!mobile) {\n <mat-form-field>\n <input matInput hidden formControlName=\"accountInheritance\" type=\"text\" />\n\n <!-- check box (if desktop) -->\n <mat-checkbox (change)=\"setAccountInheritance($event.checked)\" [checked]=\"accountInheritance\">\n <span translate>SETTINGS.INHERIT_FROM_ACCOUNT</span>\n </mat-checkbox>\n\n <mat-error *ngIf=\"form.controls.accountInheritance.hasError('required')\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n } @else {\n <!-- account inheritance (if mobile) -->\n <mat-form-field>\n <input matInput hidden formControlName=\"accountInheritance\" type=\"text\" />\n\n <!-- slide toggle -->\n <ion-text translate>SETTINGS.INHERIT_FROM_ACCOUNT</ion-text>\n <mat-slide-toggle\n matSuffix\n (change)=\"setAccountInheritance($event.checked)\"\n [checked]=\"accountInheritance\"\n ></mat-slide-toggle>\n <mat-error *ngIf=\"form.controls.accountInheritance.hasError('required')\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n }\n </ion-item>\n\n <!-- help on account -->\n @if (accountInheritance) {\n <ion-item [button]=\"true\" lines=\"none\" (click)=\"openAccountPage()\">\n @if (!mobile) {\n <ion-icon slot=\"start\" name=\"help-circle-outline\"></ion-icon>\n }\n <ion-label>\n <small [innerHTML]=\"'SETTINGS.INHERIT_FROM_ACCOUNT_HELP' | translate\"></small>\n </ion-label>\n <ion-text color=\"tertiary\" translate>SETTINGS.BTN_SHOW_ACCOUNT</ion-text>\n <ion-icon slot=\"end\" color=\"tertiary\" name=\"chevron-forward\"></ion-icon>\n </ion-item>\n }\n </ion-list>\n\n <ion-list-header><h3 translate>SETTINGS.DISPLAY_DIVIDER</h3></ion-list-header>\n\n <ion-list [inset]=\"mobile\">\n <!-- locale -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <ion-icon matPrefix name=\"language\"></ion-icon>\n <mat-label>{{ 'SETTINGS.LOCALE' | translate }}</mat-label>\n <mat-select formControlName=\"locale\" required>\n <mat-option *ngFor=\"let item of locales\" [value]=\"item.key\">\n {{ item.value }}\n </mat-option>\n </mat-select>\n <mat-error *ngIf=\"form.controls.locale.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n </mat-form-field>\n </ion-item>\n\n <!-- dark mode (if allow by the environment token) -->\n @if (settings.allowDarkMode) {\n <ion-item lines=\"none\">\n <mat-form-field>\n <ion-icon matPrefix name=\"moon\"></ion-icon>\n\n <input matInput hidden formControlName=\"darkMode\" type=\"text\" />\n\n <!-- slide toggle -->\n <ion-text translate>SETTINGS.BTN_DARK_MODE</ion-text>\n <mat-slide-toggle\n matSuffix\n (change)=\"toggleDarkMode($event.checked)\"\n [checked]=\"darkMode\"\n ></mat-slide-toggle>\n </mat-form-field>\n </ion-item>\n }\n </ion-list>\n\n <!-- Network entry -->\n <ion-list-header><h3 translate>SETTINGS.NETWORK_DIVIDER</h3></ion-list-header>\n\n <ion-list [inset]=\"mobile\">\n <!-- Peer address -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <ion-icon matPrefix name=\"cloud\"></ion-icon>\n\n <mat-label>{{ 'SETTINGS.PEER_URL' | translate }}</mat-label>\n <input matInput type=\"text\" formControlName=\"peerUrl\" required />\n\n <button\n mat-icon-button\n type=\"button\"\n matSuffix\n (click)=\"showSelectPeerModal()\"\n tabindex=\"-1\"\n [title]=\"!showTooltip ? ('SETTINGS.BTN_CHANGE_PEER' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('SETTINGS.BTN_CHANGE_PEER' | translate) : ''\"\n >\n <mat-icon>search</mat-icon>\n </button>\n\n <mat-error *ngIf=\"form.controls.peerUrl.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngIf=\"form.controls.peerUrl.hasError('peerAlive')\" translate>\n SETTINGS.ERROR.PEER_NOT_REACHABLE\n </mat-error>\n <mat-error *ngIf=\"form.controls.peerUrl.hasError('peerNotCompatible')\" translate>\n SETTINGS.ERROR.PEER_NOT_COMPATIBLE\n </mat-error>\n </mat-form-field>\n </ion-item>\n\n <!-- Offline mode (if mobile) -->\n @if (mobile) {\n <ion-item lines=\"none\">\n <mat-form-field>\n <input matInput hidden formControlName=\"accountInheritance\" type=\"text\" />\n\n <!-- slide toggle -->\n <ion-text translate>SETTINGS.OFFLINE_MODE</ion-text>\n <mat-slide-toggle\n matSuffix\n (change)=\"setForceOffline($event.checked)\"\n [checked]=\"network.offline\"\n ></mat-slide-toggle>\n </mat-form-field>\n </ion-item>\n }\n </ion-list>\n\n <!-- Data entry -->\n <ion-list-header>\n <h3 translate>SETTINGS.DATA_ENTRY_DIVIDER</h3>\n </ion-list-header>\n\n <ion-list [inset]=\"mobile\">\n <!-- Usage mode -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <ion-icon matPrefix name=\"id-card\"></ion-icon>\n <mat-label>{{ 'SETTINGS.USAGE_MODE' | translate }}</mat-label>\n <mat-select formControlName=\"usageMode\" required>\n <mat-option *ngFor=\"let item of usageModes\" [value]=\"item\">\n {{ 'SETTINGS.USAGE_MODES.' + item | translate }}\n </mat-option>\n </mat-select>\n <mat-error *ngIf=\"form.controls.usageMode.hasError('required')\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n </ion-item>\n\n <!-- lat/long format-->\n <ion-item lines=\"none\">\n <mat-form-field>\n <ion-icon matPrefix name=\"locate\"></ion-icon>\n <mat-label>{{ 'SETTINGS.LAT_LONG_FORMAT' | translate }}</mat-label>\n <mat-select formControlName=\"latLongFormat\" required>\n <mat-option *ngFor=\"let item of latLongFormats\" [value]=\"item\">\n {{ 'COMMON.LAT_LONG.ENUM.' + item | uppercase | translate }}\n </mat-option>\n </mat-select>\n <mat-error *ngIf=\"form.controls.latLongFormat.hasError('required')\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n </ion-item>\n </ion-list>\n\n <!-- fields options -->\n <app-properties-form\n #propertiesForm\n formArrayName=\"properties\"\n [definitions]=\"propertyDefinitions\"\n [options]=\"{ allowEmptyArray: true }\"\n [mobile]=\"mobile\"\n [showHintKey]=\"showOptionKeys\"\n addButtonText=\"COMMON.BTN_SHOW_MORE\"\n addButtonTitle=\"SETTINGS.BTN_SHOW_MORE_HELP\"\n [debug]=\"debug\"\n ></app-properties-form>\n </ion-col>\n\n <!-- right margin -->\n <ion-col size=\"0\" size-lg=\"1\" size-xl=\"2\">&nbsp;</ion-col>\n </ion-row>\n </ion-grid>\n </form>\n</ion-content>\n\n<ion-footer hidden-xs hidden-sm hidden-mobile>\n <app-form-buttons-bar\n (onCancel)=\"cancel()\"\n (onSave)=\"save($event)\"\n [disabled]=\"!form.dirty || saving\"\n ></app-form-buttons-bar>\n</ion-footer>\n", dependencies: [{ kind: "directive", type: i3$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: i2$1.IonListHeader, selector: "ion-list-header", inputs: ["color", "lines", "mode"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "component", type: i2$1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1$3.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i3.MatPrefix, selector: "[matPrefix], [matIconPrefix], [matTextPrefix]", inputs: ["matTextPrefix"] }, { kind: "directive", type: i3.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i5.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: i6$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i11$1.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i12.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i12.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i12.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i6$3.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i2.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i9.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "component", type: i12$2.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "directive", type: i1$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: ToolbarComponent, selector: "app-toolbar", inputs: ["progressBarMode", "title", "color", "class", "id", "backHref", "defaultBackHref", "hasValidate", "hasClose", "hasSearch", "canGoBack", "canShowMenu"], outputs: ["onValidate", "onClose", "onValidateAndClose", "onBackClick", "onSearchInput", "onSearch"] }, { kind: "component", type: FormButtonsBarComponent, selector: "app-form-buttons-bar", inputs: ["disabled", "disabledCancel", "disabledEscape", "classList", "saveButtonColor", "backText", "cancelText", "nextText", "showBack", "showCancel", "showNext", "showSave"], outputs: ["onCancel", "onSave", "onNext", "onBack", "onSaveAndClose", "onSaveAndNext"] }, { kind: "component", type: AppPropertiesForm, selector: "app-properties-form", inputs: ["showToolbar", "formArrayName", "formArray", "options", "chipColor", "mobile", "appearance", "subscriptSizing", "showHintKey", "hintKeyPrefix", "showMoreButton", "addButtonText", "addButtonTitle", "showAddButton", "panelClass", "panelWidth", "canDownload", "canImport", "showMoreButtonTitle", "definitions", "importPolicy"] }, { kind: "pipe", type: i3$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.UpperCasePipe, name: "uppercase" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
40649
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SettingsPage, selector: "page-settings", host: { listeners: { "window:beforeunload": "handleRefresh($event)" } }, providers: [{ provide: ValidatorService, useExisting: LocalSettingsValidatorService }], viewQueries: [{ propertyName: "propertiesForm", first: true, predicate: ["propertiesForm"], descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: "@let hasValidate = !loading && (dirty || saving);\n<app-toolbar\n [title]=\"'SETTINGS.TITLE' | translate\"\n color=\"primary\"\n [hasValidate]=\"hasValidate\"\n [hasClose]=\"!hasValidate\"\n (onValidate)=\"save($event)\"\n (onClose)=\"close($event)\"\n>\n <ion-buttons slot=\"end\">\n <!-- options menu -->\n <ion-button [matMenuTriggerFor]=\"optionsMenu\" [disabled]=\"loading || saving\">\n <mat-icon slot=\"icon-only\">more_vert</mat-icon>\n </ion-button>\n </ion-buttons>\n</app-toolbar>\n\n<!-- Options menu -->\n<mat-menu #optionsMenu=\"matMenu\">\n <!-- Clear cache -->\n <button mat-menu-item [disabled]=\"saving\" (click)=\"clearCache($event)\">\n <mat-icon><ion-icon name=\"trash-outline\"></ion-icon></mat-icon>\n <ion-label translate>SETTINGS.BTN_CLEAR_CACHE</ion-label>\n </button>\n\n <!-- Reset -->\n <button mat-menu-item [disabled]=\"saving || !dirty\" (click)=\"cancel($event)\">\n <mat-icon><ion-icon name=\"refresh\"></ion-icon></mat-icon>\n <mat-label translate>COMMON.BTN_RESET</mat-label>\n </button>\n\n <!-- additional items -->\n @if (!loading) {\n @for (item of menuItems; track item) {\n @if (item.path) {\n <a mat-menu-item class=\"{{ item.cssClass }} {{ item.color }}\" (click)=\"executeAction($event, item)\">\n <mat-icon *ngIf=\"item.icon\"><ion-icon [name]=\"item.icon\"></ion-icon></mat-icon>\n <mat-icon *ngIf=\"item.matIcon\">{{ item.matIcon }}</mat-icon>\n <mat-label translate>{{ item.title }}</mat-label>\n </a>\n } @else if (!item.action) {\n <!-- divider -->\n <mat-divider class=\"{{ item.cssClass }} {{ item.color }}\">\n <mat-label translate>{{ item.title }}</mat-label>\n </mat-divider>\n }\n }\n }\n\n <!-- debug -->\n @if (isAdmin) {\n <mat-divider></mat-divider>\n\n <button\n mat-menu-item\n #menuTrigger=\"matMenuTrigger\"\n (mouseenter)=\"menuTrigger.openMenu()\"\n [matMenuTriggerFor]=\"debugMenu\"\n >\n <mat-icon><ion-icon name=\"bug\"></ion-icon></mat-icon>\n <ion-label translate>COMMON.DEBUG.BTN_DEBUG_DOTS</ion-label>\n </button>\n\n <mat-menu #debugMenu=\"matMenu\">\n <!-- enable debug -->\n <button mat-menu-item (click)=\"devToggleDebug()\">\n <mat-icon>{{ debug ? 'check_box' : 'check_box_outline_blank' }}</mat-icon>\n <mat-label translate>COMMON.DEBUG.BTN_ENABLE_DEBUG</mat-label>\n </button>\n\n <!-- show option key-->\n <button mat-menu-item (click)=\"toggleShowOptionKeys()\">\n <mat-icon>{{ showOptionKeys ? 'check_box' : 'check_box_outline_blank' }}</mat-icon>\n <mat-label translate>SETTINGS.BTN_SHOW_OPTION_KEY</mat-label>\n </button>\n </mat-menu>\n }\n</mat-menu>\n\n<ion-content>\n <form [formGroup]=\"form\" novalidate (ngSubmit)=\"save($event)\" class=\"form-container\">\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <!-- left margin -->\n <ion-col size=\"0\" size-lg=\"1\" size-xl=\"2\">&nbsp;</ion-col>\n\n <ion-col class=\"ion-padding\">\n <!-- error -->\n <ion-item *ngIf=\"errorSubject | async; let error\" lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n\n <ion-list-header class=\"hidden-xs hidden-xxs\" translate>SETTINGS.DESCRIPTION</ion-list-header>\n\n <ion-list [inset]=\"mobile\" [class.cdk-visually-hidden]=\"!isLogin\">\n <ion-item lines=\"none\">\n <!-- account inheritance (desktop) -->\n @if (!mobile) {\n <mat-form-field>\n <input matInput hidden formControlName=\"accountInheritance\" type=\"text\" />\n\n <!-- check box (if desktop) -->\n <mat-checkbox\n (change)=\"setAccountInheritance($event.checked, { updateInheritanceValues: true })\"\n [checked]=\"accountInheritance\"\n >\n <span>{{ 'SETTINGS.INHERIT_FROM_ACCOUNT' | translate }}</span>\n </mat-checkbox>\n\n <mat-error *ngIf=\"form.controls.accountInheritance.hasError('required')\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n } @else {\n <!-- account inheritance (if mobile) -->\n <mat-form-field>\n <input matInput hidden formControlName=\"accountInheritance\" type=\"text\" />\n\n <!-- slide toggle -->\n <ion-text translate>SETTINGS.INHERIT_FROM_ACCOUNT</ion-text>\n <mat-slide-toggle\n matSuffix\n (change)=\"setAccountInheritance($event.checked, { updateInheritanceValues: true })\"\n [checked]=\"accountInheritance\"\n ></mat-slide-toggle>\n <mat-error *ngIf=\"form.controls.accountInheritance.hasError('required')\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n }\n </ion-item>\n\n <!-- help on account -->\n @if (accountInheritance) {\n <ion-item [button]=\"true\" lines=\"none\" (click)=\"openAccountPage()\">\n @if (!mobile) {\n <ion-icon slot=\"start\" name=\"help-circle-outline\"></ion-icon>\n }\n <ion-label>\n <small [innerHTML]=\"'SETTINGS.INHERIT_FROM_ACCOUNT_HELP' | translate\"></small>\n </ion-label>\n <ion-text color=\"tertiary\" translate>SETTINGS.BTN_SHOW_ACCOUNT</ion-text>\n <ion-icon slot=\"end\" color=\"tertiary\" name=\"chevron-forward\"></ion-icon>\n </ion-item>\n }\n </ion-list>\n\n <ion-list-header><h3 translate>SETTINGS.DISPLAY_DIVIDER</h3></ion-list-header>\n\n <ion-list [inset]=\"mobile\">\n <!-- locale -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <ion-icon matPrefix name=\"language\"></ion-icon>\n <mat-label>{{ 'SETTINGS.LOCALE' | translate }}</mat-label>\n <mat-select formControlName=\"locale\" required>\n <mat-option *ngFor=\"let item of locales\" [value]=\"item.key\">\n {{ item.value }}\n </mat-option>\n </mat-select>\n <mat-error *ngIf=\"form.controls.locale.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n </mat-form-field>\n </ion-item>\n\n <!-- dark mode (if allow by the environment token) -->\n @if (settings.allowDarkMode) {\n <ion-item lines=\"none\">\n <mat-form-field>\n <ion-icon matPrefix name=\"moon\"></ion-icon>\n\n <input matInput hidden formControlName=\"darkMode\" type=\"text\" />\n\n <!-- slide toggle -->\n <ion-text translate>SETTINGS.BTN_DARK_MODE</ion-text>\n <mat-slide-toggle\n matSuffix\n (change)=\"toggleDarkMode($event.checked)\"\n [checked]=\"darkMode\"\n ></mat-slide-toggle>\n </mat-form-field>\n </ion-item>\n }\n </ion-list>\n\n <!-- Network entry -->\n <ion-list-header><h3 translate>SETTINGS.NETWORK_DIVIDER</h3></ion-list-header>\n\n <ion-list [inset]=\"mobile\">\n <!-- Peer address -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <ion-icon matPrefix name=\"cloud\"></ion-icon>\n\n <mat-label>{{ 'SETTINGS.PEER_URL' | translate }}</mat-label>\n <input matInput type=\"text\" formControlName=\"peerUrl\" required />\n\n <button\n mat-icon-button\n type=\"button\"\n matSuffix\n (click)=\"showSelectPeerModal($event)\"\n tabindex=\"-1\"\n [title]=\"!showTooltip ? ('SETTINGS.BTN_CHANGE_PEER' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('SETTINGS.BTN_CHANGE_PEER' | translate) : ''\"\n >\n <mat-icon>search</mat-icon>\n </button>\n\n <mat-error *ngIf=\"form.controls.peerUrl.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngIf=\"form.controls.peerUrl.hasError('peerAlive')\" translate>\n SETTINGS.ERROR.PEER_NOT_REACHABLE\n </mat-error>\n <mat-error *ngIf=\"form.controls.peerUrl.hasError('peerNotCompatible')\" translate>\n SETTINGS.ERROR.PEER_NOT_COMPATIBLE\n </mat-error>\n </mat-form-field>\n </ion-item>\n\n <!-- Offline mode (if mobile) -->\n @if (mobile) {\n <ion-item lines=\"none\">\n <mat-form-field>\n <input matInput hidden formControlName=\"accountInheritance\" type=\"text\" />\n\n <!-- slide toggle -->\n <ion-text translate>SETTINGS.OFFLINE_MODE</ion-text>\n <mat-slide-toggle\n matSuffix\n (change)=\"setForceOffline($event.checked)\"\n [checked]=\"network.offline\"\n ></mat-slide-toggle>\n </mat-form-field>\n </ion-item>\n }\n </ion-list>\n\n <!-- Data entry -->\n <ion-list-header>\n <h3 translate>SETTINGS.DATA_ENTRY_DIVIDER</h3>\n </ion-list-header>\n\n <ion-list [inset]=\"mobile\">\n <!-- Usage mode -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <ion-icon matPrefix name=\"id-card\"></ion-icon>\n <mat-label>{{ 'SETTINGS.USAGE_MODE' | translate }}</mat-label>\n <mat-select formControlName=\"usageMode\" required>\n <mat-option *ngFor=\"let item of usageModes\" [value]=\"item\">\n {{ 'SETTINGS.USAGE_MODES.' + item | translate }}\n </mat-option>\n </mat-select>\n <mat-error *ngIf=\"form.controls.usageMode.hasError('required')\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n </ion-item>\n\n <!-- lat/long format-->\n <ion-item lines=\"none\">\n <mat-form-field>\n <ion-icon matPrefix name=\"locate\"></ion-icon>\n <mat-label>{{ 'SETTINGS.LAT_LONG_FORMAT' | translate }}</mat-label>\n <mat-select formControlName=\"latLongFormat\" required>\n <mat-option *ngFor=\"let item of latLongFormats\" [value]=\"item\">\n {{ 'COMMON.LAT_LONG.ENUM.' + item | uppercase | translate }}\n </mat-option>\n </mat-select>\n <mat-error *ngIf=\"form.controls.latLongFormat.hasError('required')\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n </ion-item>\n </ion-list>\n\n <!-- fields options -->\n <app-properties-form\n #propertiesForm\n formArrayName=\"properties\"\n [definitions]=\"propertyDefinitions\"\n [options]=\"{ allowEmptyArray: true }\"\n [mobile]=\"mobile\"\n [showHintKey]=\"showOptionKeys\"\n addButtonText=\"COMMON.BTN_SHOW_MORE\"\n addButtonTitle=\"SETTINGS.BTN_SHOW_MORE_HELP\"\n [debug]=\"debug\"\n ></app-properties-form>\n </ion-col>\n\n <!-- right margin -->\n <ion-col size=\"0\" size-lg=\"1\" size-xl=\"2\">&nbsp;</ion-col>\n </ion-row>\n </ion-grid>\n </form>\n</ion-content>\n\n<ion-footer hidden-xs hidden-sm hidden-mobile>\n <app-form-buttons-bar\n (onCancel)=\"cancel()\"\n (onSave)=\"save($event)\"\n [disabled]=\"!form.dirty || saving\"\n ></app-form-buttons-bar>\n</ion-footer>\n", dependencies: [{ kind: "directive", type: i3$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: i2$1.IonListHeader, selector: "ion-list-header", inputs: ["color", "lines", "mode"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "component", type: i2$1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1$3.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i3.MatPrefix, selector: "[matPrefix], [matIconPrefix], [matTextPrefix]", inputs: ["matTextPrefix"] }, { kind: "directive", type: i3.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i5.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: i6$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i11$1.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i12.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i12.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i12.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i6$3.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i2.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i9.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "component", type: i12$2.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "directive", type: i1$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: ToolbarComponent, selector: "app-toolbar", inputs: ["progressBarMode", "title", "color", "class", "id", "backHref", "defaultBackHref", "hasValidate", "hasClose", "hasSearch", "canGoBack", "canShowMenu"], outputs: ["onValidate", "onClose", "onValidateAndClose", "onBackClick", "onSearchInput", "onSearch"] }, { kind: "component", type: FormButtonsBarComponent, selector: "app-form-buttons-bar", inputs: ["disabled", "disabledCancel", "disabledEscape", "classList", "saveButtonColor", "backText", "cancelText", "nextText", "showBack", "showCancel", "showNext", "showSave"], outputs: ["onCancel", "onSave", "onNext", "onBack", "onSaveAndClose", "onSaveAndNext"] }, { kind: "component", type: AppPropertiesForm, selector: "app-properties-form", inputs: ["showToolbar", "formArrayName", "formArray", "options", "chipColor", "mobile", "appearance", "subscriptSizing", "showHintKey", "hintKeyPrefix", "showMoreButton", "addButtonText", "addButtonTitle", "showAddButton", "panelClass", "panelWidth", "canDownload", "canImport", "showMoreButtonTitle", "definitions", "importPolicy"] }, { kind: "pipe", type: i3$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.UpperCasePipe, name: "uppercase" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
40263
40650
  }
40264
40651
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SettingsPage, decorators: [{
40265
40652
  type: Component,
40266
- args: [{ selector: 'page-settings', providers: [{ provide: ValidatorService, useExisting: LocalSettingsValidatorService }], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let hasValidate = !loading && (dirty || saving);\n<app-toolbar\n [title]=\"'SETTINGS.TITLE' | translate\"\n color=\"primary\"\n [hasValidate]=\"hasValidate\"\n [hasClose]=\"!hasValidate\"\n (onValidate)=\"save($event)\"\n (onClose)=\"close($event)\"\n>\n <ion-buttons slot=\"end\">\n <!-- options menu -->\n <ion-button [matMenuTriggerFor]=\"optionsMenu\" [disabled]=\"loading || saving\">\n <mat-icon slot=\"icon-only\">more_vert</mat-icon>\n </ion-button>\n </ion-buttons>\n</app-toolbar>\n\n<!-- Options menu -->\n<mat-menu #optionsMenu=\"matMenu\">\n <!-- Clear cache -->\n <button mat-menu-item [disabled]=\"saving\" (click)=\"clearCache($event)\">\n <mat-icon><ion-icon name=\"trash-outline\"></ion-icon></mat-icon>\n <ion-label translate>SETTINGS.BTN_CLEAR_CACHE</ion-label>\n </button>\n\n <!-- Reset -->\n <button mat-menu-item [disabled]=\"saving || !dirty\" (click)=\"cancel($event)\">\n <mat-icon><ion-icon name=\"refresh\"></ion-icon></mat-icon>\n <mat-label translate>COMMON.BTN_RESET</mat-label>\n </button>\n\n <!-- additional items -->\n @if (!loading) {\n @for (item of menuItems; track item) {\n @if (item.path) {\n <a mat-menu-item class=\"{{ item.cssClass }} {{ item.color }}\" (click)=\"executeAction($event, item)\">\n <mat-icon *ngIf=\"item.icon\"><ion-icon [name]=\"item.icon\"></ion-icon></mat-icon>\n <mat-icon *ngIf=\"item.matIcon\">{{ item.matIcon }}</mat-icon>\n <mat-label translate>{{ item.title }}</mat-label>\n </a>\n } @else if (!item.action) {\n <!-- divider -->\n <mat-divider class=\"{{ item.cssClass }} {{ item.color }}\">\n <mat-label translate>{{ item.title }}</mat-label>\n </mat-divider>\n }\n }\n }\n\n <!-- debug -->\n @if (isAdmin) {\n <mat-divider></mat-divider>\n\n <button\n mat-menu-item\n #menuTrigger=\"matMenuTrigger\"\n (mouseenter)=\"menuTrigger.openMenu()\"\n [matMenuTriggerFor]=\"debugMenu\"\n >\n <mat-icon><ion-icon name=\"bug\"></ion-icon></mat-icon>\n <ion-label translate>COMMON.DEBUG.BTN_DEBUG_DOTS</ion-label>\n </button>\n\n <mat-menu #debugMenu=\"matMenu\">\n <!-- enable debug -->\n <button mat-menu-item (click)=\"devToggleDebug()\">\n <mat-icon>{{ debug ? 'check_box' : 'check_box_outline_blank' }}</mat-icon>\n <mat-label translate>COMMON.DEBUG.BTN_ENABLE_DEBUG</mat-label>\n </button>\n\n <!-- show option key-->\n <button mat-menu-item (click)=\"toggleShowOptionKeys()\">\n <mat-icon>{{ showOptionKeys ? 'check_box' : 'check_box_outline_blank' }}</mat-icon>\n <mat-label translate>SETTINGS.BTN_SHOW_OPTION_KEY</mat-label>\n </button>\n </mat-menu>\n }\n</mat-menu>\n\n<ion-content>\n <form [formGroup]=\"form\" novalidate (ngSubmit)=\"save($event)\" class=\"form-container\">\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <!-- left margin -->\n <ion-col size=\"0\" size-lg=\"1\" size-xl=\"2\">&nbsp;</ion-col>\n\n <ion-col class=\"ion-padding\">\n <!-- error -->\n <ion-item *ngIf=\"errorSubject | async; let error\" lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n\n <ion-list-header class=\"hidden-xs hidden-xxs\" translate>SETTINGS.DESCRIPTION</ion-list-header>\n\n <ion-list [inset]=\"mobile\" [class.cdk-visually-hidden]=\"!isLogin\">\n <ion-item lines=\"none\">\n <!-- account inheritance (desktop) -->\n @if (!mobile) {\n <mat-form-field>\n <input matInput hidden formControlName=\"accountInheritance\" type=\"text\" />\n\n <!-- check box (if desktop) -->\n <mat-checkbox (change)=\"setAccountInheritance($event.checked)\" [checked]=\"accountInheritance\">\n <span translate>SETTINGS.INHERIT_FROM_ACCOUNT</span>\n </mat-checkbox>\n\n <mat-error *ngIf=\"form.controls.accountInheritance.hasError('required')\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n } @else {\n <!-- account inheritance (if mobile) -->\n <mat-form-field>\n <input matInput hidden formControlName=\"accountInheritance\" type=\"text\" />\n\n <!-- slide toggle -->\n <ion-text translate>SETTINGS.INHERIT_FROM_ACCOUNT</ion-text>\n <mat-slide-toggle\n matSuffix\n (change)=\"setAccountInheritance($event.checked)\"\n [checked]=\"accountInheritance\"\n ></mat-slide-toggle>\n <mat-error *ngIf=\"form.controls.accountInheritance.hasError('required')\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n }\n </ion-item>\n\n <!-- help on account -->\n @if (accountInheritance) {\n <ion-item [button]=\"true\" lines=\"none\" (click)=\"openAccountPage()\">\n @if (!mobile) {\n <ion-icon slot=\"start\" name=\"help-circle-outline\"></ion-icon>\n }\n <ion-label>\n <small [innerHTML]=\"'SETTINGS.INHERIT_FROM_ACCOUNT_HELP' | translate\"></small>\n </ion-label>\n <ion-text color=\"tertiary\" translate>SETTINGS.BTN_SHOW_ACCOUNT</ion-text>\n <ion-icon slot=\"end\" color=\"tertiary\" name=\"chevron-forward\"></ion-icon>\n </ion-item>\n }\n </ion-list>\n\n <ion-list-header><h3 translate>SETTINGS.DISPLAY_DIVIDER</h3></ion-list-header>\n\n <ion-list [inset]=\"mobile\">\n <!-- locale -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <ion-icon matPrefix name=\"language\"></ion-icon>\n <mat-label>{{ 'SETTINGS.LOCALE' | translate }}</mat-label>\n <mat-select formControlName=\"locale\" required>\n <mat-option *ngFor=\"let item of locales\" [value]=\"item.key\">\n {{ item.value }}\n </mat-option>\n </mat-select>\n <mat-error *ngIf=\"form.controls.locale.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n </mat-form-field>\n </ion-item>\n\n <!-- dark mode (if allow by the environment token) -->\n @if (settings.allowDarkMode) {\n <ion-item lines=\"none\">\n <mat-form-field>\n <ion-icon matPrefix name=\"moon\"></ion-icon>\n\n <input matInput hidden formControlName=\"darkMode\" type=\"text\" />\n\n <!-- slide toggle -->\n <ion-text translate>SETTINGS.BTN_DARK_MODE</ion-text>\n <mat-slide-toggle\n matSuffix\n (change)=\"toggleDarkMode($event.checked)\"\n [checked]=\"darkMode\"\n ></mat-slide-toggle>\n </mat-form-field>\n </ion-item>\n }\n </ion-list>\n\n <!-- Network entry -->\n <ion-list-header><h3 translate>SETTINGS.NETWORK_DIVIDER</h3></ion-list-header>\n\n <ion-list [inset]=\"mobile\">\n <!-- Peer address -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <ion-icon matPrefix name=\"cloud\"></ion-icon>\n\n <mat-label>{{ 'SETTINGS.PEER_URL' | translate }}</mat-label>\n <input matInput type=\"text\" formControlName=\"peerUrl\" required />\n\n <button\n mat-icon-button\n type=\"button\"\n matSuffix\n (click)=\"showSelectPeerModal()\"\n tabindex=\"-1\"\n [title]=\"!showTooltip ? ('SETTINGS.BTN_CHANGE_PEER' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('SETTINGS.BTN_CHANGE_PEER' | translate) : ''\"\n >\n <mat-icon>search</mat-icon>\n </button>\n\n <mat-error *ngIf=\"form.controls.peerUrl.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngIf=\"form.controls.peerUrl.hasError('peerAlive')\" translate>\n SETTINGS.ERROR.PEER_NOT_REACHABLE\n </mat-error>\n <mat-error *ngIf=\"form.controls.peerUrl.hasError('peerNotCompatible')\" translate>\n SETTINGS.ERROR.PEER_NOT_COMPATIBLE\n </mat-error>\n </mat-form-field>\n </ion-item>\n\n <!-- Offline mode (if mobile) -->\n @if (mobile) {\n <ion-item lines=\"none\">\n <mat-form-field>\n <input matInput hidden formControlName=\"accountInheritance\" type=\"text\" />\n\n <!-- slide toggle -->\n <ion-text translate>SETTINGS.OFFLINE_MODE</ion-text>\n <mat-slide-toggle\n matSuffix\n (change)=\"setForceOffline($event.checked)\"\n [checked]=\"network.offline\"\n ></mat-slide-toggle>\n </mat-form-field>\n </ion-item>\n }\n </ion-list>\n\n <!-- Data entry -->\n <ion-list-header>\n <h3 translate>SETTINGS.DATA_ENTRY_DIVIDER</h3>\n </ion-list-header>\n\n <ion-list [inset]=\"mobile\">\n <!-- Usage mode -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <ion-icon matPrefix name=\"id-card\"></ion-icon>\n <mat-label>{{ 'SETTINGS.USAGE_MODE' | translate }}</mat-label>\n <mat-select formControlName=\"usageMode\" required>\n <mat-option *ngFor=\"let item of usageModes\" [value]=\"item\">\n {{ 'SETTINGS.USAGE_MODES.' + item | translate }}\n </mat-option>\n </mat-select>\n <mat-error *ngIf=\"form.controls.usageMode.hasError('required')\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n </ion-item>\n\n <!-- lat/long format-->\n <ion-item lines=\"none\">\n <mat-form-field>\n <ion-icon matPrefix name=\"locate\"></ion-icon>\n <mat-label>{{ 'SETTINGS.LAT_LONG_FORMAT' | translate }}</mat-label>\n <mat-select formControlName=\"latLongFormat\" required>\n <mat-option *ngFor=\"let item of latLongFormats\" [value]=\"item\">\n {{ 'COMMON.LAT_LONG.ENUM.' + item | uppercase | translate }}\n </mat-option>\n </mat-select>\n <mat-error *ngIf=\"form.controls.latLongFormat.hasError('required')\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n </ion-item>\n </ion-list>\n\n <!-- fields options -->\n <app-properties-form\n #propertiesForm\n formArrayName=\"properties\"\n [definitions]=\"propertyDefinitions\"\n [options]=\"{ allowEmptyArray: true }\"\n [mobile]=\"mobile\"\n [showHintKey]=\"showOptionKeys\"\n addButtonText=\"COMMON.BTN_SHOW_MORE\"\n addButtonTitle=\"SETTINGS.BTN_SHOW_MORE_HELP\"\n [debug]=\"debug\"\n ></app-properties-form>\n </ion-col>\n\n <!-- right margin -->\n <ion-col size=\"0\" size-lg=\"1\" size-xl=\"2\">&nbsp;</ion-col>\n </ion-row>\n </ion-grid>\n </form>\n</ion-content>\n\n<ion-footer hidden-xs hidden-sm hidden-mobile>\n <app-form-buttons-bar\n (onCancel)=\"cancel()\"\n (onSave)=\"save($event)\"\n [disabled]=\"!form.dirty || saving\"\n ></app-form-buttons-bar>\n</ion-footer>\n" }]
40653
+ args: [{ selector: 'page-settings', providers: [{ provide: ValidatorService, useExisting: LocalSettingsValidatorService }], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let hasValidate = !loading && (dirty || saving);\n<app-toolbar\n [title]=\"'SETTINGS.TITLE' | translate\"\n color=\"primary\"\n [hasValidate]=\"hasValidate\"\n [hasClose]=\"!hasValidate\"\n (onValidate)=\"save($event)\"\n (onClose)=\"close($event)\"\n>\n <ion-buttons slot=\"end\">\n <!-- options menu -->\n <ion-button [matMenuTriggerFor]=\"optionsMenu\" [disabled]=\"loading || saving\">\n <mat-icon slot=\"icon-only\">more_vert</mat-icon>\n </ion-button>\n </ion-buttons>\n</app-toolbar>\n\n<!-- Options menu -->\n<mat-menu #optionsMenu=\"matMenu\">\n <!-- Clear cache -->\n <button mat-menu-item [disabled]=\"saving\" (click)=\"clearCache($event)\">\n <mat-icon><ion-icon name=\"trash-outline\"></ion-icon></mat-icon>\n <ion-label translate>SETTINGS.BTN_CLEAR_CACHE</ion-label>\n </button>\n\n <!-- Reset -->\n <button mat-menu-item [disabled]=\"saving || !dirty\" (click)=\"cancel($event)\">\n <mat-icon><ion-icon name=\"refresh\"></ion-icon></mat-icon>\n <mat-label translate>COMMON.BTN_RESET</mat-label>\n </button>\n\n <!-- additional items -->\n @if (!loading) {\n @for (item of menuItems; track item) {\n @if (item.path) {\n <a mat-menu-item class=\"{{ item.cssClass }} {{ item.color }}\" (click)=\"executeAction($event, item)\">\n <mat-icon *ngIf=\"item.icon\"><ion-icon [name]=\"item.icon\"></ion-icon></mat-icon>\n <mat-icon *ngIf=\"item.matIcon\">{{ item.matIcon }}</mat-icon>\n <mat-label translate>{{ item.title }}</mat-label>\n </a>\n } @else if (!item.action) {\n <!-- divider -->\n <mat-divider class=\"{{ item.cssClass }} {{ item.color }}\">\n <mat-label translate>{{ item.title }}</mat-label>\n </mat-divider>\n }\n }\n }\n\n <!-- debug -->\n @if (isAdmin) {\n <mat-divider></mat-divider>\n\n <button\n mat-menu-item\n #menuTrigger=\"matMenuTrigger\"\n (mouseenter)=\"menuTrigger.openMenu()\"\n [matMenuTriggerFor]=\"debugMenu\"\n >\n <mat-icon><ion-icon name=\"bug\"></ion-icon></mat-icon>\n <ion-label translate>COMMON.DEBUG.BTN_DEBUG_DOTS</ion-label>\n </button>\n\n <mat-menu #debugMenu=\"matMenu\">\n <!-- enable debug -->\n <button mat-menu-item (click)=\"devToggleDebug()\">\n <mat-icon>{{ debug ? 'check_box' : 'check_box_outline_blank' }}</mat-icon>\n <mat-label translate>COMMON.DEBUG.BTN_ENABLE_DEBUG</mat-label>\n </button>\n\n <!-- show option key-->\n <button mat-menu-item (click)=\"toggleShowOptionKeys()\">\n <mat-icon>{{ showOptionKeys ? 'check_box' : 'check_box_outline_blank' }}</mat-icon>\n <mat-label translate>SETTINGS.BTN_SHOW_OPTION_KEY</mat-label>\n </button>\n </mat-menu>\n }\n</mat-menu>\n\n<ion-content>\n <form [formGroup]=\"form\" novalidate (ngSubmit)=\"save($event)\" class=\"form-container\">\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <!-- left margin -->\n <ion-col size=\"0\" size-lg=\"1\" size-xl=\"2\">&nbsp;</ion-col>\n\n <ion-col class=\"ion-padding\">\n <!-- error -->\n <ion-item *ngIf=\"errorSubject | async; let error\" lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n\n <ion-list-header class=\"hidden-xs hidden-xxs\" translate>SETTINGS.DESCRIPTION</ion-list-header>\n\n <ion-list [inset]=\"mobile\" [class.cdk-visually-hidden]=\"!isLogin\">\n <ion-item lines=\"none\">\n <!-- account inheritance (desktop) -->\n @if (!mobile) {\n <mat-form-field>\n <input matInput hidden formControlName=\"accountInheritance\" type=\"text\" />\n\n <!-- check box (if desktop) -->\n <mat-checkbox\n (change)=\"setAccountInheritance($event.checked, { updateInheritanceValues: true })\"\n [checked]=\"accountInheritance\"\n >\n <span>{{ 'SETTINGS.INHERIT_FROM_ACCOUNT' | translate }}</span>\n </mat-checkbox>\n\n <mat-error *ngIf=\"form.controls.accountInheritance.hasError('required')\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n } @else {\n <!-- account inheritance (if mobile) -->\n <mat-form-field>\n <input matInput hidden formControlName=\"accountInheritance\" type=\"text\" />\n\n <!-- slide toggle -->\n <ion-text translate>SETTINGS.INHERIT_FROM_ACCOUNT</ion-text>\n <mat-slide-toggle\n matSuffix\n (change)=\"setAccountInheritance($event.checked, { updateInheritanceValues: true })\"\n [checked]=\"accountInheritance\"\n ></mat-slide-toggle>\n <mat-error *ngIf=\"form.controls.accountInheritance.hasError('required')\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n }\n </ion-item>\n\n <!-- help on account -->\n @if (accountInheritance) {\n <ion-item [button]=\"true\" lines=\"none\" (click)=\"openAccountPage()\">\n @if (!mobile) {\n <ion-icon slot=\"start\" name=\"help-circle-outline\"></ion-icon>\n }\n <ion-label>\n <small [innerHTML]=\"'SETTINGS.INHERIT_FROM_ACCOUNT_HELP' | translate\"></small>\n </ion-label>\n <ion-text color=\"tertiary\" translate>SETTINGS.BTN_SHOW_ACCOUNT</ion-text>\n <ion-icon slot=\"end\" color=\"tertiary\" name=\"chevron-forward\"></ion-icon>\n </ion-item>\n }\n </ion-list>\n\n <ion-list-header><h3 translate>SETTINGS.DISPLAY_DIVIDER</h3></ion-list-header>\n\n <ion-list [inset]=\"mobile\">\n <!-- locale -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <ion-icon matPrefix name=\"language\"></ion-icon>\n <mat-label>{{ 'SETTINGS.LOCALE' | translate }}</mat-label>\n <mat-select formControlName=\"locale\" required>\n <mat-option *ngFor=\"let item of locales\" [value]=\"item.key\">\n {{ item.value }}\n </mat-option>\n </mat-select>\n <mat-error *ngIf=\"form.controls.locale.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n </mat-form-field>\n </ion-item>\n\n <!-- dark mode (if allow by the environment token) -->\n @if (settings.allowDarkMode) {\n <ion-item lines=\"none\">\n <mat-form-field>\n <ion-icon matPrefix name=\"moon\"></ion-icon>\n\n <input matInput hidden formControlName=\"darkMode\" type=\"text\" />\n\n <!-- slide toggle -->\n <ion-text translate>SETTINGS.BTN_DARK_MODE</ion-text>\n <mat-slide-toggle\n matSuffix\n (change)=\"toggleDarkMode($event.checked)\"\n [checked]=\"darkMode\"\n ></mat-slide-toggle>\n </mat-form-field>\n </ion-item>\n }\n </ion-list>\n\n <!-- Network entry -->\n <ion-list-header><h3 translate>SETTINGS.NETWORK_DIVIDER</h3></ion-list-header>\n\n <ion-list [inset]=\"mobile\">\n <!-- Peer address -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <ion-icon matPrefix name=\"cloud\"></ion-icon>\n\n <mat-label>{{ 'SETTINGS.PEER_URL' | translate }}</mat-label>\n <input matInput type=\"text\" formControlName=\"peerUrl\" required />\n\n <button\n mat-icon-button\n type=\"button\"\n matSuffix\n (click)=\"showSelectPeerModal($event)\"\n tabindex=\"-1\"\n [title]=\"!showTooltip ? ('SETTINGS.BTN_CHANGE_PEER' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('SETTINGS.BTN_CHANGE_PEER' | translate) : ''\"\n >\n <mat-icon>search</mat-icon>\n </button>\n\n <mat-error *ngIf=\"form.controls.peerUrl.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngIf=\"form.controls.peerUrl.hasError('peerAlive')\" translate>\n SETTINGS.ERROR.PEER_NOT_REACHABLE\n </mat-error>\n <mat-error *ngIf=\"form.controls.peerUrl.hasError('peerNotCompatible')\" translate>\n SETTINGS.ERROR.PEER_NOT_COMPATIBLE\n </mat-error>\n </mat-form-field>\n </ion-item>\n\n <!-- Offline mode (if mobile) -->\n @if (mobile) {\n <ion-item lines=\"none\">\n <mat-form-field>\n <input matInput hidden formControlName=\"accountInheritance\" type=\"text\" />\n\n <!-- slide toggle -->\n <ion-text translate>SETTINGS.OFFLINE_MODE</ion-text>\n <mat-slide-toggle\n matSuffix\n (change)=\"setForceOffline($event.checked)\"\n [checked]=\"network.offline\"\n ></mat-slide-toggle>\n </mat-form-field>\n </ion-item>\n }\n </ion-list>\n\n <!-- Data entry -->\n <ion-list-header>\n <h3 translate>SETTINGS.DATA_ENTRY_DIVIDER</h3>\n </ion-list-header>\n\n <ion-list [inset]=\"mobile\">\n <!-- Usage mode -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <ion-icon matPrefix name=\"id-card\"></ion-icon>\n <mat-label>{{ 'SETTINGS.USAGE_MODE' | translate }}</mat-label>\n <mat-select formControlName=\"usageMode\" required>\n <mat-option *ngFor=\"let item of usageModes\" [value]=\"item\">\n {{ 'SETTINGS.USAGE_MODES.' + item | translate }}\n </mat-option>\n </mat-select>\n <mat-error *ngIf=\"form.controls.usageMode.hasError('required')\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n </ion-item>\n\n <!-- lat/long format-->\n <ion-item lines=\"none\">\n <mat-form-field>\n <ion-icon matPrefix name=\"locate\"></ion-icon>\n <mat-label>{{ 'SETTINGS.LAT_LONG_FORMAT' | translate }}</mat-label>\n <mat-select formControlName=\"latLongFormat\" required>\n <mat-option *ngFor=\"let item of latLongFormats\" [value]=\"item\">\n {{ 'COMMON.LAT_LONG.ENUM.' + item | uppercase | translate }}\n </mat-option>\n </mat-select>\n <mat-error *ngIf=\"form.controls.latLongFormat.hasError('required')\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n </ion-item>\n </ion-list>\n\n <!-- fields options -->\n <app-properties-form\n #propertiesForm\n formArrayName=\"properties\"\n [definitions]=\"propertyDefinitions\"\n [options]=\"{ allowEmptyArray: true }\"\n [mobile]=\"mobile\"\n [showHintKey]=\"showOptionKeys\"\n addButtonText=\"COMMON.BTN_SHOW_MORE\"\n addButtonTitle=\"SETTINGS.BTN_SHOW_MORE_HELP\"\n [debug]=\"debug\"\n ></app-properties-form>\n </ion-col>\n\n <!-- right margin -->\n <ion-col size=\"0\" size-lg=\"1\" size-xl=\"2\">&nbsp;</ion-col>\n </ion-row>\n </ion-grid>\n </form>\n</ion-content>\n\n<ion-footer hidden-xs hidden-sm hidden-mobile>\n <app-form-buttons-bar\n (onCancel)=\"cancel()\"\n (onSave)=\"save($event)\"\n [disabled]=\"!form.dirty || saving\"\n ></app-form-buttons-bar>\n</ion-footer>\n" }]
40267
40654
  }], ctorParameters: () => [{ type: i0.Injector }, { type: PlatformService }, { type: i2$1.NavController }, { type: LocalSettingsValidatorService }, { type: i2$1.AlertController }, { type: i1$1.TranslateService }, { type: i1$3.UntypedFormBuilder }, { type: AccountService }, { type: LocalSettingsService }, { type: i0.ChangeDetectorRef }, { type: NetworkService }, { type: undefined, decorators: [{
40268
40655
  type: Inject,
40269
40656
  args: [APP_LOCALES]
@@ -40884,6 +41271,7 @@ class AppTable {
40884
41271
  propagateRowError = false;
40885
41272
  permanentSelectionAllowed = false; // Allow keep selected rows over page change or sort change
40886
41273
  openRowThrottleTime = 800; // Prevent double click - see issue sumaris-app#864
41274
+ loadingDebounceTime = 250; // Debounce time for loading spinner
40887
41275
  defaultSortBy;
40888
41276
  defaultSortDirection;
40889
41277
  defaultPageSize = DEFAULT_PAGE_SIZE;
@@ -41922,9 +42310,12 @@ class AppTable {
41922
42310
  return true; // Already the edited row
41923
42311
  if (event?.defaultPrevented)
41924
42312
  return false;
41925
- const confirmed = this.confirmEditCreate();
41926
- if (!confirmed)
41927
- return false;
42313
+ // Don't call confirmEditCreate() if canceling
42314
+ if (!toBoolean(opts?.canceling, false)) {
42315
+ const confirmed = this.confirmEditCreate();
42316
+ if (!confirmed)
42317
+ return false;
42318
+ }
41928
42319
  if (!row.editing && !this.loading) {
41929
42320
  this.focusColumn = opts?.focusColumn ?? this.focusColumn;
41930
42321
  this._dataSource.startEdit(row);
@@ -42273,20 +42664,20 @@ class AppTable {
42273
42664
  }
42274
42665
  async askDeleteConfirmation(event, rows, opts) {
42275
42666
  if (isNotNilOrBlank(opts?.messageKey)) {
42276
- return Alerts.askConfirmation(opts.messageKey, this.alertCtrl, this.translate, event);
42667
+ return Alerts.askConfirmation(opts.messageKey, this.alertCtrl, this.translate, event, opts);
42277
42668
  }
42278
42669
  if (this.undoableDeletion) {
42279
42670
  // Special message, for undoable deletion
42280
- return Alerts.askConfirmation(rows?.length === 1 ? 'CONFIRM.DELETE_ROW' : 'CONFIRM.DELETE_ROWS', this.alertCtrl, this.translate, event);
42671
+ return Alerts.askConfirmation(rows?.length === 1 ? 'CONFIRM.DELETE_ROW' : 'CONFIRM.DELETE_ROWS', this.alertCtrl, this.translate, event, opts);
42281
42672
  }
42282
42673
  // Immediate deletion action
42283
- return Alerts.askActionConfirmation(this.alertCtrl, this.translate, true, event);
42674
+ return Alerts.askActionConfirmation(this.alertCtrl, this.translate, true, event, opts);
42284
42675
  }
42285
- async askCancelConfirmation(event, rows) {
42286
- return Alerts.askConfirmation(rows?.length === 1 ? 'CONFIRM.CANCEL_ROW' : 'CONFIRM.CANCEL_ROWS', this.alertCtrl, this.translate, event);
42676
+ async askCancelConfirmation(event, rows, opts) {
42677
+ return Alerts.askConfirmation(rows?.length === 1 ? 'CONFIRM.CANCEL_ROW' : 'CONFIRM.CANCEL_ROWS', this.alertCtrl, this.translate, event, opts);
42287
42678
  }
42288
- async askRestoreConfirmation(event) {
42289
- return Alerts.askActionConfirmation(this.alertCtrl, this.translate, false, event);
42679
+ async askRestoreConfirmation(event, opts) {
42680
+ return Alerts.askActionConfirmation(this.alertCtrl, this.translate, false, event, opts);
42290
42681
  }
42291
42682
  async showToast(opts) {
42292
42683
  if (!this.toastController)
@@ -42487,7 +42878,7 @@ class AppTable {
42487
42878
  this.checkIfRowPristine(row);
42488
42879
  // Restore editing state
42489
42880
  if (keepEditing) {
42490
- this.editRow(undefined, row);
42881
+ this.editRow(undefined, row, { canceling: true });
42491
42882
  }
42492
42883
  }
42493
42884
  async checkIfRowPristine(row, opts) {
@@ -42537,7 +42928,7 @@ class AppTable {
42537
42928
  // If changed to True: propagate as soon as possible
42538
42929
  tap$1((loading) => loading && this.setLoading(true)),
42539
42930
  // If changed to False: wait 250ms before propagate (to make sure the spinner has been displayed)
42540
- debounceTime(250), tap$1((loading) => !loading && this.setLoading(false)))
42931
+ debounceTime(this.loadingDebounceTime), tap$1((loading) => !loading && this.setLoading(false)))
42541
42932
  .subscribe();
42542
42933
  this.registerSubscription(this._dataSourceLoadingSubscription);
42543
42934
  }
@@ -42590,7 +42981,7 @@ class AppTable {
42590
42981
  }
42591
42982
  }
42592
42983
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AppTable, deps: "invalid", target: i0.ɵɵFactoryTarget.Directive });
42593
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: AppTable, inputs: { settingsId: "settingsId", debug: "debug", i18nColumnPrefix: "i18nColumnPrefix", i18nColumnSuffix: "i18nColumnSuffix", autoLoad: "autoLoad", readOnly: "readOnly", inlineEdition: "inlineEdition", focusFirstColumn: "focusFirstColumn", confirmBeforeDelete: "confirmBeforeDelete", confirmBeforeCancel: "confirmBeforeCancel", undoableDeletion: "undoableDeletion", saveBeforeDelete: "saveBeforeDelete", keepEditedRowOnSave: "keepEditedRowOnSave", saveBeforeSort: "saveBeforeSort", saveBeforeFilter: "saveBeforeFilter", propagateRowError: "propagateRowError", permanentSelectionAllowed: "permanentSelectionAllowed", openRowThrottleTime: "openRowThrottleTime", defaultSortBy: "defaultSortBy", defaultSortDirection: "defaultSortDirection", defaultPageSize: "defaultPageSize", defaultPageSizeOptions: "defaultPageSizeOptions", focusColumn: "focusColumn", dataSource: "dataSource", filter: "filter", disabled: "disabled", paginator: "paginator" }, outputs: { onRefresh: "onRefresh", onClickRow: "onClickRow", onOpenRow: "onOpenRow", onNewRow: "onNewRow", onStartEditingRow: "onStartEditingRow", onConfirmEditCreateRow: "onConfirmEditCreateRow", onCancelOrDeleteRow: "onCancelOrDeleteRow", onBeforeDeleteRows: "onBeforeDeleteRows", onBeforeCancelRows: "onBeforeCancelRows", onBeforeSave: "onBeforeSave", onAfterDeletedRows: "onAfterDeletedRows", onSort: "onSort", onDirty: "onDirty", onError: "onError" }, viewQueries: [{ propertyName: "table", first: true, predicate: MatTable, descendants: true }, { propertyName: "childPaginator", first: true, predicate: MatPaginator, descendants: true }, { propertyName: "sort", first: true, predicate: MatSort, descendants: true }], ngImport: i0 });
42984
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: AppTable, inputs: { settingsId: "settingsId", debug: "debug", i18nColumnPrefix: "i18nColumnPrefix", i18nColumnSuffix: "i18nColumnSuffix", autoLoad: "autoLoad", readOnly: "readOnly", inlineEdition: "inlineEdition", focusFirstColumn: "focusFirstColumn", confirmBeforeDelete: "confirmBeforeDelete", confirmBeforeCancel: "confirmBeforeCancel", undoableDeletion: "undoableDeletion", saveBeforeDelete: "saveBeforeDelete", keepEditedRowOnSave: "keepEditedRowOnSave", saveBeforeSort: "saveBeforeSort", saveBeforeFilter: "saveBeforeFilter", propagateRowError: "propagateRowError", permanentSelectionAllowed: "permanentSelectionAllowed", openRowThrottleTime: "openRowThrottleTime", loadingDebounceTime: "loadingDebounceTime", defaultSortBy: "defaultSortBy", defaultSortDirection: "defaultSortDirection", defaultPageSize: "defaultPageSize", defaultPageSizeOptions: "defaultPageSizeOptions", focusColumn: "focusColumn", dataSource: "dataSource", filter: "filter", disabled: "disabled", paginator: "paginator" }, outputs: { onRefresh: "onRefresh", onClickRow: "onClickRow", onOpenRow: "onOpenRow", onNewRow: "onNewRow", onStartEditingRow: "onStartEditingRow", onConfirmEditCreateRow: "onConfirmEditCreateRow", onCancelOrDeleteRow: "onCancelOrDeleteRow", onBeforeDeleteRows: "onBeforeDeleteRows", onBeforeCancelRows: "onBeforeCancelRows", onBeforeSave: "onBeforeSave", onAfterDeletedRows: "onAfterDeletedRows", onSort: "onSort", onDirty: "onDirty", onError: "onError" }, viewQueries: [{ propertyName: "table", first: true, predicate: MatTable, descendants: true }, { propertyName: "childPaginator", first: true, predicate: MatPaginator, descendants: true }, { propertyName: "sort", first: true, predicate: MatSort, descendants: true }], ngImport: i0 });
42594
42985
  }
42595
42986
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AppTable, decorators: [{
42596
42987
  type: Directive
@@ -42630,6 +43021,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
42630
43021
  type: Input
42631
43022
  }], openRowThrottleTime: [{
42632
43023
  type: Input
43024
+ }], loadingDebounceTime: [{
43025
+ type: Input
42633
43026
  }], defaultSortBy: [{
42634
43027
  type: Input
42635
43028
  }], defaultSortDirection: [{
@@ -42873,7 +43266,6 @@ class AppRowField {
42873
43266
  headerI18n;
42874
43267
  sortable = false;
42875
43268
  resizable = false;
42876
- required;
42877
43269
  readonly = false;
42878
43270
  sticky = false;
42879
43271
  draggable = false;
@@ -42890,6 +43282,7 @@ class AppRowField {
42890
43282
  classList;
42891
43283
  debug = false;
42892
43284
  logPrefix = '[row-field]';
43285
+ required;
42893
43286
  constructor(
42894
43287
  // protected translate: TranslateService,
42895
43288
  cd,
@@ -42908,12 +43301,16 @@ class AppRowField {
42908
43301
  ngOnDestroy() {
42909
43302
  this.matTable.removeColumnDef(this.columnDef);
42910
43303
  }
43304
+ onDefinitionChanged(defintion) {
43305
+ // Propagate `definition.required` in `#headerTemplate`
43306
+ this.required = defintion.required;
43307
+ }
42911
43308
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AppRowField, deps: [{ token: i0.ChangeDetectorRef }, { token: i7.MatTable }], target: i0.ɵɵFactoryTarget.Component });
42912
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AppRowField, selector: "app-row-field", inputs: { name: "name", definition: "definition", definitionFn: "definitionFn", headerI18n: "headerI18n", sortable: "sortable", resizable: "resizable", required: "required", readonly: "readonly", sticky: "sticky", draggable: "draggable", disabled: "disabled", placeholder: "placeholder", compact: "compact", floatLabel: "floatLabel", appearance: "appearance", tabindex: "tabindex", autofocus: "autofocus", clearable: "clearable", chipColor: "chipColor", classList: ["class", "classList"], debug: "debug" }, viewQueries: [{ propertyName: "columnDef", first: true, predicate: MatColumnDef, descendants: true }], ngImport: i0, template: "<ng-container [matColumnDef]=\"name\" [sticky]=\"sticky\">\n <th mat-header-cell *matHeaderCellDef cdkDrag [cdkDragDisabled]=\"sticky || !draggable\" [resizable]=\"resizable\">\n <!-- if sortable, wrap the header with a mat-sort-header -->\n @if (sortable) {\n <span mat-sort-header>\n <ng-container *ngTemplateOutlet=\"headerTemplate\"></ng-container>\n </span>\n } @else {\n <ng-container *ngTemplateOutlet=\"headerTemplate\"></ng-container>\n }\n <ng-template #headerTemplate>\n <ion-label appAutoTitle>{{ headerI18n | translate }}</ion-label>\n @if (required) {\n <ion-label color=\"danger\" [innerHTML]=\"'&nbsp;*'\"></ion-label>\n }\n </ng-template>\n </th>\n <td mat-cell *matCellDef=\"let row\">\n @if (!readonly && row.editing) {\n <app-form-field\n [definition]=\"definition || definitionFn(row)\"\n [required]=\"required\"\n [readonly]=\"readonly\"\n [disabled]=\"disabled\"\n [formControl]=\"row.validator | formGetControl: name\"\n [placeholder]=\"placeholder\"\n [compact]=\"compact\"\n [floatLabel]=\"floatLabel\"\n [appearance]=\"appearance\"\n [tabindex]=\"tabindex\"\n [autofocus]=\"autofocus\"\n [clearable]=\"clearable\"\n [chipColor]=\"chipColor\"\n [class]=\"classList\"\n [debug]=\"debug\"\n ></app-form-field>\n } @else {\n <ion-label appAutoTitle>\n {{ row.currentData | propertyGet: name | valueFormat: definition || definitionFn(row) }}\n </ion-label>\n }\n </td>\n</ng-container>\n", styles: [""], dependencies: [{ kind: "directive", type: i3$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$3.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i6$4.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i7.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i7.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i7.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i7.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i7.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i8$2.MatSortHeader, selector: "[mat-sort-header]", inputs: ["mat-sort-header", "arrowPosition", "start", "disabled", "sortActionDescription", "disableClear"], exportAs: ["matSortHeader"] }, { kind: "directive", type: AutofocusDirective, selector: "[autofocus], input[appAutofocus]", inputs: ["appAutofocus", "autofocusDelay"] }, { kind: "directive", type: AutoTitleDirective, selector: "ion-label[appAutoTitle], mat-label[appAutoTitle]", inputs: ["appAutoTitle"] }, { kind: "component", type: AppFormField, selector: "app-form-field", inputs: ["definition", "readonly", "disabled", "formControl", "formControlName", "placeholder", "compact", "required", "hideRequiredMarker", "floatLabel", "label", "appearance", "subscriptSizing", "tabindex", "autofocus", "clearable", "chipColor", "class", "debug", "panelClass", "panelWidth"], outputs: ["keyup.enter"] }, { kind: "component", type: ResizableComponent, selector: "th[resizable]", inputs: ["resizable"], outputs: ["sizeChanged"] }, { kind: "directive", type: ResizableDirective, selector: "[resizable]", inputs: ["minWidth"], outputs: ["resizable", "fit"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }, { kind: "pipe", type: PropertyGetPipe, name: "propertyGet" }, { kind: "pipe", type: ValueFormatPipe, name: "valueFormat" }, { kind: "pipe", type: FormGetControlPipe, name: "formGetControl" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
43309
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AppRowField, selector: "app-row-field", inputs: { name: "name", definition: "definition", definitionFn: "definitionFn", headerI18n: "headerI18n", sortable: "sortable", resizable: "resizable", readonly: "readonly", sticky: "sticky", draggable: "draggable", disabled: "disabled", placeholder: "placeholder", compact: "compact", floatLabel: "floatLabel", appearance: "appearance", tabindex: "tabindex", autofocus: "autofocus", clearable: "clearable", chipColor: "chipColor", classList: ["class", "classList"], debug: "debug" }, viewQueries: [{ propertyName: "columnDef", first: true, predicate: MatColumnDef, descendants: true }], ngImport: i0, template: "<ng-container [matColumnDef]=\"name\" [sticky]=\"sticky\">\n <th mat-header-cell *matHeaderCellDef cdkDrag [cdkDragDisabled]=\"sticky || !draggable\" [resizable]=\"resizable\">\n <!-- if sortable, wrap the header with a mat-sort-header -->\n @if (sortable) {\n <span mat-sort-header>\n <ng-container *ngTemplateOutlet=\"headerTemplate\"></ng-container>\n </span>\n } @else {\n <ng-container *ngTemplateOutlet=\"headerTemplate\"></ng-container>\n }\n <ng-template #headerTemplate>\n <ion-label appAutoTitle>{{ headerI18n | translate }}</ion-label>\n @if (required) {\n <ion-label color=\"danger\" [innerHTML]=\"'&nbsp;*'\"></ion-label>\n }\n </ng-template>\n </th>\n <td mat-cell *matCellDef=\"let row\">\n @if (!readonly && row.editing) {\n <app-form-field\n [definition]=\"definition || definitionFn(row)\"\n [required]=\"required\"\n [readonly]=\"readonly\"\n [disabled]=\"disabled\"\n [formControl]=\"row.validator | formGetControl: name\"\n [placeholder]=\"placeholder\"\n [compact]=\"compact\"\n [floatLabel]=\"floatLabel\"\n [appearance]=\"appearance\"\n [tabindex]=\"tabindex\"\n [autofocus]=\"autofocus\"\n [clearable]=\"clearable\"\n [chipColor]=\"chipColor\"\n [class]=\"classList\"\n [debug]=\"debug\"\n (definitionChanged)=\"onDefinitionChanged($event)\"\n ></app-form-field>\n } @else {\n <ion-label appAutoTitle>\n {{ row.currentData | propertyGet: name | valueFormat: definition || definitionFn(row) }}\n </ion-label>\n }\n </td>\n</ng-container>\n", styles: [""], dependencies: [{ kind: "directive", type: i3$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$3.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i6$4.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i7.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i7.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i7.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i7.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i7.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i8$2.MatSortHeader, selector: "[mat-sort-header]", inputs: ["mat-sort-header", "arrowPosition", "start", "disabled", "sortActionDescription", "disableClear"], exportAs: ["matSortHeader"] }, { kind: "directive", type: AutofocusDirective, selector: "[autofocus], input[appAutofocus]", inputs: ["appAutofocus", "autofocusDelay"] }, { kind: "directive", type: AutoTitleDirective, selector: "ion-label[appAutoTitle], mat-label[appAutoTitle]", inputs: ["appAutoTitle"] }, { kind: "component", type: AppFormField, selector: "app-form-field", inputs: ["definition", "readonly", "disabled", "formControl", "formControlName", "placeholder", "compact", "required", "hideRequiredMarker", "floatLabel", "label", "appearance", "subscriptSizing", "tabindex", "autofocus", "clearable", "chipColor", "class", "debug", "panelClass", "panelWidth"], outputs: ["keyup.enter", "definitionChanged"] }, { kind: "component", type: ResizableComponent, selector: "th[resizable]", inputs: ["resizable"], outputs: ["sizeChanged"] }, { kind: "directive", type: ResizableDirective, selector: "[resizable]", inputs: ["minWidth"], outputs: ["resizable", "fit"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }, { kind: "pipe", type: PropertyGetPipe, name: "propertyGet" }, { kind: "pipe", type: ValueFormatPipe, name: "valueFormat" }, { kind: "pipe", type: FormGetControlPipe, name: "formGetControl" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
42913
43310
  }
42914
43311
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AppRowField, decorators: [{
42915
43312
  type: Component,
42916
- args: [{ selector: 'app-row-field', changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container [matColumnDef]=\"name\" [sticky]=\"sticky\">\n <th mat-header-cell *matHeaderCellDef cdkDrag [cdkDragDisabled]=\"sticky || !draggable\" [resizable]=\"resizable\">\n <!-- if sortable, wrap the header with a mat-sort-header -->\n @if (sortable) {\n <span mat-sort-header>\n <ng-container *ngTemplateOutlet=\"headerTemplate\"></ng-container>\n </span>\n } @else {\n <ng-container *ngTemplateOutlet=\"headerTemplate\"></ng-container>\n }\n <ng-template #headerTemplate>\n <ion-label appAutoTitle>{{ headerI18n | translate }}</ion-label>\n @if (required) {\n <ion-label color=\"danger\" [innerHTML]=\"'&nbsp;*'\"></ion-label>\n }\n </ng-template>\n </th>\n <td mat-cell *matCellDef=\"let row\">\n @if (!readonly && row.editing) {\n <app-form-field\n [definition]=\"definition || definitionFn(row)\"\n [required]=\"required\"\n [readonly]=\"readonly\"\n [disabled]=\"disabled\"\n [formControl]=\"row.validator | formGetControl: name\"\n [placeholder]=\"placeholder\"\n [compact]=\"compact\"\n [floatLabel]=\"floatLabel\"\n [appearance]=\"appearance\"\n [tabindex]=\"tabindex\"\n [autofocus]=\"autofocus\"\n [clearable]=\"clearable\"\n [chipColor]=\"chipColor\"\n [class]=\"classList\"\n [debug]=\"debug\"\n ></app-form-field>\n } @else {\n <ion-label appAutoTitle>\n {{ row.currentData | propertyGet: name | valueFormat: definition || definitionFn(row) }}\n </ion-label>\n }\n </td>\n</ng-container>\n" }]
43313
+ args: [{ selector: 'app-row-field', changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<ng-container [matColumnDef]=\"name\" [sticky]=\"sticky\">\n <th mat-header-cell *matHeaderCellDef cdkDrag [cdkDragDisabled]=\"sticky || !draggable\" [resizable]=\"resizable\">\n <!-- if sortable, wrap the header with a mat-sort-header -->\n @if (sortable) {\n <span mat-sort-header>\n <ng-container *ngTemplateOutlet=\"headerTemplate\"></ng-container>\n </span>\n } @else {\n <ng-container *ngTemplateOutlet=\"headerTemplate\"></ng-container>\n }\n <ng-template #headerTemplate>\n <ion-label appAutoTitle>{{ headerI18n | translate }}</ion-label>\n @if (required) {\n <ion-label color=\"danger\" [innerHTML]=\"'&nbsp;*'\"></ion-label>\n }\n </ng-template>\n </th>\n <td mat-cell *matCellDef=\"let row\">\n @if (!readonly && row.editing) {\n <app-form-field\n [definition]=\"definition || definitionFn(row)\"\n [required]=\"required\"\n [readonly]=\"readonly\"\n [disabled]=\"disabled\"\n [formControl]=\"row.validator | formGetControl: name\"\n [placeholder]=\"placeholder\"\n [compact]=\"compact\"\n [floatLabel]=\"floatLabel\"\n [appearance]=\"appearance\"\n [tabindex]=\"tabindex\"\n [autofocus]=\"autofocus\"\n [clearable]=\"clearable\"\n [chipColor]=\"chipColor\"\n [class]=\"classList\"\n [debug]=\"debug\"\n (definitionChanged)=\"onDefinitionChanged($event)\"\n ></app-form-field>\n } @else {\n <ion-label appAutoTitle>\n {{ row.currentData | propertyGet: name | valueFormat: definition || definitionFn(row) }}\n </ion-label>\n }\n </td>\n</ng-container>\n" }]
42917
43314
  }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i7.MatTable }], propDecorators: { columnDef: [{
42918
43315
  type: ViewChild,
42919
43316
  args: [MatColumnDef]
@@ -42929,8 +43326,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
42929
43326
  type: Input
42930
43327
  }], resizable: [{
42931
43328
  type: Input
42932
- }], required: [{
42933
- type: Input
42934
43329
  }], readonly: [{
42935
43330
  type: Input
42936
43331
  }], sticky: [{
@@ -43172,11 +43567,11 @@ class AppPropertiesTable extends AppInMemoryTable {
43172
43567
  this.markRowAsDirty();
43173
43568
  }
43174
43569
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AppPropertiesTable, deps: [{ token: i0.Injector }, { token: i1$3.UntypedFormBuilder }, { token: PropertyEntityValidator }, { token: ENVIRONMENT }], target: i0.ɵɵFactoryTarget.Component });
43175
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: AppPropertiesTable, selector: "app-properties-table", inputs: { definitions: "definitions", showToolbar: "showToolbar" }, providers: [{ provide: PropertyEntityValidator, useClass: PropertyEntityValidator }], viewQueries: [{ propertyName: "filterExpansionPanel", first: true, predicate: MatExpansionPanel, descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: "<mat-toolbar *ngIf=\"showToolbar\" [class.expanded]=\"filterExpansionPanel.expanded\">\n <!-- Refresh -->\n <button\n mat-icon-button\n *ngIf=\"!mobile\"\n [title]=\"!showTooltip ? ('COMMON.BTN_REFRESH' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_REFRESH' | translate) : ''\"\n (click)=\"emitRefresh()\"\n >\n <mat-icon>refresh</mat-icon>\n </button>\n\n <ion-item *ngIf=\"!mobile && error; let error\" lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n\n <div class=\"toolbar-spacer\"></div>\n\n <!-- Reset filter -->\n <button mat-icon-button (click)=\"resetFilter()\" *ngIf=\"filterCriteriaCount\">\n <mat-icon color=\"accent\">filter_list_alt</mat-icon>\n <mat-icon class=\"icon-secondary\" style=\"left: 16px; top: 5px; font-weight: bold\">close</mat-icon>\n </button>\n\n <!-- Show filter -->\n <button mat-icon-button (click)=\"filterExpansionPanel.toggle()\">\n <mat-icon\n [matBadge]=\"filterCriteriaCount\"\n [matBadgeHidden]=\"filterIsEmpty\"\n matBadgeColor=\"accent\"\n matBadgeSize=\"small\"\n matBadgePosition=\"above after\"\n aria-hidden=\"false\"\n >\n filter_list_alt\n </mat-icon>\n </button>\n</mat-toolbar>\n\n<ion-content class=\"ion-no-padding\">\n <!-- search -->\n <mat-expansion-panel\n #filterExpansionPanel\n class=\"filter-panel\"\n [class.ion-no-padding]=\"mobile\"\n [class.filter-panel-floating]=\"filterPanelFloating\"\n >\n <form class=\"form-container\" [formGroup]=\"filterForm\" (ngSubmit)=\"emitRefresh()\">\n <ion-grid>\n <ion-row>\n <ion-col>\n <!-- search -->\n <mat-form-field>\n <mat-label>{{ 'SETTINGS.FILTER.SEARCH' | translate }}</mat-label>\n <input matInput formControlName=\"searchText\" />\n\n <button\n mat-icon-button\n matSuffix\n tabindex=\"-1\"\n type=\"button\"\n (click)=\"clearControlValue($event, filterForm.controls.searchText)\"\n [hidden]=\"filterForm.controls.searchText.disabled || !filterForm.controls.searchText.value\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </mat-form-field>\n </ion-col>\n\n <ion-col>\n <mat-boolean-field\n [style]=\"'checkbox'\"\n formControlName=\"notDefaultOnly\"\n [placeholder]=\"'SETTINGS.FILTER.ONLY_NOT_DEFAULT' | translate\"\n floatLabel=\"always\"\n ></mat-boolean-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </form>\n\n <mat-action-row>\n <!-- Counter -->\n <ion-label\n [hidden]=\"(loadingSubject | async) || filterForm.dirty\"\n [color]=\"empty && 'danger'\"\n class=\"ion-padding\"\n >\n {{\n (totalRowCount ? 'COMMON.RESULT_COUNT' : 'COMMON.NO_RESULT')\n | translate\n : {\n count: (totalRowCount | numberFormat),\n }\n }}\n </ion-label>\n\n <div class=\"toolbar-spacer\"></div>\n\n <button\n mat-icon-button\n color=\"accent\"\n *ngIf=\"filterPanelFloating\"\n (click)=\"toggleFilterPanelFloating()\"\n class=\"hidden-xs hidden-sm hidden-md\"\n [title]=\"!showTooltip ? ((filterPanelFloating ? 'COMMON.BTN_EXPAND' : 'COMMON.BTN_HIDE') | translate) : ''\"\n [matTooltip]=\"showTooltip ? ((filterPanelFloating ? 'COMMON.BTN_EXPAND' : 'COMMON.BTN_HIDE') | translate) : ''\"\n >\n <mat-icon>\n <span style=\"transform: rotate(90deg)\">{{ filterPanelFloating ? '&#xbb;' : '&#xab;' }}</span>\n </mat-icon>\n </button>\n\n <!-- Close panel -->\n <ion-button mat-button fill=\"clear\" color=\"dark\" (click)=\"closeFilterPanel()\" [disabled]=\"loadingSubject | async\">\n <ion-text translate>COMMON.BTN_CLOSE</ion-text>\n </ion-button>\n\n <!-- Search button -->\n <ion-button\n mat-button\n [color]=\"filterForm.dirty ? 'tertiary' : 'dark'\"\n [fill]=\"filterForm.dirty ? 'solid' : 'clear'\"\n (click)=\"applyFilterAndClosePanel($event)\"\n [disabled]=\"loadingSubject | async\"\n >\n <ion-text translate>COMMON.BTN_APPLY</ion-text>\n </ion-button>\n </mat-action-row>\n </mat-expansion-panel>\n\n <ion-refresher slot=\"fixed\" *ngIf=\"mobile\" (ionRefresh)=\"doRefresh($event)\">\n <ion-refresher-content></ion-refresher-content>\n </ion-refresher>\n\n <!-- table -->\n <div class=\"table-container\">\n <table\n #table\n mat-table\n matSort\n matSortDisableClear\n [dataSource]=\"dataSource\"\n [matSortActive]=\"defaultSortBy\"\n [matSortDirection]=\"defaultSortDirection\"\n [trackBy]=\"trackByFn\"\n [style.--mat-row-height]=\"'52px'\"\n >\n <ng-container matColumnDef=\"select\">\n <th mat-header-cell *matHeaderCellDef [class.cdk-visually-hidden]=\"true\"></th>\n <td mat-cell *matCellDef=\"let row\" [class.cdk-visually-hidden]=\"true\"></td>\n </ng-container>\n\n <!-- id column (hidden) -->\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef [class.cdk-visually-hidden]=\"!debug\"></th>\n <td mat-cell *matCellDef=\"let row\" [class.cdk-visually-hidden]=\"!debug\">\n {{ row.currentData.id }}\n </td>\n </ng-container>\n\n <!-- definition column -->\n <ng-container matColumnDef=\"definition\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header class=\"ion-padding-start\">\n <ion-label>{{ i18nColumnPrefix + 'DEFINITION' | translate }}</ion-label>\n </th>\n <td mat-cell *matCellDef=\"let row\" class=\"ion-padding-start\">\n {{ row.currentData | propertyGet: 'definition.label' }}\n </td>\n </ng-container>\n\n <!-- value column -->\n <app-row-field\n name=\"value\"\n floatLabel=\"never\"\n [definitionFn]=\"definitionByRow\"\n [headerI18n]=\"i18nColumnPrefix + 'VALUE'\"\n [required]=\"true\"\n ></app-row-field>\n\n <!-- Actions buttons column -->\n <app-actions-column\n [stickyEnd]=\"true\"\n [canCancel]=\"false\"\n [canDelete]=\"false\"\n [dirtyIcon]=\"false\"\n [cellTemplate]=\"cellInjection\"\n >\n <!-- cell injection-->\n <ng-template #cellInjection let-row>\n <button\n type=\"button\"\n mat-icon-button\n *ngIf=\"\n !mobile &&\n !disabled &&\n (row.validator | formGetValue: 'value') !== (row.validator | formGetValue: 'definition').defaultValue\n \"\n [class.visible-hover-row]=\"!mobile\"\n [tabindex]=\"-1\"\n [title]=\"!showTooltip ? ('SETTINGS.BTN_RESET_PROPERTY' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('SETTINGS.BTN_RESET_PROPERTY' | translate) : ''\"\n (click)=\"resetProperty($event, row)\"\n >\n <mat-icon>undo</mat-icon>\n </button>\n </ng-template>\n </app-actions-column>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns; sticky: true\"></tr>\n <tr\n mat-row\n *matRowDef=\"let row; columns: displayedColumns\"\n [class.mat-row-selected]=\"row.editing\"\n [class.mat-row-error]=\"row.validator?.invalid\"\n [class.mat-row-dirty]=\"row.validator?.dirty\"\n (click)=\"clickRow($event, row)\"\n (keydown.escape)=\"escapeEditingRow($event)\"\n [cdkTrapFocus]=\"row.validator?.invalid\"\n ></tr>\n </table>\n\n <ng-container *ngIf=\"loadingSubject | async; else noResult\">\n <ion-item>\n <ion-skeleton-text animated></ion-skeleton-text>\n </ion-item>\n </ng-container>\n\n <ng-template #noResult>\n <ion-item *ngIf=\"totalRowCount === 0\">\n <ion-text color=\"danger\" class=\"text-italic\" translate>COMMON.NO_RESULT</ion-text>\n </ion-item>\n </ng-template>\n </div>\n</ion-content>\n", styles: [""], dependencies: [{ kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonRefresher, selector: "ion-refresher", inputs: ["closeDuration", "disabled", "mode", "pullFactor", "pullMax", "pullMin", "snapbackDuration"] }, { kind: "component", type: i2$1.IonRefresherContent, selector: "ion-refresher-content", inputs: ["pullingIcon", "pullingText", "refreshingSpinner", "refreshingText"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "component", type: i2$1.IonSkeletonText, selector: "ion-skeleton-text", inputs: ["animated"] }, { kind: "component", type: i2$1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i7.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i7.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i7.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i7.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i7.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i7.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i7.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i7.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i7.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i7.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "directive", type: i8$2.MatSort, selector: "[matSort]", inputs: ["matSortActive", "matSortStart", "matSortDirection", "matSortDisableClear", "matSortDisabled"], outputs: ["matSortChange"], exportAs: ["matSort"] }, { kind: "component", type: i8$2.MatSortHeader, selector: "[mat-sort-header]", inputs: ["mat-sort-header", "arrowPosition", "start", "disabled", "sortActionDescription", "disableClear"], exportAs: ["matSortHeader"] }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i13$4.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "directive", type: i13$4.MatExpansionPanelActionRow, selector: "mat-action-row" }, { kind: "component", type: i10$1.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "component", type: i6$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i11$1.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "directive", type: i13$1.MatBadge, selector: "[matBadge]", inputs: ["matBadgeColor", "matBadgeOverlap", "matBadgeDisabled", "matBadgePosition", "matBadge", "matBadgeDescription", "matBadgeSize", "matBadgeHidden"] }, { kind: "directive", type: i1$6.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "directive", type: i1$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: MatBooleanField, selector: "mat-boolean-field", inputs: ["disabled", "formControl", "formControlName", "placeholder", "floatLabel", "appearance", "subscriptSizing", "readonly", "required", "compact", "autofocus", "style", "buttonsColCount", "class", "yesLabel", "noLabel", "showButtonIcons", "yesIcon", "noIcon", "clearable", "labelPosition", "tabindex", "showRadio", "value"], outputs: ["keyup.enter", "focus", "blur"] }, { kind: "component", type: ActionsColumnComponent, selector: "app-actions-column", inputs: ["matColumnDef", "style", "showPendingSpinner", "stickyEnd", "canCancel", "canConfirm", "canDelete", "canBackward", "canForward", "canConfirmAndAdd", "dirtyIcon", "optionsTitle", "class", "cellTemplateStart", "cellTemplate"], outputs: ["optionsClick", "cancelOrDeleteClick", "confirmEditCreateClick", "confirmAndAddClick", "backward", "forward"] }, { kind: "component", type: AppRowField, selector: "app-row-field", inputs: ["name", "definition", "definitionFn", "headerI18n", "sortable", "resizable", "required", "readonly", "sticky", "draggable", "disabled", "placeholder", "compact", "floatLabel", "appearance", "tabindex", "autofocus", "clearable", "chipColor", "class", "debug"] }, { kind: "pipe", type: i3$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }, { kind: "pipe", type: PropertyGetPipe, name: "propertyGet" }, { kind: "pipe", type: NumberFormatPipe, name: "numberFormat" }, { kind: "pipe", type: FormGetValuePipe, name: "formGetValue" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
43570
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: AppPropertiesTable, selector: "app-properties-table", inputs: { definitions: "definitions", showToolbar: "showToolbar" }, providers: [{ provide: PropertyEntityValidator, useClass: PropertyEntityValidator }], viewQueries: [{ propertyName: "filterExpansionPanel", first: true, predicate: MatExpansionPanel, descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: "<mat-toolbar *ngIf=\"showToolbar\" [class.expanded]=\"filterExpansionPanel.expanded\">\n <!-- Refresh -->\n <button\n mat-icon-button\n *ngIf=\"!mobile\"\n [title]=\"!showTooltip ? ('COMMON.BTN_REFRESH' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_REFRESH' | translate) : ''\"\n (click)=\"emitRefresh()\"\n >\n <mat-icon>refresh</mat-icon>\n </button>\n\n <ion-item *ngIf=\"!mobile && error; let error\" lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n\n <div class=\"toolbar-spacer\"></div>\n\n <!-- Reset filter -->\n <button mat-icon-button (click)=\"resetFilter()\" *ngIf=\"filterCriteriaCount\">\n <mat-icon color=\"accent\">filter_list_alt</mat-icon>\n <mat-icon class=\"icon-secondary\" style=\"left: 16px; top: 5px; font-weight: bold\">close</mat-icon>\n </button>\n\n <!-- Show filter -->\n <button mat-icon-button (click)=\"filterExpansionPanel.toggle()\">\n <mat-icon\n [matBadge]=\"filterCriteriaCount\"\n [matBadgeHidden]=\"filterIsEmpty\"\n matBadgeColor=\"accent\"\n matBadgeSize=\"small\"\n matBadgePosition=\"above after\"\n aria-hidden=\"false\"\n >\n filter_list_alt\n </mat-icon>\n </button>\n</mat-toolbar>\n\n<ion-content class=\"ion-no-padding\">\n <!-- search -->\n <mat-expansion-panel\n #filterExpansionPanel\n class=\"filter-panel\"\n [class.ion-no-padding]=\"mobile\"\n [class.filter-panel-floating]=\"filterPanelFloating\"\n >\n <form class=\"form-container\" [formGroup]=\"filterForm\" (ngSubmit)=\"emitRefresh()\">\n <ion-grid>\n <ion-row>\n <ion-col>\n <!-- search -->\n <mat-form-field>\n <mat-label>{{ 'SETTINGS.FILTER.SEARCH' | translate }}</mat-label>\n <input matInput formControlName=\"searchText\" />\n\n <button\n mat-icon-button\n matSuffix\n tabindex=\"-1\"\n type=\"button\"\n (click)=\"clearControlValue($event, filterForm.controls.searchText)\"\n [hidden]=\"filterForm.controls.searchText.disabled || !filterForm.controls.searchText.value\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </mat-form-field>\n </ion-col>\n\n <ion-col>\n <mat-boolean-field\n [style]=\"'checkbox'\"\n formControlName=\"notDefaultOnly\"\n [placeholder]=\"'SETTINGS.FILTER.ONLY_NOT_DEFAULT' | translate\"\n floatLabel=\"always\"\n ></mat-boolean-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </form>\n\n <mat-action-row>\n <!-- Counter -->\n <ion-label\n [hidden]=\"(loadingSubject | async) || filterForm.dirty\"\n [color]=\"empty && 'danger'\"\n class=\"ion-padding\"\n >\n {{\n (totalRowCount ? 'COMMON.RESULT_COUNT' : 'COMMON.NO_RESULT')\n | translate\n : {\n count: (totalRowCount | numberFormat),\n }\n }}\n </ion-label>\n\n <div class=\"toolbar-spacer\"></div>\n\n <button\n mat-icon-button\n color=\"accent\"\n *ngIf=\"filterPanelFloating\"\n (click)=\"toggleFilterPanelFloating()\"\n class=\"hidden-xs hidden-sm hidden-md\"\n [title]=\"!showTooltip ? ((filterPanelFloating ? 'COMMON.BTN_EXPAND' : 'COMMON.BTN_HIDE') | translate) : ''\"\n [matTooltip]=\"showTooltip ? ((filterPanelFloating ? 'COMMON.BTN_EXPAND' : 'COMMON.BTN_HIDE') | translate) : ''\"\n >\n <mat-icon>\n <span style=\"transform: rotate(90deg)\">{{ filterPanelFloating ? '&#xbb;' : '&#xab;' }}</span>\n </mat-icon>\n </button>\n\n <!-- Close panel -->\n <ion-button mat-button fill=\"clear\" color=\"dark\" (click)=\"closeFilterPanel()\" [disabled]=\"loadingSubject | async\">\n <ion-text translate>COMMON.BTN_CLOSE</ion-text>\n </ion-button>\n\n <!-- Search button -->\n <ion-button\n mat-button\n [color]=\"filterForm.dirty ? 'tertiary' : 'dark'\"\n [fill]=\"filterForm.dirty ? 'solid' : 'clear'\"\n (click)=\"applyFilterAndClosePanel($event)\"\n [disabled]=\"loadingSubject | async\"\n >\n <ion-text translate>COMMON.BTN_APPLY</ion-text>\n </ion-button>\n </mat-action-row>\n </mat-expansion-panel>\n\n <ion-refresher slot=\"fixed\" *ngIf=\"mobile\" (ionRefresh)=\"doRefresh($event)\">\n <ion-refresher-content></ion-refresher-content>\n </ion-refresher>\n\n <!-- table -->\n <div class=\"table-container\">\n <table\n #table\n mat-table\n matSort\n matSortDisableClear\n [dataSource]=\"dataSource\"\n [matSortActive]=\"defaultSortBy\"\n [matSortDirection]=\"defaultSortDirection\"\n [trackBy]=\"trackByFn\"\n [style.--mat-row-height]=\"'52px'\"\n >\n <ng-container matColumnDef=\"select\">\n <th mat-header-cell *matHeaderCellDef [class.cdk-visually-hidden]=\"true\"></th>\n <td mat-cell *matCellDef=\"let row\" [class.cdk-visually-hidden]=\"true\"></td>\n </ng-container>\n\n <!-- id column (hidden) -->\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef [class.cdk-visually-hidden]=\"!debug\"></th>\n <td mat-cell *matCellDef=\"let row\" [class.cdk-visually-hidden]=\"!debug\">\n {{ row.currentData.id }}\n </td>\n </ng-container>\n\n <!-- definition column -->\n <ng-container matColumnDef=\"definition\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header class=\"ion-padding-start\">\n <ion-label>{{ i18nColumnPrefix + 'DEFINITION' | translate }}</ion-label>\n </th>\n <td mat-cell *matCellDef=\"let row\" class=\"ion-padding-start\">\n {{ row.currentData | propertyGet: 'definition.label' }}\n </td>\n </ng-container>\n\n <!-- value column -->\n <app-row-field\n name=\"value\"\n floatLabel=\"never\"\n [definitionFn]=\"definitionByRow\"\n [headerI18n]=\"i18nColumnPrefix + 'VALUE'\"\n ></app-row-field>\n\n <!-- Actions buttons column -->\n <app-actions-column\n [stickyEnd]=\"true\"\n [canCancel]=\"false\"\n [canDelete]=\"false\"\n [dirtyIcon]=\"false\"\n [cellTemplate]=\"cellInjection\"\n >\n <!-- cell injection-->\n <ng-template #cellInjection let-row>\n <button\n type=\"button\"\n mat-icon-button\n *ngIf=\"\n !mobile &&\n !disabled &&\n (row.validator | formGetValue: 'value') !== (row.validator | formGetValue: 'definition').defaultValue\n \"\n [class.visible-hover-row]=\"!mobile\"\n [tabindex]=\"-1\"\n [title]=\"!showTooltip ? ('SETTINGS.BTN_RESET_PROPERTY' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('SETTINGS.BTN_RESET_PROPERTY' | translate) : ''\"\n (click)=\"resetProperty($event, row)\"\n >\n <mat-icon>undo</mat-icon>\n </button>\n </ng-template>\n </app-actions-column>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns; sticky: true\"></tr>\n <tr\n mat-row\n *matRowDef=\"let row; columns: displayedColumns\"\n [class.mat-row-selected]=\"row.editing\"\n [class.mat-row-error]=\"row.validator?.invalid\"\n [class.mat-row-dirty]=\"row.validator?.dirty\"\n (click)=\"clickRow($event, row)\"\n (keydown.escape)=\"escapeEditingRow($event)\"\n [cdkTrapFocus]=\"row.validator?.invalid\"\n ></tr>\n </table>\n\n <ng-container *ngIf=\"loadingSubject | async; else noResult\">\n <ion-item>\n <ion-skeleton-text animated></ion-skeleton-text>\n </ion-item>\n </ng-container>\n\n <ng-template #noResult>\n <ion-item *ngIf=\"totalRowCount === 0\">\n <ion-text color=\"danger\" class=\"text-italic\" translate>COMMON.NO_RESULT</ion-text>\n </ion-item>\n </ng-template>\n </div>\n</ion-content>\n", styles: [""], dependencies: [{ kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonRefresher, selector: "ion-refresher", inputs: ["closeDuration", "disabled", "mode", "pullFactor", "pullMax", "pullMin", "snapbackDuration"] }, { kind: "component", type: i2$1.IonRefresherContent, selector: "ion-refresher-content", inputs: ["pullingIcon", "pullingText", "refreshingSpinner", "refreshingText"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "component", type: i2$1.IonSkeletonText, selector: "ion-skeleton-text", inputs: ["animated"] }, { kind: "component", type: i2$1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i7.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i7.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i7.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i7.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i7.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i7.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i7.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i7.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i7.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i7.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "directive", type: i8$2.MatSort, selector: "[matSort]", inputs: ["matSortActive", "matSortStart", "matSortDirection", "matSortDisableClear", "matSortDisabled"], outputs: ["matSortChange"], exportAs: ["matSort"] }, { kind: "component", type: i8$2.MatSortHeader, selector: "[mat-sort-header]", inputs: ["mat-sort-header", "arrowPosition", "start", "disabled", "sortActionDescription", "disableClear"], exportAs: ["matSortHeader"] }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i13$4.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "directive", type: i13$4.MatExpansionPanelActionRow, selector: "mat-action-row" }, { kind: "component", type: i10$1.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "component", type: i6$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i11$1.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "directive", type: i13$1.MatBadge, selector: "[matBadge]", inputs: ["matBadgeColor", "matBadgeOverlap", "matBadgeDisabled", "matBadgePosition", "matBadge", "matBadgeDescription", "matBadgeSize", "matBadgeHidden"] }, { kind: "directive", type: i1$6.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "directive", type: i1$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: MatBooleanField, selector: "mat-boolean-field", inputs: ["disabled", "formControl", "formControlName", "placeholder", "floatLabel", "appearance", "subscriptSizing", "readonly", "required", "compact", "autofocus", "style", "buttonsColCount", "class", "yesLabel", "noLabel", "showButtonIcons", "yesIcon", "noIcon", "clearable", "labelPosition", "tabindex", "showRadio", "value"], outputs: ["keyup.enter", "focus", "blur"] }, { kind: "component", type: ActionsColumnComponent, selector: "app-actions-column", inputs: ["matColumnDef", "style", "showPendingSpinner", "stickyEnd", "canCancel", "canConfirm", "canDelete", "canBackward", "canForward", "canConfirmAndAdd", "dirtyIcon", "optionsTitle", "class", "cellTemplateStart", "cellTemplate"], outputs: ["optionsClick", "cancelOrDeleteClick", "confirmEditCreateClick", "confirmAndAddClick", "backward", "forward"] }, { kind: "component", type: AppRowField, selector: "app-row-field", inputs: ["name", "definition", "definitionFn", "headerI18n", "sortable", "resizable", "readonly", "sticky", "draggable", "disabled", "placeholder", "compact", "floatLabel", "appearance", "tabindex", "autofocus", "clearable", "chipColor", "class", "debug"] }, { kind: "pipe", type: i3$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }, { kind: "pipe", type: PropertyGetPipe, name: "propertyGet" }, { kind: "pipe", type: NumberFormatPipe, name: "numberFormat" }, { kind: "pipe", type: FormGetValuePipe, name: "formGetValue" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
43176
43571
  }
43177
43572
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AppPropertiesTable, decorators: [{
43178
43573
  type: Component,
43179
- args: [{ selector: 'app-properties-table', providers: [{ provide: PropertyEntityValidator, useClass: PropertyEntityValidator }], changeDetection: ChangeDetectionStrategy.OnPush, template: "<mat-toolbar *ngIf=\"showToolbar\" [class.expanded]=\"filterExpansionPanel.expanded\">\n <!-- Refresh -->\n <button\n mat-icon-button\n *ngIf=\"!mobile\"\n [title]=\"!showTooltip ? ('COMMON.BTN_REFRESH' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_REFRESH' | translate) : ''\"\n (click)=\"emitRefresh()\"\n >\n <mat-icon>refresh</mat-icon>\n </button>\n\n <ion-item *ngIf=\"!mobile && error; let error\" lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n\n <div class=\"toolbar-spacer\"></div>\n\n <!-- Reset filter -->\n <button mat-icon-button (click)=\"resetFilter()\" *ngIf=\"filterCriteriaCount\">\n <mat-icon color=\"accent\">filter_list_alt</mat-icon>\n <mat-icon class=\"icon-secondary\" style=\"left: 16px; top: 5px; font-weight: bold\">close</mat-icon>\n </button>\n\n <!-- Show filter -->\n <button mat-icon-button (click)=\"filterExpansionPanel.toggle()\">\n <mat-icon\n [matBadge]=\"filterCriteriaCount\"\n [matBadgeHidden]=\"filterIsEmpty\"\n matBadgeColor=\"accent\"\n matBadgeSize=\"small\"\n matBadgePosition=\"above after\"\n aria-hidden=\"false\"\n >\n filter_list_alt\n </mat-icon>\n </button>\n</mat-toolbar>\n\n<ion-content class=\"ion-no-padding\">\n <!-- search -->\n <mat-expansion-panel\n #filterExpansionPanel\n class=\"filter-panel\"\n [class.ion-no-padding]=\"mobile\"\n [class.filter-panel-floating]=\"filterPanelFloating\"\n >\n <form class=\"form-container\" [formGroup]=\"filterForm\" (ngSubmit)=\"emitRefresh()\">\n <ion-grid>\n <ion-row>\n <ion-col>\n <!-- search -->\n <mat-form-field>\n <mat-label>{{ 'SETTINGS.FILTER.SEARCH' | translate }}</mat-label>\n <input matInput formControlName=\"searchText\" />\n\n <button\n mat-icon-button\n matSuffix\n tabindex=\"-1\"\n type=\"button\"\n (click)=\"clearControlValue($event, filterForm.controls.searchText)\"\n [hidden]=\"filterForm.controls.searchText.disabled || !filterForm.controls.searchText.value\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </mat-form-field>\n </ion-col>\n\n <ion-col>\n <mat-boolean-field\n [style]=\"'checkbox'\"\n formControlName=\"notDefaultOnly\"\n [placeholder]=\"'SETTINGS.FILTER.ONLY_NOT_DEFAULT' | translate\"\n floatLabel=\"always\"\n ></mat-boolean-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </form>\n\n <mat-action-row>\n <!-- Counter -->\n <ion-label\n [hidden]=\"(loadingSubject | async) || filterForm.dirty\"\n [color]=\"empty && 'danger'\"\n class=\"ion-padding\"\n >\n {{\n (totalRowCount ? 'COMMON.RESULT_COUNT' : 'COMMON.NO_RESULT')\n | translate\n : {\n count: (totalRowCount | numberFormat),\n }\n }}\n </ion-label>\n\n <div class=\"toolbar-spacer\"></div>\n\n <button\n mat-icon-button\n color=\"accent\"\n *ngIf=\"filterPanelFloating\"\n (click)=\"toggleFilterPanelFloating()\"\n class=\"hidden-xs hidden-sm hidden-md\"\n [title]=\"!showTooltip ? ((filterPanelFloating ? 'COMMON.BTN_EXPAND' : 'COMMON.BTN_HIDE') | translate) : ''\"\n [matTooltip]=\"showTooltip ? ((filterPanelFloating ? 'COMMON.BTN_EXPAND' : 'COMMON.BTN_HIDE') | translate) : ''\"\n >\n <mat-icon>\n <span style=\"transform: rotate(90deg)\">{{ filterPanelFloating ? '&#xbb;' : '&#xab;' }}</span>\n </mat-icon>\n </button>\n\n <!-- Close panel -->\n <ion-button mat-button fill=\"clear\" color=\"dark\" (click)=\"closeFilterPanel()\" [disabled]=\"loadingSubject | async\">\n <ion-text translate>COMMON.BTN_CLOSE</ion-text>\n </ion-button>\n\n <!-- Search button -->\n <ion-button\n mat-button\n [color]=\"filterForm.dirty ? 'tertiary' : 'dark'\"\n [fill]=\"filterForm.dirty ? 'solid' : 'clear'\"\n (click)=\"applyFilterAndClosePanel($event)\"\n [disabled]=\"loadingSubject | async\"\n >\n <ion-text translate>COMMON.BTN_APPLY</ion-text>\n </ion-button>\n </mat-action-row>\n </mat-expansion-panel>\n\n <ion-refresher slot=\"fixed\" *ngIf=\"mobile\" (ionRefresh)=\"doRefresh($event)\">\n <ion-refresher-content></ion-refresher-content>\n </ion-refresher>\n\n <!-- table -->\n <div class=\"table-container\">\n <table\n #table\n mat-table\n matSort\n matSortDisableClear\n [dataSource]=\"dataSource\"\n [matSortActive]=\"defaultSortBy\"\n [matSortDirection]=\"defaultSortDirection\"\n [trackBy]=\"trackByFn\"\n [style.--mat-row-height]=\"'52px'\"\n >\n <ng-container matColumnDef=\"select\">\n <th mat-header-cell *matHeaderCellDef [class.cdk-visually-hidden]=\"true\"></th>\n <td mat-cell *matCellDef=\"let row\" [class.cdk-visually-hidden]=\"true\"></td>\n </ng-container>\n\n <!-- id column (hidden) -->\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef [class.cdk-visually-hidden]=\"!debug\"></th>\n <td mat-cell *matCellDef=\"let row\" [class.cdk-visually-hidden]=\"!debug\">\n {{ row.currentData.id }}\n </td>\n </ng-container>\n\n <!-- definition column -->\n <ng-container matColumnDef=\"definition\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header class=\"ion-padding-start\">\n <ion-label>{{ i18nColumnPrefix + 'DEFINITION' | translate }}</ion-label>\n </th>\n <td mat-cell *matCellDef=\"let row\" class=\"ion-padding-start\">\n {{ row.currentData | propertyGet: 'definition.label' }}\n </td>\n </ng-container>\n\n <!-- value column -->\n <app-row-field\n name=\"value\"\n floatLabel=\"never\"\n [definitionFn]=\"definitionByRow\"\n [headerI18n]=\"i18nColumnPrefix + 'VALUE'\"\n [required]=\"true\"\n ></app-row-field>\n\n <!-- Actions buttons column -->\n <app-actions-column\n [stickyEnd]=\"true\"\n [canCancel]=\"false\"\n [canDelete]=\"false\"\n [dirtyIcon]=\"false\"\n [cellTemplate]=\"cellInjection\"\n >\n <!-- cell injection-->\n <ng-template #cellInjection let-row>\n <button\n type=\"button\"\n mat-icon-button\n *ngIf=\"\n !mobile &&\n !disabled &&\n (row.validator | formGetValue: 'value') !== (row.validator | formGetValue: 'definition').defaultValue\n \"\n [class.visible-hover-row]=\"!mobile\"\n [tabindex]=\"-1\"\n [title]=\"!showTooltip ? ('SETTINGS.BTN_RESET_PROPERTY' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('SETTINGS.BTN_RESET_PROPERTY' | translate) : ''\"\n (click)=\"resetProperty($event, row)\"\n >\n <mat-icon>undo</mat-icon>\n </button>\n </ng-template>\n </app-actions-column>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns; sticky: true\"></tr>\n <tr\n mat-row\n *matRowDef=\"let row; columns: displayedColumns\"\n [class.mat-row-selected]=\"row.editing\"\n [class.mat-row-error]=\"row.validator?.invalid\"\n [class.mat-row-dirty]=\"row.validator?.dirty\"\n (click)=\"clickRow($event, row)\"\n (keydown.escape)=\"escapeEditingRow($event)\"\n [cdkTrapFocus]=\"row.validator?.invalid\"\n ></tr>\n </table>\n\n <ng-container *ngIf=\"loadingSubject | async; else noResult\">\n <ion-item>\n <ion-skeleton-text animated></ion-skeleton-text>\n </ion-item>\n </ng-container>\n\n <ng-template #noResult>\n <ion-item *ngIf=\"totalRowCount === 0\">\n <ion-text color=\"danger\" class=\"text-italic\" translate>COMMON.NO_RESULT</ion-text>\n </ion-item>\n </ng-template>\n </div>\n</ion-content>\n" }]
43574
+ args: [{ selector: 'app-properties-table', providers: [{ provide: PropertyEntityValidator, useClass: PropertyEntityValidator }], changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<mat-toolbar *ngIf=\"showToolbar\" [class.expanded]=\"filterExpansionPanel.expanded\">\n <!-- Refresh -->\n <button\n mat-icon-button\n *ngIf=\"!mobile\"\n [title]=\"!showTooltip ? ('COMMON.BTN_REFRESH' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_REFRESH' | translate) : ''\"\n (click)=\"emitRefresh()\"\n >\n <mat-icon>refresh</mat-icon>\n </button>\n\n <ion-item *ngIf=\"!mobile && error; let error\" lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n\n <div class=\"toolbar-spacer\"></div>\n\n <!-- Reset filter -->\n <button mat-icon-button (click)=\"resetFilter()\" *ngIf=\"filterCriteriaCount\">\n <mat-icon color=\"accent\">filter_list_alt</mat-icon>\n <mat-icon class=\"icon-secondary\" style=\"left: 16px; top: 5px; font-weight: bold\">close</mat-icon>\n </button>\n\n <!-- Show filter -->\n <button mat-icon-button (click)=\"filterExpansionPanel.toggle()\">\n <mat-icon\n [matBadge]=\"filterCriteriaCount\"\n [matBadgeHidden]=\"filterIsEmpty\"\n matBadgeColor=\"accent\"\n matBadgeSize=\"small\"\n matBadgePosition=\"above after\"\n aria-hidden=\"false\"\n >\n filter_list_alt\n </mat-icon>\n </button>\n</mat-toolbar>\n\n<ion-content class=\"ion-no-padding\">\n <!-- search -->\n <mat-expansion-panel\n #filterExpansionPanel\n class=\"filter-panel\"\n [class.ion-no-padding]=\"mobile\"\n [class.filter-panel-floating]=\"filterPanelFloating\"\n >\n <form class=\"form-container\" [formGroup]=\"filterForm\" (ngSubmit)=\"emitRefresh()\">\n <ion-grid>\n <ion-row>\n <ion-col>\n <!-- search -->\n <mat-form-field>\n <mat-label>{{ 'SETTINGS.FILTER.SEARCH' | translate }}</mat-label>\n <input matInput formControlName=\"searchText\" />\n\n <button\n mat-icon-button\n matSuffix\n tabindex=\"-1\"\n type=\"button\"\n (click)=\"clearControlValue($event, filterForm.controls.searchText)\"\n [hidden]=\"filterForm.controls.searchText.disabled || !filterForm.controls.searchText.value\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </mat-form-field>\n </ion-col>\n\n <ion-col>\n <mat-boolean-field\n [style]=\"'checkbox'\"\n formControlName=\"notDefaultOnly\"\n [placeholder]=\"'SETTINGS.FILTER.ONLY_NOT_DEFAULT' | translate\"\n floatLabel=\"always\"\n ></mat-boolean-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </form>\n\n <mat-action-row>\n <!-- Counter -->\n <ion-label\n [hidden]=\"(loadingSubject | async) || filterForm.dirty\"\n [color]=\"empty && 'danger'\"\n class=\"ion-padding\"\n >\n {{\n (totalRowCount ? 'COMMON.RESULT_COUNT' : 'COMMON.NO_RESULT')\n | translate\n : {\n count: (totalRowCount | numberFormat),\n }\n }}\n </ion-label>\n\n <div class=\"toolbar-spacer\"></div>\n\n <button\n mat-icon-button\n color=\"accent\"\n *ngIf=\"filterPanelFloating\"\n (click)=\"toggleFilterPanelFloating()\"\n class=\"hidden-xs hidden-sm hidden-md\"\n [title]=\"!showTooltip ? ((filterPanelFloating ? 'COMMON.BTN_EXPAND' : 'COMMON.BTN_HIDE') | translate) : ''\"\n [matTooltip]=\"showTooltip ? ((filterPanelFloating ? 'COMMON.BTN_EXPAND' : 'COMMON.BTN_HIDE') | translate) : ''\"\n >\n <mat-icon>\n <span style=\"transform: rotate(90deg)\">{{ filterPanelFloating ? '&#xbb;' : '&#xab;' }}</span>\n </mat-icon>\n </button>\n\n <!-- Close panel -->\n <ion-button mat-button fill=\"clear\" color=\"dark\" (click)=\"closeFilterPanel()\" [disabled]=\"loadingSubject | async\">\n <ion-text translate>COMMON.BTN_CLOSE</ion-text>\n </ion-button>\n\n <!-- Search button -->\n <ion-button\n mat-button\n [color]=\"filterForm.dirty ? 'tertiary' : 'dark'\"\n [fill]=\"filterForm.dirty ? 'solid' : 'clear'\"\n (click)=\"applyFilterAndClosePanel($event)\"\n [disabled]=\"loadingSubject | async\"\n >\n <ion-text translate>COMMON.BTN_APPLY</ion-text>\n </ion-button>\n </mat-action-row>\n </mat-expansion-panel>\n\n <ion-refresher slot=\"fixed\" *ngIf=\"mobile\" (ionRefresh)=\"doRefresh($event)\">\n <ion-refresher-content></ion-refresher-content>\n </ion-refresher>\n\n <!-- table -->\n <div class=\"table-container\">\n <table\n #table\n mat-table\n matSort\n matSortDisableClear\n [dataSource]=\"dataSource\"\n [matSortActive]=\"defaultSortBy\"\n [matSortDirection]=\"defaultSortDirection\"\n [trackBy]=\"trackByFn\"\n [style.--mat-row-height]=\"'52px'\"\n >\n <ng-container matColumnDef=\"select\">\n <th mat-header-cell *matHeaderCellDef [class.cdk-visually-hidden]=\"true\"></th>\n <td mat-cell *matCellDef=\"let row\" [class.cdk-visually-hidden]=\"true\"></td>\n </ng-container>\n\n <!-- id column (hidden) -->\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef [class.cdk-visually-hidden]=\"!debug\"></th>\n <td mat-cell *matCellDef=\"let row\" [class.cdk-visually-hidden]=\"!debug\">\n {{ row.currentData.id }}\n </td>\n </ng-container>\n\n <!-- definition column -->\n <ng-container matColumnDef=\"definition\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header class=\"ion-padding-start\">\n <ion-label>{{ i18nColumnPrefix + 'DEFINITION' | translate }}</ion-label>\n </th>\n <td mat-cell *matCellDef=\"let row\" class=\"ion-padding-start\">\n {{ row.currentData | propertyGet: 'definition.label' }}\n </td>\n </ng-container>\n\n <!-- value column -->\n <app-row-field\n name=\"value\"\n floatLabel=\"never\"\n [definitionFn]=\"definitionByRow\"\n [headerI18n]=\"i18nColumnPrefix + 'VALUE'\"\n ></app-row-field>\n\n <!-- Actions buttons column -->\n <app-actions-column\n [stickyEnd]=\"true\"\n [canCancel]=\"false\"\n [canDelete]=\"false\"\n [dirtyIcon]=\"false\"\n [cellTemplate]=\"cellInjection\"\n >\n <!-- cell injection-->\n <ng-template #cellInjection let-row>\n <button\n type=\"button\"\n mat-icon-button\n *ngIf=\"\n !mobile &&\n !disabled &&\n (row.validator | formGetValue: 'value') !== (row.validator | formGetValue: 'definition').defaultValue\n \"\n [class.visible-hover-row]=\"!mobile\"\n [tabindex]=\"-1\"\n [title]=\"!showTooltip ? ('SETTINGS.BTN_RESET_PROPERTY' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('SETTINGS.BTN_RESET_PROPERTY' | translate) : ''\"\n (click)=\"resetProperty($event, row)\"\n >\n <mat-icon>undo</mat-icon>\n </button>\n </ng-template>\n </app-actions-column>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns; sticky: true\"></tr>\n <tr\n mat-row\n *matRowDef=\"let row; columns: displayedColumns\"\n [class.mat-row-selected]=\"row.editing\"\n [class.mat-row-error]=\"row.validator?.invalid\"\n [class.mat-row-dirty]=\"row.validator?.dirty\"\n (click)=\"clickRow($event, row)\"\n (keydown.escape)=\"escapeEditingRow($event)\"\n [cdkTrapFocus]=\"row.validator?.invalid\"\n ></tr>\n </table>\n\n <ng-container *ngIf=\"loadingSubject | async; else noResult\">\n <ion-item>\n <ion-skeleton-text animated></ion-skeleton-text>\n </ion-item>\n </ng-container>\n\n <ng-template #noResult>\n <ion-item *ngIf=\"totalRowCount === 0\">\n <ion-text color=\"danger\" class=\"text-italic\" translate>COMMON.NO_RESULT</ion-text>\n </ion-item>\n </ng-template>\n </div>\n</ion-content>\n" }]
43180
43575
  }], ctorParameters: () => [{ type: i0.Injector }, { type: i1$3.UntypedFormBuilder }, { type: PropertyEntityValidator }, { type: Environment, decorators: [{
43181
43576
  type: Inject,
43182
43577
  args: [ENVIRONMENT]
@@ -43752,6 +44147,7 @@ class AppAsyncTable {
43752
44147
  propagateRowError = false;
43753
44148
  permanentSelectionAllowed = false; // Allow keep selected rows over page change or sort change
43754
44149
  openRowThrottleTime = 800; // Prevent double click - see issue sumaris-app#864
44150
+ loadingDebounceTime = 250; // Debounce time for loading spinner
43755
44151
  defaultSortBy;
43756
44152
  defaultSortDirection;
43757
44153
  defaultPageSize = DEFAULT_PAGE_SIZE;
@@ -44292,6 +44688,12 @@ class AppAsyncTable {
44292
44688
  return true; // no row to confirm
44293
44689
  // Stop event
44294
44690
  event?.stopPropagation();
44691
+ // Wait for async validation to complete (with timeout safety net).
44692
+ // This prevents the internal waitWhilePending() in AsyncTableElementReactiveForms.isValid()
44693
+ // (from @e-is/ngx-material-table) from hanging indefinitely if statusChanges never emits.
44694
+ if (row.validator?.pending) {
44695
+ await AppFormUtils.waitWhilePending(row.validator, { timeout: 10000 });
44696
+ }
44295
44697
  // Confirmation edition or creation
44296
44698
  const confirmed = await row.confirmEditCreate();
44297
44699
  if (confirmed) {
@@ -44776,8 +45178,11 @@ class AppAsyncTable {
44776
45178
  return true; // Already the edited row
44777
45179
  if (event?.defaultPrevented)
44778
45180
  return false;
44779
- if (!(await this.confirmEditCreate())) {
44780
- return false;
45181
+ // Don't call confirmEditCreate() if canceling
45182
+ if (!toBoolean(opts?.canceling, false)) {
45183
+ if (!(await this.confirmEditCreate())) {
45184
+ return false;
45185
+ }
44781
45186
  }
44782
45187
  if (!row.editing && !this.loading) {
44783
45188
  this.focusColumn = opts?.focusColumn ?? this.focusColumn;
@@ -44869,8 +45274,8 @@ class AppAsyncTable {
44869
45274
  }
44870
45275
  }
44871
45276
  // Ask user confirmation
44872
- if (this.confirmBeforeCancel && (!opts || opts.interactive !== false)) {
44873
- return this.askCancelConfirmation(null, rows);
45277
+ if (this.confirmBeforeCancel && opts?.interactive !== false) {
45278
+ return this.askCancelConfirmation(null, rows, opts);
44874
45279
  }
44875
45280
  return true;
44876
45281
  }
@@ -45124,20 +45529,20 @@ class AppAsyncTable {
45124
45529
  }
45125
45530
  async askDeleteConfirmation(event, rows, opts) {
45126
45531
  if (isNotNilOrBlank(opts?.messageKey)) {
45127
- return Alerts.askConfirmation(opts.messageKey, this.alertCtrl, this.translate, event);
45532
+ return Alerts.askConfirmation(opts.messageKey, this.alertCtrl, this.translate, event, opts);
45128
45533
  }
45129
45534
  if (this.undoableDeletion) {
45130
45535
  // Special message, for undoable deletion
45131
- return Alerts.askConfirmation(rows?.length === 1 ? 'CONFIRM.DELETE_ROW' : 'CONFIRM.DELETE_ROWS', this.alertCtrl, this.translate, event);
45536
+ return Alerts.askConfirmation(rows?.length === 1 ? 'CONFIRM.DELETE_ROW' : 'CONFIRM.DELETE_ROWS', this.alertCtrl, this.translate, event, opts);
45132
45537
  }
45133
45538
  // Immediate deletion action
45134
- return Alerts.askActionConfirmation(this.alertCtrl, this.translate, true, event);
45539
+ return Alerts.askActionConfirmation(this.alertCtrl, this.translate, true, event, opts);
45135
45540
  }
45136
- async askCancelConfirmation(event, rows) {
45137
- return Alerts.askConfirmation(rows?.length === 1 ? 'CONFIRM.CANCEL_ROW' : 'CONFIRM.CANCEL_ROWS', this.alertCtrl, this.translate, event);
45541
+ async askCancelConfirmation(event, rows, opts) {
45542
+ return Alerts.askConfirmation(rows?.length === 1 ? 'CONFIRM.CANCEL_ROW' : 'CONFIRM.CANCEL_ROWS', this.alertCtrl, this.translate, event, opts);
45138
45543
  }
45139
- async askRestoreConfirmation(event) {
45140
- return Alerts.askActionConfirmation(this.alertCtrl, this.translate, false, event);
45544
+ async askRestoreConfirmation(event, opts) {
45545
+ return Alerts.askActionConfirmation(this.alertCtrl, this.translate, false, event, opts);
45141
45546
  }
45142
45547
  async showToast(opts) {
45143
45548
  if (!this.toastController)
@@ -45318,6 +45723,7 @@ class AppAsyncTable {
45318
45723
  // Make sure row will be cancelled, and NOT deleted
45319
45724
  if (row.id === -1 || !row.editing)
45320
45725
  throw new Error('Row cannot be canceling, but only deleting');
45726
+ // TODO invert this condition to call canCancelRows
45321
45727
  const confirmed = !opts || opts.interactive !== false;
45322
45728
  // Ask user confirmation, if cancel
45323
45729
  if (!confirmed && row.dirty && (this.confirmBeforeCancel || this.onBeforeCancelRows.observed)) {
@@ -45333,7 +45739,7 @@ class AppAsyncTable {
45333
45739
  await this.checkIfRowPristine(row);
45334
45740
  // Restore editing state
45335
45741
  if (keepEditing) {
45336
- await this.editRow(undefined, row);
45742
+ await this.editRow(undefined, row, { canceling: true });
45337
45743
  }
45338
45744
  }
45339
45745
  async checkIfRowPristine(row, opts) {
@@ -45383,7 +45789,7 @@ class AppAsyncTable {
45383
45789
  // If changed to True: propagate as soon as possible
45384
45790
  tap$1((loading) => loading && this.setLoading(true)),
45385
45791
  // If changed to False: wait 250ms before propagate (to make sure the spinner has been displayed)
45386
- debounceTime(250), tap$1((loading) => !loading && this.setLoading(false)))
45792
+ debounceTime(this.loadingDebounceTime), tap$1((loading) => !loading && this.setLoading(false)))
45387
45793
  .subscribe();
45388
45794
  this.registerSubscription(this._dataSourceLoadingSubscription);
45389
45795
  }
@@ -45435,7 +45841,7 @@ class AppAsyncTable {
45435
45841
  }
45436
45842
  }
45437
45843
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AppAsyncTable, deps: "invalid", target: i0.ɵɵFactoryTarget.Directive });
45438
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: AppAsyncTable, inputs: { settingsId: "settingsId", debug: "debug", i18nColumnPrefix: "i18nColumnPrefix", i18nColumnSuffix: "i18nColumnSuffix", autoLoad: "autoLoad", readOnly: "readOnly", inlineEdition: "inlineEdition", focusFirstColumn: "focusFirstColumn", confirmBeforeDelete: "confirmBeforeDelete", confirmBeforeCancel: "confirmBeforeCancel", undoableDeletion: "undoableDeletion", saveBeforeDelete: "saveBeforeDelete", keepEditedRowOnSave: "keepEditedRowOnSave", saveBeforeSort: "saveBeforeSort", saveBeforeFilter: "saveBeforeFilter", propagateRowError: "propagateRowError", permanentSelectionAllowed: "permanentSelectionAllowed", openRowThrottleTime: "openRowThrottleTime", defaultSortBy: "defaultSortBy", defaultSortDirection: "defaultSortDirection", defaultPageSize: "defaultPageSize", defaultPageSizeOptions: "defaultPageSizeOptions", focusColumn: "focusColumn", dataSource: "dataSource", filter: "filter", disabled: "disabled", paginator: "paginator" }, outputs: { onRefresh: "onRefresh", onClickRow: "onClickRow", onOpenRow: "onOpenRow", onNewRow: "onNewRow", onStartEditingRow: "onStartEditingRow", onConfirmEditCreateRow: "onConfirmEditCreateRow", onCancelOrDeleteRow: "onCancelOrDeleteRow", onBeforeDeleteRows: "onBeforeDeleteRows", onBeforeCancelRows: "onBeforeCancelRows", onBeforeSave: "onBeforeSave", onAfterDeletedRows: "onAfterDeletedRows", onSort: "onSort", onDirty: "onDirty", onError: "onError" }, viewQueries: [{ propertyName: "table", first: true, predicate: MatTable, descendants: true }, { propertyName: "childPaginator", first: true, predicate: MatPaginator, descendants: true }, { propertyName: "sort", first: true, predicate: MatSort, descendants: true }], ngImport: i0 });
45844
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: AppAsyncTable, inputs: { settingsId: "settingsId", debug: "debug", i18nColumnPrefix: "i18nColumnPrefix", i18nColumnSuffix: "i18nColumnSuffix", autoLoad: "autoLoad", readOnly: "readOnly", inlineEdition: "inlineEdition", focusFirstColumn: "focusFirstColumn", confirmBeforeDelete: "confirmBeforeDelete", confirmBeforeCancel: "confirmBeforeCancel", undoableDeletion: "undoableDeletion", saveBeforeDelete: "saveBeforeDelete", keepEditedRowOnSave: "keepEditedRowOnSave", saveBeforeSort: "saveBeforeSort", saveBeforeFilter: "saveBeforeFilter", propagateRowError: "propagateRowError", permanentSelectionAllowed: "permanentSelectionAllowed", openRowThrottleTime: "openRowThrottleTime", loadingDebounceTime: "loadingDebounceTime", defaultSortBy: "defaultSortBy", defaultSortDirection: "defaultSortDirection", defaultPageSize: "defaultPageSize", defaultPageSizeOptions: "defaultPageSizeOptions", focusColumn: "focusColumn", dataSource: "dataSource", filter: "filter", disabled: "disabled", paginator: "paginator" }, outputs: { onRefresh: "onRefresh", onClickRow: "onClickRow", onOpenRow: "onOpenRow", onNewRow: "onNewRow", onStartEditingRow: "onStartEditingRow", onConfirmEditCreateRow: "onConfirmEditCreateRow", onCancelOrDeleteRow: "onCancelOrDeleteRow", onBeforeDeleteRows: "onBeforeDeleteRows", onBeforeCancelRows: "onBeforeCancelRows", onBeforeSave: "onBeforeSave", onAfterDeletedRows: "onAfterDeletedRows", onSort: "onSort", onDirty: "onDirty", onError: "onError" }, viewQueries: [{ propertyName: "table", first: true, predicate: MatTable, descendants: true }, { propertyName: "childPaginator", first: true, predicate: MatPaginator, descendants: true }, { propertyName: "sort", first: true, predicate: MatSort, descendants: true }], ngImport: i0 });
45439
45845
  }
45440
45846
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AppAsyncTable, decorators: [{
45441
45847
  type: Directive
@@ -45475,6 +45881,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
45475
45881
  type: Input
45476
45882
  }], openRowThrottleTime: [{
45477
45883
  type: Input
45884
+ }], loadingDebounceTime: [{
45885
+ type: Input
45478
45886
  }], defaultSortBy: [{
45479
45887
  type: Input
45480
45888
  }], defaultSortDirection: [{
@@ -48031,7 +48439,7 @@ class AccountPage extends AppForm {
48031
48439
  this.saving = false;
48032
48440
  }
48033
48441
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AccountPage, deps: [{ token: i0.Injector }, { token: i1$3.UntypedFormBuilder }, { token: AccountService }, { token: NetworkService }, { token: i2$1.NavController }, { token: AccountValidatorService }, { token: ConfigService }, { token: i0.ChangeDetectorRef }, { token: ImageService }, { token: ENVIRONMENT }, { token: APP_LOCALES }, { token: APP_BASE_HREF, optional: true }], target: i0.ɵɵFactoryTarget.Component });
48034
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AccountPage, selector: "app-account-page", host: { listeners: { "window:beforeunload": "handleRefresh($event)" } }, viewQueries: [{ propertyName: "tabGroup", first: true, predicate: ["tabGroup"], descendants: true, static: true }, { propertyName: "propertiesTable", first: true, predicate: ["propertiesTable"], descendants: true, static: true }, { propertyName: "tokensTable", first: true, predicate: ["tokensTable"], descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: "<app-toolbar\n [title]=\"'ACCOUNT.TITLE' | translate\"\n color=\"primary\"\n [hasValidate]=\"dirty && !saving\"\n [hasClose]=\"!dirty && !saving\"\n (onValidate)=\"save()\"\n (onClose)=\"close($event)\"\n>\n <ion-buttons slot=\"end\">\n @if (loading || saving) {\n <!-- loader -->\n <ion-spinner></ion-spinner>\n } @else if (network.online) {\n <!-- refresh button (if online) -->\n <ion-button [matTooltip]=\"'COMMON.BTN_REFRESH' | translate\" (click)=\"refresh($event)\">\n <mat-icon slot=\"icon-only\">refresh</mat-icon>\n </ion-button>\n }\n </ion-buttons>\n</app-toolbar>\n\n<ion-content>\n <!-- Install (and upgrade) card -->\n <app-install-upgrade-card [isLogin]=\"isLogin\" [showInstallButton]=\"true\"></app-install-upgrade-card>\n\n <!-- error -->\n @if (mobile && (errorSubject | async); as error) {\n <ion-item lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n }\n\n <form [formGroup]=\"form\" novalidate class=\"form-container\">\n <mat-tab-group\n #tabGroup\n [(selectedIndex)]=\"selectedTabIndex\"\n class=\"mat-mdc-tab-disabled-hidden\"\n [mat-stretch-tabs]=\"mobile\"\n dynamicHeight\n >\n <!-- TAB: user details -->\n <mat-tab [label]=\"'ACCOUNT.USER_DETAILS.TITLE' | translate\" class=\"ion-padding\">\n <ng-template mat-tab-label>\n <mat-icon>\n <ion-icon matPrefix slot=\"start\" name=\"person-circle\"></ion-icon>\n </mat-icon>\n <ion-label>{{ 'ACCOUNT.USER_DETAILS.TITLE' | translate }}</ion-label>\n <ion-icon slot=\"end\" name=\"alert-circle\" color=\"danger\" *ngIf=\"submitted && form.invalid\"></ion-icon>\n </ng-template>\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <!-- left margin -->\n <ion-col size=\"0\" size-lg=\"1\" size-xl=\"2\">&nbsp;</ion-col>\n\n <ion-col class=\"ion-padding\">\n <!-- Avatar block -->\n @if (showAvatar) {\n @let accountAvatar = form?.controls?.avatar?.value || form?.value?.avatar || accountService?.account?.avatar || defaultAvatarImage;\n @let isDefaultAvatar = accountAvatar?.endsWith(defaultAvatarImage);\n <ion-item lines=\"none\" class=\"ion-margin-bottom\" style=\"--inner-padding-end: 0\">\n <ion-button slot=\"start\" fill=\"clear\"\n class=\"user-avatar\"\n [style.background-image]=\"'url(' + (accountAvatar || defaultAvatarImage) + ')'\"\n (click)=\"onChangeAvatar($event)\"\n [disabled]=\"disabled\"\n [matTooltip]=\"'IMAGE.BTN_CAMERA_SOURCE' | translate\"\n >\n @if (!accountReadOnly && enabled) {\n <ion-icon [class.visible-hover]=\"!isDefaultAvatar\" name=\"camera\" slot=\"icon-only\"></ion-icon>\n }\n </ion-button>\n </ion-item>\n }\n\n <ion-list-header><p [innerHTML]=\"'ACCOUNT.USER_DETAILS.DESCRIPTION' | translate\"></p></ion-list-header>\n\n <ion-list [inset]=\"mobile\">\n <!-- Email -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <mat-label>{{ 'USER.EMAIL' | translate }}</mat-label>\n <input matInput formControlName=\"email\" autocomplete=\"section-red email\" />\n <mat-error *ngIf=\"form.controls.email.hasError('required') && form.controls.email.dirty\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"form.controls.email.hasError('email') && form.controls.email.dirty\">\n <span>{{ 'ERROR.FIELD_NOT_VALID_EMAIL' | translate }}</span>\n </mat-error>\n </mat-form-field>\n </ion-item>\n\n @if (email?.notConfirmed) {\n <ion-item lines=\"none\">\n <ion-grid class=\"ion-no-padding ion-padding-bottom\">\n <ion-row>\n <ion-col color=\"danger\">\n <ion-icon slot=\"start\" color=\"danger\" name=\"alert-circle\"></ion-icon>\n <ion-text [innerHTML]=\"'ACCOUNT.EMAIL_NOT_CONFIRMED_LABEL' | translate\"></ion-text>\n </ion-col>\n </ion-row>\n\n <ion-row style=\"height: 51px\">\n <ion-col style=\"text-align: right\">\n <ion-text>\n <small [innerHTML]=\"'ACCOUNT.EMAIL_NOT_RECEIVED_QUESTION' | translate\"></small>\n </ion-text>\n </ion-col>\n <ion-col size=\"auto\">\n @if (!email.sending) {\n <ion-button fill=\"solid\" color=\"secondary\" (click)=\"sendConfirmationEmail($event)\">\n {{ 'ACCOUNT.BTN_RESEND' | translate }}\n </ion-button>\n } @else {\n <ion-spinner class=\"ion-no-padding\"></ion-spinner>\n }\n </ion-col>\n </ion-row>\n\n <ion-row *ngIf=\"email.error && !email.sending\">\n <ion-col>\n <span *ngIf=\"email.error && !email.sending\" [innerHTML]=\"email.error | translate\"></span>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-item>\n }\n <!-- Last name -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <mat-label>{{ 'USER.LAST_NAME' | translate }}</mat-label>\n <input\n matInput\n [appAutofocus]=\"true\"\n [autofocusDelay]=\"500\"\n formControlName=\"lastName\"\n autocomplete=\"section-blue family-name\"\n required\n />\n <mat-error\n *ngIf=\"form.controls.lastName.hasError('required') && form.controls.lastName.dirty\"\n translate\n >\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"form.controls.lastName.hasError('minlength') && form.controls.lastName.dirty\">\n <span>{{ 'ERROR.FIELD_MIN_LENGTH' | translate: { requiredLength: 2 } }}</span>\n </mat-error>\n </mat-form-field>\n </ion-item>\n\n <!-- First name -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <mat-label>{{ 'USER.FIRST_NAME' | translate }}</mat-label>\n <input matInput formControlName=\"firstName\" autocomplete=\"section-blue given-name\" required />\n <mat-error\n *ngIf=\"form.controls.firstName.hasError('required') && form.controls.firstName.dirty\"\n translate\n >\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"form.controls.firstName.hasError('minlength') && form.controls.firstName.dirty\">\n <span>{{ 'ERROR.FIELD_MIN_LENGTH' | translate: { requiredLength: 2 } }}</span>\n </mat-error>\n </mat-form-field>\n </ion-item>\n\n <!-- Additional fields -->\n @for (definition of additionalFields; track definition.key) {\n <ion-item lines=\"none\">\n <app-form-field\n [definition]=\"definition\"\n [formControlName]=\"definition.key\"\n [required]=\"\n (definition.extra && definition.extra.account && definition.extra.account.required) || false\n \"\n ></app-form-field>\n </ion-item>\n }\n </ion-list>\n\n <!-- Security -->\n @if (showSecurityDetails) {\n <ion-list-header><h3 translate>ACCOUNT.SECURITY.TITLE</h3></ion-list-header>\n\n <ion-list [inset]=\"mobile\">\n <ion-item\n [button]=\"mobile\"\n routerDirection=\"forward\"\n lines=\"none\"\n (click)=\"openChangePasswordPage()\"\n [disabled]=\"disabled\"\n >\n @if (!mobile) {\n <ion-icon slot=\"start\" name=\"keypad\"></ion-icon>\n <ion-label>{{ 'ACCOUNT.SECURITY.PASSWORD' | translate }}</ion-label>\n }\n <ion-text color=\"tertiary\">{{ 'ACCOUNT.SECURITY.BTN_CHANGE_PASSWORD' | translate }}</ion-text>\n @if (!mobile) {\n <ion-icon slot=\"end\" color=\"tertiary\" name=\"chevron-forward\"></ion-icon>\n }\n </ion-item>\n </ion-list>\n }\n\n <!-- Technical details -->\n @if (showTechnicalDetails) {\n <ion-list-header><h3 translate>ACCOUNT.USER_DETAILS.TECHNICAL_DIVIDER</h3></ion-list-header>\n\n <ion-list [inset]=\"mobile\">\n <!-- profile -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <mat-label>{{ 'ACCOUNT.USER_DETAILS.PROFILE' | translate }}</mat-label>\n <input matInput hidden readonly=\"true\" formControlName=\"mainProfile\" required />\n <span>{{ 'USER.PROFILE_ENUM.' + form.controls.mainProfile.value | translate }}</span>\n <mat-error\n *ngIf=\"form.controls.firstName.hasError('mainProfile') && form.controls.mainProfile.dirty\"\n translate\n >\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n </ion-item>\n\n <!-- pubkey -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <mat-label>{{ 'ACCOUNT.USER_DETAILS.PUBKEY' | translate }}</mat-label>\n <input matInput readonly=\"true\" formControlName=\"pubkey\" required />\n <mat-error\n *ngIf=\"form.controls.firstName.hasError('pubkey') && form.controls.firstName.dirty\"\n translate\n >\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"form.controls.firstName.hasError('pubkey') && form.controls.firstName.dirty\">\n <span translate>ERROR.FIELD_NOT_VALID_PUBKEY</span>\n </mat-error>\n </mat-form-field>\n </ion-item>\n </ion-list>\n }\n </ion-col>\n\n <!-- right margin -->\n <ion-col size=\"0\" size-lg=\"1\" size-xl=\"2\">&nbsp;</ion-col>\n </ion-row>\n </ion-grid>\n </mat-tab>\n\n <!-- TAB: settings -->\n <mat-tab [label]=\"'ACCOUNT.SETTINGS.TITLE' | translate\" formGroupName=\"settings\">\n <ng-template mat-tab-label>\n <mat-icon>\n <ion-icon matPrefix slot=\"start\" name=\"settings\"></ion-icon>\n </mat-icon>\n <ion-label translate>ACCOUNT.SETTINGS.TITLE</ion-label>\n </ng-template>\n\n <div class=\"ion-padding\">\n <p [innerHTML]=\"'ACCOUNT.SETTINGS.DESCRIPTION' | translate\"></p>\n\n <mat-form-field>\n <mat-label>{{ 'ACCOUNT.SETTINGS.LOCALE' | translate }}</mat-label>\n <mat-select formControlName=\"locale\" required>\n <mat-option *ngFor=\"let item of locales\" [value]=\"item.key\">\n {{ item.value }}\n </mat-option>\n </mat-select>\n <mat-error\n *ngIf=\"settingsForm?.controls.locale.hasError('required') && settingsForm?.controls.locale.dirty\"\n translate\n >\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n\n <!-- lat/long format-->\n @if (showLatLonFormat) {\n @let control = settingsForm | formGetControl: 'latLongFormat';\n <mat-form-field>\n <mat-label>{{ 'ACCOUNT.SETTINGS.LAT_LONG_FORMAT' | translate }}</mat-label>\n <mat-select [formControl]=\"control\" required>\n @for (item of latLongFormats; track item) {\n <mat-option [value]=\"item\">\n {{ 'COMMON.LAT_LONG.ENUM.' + item | uppercase | translate }}\n </mat-option>\n }\n </mat-select>\n @if (control.dirty && control.hasError('required')) {\n <mat-error translate>ERROR.FIELD_REQUIRED</mat-error>\n }\n </mat-form-field>\n }\n </div>\n\n <!-- Options table -->\n <div class=\"options-table\" [class.cdk-visually-hidden]=\"optionDefinitions | isEmptyArray\">\n <app-properties-table\n #propertiesTable\n i18nColumnPrefix=\"ACCOUNT.SETTINGS.OPTIONS.\"\n [definitions]=\"optionDefinitions\"\n [disabled]=\"disabled\"\n [canEdit]=\"true\"\n [debug]=\"debug\"\n (onDirty)=\"$event ? markAsDirty() : undefined\"\n ></app-properties-table>\n </div>\n </mat-tab>\n\n <!-- TAB: tokens -->\n <mat-tab [label]=\"'ACCOUNT.TOKENS.TITLE' | translate\" formGroupName=\"tokens\" [disabled]=\"!showApiTokens\">\n <ng-template mat-tab-label>\n <mat-icon>\n <ion-icon matPrefix slot=\"start\" name=\"ticket\"></ion-icon>\n </mat-icon>\n <ion-label translate>ACCOUNT.TOKENS.TITLE</ion-label>\n </ng-template>\n\n <div class=\"ion-padding\">\n <p [innerHTML]=\"'ACCOUNT.TOKENS.DESCRIPTION' | translate\"></p>\n </div>\n <!-- Tokens table -->\n <div class=\"tokens-table\">\n <app-user-token-table\n #tokensTable\n [disabled]=\"disabled\"\n [canEdit]=\"true\"\n [debug]=\"debug\"\n (onDirty)=\"$event ? markAsDirty() : undefined\"\n ></app-user-token-table>\n </div>\n </mat-tab>\n </mat-tab-group>\n </form>\n</ion-content>\n\n<ion-footer hidden-xs hidden-sm hidden-mobile>\n <app-form-buttons-bar (onCancel)=\"cancel()\" (onSave)=\"save()\" [disabled]=\"!form.dirty || !valid || saving\">\n <!-- error -->\n <ion-item *ngIf=\"error && !mobile\" lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n </app-form-buttons-bar>\n</ion-footer>\n", styles: ["mat-tab-group .mat-tab-body-content{padding:var(--ion-grid-padding);display:grid}form{width:100%}form mat-form-field{width:100%}.user-avatar{-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;background-size:cover;background-repeat:no-repeat;background-position:center;background-color:var(--ion-color-secondary);overflow:hidden!important;font-size:var(--avatar-size, 96px)!important;line-height:var(--avatar-size, 96px);height:var(--avatar-size, 96px)!important;width:var(--avatar-size, 96px)!important;border:solid 1px rgba(var(--ion-color-secondary-rgb),.5);border-radius:50%;display:inline-block}.user-avatar ion-icon{position:fixed;left:0;bottom:16px;width:var(--avatar-size, 96px)}.user-avatar .visible-hover{display:none;visibility:hidden}.user-avatar:hover{background-color:rgba(var(--ion-color-secondary-rgb),.8);border:solid 2px var(--ion-color-secondary-shade);cursor:pointer}.user-avatar:hover .visible-hover{display:inline;visibility:visible}div.options-table{height:calc(100vh - 380px)}div.tokens-table{height:calc(100vh - 250px)}\n"], dependencies: [{ kind: "directive", type: i3$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: i2$1.IonListHeader, selector: "ion-list-header", inputs: ["color", "lines", "mode"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "component", type: i2$1.IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: i2$1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$3.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1$3.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i3.MatPrefix, selector: "[matPrefix], [matIconPrefix], [matTextPrefix]", inputs: ["matTextPrefix"] }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i6$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i6$3.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i2.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "directive", type: i8.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i8.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass"], exportAs: ["matTab"] }, { kind: "component", type: i8.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "directive", type: i1$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: AutofocusDirective, selector: "[autofocus], input[appAutofocus]", inputs: ["appAutofocus", "autofocusDelay"] }, { kind: "component", type: ToolbarComponent, selector: "app-toolbar", inputs: ["progressBarMode", "title", "color", "class", "id", "backHref", "defaultBackHref", "hasValidate", "hasClose", "hasSearch", "canGoBack", "canShowMenu"], outputs: ["onValidate", "onClose", "onValidateAndClose", "onBackClick", "onSearchInput", "onSearch"] }, { kind: "component", type: AppFormField, selector: "app-form-field", inputs: ["definition", "readonly", "disabled", "formControl", "formControlName", "placeholder", "compact", "required", "hideRequiredMarker", "floatLabel", "label", "appearance", "subscriptSizing", "tabindex", "autofocus", "clearable", "chipColor", "class", "debug", "panelClass", "panelWidth"], outputs: ["keyup.enter"] }, { kind: "component", type: FormButtonsBarComponent, selector: "app-form-buttons-bar", inputs: ["disabled", "disabledCancel", "disabledEscape", "classList", "saveButtonColor", "backText", "cancelText", "nextText", "showBack", "showCancel", "showNext", "showSave"], outputs: ["onCancel", "onSave", "onNext", "onBack", "onSaveAndClose", "onSaveAndNext"] }, { kind: "component", type: AppPropertiesTable, selector: "app-properties-table", inputs: ["definitions", "showToolbar"] }, { kind: "component", type: AppInstallUpgradeCard, selector: "app-install-upgrade-card", inputs: ["isLogin", "showUpgradeWarning", "showOfflineWarning", "showInstallButton", "debug"] }, { kind: "component", type: UserTokenTable, selector: "app-user-token-table", inputs: ["useSticky"] }, { kind: "pipe", type: i3$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.UpperCasePipe, name: "uppercase" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }, { kind: "pipe", type: EmptyArrayPipe, name: "isEmptyArray" }, { kind: "pipe", type: FormGetControlPipe, name: "formGetControl" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
48442
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AccountPage, selector: "app-account-page", host: { listeners: { "window:beforeunload": "handleRefresh($event)" } }, viewQueries: [{ propertyName: "tabGroup", first: true, predicate: ["tabGroup"], descendants: true, static: true }, { propertyName: "propertiesTable", first: true, predicate: ["propertiesTable"], descendants: true, static: true }, { propertyName: "tokensTable", first: true, predicate: ["tokensTable"], descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: "<app-toolbar\n [title]=\"'ACCOUNT.TITLE' | translate\"\n color=\"primary\"\n [hasValidate]=\"dirty && !saving\"\n [hasClose]=\"!dirty && !saving\"\n (onValidate)=\"save()\"\n (onClose)=\"close($event)\"\n>\n <ion-buttons slot=\"end\">\n @if (loading || saving) {\n <!-- loader -->\n <ion-spinner></ion-spinner>\n } @else if (network.online) {\n <!-- refresh button (if online) -->\n <ion-button [matTooltip]=\"'COMMON.BTN_REFRESH' | translate\" (click)=\"refresh($event)\">\n <mat-icon slot=\"icon-only\">refresh</mat-icon>\n </ion-button>\n }\n </ion-buttons>\n</app-toolbar>\n\n<ion-content>\n <!-- Install (and upgrade) card -->\n <app-install-upgrade-card [isLogin]=\"isLogin\" [showInstallButton]=\"true\"></app-install-upgrade-card>\n\n <!-- error -->\n @if (mobile && (errorSubject | async); as error) {\n <ion-item lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n }\n\n <form [formGroup]=\"form\" novalidate class=\"form-container\">\n <mat-tab-group\n #tabGroup\n [(selectedIndex)]=\"selectedTabIndex\"\n class=\"mat-mdc-tab-disabled-hidden\"\n [mat-stretch-tabs]=\"mobile\"\n dynamicHeight\n >\n <!-- TAB: user details -->\n <mat-tab [label]=\"'ACCOUNT.USER_DETAILS.TITLE' | translate\" class=\"ion-padding\">\n <ng-template mat-tab-label>\n <mat-icon>\n <ion-icon matPrefix slot=\"start\" name=\"person-circle\"></ion-icon>\n </mat-icon>\n <ion-label>{{ 'ACCOUNT.USER_DETAILS.TITLE' | translate }}</ion-label>\n <ion-icon slot=\"end\" name=\"alert-circle\" color=\"danger\" *ngIf=\"submitted && form.invalid\"></ion-icon>\n </ng-template>\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <!-- left margin -->\n <ion-col size=\"0\" size-lg=\"1\" size-xl=\"2\">&nbsp;</ion-col>\n\n <ion-col class=\"ion-padding\">\n <!-- Avatar block -->\n @if (showAvatar) {\n @let accountAvatar = form?.controls?.avatar?.value || form?.value?.avatar || accountService?.account?.avatar || defaultAvatarImage;\n @let isDefaultAvatar = accountAvatar?.endsWith(defaultAvatarImage);\n <ion-item lines=\"none\" class=\"ion-margin-bottom\" style=\"--inner-padding-end: 0\">\n <ion-button slot=\"start\" fill=\"clear\"\n class=\"user-avatar\"\n [style.background-image]=\"'url(' + (accountAvatar || defaultAvatarImage) + ')'\"\n (click)=\"onChangeAvatar($event)\"\n [disabled]=\"disabled\"\n [matTooltip]=\"'IMAGE.BTN_CAMERA_SOURCE' | translate\"\n >\n @if (!accountReadOnly && enabled) {\n <ion-icon [class.visible-hover]=\"!isDefaultAvatar\" name=\"camera\" slot=\"icon-only\"></ion-icon>\n }\n </ion-button>\n </ion-item>\n }\n\n <ion-list-header><p [innerHTML]=\"'ACCOUNT.USER_DETAILS.DESCRIPTION' | translate\"></p></ion-list-header>\n\n <ion-list [inset]=\"mobile\">\n <!-- Email -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <mat-label>{{ 'USER.EMAIL' | translate }}</mat-label>\n <input matInput formControlName=\"email\" autocomplete=\"section-red email\" />\n <mat-error *ngIf=\"form.controls.email.hasError('required') && form.controls.email.dirty\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"form.controls.email.hasError('email') && form.controls.email.dirty\">\n <span>{{ 'ERROR.FIELD_NOT_VALID_EMAIL' | translate }}</span>\n </mat-error>\n </mat-form-field>\n </ion-item>\n\n @if (email?.notConfirmed) {\n <ion-item lines=\"none\">\n <ion-grid class=\"ion-no-padding ion-padding-bottom\">\n <ion-row>\n <ion-col color=\"danger\">\n <ion-icon slot=\"start\" color=\"danger\" name=\"alert-circle\"></ion-icon>\n <ion-text [innerHTML]=\"'ACCOUNT.EMAIL_NOT_CONFIRMED_LABEL' | translate\"></ion-text>\n </ion-col>\n </ion-row>\n\n <ion-row style=\"height: 51px\">\n <ion-col style=\"text-align: right\">\n <ion-text>\n <small [innerHTML]=\"'ACCOUNT.EMAIL_NOT_RECEIVED_QUESTION' | translate\"></small>\n </ion-text>\n </ion-col>\n <ion-col size=\"auto\">\n @if (!email.sending) {\n <ion-button fill=\"solid\" color=\"secondary\" (click)=\"sendConfirmationEmail($event)\">\n {{ 'ACCOUNT.BTN_RESEND' | translate }}\n </ion-button>\n } @else {\n <ion-spinner class=\"ion-no-padding\"></ion-spinner>\n }\n </ion-col>\n </ion-row>\n\n <ion-row *ngIf=\"email.error && !email.sending\">\n <ion-col>\n <span *ngIf=\"email.error && !email.sending\" [innerHTML]=\"email.error | translate\"></span>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-item>\n }\n <!-- Last name -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <mat-label>{{ 'USER.LAST_NAME' | translate }}</mat-label>\n <input\n matInput\n [appAutofocus]=\"true\"\n [autofocusDelay]=\"500\"\n formControlName=\"lastName\"\n autocomplete=\"section-blue family-name\"\n required\n />\n <mat-error\n *ngIf=\"form.controls.lastName.hasError('required') && form.controls.lastName.dirty\"\n translate\n >\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"form.controls.lastName.hasError('minlength') && form.controls.lastName.dirty\">\n <span>{{ 'ERROR.FIELD_MIN_LENGTH' | translate: { requiredLength: 2 } }}</span>\n </mat-error>\n </mat-form-field>\n </ion-item>\n\n <!-- First name -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <mat-label>{{ 'USER.FIRST_NAME' | translate }}</mat-label>\n <input matInput formControlName=\"firstName\" autocomplete=\"section-blue given-name\" required />\n <mat-error\n *ngIf=\"form.controls.firstName.hasError('required') && form.controls.firstName.dirty\"\n translate\n >\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"form.controls.firstName.hasError('minlength') && form.controls.firstName.dirty\">\n <span>{{ 'ERROR.FIELD_MIN_LENGTH' | translate: { requiredLength: 2 } }}</span>\n </mat-error>\n </mat-form-field>\n </ion-item>\n\n <!-- Additional fields -->\n @for (definition of additionalFields; track definition.key) {\n <ion-item lines=\"none\">\n <app-form-field\n [definition]=\"definition\"\n [formControlName]=\"definition.key\"\n [required]=\"\n (definition.extra && definition.extra.account && definition.extra.account.required) || false\n \"\n ></app-form-field>\n </ion-item>\n }\n </ion-list>\n\n <!-- Security -->\n @if (showSecurityDetails) {\n <ion-list-header><h3 translate>ACCOUNT.SECURITY.TITLE</h3></ion-list-header>\n\n <ion-list [inset]=\"mobile\">\n <ion-item\n [button]=\"mobile\"\n routerDirection=\"forward\"\n lines=\"none\"\n (click)=\"openChangePasswordPage()\"\n [disabled]=\"disabled\"\n >\n @if (!mobile) {\n <ion-icon slot=\"start\" name=\"keypad\"></ion-icon>\n <ion-label>{{ 'ACCOUNT.SECURITY.PASSWORD' | translate }}</ion-label>\n }\n <ion-text color=\"tertiary\">{{ 'ACCOUNT.SECURITY.BTN_CHANGE_PASSWORD' | translate }}</ion-text>\n @if (!mobile) {\n <ion-icon slot=\"end\" color=\"tertiary\" name=\"chevron-forward\"></ion-icon>\n }\n </ion-item>\n </ion-list>\n }\n\n <!-- Technical details -->\n @if (showTechnicalDetails) {\n <ion-list-header><h3 translate>ACCOUNT.USER_DETAILS.TECHNICAL_DIVIDER</h3></ion-list-header>\n\n <ion-list [inset]=\"mobile\">\n <!-- profile -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <mat-label>{{ 'ACCOUNT.USER_DETAILS.PROFILE' | translate }}</mat-label>\n <input matInput hidden readonly=\"true\" formControlName=\"mainProfile\" required />\n <span>{{ 'USER.PROFILE_ENUM.' + form.controls.mainProfile.value | translate }}</span>\n <mat-error\n *ngIf=\"form.controls.firstName.hasError('mainProfile') && form.controls.mainProfile.dirty\"\n translate\n >\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n </ion-item>\n\n <!-- pubkey -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <mat-label>{{ 'ACCOUNT.USER_DETAILS.PUBKEY' | translate }}</mat-label>\n <input matInput readonly=\"true\" formControlName=\"pubkey\" required />\n <mat-error\n *ngIf=\"form.controls.firstName.hasError('pubkey') && form.controls.firstName.dirty\"\n translate\n >\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"form.controls.firstName.hasError('pubkey') && form.controls.firstName.dirty\">\n <span translate>ERROR.FIELD_NOT_VALID_PUBKEY</span>\n </mat-error>\n </mat-form-field>\n </ion-item>\n </ion-list>\n }\n </ion-col>\n\n <!-- right margin -->\n <ion-col size=\"0\" size-lg=\"1\" size-xl=\"2\">&nbsp;</ion-col>\n </ion-row>\n </ion-grid>\n </mat-tab>\n\n <!-- TAB: settings -->\n <mat-tab [label]=\"'ACCOUNT.SETTINGS.TITLE' | translate\" formGroupName=\"settings\">\n <ng-template mat-tab-label>\n <mat-icon>\n <ion-icon matPrefix slot=\"start\" name=\"settings\"></ion-icon>\n </mat-icon>\n <ion-label translate>ACCOUNT.SETTINGS.TITLE</ion-label>\n </ng-template>\n\n <div class=\"ion-padding\">\n <p [innerHTML]=\"'ACCOUNT.SETTINGS.DESCRIPTION' | translate\"></p>\n\n <mat-form-field>\n <mat-label>{{ 'ACCOUNT.SETTINGS.LOCALE' | translate }}</mat-label>\n <mat-select formControlName=\"locale\" required>\n <mat-option *ngFor=\"let item of locales\" [value]=\"item.key\">\n {{ item.value }}\n </mat-option>\n </mat-select>\n <mat-error\n *ngIf=\"settingsForm?.controls.locale.hasError('required') && settingsForm?.controls.locale.dirty\"\n translate\n >\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n\n <!-- lat/long format-->\n @if (showLatLonFormat) {\n @let control = settingsForm | formGetControl: 'latLongFormat';\n <mat-form-field>\n <mat-label>{{ 'ACCOUNT.SETTINGS.LAT_LONG_FORMAT' | translate }}</mat-label>\n <mat-select [formControl]=\"control\" required>\n @for (item of latLongFormats; track item) {\n <mat-option [value]=\"item\">\n {{ 'COMMON.LAT_LONG.ENUM.' + item | uppercase | translate }}\n </mat-option>\n }\n </mat-select>\n @if (control.dirty && control.hasError('required')) {\n <mat-error translate>ERROR.FIELD_REQUIRED</mat-error>\n }\n </mat-form-field>\n }\n </div>\n\n <!-- Options table -->\n <div class=\"options-table\" [class.cdk-visually-hidden]=\"optionDefinitions | isEmptyArray\">\n <app-properties-table\n #propertiesTable\n i18nColumnPrefix=\"ACCOUNT.SETTINGS.OPTIONS.\"\n [definitions]=\"optionDefinitions\"\n [disabled]=\"disabled\"\n [canEdit]=\"true\"\n [debug]=\"debug\"\n (onDirty)=\"$event ? markAsDirty() : undefined\"\n ></app-properties-table>\n </div>\n </mat-tab>\n\n <!-- TAB: tokens -->\n <mat-tab [label]=\"'ACCOUNT.TOKENS.TITLE' | translate\" formGroupName=\"tokens\" [disabled]=\"!showApiTokens\">\n <ng-template mat-tab-label>\n <mat-icon>\n <ion-icon matPrefix slot=\"start\" name=\"ticket\"></ion-icon>\n </mat-icon>\n <ion-label translate>ACCOUNT.TOKENS.TITLE</ion-label>\n </ng-template>\n\n <div class=\"ion-padding\">\n <p [innerHTML]=\"'ACCOUNT.TOKENS.DESCRIPTION' | translate\"></p>\n </div>\n <!-- Tokens table -->\n <div class=\"tokens-table\">\n <app-user-token-table\n #tokensTable\n [disabled]=\"disabled\"\n [canEdit]=\"true\"\n [debug]=\"debug\"\n (onDirty)=\"$event ? markAsDirty() : undefined\"\n ></app-user-token-table>\n </div>\n </mat-tab>\n </mat-tab-group>\n </form>\n</ion-content>\n\n<ion-footer hidden-xs hidden-sm hidden-mobile>\n <app-form-buttons-bar (onCancel)=\"cancel()\" (onSave)=\"save()\" [disabled]=\"!form.dirty || !valid || saving\">\n <!-- error -->\n <ion-item *ngIf=\"error && !mobile\" lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n </app-form-buttons-bar>\n</ion-footer>\n", styles: ["mat-tab-group .mat-tab-body-content{padding:var(--ion-grid-padding);display:grid}form{width:100%}form mat-form-field{width:100%}.user-avatar{-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;background-size:cover;background-repeat:no-repeat;background-position:center;background-color:var(--ion-color-secondary);overflow:hidden!important;font-size:var(--avatar-size, 96px)!important;line-height:var(--avatar-size, 96px);height:var(--avatar-size, 96px)!important;width:var(--avatar-size, 96px)!important;border:solid 1px rgba(var(--ion-color-secondary-rgb),.5);border-radius:50%;display:inline-block}.user-avatar ion-icon{position:fixed;left:0;bottom:16px;width:var(--avatar-size, 96px)}.user-avatar .visible-hover{display:none;visibility:hidden}.user-avatar:hover{background-color:rgba(var(--ion-color-secondary-rgb),.8);border:solid 2px var(--ion-color-secondary-shade);cursor:pointer}.user-avatar:hover .visible-hover{display:inline;visibility:visible}div.options-table{height:calc(100vh - 380px)}div.tokens-table{height:calc(100vh - 250px)}\n"], dependencies: [{ kind: "directive", type: i3$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: i2$1.IonListHeader, selector: "ion-list-header", inputs: ["color", "lines", "mode"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "component", type: i2$1.IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: i2$1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$3.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1$3.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i3.MatPrefix, selector: "[matPrefix], [matIconPrefix], [matTextPrefix]", inputs: ["matTextPrefix"] }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i6$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i6$3.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i2.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "directive", type: i8.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i8.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass"], exportAs: ["matTab"] }, { kind: "component", type: i8.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "directive", type: i1$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: AutofocusDirective, selector: "[autofocus], input[appAutofocus]", inputs: ["appAutofocus", "autofocusDelay"] }, { kind: "component", type: ToolbarComponent, selector: "app-toolbar", inputs: ["progressBarMode", "title", "color", "class", "id", "backHref", "defaultBackHref", "hasValidate", "hasClose", "hasSearch", "canGoBack", "canShowMenu"], outputs: ["onValidate", "onClose", "onValidateAndClose", "onBackClick", "onSearchInput", "onSearch"] }, { kind: "component", type: AppFormField, selector: "app-form-field", inputs: ["definition", "readonly", "disabled", "formControl", "formControlName", "placeholder", "compact", "required", "hideRequiredMarker", "floatLabel", "label", "appearance", "subscriptSizing", "tabindex", "autofocus", "clearable", "chipColor", "class", "debug", "panelClass", "panelWidth"], outputs: ["keyup.enter", "definitionChanged"] }, { kind: "component", type: FormButtonsBarComponent, selector: "app-form-buttons-bar", inputs: ["disabled", "disabledCancel", "disabledEscape", "classList", "saveButtonColor", "backText", "cancelText", "nextText", "showBack", "showCancel", "showNext", "showSave"], outputs: ["onCancel", "onSave", "onNext", "onBack", "onSaveAndClose", "onSaveAndNext"] }, { kind: "component", type: AppPropertiesTable, selector: "app-properties-table", inputs: ["definitions", "showToolbar"] }, { kind: "component", type: AppInstallUpgradeCard, selector: "app-install-upgrade-card", inputs: ["isLogin", "showUpgradeWarning", "showOfflineWarning", "showInstallButton", "debug"] }, { kind: "component", type: UserTokenTable, selector: "app-user-token-table", inputs: ["useSticky"] }, { kind: "pipe", type: i3$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.UpperCasePipe, name: "uppercase" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }, { kind: "pipe", type: EmptyArrayPipe, name: "isEmptyArray" }, { kind: "pipe", type: FormGetControlPipe, name: "formGetControl" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
48035
48443
  }
48036
48444
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AccountPage, decorators: [{
48037
48445
  type: Component,
@@ -48962,7 +49370,7 @@ class AppEntityEditor extends AppTabEditor {
48962
49370
  if (this.loading || this.saving)
48963
49371
  return false;
48964
49372
  // Ask user confirmation
48965
- const confirmation = await Alerts.askDeleteConfirmation(this.alertCtrl, this.translate);
49373
+ const confirmation = await Alerts.askDeleteConfirmation(this.alertCtrl, this.translate, event, opts);
48966
49374
  if (!confirmation)
48967
49375
  return;
48968
49376
  console.debug('[data-editor] Asking to delete...');
@@ -50068,7 +50476,7 @@ class UsersPage extends AppTable {
50068
50476
  }
50069
50477
  getDisplayValueFn = FormFieldDefinitionUtils.getDisplayValueFn;
50070
50478
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: UsersPage, deps: [{ token: i0.Injector }, { token: i1$3.UntypedFormBuilder }, { token: AccountService }, { token: i1$8.ValidatorService }, { token: ConfigService }, { token: APP_PERSON_SERVICE }, { token: MessageService }, { token: MenuService }, { token: ENVIRONMENT }], target: i0.ɵɵFactoryTarget.Component });
50071
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: UsersPage, selector: "app-users-table", inputs: { showUsernameColumn: "showUsernameColumn", showUsernameExtranetColumn: "showUsernameExtranetColumn", showEmailColumn: "showEmailColumn", showPubkeyColumn: "showPubkeyColumn", showCreationDateColumn: "showCreationDateColumn", showUpdateDateColumn: "showUpdateDateColumn", title: "title", canSendMessage: ["canSendMessage", "canSendMessage", booleanAttribute], canEdit: ["canEdit", "canEdit", booleanAttribute], compact: ["compact", "compact", booleanAttribute], usePageSettings: ["usePageSettings", "usePageSettings", booleanAttribute], sticky: ["sticky", "sticky", booleanAttribute], stickyEnd: ["stickyEnd", "stickyEnd", booleanAttribute], canDownload: ["canDownload", "canDownload", booleanAttribute], inModal: ["inModal", "inModal", booleanAttribute], showFooter: ["showFooter", "showFooter", booleanAttribute], showToolbar: ["showToolbar", "showToolbar", booleanAttribute], showPaginator: ["showPaginator", "showPaginator", booleanAttribute] }, providers: [{ provide: ValidatorService, useExisting: PersonValidatorService }], viewQueries: [{ propertyName: "filterExpansionPanel", first: true, predicate: MatExpansionPanel, descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: "<app-toolbar\n [title]=\"title || 'USER.LIST.TITLE' | translate\"\n color=\"primary\"\n [canGoBack]=\"false\"\n [class.cdk-visible-hidden]=\"!showToolbar\"\n [hasValidate]=\"(loadingSubject | async) !== true && (dirtySubject | async) === true\"\n (onValidate)=\"save()\"\n>\n <ion-buttons slot=\"end\">\n <!-- Compose message -->\n <button\n mat-icon-button\n *ngIf=\"canSendMessage\"\n [title]=\"!showTooltip ? ('USER.LIST.BTN_SEND_MESSAGE' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('USER.LIST.BTN_SEND_MESSAGE' | translate) : ''\"\n (click)=\"openComposeMessageModal($event)\"\n >\n <ion-icon name=\"mail\" slot=\"icon-only\"></ion-icon>\n </button>\n\n @if (selection | isEmptySelection) {\n <!-- Add -->\n <button\n mat-icon-button\n *ngIf=\"canEdit && !mobile\"\n [title]=\"!showTooltip ? ('COMMON.BTN_ADD' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_ADD' | translate) : ''\"\n (click)=\"addRow()\"\n >\n <mat-icon>add</mat-icon>\n </button>\n\n <!-- Refresh -->\n <button\n mat-icon-button\n *ngIf=\"!mobile\"\n [title]=\"!showTooltip ? ('COMMON.BTN_REFRESH' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_REFRESH' | translate) : ''\"\n (click)=\"emitRefresh()\"\n >\n <mat-icon>refresh</mat-icon>\n </button>\n\n <!-- reset filter -->\n <button mat-icon-button (click)=\"resetFilter()\" *ngIf=\"filterCriteriaCount\">\n <mat-icon color=\"accent\">filter_list_alt</mat-icon>\n <mat-icon class=\"icon-secondary\" style=\"left: 16px; top: 5px; font-weight: bold\">close</mat-icon>\n </button>\n\n <!-- show filter -->\n <button mat-icon-button (click)=\"filterExpansionPanel.toggle()\">\n <mat-icon\n *ngIf=\"filterCriteriaCount; else emptyFilter\"\n [matBadge]=\"filterCriteriaCount\"\n matBadgeColor=\"accent\"\n matBadgeSize=\"small\"\n matBadgePosition=\"above after\"\n aria-hidden=\"false\"\n >\n filter_list_alt\n </mat-icon>\n <ng-template #emptyFilter>\n <mat-icon>filter_list_alt</mat-icon>\n </ng-template>\n </button>\n } @else {\n <!-- delete -->\n <button\n mat-icon-button\n class=\"hidden-xs hidden-sm\"\n [title]=\"!showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n (click)=\"deleteSelection($event)\"\n >\n <mat-icon>delete</mat-icon>\n </button>\n }\n\n @if (!mobile && (canDownload || canDebug)) {\n <button\n mat-icon-button\n [title]=\"!showTooltip ? ('COMMON.BTN_OPTIONS' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_OPTIONS' | translate) : ''\"\n [matMenuTriggerFor]=\"toolbarOptionsMenu\"\n >\n <mat-icon>more_vert</mat-icon>\n </button>\n }\n </ion-buttons>\n</app-toolbar>\n\n<!-- Toolbar option menu -->\n<mat-menu #toolbarOptionsMenu=\"matMenu\" xPosition=\"after\">\n <ng-template matMenuContent>\n <!-- export button -->\n @if (canDownload) {\n <button mat-menu-item (click)=\"exportToCsv($event)\">\n <mat-icon>download</mat-icon>\n <ion-label translate>FILE.CSV.BTN_DOWNLOAD_HELP</ion-label>\n </button>\n }\n\n <!-- Debug only: Test select users modal -->\n @if (canDebug) {\n <mat-divider></mat-divider>\n\n <button mat-menu-item #menuTrigger=\"matMenuTrigger\" (mouseenter)=\"menuTrigger.openMenu()\" [matMenuTriggerFor]=\"debugMenu\">\n <mat-icon>\n <ion-icon name=\"bug\"></ion-icon>\n </mat-icon>\n <ion-label translate>COMMON.DEBUG.BTN_DEBUG_DOTS</ion-label>\n </button>\n }\n </ng-template>\n</mat-menu>\n\n<!-- debug menu -->\n<mat-menu #debugMenu=\"matMenu\">\n <ng-template matMenuContent>\n\n <button mat-menu-item (click)=\"devToggleDebug()\">\n <mat-icon>{{ debug ? 'check_box' : 'check_box_outline_blank' }}</mat-icon>\n <ion-label translate>COMMON.DEBUG.BTN_ENABLE_DEBUG</ion-label>\n </button>\n </ng-template>\n</mat-menu>\n\n<ion-content class=\"ion-no-padding\">\n <!-- error -->\n <ion-item\n *ngIf=\"mobile && (errorSubject | async); let error\"\n lines=\"none\"\n class=\"hidden-sm hidden-xs hidden-mobile\"\n @slideUpDownAnimation\n >\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n\n <!-- debug -->\n <app-debug *ngIf=\"debug\">\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col>\n focusColumn: {{ focusColumn }}\n <br/>\n loadingSubject: {{ loading }}\n <br/>\n </ion-col>\n </ion-row>\n </ion-grid>\n </app-debug>\n\n <!-- search -->\n <mat-expansion-panel #filterExpansionPanel class=\"ion-no-padding filter-panel filter-panel-floating\">\n <form class=\"form-container ion-padding\" [formGroup]=\"filterForm\" (ngSubmit)=\"emitRefresh()\">\n <ion-grid>\n <ion-row>\n <ion-col>\n <!-- search -->\n <mat-form-field>\n <mat-label>{{ 'USER.LIST.FILTER.SEARCH' | translate }}</mat-label>\n <input matInput formControlName=\"searchText\"/>\n <button\n mat-icon-button\n matSuffix\n tabindex=\"-1\"\n type=\"button\"\n (click)=\"clearControlValue($event, filterForm.controls.searchText)\"\n [hidden]=\"filterForm.controls.searchText.disabled || !filterForm.controls.searchText.value\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </mat-form-field>\n </ion-col>\n\n <ion-col>\n <!-- status -->\n <mat-form-field>\n <mat-label>{{ 'USER.STATUS' | translate }}</mat-label>\n <mat-select formControlName=\"statusId\">\n <mat-option [value]=\"null\">\n <i><span translate>COMMON.EMPTY_OPTION</span></i>\n </mat-option>\n <mat-option *ngFor=\"let item of statusList\" [value]=\"item.id\">\n <ion-icon [name]=\"item.icon\"></ion-icon>\n {{ item.label | translate }}\n </mat-option>\n </mat-select>\n\n <button\n mat-icon-button\n matSuffix\n tabindex=\"-1\"\n type=\"button\"\n (click)=\"clearControlValue($event, filterForm.controls.statusId)\"\n [hidden]=\"filterForm.controls.statusId.disabled || filterForm.controls.statusId.value == null\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </mat-form-field>\n </ion-col>\n\n <ion-col>\n <!-- user profiles -->\n <mat-chips-field\n formControlName=\"userProfiles\"\n [placeholder]=\"'USER.PROFILES' | translate\"\n chipColor=\"accent\"\n [items]=\"userProfileList\"\n [displayAttributes]=\"['name']\"\n [clearable]=\"true\">\n </mat-chips-field>\n </ion-col>\n </ion-row>\n\n <!-- Additional fields filters -->\n @if (additionalFilterFields | isNotEmptyArray) {\n <ion-row>\n @for (field of additionalFilterFields; track field.key) {\n <ion-col size=\"12\" size-md=\"6\" size-lg=\"4\">\n <app-form-field\n [definition]=\"field\"\n [formControlName]=\"field.key\"\n [clearable]=\"true\"\n floatLabel=\"auto\">\n </app-form-field>\n </ion-col>\n }\n </ion-row>\n }\n </ion-grid>\n </form>\n\n <mat-action-row>\n <!-- Counter -->\n <ion-label\n [hidden]=\"(loadingSubject | async) === true || filterForm.dirty\"\n [color]=\"empty && 'danger'\"\n class=\"ion-padding\"\n >\n {{\n (totalRowCount ? 'COMMON.RESULT_COUNT' : 'COMMON.NO_RESULT')\n | translate\n : {\n count: (totalRowCount | numberFormat),\n }\n }}\n </ion-label>\n\n <div class=\"toolbar-spacer\"></div>\n\n <!-- Close panel -->\n <ion-button\n mat-button\n fill=\"clear\"\n color=\"dark\"\n (click)=\"filterExpansionPanel.close()\"\n [disabled]=\"loadingSubject | async\"\n >\n <ion-text translate>COMMON.BTN_CLOSE</ion-text>\n </ion-button>\n\n <!-- Search button -->\n <ion-button\n mat-button\n [color]=\"filterForm.dirty ? 'tertiary' : 'dark'\"\n [fill]=\"filterForm.dirty ? 'solid' : 'clear'\"\n (click)=\"applyFilterAndClosePanel($event)\"\n [disabled]=\"loadingSubject | async\"\n >\n <ion-text translate>COMMON.BTN_APPLY</ion-text>\n </ion-button>\n </mat-action-row>\n </mat-expansion-panel>\n\n <!-- error -->\n @if (mobile && (errorSubject | async); as error) {\n <ion-item lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n }\n\n <div class=\"table-container\" [class.has-toolbar]=\"showToolbar\"\n [class.has-paginator]=\"showPaginator\" [class.has-form-buttons]=\"showFormButtons\">\n <table\n #table\n mat-table\n matSort\n [class.compact]=\"compact\"\n [dataSource]=\"dataSource\"\n [matSortActive]=\"defaultSortBy\"\n [matSortDirection]=\"defaultSortDirection\"\n matSortDisableClear\n [trackBy]=\"trackByFn\"\n >\n <ng-container matColumnDef=\"select\" [sticky]=\"sticky\">\n <th mat-header-cell *matHeaderCellDef [class.cdk-visually-hidden]=\"!inlineEdition\">\n <mat-checkbox\n (change)=\"$event ? masterToggle() : null\"\n [checked]=\"this | isAllSelected\"\n [indeterminate]=\"this | isNotAllSelected\"\n ></mat-checkbox>\n </th>\n <td mat-cell *matCellDef=\"let row\" [class.cdk-visually-hidden]=\"!inlineEdition\">\n <mat-checkbox (click)=\"toggleSelectRow($event, row)\" [checked]=\"selection | isSelected: row\"></mat-checkbox>\n </td>\n </ng-container>\n\n <!-- Id Column -->\n <ng-container matColumnDef=\"id\" [sticky]=\"sticky\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <ion-label>#</ion-label>\n </th>\n <td mat-cell *matCellDef=\"let row\">\n <ion-label>{{ row.currentData.id }}</ion-label>\n </td>\n </ng-container>\n\n <!-- avatar Column -->\n <ng-container matColumnDef=\"avatar\" [sticky]=\"sticky\">\n <th mat-header-cell *matHeaderCellDef></th>\n <td mat-cell *matCellDef=\"let row\">\n <ion-avatar [style.background-color]=\"'white'\">\n @if (row.currentData.avatar; as avatarUrl) {\n <ion-img [src]=\"avatarUrl\"></ion-img>\n } @else {\n <svg width=\"39\" height=\"39\" [data-jdenticon-value]=\"row.currentData.id\"></svg>\n }\n </ion-avatar>\n </td>\n </ng-container>\n\n <!-- lastName -->\n <ng-container matColumnDef=\"lastName\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <span translate>USER.LAST_NAME</span>\n </th>\n <td mat-cell *matCellDef=\"let row\" (click)=\"focusColumn = 'lastName'\">\n @if (row.editing) {\n <mat-form-field>\n <input\n matInput\n [formControl]=\"row.validator.controls['lastName']\"\n [placeholder]=\"'USER.LAST_NAME' | translate\"\n [readonly]=\"!row.editing\"\n [appAutofocus]=\"row.editing && focusColumn === 'lastName'\"\n />\n <mat-error *ngIf=\"row.validator.controls['lastName'].hasError('required')\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"row.validator.controls['lastName'].hasError('minlength')\">\n <span>{{ 'ERROR.FIELD_MIN_LENGTH' | translate: {requiredLength: 2} }}</span>\n </mat-error>\n </mat-form-field>\n } @else {\n <ion-label appAutoTitle>\n {{ row.validator | formGetValue: 'lastName' }}\n </ion-label>\n }\n </td>\n </ng-container>\n\n <!-- firstname -->\n <ng-container matColumnDef=\"firstName\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <span translate>USER.FIRST_NAME</span>\n </th>\n <td mat-cell *matCellDef=\"let row\" (click)=\"focusColumn = 'firstName'\">\n @if (row.editing) {\n <mat-form-field>\n <input\n matInput\n [formControl]=\"row.validator.controls['firstName']\"\n [placeholder]=\"'USER.FIRST_NAME' | translate\"\n [readonly]=\"!row.editing\"\n [appAutofocus]=\"row.editing && focusColumn === 'firstName'\"\n />\n <mat-error *ngIf=\"row.validator.controls['firstName'].hasError('required')\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"row.validator.controls['firstName'].hasError('minlength')\">\n <span>{{ 'ERROR.FIELD_MIN_LENGTH' | translate: {requiredLength: 2} }}</span>\n </mat-error>\n </mat-form-field>\n } @else {\n <ion-label appAutoTitle>\n {{ row.validator | formGetValue: 'firstName' }}\n </ion-label>\n }\n </td>\n </ng-container>\n\n <!-- email -->\n <ng-container matColumnDef=\"email\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <span translate>USER.EMAIL</span>\n </th>\n <td mat-cell *matCellDef=\"let row\" (click)=\"focusColumn = 'email'\">\n @if (row.editing) {\n <mat-form-field>\n <input\n matInput\n [formControl]=\"row.validator.controls['email']\"\n [placeholder]=\"'USER.EMAIL' | translate\"\n [readonly]=\"!row.editing\"\n [appAutofocus]=\"row.editing && focusColumn === 'email'\"\n />\n <mat-error *ngIf=\"row.validator.controls['email'].hasError('required')\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"row.validator.controls['email'].hasError('email')\">\n <span translate>ERROR.FIELD_NOT_VALID_EMAIL</span>\n </mat-error>\n </mat-form-field>\n } @else {\n <ion-label appAutoTitle>\n {{ row.validator | formGetValue: 'email' }}\n </ion-label>\n }\n </td>\n </ng-container>\n\n <!-- additional fields -->\n @for (definition of additionalFields; track definition.key) {\n <ng-container [matColumnDef]=\"definition.key\">\n <th mat-header-cell *matHeaderCellDef>\n <span>{{ definition.label | translate }}</span>\n </th>\n <td mat-cell *matCellDef=\"let row\" (click)=\"focusColumn = definition.key\">\n @if (row.editing) {\n <app-form-field\n floatLabel=\"never\"\n [definition]=\"definition\"\n [formControl]=\"row.validator.controls[definition.key]\"\n [required]=\"definition.extra?.users?.required\"\n [autofocus]=\"row.editing && focusColumn === definition.key\"\n ></app-form-field>\n } @else {\n <ion-label appAutoTitle>{{ row.validator | formGetValue: definition.key | displayWith: getDisplayValueFn(definition) }}</ion-label>\n }\n </td>\n </ng-container>\n }\n\n <!-- profile column -->\n <ng-container matColumnDef=\"profile\">\n <th mat-header-cell *matHeaderCellDef>\n <span translate>USER.PROFILE</span>\n </th>\n <td mat-cell *matCellDef=\"let row\" (click)=\"focusColumn = 'profile'\">\n @if (row.editing) {\n <mat-form-field>\n <mat-select\n [formControl]=\"row.validator | formGetControl: 'mainProfile'\"\n [placeholder]=\"'USER.PROFILE' | translate\"\n >\n <mat-option *ngFor=\"let item of profiles\" [value]=\"item\">\n {{ 'USER.PROFILE_ENUM.' + item | uppercase | translate }}\n </mat-option>\n </mat-select>\n <mat-error *ngIf=\"row.validator.controls['mainProfile'].hasError('required')\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n } @else {\n @let profile = row.validator | formGetValue: 'mainProfile';\n @if (profile) {\n <ion-label appAutoTitle>{{ 'USER.PROFILE_ENUM.' + profile | uppercase | translate }}</ion-label>\n }\n }\n\n </td>\n </ng-container>\n\n <!-- Status column -->\n <ng-container matColumnDef=\"status\">\n <th mat-header-cell *matHeaderCellDef>\n <span translate>USER.STATUS</span>\n </th>\n <td mat-cell *matCellDef=\"let row\" (click)=\"focusColumn = 'status'\">\n @if (row.editing) {\n @let control = row.validator | formGetControl: 'statusId';\n <mat-form-field>\n <mat-select\n [formControl]=\"control\"\n [placeholder]=\"'REFERENTIAL.STATUS' | translate\"\n >\n <mat-select-trigger>\n <span *ngIf=\"control.value >= 0\">\n {{ statusById[control.value]?.label | translate }}\n </span>\n </mat-select-trigger>\n @for (item of statusList; track item.id) {\n <mat-option [value]=\"item.id\">\n <mat-icon>\n <ion-icon [name]=\"item.icon\"></ion-icon>\n </mat-icon>\n {{ item.label | translate }}\n </mat-option>\n }\n </mat-select>\n <mat-error *ngIf=\"control.hasError('required')\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n } @else {\n @let statusId = row.validator | formGetValue: 'statusId';\n @if (statusId >= 0) {\n <ion-label appAutoTitle>\n {{ (statusById | mapGet: statusId)?.label | translate }}\n </ion-label>\n }\n }\n </td>\n </ng-container>\n\n <!-- username -->\n <ng-container matColumnDef=\"username\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <span translate>USER.USERNAME</span>\n </th>\n <td mat-cell *matCellDef=\"let row\" (click)=\"focusColumn = 'username'\">\n @if (row.editing) {\n <mat-form-field>\n <input\n matInput\n [formControl]=\"row.validator.controls.username\"\n [placeholder]=\"'USER.USERNAME' | translate\"\n [readonly]=\"!row.editing\"\n [appAutofocus]=\"row.editing && focusColumn === 'username'\"\n />\n <mat-error *ngIf=\"row.validator.controls.username.hasError('required')\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n } @else {\n <ion-label appAutoTitle>\n {{ row.validator | formGetValue: 'username' }}\n </ion-label>\n }\n </td>\n </ng-container>\n\n <!-- username extranet -->\n <ng-container matColumnDef=\"usernameExtranet\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <span translate>USER.USERNAME_EXTRANET</span>\n </th>\n <td mat-cell *matCellDef=\"let row\" (click)=\"focusColumn = 'usernameExtranet'\">\n <mat-form-field *ngIf=\"row.editing; else readonlyCell\">\n <input\n matInput\n [formControl]=\"row.validator.controls.usernameExtranet\"\n [placeholder]=\"'USER.USERNAME_EXTRANET' | translate\"\n [readonly]=\"!row.editing\"\n [appAutofocus]=\"row.editing && focusColumn === 'usernameExtranet'\"\n />\n <mat-error *ngIf=\"row.validator.controls.usernameExtranet.hasError('required')\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n <ng-template #readonlyCell>\n <ion-label appAutoTitle>\n {{ row.validator | formGetValue: 'usernameExtranet' }}\n </ion-label>\n </ng-template>\n </td>\n </ng-container>\n\n <!-- pubkey -->\n <ng-container matColumnDef=\"pubkey\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <span translate>USER.PUBKEY</span>\n </th>\n <td\n mat-cell\n *matCellDef=\"let row\"\n [title]=\"!showTooltip ? (row.validator.controls.pubkey.valueChanges | async) : ''\"\n [matTooltip]=\"showTooltip ? (row.validator.controls.pubkey.valueChanges | async) : ''\"\n (click)=\"focusColumn = 'pubkey'\"\n >\n <mat-form-field *ngIf=\"row.editing; else readonlyCell\">\n <input\n matInput\n [formControl]=\"row.validator.controls['pubkey']\"\n [placeholder]=\"'USER.PUBKEY' | translate\"\n [readonly]=\"!row.editing\"\n autocomplete=\"off\"\n />\n <mat-error *ngIf=\"row.validator.controls.pubkey.hasError('required')\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"row.validator.controls.pubkey.hasError('pubkey')\">\n <span translate>ERROR.FIELD_NOT_VALID_PUBKEY</span>\n </mat-error>\n </mat-form-field>\n <ng-template #readonlyCell>\n <ion-label appAutoTitle>\n {{ row.validator | formGetValue: 'pubkey' }}\n </ion-label>\n </ng-template>\n </td>\n </ng-container>\n\n <!-- Creation date column -->\n <ng-container matColumnDef=\"creationDate\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <ion-label translate>USER.CREATION_DATE</ion-label>\n </th>\n <td mat-cell *matCellDef=\"let row\" class=\"mat-form-field-disabled\">\n <ion-text class=\"ion-text-end\" color=\"medium\" *ngIf=\"row.id !== -1\">\n {{ row.currentData.creationDate | dateFormat: {time: true} }}\n </ion-text>\n </td>\n </ng-container>\n\n <!-- Update date column -->\n <ng-container matColumnDef=\"updateDate\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <ion-label translate>USER.UPDATE_DATE</ion-label>\n </th>\n <td mat-cell *matCellDef=\"let row\" class=\"mat-form-field-disabled\">\n <ion-text class=\"ion-text-end\" color=\"medium\" *ngIf=\"row.id !== -1\">\n {{ row.currentData.updateDate | dateFormat: {time: true} }}\n </ion-text>\n </td>\n </ng-container>\n\n <!-- Actions buttons column -->\n <app-actions-column\n [stickyEnd]=\"stickyEnd\"\n (cancelOrDeleteClick)=\"cancelOrDelete($event.event, $event.row)\"\n (confirmEditCreateClick)=\"confirmEditCreate($event.event, $event.row)\"\n (confirmAndAddClick)=\"confirmAndAdd($event.event, $event.row)\"\n (backward)=\"confirmAndBackward($event.event, $event.row)\"\n (forward)=\"confirmAndForward($event.event, $event.row)\"\n [canCancel]=\"false\"\n >\n <!-- table options menu -->\n <button\n matHeader\n mat-icon-button\n [title]=\"!showTooltip ? ('COMMON.BTN_OPTIONS' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_OPTIONS' | translate) : ''\"\n [matMenuTriggerFor]=\"tableOptionsMenu\"\n >\n <mat-icon>more_vert</mat-icon>\n </button>\n </app-actions-column>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns; sticky: true\"></tr>\n <tr\n mat-row\n *matRowDef=\"let row; columns: displayedColumns\"\n [class.mat-row-error]=\"row.validator.invalid\"\n [class.mat-row-dirty]=\"row.currentData.dirty\"\n [class.mat-row-disabled]=\"!row.editing\"\n [class.mat-mdc-row-selected]=\"row.editing || (!canEdit && selection | isSelected: row)\"\n (click)=\"clickRow($event, row)\"\n (keydown.escape)=\"escapeEditingRow($event)\"\n [cdkTrapFocus]=\"!row.validator?.valid\"\n ></tr>\n </table>\n\n <!-- D\u00E9clencheur invisible pour le mat-menu -->\n\n @if (loadingSubject | async) {\n <ion-item>\n <ion-skeleton-text animated></ion-skeleton-text>\n </ion-item>\n } @else if (totalRowCount === 0) {\n <ion-item>\n <ion-text color=\"danger\" class=\"text-italic\" translate>COMMON.NO_RESULT</ion-text>\n </ion-item>\n }\n </div>\n</ion-content>\n\n<!-- Table options menu -->\n<mat-menu #tableOptionsMenu=\"matMenu\" xPosition=\"after\">\n <!-- display columns -->\n <button mat-menu-item (click)=\"openSelectColumnsModal($event)\">\n <mat-icon>view_column</mat-icon>\n <ion-label translate>COMMON.DISPLAYED_COLUMNS_DOTS</ion-label>\n </button>\n\n @if (usePageSettings) {\n <mat-divider></mat-divider>\n\n <!-- Compact mode -->\n <button mat-menu-item (click)=\"toggleCompactMode()\">\n <mat-icon>{{ compact ? 'check_box' : 'check_box_outline_blank' }}</mat-icon>\n <ion-label translate>COMMON.BTN_COMPACT_ROWS</ion-label>\n </button>\n }\n</mat-menu>\n\n<ion-footer>\n <mat-paginator *ngIf=\"showPaginator\"\n [length]=\"totalRowCount\"\n [pageSize]=\"defaultPageSize\"\n [pageSizeOptions]=\"defaultPageSizeOptions\"\n showFirstLastButtons\n ></mat-paginator>\n\n @if (showFormButtons) {\n <app-form-buttons-bar\n (onCancel)=\"cancel($event)\"\n (onSave)=\"save()\"\n [disabled]=\"(loadingSubject | async) === true || (dirtySubject | async) !== true\"\n >\n <!-- error -->\n <ion-item *ngIf=\"errorSubject | async; let error\" lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n </app-form-buttons-bar>\n }\n</ion-footer>\n\n<ion-fab slot=\"fixed\" vertical=\"bottom\" horizontal=\"end\" *ngIf=\"mobile\">\n <ion-fab-button color=\"tertiary\" (click)=\"addRow()\">\n <ion-icon name=\"add\"></ion-icon>\n </ion-fab-button>\n</ion-fab>\n", styles: [".mat-expansion-panel{margin-bottom:5px}.mat-expansion-panel .form-container mat-form-field{width:100%}.mat-expansion-panel mat-action-row ion-label{line-height:36px}ion-content{height:calc(100% - var(--table-offset, 0px))}.table-container{height:100%}.table-container.has-toolbar{--table-toolbar-height: var(--mat-toolbar-height, 64px)}.table-container.has-paginator{--table-paginator-height: var(--app-paginator-height, 34px)}.table-container.has-form-buttons{--form-buttons-height: var(--ion-toolbar-height, 56px)}.mat-mdc-table .mat-column-avatar{--avatar-width: 40px;--avatar-height: 40px;width:calc(var(--avatar-width) + 10px)}.mat-mdc-table .mat-column-avatar ion-avatar{--border-radius: 5px !important;--border-width: 1px !important;--border-color: rgba(var(--ion-color-secondary-rgb), .5) !important;height:var(--avatar-height);max-height:var(--avatar-height);width:var(--avatar-width);max-width:var(--avatar-width);border:solid var(--border-width) var(--border-color);margin:0 2px}.mat-mdc-table .mat-column-avatar ion-avatar svg{max-height:calc(var(--avatar-height) - 1px);max-width:calc(var(--avatar-width) - 1px)}.mat-mdc-table.compact{--mat-row-height: 33px}.mat-mdc-table.compact .mat-column-avatar{--avatar-width: calc(var(--mat-row-height, 33px) - 4px);--avatar-height: calc(var(--mat-row-height, 33px) - 4px)}.mat-mdc-table .mat-column-id{min-width:50px;max-width:120px;padding-right:var(--mat-cell-horizontal-padding, 4px)}.mat-mdc-table .mat-column-lastName,.mat-mdc-table .mat-column-firstName{min-width:80px}.mat-mdc-table .mat-column-email,.mat-mdc-table .mat-column-pubkey,.mat-mdc-table .mat-column-department{min-width:180px}.mat-mdc-table .mat-column-profile{min-width:110px}.mat-mdc-table .mat-column-status,.mat-mdc-table .mat-column-creationDate,.mat-mdc-table .mat-column-updateDate{min-width:130px}\n"], dependencies: [{ kind: "directive", type: i4$3.SvgJdenticonDirective, selector: "svg[data-jdenticon-hash],svg[data-jdenticon-value]", inputs: ["data-jdenticon-hash", "data-jdenticon-value", "width", "height"] }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "directive", type: i3$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i2$1.IonAvatar, selector: "ion-avatar" }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonFab, selector: "ion-fab", inputs: ["activated", "edge", "horizontal", "vertical"] }, { kind: "component", type: i2$1.IonFabButton, selector: "ion-fab-button", inputs: ["activated", "closeIcon", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "show", "size", "target", "translucent", "type"] }, { kind: "component", type: i2$1.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonImg, selector: "ion-img", inputs: ["alt", "src"] }, { kind: "component", type: i2$1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "component", type: i2$1.IonSkeletonText, selector: "ion-skeleton-text", inputs: ["animated"] }, { kind: "component", type: i2$1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$3.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i7.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i7.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i7.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i7.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i7.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i7.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i7.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i7.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i7.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i7.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "directive", type: i8$2.MatSort, selector: "[matSort]", inputs: ["matSortActive", "matSortStart", "matSortDirection", "matSortDisableClear", "matSortDisabled"], outputs: ["matSortChange"], exportAs: ["matSort"] }, { kind: "component", type: i8$2.MatSortHeader, selector: "[mat-sort-header]", inputs: ["mat-sort-header", "arrowPosition", "start", "disabled", "sortActionDescription", "disableClear"], exportAs: ["matSortHeader"] }, { kind: "component", type: i9$2.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i3.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i5.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: i13$4.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "directive", type: i13$4.MatExpansionPanelActionRow, selector: "mat-action-row" }, { kind: "component", type: i6$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i11$1.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i12.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i12.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i12.MatMenuContent, selector: "ng-template[matMenuContent]" }, { kind: "directive", type: i12.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i6$3.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "directive", type: i6$3.MatSelectTrigger, selector: "mat-select-trigger" }, { kind: "component", type: i2.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i9.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i13$1.MatBadge, selector: "[matBadge]", inputs: ["matBadgeColor", "matBadgeOverlap", "matBadgeDisabled", "matBadgePosition", "matBadge", "matBadgeDescription", "matBadgeSize", "matBadgeHidden"] }, { kind: "directive", type: i1$6.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "directive", type: i1$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: MatChipsField, selector: "mat-chips-field", inputs: ["equals", "logPrefix", "formControl", "formControlName", "floatLabel", "placeholder", "suggestFn", "required", "mobile", "readonly", "clearable", "debounceTime", "displaySeparator", "displayWith", "displayAttributes", "displayColumnSizes", "displayColumnNames", "highlightAccent", "showAllOnFocus", "showPanelOnFocus", "autofocus", "config", "i18nPrefix", "noResultMessage", "panelClass", "panelWidth", "disableRipple", "matAutocompletePosition", "itemSize", "fetchMoreThreshold", "suggestLengthThreshold", "showLoadingSpinner", "chipColor", "debug", "applyImplicitValue", "dropButtonTitle", "clearButtonTitle", "trimSearchText", "hideRequiredMarker", "colSizes", "separatorKeysCodes", "showChips", "appearance", "subscriptSizing", "class", "filter", "tabindex", "items"], outputs: ["click", "blur", "focus", "dropButtonClick", "keydown.escape", "keyup.enter"] }, { kind: "directive", type: AutofocusDirective, selector: "[autofocus], input[appAutofocus]", inputs: ["appAutofocus", "autofocusDelay"] }, { kind: "directive", type: AutoTitleDirective, selector: "ion-label[appAutoTitle], mat-label[appAutoTitle]", inputs: ["appAutoTitle"] }, { kind: "component", type: ToolbarComponent, selector: "app-toolbar", inputs: ["progressBarMode", "title", "color", "class", "id", "backHref", "defaultBackHref", "hasValidate", "hasClose", "hasSearch", "canGoBack", "canShowMenu"], outputs: ["onValidate", "onClose", "onValidateAndClose", "onBackClick", "onSearchInput", "onSearch"] }, { kind: "component", type: AppFormField, selector: "app-form-field", inputs: ["definition", "readonly", "disabled", "formControl", "formControlName", "placeholder", "compact", "required", "hideRequiredMarker", "floatLabel", "label", "appearance", "subscriptSizing", "tabindex", "autofocus", "clearable", "chipColor", "class", "debug", "panelClass", "panelWidth"], outputs: ["keyup.enter"] }, { kind: "component", type: FormButtonsBarComponent, selector: "app-form-buttons-bar", inputs: ["disabled", "disabledCancel", "disabledEscape", "classList", "saveButtonColor", "backText", "cancelText", "nextText", "showBack", "showCancel", "showNext", "showSave"], outputs: ["onCancel", "onSave", "onNext", "onBack", "onSaveAndClose", "onSaveAndNext"] }, { kind: "component", type: ActionsColumnComponent, selector: "app-actions-column", inputs: ["matColumnDef", "style", "showPendingSpinner", "stickyEnd", "canCancel", "canConfirm", "canDelete", "canBackward", "canForward", "canConfirmAndAdd", "dirtyIcon", "optionsTitle", "class", "cellTemplateStart", "cellTemplate"], outputs: ["optionsClick", "cancelOrDeleteClick", "confirmEditCreateClick", "confirmAndAddClick", "backward", "forward"] }, { kind: "component", type: DebugComponent, selector: "app-debug", inputs: ["titlePrefix", "title", "enable", "expanded"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }, { kind: "pipe", type: i3$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.UpperCasePipe, name: "uppercase" }, { kind: "pipe", type: DateFormatPipe, name: "dateFormat" }, { kind: "pipe", type: NumberFormatPipe, name: "numberFormat" }, { kind: "pipe", type: NotEmptyArrayPipe, name: "isNotEmptyArray" }, { kind: "pipe", type: MapGetPipe, name: "mapGet" }, { kind: "pipe", type: FormGetControlPipe, name: "formGetControl" }, { kind: "pipe", type: FormGetValuePipe, name: "formGetValue" }, { kind: "pipe", type: IsSelectedPipe, name: "isSelected" }, { kind: "pipe", type: IsEmptySelectionPipe, name: "isEmptySelection" }, { kind: "pipe", type: DisplayWithPipe, name: "displayWith" }, { kind: "pipe", type: IsAllSelectedPipe, name: "isAllSelected" }, { kind: "pipe", type: IsNotAllSelectedPipe, name: "isNotAllSelected" }], animations: [slideUpDownAnimation], changeDetection: i0.ChangeDetectionStrategy.OnPush });
50479
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: UsersPage, selector: "app-users-table", inputs: { showUsernameColumn: "showUsernameColumn", showUsernameExtranetColumn: "showUsernameExtranetColumn", showEmailColumn: "showEmailColumn", showPubkeyColumn: "showPubkeyColumn", showCreationDateColumn: "showCreationDateColumn", showUpdateDateColumn: "showUpdateDateColumn", title: "title", canSendMessage: ["canSendMessage", "canSendMessage", booleanAttribute], canEdit: ["canEdit", "canEdit", booleanAttribute], compact: ["compact", "compact", booleanAttribute], usePageSettings: ["usePageSettings", "usePageSettings", booleanAttribute], sticky: ["sticky", "sticky", booleanAttribute], stickyEnd: ["stickyEnd", "stickyEnd", booleanAttribute], canDownload: ["canDownload", "canDownload", booleanAttribute], inModal: ["inModal", "inModal", booleanAttribute], showFooter: ["showFooter", "showFooter", booleanAttribute], showToolbar: ["showToolbar", "showToolbar", booleanAttribute], showPaginator: ["showPaginator", "showPaginator", booleanAttribute] }, providers: [{ provide: ValidatorService, useExisting: PersonValidatorService }], viewQueries: [{ propertyName: "filterExpansionPanel", first: true, predicate: MatExpansionPanel, descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: "<app-toolbar\n [title]=\"title || 'USER.LIST.TITLE' | translate\"\n color=\"primary\"\n [canGoBack]=\"false\"\n [class.cdk-visible-hidden]=\"!showToolbar\"\n [hasValidate]=\"(loadingSubject | async) !== true && (dirtySubject | async) === true\"\n (onValidate)=\"save()\"\n>\n <ion-buttons slot=\"end\">\n <!-- Compose message -->\n <button\n mat-icon-button\n *ngIf=\"canSendMessage\"\n [title]=\"!showTooltip ? ('USER.LIST.BTN_SEND_MESSAGE' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('USER.LIST.BTN_SEND_MESSAGE' | translate) : ''\"\n (click)=\"openComposeMessageModal($event)\"\n >\n <ion-icon name=\"mail\" slot=\"icon-only\"></ion-icon>\n </button>\n\n @if (selection | isEmptySelection) {\n <!-- Add -->\n <button\n mat-icon-button\n *ngIf=\"canEdit && !mobile\"\n [title]=\"!showTooltip ? ('COMMON.BTN_ADD' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_ADD' | translate) : ''\"\n (click)=\"addRow()\"\n >\n <mat-icon>add</mat-icon>\n </button>\n\n <!-- Refresh -->\n <button\n mat-icon-button\n *ngIf=\"!mobile\"\n [title]=\"!showTooltip ? ('COMMON.BTN_REFRESH' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_REFRESH' | translate) : ''\"\n (click)=\"emitRefresh()\"\n >\n <mat-icon>refresh</mat-icon>\n </button>\n\n <!-- reset filter -->\n <button mat-icon-button (click)=\"resetFilter()\" *ngIf=\"filterCriteriaCount\">\n <mat-icon color=\"accent\">filter_list_alt</mat-icon>\n <mat-icon class=\"icon-secondary\" style=\"left: 16px; top: 5px; font-weight: bold\">close</mat-icon>\n </button>\n\n <!-- show filter -->\n <button mat-icon-button (click)=\"filterExpansionPanel.toggle()\">\n <mat-icon\n *ngIf=\"filterCriteriaCount; else emptyFilter\"\n [matBadge]=\"filterCriteriaCount\"\n matBadgeColor=\"accent\"\n matBadgeSize=\"small\"\n matBadgePosition=\"above after\"\n aria-hidden=\"false\"\n >\n filter_list_alt\n </mat-icon>\n <ng-template #emptyFilter>\n <mat-icon>filter_list_alt</mat-icon>\n </ng-template>\n </button>\n } @else {\n <!-- delete -->\n <button\n mat-icon-button\n class=\"hidden-xs hidden-sm\"\n [title]=\"!showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n (click)=\"deleteSelection($event)\"\n >\n <mat-icon>delete</mat-icon>\n </button>\n }\n\n @if (!mobile && (canDownload || canDebug)) {\n <button\n mat-icon-button\n [title]=\"!showTooltip ? ('COMMON.BTN_OPTIONS' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_OPTIONS' | translate) : ''\"\n [matMenuTriggerFor]=\"toolbarOptionsMenu\"\n >\n <mat-icon>more_vert</mat-icon>\n </button>\n }\n </ion-buttons>\n</app-toolbar>\n\n<!-- Toolbar option menu -->\n<mat-menu #toolbarOptionsMenu=\"matMenu\" xPosition=\"after\">\n <ng-template matMenuContent>\n <!-- export button -->\n @if (canDownload) {\n <button mat-menu-item (click)=\"exportToCsv($event)\">\n <mat-icon>download</mat-icon>\n <ion-label translate>FILE.CSV.BTN_DOWNLOAD_HELP</ion-label>\n </button>\n }\n\n <!-- Debug only: Test select users modal -->\n @if (canDebug) {\n <mat-divider></mat-divider>\n\n <button mat-menu-item #menuTrigger=\"matMenuTrigger\" (mouseenter)=\"menuTrigger.openMenu()\" [matMenuTriggerFor]=\"debugMenu\">\n <mat-icon>\n <ion-icon name=\"bug\"></ion-icon>\n </mat-icon>\n <ion-label translate>COMMON.DEBUG.BTN_DEBUG_DOTS</ion-label>\n </button>\n }\n </ng-template>\n</mat-menu>\n\n<!-- debug menu -->\n<mat-menu #debugMenu=\"matMenu\">\n <ng-template matMenuContent>\n\n <button mat-menu-item (click)=\"devToggleDebug()\">\n <mat-icon>{{ debug ? 'check_box' : 'check_box_outline_blank' }}</mat-icon>\n <ion-label translate>COMMON.DEBUG.BTN_ENABLE_DEBUG</ion-label>\n </button>\n </ng-template>\n</mat-menu>\n\n<ion-content class=\"ion-no-padding\">\n <!-- error -->\n <ion-item\n *ngIf=\"mobile && (errorSubject | async); let error\"\n lines=\"none\"\n class=\"hidden-sm hidden-xs hidden-mobile\"\n @slideUpDownAnimation\n >\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n\n <!-- debug -->\n <app-debug *ngIf=\"debug\">\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col>\n focusColumn: {{ focusColumn }}\n <br/>\n loadingSubject: {{ loading }}\n <br/>\n </ion-col>\n </ion-row>\n </ion-grid>\n </app-debug>\n\n <!-- search -->\n <mat-expansion-panel #filterExpansionPanel class=\"ion-no-padding filter-panel filter-panel-floating\">\n <form class=\"form-container ion-padding\" [formGroup]=\"filterForm\" (ngSubmit)=\"emitRefresh()\">\n <ion-grid>\n <ion-row>\n <ion-col>\n <!-- search -->\n <mat-form-field>\n <mat-label>{{ 'USER.LIST.FILTER.SEARCH' | translate }}</mat-label>\n <input matInput formControlName=\"searchText\"/>\n <button\n mat-icon-button\n matSuffix\n tabindex=\"-1\"\n type=\"button\"\n (click)=\"clearControlValue($event, filterForm.controls.searchText)\"\n [hidden]=\"filterForm.controls.searchText.disabled || !filterForm.controls.searchText.value\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </mat-form-field>\n </ion-col>\n\n <ion-col>\n <!-- status -->\n <mat-form-field>\n <mat-label>{{ 'USER.STATUS' | translate }}</mat-label>\n <mat-select formControlName=\"statusId\">\n <mat-option [value]=\"null\">\n <i><span translate>COMMON.EMPTY_OPTION</span></i>\n </mat-option>\n <mat-option *ngFor=\"let item of statusList\" [value]=\"item.id\">\n <ion-icon [name]=\"item.icon\"></ion-icon>\n {{ item.label | translate }}\n </mat-option>\n </mat-select>\n\n <button\n mat-icon-button\n matSuffix\n tabindex=\"-1\"\n type=\"button\"\n (click)=\"clearControlValue($event, filterForm.controls.statusId)\"\n [hidden]=\"filterForm.controls.statusId.disabled || filterForm.controls.statusId.value == null\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </mat-form-field>\n </ion-col>\n\n <ion-col>\n <!-- user profiles -->\n <mat-chips-field\n formControlName=\"userProfiles\"\n [placeholder]=\"'USER.PROFILES' | translate\"\n chipColor=\"accent\"\n [items]=\"userProfileList\"\n [displayAttributes]=\"['name']\"\n [clearable]=\"true\">\n </mat-chips-field>\n </ion-col>\n </ion-row>\n\n <!-- Additional fields filters -->\n @if (additionalFilterFields | isNotEmptyArray) {\n <ion-row>\n @for (field of additionalFilterFields; track field.key) {\n <ion-col size=\"12\" size-md=\"6\" size-lg=\"4\">\n <app-form-field\n [definition]=\"field\"\n [formControlName]=\"field.key\"\n [clearable]=\"true\"\n floatLabel=\"auto\">\n </app-form-field>\n </ion-col>\n }\n </ion-row>\n }\n </ion-grid>\n </form>\n\n <mat-action-row>\n <!-- Counter -->\n <ion-label\n [hidden]=\"(loadingSubject | async) === true || filterForm.dirty\"\n [color]=\"empty && 'danger'\"\n class=\"ion-padding\"\n >\n {{\n (totalRowCount ? 'COMMON.RESULT_COUNT' : 'COMMON.NO_RESULT')\n | translate\n : {\n count: (totalRowCount | numberFormat),\n }\n }}\n </ion-label>\n\n <div class=\"toolbar-spacer\"></div>\n\n <!-- Close panel -->\n <ion-button\n mat-button\n fill=\"clear\"\n color=\"dark\"\n (click)=\"filterExpansionPanel.close()\"\n [disabled]=\"loadingSubject | async\"\n >\n <ion-text translate>COMMON.BTN_CLOSE</ion-text>\n </ion-button>\n\n <!-- Search button -->\n <ion-button\n mat-button\n [color]=\"filterForm.dirty ? 'tertiary' : 'dark'\"\n [fill]=\"filterForm.dirty ? 'solid' : 'clear'\"\n (click)=\"applyFilterAndClosePanel($event)\"\n [disabled]=\"loadingSubject | async\"\n >\n <ion-text translate>COMMON.BTN_APPLY</ion-text>\n </ion-button>\n </mat-action-row>\n </mat-expansion-panel>\n\n <!-- error -->\n @if (mobile && (errorSubject | async); as error) {\n <ion-item lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n }\n\n <div class=\"table-container\" [class.has-toolbar]=\"showToolbar\"\n [class.has-paginator]=\"showPaginator\" [class.has-form-buttons]=\"showFormButtons\">\n <table\n #table\n mat-table\n matSort\n [class.compact]=\"compact\"\n [dataSource]=\"dataSource\"\n [matSortActive]=\"defaultSortBy\"\n [matSortDirection]=\"defaultSortDirection\"\n matSortDisableClear\n [trackBy]=\"trackByFn\"\n >\n <ng-container matColumnDef=\"select\" [sticky]=\"sticky\">\n <th mat-header-cell *matHeaderCellDef [class.cdk-visually-hidden]=\"!inlineEdition\">\n <mat-checkbox\n (change)=\"$event ? masterToggle() : null\"\n [checked]=\"this | isAllSelected\"\n [indeterminate]=\"this | isNotAllSelected\"\n ></mat-checkbox>\n </th>\n <td mat-cell *matCellDef=\"let row\" [class.cdk-visually-hidden]=\"!inlineEdition\">\n <mat-checkbox (click)=\"toggleSelectRow($event, row)\" [checked]=\"selection | isSelected: row\"></mat-checkbox>\n </td>\n </ng-container>\n\n <!-- Id Column -->\n <ng-container matColumnDef=\"id\" [sticky]=\"sticky\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <ion-label>#</ion-label>\n </th>\n <td mat-cell *matCellDef=\"let row\">\n <ion-label>{{ row.currentData.id }}</ion-label>\n </td>\n </ng-container>\n\n <!-- avatar Column -->\n <ng-container matColumnDef=\"avatar\" [sticky]=\"sticky\">\n <th mat-header-cell *matHeaderCellDef></th>\n <td mat-cell *matCellDef=\"let row\">\n <ion-avatar [style.background-color]=\"'white'\">\n @if (row.currentData.avatar; as avatarUrl) {\n <ion-img [src]=\"avatarUrl\"></ion-img>\n } @else {\n <svg width=\"39\" height=\"39\" [data-jdenticon-value]=\"row.currentData.id\"></svg>\n }\n </ion-avatar>\n </td>\n </ng-container>\n\n <!-- lastName -->\n <ng-container matColumnDef=\"lastName\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <span translate>USER.LAST_NAME</span>\n </th>\n <td mat-cell *matCellDef=\"let row\" (click)=\"focusColumn = 'lastName'\">\n @if (row.editing) {\n <mat-form-field>\n <input\n matInput\n [formControl]=\"row.validator.controls['lastName']\"\n [placeholder]=\"'USER.LAST_NAME' | translate\"\n [readonly]=\"!row.editing\"\n [appAutofocus]=\"row.editing && focusColumn === 'lastName'\"\n />\n <mat-error *ngIf=\"row.validator.controls['lastName'].hasError('required')\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"row.validator.controls['lastName'].hasError('minlength')\">\n <span>{{ 'ERROR.FIELD_MIN_LENGTH' | translate: {requiredLength: 2} }}</span>\n </mat-error>\n </mat-form-field>\n } @else {\n <ion-label appAutoTitle>\n {{ row.validator | formGetValue: 'lastName' }}\n </ion-label>\n }\n </td>\n </ng-container>\n\n <!-- firstname -->\n <ng-container matColumnDef=\"firstName\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <span translate>USER.FIRST_NAME</span>\n </th>\n <td mat-cell *matCellDef=\"let row\" (click)=\"focusColumn = 'firstName'\">\n @if (row.editing) {\n <mat-form-field>\n <input\n matInput\n [formControl]=\"row.validator.controls['firstName']\"\n [placeholder]=\"'USER.FIRST_NAME' | translate\"\n [readonly]=\"!row.editing\"\n [appAutofocus]=\"row.editing && focusColumn === 'firstName'\"\n />\n <mat-error *ngIf=\"row.validator.controls['firstName'].hasError('required')\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"row.validator.controls['firstName'].hasError('minlength')\">\n <span>{{ 'ERROR.FIELD_MIN_LENGTH' | translate: {requiredLength: 2} }}</span>\n </mat-error>\n </mat-form-field>\n } @else {\n <ion-label appAutoTitle>\n {{ row.validator | formGetValue: 'firstName' }}\n </ion-label>\n }\n </td>\n </ng-container>\n\n <!-- email -->\n <ng-container matColumnDef=\"email\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <span translate>USER.EMAIL</span>\n </th>\n <td mat-cell *matCellDef=\"let row\" (click)=\"focusColumn = 'email'\">\n @if (row.editing) {\n <mat-form-field>\n <input\n matInput\n [formControl]=\"row.validator.controls['email']\"\n [placeholder]=\"'USER.EMAIL' | translate\"\n [readonly]=\"!row.editing\"\n [appAutofocus]=\"row.editing && focusColumn === 'email'\"\n />\n <mat-error *ngIf=\"row.validator.controls['email'].hasError('required')\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"row.validator.controls['email'].hasError('email')\">\n <span translate>ERROR.FIELD_NOT_VALID_EMAIL</span>\n </mat-error>\n </mat-form-field>\n } @else {\n <ion-label appAutoTitle>\n {{ row.validator | formGetValue: 'email' }}\n </ion-label>\n }\n </td>\n </ng-container>\n\n <!-- additional fields -->\n @for (definition of additionalFields; track definition.key) {\n <ng-container [matColumnDef]=\"definition.key\">\n <th mat-header-cell *matHeaderCellDef>\n <span>{{ definition.label | translate }}</span>\n </th>\n <td mat-cell *matCellDef=\"let row\" (click)=\"focusColumn = definition.key\">\n @if (row.editing) {\n <app-form-field\n floatLabel=\"never\"\n [definition]=\"definition\"\n [formControl]=\"row.validator.controls[definition.key]\"\n [required]=\"definition.extra?.users?.required\"\n [autofocus]=\"row.editing && focusColumn === definition.key\"\n ></app-form-field>\n } @else {\n <ion-label appAutoTitle>{{ row.validator | formGetValue: definition.key | displayWith: getDisplayValueFn(definition) }}</ion-label>\n }\n </td>\n </ng-container>\n }\n\n <!-- profile column -->\n <ng-container matColumnDef=\"profile\">\n <th mat-header-cell *matHeaderCellDef>\n <span translate>USER.PROFILE</span>\n </th>\n <td mat-cell *matCellDef=\"let row\" (click)=\"focusColumn = 'profile'\">\n @if (row.editing) {\n <mat-form-field>\n <mat-select\n [formControl]=\"row.validator | formGetControl: 'mainProfile'\"\n [placeholder]=\"'USER.PROFILE' | translate\"\n >\n <mat-option *ngFor=\"let item of profiles\" [value]=\"item\">\n {{ 'USER.PROFILE_ENUM.' + item | uppercase | translate }}\n </mat-option>\n </mat-select>\n <mat-error *ngIf=\"row.validator.controls['mainProfile'].hasError('required')\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n } @else {\n @let profile = row.validator | formGetValue: 'mainProfile';\n @if (profile) {\n <ion-label appAutoTitle>{{ 'USER.PROFILE_ENUM.' + profile | uppercase | translate }}</ion-label>\n }\n }\n\n </td>\n </ng-container>\n\n <!-- Status column -->\n <ng-container matColumnDef=\"status\">\n <th mat-header-cell *matHeaderCellDef>\n <span translate>USER.STATUS</span>\n </th>\n <td mat-cell *matCellDef=\"let row\" (click)=\"focusColumn = 'status'\">\n @if (row.editing) {\n @let control = row.validator | formGetControl: 'statusId';\n <mat-form-field>\n <mat-select\n [formControl]=\"control\"\n [placeholder]=\"'REFERENTIAL.STATUS' | translate\"\n >\n <mat-select-trigger>\n <span *ngIf=\"control.value >= 0\">\n {{ statusById[control.value]?.label | translate }}\n </span>\n </mat-select-trigger>\n @for (item of statusList; track item.id) {\n <mat-option [value]=\"item.id\">\n <mat-icon>\n <ion-icon [name]=\"item.icon\"></ion-icon>\n </mat-icon>\n {{ item.label | translate }}\n </mat-option>\n }\n </mat-select>\n <mat-error *ngIf=\"control.hasError('required')\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n } @else {\n @let statusId = row.validator | formGetValue: 'statusId';\n @if (statusId >= 0) {\n <ion-label appAutoTitle>\n {{ (statusById | mapGet: statusId)?.label | translate }}\n </ion-label>\n }\n }\n </td>\n </ng-container>\n\n <!-- username -->\n <ng-container matColumnDef=\"username\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <span translate>USER.USERNAME</span>\n </th>\n <td mat-cell *matCellDef=\"let row\" (click)=\"focusColumn = 'username'\">\n @if (row.editing) {\n <mat-form-field>\n <input\n matInput\n [formControl]=\"row.validator.controls.username\"\n [placeholder]=\"'USER.USERNAME' | translate\"\n [readonly]=\"!row.editing\"\n [appAutofocus]=\"row.editing && focusColumn === 'username'\"\n />\n <mat-error *ngIf=\"row.validator.controls.username.hasError('required')\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n } @else {\n <ion-label appAutoTitle>\n {{ row.validator | formGetValue: 'username' }}\n </ion-label>\n }\n </td>\n </ng-container>\n\n <!-- username extranet -->\n <ng-container matColumnDef=\"usernameExtranet\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <span translate>USER.USERNAME_EXTRANET</span>\n </th>\n <td mat-cell *matCellDef=\"let row\" (click)=\"focusColumn = 'usernameExtranet'\">\n <mat-form-field *ngIf=\"row.editing; else readonlyCell\">\n <input\n matInput\n [formControl]=\"row.validator.controls.usernameExtranet\"\n [placeholder]=\"'USER.USERNAME_EXTRANET' | translate\"\n [readonly]=\"!row.editing\"\n [appAutofocus]=\"row.editing && focusColumn === 'usernameExtranet'\"\n />\n <mat-error *ngIf=\"row.validator.controls.usernameExtranet.hasError('required')\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n <ng-template #readonlyCell>\n <ion-label appAutoTitle>\n {{ row.validator | formGetValue: 'usernameExtranet' }}\n </ion-label>\n </ng-template>\n </td>\n </ng-container>\n\n <!-- pubkey -->\n <ng-container matColumnDef=\"pubkey\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <span translate>USER.PUBKEY</span>\n </th>\n <td\n mat-cell\n *matCellDef=\"let row\"\n [title]=\"!showTooltip ? (row.validator.controls.pubkey.valueChanges | async) : ''\"\n [matTooltip]=\"showTooltip ? (row.validator.controls.pubkey.valueChanges | async) : ''\"\n (click)=\"focusColumn = 'pubkey'\"\n >\n <mat-form-field *ngIf=\"row.editing; else readonlyCell\">\n <input\n matInput\n [formControl]=\"row.validator.controls['pubkey']\"\n [placeholder]=\"'USER.PUBKEY' | translate\"\n [readonly]=\"!row.editing\"\n autocomplete=\"off\"\n />\n <mat-error *ngIf=\"row.validator.controls.pubkey.hasError('required')\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"row.validator.controls.pubkey.hasError('pubkey')\">\n <span translate>ERROR.FIELD_NOT_VALID_PUBKEY</span>\n </mat-error>\n </mat-form-field>\n <ng-template #readonlyCell>\n <ion-label appAutoTitle>\n {{ row.validator | formGetValue: 'pubkey' }}\n </ion-label>\n </ng-template>\n </td>\n </ng-container>\n\n <!-- Creation date column -->\n <ng-container matColumnDef=\"creationDate\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <ion-label translate>USER.CREATION_DATE</ion-label>\n </th>\n <td mat-cell *matCellDef=\"let row\" class=\"mat-form-field-disabled\">\n <ion-text class=\"ion-text-end\" color=\"medium\" *ngIf=\"row.id !== -1\">\n {{ row.currentData.creationDate | dateFormat: {time: true} }}\n </ion-text>\n </td>\n </ng-container>\n\n <!-- Update date column -->\n <ng-container matColumnDef=\"updateDate\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <ion-label translate>USER.UPDATE_DATE</ion-label>\n </th>\n <td mat-cell *matCellDef=\"let row\" class=\"mat-form-field-disabled\">\n <ion-text class=\"ion-text-end\" color=\"medium\" *ngIf=\"row.id !== -1\">\n {{ row.currentData.updateDate | dateFormat: {time: true} }}\n </ion-text>\n </td>\n </ng-container>\n\n <!-- Actions buttons column -->\n <app-actions-column\n [stickyEnd]=\"stickyEnd\"\n (cancelOrDeleteClick)=\"cancelOrDelete($event.event, $event.row)\"\n (confirmEditCreateClick)=\"confirmEditCreate($event.event, $event.row)\"\n (confirmAndAddClick)=\"confirmAndAdd($event.event, $event.row)\"\n (backward)=\"confirmAndBackward($event.event, $event.row)\"\n (forward)=\"confirmAndForward($event.event, $event.row)\"\n [canCancel]=\"false\"\n >\n <!-- table options menu -->\n <button\n matHeader\n mat-icon-button\n [title]=\"!showTooltip ? ('COMMON.BTN_OPTIONS' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_OPTIONS' | translate) : ''\"\n [matMenuTriggerFor]=\"tableOptionsMenu\"\n >\n <mat-icon>more_vert</mat-icon>\n </button>\n </app-actions-column>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns; sticky: true\"></tr>\n <tr\n mat-row\n *matRowDef=\"let row; columns: displayedColumns\"\n [class.mat-row-error]=\"row.validator.invalid\"\n [class.mat-row-dirty]=\"row.currentData.dirty\"\n [class.mat-row-disabled]=\"!row.editing\"\n [class.mat-mdc-row-selected]=\"row.editing || (!canEdit && selection | isSelected: row)\"\n (click)=\"clickRow($event, row)\"\n (keydown.escape)=\"escapeEditingRow($event)\"\n [cdkTrapFocus]=\"!row.validator?.valid\"\n ></tr>\n </table>\n\n <!-- D\u00E9clencheur invisible pour le mat-menu -->\n\n @if (loadingSubject | async) {\n <ion-item>\n <ion-skeleton-text animated></ion-skeleton-text>\n </ion-item>\n } @else if (totalRowCount === 0) {\n <ion-item>\n <ion-text color=\"danger\" class=\"text-italic\" translate>COMMON.NO_RESULT</ion-text>\n </ion-item>\n }\n </div>\n</ion-content>\n\n<!-- Table options menu -->\n<mat-menu #tableOptionsMenu=\"matMenu\" xPosition=\"after\">\n <!-- display columns -->\n <button mat-menu-item (click)=\"openSelectColumnsModal($event)\">\n <mat-icon>view_column</mat-icon>\n <ion-label translate>COMMON.DISPLAYED_COLUMNS_DOTS</ion-label>\n </button>\n\n @if (usePageSettings) {\n <mat-divider></mat-divider>\n\n <!-- Compact mode -->\n <button mat-menu-item (click)=\"toggleCompactMode()\">\n <mat-icon>{{ compact ? 'check_box' : 'check_box_outline_blank' }}</mat-icon>\n <ion-label translate>COMMON.BTN_COMPACT_ROWS</ion-label>\n </button>\n }\n</mat-menu>\n\n<ion-footer>\n <mat-paginator *ngIf=\"showPaginator\"\n [length]=\"totalRowCount\"\n [pageSize]=\"defaultPageSize\"\n [pageSizeOptions]=\"defaultPageSizeOptions\"\n showFirstLastButtons\n ></mat-paginator>\n\n @if (showFormButtons) {\n <app-form-buttons-bar\n (onCancel)=\"cancel($event)\"\n (onSave)=\"save()\"\n [disabled]=\"(loadingSubject | async) === true || (dirtySubject | async) !== true\"\n >\n <!-- error -->\n <ion-item *ngIf=\"errorSubject | async; let error\" lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n </app-form-buttons-bar>\n }\n</ion-footer>\n\n<ion-fab slot=\"fixed\" vertical=\"bottom\" horizontal=\"end\" *ngIf=\"mobile\">\n <ion-fab-button color=\"tertiary\" (click)=\"addRow()\">\n <ion-icon name=\"add\"></ion-icon>\n </ion-fab-button>\n</ion-fab>\n", styles: [".mat-expansion-panel{margin-bottom:5px}.mat-expansion-panel .form-container mat-form-field{width:100%}.mat-expansion-panel mat-action-row ion-label{line-height:36px}ion-content{height:calc(100% - var(--table-offset, 0px))}.table-container{height:100%}.table-container.has-toolbar{--table-toolbar-height: var(--mat-toolbar-height, 64px)}.table-container.has-paginator{--table-paginator-height: var(--app-paginator-height, 34px)}.table-container.has-form-buttons{--form-buttons-height: var(--ion-toolbar-height, 56px)}.mat-mdc-table .mat-column-avatar{--avatar-width: 40px;--avatar-height: 40px;width:calc(var(--avatar-width) + 10px)}.mat-mdc-table .mat-column-avatar ion-avatar{--border-radius: 5px !important;--border-width: 1px !important;--border-color: rgba(var(--ion-color-secondary-rgb), .5) !important;height:var(--avatar-height);max-height:var(--avatar-height);width:var(--avatar-width);max-width:var(--avatar-width);border:solid var(--border-width) var(--border-color);margin:0 2px}.mat-mdc-table .mat-column-avatar ion-avatar svg{max-height:calc(var(--avatar-height) - 1px);max-width:calc(var(--avatar-width) - 1px)}.mat-mdc-table.compact{--mat-row-height: 33px}.mat-mdc-table.compact .mat-column-avatar{--avatar-width: calc(var(--mat-row-height, 33px) - 4px);--avatar-height: calc(var(--mat-row-height, 33px) - 4px)}.mat-mdc-table .mat-column-id{min-width:50px;max-width:120px;padding-right:var(--mat-cell-horizontal-padding, 4px)}.mat-mdc-table .mat-column-lastName,.mat-mdc-table .mat-column-firstName{min-width:80px}.mat-mdc-table .mat-column-email,.mat-mdc-table .mat-column-pubkey,.mat-mdc-table .mat-column-department{min-width:180px}.mat-mdc-table .mat-column-profile{min-width:110px}.mat-mdc-table .mat-column-status,.mat-mdc-table .mat-column-creationDate,.mat-mdc-table .mat-column-updateDate{min-width:130px}\n"], dependencies: [{ kind: "directive", type: i4$3.SvgJdenticonDirective, selector: "svg[data-jdenticon-hash],svg[data-jdenticon-value]", inputs: ["data-jdenticon-hash", "data-jdenticon-value", "width", "height"] }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "directive", type: i3$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i2$1.IonAvatar, selector: "ion-avatar" }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonFab, selector: "ion-fab", inputs: ["activated", "edge", "horizontal", "vertical"] }, { kind: "component", type: i2$1.IonFabButton, selector: "ion-fab-button", inputs: ["activated", "closeIcon", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "show", "size", "target", "translucent", "type"] }, { kind: "component", type: i2$1.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonImg, selector: "ion-img", inputs: ["alt", "src"] }, { kind: "component", type: i2$1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "component", type: i2$1.IonSkeletonText, selector: "ion-skeleton-text", inputs: ["animated"] }, { kind: "component", type: i2$1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$3.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i7.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i7.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i7.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i7.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i7.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i7.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i7.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i7.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i7.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i7.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "directive", type: i8$2.MatSort, selector: "[matSort]", inputs: ["matSortActive", "matSortStart", "matSortDirection", "matSortDisableClear", "matSortDisabled"], outputs: ["matSortChange"], exportAs: ["matSort"] }, { kind: "component", type: i8$2.MatSortHeader, selector: "[mat-sort-header]", inputs: ["mat-sort-header", "arrowPosition", "start", "disabled", "sortActionDescription", "disableClear"], exportAs: ["matSortHeader"] }, { kind: "component", type: i9$2.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i3.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i5.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: i13$4.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "directive", type: i13$4.MatExpansionPanelActionRow, selector: "mat-action-row" }, { kind: "component", type: i6$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i11$1.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i12.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i12.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i12.MatMenuContent, selector: "ng-template[matMenuContent]" }, { kind: "directive", type: i12.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i6$3.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "directive", type: i6$3.MatSelectTrigger, selector: "mat-select-trigger" }, { kind: "component", type: i2.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i9.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i13$1.MatBadge, selector: "[matBadge]", inputs: ["matBadgeColor", "matBadgeOverlap", "matBadgeDisabled", "matBadgePosition", "matBadge", "matBadgeDescription", "matBadgeSize", "matBadgeHidden"] }, { kind: "directive", type: i1$6.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "directive", type: i1$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: MatChipsField, selector: "mat-chips-field", inputs: ["equals", "logPrefix", "formControl", "formControlName", "floatLabel", "placeholder", "suggestFn", "required", "mobile", "readonly", "clearable", "debounceTime", "displaySeparator", "displayWith", "displayAttributes", "displayColumnSizes", "displayColumnNames", "highlightAccent", "showAllOnFocus", "showPanelOnFocus", "autofocus", "config", "i18nPrefix", "noResultMessage", "panelClass", "panelWidth", "disableRipple", "matAutocompletePosition", "itemSize", "fetchMoreThreshold", "suggestLengthThreshold", "showLoadingSpinner", "chipColor", "debug", "applyImplicitValue", "dropButtonTitle", "clearButtonTitle", "trimSearchText", "hideRequiredMarker", "colSizes", "separatorKeysCodes", "showChips", "appearance", "subscriptSizing", "class", "filter", "tabindex", "items"], outputs: ["click", "blur", "focus", "dropButtonClick", "keydown.escape", "keyup.enter"] }, { kind: "directive", type: AutofocusDirective, selector: "[autofocus], input[appAutofocus]", inputs: ["appAutofocus", "autofocusDelay"] }, { kind: "directive", type: AutoTitleDirective, selector: "ion-label[appAutoTitle], mat-label[appAutoTitle]", inputs: ["appAutoTitle"] }, { kind: "component", type: ToolbarComponent, selector: "app-toolbar", inputs: ["progressBarMode", "title", "color", "class", "id", "backHref", "defaultBackHref", "hasValidate", "hasClose", "hasSearch", "canGoBack", "canShowMenu"], outputs: ["onValidate", "onClose", "onValidateAndClose", "onBackClick", "onSearchInput", "onSearch"] }, { kind: "component", type: AppFormField, selector: "app-form-field", inputs: ["definition", "readonly", "disabled", "formControl", "formControlName", "placeholder", "compact", "required", "hideRequiredMarker", "floatLabel", "label", "appearance", "subscriptSizing", "tabindex", "autofocus", "clearable", "chipColor", "class", "debug", "panelClass", "panelWidth"], outputs: ["keyup.enter", "definitionChanged"] }, { kind: "component", type: FormButtonsBarComponent, selector: "app-form-buttons-bar", inputs: ["disabled", "disabledCancel", "disabledEscape", "classList", "saveButtonColor", "backText", "cancelText", "nextText", "showBack", "showCancel", "showNext", "showSave"], outputs: ["onCancel", "onSave", "onNext", "onBack", "onSaveAndClose", "onSaveAndNext"] }, { kind: "component", type: ActionsColumnComponent, selector: "app-actions-column", inputs: ["matColumnDef", "style", "showPendingSpinner", "stickyEnd", "canCancel", "canConfirm", "canDelete", "canBackward", "canForward", "canConfirmAndAdd", "dirtyIcon", "optionsTitle", "class", "cellTemplateStart", "cellTemplate"], outputs: ["optionsClick", "cancelOrDeleteClick", "confirmEditCreateClick", "confirmAndAddClick", "backward", "forward"] }, { kind: "component", type: DebugComponent, selector: "app-debug", inputs: ["titlePrefix", "title", "enable", "expanded"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }, { kind: "pipe", type: i3$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.UpperCasePipe, name: "uppercase" }, { kind: "pipe", type: DateFormatPipe, name: "dateFormat" }, { kind: "pipe", type: NumberFormatPipe, name: "numberFormat" }, { kind: "pipe", type: NotEmptyArrayPipe, name: "isNotEmptyArray" }, { kind: "pipe", type: MapGetPipe, name: "mapGet" }, { kind: "pipe", type: FormGetControlPipe, name: "formGetControl" }, { kind: "pipe", type: FormGetValuePipe, name: "formGetValue" }, { kind: "pipe", type: IsSelectedPipe, name: "isSelected" }, { kind: "pipe", type: IsEmptySelectionPipe, name: "isEmptySelection" }, { kind: "pipe", type: DisplayWithPipe, name: "displayWith" }, { kind: "pipe", type: IsAllSelectedPipe, name: "isAllSelected" }, { kind: "pipe", type: IsNotAllSelectedPipe, name: "isNotAllSelected" }], animations: [slideUpDownAnimation], changeDetection: i0.ChangeDetectionStrategy.OnPush });
50072
50480
  }
50073
50481
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: UsersPage, decorators: [{
50074
50482
  type: Component,
@@ -52824,7 +53232,7 @@ class MarkdownTestPage {
52824
53232
  debug;
52825
53233
  // URL from a starting markdown
52826
53234
  //protected markdownUrl = 'http://localhost:3000/user-manual/index.md';
52827
- markdownUrl = 'https://gitlab.ifremer.fr/sih-public/sumaris/sumaris-doc/-/raw/master/projects/pifil/man/quick_start_guide_fr.md';
53235
+ markdownUrl = 'https://gitlab.ifremer.fr/sih-public/sumaris/sumaris-doc/-/raw/master/projects/pifil/man/manuel_utilisateur-pifil.md';
52828
53236
  //protected markdownUrl = 'https://gitlab.ifremer.fr/sih-public/sumaris/sumaris-doc/-/raw/master/README.md';
52829
53237
  //protected markdownUrl = 'https://gitlab.ifremer.fr/sih-public/sumaris/sumaris-doc/-/raw/master/model/index.md';
52830
53238
  //protected markdownUrl = 'https://gitlab.ifremer.fr/sih-public/sumaris/sumaris-doc/-/raw/master/model/administration/index.md';
@@ -55180,5 +55588,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
55180
55588
  * Generated bundle index. Do not edit.
55181
55589
  */
55182
55590
 
55183
- export { APP_ABOUT_DEVELOPERS, APP_ABOUT_PARTNERS, APP_ACCOUNT_SERVICE, APP_ACCOUNT_SERVICE_OPTIONS, APP_CELL_SELECTION_SERVICE_CONFIG_TOKEN, APP_CELL_SELECTION_SERVICE_TOKEN, APP_CONFIG_OPTIONS, APP_DEBUG_DATA_SERVICE, APP_FEED_SERVICE, APP_FORM_ERROR_I18N_KEYS, APP_GRAPHQL_FRAGMENTS, APP_GRAPHQL_TYPE_POLICIES, APP_HOME_BUTTONS, APP_HOME_CONFIG, APP_HOME_TOOLBAR_BUTTONS, APP_HOTKEYS_CONFIG, APP_JOB_PROGRESSION_SERVICE, APP_LOCALES, APP_LOCAL_SETTINGS, APP_LOCAL_SETTINGS_OPTIONS, APP_LOCAL_STORAGE_TYPE_POLICIES, APP_LOGGING_SERVICE, APP_MENU_ITEMS, APP_MENU_OPTIONS, APP_NAMED_FILTER_SERVICE, APP_PERSON_SERVICE, APP_PERSON_SERVICE_OPTIONS, APP_PROGRESS_BAR_SERVICE, APP_SETTINGS_MENU_ITEMS, APP_SHOW_TOOLTIP, APP_STORAGE, APP_STORAGE_EXPLORER_PROTECTED_KEYS, APP_TESTING_PAGES, APP_USER_EVENT_LIST_INFINITE_SCROLL_THRESHOLD, APP_USER_EVENT_SERVICE, APP_USER_SETTINGS_OPTIONS, APP_USER_TOKEN_SCOPES, AboutModal, AbstractNamedFilterService, AbstractPersonService, AbstractSelectionModelPipe, AbstractTableSelectionPipe, AbstractUserEventService, Account, AccountPage, AccountService, AccountToStringPipe, AccountUtils, ActionsColumnComponent, AdminModule, AdminRoutingModule, AdminUsersModule, Alerts, AndroidOsEnvironment, AppAboutModalModule, AppAccountModule, AppAsyncTable, AppAuthForm, AppAuthModal, AppAuthModule, AppChangePasswordModule, AppChangePasswordPage, AppEditor, AppEditorOptions, AppEntityEditor, AppEntityEditorModal, AppEntityEditorModalOptions, AppEntityFormModule, AppForm, AppFormArray, AppFormButtonsBarModule, AppFormContainer, AppFormField, AppFormModule, AppFormProvider, AppFormUtils, AppGestureConfig, AppGraphQLModule, AppHomePageModule, AppIconComponent, AppIconModule, AppIconSelectorField, AppIconSelectorModal, AppIconSelectorModule, AppImageGalleryComponent, AppImageGallerySlideshowComponent, AppInMemoryTable, AppInstallUpgradeCard, AppInstallUpgradeCardModule, AppListForm, AppListFormModule, AppLoadingSpinner, AppMarkdownContent, AppMarkdownModal, AppMenuModule, AppNullForm, AppPropertiesForm, AppPropertiesFormModule, AppPropertiesTable, AppPropertiesUtils, AppPropertyUtils, AppRegisterModule, AppResetPasswordModal, AppRowField, AppSelectPeerModule, AppSelectUsersModal, AppSettingsPageModule, AppTabEditor, AppTabEditorOptions, AppTable, AppTableModule, AppTableUtils, AppTextFormModule, AppTextPopoverModule, AppUpdateOfflineModeCard, AppUpdateOfflineModeCardModule, AppValidatorService, AppendQueryParamsPipePipe, ArrayDistinctPipe, ArrayFilterPipe, ArrayFindByPropertyPipe, ArrayFirstPipe, ArrayFormTestPage, ArrayIncludesPipe, ArrayJoinPipe, ArrayLastPipe, ArrayLengthPipe, ArrayMapPipe, ArrayPluckPipe, ArraySlicePipe, ArraySortPipe, AsAnyPipe, AsArrayPipe, AsBooleanPipe, AsFloatLabelTypePipe, AsObservablePipe, AudioProvider, AudioTestingModule, AudioTestingPage, AuthGuardService, AutoResizeDirective, AutoTitleDirective, AutoTooltipDirective, AutocompleteTestPage, AutofocusDirective, BadgeDirective, BadgeNumberPipe, Base58, BaseEntityService, BaseGraphqlService, BaseGraphqlServiceOptions, BaseReferential, Beans, BooleanFormatPipe, BooleanTestPage, CORE_CONFIG_OPTIONS, CORE_TESTING_PAGES, CapitalizePipe, CellIdentifierDirective, CellSelectionDirective, CellSelectionService, CellValueChangeListener, ChangeCaseToUnderscorePipe, ChangePasswordForm, ChipsTestPage, Color, ColorScale, ComponentDirtyGuard, ConfigFragments, ConfigService, Configuration, CoreModule, CorePipesModule, CoreTestingModule, CryptoService, CsvUtils, DATE_ISO_PATTERN, DATE_ISO_PATTERNS, DATE_MATCH_REGEXP, DATE_PATTERN, DATE_UNIX_MS_TIMESTAMP, DATE_UNIX_TIMESTAMP, DEFAULT_JOIN_ARRAY_VALUES_SEPARATOR, DEFAULT_JOIN_PROPERTIES_SEPARATOR, DEFAULT_MENU_SHOW_WHEN, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLACEHOLDER_CHAR, DEFAULT_REQUIRED_COLUMNS, DateDiffDurationPipe, DateFormatPipe, DateFormatService, DateFromNowPipe, DateFromPipe, DateShortTestPage, DateTestPage, DateTimeTestPage, DateUtils, DebugComponent, Department, DepartmentToStringPipe, DisplayWithPipe, DragAndDropDirective, DurationPipe, DurationTestPage, ED25519_SEED_LENGTH, EMPTY_PLACEHOLDER_CHAR, EMPTY_PLACEHOLDER_CHAR_REGEXP_GLOBAL, ENTITIES_STORAGE_KEY_PREFIX, ENVIRONMENT, EmptyArrayPipe, EntitiesAsyncTableDataSource, EntitiesStorage, EntitiesTableDataSource, Entity, EntityClass, EntityClasses, EntityFilter, EntityFilterUtils, EntityMetadataComponent, EntityStore, EntityUtils, Environment, EnvironmentHttpLoader, EnvironmentLoader, ErrorCodes, EvenPipe, FeedDirective, FeedModule, FeedPage, FeedService, FeedsComponent, FileResponse, FileService, FileSizePipe, FilesUtils, FirstFalsePipe, FirstPipe, FirstTruePipe, FormArrayAtControlPipe, FormArrayAtGroupPipe, FormArrayHelper, FormArrayTestModule, FormButtonsBarComponent, FormButtonsBarToken, FormErrorPipe, FormErrorTranslatePipe, FormErrorTranslator, FormFieldDefinitionUtils, FormFieldValuesHolder, FormGetArrayPipe, FormGetControlPipe, FormGetGroupPipe, FormGetNamePipe, FormGetPipe, FormGetValuePipe, GalleryTestPage, GeolocationUtils, GraphqlService, HAMMER_PRESS_TIME, HAMMER_TAP_TIME, HighlightPipe, HomePage, Hotkeys, HotkeysDialogComponent, IMAGE_DEFAULTS, IPosition, IconSelectorService, IconSelectorTestPage, IconSelectorTestingModule, ImageAttachment, ImageAttachmentFilter, ImageAttachmentService, ImageGalleryModule, ImageGalleryTestingModule, ImageModule, ImageService, ImagesUtils, InMemoryEntitiesService, IsAllSelectedPipe, IsEmptySelectionPipe, IsLoginAccountPipe, IsMultipleSelectionPipe, IsNilOrBlankPipe, IsNilOrNaNPipe, IsNilPipe, IsNotAllSelectedPipe, IsNotEmptySelectionPipe, IsNotNilOrBlankPipe, IsNotNilOrNaNPipe, IsNotNilPipe, IsOnDeskPipe, IsOnFieldPipe, IsSelectedPipe, IsSingleSelectionPipe, IsValidDatePipe, JobModule, JobProgression, JobProgressionComponent, JobProgressionIcon, JobProgressionList, JobProgressionService, JobProgressionTestService, JobProgressionTestingPage, JobTestingModule, JobUtils, JsonFeedUtils, JsonUtils, KEYBOARD_HIDE_DELAY_MS, LAT_LONG_PATTERNS, LAT_LONG_PATTERN_MAX_DECIMALS, LAT_LONG_VALUE_MAX_DECIMALS, LatLongFormatPipe, LatLongTestPage, LatitudeFormatPipe, LocalSettingsService, LogLevel, LogUtils, Logger, LoggingService, LoggingServiceModule, LongitudeFormatPipe, MASKS, MASK_RANGES, MAT_FORM_FIELD_DEFAULT_APPEARANCE, MAT_FORM_FIELD_DEFAULT_SUBSCRIPT_SIZING, MINIFY_ENTITY_FOR_LOCAL_STORAGE, MINIFY_ENTITY_FOR_POD, MOMENT_NO_TIME_PROPERTY, MapGetPipe, MapKeysPipe, MapPipe, MapToPipe, MapValuesPipe, MarkdownDirective, MarkdownService, MarkdownTestPage, MarkdownTestingModule, MarkdownUtils, MaskitoPlaceholderPipe, MaskitoTestPage, MatAutocompleteConfigHolder, MatAutocompleteField, MatAutocompleteFieldUtils, MatBadgeTestPage, MatBooleanField, MatChipsField, MatColorPipe, MatCommonTestPage, MatDate, MatDateShort, MatDateTime, MatDuration, MatLatLongField, MatLatLongFieldInput, MatPaginatorI18n, MatStepperI18n, MatSwipeField, MaterialAutocompleteFooterDirective, MaterialAutocompleteHeaderDirective, MaterialTestingModule, MathAbsPipe, MenuComponent, MenuItem, MenuItems, MenuOptions, MenuService, MenuTestingModule, MenuTestingPage, Message, MessageFilter, MessageForm, MessageModal, MessageModule, MessageService, MessageTypeList, MessageTypes, MimeTypes, ModalToolbarComponent, NETWORK_DEFAULT_CONNECTION_TIMEOUT, NamedFilter, NamedFilterFilter, NamedFilterSelector, NamedFilterSelectorTestingModule, NamedFilterSelectorTestingPage, NavActionsColumnComponent, NestedTableTestPage, NetworkService, NetworkUtils, NewTokenForm, NewTokenModal, NgInitDirective, NgVarDirective, NoHtmlPipe, NotEmptyArrayPipe, NumberFormatPipe, ObservableTestPage, OddPipe, OtherMenuTestingPage, PEER_URL_REGEXP, PLUS_PLACEHOLDER_CHAR_REGEXP_GLOBAL, PRINT_ID_QUERY_PARAM, PRINT_LOADING_STORAGE_KEY_PREFIX, PRIORITIZED_AUTHORITIES, PUBKEY_REGEXP, Peer, Person, PersonFilter, PersonFilterAdditionalFields, PersonFragments, PersonService, PersonToStringPipe, PersonUtils, PersonValidatorService, PlatformService, PrintService, ProgressBarService, ProgressInterceptor, PropertiesFormTestPage, PropertiesFormTestingModule, PropertyEntity, PropertyEntityFilter, PropertyEntityValidator, PropertyFormatPipe, PropertyGetPipe, RESERVED_END_COLUMNS, RESERVED_START_COLUMNS, Referential, ReferentialFilter, ReferentialRef, ReferentialToStringPipe, ReferentialUtils, ReferentialValidatorService, ReferentialsToStringPipe, RegExpUtils, RegisterConfirmPage, RegisterForm, RegisterModal, ResizableComponent, ResizableDirective, ResizableModule, RoundPipe, RxStateComputed, RxStateModule, RxStateOutput, RxStateProperty, RxStateRegister, RxStateSelect, SCRYPT_PARAMS, SETTINGS_COMPACT_ROWS, SETTINGS_DISPLAY_COLUMNS, SETTINGS_FILTER, SETTINGS_PAGE_SIZE, SETTINGS_SORTED_COLUMN, SETTINGS_STORAGE_KEY, SETTINGS_TRANSIENT_PROPERTIES, SHARED_MATERIAL_TESTING_PAGES, SHARED_STORAGE_TESTING_PAGES, SHARED_TESTING_PAGES, SOCIAL_CONFIG_OPTIONS, SOCIAL_TESTING_PAGES, SPACE_PLACEHOLDER_CHAR, SPACE_PLACEHOLDER_CHAR_REGEXP_GLOBAL, STARTUP_DATA_STORAGE_KEY, SafeHtmlPipe, SafeStylePipe, SelectPeerModal, SelectionLengthPipe, ServerErrorCodes, SettingsPage, SharedAsyncValidators, SharedBadgeModule, SharedDebugModule, SharedDirectivesModule, SharedFormArrayValidators, SharedFormGroupValidators, SharedHotkeysModule, SharedMarkdownModule, SharedMatAutocompleteModule, SharedMatBooleanModule, SharedMatChipsModule, SharedMatDateTimeModule, SharedMatDurationModule, SharedMatLatLongModule, SharedMatSwipeModule, SharedMaterialModule, SharedModule, SharedNamedFilterModule, SharedPipesModule, SharedRoutingModule, SharedTestingModule, SharedTestsPage, SharedToolbarModule, SharedValidators, SocialErrorCodes, SocialModule, SocialModuleOptionsToken, SocialTestingModule, Software, SplitArrayInChunksPipe, StartableService, StartupService, StatusById, StatusIds, StatusList, StorageDrivers, StorageExplorerComponent, StorageExplorerModule, StorageExplorerTestingModule, StorageExplorerTestingRoutingModule, StorageService, StrIncludesPipe, StrLengthPipe, StrReplacePipe, SubMenuTabDirective, SwipeTestPage, TABLE_SETTINGS_ENUM, TOOLBAR_HEADER_ID, Table2TestPage, TableSelectColumnsComponent, TableTestPage, TableTestingModule, TableValidatorService, TextForm, TextFormTestingModule, TextFormTestingPage, TextPopover, TextPopoverTestingModule, TextPopoverTestingPage, ThrottledClickDirective, ToStringPipe, ToastTestingModule, ToastTestingPage, Toasts, TokenScope, ToolbarComponent, ToolbarToken, TranslatablePipe, TranslateContextPipe, TranslateContextService, TreeItemEntityUtils, TruncHtmlPipe, TruncTextPipe, TruncateHtmlPipe, UploadFile, UploadFileComponent, UploadFilePopover, UploadFileTestingModule, UploadFileTestingPage, UriUtils, UrlUtils, UserEventModule, UserEventNotificationIcon, UserEventNotificationList, UserEventNotificationModal, UserEventTestService, UserEventTestingModule, UserEventTestingPage, UserSettings, UserToken, UserTokenTable, UsersPage, UsersUtils, ValueFormatPipe, VersionUtils, accountToString, adaptValueToControl, addValueInArray, arrayDistinct, arrayResize, arraySize, asInputElement, assignSkipUndefined, base64ArrayBuffer, booleanToString, canHaveFocus, capitalizeFirstLetter, chainPromises, changeCaseToUnderscore, clearValueInArray, collectByProperty, collectByPropertyPath, compareValues, compareValuesDesc, compareVersionNumbers, composeComparators, computeDecimalDegrees, computeDecimalPart, copyEntity2Form, createAppStartupInitializer, createPromiseEvent, createPromiseEventEmitter, decorateWithTakeUntil, deepMergeSkipUndefined, departmentToString, departmentsToString, disableAndClearControl, disableAndClearControls, disableControl, disableControls, emitPromiseEvent, enableControl, enableControls, enableRxStateProdMode, entityToString, equals, equalsOrNil, escapeRegExp, expansionAnimation, fadeInAnimation, fadeInOutAnimation, fadeInSlowAnimation, filterFalse, filterFormErrors, filterFormErrorsByPath, filterFormErrorsByPrefix, filterNotNil, filterNumberInput, filterTrue, findParentWithClass, firstArrayValue, firstFalse, firstFalsePromise, firstNotNil, firstNotNilPromise, firstPromise, firstTrue, firstTruePromise, focusInput, focusNextInput, focusPreviousInput, formatLatLong, formatLatitude, formatLongitude, fromDateISOString, fromScrollEndEvent, fromUnixMsTimestamp, fromUnixTimestamp, getCaretPosition, getColorContrast, getColorLuminance, getColorShade, getColorTint, getControlFromPath, getFocusableInputElements, getFormErrors, getFormValueFromEntity, getInputRangeFromCaretIndex, getInputSelectionRangesFromMask, getProperty, getPropertyByPath, getPropertyByPathAsString, getRandomImage, getRandomImageWithCredit, getUserAgent, hexToRgb, hexToRgbArray, initArrayControlsFromValues, interpolateString, intersectArrays, isAndroid, isBlankString, isCapacitor, isChrome, isControlHasInput, isEdge, isEmptyArray, isEntityService, isFirefox, isFocusableElement, isIOS, isInputElement, isInstanceOf, isInt, isIpad, isLightColor, isMacOS, isMobile, isNil, isNilOrBlank, isNilOrNaN, isNotEmptyArray, isNotNil, isNotNilBoolean, isNotNilObject, isNotNilOrBlank, isNotNilOrNaN, isNotNilString, isNumber, isNumberRange, isOnFieldMode, isPrint, isProgressEvent, isPromise, isResponseEvent, isSafari, isSameVersion, isStartableService, isTouchUi, isVersionCompatible, isWindows, joinProperties, joinPropertiesPath, lastArrayValue, logFormErrors, markAllAsTouched, markAsUntouched, markControlAsTouched, markFormGroupAsTouched, maskitoAutoSelectByMaskPattern, maskitoPrefixPlugin, matchMedia, matchUpperCase, mergeLoadResult, mergeObjectsWithoutUndefined, mixHex, moveInputCaretToSeparator, newArray, noHtml, noTrailingSlash, notNilOrDefault, nullIfNilOrBlank, nullIfUndefined, numberOrNilAttribute, numberToString, parseLatitudeOrLongitude, propertiesPathComparator, propertyComparator, propertyPathComparator, provideAccountService, providePersonService, referentialToString, referentialsToString, remove, removeAll, removeDiacritics, removeDuplicatesFromArray, removeEnd, removeValueInArray, replaceAll, resetCalculatedValue, resizeArray, rgbArrayToHex, rgbToHex, round, scrollFactory, selectInputContent, selectInputContentFromEvent, selectInputRange, setCalculatedValue, setControlEnabled, setControlRequired, setControlsEnabled, setFormErrors, setPropertyByPath, setTabIndex, sleep, slideDownAnimation, slideInAnimation, slideInOutAnimation, slideUpDownAnimation, sort, splitArrayInChunks, splitById, splitByProperty, splitDegreesToDDArray, splitDegreesToDDMMArray, splitDegreesToDDMMSSArray, startsWithUpperCase, suggestFromArray, suggestFromStringArray, tabindexComparator, testUserAgent, toBoolean, toDateISOString, toDuration, toFloat, toInt, toLoadData, toLoadResult, toNotNil, toNumber, trimEmptyToNull, truncateHtml, uncapitalizeFirstLetter, undefinedIfNull, underscoreToChangeCase, updateValueAndValidity, waitFor, waitForFalse, waitForTrue, waitIdle, waitWhilePending };
55591
+ export { APP_ABOUT_DEVELOPERS, APP_ABOUT_PARTNERS, APP_ACCOUNT_SERVICE, APP_ACCOUNT_SERVICE_OPTIONS, APP_CELL_SELECTION_SERVICE_CONFIG_TOKEN, APP_CELL_SELECTION_SERVICE_TOKEN, APP_CONFIG_OPTIONS, APP_DEBUG_DATA_SERVICE, APP_FEED_SERVICE, APP_FORM_ERROR_I18N_KEYS, APP_GRAPHQL_FRAGMENTS, APP_GRAPHQL_TYPE_POLICIES, APP_HOME_BUTTONS, APP_HOME_CONFIG, APP_HOME_TOOLBAR_BUTTONS, APP_HOTKEYS_CONFIG, APP_JOB_PROGRESSION_SERVICE, APP_LOCALES, APP_LOCAL_SETTINGS, APP_LOCAL_SETTINGS_OPTIONS, APP_LOCAL_STORAGE_TYPE_POLICIES, APP_LOGGING_SERVICE, APP_MENU_ITEMS, APP_MENU_OPTIONS, APP_NAMED_FILTER_SERVICE, APP_PERSON_SERVICE, APP_PERSON_SERVICE_OPTIONS, APP_PROGRESS_BAR_SERVICE, APP_SETTINGS_MENU_ITEMS, APP_SHOW_TOOLTIP, APP_STORAGE, APP_STORAGE_EXPLORER_PROTECTED_KEYS, APP_TESTING_PAGES, APP_USER_EVENT_LIST_INFINITE_SCROLL_THRESHOLD, APP_USER_EVENT_SERVICE, APP_USER_SETTINGS_OPTIONS, APP_USER_TOKEN_SCOPES, AboutModal, AbstractNamedFilterService, AbstractPersonService, AbstractSelectionModelPipe, AbstractTableSelectionPipe, AbstractUserEventService, Account, AccountPage, AccountService, AccountToStringPipe, AccountUtils, ActionsColumnComponent, AdminModule, AdminRoutingModule, AdminUsersModule, Alerts, AndroidOsEnvironment, AppAboutModalModule, AppAccountModule, AppAsyncTable, AppAuthForm, AppAuthModal, AppAuthModule, AppChangePasswordModule, AppChangePasswordPage, AppEditor, AppEditorOptions, AppEntityEditor, AppEntityEditorModal, AppEntityEditorModalOptions, AppEntityFormModule, AppForm, AppFormArray, AppFormButtonsBarModule, AppFormContainer, AppFormField, AppFormModule, AppFormProvider, AppFormUtils, AppGestureConfig, AppGraphQLModule, AppHomePageModule, AppIconComponent, AppIconModule, AppIconSelectorField, AppIconSelectorModal, AppIconSelectorModule, AppImageGalleryComponent, AppImageGallerySlideshowComponent, AppInMemoryTable, AppInstallUpgradeCard, AppInstallUpgradeCardModule, AppListForm, AppListFormModule, AppLoadingSpinner, AppMarkdownContent, AppMarkdownModal, AppMenuModule, AppNullForm, AppPropertiesForm, AppPropertiesFormModule, AppPropertiesTable, AppPropertiesUtils, AppPropertyUtils, AppRegisterModule, AppResetPasswordModal, AppRowField, AppSelectPeerModule, AppSelectUsersModal, AppSettingsPageModule, AppTabEditor, AppTabEditorOptions, AppTable, AppTableModule, AppTableUtils, AppTextFormModule, AppTextPopoverModule, AppUpdateOfflineModeCard, AppUpdateOfflineModeCardModule, AppValidatorService, AppendQueryParamsPipePipe, ArrayDistinctPipe, ArrayFilterPipe, ArrayFindByPropertyPipe, ArrayFirstPipe, ArrayFormTestPage, ArrayIncludesPipe, ArrayJoinPipe, ArrayLastPipe, ArrayLengthPipe, ArrayMapPipe, ArrayPluckPipe, ArraySlicePipe, ArraySortPipe, AsAnyPipe, AsArrayPipe, AsBooleanPipe, AsFloatLabelTypePipe, AsObservablePipe, AudioProvider, AudioTestingModule, AudioTestingPage, AuthGuardService, AutoResizeDirective, AutoTitleDirective, AutoTooltipDirective, AutocompleteTestPage, AutofocusDirective, BadgeDirective, BadgeNumberPipe, Base58, BaseEntityService, BaseGraphqlService, BaseGraphqlServiceOptions, BaseReferential, Beans, BooleanFormatPipe, BooleanTestPage, CORE_CONFIG_OPTIONS, CORE_TESTING_PAGES, CapitalizePipe, CellIdentifierDirective, CellSelectionDirective, CellSelectionService, CellValueChangeListener, ChangeCaseToUnderscorePipe, ChangePasswordForm, ChipsTestPage, Color, ColorScale, ComponentDirtyGuard, ConfigFragments, ConfigService, Configuration, CoreModule, CorePipesModule, CoreTestingModule, CryptoService, CsvUtils, DATE_ISO_PATTERN, DATE_ISO_PATTERNS, DATE_MATCH_REGEXP, DATE_PATTERN, DATE_UNIX_MS_TIMESTAMP, DATE_UNIX_TIMESTAMP, DEFAULT_JOIN_ARRAY_VALUES_SEPARATOR, DEFAULT_JOIN_PROPERTIES_SEPARATOR, DEFAULT_MENU_SHOW_WHEN, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLACEHOLDER_CHAR, DEFAULT_REQUIRED_COLUMNS, DateDiffDurationPipe, DateFormatPipe, DateFormatService, DateFromNowPipe, DateFromPipe, DateShortTestPage, DateTestPage, DateTimeTestPage, DateUtils, DebugComponent, Department, DepartmentToStringPipe, DisplayWithPipe, DragAndDropDirective, DurationPipe, DurationTestPage, ED25519_SEED_LENGTH, EMPTY_PLACEHOLDER_CHAR, EMPTY_PLACEHOLDER_CHAR_REGEXP_GLOBAL, ENTITIES_STORAGE_KEY_PREFIX, ENVIRONMENT, EmptyArrayPipe, EntitiesAsyncTableDataSource, EntitiesStorage, EntitiesTableDataSource, Entity, EntityClass, EntityClasses, EntityFilter, EntityFilterUtils, EntityMetadataComponent, EntityStore, EntityUtils, Environment, EnvironmentHttpLoader, EnvironmentLoader, ErrorCodes, EvenPipe, FeedDirective, FeedModule, FeedPage, FeedService, FeedsComponent, FileResponse, FileService, FileSizePipe, FilesUtils, FirstFalsePipe, FirstPipe, FirstTruePipe, FormArrayAtControlPipe, FormArrayAtGroupPipe, FormArrayHelper, FormArrayTestModule, FormButtonsBarComponent, FormButtonsBarToken, FormErrorPipe, FormErrorTranslatePipe, FormErrorTranslator, FormFieldDefinitionUtils, FormFieldValuesHolder, FormGetArrayPipe, FormGetControlPipe, FormGetGroupPipe, FormGetNamePipe, FormGetPipe, FormGetValuePipe, GalleryTestPage, GeolocationUtils, GitlabUtils, GraphqlService, HAMMER_PRESS_TIME, HAMMER_TAP_TIME, HighlightPipe, HomePage, Hotkeys, HotkeysDialogComponent, IMAGE_DEFAULTS, IPosition, IconSelectorService, IconSelectorTestPage, IconSelectorTestingModule, ImageAttachment, ImageAttachmentFilter, ImageAttachmentService, ImageGalleryModule, ImageGalleryTestingModule, ImageModule, ImageService, ImagesUtils, InMemoryEntitiesService, IsAllSelectedPipe, IsEmptySelectionPipe, IsLoginAccountPipe, IsMultipleSelectionPipe, IsNilOrBlankPipe, IsNilOrNaNPipe, IsNilPipe, IsNotAllSelectedPipe, IsNotEmptySelectionPipe, IsNotNilOrBlankPipe, IsNotNilOrNaNPipe, IsNotNilPipe, IsOnDeskPipe, IsOnFieldPipe, IsSelectedPipe, IsSingleSelectionPipe, IsValidDatePipe, JobModule, JobProgression, JobProgressionComponent, JobProgressionIcon, JobProgressionList, JobProgressionService, JobProgressionTestService, JobProgressionTestingPage, JobTestingModule, JobUtils, JsonFeedUtils, JsonUtils, KEYBOARD_HIDE_DELAY_MS, LAT_LONG_PATTERNS, LAT_LONG_PATTERN_MAX_DECIMALS, LAT_LONG_VALUE_MAX_DECIMALS, LatLongFormatPipe, LatLongTestPage, LatitudeFormatPipe, LocalSettingsService, LogLevel, LogUtils, Logger, LoggingService, LoggingServiceModule, LongitudeFormatPipe, MASKS, MASK_RANGES, MAT_FORM_FIELD_DEFAULT_APPEARANCE, MAT_FORM_FIELD_DEFAULT_SUBSCRIPT_SIZING, MINIFY_ENTITY_FOR_LOCAL_STORAGE, MINIFY_ENTITY_FOR_POD, MOMENT_NO_TIME_PROPERTY, MapGetPipe, MapKeysPipe, MapPipe, MapToPipe, MapValuesPipe, MarkdownDirective, MarkdownService, MarkdownTestPage, MarkdownTestingModule, MarkdownUtils, MaskitoPlaceholderPipe, MaskitoTestPage, MatAutocompleteConfigHolder, MatAutocompleteField, MatAutocompleteFieldUtils, MatBadgeTestPage, MatBooleanField, MatChipsField, MatColorPipe, MatCommonTestPage, MatDate, MatDateShort, MatDateTime, MatDuration, MatLatLongField, MatLatLongFieldInput, MatPaginatorI18n, MatStepperI18n, MatSwipeField, MaterialAutocompleteFooterDirective, MaterialAutocompleteHeaderDirective, MaterialTestingModule, MathAbsPipe, MenuComponent, MenuItem, MenuItems, MenuOptions, MenuService, MenuTestingModule, MenuTestingPage, Message, MessageFilter, MessageForm, MessageModal, MessageModule, MessageService, MessageTypeList, MessageTypes, MimeTypes, ModalToolbarComponent, NETWORK_DEFAULT_CONNECTION_TIMEOUT, NamedFilter, NamedFilterFilter, NamedFilterSelector, NamedFilterSelectorTestingModule, NamedFilterSelectorTestingPage, NavActionsColumnComponent, NestedTableTestPage, NetworkService, NetworkUtils, NewTokenForm, NewTokenModal, NgInitDirective, NgVarDirective, NoHtmlPipe, NotEmptyArrayPipe, NumberFormatPipe, ObservableTestPage, OddPipe, OtherMenuTestingPage, PEER_URL_REGEXP, PLUS_PLACEHOLDER_CHAR_REGEXP_GLOBAL, PRINT_ID_QUERY_PARAM, PRINT_LOADING_STORAGE_KEY_PREFIX, PRIORITIZED_AUTHORITIES, PUBKEY_REGEXP, Peer, Person, PersonFilter, PersonFilterAdditionalFields, PersonFragments, PersonService, PersonToStringPipe, PersonUtils, PersonValidatorService, PlatformService, PrintService, ProgressBarService, ProgressInterceptor, PropertiesFormTestPage, PropertiesFormTestingModule, PropertyEntity, PropertyEntityFilter, PropertyEntityValidator, PropertyFormatPipe, PropertyGetPipe, RESERVED_END_COLUMNS, RESERVED_START_COLUMNS, Referential, ReferentialFilter, ReferentialRef, ReferentialToStringPipe, ReferentialUtils, ReferentialValidatorService, ReferentialsToStringPipe, RegExpUtils, RegisterConfirmPage, RegisterForm, RegisterModal, ResizableComponent, ResizableDirective, ResizableModule, RoundPipe, RxStateComputed, RxStateModule, RxStateOutput, RxStateProperty, RxStateRegister, RxStateSelect, SCRYPT_PARAMS, SETTINGS_COMPACT_ROWS, SETTINGS_DISPLAY_COLUMNS, SETTINGS_FILTER, SETTINGS_PAGE_SIZE, SETTINGS_SORTED_COLUMN, SETTINGS_STORAGE_KEY, SETTINGS_TRANSIENT_PROPERTIES, SHARED_MATERIAL_TESTING_PAGES, SHARED_STORAGE_TESTING_PAGES, SHARED_TESTING_PAGES, SOCIAL_CONFIG_OPTIONS, SOCIAL_TESTING_PAGES, SPACE_PLACEHOLDER_CHAR, SPACE_PLACEHOLDER_CHAR_REGEXP_GLOBAL, STARTUP_DATA_STORAGE_KEY, SafeHtmlPipe, SafeStylePipe, SelectPeerModal, SelectionLengthPipe, ServerErrorCodes, SettingsPage, SharedAsyncValidators, SharedBadgeModule, SharedDebugModule, SharedDirectivesModule, SharedFormArrayValidators, SharedFormGroupValidators, SharedHotkeysModule, SharedMarkdownModule, SharedMatAutocompleteModule, SharedMatBooleanModule, SharedMatChipsModule, SharedMatDateTimeModule, SharedMatDurationModule, SharedMatLatLongModule, SharedMatSwipeModule, SharedMaterialModule, SharedModule, SharedNamedFilterModule, SharedPipesModule, SharedRoutingModule, SharedTestingModule, SharedTestsPage, SharedToolbarModule, SharedValidators, SocialErrorCodes, SocialModule, SocialModuleOptionsToken, SocialTestingModule, Software, SplitArrayInChunksPipe, StartableService, StartupService, StatusById, StatusIds, StatusList, StorageDrivers, StorageExplorerComponent, StorageExplorerModule, StorageExplorerTestingModule, StorageExplorerTestingRoutingModule, StorageService, StrIncludesPipe, StrLengthPipe, StrReplacePipe, SubMenuTabDirective, SwipeTestPage, TABLE_SETTINGS_ENUM, TOOLBAR_HEADER_ID, Table2TestPage, TableSelectColumnsComponent, TableTestPage, TableTestingModule, TableValidatorService, TextForm, TextFormTestingModule, TextFormTestingPage, TextPopover, TextPopoverTestingModule, TextPopoverTestingPage, ThrottledClickDirective, ToStringPipe, ToastTestingModule, ToastTestingPage, Toasts, TokenScope, ToolbarComponent, ToolbarToken, TranslatablePipe, TranslateContextPipe, TranslateContextService, TreeItemEntityUtils, TruncHtmlPipe, TruncTextPipe, TruncateHtmlPipe, UploadFile, UploadFileComponent, UploadFilePopover, UploadFileTestingModule, UploadFileTestingPage, UriUtils, UrlUtils, UserEventModule, UserEventNotificationIcon, UserEventNotificationList, UserEventNotificationModal, UserEventTestService, UserEventTestingModule, UserEventTestingPage, UserSettings, UserToken, UserTokenTable, UsersPage, UsersUtils, ValueFormatPipe, VersionUtils, accountToString, adaptValueToControl, addValueInArray, arrayDistinct, arrayResize, arraySize, asInputElement, assignSkipUndefined, base64ArrayBuffer, booleanToString, canHaveFocus, capitalizeFirstLetter, chainPromises, changeCaseToUnderscore, clearValueInArray, collectByProperty, collectByPropertyPath, compareValues, compareValuesDesc, compareVersionNumbers, composeComparators, computeDecimalDegrees, computeDecimalPart, copyEntity2Form, createAppStartupInitializer, createPromiseEvent, createPromiseEventEmitter, decorateWithTakeUntil, deepMergeSkipUndefined, departmentToString, departmentsToString, disableAndClearControl, disableAndClearControls, disableControl, disableControls, emitPromiseEvent, enableControl, enableControls, enableRxStateProdMode, entityToString, equals, equalsOrNil, escapeRegExp, expansionAnimation, fadeInAnimation, fadeInOutAnimation, fadeInSlowAnimation, filterFalse, filterFormErrors, filterFormErrorsByPath, filterFormErrorsByPrefix, filterNotNil, filterNumberInput, filterTrue, findParentWithClass, firstArrayValue, firstFalse, firstFalsePromise, firstNotNil, firstNotNilPromise, firstPromise, firstTrue, firstTruePromise, focusInput, focusNextInput, focusPreviousInput, formatLatLong, formatLatitude, formatLongitude, fromDateISOString, fromScrollEndEvent, fromUnixMsTimestamp, fromUnixTimestamp, getCaretPosition, getColorContrast, getColorLuminance, getColorShade, getColorTint, getControlFromPath, getFocusableInputElements, getFormErrors, getFormValueFromEntity, getInputRangeFromCaretIndex, getInputSelectionRangesFromMask, getProperty, getPropertyByPath, getPropertyByPathAsString, getRandomImage, getRandomImageWithCredit, getUserAgent, hexToRgb, hexToRgbArray, initArrayControlsFromValues, interpolateString, intersectArrays, isAndroid, isBlankString, isCapacitor, isChrome, isControlHasInput, isEdge, isEmptyArray, isEntityService, isFirefox, isFocusableElement, isIOS, isInputElement, isInstanceOf, isInt, isIpad, isLightColor, isMacOS, isMobile, isNil, isNilOrBlank, isNilOrNaN, isNotEmptyArray, isNotNil, isNotNilBoolean, isNotNilObject, isNotNilOrBlank, isNotNilOrNaN, isNotNilString, isNumber, isNumberRange, isOnFieldMode, isPrint, isProgressEvent, isPromise, isResponseEvent, isSafari, isSameVersion, isStartableService, isTouchUi, isVersionCompatible, isWebAnimationsSupported, isWindows, joinProperties, joinPropertiesPath, lastArrayValue, logFormErrors, markAllAsTouched, markAsUntouched, markControlAsTouched, markFormGroupAsTouched, maskitoAutoSelectByMaskPattern, maskitoPrefixPlugin, matchMedia, matchUpperCase, mergeLoadResult, mergeObjectsWithoutUndefined, mixHex, moveInputCaretToSeparator, newArray, noHtml, noTrailingSlash, notNilOrDefault, nullIfNilOrBlank, nullIfUndefined, numberOrNilAttribute, numberToString, parseLatitudeOrLongitude, propertiesPathComparator, propertyComparator, propertyPathComparator, provideAccountService, providePersonService, referentialToString, referentialsToString, remove, removeAll, removeDiacritics, removeDuplicatesFromArray, removeEnd, removeValueInArray, replaceAll, resetCalculatedValue, resizeArray, rgbArrayToHex, rgbToHex, round, scrollFactory, selectInputContent, selectInputContentFromEvent, selectInputRange, setCalculatedValue, setControlEnabled, setControlRequired, setControlsEnabled, setFormErrors, setPropertyByPath, setTabIndex, sleep, slideDownAnimation, slideInAnimation, slideInOutAnimation, slideUpDownAnimation, sort, splitArrayInChunks, splitById, splitByProperty, splitDegreesToDDArray, splitDegreesToDDMMArray, splitDegreesToDDMMSSArray, startsWithUpperCase, suggestFromArray, suggestFromStringArray, tabindexComparator, testUserAgent, toBoolean, toDateISOString, toDuration, toFloat, toInt, toLoadData, toLoadResult, toNotNil, toNumber, trimEmptyToNull, truncateHtml, uncapitalizeFirstLetter, undefinedIfNull, underscoreToChangeCase, updateValueAndValidity, waitFor, waitForFalse, waitForTrue, waitIdle, waitWhilePending };
55184
55592
  //# sourceMappingURL=sumaris-net.ngx-components.mjs.map