@sumaris-net/ngx-components 18.5.0-rc2 → 18.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/esm2022/src/app/core/account/account.module.mjs +7 -3
  2. package/esm2022/src/app/core/account/account.page.mjs +9 -6
  3. package/esm2022/src/app/core/account/password/change-password.page.mjs +2 -2
  4. package/esm2022/src/app/core/form/properties/properties.form.mjs +33 -14
  5. package/esm2022/src/app/core/graphql/graphql.service.mjs +3 -3
  6. package/esm2022/src/app/core/install/install-upgrade-card.component.mjs +26 -8
  7. package/esm2022/src/app/core/services/config/core.config.mjs +6 -1
  8. package/esm2022/src/app/core/services/network.service.mjs +7 -4
  9. package/esm2022/src/app/core/services/validator/local-settings.validator.mjs +5 -10
  10. package/esm2022/src/app/core/settings/settings.page.mjs +15 -4
  11. package/esm2022/src/environments/environment.class.mjs +2 -1
  12. package/esm2022/src/environments/environment.mjs +3 -2
  13. package/fesm2022/sumaris-net.ngx-components.mjs +100 -43
  14. package/fesm2022/sumaris-net.ngx-components.mjs.map +1 -1
  15. package/package.json +1 -1
  16. package/src/app/core/account/account.module.d.ts +2 -1
  17. package/src/app/core/form/properties/properties.form.d.ts +7 -2
  18. package/src/app/core/install/install-upgrade-card.component.d.ts +2 -2
  19. package/src/app/core/services/config/core.config.d.ts +1 -0
  20. package/src/app/core/services/network.service.d.ts +1 -0
  21. package/src/app/core/settings/settings.page.d.ts +3 -1
  22. package/src/assets/i18n/en-US.json +1 -0
  23. package/src/assets/i18n/en.json +1 -0
  24. package/src/assets/i18n/fr.json +1 -0
  25. package/src/assets/manifest.json +1 -1
  26. package/src/environments/environment.class.d.ts +1 -0
