@sumaris-net/ngx-components 18.23.19 → 18.23.21
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 +10 -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/model/peer.model.mjs +6 -5
- package/esm2022/src/app/core/services/platform.service.mjs +47 -2
- package/esm2022/src/app/shared/file/uri.utils.mjs +1 -1
- package/esm2022/src/app/shared/file/url.utils.mjs +18 -2
- package/esm2022/src/app/shared/image/image.service.mjs +2 -2
- package/esm2022/src/app/social/feed/feed.component.mjs +3 -3
- package/fesm2022/sumaris-net.ngx-components.mjs +138 -52
- 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/core/services/model/peer.model.d.ts +1 -1
- package/src/app/core/services/platform.service.d.ts +8 -1
- package/src/app/shared/file/url.utils.d.ts +9 -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
|
@@ -6134,12 +6134,18 @@ class UrlUtils {
|
|
|
6134
6134
|
return href;
|
|
6135
6135
|
}
|
|
6136
6136
|
static addTrailingSlash(url) {
|
|
6137
|
+
if (!url)
|
|
6138
|
+
return '/';
|
|
6137
6139
|
return url.endsWith('/') ? url : url + '/';
|
|
6138
6140
|
}
|
|
6139
6141
|
static addStartingSlash(url) {
|
|
6142
|
+
if (!url)
|
|
6143
|
+
return '/';
|
|
6140
6144
|
return url.startsWith('/') ? url : '/' + url;
|
|
6141
6145
|
}
|
|
6142
6146
|
static removeStartingSlash(url) {
|
|
6147
|
+
if (!url)
|
|
6148
|
+
return '';
|
|
6143
6149
|
return url.startsWith('/') ? url.substring(1) : url;
|
|
6144
6150
|
}
|
|
6145
6151
|
static concat(basePath, relativePath) {
|
|
@@ -6149,6 +6155,16 @@ class UrlUtils {
|
|
|
6149
6155
|
return basePath;
|
|
6150
6156
|
return this.addTrailingSlash(basePath) + this.removeStartingSlash(relativePath);
|
|
6151
6157
|
}
|
|
6158
|
+
/**
|
|
6159
|
+
* Cleans the given pathname by replacing occurrences of multiple consecutive slashes with a single slash.
|
|
6160
|
+
*
|
|
6161
|
+
* @param {string} pathname - The pathname to be cleaned.
|
|
6162
|
+
* @return {string} The cleaned pathname with redundant slashes removed.
|
|
6163
|
+
*/
|
|
6164
|
+
static cleanPath(pathname) {
|
|
6165
|
+
return pathname?.replace(/\/\/+/, '/');
|
|
6166
|
+
}
|
|
6167
|
+
static noTrailingSlash = noTrailingSlash;
|
|
6152
6168
|
}
|
|
6153
6169
|
|
|
6154
6170
|
/**
|
|
@@ -16746,12 +16762,12 @@ let Peer = class Peer extends Entity {
|
|
|
16746
16762
|
static { Peer_1 = this; }
|
|
16747
16763
|
static fromObject;
|
|
16748
16764
|
static parseUrl(peerUrl) {
|
|
16749
|
-
const url = new URL(peerUrl);
|
|
16750
|
-
return
|
|
16765
|
+
const url = (typeof peerUrl === 'string') ? new URL(peerUrl) : peerUrl;
|
|
16766
|
+
return Peer_1.fromObject({
|
|
16751
16767
|
dns: url.hostname,
|
|
16752
16768
|
port: isNilOrBlank(url.port) ? undefined : url.port,
|
|
16753
16769
|
useSsl: url.protocol && (url.protocol.startsWith('https') || url.protocol.startsWith('wss')),
|
|
16754
|
-
path: noTrailingSlash(url.pathname),
|
|
16770
|
+
path: UrlUtils.noTrailingSlash(UrlUtils.cleanPath(url.pathname)),
|
|
16755
16771
|
});
|
|
16756
16772
|
}
|
|
16757
16773
|
static path(peer, ...paths) {
|
|
@@ -16767,7 +16783,7 @@ let Peer = class Peer extends Entity {
|
|
|
16767
16783
|
return path;
|
|
16768
16784
|
})
|
|
16769
16785
|
.filter(isNotNilOrBlank);
|
|
16770
|
-
return [noTrailingSlash(
|
|
16786
|
+
return [noTrailingSlash(Peer_1.fromObject(peer).url)].concat(...paths).join('/');
|
|
16771
16787
|
}
|
|
16772
16788
|
static equals(p1, p2) {
|
|
16773
16789
|
return p1 && p2 && Peer_1.fromObject(p1).equals(p2);
|
|
@@ -22050,6 +22066,12 @@ const CORE_CONFIG_OPTIONS = Object.freeze({
|
|
|
22050
22066
|
type: 'boolean',
|
|
22051
22067
|
defaultValue: false
|
|
22052
22068
|
},
|
|
22069
|
+
ACCOUNT_AVATAR_MAX_SIZE: {
|
|
22070
|
+
key: 'sumaris.account.avatar.maxSize',
|
|
22071
|
+
label: 'CONFIGURATION.OPTIONS.ACCOUNT.AVATAR_MAX_SIZE',
|
|
22072
|
+
type: 'integer',
|
|
22073
|
+
defaultValue: 200
|
|
22074
|
+
},
|
|
22053
22075
|
});
|
|
22054
22076
|
|
|
22055
22077
|
class UriUtils {
|
|
@@ -24848,6 +24870,10 @@ let Configuration = class Configuration extends Software {
|
|
|
24848
24870
|
const value = this.getProperty(definition);
|
|
24849
24871
|
return isNotNil(value) ? parseInt(value) : undefined;
|
|
24850
24872
|
}
|
|
24873
|
+
getPropertyAsNumber(definition) {
|
|
24874
|
+
const value = +this.getProperty(definition);
|
|
24875
|
+
return isNotNilOrNaN(value) ? value : undefined;
|
|
24876
|
+
}
|
|
24851
24877
|
getPropertyAsNumbers(definition) {
|
|
24852
24878
|
const value = this.getProperty(definition);
|
|
24853
24879
|
if (typeof value === 'string')
|
|
@@ -25680,6 +25706,49 @@ class PlatformService extends StartableService {
|
|
|
25680
25706
|
}
|
|
25681
25707
|
document.documentElement.classList.toggle('high-contrast', enable);
|
|
25682
25708
|
}
|
|
25709
|
+
/**
|
|
25710
|
+
* Handle deep links (Android App Links / iOS Universal Links)
|
|
25711
|
+
*/
|
|
25712
|
+
async addDeepLinkListener(listenerFn) {
|
|
25713
|
+
if (!this.isApp() || (typeof listenerFn !== 'function'))
|
|
25714
|
+
return; // Skip
|
|
25715
|
+
// Handle deep links (Android App Links / iOS Universal Links)
|
|
25716
|
+
try {
|
|
25717
|
+
console.info('[platform] Start listen deep links');
|
|
25718
|
+
const { App } = await import('@capacitor/app');
|
|
25719
|
+
const listener = await App.addListener('appUrlOpen', (data) => {
|
|
25720
|
+
try {
|
|
25721
|
+
console.info(`[platform] Processing deep link: ${data?.url}`);
|
|
25722
|
+
const url = new URL(data?.url || '');
|
|
25723
|
+
// Get the peer
|
|
25724
|
+
const peer = Peer.parseUrl(url);
|
|
25725
|
+
peer.path = undefined; // FIXME We clean the path (=baseHref) as we cannot detect it from a deep link
|
|
25726
|
+
// Build an absolute path from the URL pathname and search
|
|
25727
|
+
const path = UrlUtils.addTrailingSlash(UrlUtils.cleanUrl(url.pathname || '/'));
|
|
25728
|
+
const query = url.search || '';
|
|
25729
|
+
const fullPath = `${path}${query}`;
|
|
25730
|
+
console.info(`[platform] Processing deep link [OK] - peer: ${peer.url} path: ${fullPath}`);
|
|
25731
|
+
// Call the given listener
|
|
25732
|
+
listenerFn({ peerUrl: peer.url, path: fullPath });
|
|
25733
|
+
}
|
|
25734
|
+
catch (e) {
|
|
25735
|
+
console.warn('[platform] Failed to handle deep link', e, data);
|
|
25736
|
+
}
|
|
25737
|
+
});
|
|
25738
|
+
let subscription = new Subscription();
|
|
25739
|
+
subscription.add(() => {
|
|
25740
|
+
console.info('[platform] Stop listen deep links');
|
|
25741
|
+
listener.remove();
|
|
25742
|
+
this.unregisterSubscription(subscription);
|
|
25743
|
+
subscription = null; // Forget the subscription
|
|
25744
|
+
});
|
|
25745
|
+
this.registerSubscription(subscription);
|
|
25746
|
+
return subscription;
|
|
25747
|
+
}
|
|
25748
|
+
catch (err) {
|
|
25749
|
+
console.warn('[platform] Capacitor App plugin not available for deep links', err);
|
|
25750
|
+
}
|
|
25751
|
+
}
|
|
25683
25752
|
showToast(opts) {
|
|
25684
25753
|
if (!this.toastController)
|
|
25685
25754
|
throw new Error("Missing toastController in component's constructor");
|
|
@@ -27600,7 +27669,7 @@ class ImageService extends StartableService {
|
|
|
27600
27669
|
if (style === 'upload-popover') {
|
|
27601
27670
|
return this.addUploadedImages(event, opts);
|
|
27602
27671
|
}
|
|
27603
|
-
console.info(`[image-service] Adding image
|
|
27672
|
+
console.info(`[image-service] Adding image from camera...`, opts);
|
|
27604
27673
|
try {
|
|
27605
27674
|
const data = await Camera.getPhoto({
|
|
27606
27675
|
height: this.defaultMaxHeight,
|
|
@@ -35981,11 +36050,11 @@ class FeedsComponent {
|
|
|
35981
36050
|
}
|
|
35982
36051
|
}
|
|
35983
36052
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FeedsComponent, deps: [{ token: LocalSettingsService }, { token: ENVIRONMENT }, { token: APP_FEED_SERVICE, optional: true }, { token: AccountService }, { token: i2$1.AlertController }], target: i0.ɵɵFactoryTarget.Component });
|
|
35984
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: FeedsComponent, selector: "app-feed", inputs: { debug: "debug", mobile: "mobile", showHeader: "showHeader", showReadMoreButton: "showReadMoreButton", headerColor: "headerColor", cardColor: "cardColor", shape: "shape", class: "class", itemId: "itemId", filterItem: "filterItem", feeds: "feeds", urls: "urls", maxAgeInMonths: "maxAgeInMonths", maxContentLength: "maxContentLength" }, outputs: { editItem: "editItem", deleteItem: "deleteItem" }, host: { properties: { "class": "this.hostClass" } }, providers: [RxState], viewQueries: [{ propertyName: "modal", first: true, predicate: ["modal"], descendants: true }], ngImport: i0, template: "<!-- debug -->\n@if (debug) {\n <app-debug [title]=\"'Feed'\">\n <p>\n hasFeeds?: {{ hasFeeds$ | async }}\n <br />\n urls: {{ urls$ | async | json }}\n <br />\n shape: {{ shape }}\n </p>\n </app-debug>\n}\n\n@let feeds = feeds$ | async;\n@let userId = userId$ | async;\n\n@if (feeds | isNotEmptyArray) {\n <!-- top header -->\n @if (showHeader && (feeds | arrayFirst); as feed) {\n <ion-item lines=\"none\" [color]=\"headerColor\" class=\"feed-header shape-{{ shape }}\">\n <ion-icon slot=\"start\" name=\"megaphone\"></ion-icon>\n <ion-label>\n <b>{{ feed.title || ('SOCIAL.FEED.NEWS' | translate) }}</b>\n </ion-label>\n <ion-button slot=\"end\" fill=\"clear\" (click)=\"openFeedHome()\" shape=\"\">\n <ion-label translate>SOCIAL.FEED.SHOW_ALL_FEED</ion-label>\n <ion-icon slot=\"end\" name=\"chevron-forward-outline\"></ion-icon>\n </ion-button>\n </ion-item>\n }\n\n <div class=\"feed-content shape-{{ shape }} ion-no-padding\" [class.has-header]=\"showHeader\">\n <!-- feeds -->\n @for (feed of feeds; track feed.feed_url; let firstFeed = $first; let lastFeed = $last) {\n <!-- items -->\n @for (item of feed.items | arrayFilter: filterItem; track item.id; let firstItem = $first; let lastItem = $last) {\n <ion-card\n [class.first]=\"firstFeed && firstItem\"\n [class.last]=\"lastFeed && lastItem\"\n [color]=\"cardColor !== 'light-transparent' ? cardColor : undefined\"\n class=\"feed-item-card\"\n >\n <ion-card-header>\n <ion-card-subtitle style=\"vertical-align: middle\">\n <!-- Authors -->\n @for (author of item.authors || feed.authors; track author) {\n @if (author.name || author.avatar) {\n <ion-chip (click)=\"openUrl(author.url)\" tappable>\n @if (author.avatar) {\n <ion-avatar>\n <ion-img [src]=\"author.avatar\" [alt]=\"author.name\"></ion-img>\n </ion-avatar>\n }\n @if (author.name) {\n <ion-label class=\"author\">{{ author.name }}</ion-label>\n }\n </ion-chip>\n }\n }\n <ion-note class=\"ion-float-end\">\n <small>{{ item.date_published | dateFromNow }}</small>\n </ion-note>\n </ion-card-subtitle>\n\n <!-- title -->\n <ion-card-title (click)=\"openFeedItem(item, feed)\" tappable>{{ item?.title }}</ion-card-title>\n\n <!-- tags -->\n @let tags = item | map: getTags;\n @if (tags | isNotEmptyArray) {\n <ion-text class=\"tags\">\n @for (tag of tags; track tag; let last = $last) {\n <a (click)=\"openTag(feed, tag)\" tappable>\n <ion-text>#{{ tag }}</ion-text>\n </a>\n @if (!last) {\n \n }\n }\n </ion-text>\n }\n </ion-card-header>\n\n <!-- Feed content -->\n <ion-card-content>\n <ion-text [feed]=\"item.url || feed.feed_url\">\n @if (item.content_html) {\n <p [innerHTML]=\"item.content_html\"></p>\n } @else if (item.content_text) {\n <p>\n <markdown [data]=\"item.content_text\" emoji></markdown>\n </p>\n }\n </ion-text>\n </ion-card-content>\n\n @let editable = canEditItem(item, userId, feed);\n @if (editable || showReadMoreButton) {\n @if (editable) {\n\n <!-- Delete button (visible hover)-->\n <button\n mat-icon-button\n (click)=\"onDeleteItem($event, item)\"\n class=\"visible-hover ion-float-start\"\n [title]=\"'COMMON.BTN_DELETE' | translate\"\n >\n <mat-icon>delete</mat-icon>\n </button>\n <!-- Edit button (visible hover) -->\n <ion-button\n (click)=\"onEditItem($event, item)\"\n class=\"visible-hover ion-float-start\" fill=\"clear\"\n >\n <mat-icon slot=\"start\">edit</mat-icon>\n<!-- <ion-icon name=\"pencil\" slot=\"start\"></ion-icon>-->\n <ion-label translate>COMMON.BTN_EDIT</ion-label>\n </ion-button>\n }\n @if (showReadMoreButton) {\n <ion-button (click)=\"openFeedItem(item, feed)\" class=\"ion-float-end\" fill=\"clear\">\n <ion-label>{{ (item.truncated ? 'SOCIAL.FEED.READ_MORE' : 'COMMON.BTN_SHOW') | translate }}</ion-label>\n <ion-icon slot=\"end\" name=\"chevron-forward-outline\"></ion-icon>\n </ion-button>\n }\n }\n </ion-card>\n }\n }\n </div>\n}\n\n<!-- Details modal -->\n<ion-modal #modal [showBackdrop]=\"false\"\n class=\"stack-modal\" [class.modal-large]=\"!mobile\">\n <ng-template>\n <ion-header>\n <ion-toolbar color=\"secondary\">\n <ion-title>{{ 'SOCIAL.FEED.NEWS' | translate }}</ion-title>\n <ion-buttons slot=\"end\">\n <ion-button (click)=\"modal.dismiss()\">\n <ion-icon slot=\"icon-only\" name=\"close\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </ion-header>\n <ion-content>\n <div class=\"ion-padding\">\n <app-feed\n [urls]=\"urls\"\n [showHeader]=\"false\"\n [showReadMoreButton]=\"false\"\n [maxContentLength]=\"-1\"\n [maxAgeInMonths]=\"-1\"\n [itemId]=\"modalItemId\"\n cardColor=\"light\"\n [debug]=\"debug\"\n ></app-feed>\n </div>\n </ion-content>\n </ng-template>\n</ion-modal>\n", styles: [":host{display:block;height:calc(100% - 10px);max-height:fit-content;overflow:hidden;--feed-header-height: 48px;-webkit-box-shadow:0 3px 1px -2px rgba(0,0,0,.2),0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12);box-shadow:0 3px 1px -2px #0003,0 2px 2px #00000024,0 1px 5px #0000001f;-webkit-margin-start:10px;margin-inline-start:10px;-webkit-margin-end:10px;margin-inline-end:10px;margin-top:0;margin-bottom:10px;--feed-border-radius: 4px;border-radius:var(--feed-border-radius);--ion-card-background: rgba(var(--ion-background-color-rgb), .6)}:host.shape-round{--feed-border-radius: 12px}ion-button{text-transform:unset;--color: rgba(var(--ion-color-contrast-rgb), .7)}ion-item.feed-header{height:var(--feed-header-height);margin:0;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);border-radius:var(--feed-border-radius) var(--feed-border-radius) 0 0}ion-item.feed-header ion-icon[slot=start]{-webkit-margin-end:8px;margin-inline-end:8px}.feed-content{height:auto;overflow-y:auto;--margin-bottom: 8px}.feed-content.has-header{height:calc(100% - var(--feed-header-height, 0))}.feed-content ion-card.feed-item-card{--top-radius: 4px;--bottom-radius: 4px;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);margin:0 0 var(--margin-bottom) 0;border-radius:var(--top-radius) var(--top-radius) var(--bottom-radius) var(--bottom-radius);--ion-card-color-contrast-rgb: var(--ion-color-contrast-rgb, var(--ion-color-dark-rgb, 0, 0, 0));--color: rgba(var(--ion-card-color-contrast-rgb), .87)}.feed-content ion-card.feed-item-card.first{--top-radius: 0}.feed-content ion-card.feed-item-card.last{--margin-bottom: 0;--bottom-radius: 0}.feed-content ion-card.feed-item-card ion-card-header ion-card-title{--color: rgba(var(--ion-card-color-contrast-rgb), .87)}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-chip{--background: transparent;--border-color: transparent;--border-width: 0}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-chip ion-avatar{--color: rgba(var(--ion-card-color-contrast-rgb), .6);border:1px solid var(--color)}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-chip ion-label{--color: rgba(var(--ion-card-color-contrast-rgb), .8);color:var(--color)}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-note{--color: rgba(var(--ion-card-color-contrast-rgb), .6);color:var(--color)}.feed-content ion-card.feed-item-card ion-card-header .tags{display:inline}.feed-content ion-card.feed-item-card ion-card-header .tags a{color:rgba(var(--ion-card-color-contrast-rgb),.6)!important}.feed-content ion-card.feed-item-card ion-card-content ion-text ::ng-deep img{max-width:100%;height:auto!important}.feed-content ion-card.feed-item-card .visible-hover{opacity:0;transition:opacity .2s ease-in-out}.feed-content ion-card.feed-item-card:hover .visible-hover{opacity:1}\n"], dependencies: [{ kind: "component", type: i2$1.IonAvatar, selector: "ion-avatar" }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$1.IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: i2$1.IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: i2$1.IonCardHeader, selector: "ion-card-header", inputs: ["color", "mode", "translucent"] }, { kind: "component", type: i2$1.IonCardSubtitle, selector: "ion-card-subtitle", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonCardTitle, selector: "ion-card-title", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonChip, selector: "ion-chip", inputs: ["color", "disabled", "mode", "outline"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonImg, selector: "ion-img", inputs: ["alt", "src"] }, { kind: "component", type: i2$1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: i2$1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonModal, selector: "ion-modal" }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: i4$2.MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }, { kind: "component", type: i6$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i11$1.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: DebugComponent, selector: "app-debug", inputs: ["titlePrefix", "title", "enable", "expanded"] }, { kind: "directive", type: MarkdownDirective, selector: "markdown,[markdown]" }, { kind: "component", type: FeedsComponent, selector: "app-feed", inputs: ["debug", "mobile", "showHeader", "showReadMoreButton", "headerColor", "cardColor", "shape", "class", "itemId", "filterItem", "feeds", "urls", "maxAgeInMonths", "maxContentLength"], outputs: ["editItem", "deleteItem"] }, { kind: "directive", type: FeedDirective, selector: "feed,[feed]", inputs: ["feed"] }, { kind: "pipe", type: i3$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.JsonPipe, name: "json" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }, { kind: "pipe", type: DateFromNowPipe, name: "dateFromNow" }, { kind: "pipe", type: NotEmptyArrayPipe, name: "isNotEmptyArray" }, { kind: "pipe", type: ArrayFirstPipe, name: "arrayFirst" }, { kind: "pipe", type: ArrayFilterPipe, name: "arrayFilter" }, { kind: "pipe", type: MapPipe, name: "map" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
36053
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: FeedsComponent, selector: "app-feed", inputs: { debug: "debug", mobile: "mobile", showHeader: "showHeader", showReadMoreButton: "showReadMoreButton", headerColor: "headerColor", cardColor: "cardColor", shape: "shape", class: "class", itemId: "itemId", filterItem: "filterItem", feeds: "feeds", urls: "urls", maxAgeInMonths: "maxAgeInMonths", maxContentLength: "maxContentLength" }, outputs: { editItem: "editItem", deleteItem: "deleteItem" }, host: { properties: { "class": "this.hostClass" } }, providers: [RxState], viewQueries: [{ propertyName: "modal", first: true, predicate: ["modal"], descendants: true }], ngImport: i0, template: "<!-- debug -->\n@if (debug) {\n <app-debug [title]=\"'Feed'\">\n <p>\n hasFeeds?: {{ hasFeeds$ | async }}\n <br />\n urls: {{ urls$ | async | json }}\n <br />\n shape: {{ shape }}\n </p>\n </app-debug>\n}\n\n@let feeds = feeds$ | async;\n@let userId = userId$ | async;\n\n@if (feeds | isNotEmptyArray) {\n <!-- top header -->\n @if (showHeader && (feeds | arrayFirst); as feed) {\n <ion-item lines=\"none\" [color]=\"headerColor\" class=\"feed-header shape-{{ shape }}\">\n <ion-icon slot=\"start\" name=\"megaphone\"></ion-icon>\n <ion-label>\n <b>{{ feed.title || ('SOCIAL.FEED.NEWS' | translate) }}</b>\n </ion-label>\n <ion-button slot=\"end\" fill=\"clear\" (click)=\"openFeedHome()\" shape=\"\">\n <ion-label translate>SOCIAL.FEED.SHOW_ALL_FEED</ion-label>\n <ion-icon slot=\"end\" name=\"chevron-forward-outline\"></ion-icon>\n </ion-button>\n </ion-item>\n }\n\n <div class=\"feed-content shape-{{ shape }} ion-no-padding\" [class.has-header]=\"showHeader\">\n <!-- feeds -->\n @for (feed of feeds; track feed.feed_url; let firstFeed = $first; let lastFeed = $last) {\n <!-- items -->\n @for (item of feed.items | arrayFilter: filterItem; track item.id; let firstItem = $first; let lastItem = $last) {\n <ion-card\n [class.first]=\"firstFeed && firstItem\"\n [class.last]=\"lastFeed && lastItem\"\n [color]=\"cardColor !== 'light-transparent' ? cardColor : undefined\"\n class=\"feed-item-card\"\n >\n <ion-card-header>\n <ion-card-subtitle style=\"vertical-align: middle\">\n <!-- Authors -->\n @for (author of item.authors || feed.authors; track author) {\n @if (author.name || author.avatar) {\n <ion-chip (click)=\"openUrl(author.url)\" tappable>\n @if (author.avatar) {\n <ion-avatar>\n <ion-img [src]=\"author.avatar\" [alt]=\"author.name\"></ion-img>\n </ion-avatar>\n }\n @if (author.name) {\n <ion-label class=\"author\">{{ author.name }}</ion-label>\n }\n </ion-chip>\n }\n }\n <ion-note class=\"ion-float-end\">\n <small>{{ item.date_published | dateFromNow }}</small>\n </ion-note>\n </ion-card-subtitle>\n\n <!-- title -->\n <ion-card-title (click)=\"openFeedItem(item, feed)\" tappable>{{ item?.title }}</ion-card-title>\n\n <!-- tags -->\n @let tags = item | map: getTags;\n @if (tags | isNotEmptyArray) {\n <ion-text class=\"tags\">\n @for (tag of tags; track tag; let last = $last) {\n <a (click)=\"openTag(feed, tag)\" tappable>\n <ion-text>#{{ tag }}</ion-text>\n </a>\n @if (!last) {\n \n }\n }\n </ion-text>\n }\n </ion-card-header>\n\n <!-- Feed content -->\n <ion-card-content>\n <ion-text [feed]=\"item.url || feed.feed_url\">\n @if (item.content_html) {\n <p [innerHTML]=\"item.content_html\"></p>\n } @else if (item.content_text) {\n <p>\n <markdown [data]=\"item.content_text\" emoji></markdown>\n </p>\n }\n </ion-text>\n </ion-card-content>\n\n @let editable = canEditItem(item, userId, feed);\n @if (editable || showReadMoreButton) {\n @if (editable) {\n\n <!-- Delete button (visible hover)-->\n <button\n mat-icon-button\n (click)=\"onDeleteItem($event, item)\"\n class=\"visible-hover ion-float-start\"\n [title]=\"'COMMON.BTN_DELETE' | translate\"\n >\n <mat-icon>delete</mat-icon>\n </button>\n <!-- Edit button (visible hover) -->\n <ion-button\n (click)=\"onEditItem($event, item)\"\n class=\"visible-hover ion-float-start\" fill=\"clear\"\n >\n <mat-icon slot=\"start\">edit</mat-icon>\n<!-- <ion-icon name=\"pencil\" slot=\"start\"></ion-icon>-->\n <ion-label translate>COMMON.BTN_EDIT</ion-label>\n </ion-button>\n }\n @if (showReadMoreButton) {\n <ion-button (click)=\"openFeedItem(item, feed)\" class=\"ion-float-end\" fill=\"clear\">\n <ion-label>{{ (item.truncated ? 'SOCIAL.FEED.READ_MORE' : 'COMMON.BTN_SHOW') | translate }}</ion-label>\n <ion-icon slot=\"end\" name=\"chevron-forward-outline\"></ion-icon>\n </ion-button>\n }\n }\n </ion-card>\n }\n }\n </div>\n}\n\n<!-- Details modal -->\n<ion-modal #modal [showBackdrop]=\"false\"\n class=\"stack-modal\" [class.modal-large]=\"!mobile\">\n <ng-template>\n <ion-header>\n <ion-toolbar color=\"secondary\">\n <ion-title>{{ 'SOCIAL.FEED.NEWS' | translate }}</ion-title>\n <ion-buttons slot=\"end\">\n <ion-button (click)=\"modal.dismiss()\">\n <ion-icon slot=\"icon-only\" name=\"close\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </ion-header>\n <ion-content>\n <div class=\"ion-padding\">\n <app-feed\n [urls]=\"urls\"\n [showHeader]=\"false\"\n [showReadMoreButton]=\"false\"\n [maxContentLength]=\"-1\"\n [maxAgeInMonths]=\"-1\"\n [itemId]=\"modalItemId\"\n cardColor=\"light\"\n [debug]=\"debug\"\n ></app-feed>\n </div>\n </ion-content>\n </ng-template>\n</ion-modal>\n", styles: [":host{display:block;height:calc(100% - 10px);max-height:fit-content;overflow:hidden;--feed-header-height: 48px;-webkit-box-shadow:0 3px 1px -2px rgba(0,0,0,.2),0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12);box-shadow:0 3px 1px -2px rgba(0,0,0,.2),0 2px 2px rgba(0,0,0,.14),0 1px 5px rgba(0,0,0,.12);-webkit-margin-start:10px;margin-inline-start:10px;-webkit-margin-end:10px;margin-inline-end:10px;margin-top:0;margin-bottom:10px;--feed-border-radius: 4px;border-radius:var(--feed-border-radius);--ion-card-background: rgba(var(--ion-background-color-rgb), .6)}:host.shape-round{--feed-border-radius: 12px}ion-button{text-transform:unset;--color: rgba(var(--ion-color-contrast-rgb), .7)}ion-item.feed-header{height:var(--feed-header-height);margin:0;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);border-radius:var(--feed-border-radius) var(--feed-border-radius) 0 0}ion-item.feed-header ion-icon[slot=start]{-webkit-margin-end:8px;margin-inline-end:8px}.feed-content{height:auto;overflow-y:auto;--margin-bottom: 8px}.feed-content.has-header{height:calc(100% - var(--feed-header-height, 0))}.feed-content ion-card.feed-item-card{--top-radius: 4px;--bottom-radius: 4px;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);margin:0 0 var(--margin-bottom) 0;border-radius:var(--top-radius) var(--top-radius) var(--bottom-radius) var(--bottom-radius);--ion-card-color-contrast-rgb: var(--ion-color-contrast-rgb, var(--ion-color-dark-rgb, 0, 0, 0));--color: rgba(var(--ion-card-color-contrast-rgb), .87)}.feed-content ion-card.feed-item-card.first{--top-radius: 0}.feed-content ion-card.feed-item-card.last{--margin-bottom: 0;--bottom-radius: 0}.feed-content ion-card.feed-item-card ion-card-header ion-card-title{--color: rgba(var(--ion-card-color-contrast-rgb), .87)}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-chip{--background: transparent;--border-color: transparent;--border-width: 0}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-chip ion-avatar{--color: rgba(var(--ion-card-color-contrast-rgb), .6);border:1px solid var(--color)}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-chip ion-label{--color: rgba(var(--ion-card-color-contrast-rgb), .8);color:var(--color)}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-note{--color: rgba(var(--ion-card-color-contrast-rgb), .6);color:var(--color)}.feed-content ion-card.feed-item-card ion-card-header .tags{display:inline}.feed-content ion-card.feed-item-card ion-card-header .tags a{color:rgba(var(--ion-card-color-contrast-rgb),.6)!important}.feed-content ion-card.feed-item-card ion-card-content ion-text ::ng-deep img{max-width:100%;height:auto!important}.feed-content ion-card.feed-item-card .visible-hover{opacity:0;transition:opacity .2s ease-in-out}.feed-content ion-card.feed-item-card:hover .visible-hover{opacity:1}\n"], dependencies: [{ kind: "component", type: i2$1.IonAvatar, selector: "ion-avatar" }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$1.IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: i2$1.IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: i2$1.IonCardHeader, selector: "ion-card-header", inputs: ["color", "mode", "translucent"] }, { kind: "component", type: i2$1.IonCardSubtitle, selector: "ion-card-subtitle", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonCardTitle, selector: "ion-card-title", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonChip, selector: "ion-chip", inputs: ["color", "disabled", "mode", "outline"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonImg, selector: "ion-img", inputs: ["alt", "src"] }, { kind: "component", type: i2$1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: i2$1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonModal, selector: "ion-modal" }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: i4$2.MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }, { kind: "component", type: i6$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i11$1.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: DebugComponent, selector: "app-debug", inputs: ["titlePrefix", "title", "enable", "expanded"] }, { kind: "directive", type: MarkdownDirective, selector: "markdown,[markdown]" }, { kind: "component", type: FeedsComponent, selector: "app-feed", inputs: ["debug", "mobile", "showHeader", "showReadMoreButton", "headerColor", "cardColor", "shape", "class", "itemId", "filterItem", "feeds", "urls", "maxAgeInMonths", "maxContentLength"], outputs: ["editItem", "deleteItem"] }, { kind: "directive", type: FeedDirective, selector: "feed,[feed]", inputs: ["feed"] }, { kind: "pipe", type: i3$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.JsonPipe, name: "json" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }, { kind: "pipe", type: DateFromNowPipe, name: "dateFromNow" }, { kind: "pipe", type: NotEmptyArrayPipe, name: "isNotEmptyArray" }, { kind: "pipe", type: ArrayFirstPipe, name: "arrayFirst" }, { kind: "pipe", type: ArrayFilterPipe, name: "arrayFilter" }, { kind: "pipe", type: MapPipe, name: "map" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
35985
36054
|
}
|
|
35986
36055
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FeedsComponent, decorators: [{
|
|
35987
36056
|
type: Component,
|
|
35988
|
-
args: [{ selector: 'app-feed', providers: [RxState], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- debug -->\n@if (debug) {\n <app-debug [title]=\"'Feed'\">\n <p>\n hasFeeds?: {{ hasFeeds$ | async }}\n <br />\n urls: {{ urls$ | async | json }}\n <br />\n shape: {{ shape }}\n </p>\n </app-debug>\n}\n\n@let feeds = feeds$ | async;\n@let userId = userId$ | async;\n\n@if (feeds | isNotEmptyArray) {\n <!-- top header -->\n @if (showHeader && (feeds | arrayFirst); as feed) {\n <ion-item lines=\"none\" [color]=\"headerColor\" class=\"feed-header shape-{{ shape }}\">\n <ion-icon slot=\"start\" name=\"megaphone\"></ion-icon>\n <ion-label>\n <b>{{ feed.title || ('SOCIAL.FEED.NEWS' | translate) }}</b>\n </ion-label>\n <ion-button slot=\"end\" fill=\"clear\" (click)=\"openFeedHome()\" shape=\"\">\n <ion-label translate>SOCIAL.FEED.SHOW_ALL_FEED</ion-label>\n <ion-icon slot=\"end\" name=\"chevron-forward-outline\"></ion-icon>\n </ion-button>\n </ion-item>\n }\n\n <div class=\"feed-content shape-{{ shape }} ion-no-padding\" [class.has-header]=\"showHeader\">\n <!-- feeds -->\n @for (feed of feeds; track feed.feed_url; let firstFeed = $first; let lastFeed = $last) {\n <!-- items -->\n @for (item of feed.items | arrayFilter: filterItem; track item.id; let firstItem = $first; let lastItem = $last) {\n <ion-card\n [class.first]=\"firstFeed && firstItem\"\n [class.last]=\"lastFeed && lastItem\"\n [color]=\"cardColor !== 'light-transparent' ? cardColor : undefined\"\n class=\"feed-item-card\"\n >\n <ion-card-header>\n <ion-card-subtitle style=\"vertical-align: middle\">\n <!-- Authors -->\n @for (author of item.authors || feed.authors; track author) {\n @if (author.name || author.avatar) {\n <ion-chip (click)=\"openUrl(author.url)\" tappable>\n @if (author.avatar) {\n <ion-avatar>\n <ion-img [src]=\"author.avatar\" [alt]=\"author.name\"></ion-img>\n </ion-avatar>\n }\n @if (author.name) {\n <ion-label class=\"author\">{{ author.name }}</ion-label>\n }\n </ion-chip>\n }\n }\n <ion-note class=\"ion-float-end\">\n <small>{{ item.date_published | dateFromNow }}</small>\n </ion-note>\n </ion-card-subtitle>\n\n <!-- title -->\n <ion-card-title (click)=\"openFeedItem(item, feed)\" tappable>{{ item?.title }}</ion-card-title>\n\n <!-- tags -->\n @let tags = item | map: getTags;\n @if (tags | isNotEmptyArray) {\n <ion-text class=\"tags\">\n @for (tag of tags; track tag; let last = $last) {\n <a (click)=\"openTag(feed, tag)\" tappable>\n <ion-text>#{{ tag }}</ion-text>\n </a>\n @if (!last) {\n \n }\n }\n </ion-text>\n }\n </ion-card-header>\n\n <!-- Feed content -->\n <ion-card-content>\n <ion-text [feed]=\"item.url || feed.feed_url\">\n @if (item.content_html) {\n <p [innerHTML]=\"item.content_html\"></p>\n } @else if (item.content_text) {\n <p>\n <markdown [data]=\"item.content_text\" emoji></markdown>\n </p>\n }\n </ion-text>\n </ion-card-content>\n\n @let editable = canEditItem(item, userId, feed);\n @if (editable || showReadMoreButton) {\n @if (editable) {\n\n <!-- Delete button (visible hover)-->\n <button\n mat-icon-button\n (click)=\"onDeleteItem($event, item)\"\n class=\"visible-hover ion-float-start\"\n [title]=\"'COMMON.BTN_DELETE' | translate\"\n >\n <mat-icon>delete</mat-icon>\n </button>\n <!-- Edit button (visible hover) -->\n <ion-button\n (click)=\"onEditItem($event, item)\"\n class=\"visible-hover ion-float-start\" fill=\"clear\"\n >\n <mat-icon slot=\"start\">edit</mat-icon>\n<!-- <ion-icon name=\"pencil\" slot=\"start\"></ion-icon>-->\n <ion-label translate>COMMON.BTN_EDIT</ion-label>\n </ion-button>\n }\n @if (showReadMoreButton) {\n <ion-button (click)=\"openFeedItem(item, feed)\" class=\"ion-float-end\" fill=\"clear\">\n <ion-label>{{ (item.truncated ? 'SOCIAL.FEED.READ_MORE' : 'COMMON.BTN_SHOW') | translate }}</ion-label>\n <ion-icon slot=\"end\" name=\"chevron-forward-outline\"></ion-icon>\n </ion-button>\n }\n }\n </ion-card>\n }\n }\n </div>\n}\n\n<!-- Details modal -->\n<ion-modal #modal [showBackdrop]=\"false\"\n class=\"stack-modal\" [class.modal-large]=\"!mobile\">\n <ng-template>\n <ion-header>\n <ion-toolbar color=\"secondary\">\n <ion-title>{{ 'SOCIAL.FEED.NEWS' | translate }}</ion-title>\n <ion-buttons slot=\"end\">\n <ion-button (click)=\"modal.dismiss()\">\n <ion-icon slot=\"icon-only\" name=\"close\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </ion-header>\n <ion-content>\n <div class=\"ion-padding\">\n <app-feed\n [urls]=\"urls\"\n [showHeader]=\"false\"\n [showReadMoreButton]=\"false\"\n [maxContentLength]=\"-1\"\n [maxAgeInMonths]=\"-1\"\n [itemId]=\"modalItemId\"\n cardColor=\"light\"\n [debug]=\"debug\"\n ></app-feed>\n </div>\n </ion-content>\n </ng-template>\n</ion-modal>\n", styles: [":host{display:block;height:calc(100% - 10px);max-height:fit-content;overflow:hidden;--feed-header-height: 48px;-webkit-box-shadow:0 3px 1px -2px rgba(0,0,0,.2),0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12);box-shadow:0 3px 1px -2px
|
|
36057
|
+
args: [{ selector: 'app-feed', providers: [RxState], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- debug -->\n@if (debug) {\n <app-debug [title]=\"'Feed'\">\n <p>\n hasFeeds?: {{ hasFeeds$ | async }}\n <br />\n urls: {{ urls$ | async | json }}\n <br />\n shape: {{ shape }}\n </p>\n </app-debug>\n}\n\n@let feeds = feeds$ | async;\n@let userId = userId$ | async;\n\n@if (feeds | isNotEmptyArray) {\n <!-- top header -->\n @if (showHeader && (feeds | arrayFirst); as feed) {\n <ion-item lines=\"none\" [color]=\"headerColor\" class=\"feed-header shape-{{ shape }}\">\n <ion-icon slot=\"start\" name=\"megaphone\"></ion-icon>\n <ion-label>\n <b>{{ feed.title || ('SOCIAL.FEED.NEWS' | translate) }}</b>\n </ion-label>\n <ion-button slot=\"end\" fill=\"clear\" (click)=\"openFeedHome()\" shape=\"\">\n <ion-label translate>SOCIAL.FEED.SHOW_ALL_FEED</ion-label>\n <ion-icon slot=\"end\" name=\"chevron-forward-outline\"></ion-icon>\n </ion-button>\n </ion-item>\n }\n\n <div class=\"feed-content shape-{{ shape }} ion-no-padding\" [class.has-header]=\"showHeader\">\n <!-- feeds -->\n @for (feed of feeds; track feed.feed_url; let firstFeed = $first; let lastFeed = $last) {\n <!-- items -->\n @for (item of feed.items | arrayFilter: filterItem; track item.id; let firstItem = $first; let lastItem = $last) {\n <ion-card\n [class.first]=\"firstFeed && firstItem\"\n [class.last]=\"lastFeed && lastItem\"\n [color]=\"cardColor !== 'light-transparent' ? cardColor : undefined\"\n class=\"feed-item-card\"\n >\n <ion-card-header>\n <ion-card-subtitle style=\"vertical-align: middle\">\n <!-- Authors -->\n @for (author of item.authors || feed.authors; track author) {\n @if (author.name || author.avatar) {\n <ion-chip (click)=\"openUrl(author.url)\" tappable>\n @if (author.avatar) {\n <ion-avatar>\n <ion-img [src]=\"author.avatar\" [alt]=\"author.name\"></ion-img>\n </ion-avatar>\n }\n @if (author.name) {\n <ion-label class=\"author\">{{ author.name }}</ion-label>\n }\n </ion-chip>\n }\n }\n <ion-note class=\"ion-float-end\">\n <small>{{ item.date_published | dateFromNow }}</small>\n </ion-note>\n </ion-card-subtitle>\n\n <!-- title -->\n <ion-card-title (click)=\"openFeedItem(item, feed)\" tappable>{{ item?.title }}</ion-card-title>\n\n <!-- tags -->\n @let tags = item | map: getTags;\n @if (tags | isNotEmptyArray) {\n <ion-text class=\"tags\">\n @for (tag of tags; track tag; let last = $last) {\n <a (click)=\"openTag(feed, tag)\" tappable>\n <ion-text>#{{ tag }}</ion-text>\n </a>\n @if (!last) {\n \n }\n }\n </ion-text>\n }\n </ion-card-header>\n\n <!-- Feed content -->\n <ion-card-content>\n <ion-text [feed]=\"item.url || feed.feed_url\">\n @if (item.content_html) {\n <p [innerHTML]=\"item.content_html\"></p>\n } @else if (item.content_text) {\n <p>\n <markdown [data]=\"item.content_text\" emoji></markdown>\n </p>\n }\n </ion-text>\n </ion-card-content>\n\n @let editable = canEditItem(item, userId, feed);\n @if (editable || showReadMoreButton) {\n @if (editable) {\n\n <!-- Delete button (visible hover)-->\n <button\n mat-icon-button\n (click)=\"onDeleteItem($event, item)\"\n class=\"visible-hover ion-float-start\"\n [title]=\"'COMMON.BTN_DELETE' | translate\"\n >\n <mat-icon>delete</mat-icon>\n </button>\n <!-- Edit button (visible hover) -->\n <ion-button\n (click)=\"onEditItem($event, item)\"\n class=\"visible-hover ion-float-start\" fill=\"clear\"\n >\n <mat-icon slot=\"start\">edit</mat-icon>\n<!-- <ion-icon name=\"pencil\" slot=\"start\"></ion-icon>-->\n <ion-label translate>COMMON.BTN_EDIT</ion-label>\n </ion-button>\n }\n @if (showReadMoreButton) {\n <ion-button (click)=\"openFeedItem(item, feed)\" class=\"ion-float-end\" fill=\"clear\">\n <ion-label>{{ (item.truncated ? 'SOCIAL.FEED.READ_MORE' : 'COMMON.BTN_SHOW') | translate }}</ion-label>\n <ion-icon slot=\"end\" name=\"chevron-forward-outline\"></ion-icon>\n </ion-button>\n }\n }\n </ion-card>\n }\n }\n </div>\n}\n\n<!-- Details modal -->\n<ion-modal #modal [showBackdrop]=\"false\"\n class=\"stack-modal\" [class.modal-large]=\"!mobile\">\n <ng-template>\n <ion-header>\n <ion-toolbar color=\"secondary\">\n <ion-title>{{ 'SOCIAL.FEED.NEWS' | translate }}</ion-title>\n <ion-buttons slot=\"end\">\n <ion-button (click)=\"modal.dismiss()\">\n <ion-icon slot=\"icon-only\" name=\"close\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </ion-header>\n <ion-content>\n <div class=\"ion-padding\">\n <app-feed\n [urls]=\"urls\"\n [showHeader]=\"false\"\n [showReadMoreButton]=\"false\"\n [maxContentLength]=\"-1\"\n [maxAgeInMonths]=\"-1\"\n [itemId]=\"modalItemId\"\n cardColor=\"light\"\n [debug]=\"debug\"\n ></app-feed>\n </div>\n </ion-content>\n </ng-template>\n</ion-modal>\n", styles: [":host{display:block;height:calc(100% - 10px);max-height:fit-content;overflow:hidden;--feed-header-height: 48px;-webkit-box-shadow:0 3px 1px -2px rgba(0,0,0,.2),0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12);box-shadow:0 3px 1px -2px rgba(0,0,0,.2),0 2px 2px rgba(0,0,0,.14),0 1px 5px rgba(0,0,0,.12);-webkit-margin-start:10px;margin-inline-start:10px;-webkit-margin-end:10px;margin-inline-end:10px;margin-top:0;margin-bottom:10px;--feed-border-radius: 4px;border-radius:var(--feed-border-radius);--ion-card-background: rgba(var(--ion-background-color-rgb), .6)}:host.shape-round{--feed-border-radius: 12px}ion-button{text-transform:unset;--color: rgba(var(--ion-color-contrast-rgb), .7)}ion-item.feed-header{height:var(--feed-header-height);margin:0;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);border-radius:var(--feed-border-radius) var(--feed-border-radius) 0 0}ion-item.feed-header ion-icon[slot=start]{-webkit-margin-end:8px;margin-inline-end:8px}.feed-content{height:auto;overflow-y:auto;--margin-bottom: 8px}.feed-content.has-header{height:calc(100% - var(--feed-header-height, 0))}.feed-content ion-card.feed-item-card{--top-radius: 4px;--bottom-radius: 4px;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);margin:0 0 var(--margin-bottom) 0;border-radius:var(--top-radius) var(--top-radius) var(--bottom-radius) var(--bottom-radius);--ion-card-color-contrast-rgb: var(--ion-color-contrast-rgb, var(--ion-color-dark-rgb, 0, 0, 0));--color: rgba(var(--ion-card-color-contrast-rgb), .87)}.feed-content ion-card.feed-item-card.first{--top-radius: 0}.feed-content ion-card.feed-item-card.last{--margin-bottom: 0;--bottom-radius: 0}.feed-content ion-card.feed-item-card ion-card-header ion-card-title{--color: rgba(var(--ion-card-color-contrast-rgb), .87)}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-chip{--background: transparent;--border-color: transparent;--border-width: 0}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-chip ion-avatar{--color: rgba(var(--ion-card-color-contrast-rgb), .6);border:1px solid var(--color)}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-chip ion-label{--color: rgba(var(--ion-card-color-contrast-rgb), .8);color:var(--color)}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-note{--color: rgba(var(--ion-card-color-contrast-rgb), .6);color:var(--color)}.feed-content ion-card.feed-item-card ion-card-header .tags{display:inline}.feed-content ion-card.feed-item-card ion-card-header .tags a{color:rgba(var(--ion-card-color-contrast-rgb),.6)!important}.feed-content ion-card.feed-item-card ion-card-content ion-text ::ng-deep img{max-width:100%;height:auto!important}.feed-content ion-card.feed-item-card .visible-hover{opacity:0;transition:opacity .2s ease-in-out}.feed-content ion-card.feed-item-card:hover .visible-hover{opacity:1}\n"] }]
|
|
35989
36058
|
}], ctorParameters: () => [{ type: LocalSettingsService }, { type: Environment, decorators: [{
|
|
35990
36059
|
type: Inject,
|
|
35991
36060
|
args: [ENVIRONMENT]
|
|
@@ -45156,6 +45225,7 @@ class AccountPage extends AppForm {
|
|
|
45156
45225
|
showTechnicalDetails = true;
|
|
45157
45226
|
showApiTokens = false;
|
|
45158
45227
|
showAvatar = false;
|
|
45228
|
+
avatarMaxSize;
|
|
45159
45229
|
constructor(injector, formBuilder, accountService, network, navController, validatorService, configService, cd, imageService, environment, locales, appBaseHref) {
|
|
45160
45230
|
super(injector, validatorService.getFormGroup(accountService.account, { withSettings: true }));
|
|
45161
45231
|
this.formBuilder = formBuilder;
|
|
@@ -45198,12 +45268,17 @@ class AccountPage extends AppForm {
|
|
|
45198
45268
|
setTimeout(() => this.tabGroup.realignInkBar(), 500);
|
|
45199
45269
|
}
|
|
45200
45270
|
async onChangeAvatar(event) {
|
|
45201
|
-
if (!this.showAvatar || event.defaultPrevented)
|
|
45271
|
+
if (!this.showAvatar || event.defaultPrevented || this.disabled)
|
|
45202
45272
|
return;
|
|
45203
45273
|
event.preventDefault();
|
|
45204
45274
|
event.stopPropagation();
|
|
45205
45275
|
try {
|
|
45206
|
-
const images = await this.imageService.add(event, { multiple: false,
|
|
45276
|
+
const images = await this.imageService.add(event, { multiple: false,
|
|
45277
|
+
autoHideDropArea: true,
|
|
45278
|
+
style: 'camera', // Force to use the camera (if Capacitor is enabled)
|
|
45279
|
+
width: this.avatarMaxSize,
|
|
45280
|
+
height: this.avatarMaxSize,
|
|
45281
|
+
});
|
|
45207
45282
|
const img = firstArrayValue(images);
|
|
45208
45283
|
if (!img?.dataUrl)
|
|
45209
45284
|
return;
|
|
@@ -45323,7 +45398,7 @@ class AccountPage extends AppForm {
|
|
|
45323
45398
|
}
|
|
45324
45399
|
async save(event) {
|
|
45325
45400
|
if (this.saving)
|
|
45326
|
-
return false;
|
|
45401
|
+
return false; // Already saving: skip
|
|
45327
45402
|
if (!this.form.valid) {
|
|
45328
45403
|
await AppFormUtils.waitWhilePending(this.form);
|
|
45329
45404
|
if (this.form.invalid) {
|
|
@@ -45333,48 +45408,52 @@ class AccountPage extends AppForm {
|
|
|
45333
45408
|
}
|
|
45334
45409
|
}
|
|
45335
45410
|
this.submitted = true;
|
|
45336
|
-
this.saving = true;
|
|
45337
45411
|
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);
|
|
45412
|
+
this.markAsSaving();
|
|
45362
45413
|
try {
|
|
45363
|
-
|
|
45364
|
-
|
|
45365
|
-
|
|
45366
|
-
|
|
45367
|
-
this.
|
|
45368
|
-
|
|
45369
|
-
|
|
45370
|
-
|
|
45371
|
-
|
|
45372
|
-
|
|
45373
|
-
|
|
45414
|
+
const data = Account.fromObject({
|
|
45415
|
+
...this.accountService.account.asObject(),
|
|
45416
|
+
...this.form.value,
|
|
45417
|
+
});
|
|
45418
|
+
if (isNotEmptyArray(this.optionDefinitions) && this.propertiesTable) {
|
|
45419
|
+
// Merge properties
|
|
45420
|
+
await this.propertiesTable.save();
|
|
45421
|
+
const propertyEntities = this.propertiesTable.value || [];
|
|
45422
|
+
const properties = propertyEntities.reduce((res, item) => {
|
|
45423
|
+
res[item.id] = item.value;
|
|
45424
|
+
return res;
|
|
45425
|
+
}, {});
|
|
45426
|
+
const newProperties = { ...data.settings.content['properties'], ...properties };
|
|
45427
|
+
data.settings.merge({ properties: newProperties }, ['properties']);
|
|
45428
|
+
}
|
|
45429
|
+
else if (data.settings.content['properties']) {
|
|
45430
|
+
// Clean properties
|
|
45431
|
+
delete data.settings.content['properties'];
|
|
45432
|
+
}
|
|
45433
|
+
if (this.tokensTable) {
|
|
45434
|
+
await this.tokensTable.save();
|
|
45435
|
+
data.tokens = this.tokensTable.value;
|
|
45436
|
+
}
|
|
45437
|
+
console.debug('[account] Saving account...', data);
|
|
45438
|
+
try {
|
|
45439
|
+
this.disable();
|
|
45440
|
+
await this.accountService.save(data);
|
|
45441
|
+
// Reload
|
|
45442
|
+
setTimeout(() => this.onLogin(this.accountService.account), 100);
|
|
45443
|
+
this.markAsPristine();
|
|
45444
|
+
return true;
|
|
45445
|
+
}
|
|
45446
|
+
catch (err) {
|
|
45447
|
+
console.error(err);
|
|
45448
|
+
this.error = (err && err.message) || err;
|
|
45449
|
+
return false;
|
|
45450
|
+
}
|
|
45451
|
+
finally {
|
|
45452
|
+
this.enable();
|
|
45453
|
+
}
|
|
45374
45454
|
}
|
|
45375
45455
|
finally {
|
|
45376
|
-
this.
|
|
45377
|
-
this.enable();
|
|
45456
|
+
this.markAsSaved();
|
|
45378
45457
|
}
|
|
45379
45458
|
}
|
|
45380
45459
|
enable(opts) {
|
|
@@ -45428,24 +45507,31 @@ class AccountPage extends AppForm {
|
|
|
45428
45507
|
// For compatibility with deprecated key
|
|
45429
45508
|
config.getPropertyAsBoolean(CORE_CONFIG_OPTIONS.DEFAULT_LAT_LONG_FORMAT_ENABLED);
|
|
45430
45509
|
this.showAvatar = config.getPropertyAsBoolean(CORE_CONFIG_OPTIONS.ACCOUNT_AVATAR_ENABLE);
|
|
45510
|
+
this.avatarMaxSize = config.getPropertyAsInt(CORE_CONFIG_OPTIONS.ACCOUNT_AVATAR_MAX_SIZE);
|
|
45431
45511
|
const authTokenType = config.getProperty(CORE_CONFIG_OPTIONS.AUTH_TOKEN_TYPE);
|
|
45432
45512
|
this.canChangePassword = authTokenType === 'token';
|
|
45433
45513
|
this.showSecurityDetails = this.canChangePassword;
|
|
45434
45514
|
this.showApiTokens = config.getPropertyAsBoolean(CORE_CONFIG_OPTIONS.AUTH_API_TOKEN_ENABLED);
|
|
45435
45515
|
this.markForCheck();
|
|
45436
45516
|
}
|
|
45517
|
+
openChangePasswordPage() {
|
|
45518
|
+
return this.navController.navigateForward('account/password');
|
|
45519
|
+
}
|
|
45437
45520
|
markForCheck() {
|
|
45438
45521
|
this.cd.markForCheck();
|
|
45439
45522
|
}
|
|
45440
|
-
|
|
45441
|
-
this.
|
|
45523
|
+
markAsSaving() {
|
|
45524
|
+
this.saving = true;
|
|
45525
|
+
}
|
|
45526
|
+
markAsSaved() {
|
|
45527
|
+
this.saving = false;
|
|
45442
45528
|
}
|
|
45443
45529
|
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 });
|
|
45530
|
+
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
45531
|
}
|
|
45446
45532
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AccountPage, decorators: [{
|
|
45447
45533
|
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"] }]
|
|
45534
|
+
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
45535
|
}], 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
45536
|
type: Inject,
|
|
45451
45537
|
args: [ENVIRONMENT]
|