@sumaris-net/ngx-components 18.23.18 → 18.23.20
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.
- package/doc/changelog.md +7 -0
- package/esm2022/src/app/core/account/account.page.mjs +63 -46
- package/esm2022/src/app/core/services/config/core.config.mjs +7 -1
- package/esm2022/src/app/core/services/model/config.model.mjs +6 -2
- package/esm2022/src/app/core/services/platform.service.mjs +2 -2
- package/esm2022/src/app/shared/image/image.service.mjs +2 -2
- package/fesm2022/sumaris-net.ngx-components.mjs +74 -47
- package/fesm2022/sumaris-net.ngx-components.mjs.map +1 -1
- package/package.json +1 -1
- package/src/app/core/account/account.page.d.ts +4 -1
- package/src/app/core/services/config/core.config.d.ts +1 -0
- package/src/app/core/services/model/config.model.d.ts +1 -0
- package/src/app/shared/image/image.service.d.ts +1 -1
- package/src/assets/i18n/en-US.json +2 -1
- package/src/assets/i18n/en.json +2 -1
- package/src/assets/i18n/fr.json +2 -1
- package/src/assets/manifest.json +1 -1
|
@@ -22050,6 +22050,12 @@ const CORE_CONFIG_OPTIONS = Object.freeze({
|
|
|
22050
22050
|
type: 'boolean',
|
|
22051
22051
|
defaultValue: false
|
|
22052
22052
|
},
|
|
22053
|
+
ACCOUNT_AVATAR_MAX_SIZE: {
|
|
22054
|
+
key: 'sumaris.account.avatar.maxSize',
|
|
22055
|
+
label: 'CONFIGURATION.OPTIONS.ACCOUNT.AVATAR_MAX_SIZE',
|
|
22056
|
+
type: 'integer',
|
|
22057
|
+
defaultValue: 200
|
|
22058
|
+
},
|
|
22053
22059
|
});
|
|
22054
22060
|
|
|
22055
22061
|
class UriUtils {
|
|
@@ -24848,6 +24854,10 @@ let Configuration = class Configuration extends Software {
|
|
|
24848
24854
|
const value = this.getProperty(definition);
|
|
24849
24855
|
return isNotNil(value) ? parseInt(value) : undefined;
|
|
24850
24856
|
}
|
|
24857
|
+
getPropertyAsNumber(definition) {
|
|
24858
|
+
const value = +this.getProperty(definition);
|
|
24859
|
+
return isNotNilOrNaN(value) ? value : undefined;
|
|
24860
|
+
}
|
|
24851
24861
|
getPropertyAsNumbers(definition) {
|
|
24852
24862
|
const value = this.getProperty(definition);
|
|
24853
24863
|
if (typeof value === 'string')
|
|
@@ -25643,7 +25653,7 @@ class PlatformService extends StartableService {
|
|
|
25643
25653
|
const primaryColor = style.getPropertyValue('--ion-color-primary')?.trim();
|
|
25644
25654
|
const backgroundColor = !primaryColor || enable ? '#000000' : primaryColor;
|
|
25645
25655
|
// Configure status bar style
|
|
25646
|
-
const statusBarStyle = isLightColor(backgroundColor) ? Style.
|
|
25656
|
+
const statusBarStyle = isLightColor(backgroundColor) ? Style.Light : Style.Dark;
|
|
25647
25657
|
console.debug(`[platform] StatusBar icons style: ${statusBarStyle} ...`);
|
|
25648
25658
|
try {
|
|
25649
25659
|
await StatusBar.setStyle({ style: statusBarStyle });
|
|
@@ -27600,7 +27610,7 @@ class ImageService extends StartableService {
|
|
|
27600
27610
|
if (style === 'upload-popover') {
|
|
27601
27611
|
return this.addUploadedImages(event, opts);
|
|
27602
27612
|
}
|
|
27603
|
-
console.info(`[image-service] Adding image
|
|
27613
|
+
console.info(`[image-service] Adding image from camera...`, opts);
|
|
27604
27614
|
try {
|
|
27605
27615
|
const data = await Camera.getPhoto({
|
|
27606
27616
|
height: this.defaultMaxHeight,
|
|
@@ -45156,6 +45166,7 @@ class AccountPage extends AppForm {
|
|
|
45156
45166
|
showTechnicalDetails = true;
|
|
45157
45167
|
showApiTokens = false;
|
|
45158
45168
|
showAvatar = false;
|
|
45169
|
+
avatarMaxSize;
|
|
45159
45170
|
constructor(injector, formBuilder, accountService, network, navController, validatorService, configService, cd, imageService, environment, locales, appBaseHref) {
|
|
45160
45171
|
super(injector, validatorService.getFormGroup(accountService.account, { withSettings: true }));
|
|
45161
45172
|
this.formBuilder = formBuilder;
|
|
@@ -45198,12 +45209,17 @@ class AccountPage extends AppForm {
|
|
|
45198
45209
|
setTimeout(() => this.tabGroup.realignInkBar(), 500);
|
|
45199
45210
|
}
|
|
45200
45211
|
async onChangeAvatar(event) {
|
|
45201
|
-
if (!this.showAvatar || event.defaultPrevented)
|
|
45212
|
+
if (!this.showAvatar || event.defaultPrevented || this.disabled)
|
|
45202
45213
|
return;
|
|
45203
45214
|
event.preventDefault();
|
|
45204
45215
|
event.stopPropagation();
|
|
45205
45216
|
try {
|
|
45206
|
-
const images = await this.imageService.add(event, { multiple: false,
|
|
45217
|
+
const images = await this.imageService.add(event, { multiple: false,
|
|
45218
|
+
autoHideDropArea: true,
|
|
45219
|
+
style: 'camera', // Force to use the camera (if Capacitor is enabled)
|
|
45220
|
+
width: this.avatarMaxSize,
|
|
45221
|
+
height: this.avatarMaxSize,
|
|
45222
|
+
});
|
|
45207
45223
|
const img = firstArrayValue(images);
|
|
45208
45224
|
if (!img?.dataUrl)
|
|
45209
45225
|
return;
|
|
@@ -45323,7 +45339,7 @@ class AccountPage extends AppForm {
|
|
|
45323
45339
|
}
|
|
45324
45340
|
async save(event) {
|
|
45325
45341
|
if (this.saving)
|
|
45326
|
-
return false;
|
|
45342
|
+
return false; // Already saving: skip
|
|
45327
45343
|
if (!this.form.valid) {
|
|
45328
45344
|
await AppFormUtils.waitWhilePending(this.form);
|
|
45329
45345
|
if (this.form.invalid) {
|
|
@@ -45333,48 +45349,52 @@ class AccountPage extends AppForm {
|
|
|
45333
45349
|
}
|
|
45334
45350
|
}
|
|
45335
45351
|
this.submitted = true;
|
|
45336
|
-
this.saving = true;
|
|
45337
45352
|
this.error = undefined;
|
|
45338
|
-
|
|
45339
|
-
...this.accountService.account.asObject(),
|
|
45340
|
-
...this.form.value,
|
|
45341
|
-
});
|
|
45342
|
-
if (isNotEmptyArray(this.optionDefinitions) && this.propertiesTable) {
|
|
45343
|
-
// Merge properties
|
|
45344
|
-
await this.propertiesTable.save();
|
|
45345
|
-
const propertyEntities = this.propertiesTable.value || [];
|
|
45346
|
-
const properties = propertyEntities.reduce((res, item) => {
|
|
45347
|
-
res[item.id] = item.value;
|
|
45348
|
-
return res;
|
|
45349
|
-
}, {});
|
|
45350
|
-
const newProperties = { ...newAccount.settings.content['properties'], ...properties };
|
|
45351
|
-
newAccount.settings.merge({ properties: newProperties }, ['properties']);
|
|
45352
|
-
}
|
|
45353
|
-
else if (newAccount.settings.content['properties']) {
|
|
45354
|
-
// Clean properties
|
|
45355
|
-
delete newAccount.settings.content['properties'];
|
|
45356
|
-
}
|
|
45357
|
-
if (this.tokensTable) {
|
|
45358
|
-
await this.tokensTable.save();
|
|
45359
|
-
newAccount.tokens = this.tokensTable.value;
|
|
45360
|
-
}
|
|
45361
|
-
console.debug('[account] Saving account...', newAccount);
|
|
45353
|
+
this.markAsSaving();
|
|
45362
45354
|
try {
|
|
45363
|
-
|
|
45364
|
-
|
|
45365
|
-
|
|
45366
|
-
|
|
45367
|
-
this.
|
|
45368
|
-
|
|
45369
|
-
|
|
45370
|
-
|
|
45371
|
-
|
|
45372
|
-
|
|
45373
|
-
|
|
45355
|
+
const data = Account.fromObject({
|
|
45356
|
+
...this.accountService.account.asObject(),
|
|
45357
|
+
...this.form.value,
|
|
45358
|
+
});
|
|
45359
|
+
if (isNotEmptyArray(this.optionDefinitions) && this.propertiesTable) {
|
|
45360
|
+
// Merge properties
|
|
45361
|
+
await this.propertiesTable.save();
|
|
45362
|
+
const propertyEntities = this.propertiesTable.value || [];
|
|
45363
|
+
const properties = propertyEntities.reduce((res, item) => {
|
|
45364
|
+
res[item.id] = item.value;
|
|
45365
|
+
return res;
|
|
45366
|
+
}, {});
|
|
45367
|
+
const newProperties = { ...data.settings.content['properties'], ...properties };
|
|
45368
|
+
data.settings.merge({ properties: newProperties }, ['properties']);
|
|
45369
|
+
}
|
|
45370
|
+
else if (data.settings.content['properties']) {
|
|
45371
|
+
// Clean properties
|
|
45372
|
+
delete data.settings.content['properties'];
|
|
45373
|
+
}
|
|
45374
|
+
if (this.tokensTable) {
|
|
45375
|
+
await this.tokensTable.save();
|
|
45376
|
+
data.tokens = this.tokensTable.value;
|
|
45377
|
+
}
|
|
45378
|
+
console.debug('[account] Saving account...', data);
|
|
45379
|
+
try {
|
|
45380
|
+
this.disable();
|
|
45381
|
+
await this.accountService.save(data);
|
|
45382
|
+
// Reload
|
|
45383
|
+
setTimeout(() => this.onLogin(this.accountService.account), 100);
|
|
45384
|
+
this.markAsPristine();
|
|
45385
|
+
return true;
|
|
45386
|
+
}
|
|
45387
|
+
catch (err) {
|
|
45388
|
+
console.error(err);
|
|
45389
|
+
this.error = (err && err.message) || err;
|
|
45390
|
+
return false;
|
|
45391
|
+
}
|
|
45392
|
+
finally {
|
|
45393
|
+
this.enable();
|
|
45394
|
+
}
|
|
45374
45395
|
}
|
|
45375
45396
|
finally {
|
|
45376
|
-
this.
|
|
45377
|
-
this.enable();
|
|
45397
|
+
this.markAsSaved();
|
|
45378
45398
|
}
|
|
45379
45399
|
}
|
|
45380
45400
|
enable(opts) {
|
|
@@ -45428,24 +45448,31 @@ class AccountPage extends AppForm {
|
|
|
45428
45448
|
// For compatibility with deprecated key
|
|
45429
45449
|
config.getPropertyAsBoolean(CORE_CONFIG_OPTIONS.DEFAULT_LAT_LONG_FORMAT_ENABLED);
|
|
45430
45450
|
this.showAvatar = config.getPropertyAsBoolean(CORE_CONFIG_OPTIONS.ACCOUNT_AVATAR_ENABLE);
|
|
45451
|
+
this.avatarMaxSize = config.getPropertyAsInt(CORE_CONFIG_OPTIONS.ACCOUNT_AVATAR_MAX_SIZE);
|
|
45431
45452
|
const authTokenType = config.getProperty(CORE_CONFIG_OPTIONS.AUTH_TOKEN_TYPE);
|
|
45432
45453
|
this.canChangePassword = authTokenType === 'token';
|
|
45433
45454
|
this.showSecurityDetails = this.canChangePassword;
|
|
45434
45455
|
this.showApiTokens = config.getPropertyAsBoolean(CORE_CONFIG_OPTIONS.AUTH_API_TOKEN_ENABLED);
|
|
45435
45456
|
this.markForCheck();
|
|
45436
45457
|
}
|
|
45458
|
+
openChangePasswordPage() {
|
|
45459
|
+
return this.navController.navigateForward('account/password');
|
|
45460
|
+
}
|
|
45437
45461
|
markForCheck() {
|
|
45438
45462
|
this.cd.markForCheck();
|
|
45439
45463
|
}
|
|
45440
|
-
|
|
45441
|
-
this.
|
|
45464
|
+
markAsSaving() {
|
|
45465
|
+
this.saving = true;
|
|
45466
|
+
}
|
|
45467
|
+
markAsSaved() {
|
|
45468
|
+
this.saving = false;
|
|
45442
45469
|
}
|
|
45443
45470
|
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: ImageService }, { token: ENVIRONMENT }, { token: APP_LOCALES }, { token: APP_BASE_HREF, optional: true }], target: i0.ɵɵFactoryTarget.Component });
|
|
45444
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: AccountPage, selector: "app-account-page", host: { listeners: { "window:beforeunload": "handleRefresh($event)" } }, viewQueries: [{ propertyName: "tabGroup", first: true, predicate: ["tabGroup"], descendants: true, static: true }, { propertyName: "propertiesTable", first: true, predicate: ["propertiesTable"], descendants: true, static: true }, { propertyName: "tokensTable", first: true, predicate: ["tokensTable"], descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: "<app-toolbar\n [title]=\"'ACCOUNT.TITLE' | translate\"\n color=\"primary\"\n [hasValidate]=\"dirty && !saving\"\n [hasClose]=\"!dirty && !saving\"\n (onValidate)=\"save()\"\n (onClose)=\"close($event)\"\n>\n <ion-buttons slot=\"end\">\n @if (loading || saving) {\n <!-- loader -->\n <ion-spinner></ion-spinner>\n } @else if (network.online) {\n <!-- refresh button (if online) -->\n <ion-button [matTooltip]=\"'COMMON.BTN_REFRESH' | translate\" (click)=\"refresh($event)\">\n <mat-icon slot=\"icon-only\">refresh</mat-icon>\n </ion-button>\n }\n </ion-buttons>\n</app-toolbar>\n\n<ion-content>\n <!-- Install (and upgrade) card -->\n <app-install-upgrade-card [isLogin]=\"isLogin\" [showInstallButton]=\"true\"></app-install-upgrade-card>\n\n <!-- error -->\n @if (mobile && (errorSubject | async); as error) {\n <ion-item lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n }\n\n <form [formGroup]=\"form\" novalidate class=\"form-container\">\n <mat-tab-group\n #tabGroup\n [(selectedIndex)]=\"selectedTabIndex\"\n class=\"mat-mdc-tab-disabled-hidden\"\n [mat-stretch-tabs]=\"mobile\"\n dynamicHeight\n >\n <!-- TAB: user details -->\n <mat-tab [label]=\"'ACCOUNT.USER_DETAILS.TITLE' | translate\" class=\"ion-padding\">\n <ng-template mat-tab-label>\n <mat-icon>\n <ion-icon matPrefix slot=\"start\" name=\"person-circle\"></ion-icon>\n </mat-icon>\n <ion-label>{{ 'ACCOUNT.USER_DETAILS.TITLE' | translate }}</ion-label>\n <ion-icon slot=\"end\" name=\"alert-circle\" color=\"danger\" *ngIf=\"submitted && form.invalid\"></ion-icon>\n </ng-template>\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <!-- left margin -->\n <ion-col size=\"0\" size-lg=\"1\" size-xl=\"2\"> </ion-col>\n\n <ion-col class=\"ion-padding\">\n <!-- Avatar block -->\n @if (showAvatar) {\n @let accountAvatar = form?.controls?.avatar?.value || form?.value?.avatar || accountService?.account?.avatar || defaultAvatarImage;\n @let isDefaultAvatar = accountAvatar?.endsWith(defaultAvatarImage);\n <ion-item lines=\"none\" class=\"ion-margin-bottom\" style=\"--inner-padding-end: 0\">\n <ion-button slot=\"start\" fill=\"clear\"\n class=\"user-avatar\"\n [style.background-image]=\"'url(' + (accountAvatar || defaultAvatarImage) + ')'\"\n (click)=\"onChangeAvatar($event)\"\n [matTooltip]=\"'IMAGE.BTN_CAMERA_SOURCE' | translate\"\n >\n @if (!accountReadOnly) {\n <ion-icon [class.visible-hover]=\"!isDefaultAvatar\" name=\"camera\" slot=\"icon-only\"></ion-icon>\n }\n </ion-button>\n </ion-item>\n }\n\n <ion-list-header><p [innerHTML]=\"'ACCOUNT.USER_DETAILS.DESCRIPTION' | translate\"></p></ion-list-header>\n\n <ion-list [inset]=\"mobile\">\n <!-- Email -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <mat-label>{{ 'USER.EMAIL' | translate }}</mat-label>\n <input matInput formControlName=\"email\" autocomplete=\"section-red email\" />\n <mat-error *ngIf=\"form.controls.email.hasError('required') && form.controls.email.dirty\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"form.controls.email.hasError('email') && form.controls.email.dirty\">\n <span>{{ 'ERROR.FIELD_NOT_VALID_EMAIL' | translate }}</span>\n </mat-error>\n </mat-form-field>\n </ion-item>\n\n @if (email?.notConfirmed) {\n <ion-item lines=\"none\">\n <ion-grid class=\"ion-no-padding ion-padding-bottom\">\n <ion-row>\n <ion-col color=\"danger\">\n <ion-icon slot=\"start\" color=\"danger\" name=\"alert-circle\"></ion-icon>\n <ion-text [innerHTML]=\"'ACCOUNT.EMAIL_NOT_CONFIRMED_LABEL' | translate\"></ion-text>\n </ion-col>\n </ion-row>\n\n <ion-row style=\"height: 51px\">\n <ion-col style=\"text-align: right\">\n <ion-text>\n <small [innerHTML]=\"'ACCOUNT.EMAIL_NOT_RECEIVED_QUESTION' | translate\"></small>\n </ion-text>\n </ion-col>\n <ion-col size=\"auto\">\n @if (!email.sending) {\n <ion-button fill=\"solid\" color=\"secondary\" (click)=\"sendConfirmationEmail($event)\">\n {{ 'ACCOUNT.BTN_RESEND' | translate }}\n </ion-button>\n } @else {\n <ion-spinner class=\"ion-no-padding\"></ion-spinner>\n }\n </ion-col>\n </ion-row>\n\n <ion-row *ngIf=\"email.error && !email.sending\">\n <ion-col>\n <span *ngIf=\"email.error && !email.sending\" [innerHTML]=\"email.error | translate\"></span>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-item>\n }\n <!-- Last name -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <mat-label>{{ 'USER.LAST_NAME' | translate }}</mat-label>\n <input\n matInput\n [appAutofocus]=\"true\"\n [autofocusDelay]=\"500\"\n formControlName=\"lastName\"\n autocomplete=\"section-blue family-name\"\n required\n />\n <mat-error\n *ngIf=\"form.controls.lastName.hasError('required') && form.controls.lastName.dirty\"\n translate\n >\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"form.controls.lastName.hasError('minlength') && form.controls.lastName.dirty\">\n <span>{{ 'ERROR.FIELD_MIN_LENGTH' | translate: { requiredLength: 2 } }}</span>\n </mat-error>\n </mat-form-field>\n </ion-item>\n\n <!-- First name -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <mat-label>{{ 'USER.FIRST_NAME' | translate }}</mat-label>\n <input matInput formControlName=\"firstName\" autocomplete=\"section-blue given-name\" required />\n <mat-error\n *ngIf=\"form.controls.firstName.hasError('required') && form.controls.firstName.dirty\"\n translate\n >\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"form.controls.firstName.hasError('minlength') && form.controls.firstName.dirty\">\n <span>{{ 'ERROR.FIELD_MIN_LENGTH' | translate: { requiredLength: 2 } }}</span>\n </mat-error>\n </mat-form-field>\n </ion-item>\n\n <!-- Additional fields -->\n @for (definition of additionalFields; track definition.key) {\n <ion-item lines=\"none\">\n <app-form-field\n [definition]=\"definition\"\n [formControlName]=\"definition.key\"\n [required]=\"\n (definition.extra && definition.extra.account && definition.extra.account.required) || false\n \"\n ></app-form-field>\n </ion-item>\n }\n </ion-list>\n\n <!-- Security -->\n @if (showSecurityDetails) {\n <ion-list-header><h3 translate>ACCOUNT.SECURITY.TITLE</h3></ion-list-header>\n\n <ion-list [inset]=\"mobile\">\n <ion-item\n [button]=\"mobile\"\n routerDirection=\"forward\"\n lines=\"none\"\n (click)=\"openChangePasswordPage()\"\n [disabled]=\"disabled\"\n >\n @if (!mobile) {\n <ion-icon slot=\"start\" name=\"keypad\"></ion-icon>\n <ion-label>{{ 'ACCOUNT.SECURITY.PASSWORD' | translate }}</ion-label>\n }\n <ion-text color=\"tertiary\">{{ 'ACCOUNT.SECURITY.BTN_CHANGE_PASSWORD' | translate }}</ion-text>\n @if (!mobile) {\n <ion-icon slot=\"end\" color=\"tertiary\" name=\"chevron-forward\"></ion-icon>\n }\n </ion-item>\n </ion-list>\n }\n\n <!-- Technical details -->\n @if (showTechnicalDetails) {\n <ion-list-header><h3 translate>ACCOUNT.USER_DETAILS.TECHNICAL_DIVIDER</h3></ion-list-header>\n\n <ion-list [inset]=\"mobile\">\n <!-- profile -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <mat-label>{{ 'ACCOUNT.USER_DETAILS.PROFILE' | translate }}</mat-label>\n <input matInput hidden readonly=\"true\" formControlName=\"mainProfile\" required />\n <span>{{ 'USER.PROFILE_ENUM.' + form.controls.mainProfile.value | translate }}</span>\n <mat-error\n *ngIf=\"form.controls.firstName.hasError('mainProfile') && form.controls.mainProfile.dirty\"\n translate\n >\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n </ion-item>\n\n <!-- pubkey -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <mat-label>{{ 'ACCOUNT.USER_DETAILS.PUBKEY' | translate }}</mat-label>\n <input matInput readonly=\"true\" formControlName=\"pubkey\" required />\n <mat-error\n *ngIf=\"form.controls.firstName.hasError('pubkey') && form.controls.firstName.dirty\"\n translate\n >\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"form.controls.firstName.hasError('pubkey') && form.controls.firstName.dirty\">\n <span translate>ERROR.FIELD_NOT_VALID_PUBKEY</span>\n </mat-error>\n </mat-form-field>\n </ion-item>\n </ion-list>\n }\n </ion-col>\n\n <!-- right margin -->\n <ion-col size=\"0\" size-lg=\"1\" size-xl=\"2\"> </ion-col>\n </ion-row>\n </ion-grid>\n </mat-tab>\n\n <!-- TAB: settings -->\n <mat-tab [label]=\"'ACCOUNT.SETTINGS.TITLE' | translate\" formGroupName=\"settings\">\n <ng-template mat-tab-label>\n <mat-icon>\n <ion-icon matPrefix slot=\"start\" name=\"settings\"></ion-icon>\n </mat-icon>\n <ion-label translate>ACCOUNT.SETTINGS.TITLE</ion-label>\n </ng-template>\n\n <div class=\"ion-padding\">\n <p [innerHTML]=\"'ACCOUNT.SETTINGS.DESCRIPTION' | translate\"></p>\n\n <mat-form-field>\n <mat-label>{{ 'ACCOUNT.SETTINGS.LOCALE' | translate }}</mat-label>\n <mat-select formControlName=\"locale\" required>\n <mat-option *ngFor=\"let item of locales\" [value]=\"item.key\">\n {{ item.value }}\n </mat-option>\n </mat-select>\n <mat-error\n *ngIf=\"settingsForm?.controls.locale.hasError('required') && settingsForm?.controls.locale.dirty\"\n translate\n >\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n\n <!-- lat/long format-->\n @if (showLatLonFormat) {\n @let control = settingsForm | formGetControl: 'latLongFormat';\n <mat-form-field>\n <mat-label>{{ 'ACCOUNT.SETTINGS.LAT_LONG_FORMAT' | translate }}</mat-label>\n <mat-select [formControl]=\"control\" required>\n @for (item of latLongFormats; track item) {\n <mat-option [value]=\"item\">\n {{ 'COMMON.LAT_LONG.ENUM.' + item | uppercase | translate }}\n </mat-option>\n }\n </mat-select>\n @if (control.dirty && control.hasError('required')) {\n <mat-error translate>ERROR.FIELD_REQUIRED</mat-error>\n }\n </mat-form-field>\n }\n </div>\n\n <!-- Options table -->\n <div class=\"options-table\" [class.cdk-visually-hidden]=\"optionDefinitions | isEmptyArray\">\n <app-properties-table\n #propertiesTable\n i18nColumnPrefix=\"ACCOUNT.SETTINGS.OPTIONS.\"\n [definitions]=\"optionDefinitions\"\n [disabled]=\"disabled\"\n [canEdit]=\"true\"\n [debug]=\"debug\"\n (onDirty)=\"$event ? markAsDirty() : undefined\"\n ></app-properties-table>\n </div>\n </mat-tab>\n\n <!-- TAB: tokens -->\n <mat-tab [label]=\"'ACCOUNT.TOKENS.TITLE' | translate\" formGroupName=\"tokens\" [disabled]=\"!showApiTokens\">\n <ng-template mat-tab-label>\n <mat-icon>\n <ion-icon matPrefix slot=\"start\" name=\"ticket\"></ion-icon>\n </mat-icon>\n <ion-label translate>ACCOUNT.TOKENS.TITLE</ion-label>\n </ng-template>\n\n <div class=\"ion-padding\">\n <p [innerHTML]=\"'ACCOUNT.TOKENS.DESCRIPTION' | translate\"></p>\n </div>\n <!-- Tokens table -->\n <div class=\"tokens-table\">\n <app-user-token-table\n #tokensTable\n [disabled]=\"disabled\"\n [canEdit]=\"true\"\n [debug]=\"debug\"\n (onDirty)=\"$event ? markAsDirty() : undefined\"\n ></app-user-token-table>\n </div>\n </mat-tab>\n </mat-tab-group>\n </form>\n</ion-content>\n\n<ion-footer hidden-xs hidden-sm hidden-mobile>\n <app-form-buttons-bar (onCancel)=\"cancel()\" (onSave)=\"save()\" [disabled]=\"!form.dirty || !valid || saving\">\n <!-- error -->\n <ion-item *ngIf=\"error && !mobile\" lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n </app-form-buttons-bar>\n</ion-footer>\n", styles: ["mat-tab-group .mat-tab-body-content{padding:var(--ion-grid-padding);display:grid}form{width:100%}form mat-form-field{width:100%}.user-avatar{-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;background-size:cover;background-repeat:no-repeat;background-position:center;background-color:var(--ion-color-secondary);overflow:hidden!important;font-size:var(--avatar-size, 96px)!important;line-height:var(--avatar-size, 96px);height:var(--avatar-size, 96px)!important;width:var(--avatar-size, 96px)!important;border:solid 1px rgba(var(--ion-color-secondary-rgb),.5);border-radius:50%;display:inline-block}.user-avatar ion-icon{position:fixed;left:0;bottom:16px;width:var(--avatar-size, 96px)}.user-avatar .visible-hover{display:none;visibility:hidden}.user-avatar:hover{background-color:rgba(var(--ion-color-secondary-rgb),.8);border:solid 2px var(--ion-color-secondary-shade);cursor:pointer}.user-avatar:hover .visible-hover{display:inline;visibility:visible}div.options-table{height:calc(100vh - 380px)}div.tokens-table{height:calc(100vh - 250px)}\n"], dependencies: [{ kind: "directive", type: i3$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: i2$1.IonListHeader, selector: "ion-list-header", inputs: ["color", "lines", "mode"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "component", type: i2$1.IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: i2$1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$3.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1$3.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i3.MatPrefix, selector: "[matPrefix], [matIconPrefix], [matTextPrefix]", inputs: ["matTextPrefix"] }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i6$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i6$3.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i2.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "directive", type: i8.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i8.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass"], exportAs: ["matTab"] }, { kind: "component", type: i8.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "directive", type: 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", "id", "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", "panelClass", "panelWidth"], outputs: ["keyup.enter"] }, { kind: "component", type: FormButtonsBarComponent, selector: "app-form-buttons-bar", inputs: ["disabled", "disabledCancel", "disabledEscape", "classList", "saveButtonColor", "backText", "cancelText", "nextText", "showBack", "showCancel", "showNext", "showSave"], outputs: ["onCancel", "onSave", "onNext", "onBack", "onSaveAndClose", "onSaveAndNext"] }, { kind: "component", type: AppPropertiesTable, selector: "app-properties-table", inputs: ["definitions", "showToolbar"] }, { kind: "component", type: AppInstallUpgradeCard, selector: "app-install-upgrade-card", inputs: ["isLogin", "showUpgradeWarning", "showOfflineWarning", "showInstallButton", "debug"] }, { kind: "component", type: UserTokenTable, selector: "app-user-token-table", inputs: ["useSticky"] }, { kind: "pipe", type: i3$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.UpperCasePipe, name: "uppercase" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }, { kind: "pipe", type: EmptyArrayPipe, name: "isEmptyArray" }, { kind: "pipe", type: FormGetControlPipe, name: "formGetControl" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
45471
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: AccountPage, selector: "app-account-page", host: { listeners: { "window:beforeunload": "handleRefresh($event)" } }, viewQueries: [{ propertyName: "tabGroup", first: true, predicate: ["tabGroup"], descendants: true, static: true }, { propertyName: "propertiesTable", first: true, predicate: ["propertiesTable"], descendants: true, static: true }, { propertyName: "tokensTable", first: true, predicate: ["tokensTable"], descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: "<app-toolbar\n [title]=\"'ACCOUNT.TITLE' | translate\"\n color=\"primary\"\n [hasValidate]=\"dirty && !saving\"\n [hasClose]=\"!dirty && !saving\"\n (onValidate)=\"save()\"\n (onClose)=\"close($event)\"\n>\n <ion-buttons slot=\"end\">\n @if (loading || saving) {\n <!-- loader -->\n <ion-spinner></ion-spinner>\n } @else if (network.online) {\n <!-- refresh button (if online) -->\n <ion-button [matTooltip]=\"'COMMON.BTN_REFRESH' | translate\" (click)=\"refresh($event)\">\n <mat-icon slot=\"icon-only\">refresh</mat-icon>\n </ion-button>\n }\n </ion-buttons>\n</app-toolbar>\n\n<ion-content>\n <!-- Install (and upgrade) card -->\n <app-install-upgrade-card [isLogin]=\"isLogin\" [showInstallButton]=\"true\"></app-install-upgrade-card>\n\n <!-- error -->\n @if (mobile && (errorSubject | async); as error) {\n <ion-item lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n }\n\n <form [formGroup]=\"form\" novalidate class=\"form-container\">\n <mat-tab-group\n #tabGroup\n [(selectedIndex)]=\"selectedTabIndex\"\n class=\"mat-mdc-tab-disabled-hidden\"\n [mat-stretch-tabs]=\"mobile\"\n dynamicHeight\n >\n <!-- TAB: user details -->\n <mat-tab [label]=\"'ACCOUNT.USER_DETAILS.TITLE' | translate\" class=\"ion-padding\">\n <ng-template mat-tab-label>\n <mat-icon>\n <ion-icon matPrefix slot=\"start\" name=\"person-circle\"></ion-icon>\n </mat-icon>\n <ion-label>{{ 'ACCOUNT.USER_DETAILS.TITLE' | translate }}</ion-label>\n <ion-icon slot=\"end\" name=\"alert-circle\" color=\"danger\" *ngIf=\"submitted && form.invalid\"></ion-icon>\n </ng-template>\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <!-- left margin -->\n <ion-col size=\"0\" size-lg=\"1\" size-xl=\"2\"> </ion-col>\n\n <ion-col class=\"ion-padding\">\n <!-- Avatar block -->\n @if (showAvatar) {\n @let accountAvatar = form?.controls?.avatar?.value || form?.value?.avatar || accountService?.account?.avatar || defaultAvatarImage;\n @let isDefaultAvatar = accountAvatar?.endsWith(defaultAvatarImage);\n <ion-item lines=\"none\" class=\"ion-margin-bottom\" style=\"--inner-padding-end: 0\">\n <ion-button slot=\"start\" fill=\"clear\"\n class=\"user-avatar\"\n [style.background-image]=\"'url(' + (accountAvatar || defaultAvatarImage) + ')'\"\n (click)=\"onChangeAvatar($event)\"\n [disabled]=\"disabled\"\n [matTooltip]=\"'IMAGE.BTN_CAMERA_SOURCE' | translate\"\n >\n @if (!accountReadOnly && enabled) {\n <ion-icon [class.visible-hover]=\"!isDefaultAvatar\" name=\"camera\" slot=\"icon-only\"></ion-icon>\n }\n </ion-button>\n </ion-item>\n }\n\n <ion-list-header><p [innerHTML]=\"'ACCOUNT.USER_DETAILS.DESCRIPTION' | translate\"></p></ion-list-header>\n\n <ion-list [inset]=\"mobile\">\n <!-- Email -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <mat-label>{{ 'USER.EMAIL' | translate }}</mat-label>\n <input matInput formControlName=\"email\" autocomplete=\"section-red email\" />\n <mat-error *ngIf=\"form.controls.email.hasError('required') && form.controls.email.dirty\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"form.controls.email.hasError('email') && form.controls.email.dirty\">\n <span>{{ 'ERROR.FIELD_NOT_VALID_EMAIL' | translate }}</span>\n </mat-error>\n </mat-form-field>\n </ion-item>\n\n @if (email?.notConfirmed) {\n <ion-item lines=\"none\">\n <ion-grid class=\"ion-no-padding ion-padding-bottom\">\n <ion-row>\n <ion-col color=\"danger\">\n <ion-icon slot=\"start\" color=\"danger\" name=\"alert-circle\"></ion-icon>\n <ion-text [innerHTML]=\"'ACCOUNT.EMAIL_NOT_CONFIRMED_LABEL' | translate\"></ion-text>\n </ion-col>\n </ion-row>\n\n <ion-row style=\"height: 51px\">\n <ion-col style=\"text-align: right\">\n <ion-text>\n <small [innerHTML]=\"'ACCOUNT.EMAIL_NOT_RECEIVED_QUESTION' | translate\"></small>\n </ion-text>\n </ion-col>\n <ion-col size=\"auto\">\n @if (!email.sending) {\n <ion-button fill=\"solid\" color=\"secondary\" (click)=\"sendConfirmationEmail($event)\">\n {{ 'ACCOUNT.BTN_RESEND' | translate }}\n </ion-button>\n } @else {\n <ion-spinner class=\"ion-no-padding\"></ion-spinner>\n }\n </ion-col>\n </ion-row>\n\n <ion-row *ngIf=\"email.error && !email.sending\">\n <ion-col>\n <span *ngIf=\"email.error && !email.sending\" [innerHTML]=\"email.error | translate\"></span>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-item>\n }\n <!-- Last name -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <mat-label>{{ 'USER.LAST_NAME' | translate }}</mat-label>\n <input\n matInput\n [appAutofocus]=\"true\"\n [autofocusDelay]=\"500\"\n formControlName=\"lastName\"\n autocomplete=\"section-blue family-name\"\n required\n />\n <mat-error\n *ngIf=\"form.controls.lastName.hasError('required') && form.controls.lastName.dirty\"\n translate\n >\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"form.controls.lastName.hasError('minlength') && form.controls.lastName.dirty\">\n <span>{{ 'ERROR.FIELD_MIN_LENGTH' | translate: { requiredLength: 2 } }}</span>\n </mat-error>\n </mat-form-field>\n </ion-item>\n\n <!-- First name -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <mat-label>{{ 'USER.FIRST_NAME' | translate }}</mat-label>\n <input matInput formControlName=\"firstName\" autocomplete=\"section-blue given-name\" required />\n <mat-error\n *ngIf=\"form.controls.firstName.hasError('required') && form.controls.firstName.dirty\"\n translate\n >\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"form.controls.firstName.hasError('minlength') && form.controls.firstName.dirty\">\n <span>{{ 'ERROR.FIELD_MIN_LENGTH' | translate: { requiredLength: 2 } }}</span>\n </mat-error>\n </mat-form-field>\n </ion-item>\n\n <!-- Additional fields -->\n @for (definition of additionalFields; track definition.key) {\n <ion-item lines=\"none\">\n <app-form-field\n [definition]=\"definition\"\n [formControlName]=\"definition.key\"\n [required]=\"\n (definition.extra && definition.extra.account && definition.extra.account.required) || false\n \"\n ></app-form-field>\n </ion-item>\n }\n </ion-list>\n\n <!-- Security -->\n @if (showSecurityDetails) {\n <ion-list-header><h3 translate>ACCOUNT.SECURITY.TITLE</h3></ion-list-header>\n\n <ion-list [inset]=\"mobile\">\n <ion-item\n [button]=\"mobile\"\n routerDirection=\"forward\"\n lines=\"none\"\n (click)=\"openChangePasswordPage()\"\n [disabled]=\"disabled\"\n >\n @if (!mobile) {\n <ion-icon slot=\"start\" name=\"keypad\"></ion-icon>\n <ion-label>{{ 'ACCOUNT.SECURITY.PASSWORD' | translate }}</ion-label>\n }\n <ion-text color=\"tertiary\">{{ 'ACCOUNT.SECURITY.BTN_CHANGE_PASSWORD' | translate }}</ion-text>\n @if (!mobile) {\n <ion-icon slot=\"end\" color=\"tertiary\" name=\"chevron-forward\"></ion-icon>\n }\n </ion-item>\n </ion-list>\n }\n\n <!-- Technical details -->\n @if (showTechnicalDetails) {\n <ion-list-header><h3 translate>ACCOUNT.USER_DETAILS.TECHNICAL_DIVIDER</h3></ion-list-header>\n\n <ion-list [inset]=\"mobile\">\n <!-- profile -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <mat-label>{{ 'ACCOUNT.USER_DETAILS.PROFILE' | translate }}</mat-label>\n <input matInput hidden readonly=\"true\" formControlName=\"mainProfile\" required />\n <span>{{ 'USER.PROFILE_ENUM.' + form.controls.mainProfile.value | translate }}</span>\n <mat-error\n *ngIf=\"form.controls.firstName.hasError('mainProfile') && form.controls.mainProfile.dirty\"\n translate\n >\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n </ion-item>\n\n <!-- pubkey -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <mat-label>{{ 'ACCOUNT.USER_DETAILS.PUBKEY' | translate }}</mat-label>\n <input matInput readonly=\"true\" formControlName=\"pubkey\" required />\n <mat-error\n *ngIf=\"form.controls.firstName.hasError('pubkey') && form.controls.firstName.dirty\"\n translate\n >\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"form.controls.firstName.hasError('pubkey') && form.controls.firstName.dirty\">\n <span translate>ERROR.FIELD_NOT_VALID_PUBKEY</span>\n </mat-error>\n </mat-form-field>\n </ion-item>\n </ion-list>\n }\n </ion-col>\n\n <!-- right margin -->\n <ion-col size=\"0\" size-lg=\"1\" size-xl=\"2\"> </ion-col>\n </ion-row>\n </ion-grid>\n </mat-tab>\n\n <!-- TAB: settings -->\n <mat-tab [label]=\"'ACCOUNT.SETTINGS.TITLE' | translate\" formGroupName=\"settings\">\n <ng-template mat-tab-label>\n <mat-icon>\n <ion-icon matPrefix slot=\"start\" name=\"settings\"></ion-icon>\n </mat-icon>\n <ion-label translate>ACCOUNT.SETTINGS.TITLE</ion-label>\n </ng-template>\n\n <div class=\"ion-padding\">\n <p [innerHTML]=\"'ACCOUNT.SETTINGS.DESCRIPTION' | translate\"></p>\n\n <mat-form-field>\n <mat-label>{{ 'ACCOUNT.SETTINGS.LOCALE' | translate }}</mat-label>\n <mat-select formControlName=\"locale\" required>\n <mat-option *ngFor=\"let item of locales\" [value]=\"item.key\">\n {{ item.value }}\n </mat-option>\n </mat-select>\n <mat-error\n *ngIf=\"settingsForm?.controls.locale.hasError('required') && settingsForm?.controls.locale.dirty\"\n translate\n >\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n\n <!-- lat/long format-->\n @if (showLatLonFormat) {\n @let control = settingsForm | formGetControl: 'latLongFormat';\n <mat-form-field>\n <mat-label>{{ 'ACCOUNT.SETTINGS.LAT_LONG_FORMAT' | translate }}</mat-label>\n <mat-select [formControl]=\"control\" required>\n @for (item of latLongFormats; track item) {\n <mat-option [value]=\"item\">\n {{ 'COMMON.LAT_LONG.ENUM.' + item | uppercase | translate }}\n </mat-option>\n }\n </mat-select>\n @if (control.dirty && control.hasError('required')) {\n <mat-error translate>ERROR.FIELD_REQUIRED</mat-error>\n }\n </mat-form-field>\n }\n </div>\n\n <!-- Options table -->\n <div class=\"options-table\" [class.cdk-visually-hidden]=\"optionDefinitions | isEmptyArray\">\n <app-properties-table\n #propertiesTable\n i18nColumnPrefix=\"ACCOUNT.SETTINGS.OPTIONS.\"\n [definitions]=\"optionDefinitions\"\n [disabled]=\"disabled\"\n [canEdit]=\"true\"\n [debug]=\"debug\"\n (onDirty)=\"$event ? markAsDirty() : undefined\"\n ></app-properties-table>\n </div>\n </mat-tab>\n\n <!-- TAB: tokens -->\n <mat-tab [label]=\"'ACCOUNT.TOKENS.TITLE' | translate\" formGroupName=\"tokens\" [disabled]=\"!showApiTokens\">\n <ng-template mat-tab-label>\n <mat-icon>\n <ion-icon matPrefix slot=\"start\" name=\"ticket\"></ion-icon>\n </mat-icon>\n <ion-label translate>ACCOUNT.TOKENS.TITLE</ion-label>\n </ng-template>\n\n <div class=\"ion-padding\">\n <p [innerHTML]=\"'ACCOUNT.TOKENS.DESCRIPTION' | translate\"></p>\n </div>\n <!-- Tokens table -->\n <div class=\"tokens-table\">\n <app-user-token-table\n #tokensTable\n [disabled]=\"disabled\"\n [canEdit]=\"true\"\n [debug]=\"debug\"\n (onDirty)=\"$event ? markAsDirty() : undefined\"\n ></app-user-token-table>\n </div>\n </mat-tab>\n </mat-tab-group>\n </form>\n</ion-content>\n\n<ion-footer hidden-xs hidden-sm hidden-mobile>\n <app-form-buttons-bar (onCancel)=\"cancel()\" (onSave)=\"save()\" [disabled]=\"!form.dirty || !valid || saving\">\n <!-- error -->\n <ion-item *ngIf=\"error && !mobile\" lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n </app-form-buttons-bar>\n</ion-footer>\n", styles: ["mat-tab-group .mat-tab-body-content{padding:var(--ion-grid-padding);display:grid}form{width:100%}form mat-form-field{width:100%}.user-avatar{-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;background-size:cover;background-repeat:no-repeat;background-position:center;background-color:var(--ion-color-secondary);overflow:hidden!important;font-size:var(--avatar-size, 96px)!important;line-height:var(--avatar-size, 96px);height:var(--avatar-size, 96px)!important;width:var(--avatar-size, 96px)!important;border:solid 1px rgba(var(--ion-color-secondary-rgb),.5);border-radius:50%;display:inline-block}.user-avatar ion-icon{position:fixed;left:0;bottom:16px;width:var(--avatar-size, 96px)}.user-avatar .visible-hover{display:none;visibility:hidden}.user-avatar:hover{background-color:rgba(var(--ion-color-secondary-rgb),.8);border:solid 2px var(--ion-color-secondary-shade);cursor:pointer}.user-avatar:hover .visible-hover{display:inline;visibility:visible}div.options-table{height:calc(100vh - 380px)}div.tokens-table{height:calc(100vh - 250px)}\n"], dependencies: [{ kind: "directive", type: i3$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: i2$1.IonListHeader, selector: "ion-list-header", inputs: ["color", "lines", "mode"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "component", type: i2$1.IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: i2$1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$3.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1$3.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i3.MatPrefix, selector: "[matPrefix], [matIconPrefix], [matTextPrefix]", inputs: ["matTextPrefix"] }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i6$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i6$3.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i2.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "directive", type: i8.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i8.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass"], exportAs: ["matTab"] }, { kind: "component", type: i8.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "directive", type: 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", "id", "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", "panelClass", "panelWidth"], outputs: ["keyup.enter"] }, { kind: "component", type: FormButtonsBarComponent, selector: "app-form-buttons-bar", inputs: ["disabled", "disabledCancel", "disabledEscape", "classList", "saveButtonColor", "backText", "cancelText", "nextText", "showBack", "showCancel", "showNext", "showSave"], outputs: ["onCancel", "onSave", "onNext", "onBack", "onSaveAndClose", "onSaveAndNext"] }, { kind: "component", type: AppPropertiesTable, selector: "app-properties-table", inputs: ["definitions", "showToolbar"] }, { kind: "component", type: AppInstallUpgradeCard, selector: "app-install-upgrade-card", inputs: ["isLogin", "showUpgradeWarning", "showOfflineWarning", "showInstallButton", "debug"] }, { kind: "component", type: UserTokenTable, selector: "app-user-token-table", inputs: ["useSticky"] }, { kind: "pipe", type: i3$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.UpperCasePipe, name: "uppercase" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }, { kind: "pipe", type: EmptyArrayPipe, name: "isEmptyArray" }, { kind: "pipe", type: FormGetControlPipe, name: "formGetControl" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
45445
45472
|
}
|
|
45446
45473
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AccountPage, decorators: [{
|
|
45447
45474
|
type: Component,
|
|
45448
|
-
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\"> </ion-col>\n\n <ion-col class=\"ion-padding\">\n <!-- Avatar block -->\n @if (showAvatar) {\n @let accountAvatar = form?.controls?.avatar?.value || form?.value?.avatar || accountService?.account?.avatar || defaultAvatarImage;\n @let isDefaultAvatar = accountAvatar?.endsWith(defaultAvatarImage);\n <ion-item lines=\"none\" class=\"ion-margin-bottom\" style=\"--inner-padding-end: 0\">\n <ion-button slot=\"start\" fill=\"clear\"\n class=\"user-avatar\"\n [style.background-image]=\"'url(' + (accountAvatar || defaultAvatarImage) + ')'\"\n (click)=\"onChangeAvatar($event)\"\n [matTooltip]=\"'IMAGE.BTN_CAMERA_SOURCE' | translate\"\n >\n @if (!accountReadOnly) {\n <ion-icon [class.visible-hover]=\"!isDefaultAvatar\" name=\"camera\" slot=\"icon-only\"></ion-icon>\n }\n </ion-button>\n </ion-item>\n }\n\n <ion-list-header><p [innerHTML]=\"'ACCOUNT.USER_DETAILS.DESCRIPTION' | translate\"></p></ion-list-header>\n\n <ion-list [inset]=\"mobile\">\n <!-- Email -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <mat-label>{{ 'USER.EMAIL' | translate }}</mat-label>\n <input matInput formControlName=\"email\" autocomplete=\"section-red email\" />\n <mat-error *ngIf=\"form.controls.email.hasError('required') && form.controls.email.dirty\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"form.controls.email.hasError('email') && form.controls.email.dirty\">\n <span>{{ 'ERROR.FIELD_NOT_VALID_EMAIL' | translate }}</span>\n </mat-error>\n </mat-form-field>\n </ion-item>\n\n @if (email?.notConfirmed) {\n <ion-item lines=\"none\">\n <ion-grid class=\"ion-no-padding ion-padding-bottom\">\n <ion-row>\n <ion-col color=\"danger\">\n <ion-icon slot=\"start\" color=\"danger\" name=\"alert-circle\"></ion-icon>\n <ion-text [innerHTML]=\"'ACCOUNT.EMAIL_NOT_CONFIRMED_LABEL' | translate\"></ion-text>\n </ion-col>\n </ion-row>\n\n <ion-row style=\"height: 51px\">\n <ion-col style=\"text-align: right\">\n <ion-text>\n <small [innerHTML]=\"'ACCOUNT.EMAIL_NOT_RECEIVED_QUESTION' | translate\"></small>\n </ion-text>\n </ion-col>\n <ion-col size=\"auto\">\n @if (!email.sending) {\n <ion-button fill=\"solid\" color=\"secondary\" (click)=\"sendConfirmationEmail($event)\">\n {{ 'ACCOUNT.BTN_RESEND' | translate }}\n </ion-button>\n } @else {\n <ion-spinner class=\"ion-no-padding\"></ion-spinner>\n }\n </ion-col>\n </ion-row>\n\n <ion-row *ngIf=\"email.error && !email.sending\">\n <ion-col>\n <span *ngIf=\"email.error && !email.sending\" [innerHTML]=\"email.error | translate\"></span>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-item>\n }\n <!-- Last name -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <mat-label>{{ 'USER.LAST_NAME' | translate }}</mat-label>\n <input\n matInput\n [appAutofocus]=\"true\"\n [autofocusDelay]=\"500\"\n formControlName=\"lastName\"\n autocomplete=\"section-blue family-name\"\n required\n />\n <mat-error\n *ngIf=\"form.controls.lastName.hasError('required') && form.controls.lastName.dirty\"\n translate\n >\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"form.controls.lastName.hasError('minlength') && form.controls.lastName.dirty\">\n <span>{{ 'ERROR.FIELD_MIN_LENGTH' | translate: { requiredLength: 2 } }}</span>\n </mat-error>\n </mat-form-field>\n </ion-item>\n\n <!-- First name -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <mat-label>{{ 'USER.FIRST_NAME' | translate }}</mat-label>\n <input matInput formControlName=\"firstName\" autocomplete=\"section-blue given-name\" required />\n <mat-error\n *ngIf=\"form.controls.firstName.hasError('required') && form.controls.firstName.dirty\"\n translate\n >\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"form.controls.firstName.hasError('minlength') && form.controls.firstName.dirty\">\n <span>{{ 'ERROR.FIELD_MIN_LENGTH' | translate: { requiredLength: 2 } }}</span>\n </mat-error>\n </mat-form-field>\n </ion-item>\n\n <!-- Additional fields -->\n @for (definition of additionalFields; track definition.key) {\n <ion-item lines=\"none\">\n <app-form-field\n [definition]=\"definition\"\n [formControlName]=\"definition.key\"\n [required]=\"\n (definition.extra && definition.extra.account && definition.extra.account.required) || false\n \"\n ></app-form-field>\n </ion-item>\n }\n </ion-list>\n\n <!-- Security -->\n @if (showSecurityDetails) {\n <ion-list-header><h3 translate>ACCOUNT.SECURITY.TITLE</h3></ion-list-header>\n\n <ion-list [inset]=\"mobile\">\n <ion-item\n [button]=\"mobile\"\n routerDirection=\"forward\"\n lines=\"none\"\n (click)=\"openChangePasswordPage()\"\n [disabled]=\"disabled\"\n >\n @if (!mobile) {\n <ion-icon slot=\"start\" name=\"keypad\"></ion-icon>\n <ion-label>{{ 'ACCOUNT.SECURITY.PASSWORD' | translate }}</ion-label>\n }\n <ion-text color=\"tertiary\">{{ 'ACCOUNT.SECURITY.BTN_CHANGE_PASSWORD' | translate }}</ion-text>\n @if (!mobile) {\n <ion-icon slot=\"end\" color=\"tertiary\" name=\"chevron-forward\"></ion-icon>\n }\n </ion-item>\n </ion-list>\n }\n\n <!-- Technical details -->\n @if (showTechnicalDetails) {\n <ion-list-header><h3 translate>ACCOUNT.USER_DETAILS.TECHNICAL_DIVIDER</h3></ion-list-header>\n\n <ion-list [inset]=\"mobile\">\n <!-- profile -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <mat-label>{{ 'ACCOUNT.USER_DETAILS.PROFILE' | translate }}</mat-label>\n <input matInput hidden readonly=\"true\" formControlName=\"mainProfile\" required />\n <span>{{ 'USER.PROFILE_ENUM.' + form.controls.mainProfile.value | translate }}</span>\n <mat-error\n *ngIf=\"form.controls.firstName.hasError('mainProfile') && form.controls.mainProfile.dirty\"\n translate\n >\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n </ion-item>\n\n <!-- pubkey -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <mat-label>{{ 'ACCOUNT.USER_DETAILS.PUBKEY' | translate }}</mat-label>\n <input matInput readonly=\"true\" formControlName=\"pubkey\" required />\n <mat-error\n *ngIf=\"form.controls.firstName.hasError('pubkey') && form.controls.firstName.dirty\"\n translate\n >\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"form.controls.firstName.hasError('pubkey') && form.controls.firstName.dirty\">\n <span translate>ERROR.FIELD_NOT_VALID_PUBKEY</span>\n </mat-error>\n </mat-form-field>\n </ion-item>\n </ion-list>\n }\n </ion-col>\n\n <!-- right margin -->\n <ion-col size=\"0\" size-lg=\"1\" size-xl=\"2\"> </ion-col>\n </ion-row>\n </ion-grid>\n </mat-tab>\n\n <!-- TAB: settings -->\n <mat-tab [label]=\"'ACCOUNT.SETTINGS.TITLE' | translate\" formGroupName=\"settings\">\n <ng-template mat-tab-label>\n <mat-icon>\n <ion-icon matPrefix slot=\"start\" name=\"settings\"></ion-icon>\n </mat-icon>\n <ion-label translate>ACCOUNT.SETTINGS.TITLE</ion-label>\n </ng-template>\n\n <div class=\"ion-padding\">\n <p [innerHTML]=\"'ACCOUNT.SETTINGS.DESCRIPTION' | translate\"></p>\n\n <mat-form-field>\n <mat-label>{{ 'ACCOUNT.SETTINGS.LOCALE' | translate }}</mat-label>\n <mat-select formControlName=\"locale\" required>\n <mat-option *ngFor=\"let item of locales\" [value]=\"item.key\">\n {{ item.value }}\n </mat-option>\n </mat-select>\n <mat-error\n *ngIf=\"settingsForm?.controls.locale.hasError('required') && settingsForm?.controls.locale.dirty\"\n translate\n >\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n\n <!-- lat/long format-->\n @if (showLatLonFormat) {\n @let control = settingsForm | formGetControl: 'latLongFormat';\n <mat-form-field>\n <mat-label>{{ 'ACCOUNT.SETTINGS.LAT_LONG_FORMAT' | translate }}</mat-label>\n <mat-select [formControl]=\"control\" required>\n @for (item of latLongFormats; track item) {\n <mat-option [value]=\"item\">\n {{ 'COMMON.LAT_LONG.ENUM.' + item | uppercase | translate }}\n </mat-option>\n }\n </mat-select>\n @if (control.dirty && control.hasError('required')) {\n <mat-error translate>ERROR.FIELD_REQUIRED</mat-error>\n }\n </mat-form-field>\n }\n </div>\n\n <!-- Options table -->\n <div class=\"options-table\" [class.cdk-visually-hidden]=\"optionDefinitions | isEmptyArray\">\n <app-properties-table\n #propertiesTable\n i18nColumnPrefix=\"ACCOUNT.SETTINGS.OPTIONS.\"\n [definitions]=\"optionDefinitions\"\n [disabled]=\"disabled\"\n [canEdit]=\"true\"\n [debug]=\"debug\"\n (onDirty)=\"$event ? markAsDirty() : undefined\"\n ></app-properties-table>\n </div>\n </mat-tab>\n\n <!-- TAB: tokens -->\n <mat-tab [label]=\"'ACCOUNT.TOKENS.TITLE' | translate\" formGroupName=\"tokens\" [disabled]=\"!showApiTokens\">\n <ng-template mat-tab-label>\n <mat-icon>\n <ion-icon matPrefix slot=\"start\" name=\"ticket\"></ion-icon>\n </mat-icon>\n <ion-label translate>ACCOUNT.TOKENS.TITLE</ion-label>\n </ng-template>\n\n <div class=\"ion-padding\">\n <p [innerHTML]=\"'ACCOUNT.TOKENS.DESCRIPTION' | translate\"></p>\n </div>\n <!-- Tokens table -->\n <div class=\"tokens-table\">\n <app-user-token-table\n #tokensTable\n [disabled]=\"disabled\"\n [canEdit]=\"true\"\n [debug]=\"debug\"\n (onDirty)=\"$event ? markAsDirty() : undefined\"\n ></app-user-token-table>\n </div>\n </mat-tab>\n </mat-tab-group>\n </form>\n</ion-content>\n\n<ion-footer hidden-xs hidden-sm hidden-mobile>\n <app-form-buttons-bar (onCancel)=\"cancel()\" (onSave)=\"save()\" [disabled]=\"!form.dirty || !valid || saving\">\n <!-- error -->\n <ion-item *ngIf=\"error && !mobile\" lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n </app-form-buttons-bar>\n</ion-footer>\n", styles: ["mat-tab-group .mat-tab-body-content{padding:var(--ion-grid-padding);display:grid}form{width:100%}form mat-form-field{width:100%}.user-avatar{-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;background-size:cover;background-repeat:no-repeat;background-position:center;background-color:var(--ion-color-secondary);overflow:hidden!important;font-size:var(--avatar-size, 96px)!important;line-height:var(--avatar-size, 96px);height:var(--avatar-size, 96px)!important;width:var(--avatar-size, 96px)!important;border:solid 1px rgba(var(--ion-color-secondary-rgb),.5);border-radius:50%;display:inline-block}.user-avatar ion-icon{position:fixed;left:0;bottom:16px;width:var(--avatar-size, 96px)}.user-avatar .visible-hover{display:none;visibility:hidden}.user-avatar:hover{background-color:rgba(var(--ion-color-secondary-rgb),.8);border:solid 2px var(--ion-color-secondary-shade);cursor:pointer}.user-avatar:hover .visible-hover{display:inline;visibility:visible}div.options-table{height:calc(100vh - 380px)}div.tokens-table{height:calc(100vh - 250px)}\n"] }]
|
|
45475
|
+
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\"> </ion-col>\n\n <ion-col class=\"ion-padding\">\n <!-- Avatar block -->\n @if (showAvatar) {\n @let accountAvatar = form?.controls?.avatar?.value || form?.value?.avatar || accountService?.account?.avatar || defaultAvatarImage;\n @let isDefaultAvatar = accountAvatar?.endsWith(defaultAvatarImage);\n <ion-item lines=\"none\" class=\"ion-margin-bottom\" style=\"--inner-padding-end: 0\">\n <ion-button slot=\"start\" fill=\"clear\"\n class=\"user-avatar\"\n [style.background-image]=\"'url(' + (accountAvatar || defaultAvatarImage) + ')'\"\n (click)=\"onChangeAvatar($event)\"\n [disabled]=\"disabled\"\n [matTooltip]=\"'IMAGE.BTN_CAMERA_SOURCE' | translate\"\n >\n @if (!accountReadOnly && enabled) {\n <ion-icon [class.visible-hover]=\"!isDefaultAvatar\" name=\"camera\" slot=\"icon-only\"></ion-icon>\n }\n </ion-button>\n </ion-item>\n }\n\n <ion-list-header><p [innerHTML]=\"'ACCOUNT.USER_DETAILS.DESCRIPTION' | translate\"></p></ion-list-header>\n\n <ion-list [inset]=\"mobile\">\n <!-- Email -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <mat-label>{{ 'USER.EMAIL' | translate }}</mat-label>\n <input matInput formControlName=\"email\" autocomplete=\"section-red email\" />\n <mat-error *ngIf=\"form.controls.email.hasError('required') && form.controls.email.dirty\" translate>\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"form.controls.email.hasError('email') && form.controls.email.dirty\">\n <span>{{ 'ERROR.FIELD_NOT_VALID_EMAIL' | translate }}</span>\n </mat-error>\n </mat-form-field>\n </ion-item>\n\n @if (email?.notConfirmed) {\n <ion-item lines=\"none\">\n <ion-grid class=\"ion-no-padding ion-padding-bottom\">\n <ion-row>\n <ion-col color=\"danger\">\n <ion-icon slot=\"start\" color=\"danger\" name=\"alert-circle\"></ion-icon>\n <ion-text [innerHTML]=\"'ACCOUNT.EMAIL_NOT_CONFIRMED_LABEL' | translate\"></ion-text>\n </ion-col>\n </ion-row>\n\n <ion-row style=\"height: 51px\">\n <ion-col style=\"text-align: right\">\n <ion-text>\n <small [innerHTML]=\"'ACCOUNT.EMAIL_NOT_RECEIVED_QUESTION' | translate\"></small>\n </ion-text>\n </ion-col>\n <ion-col size=\"auto\">\n @if (!email.sending) {\n <ion-button fill=\"solid\" color=\"secondary\" (click)=\"sendConfirmationEmail($event)\">\n {{ 'ACCOUNT.BTN_RESEND' | translate }}\n </ion-button>\n } @else {\n <ion-spinner class=\"ion-no-padding\"></ion-spinner>\n }\n </ion-col>\n </ion-row>\n\n <ion-row *ngIf=\"email.error && !email.sending\">\n <ion-col>\n <span *ngIf=\"email.error && !email.sending\" [innerHTML]=\"email.error | translate\"></span>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-item>\n }\n <!-- Last name -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <mat-label>{{ 'USER.LAST_NAME' | translate }}</mat-label>\n <input\n matInput\n [appAutofocus]=\"true\"\n [autofocusDelay]=\"500\"\n formControlName=\"lastName\"\n autocomplete=\"section-blue family-name\"\n required\n />\n <mat-error\n *ngIf=\"form.controls.lastName.hasError('required') && form.controls.lastName.dirty\"\n translate\n >\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"form.controls.lastName.hasError('minlength') && form.controls.lastName.dirty\">\n <span>{{ 'ERROR.FIELD_MIN_LENGTH' | translate: { requiredLength: 2 } }}</span>\n </mat-error>\n </mat-form-field>\n </ion-item>\n\n <!-- First name -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <mat-label>{{ 'USER.FIRST_NAME' | translate }}</mat-label>\n <input matInput formControlName=\"firstName\" autocomplete=\"section-blue given-name\" required />\n <mat-error\n *ngIf=\"form.controls.firstName.hasError('required') && form.controls.firstName.dirty\"\n translate\n >\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"form.controls.firstName.hasError('minlength') && form.controls.firstName.dirty\">\n <span>{{ 'ERROR.FIELD_MIN_LENGTH' | translate: { requiredLength: 2 } }}</span>\n </mat-error>\n </mat-form-field>\n </ion-item>\n\n <!-- Additional fields -->\n @for (definition of additionalFields; track definition.key) {\n <ion-item lines=\"none\">\n <app-form-field\n [definition]=\"definition\"\n [formControlName]=\"definition.key\"\n [required]=\"\n (definition.extra && definition.extra.account && definition.extra.account.required) || false\n \"\n ></app-form-field>\n </ion-item>\n }\n </ion-list>\n\n <!-- Security -->\n @if (showSecurityDetails) {\n <ion-list-header><h3 translate>ACCOUNT.SECURITY.TITLE</h3></ion-list-header>\n\n <ion-list [inset]=\"mobile\">\n <ion-item\n [button]=\"mobile\"\n routerDirection=\"forward\"\n lines=\"none\"\n (click)=\"openChangePasswordPage()\"\n [disabled]=\"disabled\"\n >\n @if (!mobile) {\n <ion-icon slot=\"start\" name=\"keypad\"></ion-icon>\n <ion-label>{{ 'ACCOUNT.SECURITY.PASSWORD' | translate }}</ion-label>\n }\n <ion-text color=\"tertiary\">{{ 'ACCOUNT.SECURITY.BTN_CHANGE_PASSWORD' | translate }}</ion-text>\n @if (!mobile) {\n <ion-icon slot=\"end\" color=\"tertiary\" name=\"chevron-forward\"></ion-icon>\n }\n </ion-item>\n </ion-list>\n }\n\n <!-- Technical details -->\n @if (showTechnicalDetails) {\n <ion-list-header><h3 translate>ACCOUNT.USER_DETAILS.TECHNICAL_DIVIDER</h3></ion-list-header>\n\n <ion-list [inset]=\"mobile\">\n <!-- profile -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <mat-label>{{ 'ACCOUNT.USER_DETAILS.PROFILE' | translate }}</mat-label>\n <input matInput hidden readonly=\"true\" formControlName=\"mainProfile\" required />\n <span>{{ 'USER.PROFILE_ENUM.' + form.controls.mainProfile.value | translate }}</span>\n <mat-error\n *ngIf=\"form.controls.firstName.hasError('mainProfile') && form.controls.mainProfile.dirty\"\n translate\n >\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n </ion-item>\n\n <!-- pubkey -->\n <ion-item lines=\"none\">\n <mat-form-field>\n <mat-label>{{ 'ACCOUNT.USER_DETAILS.PUBKEY' | translate }}</mat-label>\n <input matInput readonly=\"true\" formControlName=\"pubkey\" required />\n <mat-error\n *ngIf=\"form.controls.firstName.hasError('pubkey') && form.controls.firstName.dirty\"\n translate\n >\n ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"form.controls.firstName.hasError('pubkey') && form.controls.firstName.dirty\">\n <span translate>ERROR.FIELD_NOT_VALID_PUBKEY</span>\n </mat-error>\n </mat-form-field>\n </ion-item>\n </ion-list>\n }\n </ion-col>\n\n <!-- right margin -->\n <ion-col size=\"0\" size-lg=\"1\" size-xl=\"2\"> </ion-col>\n </ion-row>\n </ion-grid>\n </mat-tab>\n\n <!-- TAB: settings -->\n <mat-tab [label]=\"'ACCOUNT.SETTINGS.TITLE' | translate\" formGroupName=\"settings\">\n <ng-template mat-tab-label>\n <mat-icon>\n <ion-icon matPrefix slot=\"start\" name=\"settings\"></ion-icon>\n </mat-icon>\n <ion-label translate>ACCOUNT.SETTINGS.TITLE</ion-label>\n </ng-template>\n\n <div class=\"ion-padding\">\n <p [innerHTML]=\"'ACCOUNT.SETTINGS.DESCRIPTION' | translate\"></p>\n\n <mat-form-field>\n <mat-label>{{ 'ACCOUNT.SETTINGS.LOCALE' | translate }}</mat-label>\n <mat-select formControlName=\"locale\" required>\n <mat-option *ngFor=\"let item of locales\" [value]=\"item.key\">\n {{ item.value }}\n </mat-option>\n </mat-select>\n <mat-error\n *ngIf=\"settingsForm?.controls.locale.hasError('required') && settingsForm?.controls.locale.dirty\"\n translate\n >\n ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n\n <!-- lat/long format-->\n @if (showLatLonFormat) {\n @let control = settingsForm | formGetControl: 'latLongFormat';\n <mat-form-field>\n <mat-label>{{ 'ACCOUNT.SETTINGS.LAT_LONG_FORMAT' | translate }}</mat-label>\n <mat-select [formControl]=\"control\" required>\n @for (item of latLongFormats; track item) {\n <mat-option [value]=\"item\">\n {{ 'COMMON.LAT_LONG.ENUM.' + item | uppercase | translate }}\n </mat-option>\n }\n </mat-select>\n @if (control.dirty && control.hasError('required')) {\n <mat-error translate>ERROR.FIELD_REQUIRED</mat-error>\n }\n </mat-form-field>\n }\n </div>\n\n <!-- Options table -->\n <div class=\"options-table\" [class.cdk-visually-hidden]=\"optionDefinitions | isEmptyArray\">\n <app-properties-table\n #propertiesTable\n i18nColumnPrefix=\"ACCOUNT.SETTINGS.OPTIONS.\"\n [definitions]=\"optionDefinitions\"\n [disabled]=\"disabled\"\n [canEdit]=\"true\"\n [debug]=\"debug\"\n (onDirty)=\"$event ? markAsDirty() : undefined\"\n ></app-properties-table>\n </div>\n </mat-tab>\n\n <!-- TAB: tokens -->\n <mat-tab [label]=\"'ACCOUNT.TOKENS.TITLE' | translate\" formGroupName=\"tokens\" [disabled]=\"!showApiTokens\">\n <ng-template mat-tab-label>\n <mat-icon>\n <ion-icon matPrefix slot=\"start\" name=\"ticket\"></ion-icon>\n </mat-icon>\n <ion-label translate>ACCOUNT.TOKENS.TITLE</ion-label>\n </ng-template>\n\n <div class=\"ion-padding\">\n <p [innerHTML]=\"'ACCOUNT.TOKENS.DESCRIPTION' | translate\"></p>\n </div>\n <!-- Tokens table -->\n <div class=\"tokens-table\">\n <app-user-token-table\n #tokensTable\n [disabled]=\"disabled\"\n [canEdit]=\"true\"\n [debug]=\"debug\"\n (onDirty)=\"$event ? markAsDirty() : undefined\"\n ></app-user-token-table>\n </div>\n </mat-tab>\n </mat-tab-group>\n </form>\n</ion-content>\n\n<ion-footer hidden-xs hidden-sm hidden-mobile>\n <app-form-buttons-bar (onCancel)=\"cancel()\" (onSave)=\"save()\" [disabled]=\"!form.dirty || !valid || saving\">\n <!-- error -->\n <ion-item *ngIf=\"error && !mobile\" lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n </app-form-buttons-bar>\n</ion-footer>\n", styles: ["mat-tab-group .mat-tab-body-content{padding:var(--ion-grid-padding);display:grid}form{width:100%}form mat-form-field{width:100%}.user-avatar{-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;background-size:cover;background-repeat:no-repeat;background-position:center;background-color:var(--ion-color-secondary);overflow:hidden!important;font-size:var(--avatar-size, 96px)!important;line-height:var(--avatar-size, 96px);height:var(--avatar-size, 96px)!important;width:var(--avatar-size, 96px)!important;border:solid 1px rgba(var(--ion-color-secondary-rgb),.5);border-radius:50%;display:inline-block}.user-avatar ion-icon{position:fixed;left:0;bottom:16px;width:var(--avatar-size, 96px)}.user-avatar .visible-hover{display:none;visibility:hidden}.user-avatar:hover{background-color:rgba(var(--ion-color-secondary-rgb),.8);border:solid 2px var(--ion-color-secondary-shade);cursor:pointer}.user-avatar:hover .visible-hover{display:inline;visibility:visible}div.options-table{height:calc(100vh - 380px)}div.tokens-table{height:calc(100vh - 250px)}\n"] }]
|
|
45449
45476
|
}], ctorParameters: () => [{ type: i0.Injector }, { type: i1$3.UntypedFormBuilder }, { type: AccountService }, { type: NetworkService }, { type: i2$1.NavController }, { type: AccountValidatorService }, { type: ConfigService }, { type: i0.ChangeDetectorRef }, { type: ImageService }, { type: Environment, decorators: [{
|
|
45450
45477
|
type: Inject,
|
|
45451
45478
|
args: [ENVIRONMENT]
|