@@ -192,6 +192,7 @@ class Environment {
192
192
  defaultAppName;
193
193
  defaultAndroidInstallUrl;
194
194
  defaultIOSInstallUrl;
195
+ defaultDesktopInstallUrl;
195
196
  // Route and style
196
197
  useHash; // Configure Angular router to use '#' in URL (need by web-ext artifact)
197
198
  allowDarkMode;
@@ -11836,7 +11837,8 @@ const environment = Object.freeze({
11836
11837
  ],
11837
11838
  defaultAppName: 'SUMARiS',
11838
11839
  defaultAndroidInstallUrl: 'https://play.google.com/store/apps/details?id=net.sumaris.app',
11839
- defaultIOSInstallUrl: 'https://play.google.com/store/apps/details?id=net.sumaris.app', // TODO change this
11840
+ defaultIOSInstallUrl: 'https://apps.apple.com/us/app/sumaris/id6736747523',
11841
+ defaultDesktopInstallUrl: 'https://gitlab.ifremer.fr/sih-public/sumaris/sumaris-app/-/releases',
11840
11842
  // Storage
11841
11843
  storage: {
11842
11844
  driverOrder: [StorageDrivers.SQLLite, StorageDrivers.IndexedDB, StorageDrivers.WebSQL, StorageDrivers.LocalStorage],
@@ -15783,6 +15785,11 @@ const CORE_CONFIG_OPTIONS = Object.freeze({
15783
15785
  label: 'CONFIGURATION.OPTIONS.IOS_INSTALL_URL',
15784
15786
  type: 'string',
15785
15787
  },
15788
+ DESKTOP_INSTALL_URL: {
15789
+ key: 'sumaris.desktop.install.url',
15790
+ label: 'CONFIGURATION.OPTIONS.DESKTOP_INSTALL_URL',
15791
+ type: 'string',
15792
+ },
15786
15793
  DB_TIMEZONE: {
15787
15794
  key: 'sumaris.persistence.db.timezone',
15788
15795
  label: 'CONFIGURATION.OPTIONS.DB_TIMEZONE',
@@ -18515,6 +18522,9 @@ class NetworkService extends StartableObservableService {
18515
18522
  get offline() {
18516
18523
  return this.connectionType === 'none';
18517
18524
  }
18525
+ get forceOffline() {
18526
+ return this._forceOffline;
18527
+ }
18518
18528
  get connectionType() {
18519
18529
  // If force offline: return 'none'
18520
18530
  return this._forceOffline
@@ -18831,7 +18841,7 @@ class NetworkService extends StartableObservableService {
18831
18841
  // Emit event
18832
18842
  if ((!opts || opts.emitEvent !== false) && this.onResetNetworkCache.observed) {
18833
18843
  this.onResetNetworkCache.emit();
18834
- // Wait observers clean their caches, if need
18844
+ // Wait observers clean their caches, if needed
18835
18845
  return sleep(500);
18836
18846
  }
18837
18847
  })
@@ -18843,7 +18853,7 @@ class NetworkService extends StartableObservableService {
18843
18853
  /* -- protected functions -- */
18844
18854
  async ngOnStart(peer) {
18845
18855
  console.info('[network] Starting network...');
18846
- // Check if can use Network plugin
18856
+ // Check if we can use Network plugin
18847
18857
  console.debug(`[network] Capacitor Network plugin is ${Capacitor.isPluginAvailable('Network') ? 'available' : 'not available'}`);
18848
18858
  // Restoring local settings
18849
18859
  peer = peer || (await this.restoreLocally());
@@ -18890,7 +18900,7 @@ class NetworkService extends StartableObservableService {
18890
18900
  this.startRefreshTimer();
18891
18901
  }
18892
18902
  ngOnStop() {
18893
- // Stop timer, if cannot refresh anymore
18903
+ // Stop timer, if we cannot refresh anymore
18894
18904
  if (this._timerRefreshCondition() === false) {
18895
18905
  this.stopRefreshTimer();
18896
18906
  }
@@ -20297,9 +20307,9 @@ class GraphqlService extends StartableService {
20297
20307
  });
20298
20308
  // Creating a mutation queue
20299
20309
  const queueLink = new QueueLink();
20300
- this.registerSubscription(this._networkStatusChanged$.subscribe((type) => {
20310
+ this.registerSubscription(this._networkStatusChanged$.subscribe((connectionType) => {
20301
20311
  // Network is offline: start buffering into queue
20302
- if (type === 'none') {
20312
+ if (connectionType === 'none') {
20303
20313
  console.info('[graphql] offline mode: enable mutations buffer');
20304
20314
  queueLink.close();
20305
20315
  }
@@ -28805,6 +28815,8 @@ class AppInstallUpgradeCard {
28805
28815
  return 'Android';
28806
28816
  case 'ios':
28807
28817
  return 'iOS';
28818
+ case 'electron':
28819
+ return 'desktop';
28808
28820
  default:
28809
28821
  return '';
28810
28822
  }
@@ -28867,11 +28879,8 @@ class AppInstallUpgradeCard {
28867
28879
  const needUpgrade = appMinVersionFromPod && !VersionUtils.isCompatible(appMinVersionFromPod, appVersion);
28868
28880
  if (!needUpgrade)
28869
28881
  return undefined;
28870
- const upgradeLinks = this.platform.is('mobileweb')
28871
- ? // All link if mobile web
28872
- installLinks
28873
- : // Filter on platform, if mobile App
28874
- installLinks.filter((link) => link.platform && this.platform.is(link.platform));
28882
+ // Filter on platform, if mobile App
28883
+ const upgradeLinks = (installLinks || []).filter((link) => link.platform && this.platform.is(link.platform));
28875
28884
  // Use min version as default version
28876
28885
  upgradeLinks.forEach((link) => {
28877
28886
  link.version = link.version || appMinVersionFromPod;
@@ -28941,6 +28950,25 @@ class AppInstallUpgradeCard {
28941
28950
  result.push({ name, url, platform: 'ios', version, title });
28942
28951
  }
28943
28952
  }
28953
+ // Desktop (Electron)
28954
+ {
28955
+ // Get URL (from config, or environment)
28956
+ let url = config.getProperty(CORE_CONFIG_OPTIONS.DESKTOP_INSTALL_URL);
28957
+ if (isNilOrBlank(url))
28958
+ url = this.environment.defaultDesktopInstallUrl || null;
28959
+ // Resolve relative URL
28960
+ const peer = this.network.peer;
28961
+ if (peer && url && (url.startsWith('./') || url.startsWith('/'))) {
28962
+ url = Peer.path(peer, url);
28963
+ }
28964
+ // Compute App name (use config if URL is specific)
28965
+ const name = isNotNilOrBlank(url) ? config.label : defaultAppName;
28966
+ if (url) {
28967
+ const version = minVersion || 'latest';
28968
+ const title = `${name} v${version}`;
28969
+ result.push({ name, url, platform: 'electron', version, title });
28970
+ }
28971
+ }
28944
28972
  return result;
28945
28973
  }
28946
28974
  listenDownloadJobs() {
@@ -29027,11 +29055,11 @@ class AppInstallUpgradeCard {
29027
29055
  await Toasts.show(this.toastController, this.translate, opts);
29028
29056
  }
29029
29057
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AppInstallUpgradeCard, deps: [{ token: ConfigService }, { token: i2$1.ToastController }, { token: i2$1.AlertController }, { token: i1$1.TranslateService }, { token: i0.ChangeDetectorRef }, { token: PlatformService }, { token: NetworkService }, { token: ENVIRONMENT }], target: i0.ɵɵFactoryTarget.Component });
29030
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: AppInstallUpgradeCard, selector: "app-install-upgrade-card", inputs: { isLogin: "isLogin", showUpgradeWarning: "showUpgradeWarning", showOfflineWarning: "showOfflineWarning", showInstallButton: "showInstallButton" }, ngImport: i0, template: "<!-- Offline mode card-->\n<ion-card\n *ngIf=\"showOfflineWarning && ((!loading && offline) || waitingNetwork)\"\n color=\"accent\"\n class=\"main ion-no-margin\"\n @slideUpDownAnimation\n>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row>\n <ion-col>\n <ion-text class=\"ion-text-wrap\" *ngIf=\"!waitingNetwork; else waitingNetworkText\">\n <h4>\n <b *ngIf=\"isLogin; else notLogin\" [innerHTML]=\"'NETWORK.INFO.OFFLINE_OR_UNAUTHORIZED' | translate\"></b>\n <ng-template #notLogin>\n <b [innerHTML]=\"'NETWORK.INFO.OFFLINE' | translate\"></b>\n </ng-template>\n </h4>\n <h3>\n <small [innerHTML]=\"'NETWORK.INFO.OFFLINE_HELP' | translate\"></small>\n </h3>\n </ion-text>\n <ng-template #waitingNetworkText>\n <ion-text class=\"ion-text-wrap\" [innerHTML]=\"'NETWORK.INFO.RETRY_TO_CONNECT' | translate\"></ion-text>\n </ng-template>\n </ion-col>\n <ion-col size=\"auto\">\n <!-- Retry button -->\n <ion-button\n *ngIf=\"!waitingNetwork; else waitingSpinner\"\n color=\"tertiary\"\n class=\"ion-float-end\"\n (click)=\"tryOnline()\"\n >\n <span translate>NETWORK.BTN_CHECK_ALIVE</span>\n </ion-button>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n</ion-card>\n\n<!-- Upgrade links -->\n<ion-card\n *ngIf=\"showUpgradeWarning && !loading && !offline && upgradeLinks\"\n color=\"accent\"\n class=\"ion-no-margin\"\n @slideUpDownAnimation\n>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row *ngFor=\"let link of upgradeLinks; last as last\">\n <ion-col>\n <ion-text class=\"ion-text-wrap\">\n <h3>\n <span *ngIf=\"link.version\" [innerHTML]=\"'INFO.UPDATE_APP_TO_VERSION' | translate: link\"></span>\n <span *ngIf=\"!link.version\" [innerHTML]=\"'INFO.UPDATE_APP' | translate: link\"></span>\n </h3>\n <h4>\n <small *ngIf=\"last\" [innerHTML]=\"'INFO.UPDATE_APP_HELP' | translate\"></small>\n </h4>\n </ion-text>\n </ion-col>\n\n <ion-col size=\"auto\">\n <ng-container *ngTemplateOutlet=\"downloadButton; context: { $implicit: link }\"></ng-container>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n</ion-card>\n\n<!-- Install links -->\n<ion-card\n *ngIf=\"showInstallButton && !loading && !offline && installLinks\"\n color=\"secondary\"\n class=\"ion-no-margin\"\n @slideUpDownAnimation\n>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row *ngFor=\"let link of installLinks; last as last\">\n <ion-col size=\"12\" size-sm=\"8\">\n <ion-text class=\"ion-text-wrap\">\n <h3\n [innerHTML]=\"\n 'INFO.DOWNLOAD_APP_TITLE' | translate: { name: link.name, platform: getPlatformName(link.platform) }\n \"\n ></h3>\n <h4><small *ngIf=\"last\" [innerHTML]=\"'INFO.DOWNLOAD_APP_HELP' | translate\"></small></h4>\n </ion-text>\n </ion-col>\n <ion-col size=\"12\" size-sm=\"4\">\n <ng-container *ngTemplateOutlet=\"downloadButton; context: { $implicit: link }\"></ng-container>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n</ion-card>\n\n<!-- Display a download button, depending on the link URL, and the device platform -->\n<ng-template #downloadButton let-link>\n @if (link.filename) {\n @if (hasDownloader) {\n <!-- Download using platform -->\n @if (!link.location) {\n <ion-button\n (click)=\"download($event, asLink(link))\"\n [disabled]=\"link.downloading\"\n color=\"tertiary\"\n class=\"ion-float-end\"\n >\n <ion-icon slot=\"start\" *ngIf=\"!link.downloading\" name=\"download\"></ion-icon>\n <ion-spinner slot=\"start\" class=\"ion-no-padding\" *ngIf=\"link.downloading\"></ion-spinner>\n <ion-label translate>COMMON.BTN_DOWNLOAD</ion-label>\n </ion-button>\n } @else {\n <!-- Open APK button -->\n <ion-button (click)=\"openFile($event, link)\" color=\"tertiary\" class=\"ion-float-end\">\n <ion-label translate>COMMON.BTN_INSTALL</ion-label>\n </ion-button>\n }\n } @else {\n <!-- Web download button -->\n <ion-button [download]=\"link.filename\" [href]=\"link.url\" color=\"tertiary\" class=\"ion-float-end\">\n <ion-label translate>COMMON.BTN_DOWNLOAD</ion-label>\n </ion-button>\n }\n } @else {\n <!-- Web redirect button -->\n <ion-button [href]=\"link.url\" rel=\"external\" color=\"tertiary\" class=\"ion-float-end\" target=\"_blank\">\n <ion-label translate>COMMON.BTN_SHOW_MORE</ion-label>\n </ion-button>\n }\n</ng-template>\n\n<!-- Waiting spinner -->\n<ng-template #waitingSpinner>\n <ion-spinner color=\"light\"></ion-spinner>\n</ng-template>\n", styles: ["ion-text small{font-size:85%}\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: "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.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.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.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { 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: "pipe", type: i1$1.TranslatePipe, name: "translate" }], animations: [slideUpDownAnimation], changeDetection: i0.ChangeDetectionStrategy.OnPush });
29058
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: AppInstallUpgradeCard, selector: "app-install-upgrade-card", inputs: { isLogin: "isLogin", showUpgradeWarning: "showUpgradeWarning", showOfflineWarning: "showOfflineWarning", showInstallButton: "showInstallButton" }, ngImport: i0, template: "<!-- Offline mode card-->\n<ion-card\n *ngIf=\"showOfflineWarning && ((!loading && offline) || waitingNetwork)\"\n color=\"accent\"\n class=\"main ion-no-margin\"\n @slideUpDownAnimation\n>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row>\n <ion-col>\n @if (waitingNetwork) {\n <ion-text class=\"ion-text-wrap\" [innerHTML]=\"'NETWORK.INFO.RETRY_TO_CONNECT' | translate\"></ion-text>\n } @else {\n <ion-text class=\"ion-text-wrap\">\n <h4>\n @if (isLogin) {\n <b [innerHTML]=\"'NETWORK.INFO.OFFLINE_OR_UNAUTHORIZED' | translate\"></b>\n } @else {\n <b [innerHTML]=\"'NETWORK.INFO.OFFLINE' | translate\"></b>\n }\n </h4>\n <h3>\n <small [innerHTML]=\"'NETWORK.INFO.OFFLINE_HELP' | translate\"></small>\n </h3>\n </ion-text>\n }\n </ion-col>\n <ion-col size=\"auto\" class=\"ion-align-items-end\">\n <!-- Retry button -->\n @if (waitingNetwork) {\n <ion-spinner color=\"light\"></ion-spinner>\n } @else {\n <ion-button color=\"tertiary\" class=\"ion-float-end\" (click)=\"tryOnline()\">\n <span translate>NETWORK.BTN_CHECK_ALIVE</span>\n </ion-button>\n }\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n</ion-card>\n\n<!-- Upgrade links -->\n<ion-card\n *ngIf=\"showUpgradeWarning && !loading && !offline && upgradeLinks\"\n color=\"accent\"\n class=\"ion-no-margin\"\n @slideUpDownAnimation\n>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row *ngFor=\"let link of upgradeLinks; last as last\">\n <ion-col>\n <ion-text class=\"ion-text-wrap\">\n <h3>\n @if (link.version) {\n <span [innerHTML]=\"'INFO.UPDATE_APP_TO_VERSION' | translate: link\"></span>\n } @else {\n <span [innerHTML]=\"'INFO.UPDATE_APP' | translate: link\"></span>\n }\n </h3>\n <h4>\n @if (last) {\n <small [innerHTML]=\"'INFO.UPDATE_APP_HELP' | translate\"></small>\n }\n </h4>\n </ion-text>\n </ion-col>\n\n <ion-col size=\"auto\">\n <ng-container *ngTemplateOutlet=\"downloadButton; context: { $implicit: link }\"></ng-container>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n</ion-card>\n\n<!-- Install links -->\n<ion-card\n *ngIf=\"showInstallButton && !loading && !offline && installLinks\"\n color=\"secondary\"\n class=\"ion-no-margin\"\n @slideUpDownAnimation\n>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row *ngFor=\"let link of installLinks; last as last\">\n <ion-col size=\"12\" size-sm=\"8\">\n <ion-text class=\"ion-text-wrap\">\n <h3\n [innerHTML]=\"\n 'INFO.DOWNLOAD_APP_TITLE' | translate: { name: link.name, platform: getPlatformName(link.platform) }\n \"\n ></h3>\n <h4><small *ngIf=\"last\" [innerHTML]=\"'INFO.DOWNLOAD_APP_HELP' | translate\"></small></h4>\n </ion-text>\n </ion-col>\n <ion-col size=\"12\" size-sm=\"4\">\n <ng-container *ngTemplateOutlet=\"downloadButton; context: { $implicit: link }\"></ng-container>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n</ion-card>\n\n<!-- Display a download button, depending on the link URL, and the device platform -->\n<ng-template #downloadButton let-link>\n @if (link.filename) {\n @if (hasDownloader) {\n <!-- Download using platform -->\n @if (!link.location) {\n <ion-button\n (click)=\"download($event, asLink(link))\"\n [disabled]=\"link.downloading\"\n color=\"tertiary\"\n class=\"ion-float-end\"\n >\n <ion-icon slot=\"start\" *ngIf=\"!link.downloading\" name=\"download\"></ion-icon>\n <ion-spinner slot=\"start\" class=\"ion-no-padding\" *ngIf=\"link.downloading\"></ion-spinner>\n <ion-label translate>COMMON.BTN_DOWNLOAD</ion-label>\n </ion-button>\n } @else {\n <!-- Open APK button -->\n <ion-button (click)=\"openFile($event, link)\" color=\"tertiary\" class=\"ion-float-end\">\n <ion-label translate>COMMON.BTN_INSTALL</ion-label>\n </ion-button>\n }\n } @else {\n <!-- Web download button -->\n <ion-button [download]=\"link.filename\" [href]=\"link.url\" color=\"tertiary\" class=\"ion-float-end\">\n <ion-label translate>COMMON.BTN_DOWNLOAD</ion-label>\n </ion-button>\n }\n } @else {\n <!-- Web redirect button -->\n <ion-button [href]=\"link.url\" rel=\"external\" color=\"tertiary\" class=\"ion-float-end\" target=\"_blank\">\n <ion-label translate>COMMON.BTN_SHOW_MORE</ion-label>\n </ion-button>\n }\n</ng-template>\n", styles: ["ion-text small{font-size:85%}\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: "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.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.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.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { 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: "pipe", type: i1$1.TranslatePipe, name: "translate" }], animations: [slideUpDownAnimation], changeDetection: i0.ChangeDetectionStrategy.OnPush });
29031
29059
  }
29032
29060
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AppInstallUpgradeCard, decorators: [{
29033
29061
  type: Component,
29034
- args: [{ selector: 'app-install-upgrade-card', changeDetection: ChangeDetectionStrategy.OnPush, animations: [slideUpDownAnimation], template: "<!-- Offline mode card-->\n<ion-card\n *ngIf=\"showOfflineWarning && ((!loading && offline) || waitingNetwork)\"\n color=\"accent\"\n class=\"main ion-no-margin\"\n @slideUpDownAnimation\n>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row>\n <ion-col>\n <ion-text class=\"ion-text-wrap\" *ngIf=\"!waitingNetwork; else waitingNetworkText\">\n <h4>\n <b *ngIf=\"isLogin; else notLogin\" [innerHTML]=\"'NETWORK.INFO.OFFLINE_OR_UNAUTHORIZED' | translate\"></b>\n <ng-template #notLogin>\n <b [innerHTML]=\"'NETWORK.INFO.OFFLINE' | translate\"></b>\n </ng-template>\n </h4>\n <h3>\n <small [innerHTML]=\"'NETWORK.INFO.OFFLINE_HELP' | translate\"></small>\n </h3>\n </ion-text>\n <ng-template #waitingNetworkText>\n <ion-text class=\"ion-text-wrap\" [innerHTML]=\"'NETWORK.INFO.RETRY_TO_CONNECT' | translate\"></ion-text>\n </ng-template>\n </ion-col>\n <ion-col size=\"auto\">\n <!-- Retry button -->\n <ion-button\n *ngIf=\"!waitingNetwork; else waitingSpinner\"\n color=\"tertiary\"\n class=\"ion-float-end\"\n (click)=\"tryOnline()\"\n >\n <span translate>NETWORK.BTN_CHECK_ALIVE</span>\n </ion-button>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n</ion-card>\n\n<!-- Upgrade links -->\n<ion-card\n *ngIf=\"showUpgradeWarning && !loading && !offline && upgradeLinks\"\n color=\"accent\"\n class=\"ion-no-margin\"\n @slideUpDownAnimation\n>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row *ngFor=\"let link of upgradeLinks; last as last\">\n <ion-col>\n <ion-text class=\"ion-text-wrap\">\n <h3>\n <span *ngIf=\"link.version\" [innerHTML]=\"'INFO.UPDATE_APP_TO_VERSION' | translate: link\"></span>\n <span *ngIf=\"!link.version\" [innerHTML]=\"'INFO.UPDATE_APP' | translate: link\"></span>\n </h3>\n <h4>\n <small *ngIf=\"last\" [innerHTML]=\"'INFO.UPDATE_APP_HELP' | translate\"></small>\n </h4>\n </ion-text>\n </ion-col>\n\n <ion-col size=\"auto\">\n <ng-container *ngTemplateOutlet=\"downloadButton; context: { $implicit: link }\"></ng-container>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n</ion-card>\n\n<!-- Install links -->\n<ion-card\n *ngIf=\"showInstallButton && !loading && !offline && installLinks\"\n color=\"secondary\"\n class=\"ion-no-margin\"\n @slideUpDownAnimation\n>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row *ngFor=\"let link of installLinks; last as last\">\n <ion-col size=\"12\" size-sm=\"8\">\n <ion-text class=\"ion-text-wrap\">\n <h3\n [innerHTML]=\"\n 'INFO.DOWNLOAD_APP_TITLE' | translate: { name: link.name, platform: getPlatformName(link.platform) }\n \"\n ></h3>\n <h4><small *ngIf=\"last\" [innerHTML]=\"'INFO.DOWNLOAD_APP_HELP' | translate\"></small></h4>\n </ion-text>\n </ion-col>\n <ion-col size=\"12\" size-sm=\"4\">\n <ng-container *ngTemplateOutlet=\"downloadButton; context: { $implicit: link }\"></ng-container>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n</ion-card>\n\n<!-- Display a download button, depending on the link URL, and the device platform -->\n<ng-template #downloadButton let-link>\n @if (link.filename) {\n @if (hasDownloader) {\n <!-- Download using platform -->\n @if (!link.location) {\n <ion-button\n (click)=\"download($event, asLink(link))\"\n [disabled]=\"link.downloading\"\n color=\"tertiary\"\n class=\"ion-float-end\"\n >\n <ion-icon slot=\"start\" *ngIf=\"!link.downloading\" name=\"download\"></ion-icon>\n <ion-spinner slot=\"start\" class=\"ion-no-padding\" *ngIf=\"link.downloading\"></ion-spinner>\n <ion-label translate>COMMON.BTN_DOWNLOAD</ion-label>\n </ion-button>\n } @else {\n <!-- Open APK button -->\n <ion-button (click)=\"openFile($event, link)\" color=\"tertiary\" class=\"ion-float-end\">\n <ion-label translate>COMMON.BTN_INSTALL</ion-label>\n </ion-button>\n }\n } @else {\n <!-- Web download button -->\n <ion-button [download]=\"link.filename\" [href]=\"link.url\" color=\"tertiary\" class=\"ion-float-end\">\n <ion-label translate>COMMON.BTN_DOWNLOAD</ion-label>\n </ion-button>\n }\n } @else {\n <!-- Web redirect button -->\n <ion-button [href]=\"link.url\" rel=\"external\" color=\"tertiary\" class=\"ion-float-end\" target=\"_blank\">\n <ion-label translate>COMMON.BTN_SHOW_MORE</ion-label>\n </ion-button>\n }\n</ng-template>\n\n<!-- Waiting spinner -->\n<ng-template #waitingSpinner>\n <ion-spinner color=\"light\"></ion-spinner>\n</ng-template>\n", styles: ["ion-text small{font-size:85%}\n"] }]
29062
+ args: [{ selector: 'app-install-upgrade-card', changeDetection: ChangeDetectionStrategy.OnPush, animations: [slideUpDownAnimation], template: "<!-- Offline mode card-->\n<ion-card\n *ngIf=\"showOfflineWarning && ((!loading && offline) || waitingNetwork)\"\n color=\"accent\"\n class=\"main ion-no-margin\"\n @slideUpDownAnimation\n>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row>\n <ion-col>\n @if (waitingNetwork) {\n <ion-text class=\"ion-text-wrap\" [innerHTML]=\"'NETWORK.INFO.RETRY_TO_CONNECT' | translate\"></ion-text>\n } @else {\n <ion-text class=\"ion-text-wrap\">\n <h4>\n @if (isLogin) {\n <b [innerHTML]=\"'NETWORK.INFO.OFFLINE_OR_UNAUTHORIZED' | translate\"></b>\n } @else {\n <b [innerHTML]=\"'NETWORK.INFO.OFFLINE' | translate\"></b>\n }\n </h4>\n <h3>\n <small [innerHTML]=\"'NETWORK.INFO.OFFLINE_HELP' | translate\"></small>\n </h3>\n </ion-text>\n }\n </ion-col>\n <ion-col size=\"auto\" class=\"ion-align-items-end\">\n <!-- Retry button -->\n @if (waitingNetwork) {\n <ion-spinner color=\"light\"></ion-spinner>\n } @else {\n <ion-button color=\"tertiary\" class=\"ion-float-end\" (click)=\"tryOnline()\">\n <span translate>NETWORK.BTN_CHECK_ALIVE</span>\n </ion-button>\n }\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n</ion-card>\n\n<!-- Upgrade links -->\n<ion-card\n *ngIf=\"showUpgradeWarning && !loading && !offline && upgradeLinks\"\n color=\"accent\"\n class=\"ion-no-margin\"\n @slideUpDownAnimation\n>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row *ngFor=\"let link of upgradeLinks; last as last\">\n <ion-col>\n <ion-text class=\"ion-text-wrap\">\n <h3>\n @if (link.version) {\n <span [innerHTML]=\"'INFO.UPDATE_APP_TO_VERSION' | translate: link\"></span>\n } @else {\n <span [innerHTML]=\"'INFO.UPDATE_APP' | translate: link\"></span>\n }\n </h3>\n <h4>\n @if (last) {\n <small [innerHTML]=\"'INFO.UPDATE_APP_HELP' | translate\"></small>\n }\n </h4>\n </ion-text>\n </ion-col>\n\n <ion-col size=\"auto\">\n <ng-container *ngTemplateOutlet=\"downloadButton; context: { $implicit: link }\"></ng-container>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n</ion-card>\n\n<!-- Install links -->\n<ion-card\n *ngIf=\"showInstallButton && !loading && !offline && installLinks\"\n color=\"secondary\"\n class=\"ion-no-margin\"\n @slideUpDownAnimation\n>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row *ngFor=\"let link of installLinks; last as last\">\n <ion-col size=\"12\" size-sm=\"8\">\n <ion-text class=\"ion-text-wrap\">\n <h3\n [innerHTML]=\"\n 'INFO.DOWNLOAD_APP_TITLE' | translate: { name: link.name, platform: getPlatformName(link.platform) }\n \"\n ></h3>\n <h4><small *ngIf=\"last\" [innerHTML]=\"'INFO.DOWNLOAD_APP_HELP' | translate\"></small></h4>\n </ion-text>\n </ion-col>\n <ion-col size=\"12\" size-sm=\"4\">\n <ng-container *ngTemplateOutlet=\"downloadButton; context: { $implicit: link }\"></ng-container>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n</ion-card>\n\n<!-- Display a download button, depending on the link URL, and the device platform -->\n<ng-template #downloadButton let-link>\n @if (link.filename) {\n @if (hasDownloader) {\n <!-- Download using platform -->\n @if (!link.location) {\n <ion-button\n (click)=\"download($event, asLink(link))\"\n [disabled]=\"link.downloading\"\n color=\"tertiary\"\n class=\"ion-float-end\"\n >\n <ion-icon slot=\"start\" *ngIf=\"!link.downloading\" name=\"download\"></ion-icon>\n <ion-spinner slot=\"start\" class=\"ion-no-padding\" *ngIf=\"link.downloading\"></ion-spinner>\n <ion-label translate>COMMON.BTN_DOWNLOAD</ion-label>\n </ion-button>\n } @else {\n <!-- Open APK button -->\n <ion-button (click)=\"openFile($event, link)\" color=\"tertiary\" class=\"ion-float-end\">\n <ion-label translate>COMMON.BTN_INSTALL</ion-label>\n </ion-button>\n }\n } @else {\n <!-- Web download button -->\n <ion-button [download]=\"link.filename\" [href]=\"link.url\" color=\"tertiary\" class=\"ion-float-end\">\n <ion-label translate>COMMON.BTN_DOWNLOAD</ion-label>\n </ion-button>\n }\n } @else {\n <!-- Web redirect button -->\n <ion-button [href]=\"link.url\" rel=\"external\" color=\"tertiary\" class=\"ion-float-end\" target=\"_blank\">\n <ion-label translate>COMMON.BTN_SHOW_MORE</ion-label>\n </ion-button>\n }\n</ng-template>\n", styles: ["ion-text small{font-size:85%}\n"] }]
29035
29063
  }], ctorParameters: () => [{ type: ConfigService }, { type: i2$1.ToastController }, { type: i2$1.AlertController }, { type: i1$1.TranslateService }, { type: i0.ChangeDetectorRef }, { type: PlatformService }, { type: NetworkService }, { type: Environment, decorators: [{
29036
29064
  type: Inject,
29037
29065
  args: [ENVIRONMENT]
@@ -30319,11 +30347,7 @@ class LocalSettingsValidatorService extends AppValidatorService {
30319
30347
  }
30320
30348
  /* -- protected methods -- */
30321
30349
  async peerAlive(peerUrlControl) {
30322
- // Offline mode: no error
30323
- if (this.networkService.offline) {
30324
- SharedValidators.clearError(peerUrlControl, 'peerAlive');
30325
- }
30326
- else {
30350
+ if (this.networkService.online) {
30327
30351
  const alive = await this.networkService.checkPeerAlive(peerUrlControl.value);
30328
30352
  // KO: add a validation error
30329
30353
  if (!alive) {
@@ -30334,11 +30358,10 @@ class LocalSettingsValidatorService extends AppValidatorService {
30334
30358
  // Return the error (should be apply to the parent form)
30335
30359
  return { peerAlive: true };
30336
30360
  }
30337
- // OK: remove the existing on control
30338
- else {
30339
- SharedValidators.clearError(peerUrlControl, 'peerAlive');
30340
- }
30341
30361
  }
30362
+ // OK: remove the existing error on control
30363
+ SharedValidators.clearError(peerUrlControl, 'peerAlive');
30364
+ return null;
30342
30365
  }
30343
30366
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: LocalSettingsValidatorService, deps: [{ token: i1$3.UntypedFormBuilder }, { token: PropertyValidator }, { token: NetworkService }], target: i0.ɵɵFactoryTarget.Injectable });
30344
30367
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: LocalSettingsValidatorService, providedIn: 'root' });
@@ -30539,8 +30562,11 @@ class AppPropertiesForm extends AppForm {
30539
30562
  this.mobile = this.mobile ?? isMobile(window);
30540
30563
  // Combo key
30541
30564
  this._autocompleteConfig = {
30542
- suggestFn: (value, filter) => this.suggestDefinition(value, filter),
30565
+ suggestFn: (value, filter, sortBy, sortDirection, opts) => this.suggestDefinitionKeys(value, filter, 'value', sortDirection, opts),
30566
+ equals: (v1, v2) => (v1?.key || v1) === (v2?.key || v2),
30543
30567
  displayWith: (value) => {
30568
+ // DEBUG
30569
+ //console.debug('[app-properties-form] Displaying value', value);
30544
30570
  // Value is an object (a Property)
30545
30571
  if (value && typeof value === 'object') {
30546
30572
  return value.value;
@@ -30555,7 +30581,7 @@ class AppPropertiesForm extends AppForm {
30555
30581
  columnNames: [this.translate.instant('SETTINGS.PROPERTY_KEY')],
30556
30582
  splitSearchText: false,
30557
30583
  showAllOnFocus: true,
30558
- selectInputContentOnFocus: true,
30584
+ selectInputContentOnFocus: !this.mobile,
30559
30585
  };
30560
30586
  this._state.hold(this._state.select('definitions'), (definitions) => {
30561
30587
  if (this.loaded)
@@ -30564,8 +30590,10 @@ class AppPropertiesForm extends AppForm {
30564
30590
  this.processDefinitions(this.definitions);
30565
30591
  }
30566
30592
  processDefinitions(definitions) {
30593
+ if (!definitions)
30594
+ return [];
30567
30595
  // DEBUG
30568
- //console.debug('[app-properties-form] Received definitions:', definitions);
30596
+ //console.debug('[app-properties-form] Processing definitions...', definitions);
30569
30597
  // Prepare definition (e.g. resolve tokens if any)
30570
30598
  definitions = FormFieldDefinitionUtils.prepareDefinitions(this.injector, definitions || []);
30571
30599
  // Fill options map
@@ -30579,7 +30607,6 @@ class AppPropertiesForm extends AppForm {
30579
30607
  })
30580
30608
  .filter(isNotNil);
30581
30609
  this._definitionByKey = definitionsByKey;
30582
- return definitions;
30583
30610
  }
30584
30611
  getDefinitionAt(index) {
30585
30612
  let definition = this._definitionsByIndex[index];
@@ -30606,9 +30633,18 @@ class AppPropertiesForm extends AppForm {
30606
30633
  async setValue(data) {
30607
30634
  if (!data)
30608
30635
  return; // Skip
30609
- const definitions = await firstNotNilPromise(this._definitions$, { stop: this.destroySubject });
30636
+ const [definitions, definitionKeys] = await Promise.all([
30637
+ firstNotNilPromise(this._definitions$, { stop: this.destroySubject }),
30638
+ firstNotNilPromise(this._definitionKeys$, { stop: this.destroySubject }),
30639
+ ]);
30610
30640
  // Transform properties map into array
30611
30641
  const values = await AppPropertiesUtils.arrayFromObject(data, definitions);
30642
+ // Workaround because when autocomplete use mat-select (when mobile), it needs to display the value - fix issue ngx-sumaris-components#16
30643
+ if (this.mobile) {
30644
+ values.forEach((property) => {
30645
+ property.key = (definitionKeys.find((defKey) => defKey.key === property.key) || { key: property.key, value: property.key });
30646
+ });
30647
+ }
30612
30648
  this._helper.resize(values.length);
30613
30649
  this._helper.formArray.patchValue(values, { emitEvent: false });
30614
30650
  this.markAsPristine();
@@ -30623,18 +30659,19 @@ class AppPropertiesForm extends AppForm {
30623
30659
  return json;
30624
30660
  }
30625
30661
  /* -- protected methods -- */
30626
- async suggestDefinition(value, filter) {
30627
- value = value ?? '*';
30662
+ async suggestDefinitionKeys(value, filter, sortBy, sortDirection, opts) {
30628
30663
  let items = await firstNotNilPromise(this._definitionKeys$, { stop: this.destroySubject });
30664
+ // DEBUG
30665
+ //console.debug('[app-properties-form] Suggesting definition keys', value, items);
30629
30666
  // Get as key
30630
- if (typeof value === 'string') {
30667
+ if (typeof value === 'string' && !RegExpUtils.hasWildcardCharacter(value)) {
30631
30668
  const obj = items.find((o) => o.key === value);
30632
30669
  if (obj)
30633
30670
  return { data: [obj] };
30634
30671
  }
30635
30672
  // Get as object
30636
- if (typeof value === 'object') {
30637
- return { data: value };
30673
+ if (value && typeof value === 'object') {
30674
+ return { data: [value] };
30638
30675
  }
30639
30676
  // Remove already used keys
30640
30677
  if (this.options?.allowDuplicatedValues !== true) {
@@ -30643,7 +30680,11 @@ class AppPropertiesForm extends AppForm {
30643
30680
  .map(AppPropertyUtils.getPropertyKey);
30644
30681
  items = items.filter((item) => !existingKeys.includes(item.key));
30645
30682
  }
30646
- return suggestFromArray(items, value, { searchAttribute: 'value', ...filter });
30683
+ value = value ?? '*';
30684
+ // Any match
30685
+ if (!value.startsWith('*'))
30686
+ value = '*' + value;
30687
+ return suggestFromArray(items, value, filter, sortBy, sortDirection, opts);
30647
30688
  }
30648
30689
  markForCheck() {
30649
30690
  this.cd.markForCheck();
@@ -30652,11 +30693,11 @@ class AppPropertiesForm extends AppForm {
30652
30693
  return isNil(obj?.key) || isNil(obj.value);
30653
30694
  }
30654
30695
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", 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 });
30655
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: AppPropertiesForm, selector: "app-properties-form", inputs: { 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], showMoreButtonTitle: "showMoreButtonTitle", definitions: "definitions" }, providers: [{ provide: PropertyValidator, useClass: PropertyValidator }, RxState], usesInheritance: true, ngImport: i0, template: "<!-- 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 color=\"light\" [title]=\"addButtonTitle | translate\" (click)=\"_helper.add()\">\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 <!-- property key -->\n @if (fieldForm | formGetControl: 'key'; as control) {\n <ion-col\n size=\"12\"\n size-sm=\"6\"\n [title]=\"(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 >\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 ></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]=\"'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]=\"'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 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 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: "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: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: MatAutocompleteField, selector: "mat-autocomplete-field", inputs: ["equals", "logPrefix", "formControl", "formControlName", "floatLabel", "label", "appearance", "subscriptSizing", "placeholder", "suggestFn", "required", "hideRequiredMarker", "mobile", "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", "class", "filter", "readonly", "tabindex", "items"], outputs: ["click", "blur", "focus", "dropButtonClick", "keydown.escape", "keydown.backspace", "keyup.enter", "selectionChange"] }, { 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"], outputs: ["keyup.enter"] }, { 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: 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 });
30696
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: AppPropertiesForm, selector: "app-properties-form", inputs: { 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], showMoreButtonTitle: "showMoreButtonTitle", definitions: "definitions" }, providers: [{ provide: PropertyValidator, useClass: PropertyValidator }, RxState], usesInheritance: true, ngImport: i0, template: "<!-- 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 color=\"light\" [title]=\"addButtonTitle | translate\" (click)=\"_helper.add()\">\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 <!-- property key -->\n @if (fieldForm | formGetControl: 'key'; as control) {\n <ion-col\n size=\"12\"\n size-sm=\"6\"\n [title]=\"(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 >\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 ></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]=\"'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]=\"'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 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 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: "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: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: MatAutocompleteField, selector: "mat-autocomplete-field", inputs: ["equals", "logPrefix", "formControl", "formControlName", "floatLabel", "label", "appearance", "subscriptSizing", "placeholder", "suggestFn", "required", "hideRequiredMarker", "mobile", "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", "class", "filter", "readonly", "tabindex", "items"], outputs: ["click", "blur", "focus", "dropButtonClick", "keydown.escape", "keydown.backspace", "keyup.enter", "selectionChange"] }, { 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"], outputs: ["keyup.enter"] }, { 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: 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 });
30656
30697
  }
30657
30698
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AppPropertiesForm, decorators: [{
30658
30699
  type: Component,
30659
- args: [{ selector: 'app-properties-form', providers: [{ provide: PropertyValidator, useClass: PropertyValidator }, RxState], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- 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 color=\"light\" [title]=\"addButtonTitle | translate\" (click)=\"_helper.add()\">\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 <!-- property key -->\n @if (fieldForm | formGetControl: 'key'; as control) {\n <ion-col\n size=\"12\"\n size-sm=\"6\"\n [title]=\"(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 >\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 ></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]=\"'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]=\"'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 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 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"] }]
30700
+ args: [{ selector: 'app-properties-form', providers: [{ provide: PropertyValidator, useClass: PropertyValidator }, RxState], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- 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 color=\"light\" [title]=\"addButtonTitle | translate\" (click)=\"_helper.add()\">\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 <!-- property key -->\n @if (fieldForm | formGetControl: 'key'; as control) {\n <ion-col\n size=\"12\"\n size-sm=\"6\"\n [title]=\"(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 >\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 ></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]=\"'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]=\"'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 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 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"] }]
30660
30701
  }], ctorParameters: () => [{ type: i0.Injector }, { type: i1$3.UntypedFormBuilder }, { type: i2.DateAdapter }, { type: i0.ChangeDetectorRef }, { type: PropertyValidator }, { type: i1$3.FormGroupDirective, decorators: [{
30661
30702
  type: Optional
30662
30703
  }] }], propDecorators: { formArrayName: [{
@@ -30931,6 +30972,13 @@ class SettingsPage extends AppForm {
30931
30972
  this.markAsDirty();
30932
30973
  }
30933
30974
  }
30975
+ setForceOffline(value) {
30976
+ this.network.setForceOffline(value);
30977
+ this.form.get('peerUrl').markAsPending(); // Force revalidation
30978
+ this.form.markAsDirty();
30979
+ this.form.updateValueAndValidity();
30980
+ this.form.markAsTouched();
30981
+ }
30934
30982
  async cancel(event) {
30935
30983
  await this.load();
30936
30984
  }
@@ -31001,7 +31049,7 @@ class SettingsPage extends AppForm {
31001
31049
  this.propertiesForm.markAsPristine(opts);
31002
31050
  }
31003
31051
  openAccountPage() {
31004
- this.navController.navigateRoot('account');
31052
+ return this.navController.navigateForward('account');
31005
31053
  }
31006
31054
  async toggleDarkMode(enable) {
31007
31055
  const oldValue = this.form.get('darkMode').value;
@@ -31019,15 +31067,19 @@ class SettingsPage extends AppForm {
31019
31067
  this.showOptionKeys = !this.showOptionKeys;
31020
31068
  this.markForCheck();
31021
31069
  }
31070
+ devToggleDebug() {
31071
+ this.debug = !this.debug;
31072
+ this.markForCheck();
31073
+ }
31022
31074
  markForCheck() {
31023
31075
  this.cd.markForCheck();
31024
31076
  }
31025
31077
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", 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 });
31026
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: SettingsPage, selector: "page-settings", 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 <!-- show option key-->\n <mat-menu #debugMenu=\"matMenu\">\n <button mat-menu-item (click)=\"toggleShowOptionKey()\">\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\" routerDirection=\"root\" lines=\"none\" (click)=\"openAccountPage()\">\n <ion-icon slot=\"start\" name=\"help-circle-outline\"></ion-icon>\n <ion-label color=\"dark\">\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]=\"'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)=\"network.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 ></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: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i8$2.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: i8$2.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i8$2.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i12.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$1.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: "component", type: ToolbarComponent, selector: "app-toolbar", inputs: ["progressBarMode", "title", "color", "class", "backHref", "defaultBackHref", "hasValidate", "hasClose", "hasSearch", "canGoBack", "canShowMenu"], outputs: ["onValidate", "onClose", "onValidateAndClose", "onBackClick", "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: ["formArrayName", "formArray", "options", "chipColor", "mobile", "appearance", "subscriptSizing", "showHintKey", "hintKeyPrefix", "showMoreButton", "addButtonText", "addButtonTitle", "showAddButton", "showMoreButtonTitle", "definitions"] }, { 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 });
31078
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: SettingsPage, selector: "page-settings", 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 <!-- show option key-->\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)=\"toggleShowOptionKey()\">\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]=\"'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: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i8$2.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: i8$2.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i8$2.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i12.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$1.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: "component", type: ToolbarComponent, selector: "app-toolbar", inputs: ["progressBarMode", "title", "color", "class", "backHref", "defaultBackHref", "hasValidate", "hasClose", "hasSearch", "canGoBack", "canShowMenu"], outputs: ["onValidate", "onClose", "onValidateAndClose", "onBackClick", "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: ["formArrayName", "formArray", "options", "chipColor", "mobile", "appearance", "subscriptSizing", "showHintKey", "hintKeyPrefix", "showMoreButton", "addButtonText", "addButtonTitle", "showAddButton", "showMoreButtonTitle", "definitions"] }, { 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 });
31027
31079
  }
31028
31080
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: SettingsPage, decorators: [{
31029
31081
  type: Component,
31030
- 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 <!-- show option key-->\n <mat-menu #debugMenu=\"matMenu\">\n <button mat-menu-item (click)=\"toggleShowOptionKey()\">\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\" routerDirection=\"root\" lines=\"none\" (click)=\"openAccountPage()\">\n <ion-icon slot=\"start\" name=\"help-circle-outline\"></ion-icon>\n <ion-label color=\"dark\">\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]=\"'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)=\"network.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 ></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" }]
31082
+ 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 <!-- show option key-->\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)=\"toggleShowOptionKey()\">\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]=\"'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" }]
31031
31083
  }], 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: [{
31032
31084
  type: Inject,
31033
31085
  args: [APP_LOCALES]
@@ -38368,6 +38420,7 @@ class AccountPage extends AppForm {
38368
38420
  return this.navController.navigateRoot('/'); // back to home
38369
38421
  }
38370
38422
  onConfigLoaded(config) {
38423
+ console.debug('[account] Config loaded: ', config);
38371
38424
  this.accountReadOnly = config.getPropertyAsBoolean(CORE_CONFIG_OPTIONS.ACCOUNT_READONLY);
38372
38425
  this.showLatLonFormat =
38373
38426
  config.getPropertyAsBoolean(CORE_CONFIG_OPTIONS.ACCOUNT_LAT_LONG_FORMAT_ENABLE) &&
@@ -38377,6 +38430,7 @@ class AccountPage extends AppForm {
38377
38430
  this.canChangePassword = authTokenType === 'token';
38378
38431
  this.showSecurityDetails = this.canChangePassword;
38379
38432
  this.showApiTokens = config.getPropertyAsBoolean(CORE_CONFIG_OPTIONS.AUTH_API_TOKEN_ENABLED);
38433
+ this.markForCheck();
38380
38434
  }
38381
38435
  markForCheck() {
38382
38436
  this.cd.markForCheck();
@@ -38385,11 +38439,11 @@ class AccountPage extends AppForm {
38385
38439
  this.navController.navigateRoot('account/password');
38386
38440
  }
38387
38441
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", 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: APP_LOCALES }], target: i0.ɵɵFactoryTarget.Component });
38388
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: AccountPage, selector: "app-account-page", 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 [canGoBack]=\"false\"\n (onClose)=\"close($event)\"\n>\n <ion-buttons slot=\"end\">\n <!-- loader -->\n <ion-spinner *ngIf=\"loading || saving; else endButtons\"></ion-spinner>\n\n <ng-template #endButtons>\n <!-- refresh button (if online) -->\n <ion-button [matTooltip]=\"'COMMON.BTN_REFRESH' | translate\" *ngIf=\"network.online\" (click)=\"refresh($event)\">\n <mat-icon slot=\"icon-only\">refresh</mat-icon>\n </ion-button>\n </ng-template>\n </ion-buttons>\n</app-toolbar>\n\n<ion-content>\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\n <form [formGroup]=\"form\" novalidate class=\"form-container\">\n <mat-tab-group #tabGroup [(selectedIndex)]=\"selectedTabIndex\" dynamicHeight>\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 <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 [button]=\"true\" routerDirection=\"root\" lines=\"none\" (click)=\"openChangePasswordPage()\">\n <ion-icon slot=\"start\" name=\"keypad\"></ion-icon>\n <ion-label>{{ 'ACCOUNT.SECURITY.PASSWORD' | translate }}</ion-label>\n <ion-text color=\"tertiary\">{{ 'ACCOUNT.SECURITY.BTN_CHANGE_PASSWORD' | translate }}</ion-text>\n <ion-icon slot=\"end\" color=\"tertiary\" name=\"chevron-forward\"></ion-icon>\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 [debug]=\"debug\"\n [canEdit]=\"true\"\n (onDirty)=\"$event ? markAsDirty() : undefined\"\n ></app-properties-table>\n </div>\n </mat-tab>\n\n <!-- TAB: tokens -->\n <mat-tab\n [label]=\"'ACCOUNT.TOKENS.TITLE' | translate\"\n formGroupName=\"tokens\"\n [class.cdk-visually-hidden]=\"!showApiTokens\"\n >\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 [canEdit]=\"true\"\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%}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: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i12.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: i7.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i7.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass"], exportAs: ["matTab"] }, { kind: "component", type: i7.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: i10$1.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", "backHref", "defaultBackHref", "hasValidate", "hasClose", "hasSearch", "canGoBack", "canShowMenu"], outputs: ["onValidate", "onClose", "onValidateAndClose", "onBackClick", "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"], 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: UserTokenTable, selector: "app-user-token-table", inputs: ["useSticky"] }, { 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 });
38442
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: AccountPage, selector: "app-account-page", 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 <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]=\"true\"\n routerDirection=\"root\"\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 <ion-icon slot=\"end\" color=\"tertiary\" name=\"chevron-forward\"></ion-icon>\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%}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: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i12.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: i7.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i7.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass"], exportAs: ["matTab"] }, { kind: "component", type: i7.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: i10$1.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", "backHref", "defaultBackHref", "hasValidate", "hasClose", "hasSearch", "canGoBack", "canShowMenu"], outputs: ["onValidate", "onClose", "onValidateAndClose", "onBackClick", "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"], 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"] }, { 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 });
38389
38443
  }
38390
38444
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AccountPage, decorators: [{
38391
38445
  type: Component,
38392
- args: [{ selector: 'app-account-page', changeDetection: ChangeDetectionStrategy.OnPush, template: "<app-toolbar\n [title]=\"'ACCOUNT.TITLE' | translate\"\n color=\"primary\"\n [hasValidate]=\"dirty && !saving\"\n [hasClose]=\"!dirty && !saving\"\n (onValidate)=\"save()\"\n [canGoBack]=\"false\"\n (onClose)=\"close($event)\"\n>\n <ion-buttons slot=\"end\">\n <!-- loader -->\n <ion-spinner *ngIf=\"loading || saving; else endButtons\"></ion-spinner>\n\n <ng-template #endButtons>\n <!-- refresh button (if online) -->\n <ion-button [matTooltip]=\"'COMMON.BTN_REFRESH' | translate\" *ngIf=\"network.online\" (click)=\"refresh($event)\">\n <mat-icon slot=\"icon-only\">refresh</mat-icon>\n </ion-button>\n </ng-template>\n </ion-buttons>\n</app-toolbar>\n\n<ion-content>\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\n <form [formGroup]=\"form\" novalidate class=\"form-container\">\n <mat-tab-group #tabGroup [(selectedIndex)]=\"selectedTabIndex\" dynamicHeight>\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 <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 [button]=\"true\" routerDirection=\"root\" lines=\"none\" (click)=\"openChangePasswordPage()\">\n <ion-icon slot=\"start\" name=\"keypad\"></ion-icon>\n <ion-label>{{ 'ACCOUNT.SECURITY.PASSWORD' | translate }}</ion-label>\n <ion-text color=\"tertiary\">{{ 'ACCOUNT.SECURITY.BTN_CHANGE_PASSWORD' | translate }}</ion-text>\n <ion-icon slot=\"end\" color=\"tertiary\" name=\"chevron-forward\"></ion-icon>\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 [debug]=\"debug\"\n [canEdit]=\"true\"\n (onDirty)=\"$event ? markAsDirty() : undefined\"\n ></app-properties-table>\n </div>\n </mat-tab>\n\n <!-- TAB: tokens -->\n <mat-tab\n [label]=\"'ACCOUNT.TOKENS.TITLE' | translate\"\n formGroupName=\"tokens\"\n [class.cdk-visually-hidden]=\"!showApiTokens\"\n >\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 [canEdit]=\"true\"\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%}div.options-table{height:calc(100vh - 380px)}div.tokens-table{height:calc(100vh - 250px)}\n"] }]
38446
+ args: [{ selector: 'app-account-page', changeDetection: ChangeDetectionStrategy.OnPush, 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 <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]=\"true\"\n routerDirection=\"root\"\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 <ion-icon slot=\"end\" color=\"tertiary\" name=\"chevron-forward\"></ion-icon>\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%}div.options-table{height:calc(100vh - 380px)}div.tokens-table{height:calc(100vh - 250px)}\n"] }]
38393
38447
  }], ctorParameters: () => [{ type: i0.Injector }, { type: i1$3.UntypedFormBuilder }, { type: AccountService }, { type: NetworkService }, { type: i2$1.NavController }, { type: AccountValidatorService }, { type: ConfigService }, { type: i0.ChangeDetectorRef }, { type: undefined, decorators: [{
38394
38448
  type: Inject,
38395
38449
  args: [APP_LOCALES]
@@ -38511,7 +38565,7 @@ class AppChangePasswordPage extends AppFormContainer {
38511
38565
  }
38512
38566
  else {
38513
38567
  const primaryColor = config.getProperty(CORE_CONFIG_OPTIONS.COLOR_PRIMARY) || 'var(--ion-color-primary)';
38514
- this.contentStyle = { 'background-color': primaryColor };
38568
+ this.contentStyle = { '--ion-background-color': primaryColor + ' !important' };
38515
38569
  }
38516
38570
  const tokenType = config.getProperty(CORE_CONFIG_OPTIONS.AUTH_TOKEN_TYPE);
38517
38571
  if (tokenType === 'token') {
@@ -38602,7 +38656,8 @@ class AppAccountModule {
38602
38656
  AppPropertiesFormModule,
38603
38657
  AppTableModule,
38604
38658
  // Sub modules
38605
- AppChangePasswordModule], exports: [TranslateModule,
38659
+ AppChangePasswordModule,
38660
+ AppInstallUpgradeCardModule], exports: [TranslateModule,
38606
38661
  // Components
38607
38662
  AccountPage,
38608
38663
  UserTokenTable,
@@ -38618,7 +38673,8 @@ class AppAccountModule {
38618
38673
  AppPropertiesFormModule,
38619
38674
  AppTableModule,
38620
38675
  // Sub modules
38621
- AppChangePasswordModule, TranslateModule,
38676
+ AppChangePasswordModule,
38677
+ AppInstallUpgradeCardModule, TranslateModule,
38622
38678
  // Sub modules
38623
38679
  AppChangePasswordModule] });
38624
38680
  }
@@ -38635,6 +38691,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImpo
38635
38691
  AppTableModule,
38636
38692
  // Sub modules
38637
38693
  AppChangePasswordModule,
38694
+ AppInstallUpgradeCardModule,
38638
38695
  ],
38639
38696
  declarations: [AccountPage, UserTokenTable, NewTokenForm, NewTokenModal],
38640
38697
  exports: [