@sumaris-net/ngx-components 2.4.142 → 2.4.144

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.
@@ -11594,10 +11594,12 @@ class TreeItemEntityUtils {
11594
11594
  if (isEmptyArray(node.children))
11595
11595
  return []; // Skip
11596
11596
  // Delete children
11597
- const deletedBatches = node.children.filter(filterFn);
11598
- node.children = node.children.filter((c) => !deletedBatches.includes(c));
11597
+ const deletedChildren = node.children.filter(filterFn);
11598
+ node.children = node.children.filter((c) => !deletedChildren.includes(c));
11599
+ // TODO: add an options to clean the parent
11600
+ //deletedChildren.forEach(child => child.parent = null);
11599
11601
  // Recursive call, in still existing children
11600
- return node.children.reduce((res, c) => res.concat(...this.deleteRecursively(c, filterFn)), deletedBatches);
11602
+ return node.children.reduce((res, c) => res.concat(...this.deleteRecursively(c, filterFn)), deletedChildren);
11601
11603
  }
11602
11604
  /**
11603
11605
  * Visit each node, from root to leaf
@@ -11970,10 +11972,10 @@ const environment = Object.freeze({
11970
11972
  helpUrl: 'https://gitlab.ifremer.fr/sih-public/sumaris/sumaris-doc/-/blob/master/user-manual/index_fr.md',
11971
11973
  // Development
11972
11974
  defaultAuthValues: {
11973
- // Basic auth (using Person.username)
11974
- // username: 'admq2', password: 'q22006'
11975
- // Token auth (using Person.pubkey)
11976
- username: 'admin@sumaris.net', password: 'admin',
11975
+ // Basic auth (using Person.username)
11976
+ // username: 'admq2', password: 'q22006'
11977
+ // Token auth (using Person.pubkey)
11978
+ //username: 'admin@sumaris.net', password: 'admin',
11977
11979
  },
11978
11980
  account: {
11979
11981
  enableListenChanges: true,
@@ -23163,84 +23165,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImpor
23163
23165
  args: [{ providedIn: 'root' }]
23164
23166
  }] });
23165
23167
 
23166
- const APP_ABOUT_DEVELOPERS = new InjectionToken('aboutDevelopers');
23167
- const APP_ABOUT_PARTNERS = new InjectionToken('aboutPartners');
23168
- class AboutModal {
23169
- constructor(translate, modalController, configService, environment, developers, partners) {
23170
- this.translate = translate;
23171
- this.modalController = modalController;
23172
- this.configService = configService;
23173
- this.environment = environment;
23174
- this.developers = developers;
23175
- this.partners = partners;
23176
- this._subscription = new Subscription();
23177
- this.name = translate.instant('APP_NAME');
23178
- if (this.name === 'APP_NAME') {
23179
- // Not translated: use name from the environnement
23180
- this.name = environment && environment.name && capitalizeFirstLetter(environment.name) || undefined;
23181
- }
23182
- this.version = environment && environment.version;
23183
- this.sourceUrl = environment && environment.sourceUrl;
23184
- this.reportIssueUrl = environment && environment.reportIssueUrl;
23185
- this.forumUrl = environment && environment.forumUrl;
23186
- this.helpUrl = environment && environment.helpUrl;
23187
- this.config$ = configService.config;
23188
- this._subscription.add(this.config$.subscribe(config => this.setConfig(config)));
23189
- }
23190
- ngOnDestroy() {
23191
- this._subscription.unsubscribe();
23192
- }
23193
- close() {
23194
- return __awaiter(this, void 0, void 0, function* () {
23195
- yield this.modalController.dismiss();
23196
- });
23197
- }
23198
- /* -- protected functions -- */
23199
- setConfig(config) {
23200
- this.forumUrl = (config === null || config === void 0 ? void 0 : config.getProperty(CORE_CONFIG_OPTIONS.FORUM_URL)) || this.environment.forumUrl;
23201
- this.helpUrl = (config === null || config === void 0 ? void 0 : config.getProperty(CORE_CONFIG_OPTIONS.HELP_URL)) || this.environment.helpUrl;
23202
- }
23203
- }
23204
- AboutModal.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: AboutModal, deps: [{ token: i1$1.TranslateService }, { token: i2.ModalController }, { token: ConfigService }, { token: ENVIRONMENT, optional: true }, { token: APP_ABOUT_DEVELOPERS, optional: true }, { token: APP_ABOUT_PARTNERS, optional: true }], target: i0.ɵɵFactoryTarget.Component });
23205
- AboutModal.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: AboutModal, selector: "app-about-modal", ngImport: i0, template: "<ion-header>\n <ion-toolbar color=\"light\">\n\n <ion-title>\n {{'ABOUT.TITLE'|translate}}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <ion-button (click)=\"close()\" visible-xs visible-sm visible-mobile>\n {{'COMMON.BTN_CLOSE'|translate}}\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n\n <ion-list lines=\"none\">\n\n <!-- Peer info -->\n <ion-item *ngIf=\"config$ | async; let config\">\n <span slot=\"start\">&nbsp;</span>\n <ion-label class=\"ion-text-wrap\" >\n <h2>\n <b>{{config.label}}</b> - <span [innerHTML]=\"config.name\"></span>\n </h2>\n <p *ngIf=\"config.description\">\n <markdown [data]=\"config.description\"\n emoji>\n </markdown>\n </p>\n </ion-label>\n </ion-item>\n\n <!-- Version info -->\n <ion-item>\n <span slot=\"start\">&nbsp;</span>\n <ion-label class=\"ion-text-wrap\">\n <h2>\n <small *ngIf=\"name\">{{'ABOUT.POWERED_BY'|translate}}</small> {{name}}\n <span *ngIf=\"version\" [innerHTML]=\"'ABOUT.VERSION'| translate: {version: version}\"></span>\n </h2>\n <p [innerHTML]=\"'ABOUT.LICENSE'| translate\"></p>\n </ion-label>\n </ion-item>\n\n <!-- Help -->\n <ion-item *ngIf=\"forumUrl || helpUrl\">\n <mat-icon slot=\"start\">help_outline</mat-icon>\n <ion-label>\n <p>\n {{'ABOUT.HELP'|translate}}\n </p>\n <p>\n <ion-button fill=\"solid\" color=\"accent\" href=\"{{helpUrl}}\" target=\"help\" size=\"default\">\n <mat-icon slot=\"start\">description</mat-icon>\n <ion-text translate>ABOUT.USER_MANUAL</ion-text>\n </ion-button>\n <ion-button fill=\"solid\" color=\"secondary\" href=\"{{forumUrl}}\" target=\"forum\" size=\"default\">\n <ion-icon slot=\"start\" name=\"chatbubbles\"></ion-icon>\n <ion-text translate>ABOUT.FORUM</ion-text>\n </ion-button>\n </p>\n </ion-label>\n </ion-item>\n\n <!-- Report issue -->\n <ion-item *ngIf=\"reportIssueUrl\">\n <ion-icon slot=\"start\" name=\"bug\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{'ABOUT.REPORT_ISSUE'|translate}}\n </p>\n <p>\n <a color=\"primary\" href=\"{{reportIssueUrl}}\" target=\"_system\" translate>ABOUT.BTN_REPORT_ISSUE</a>\n </p>\n </ion-label>\n </ion-item>\n\n\n <!-- Source code -->\n <ion-item *ngIf=\"sourceUrl\">\n <ion-icon slot=\"start\" name=\"code\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{'ABOUT.SOURCE_CODE'|translate}}\n </p>\n <p>\n <a color=\"primary\" href=\"{{sourceUrl}}\" target=\"_system\">{{sourceUrl}}</a>\n </p>\n </ion-label>\n </ion-item>\n\n <!-- Developers -->\n <ion-item *ngIf=\"developers | isNotEmptyArray\">\n <ion-icon slot=\"start\" name=\"people\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{'ABOUT.DEVELOPED_BY'|translate}}\n </p>\n <ion-list lines=\"none\">\n <ion-item *ngFor=\"let item of developers\"\n class=\"ion-no-padding\">\n <img slot=\"end\" src=\"{{item.logo}}\">\n <ion-label>\n <p>\n <a color=\"primary\" href=\"{{item.siteUrl}}\" target=\"_system\">{{item.name||item.label}}</a>\n </p>\n </ion-label>\n </ion-item>\n </ion-list>\n </ion-label>\n </ion-item>\n\n <!-- Partners -->\n <ion-item class=\"item-partners\" *ngIf=\"partners | isNotEmptyArray\">\n <ion-icon slot=\"start\" name=\"megaphone\" color=\"dark\"></ion-icon>\n <ion-text>\n <p>{{'ABOUT.PARTNERS'|translate}}</p>\n <p>\n <a *ngFor=\"let item of partners\" href=\"{{item.siteUrl}}\" class=\"partners\" target=\"_system\">\n <img src=\"{{item.logo}}\">\n </a>\n </p>\n </ion-text>\n </ion-item>\n </ion-list>\n\n</ion-content>\n\n<ion-footer hidden-xs hidden-sm hidden-mobile>\n <ion-toolbar>\n <ion-row class=\"ion-no-padding\" nowrap>\n <ion-col></ion-col>\n <ion-col size=\"auto\">\n <ion-button fill=\"solid\" color=\"tertiary\" (click)=\"close()\">{{'COMMON.BTN_CLOSE'|translate}}</ion-button>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n</ion-footer>\n", styles: [".item-partners ion-text{height:auto}.item-partners ion-text p:last-child{display:flex;flex-wrap:wrap;flex-direction:row;justify-content:space-between}.item-partners ion-text p:last-child a{padding:2px}.item-partners ion-text p:last-child a img{text-align:center;display:inline-block;max-height:40px}\n"], dependencies: [{ kind: "directive", type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i2.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.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2.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.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2.IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2.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.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2.IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: i2.IonRow, selector: "ion-row" }, { kind: "component", type: i2.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: i2.IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: i2.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: i6$4.MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "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: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "pipe", type: i3.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }, { kind: "pipe", type: NotEmptyArrayPipe, name: "isNotEmptyArray" }] });
23206
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: AboutModal, decorators: [{
23207
- type: Component,
23208
- args: [{ selector: 'app-about-modal', template: "<ion-header>\n <ion-toolbar color=\"light\">\n\n <ion-title>\n {{'ABOUT.TITLE'|translate}}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <ion-button (click)=\"close()\" visible-xs visible-sm visible-mobile>\n {{'COMMON.BTN_CLOSE'|translate}}\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n\n <ion-list lines=\"none\">\n\n <!-- Peer info -->\n <ion-item *ngIf=\"config$ | async; let config\">\n <span slot=\"start\">&nbsp;</span>\n <ion-label class=\"ion-text-wrap\" >\n <h2>\n <b>{{config.label}}</b> - <span [innerHTML]=\"config.name\"></span>\n </h2>\n <p *ngIf=\"config.description\">\n <markdown [data]=\"config.description\"\n emoji>\n </markdown>\n </p>\n </ion-label>\n </ion-item>\n\n <!-- Version info -->\n <ion-item>\n <span slot=\"start\">&nbsp;</span>\n <ion-label class=\"ion-text-wrap\">\n <h2>\n <small *ngIf=\"name\">{{'ABOUT.POWERED_BY'|translate}}</small> {{name}}\n <span *ngIf=\"version\" [innerHTML]=\"'ABOUT.VERSION'| translate: {version: version}\"></span>\n </h2>\n <p [innerHTML]=\"'ABOUT.LICENSE'| translate\"></p>\n </ion-label>\n </ion-item>\n\n <!-- Help -->\n <ion-item *ngIf=\"forumUrl || helpUrl\">\n <mat-icon slot=\"start\">help_outline</mat-icon>\n <ion-label>\n <p>\n {{'ABOUT.HELP'|translate}}\n </p>\n <p>\n <ion-button fill=\"solid\" color=\"accent\" href=\"{{helpUrl}}\" target=\"help\" size=\"default\">\n <mat-icon slot=\"start\">description</mat-icon>\n <ion-text translate>ABOUT.USER_MANUAL</ion-text>\n </ion-button>\n <ion-button fill=\"solid\" color=\"secondary\" href=\"{{forumUrl}}\" target=\"forum\" size=\"default\">\n <ion-icon slot=\"start\" name=\"chatbubbles\"></ion-icon>\n <ion-text translate>ABOUT.FORUM</ion-text>\n </ion-button>\n </p>\n </ion-label>\n </ion-item>\n\n <!-- Report issue -->\n <ion-item *ngIf=\"reportIssueUrl\">\n <ion-icon slot=\"start\" name=\"bug\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{'ABOUT.REPORT_ISSUE'|translate}}\n </p>\n <p>\n <a color=\"primary\" href=\"{{reportIssueUrl}}\" target=\"_system\" translate>ABOUT.BTN_REPORT_ISSUE</a>\n </p>\n </ion-label>\n </ion-item>\n\n\n <!-- Source code -->\n <ion-item *ngIf=\"sourceUrl\">\n <ion-icon slot=\"start\" name=\"code\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{'ABOUT.SOURCE_CODE'|translate}}\n </p>\n <p>\n <a color=\"primary\" href=\"{{sourceUrl}}\" target=\"_system\">{{sourceUrl}}</a>\n </p>\n </ion-label>\n </ion-item>\n\n <!-- Developers -->\n <ion-item *ngIf=\"developers | isNotEmptyArray\">\n <ion-icon slot=\"start\" name=\"people\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{'ABOUT.DEVELOPED_BY'|translate}}\n </p>\n <ion-list lines=\"none\">\n <ion-item *ngFor=\"let item of developers\"\n class=\"ion-no-padding\">\n <img slot=\"end\" src=\"{{item.logo}}\">\n <ion-label>\n <p>\n <a color=\"primary\" href=\"{{item.siteUrl}}\" target=\"_system\">{{item.name||item.label}}</a>\n </p>\n </ion-label>\n </ion-item>\n </ion-list>\n </ion-label>\n </ion-item>\n\n <!-- Partners -->\n <ion-item class=\"item-partners\" *ngIf=\"partners | isNotEmptyArray\">\n <ion-icon slot=\"start\" name=\"megaphone\" color=\"dark\"></ion-icon>\n <ion-text>\n <p>{{'ABOUT.PARTNERS'|translate}}</p>\n <p>\n <a *ngFor=\"let item of partners\" href=\"{{item.siteUrl}}\" class=\"partners\" target=\"_system\">\n <img src=\"{{item.logo}}\">\n </a>\n </p>\n </ion-text>\n </ion-item>\n </ion-list>\n\n</ion-content>\n\n<ion-footer hidden-xs hidden-sm hidden-mobile>\n <ion-toolbar>\n <ion-row class=\"ion-no-padding\" nowrap>\n <ion-col></ion-col>\n <ion-col size=\"auto\">\n <ion-button fill=\"solid\" color=\"tertiary\" (click)=\"close()\">{{'COMMON.BTN_CLOSE'|translate}}</ion-button>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n</ion-footer>\n", styles: [".item-partners ion-text{height:auto}.item-partners ion-text p:last-child{display:flex;flex-wrap:wrap;flex-direction:row;justify-content:space-between}.item-partners ion-text p:last-child a{padding:2px}.item-partners ion-text p:last-child a img{text-align:center;display:inline-block;max-height:40px}\n"] }]
23209
- }], ctorParameters: function () {
23210
- return [{ type: i1$1.TranslateService }, { type: i2.ModalController }, { type: ConfigService }, { type: undefined, decorators: [{
23211
- type: Optional
23212
- }, {
23213
- type: Inject,
23214
- args: [ENVIRONMENT]
23215
- }] }, { type: undefined, decorators: [{
23216
- type: Optional
23217
- }, {
23218
- type: Inject,
23219
- args: [APP_ABOUT_DEVELOPERS]
23220
- }] }, { type: undefined, decorators: [{
23221
- type: Optional
23222
- }, {
23223
- type: Inject,
23224
- args: [APP_ABOUT_PARTNERS]
23225
- }] }];
23226
- } });
23227
-
23228
23168
  var _a;
23229
- class MenuItem extends Entity {
23169
+ var MenuItem_1;
23170
+ // @dynamic
23171
+ let MenuItem = MenuItem_1 = class MenuItem extends Entity {
23230
23172
  constructor() {
23231
23173
  super(...arguments);
23232
23174
  this.$children = new BehaviorSubject([]);
23233
23175
  this.$title = new BehaviorSubject(undefined);
23234
23176
  }
23235
- static fromObject(source, opts) {
23236
- if (!source)
23237
- return null;
23238
- if (source instanceof MenuItem)
23239
- return source;
23240
- const target = new MenuItem();
23241
- target.fromObject(source, opts);
23242
- return target;
23243
- }
23244
23177
  fromObject(source, opts) {
23245
23178
  this.id = source.id;
23246
23179
  this.title = source.title;
@@ -23267,12 +23200,13 @@ class MenuItem extends Entity {
23267
23200
  this.cssClass = source.cssClass;
23268
23201
  this.ifProperty = source.ifProperty;
23269
23202
  this.pinned = toBoolean(source.pinned, false);
23203
+ this.static = isNotNil(source.static) ? source.static : undefined; // If undefined, will be set by service
23270
23204
  // Icon
23271
23205
  this.icon = source.icon;
23272
23206
  this.matIcon = source.matIcon;
23273
23207
  this.matSvgIcon = source.matSvgIcon;
23274
23208
  if (!opts || opts.withChildren === false) {
23275
- this.children = (source.children || []).map((json) => MenuItem.fromObject(json, opts)) || [];
23209
+ this.children = (source.children || []).map((json) => MenuItem_1.fromObject(json, opts)) || [];
23276
23210
  }
23277
23211
  }
23278
23212
  asObject(opts) {
@@ -23314,6 +23248,12 @@ class MenuItem extends Entity {
23314
23248
  get detached() {
23315
23249
  return !this.parentPath || !this.parent;
23316
23250
  }
23251
+ get visible() {
23252
+ return this.static || !this.detached;
23253
+ }
23254
+ get expanded() {
23255
+ return this.action !== 'expand' || this.matIcon === 'expand_less';
23256
+ }
23317
23257
  get ancestor() {
23318
23258
  var _b;
23319
23259
  if (!this.parent)
@@ -23322,12 +23262,21 @@ class MenuItem extends Entity {
23322
23262
  return this; // first sub menu = ancestor
23323
23263
  return (_b = this.parent) === null || _b === void 0 ? void 0 : _b.ancestor; // loop
23324
23264
  }
23265
+ get divider() {
23266
+ return !this.action && !this.path;
23267
+ }
23268
+ get pinnable() {
23269
+ return !this.static && (typeof this.action !== 'function');
23270
+ }
23325
23271
  isPinned() {
23326
23272
  if (this.pinned)
23327
23273
  return true;
23328
23274
  return (this.children || []).some(child => child.isPinned());
23329
23275
  }
23330
- }
23276
+ };
23277
+ MenuItem = MenuItem_1 = __decorate([
23278
+ EntityClass({ typename: 'MenuItemVO' })
23279
+ ], MenuItem);
23331
23280
  class MenuItems {
23332
23281
  /**
23333
23282
  * Compare, without checking the children
@@ -23407,13 +23356,19 @@ class MenuItems {
23407
23356
  return true;
23408
23357
  }
23409
23358
  static checkIfSubMenuVisible(item, currentPathParam) {
23359
+ var _b;
23410
23360
  if (!item.parentPath)
23411
23361
  return false; // Should have a parent
23362
+ if (item.static)
23363
+ return true; // Always keep static items
23412
23364
  if (item.isPinned())
23413
23365
  return true; // Always keep pinned items
23414
23366
  // Keep each menu items in the scope of the current url path
23415
23367
  if (currentPathParam === null || currentPathParam === void 0 ? void 0 : currentPathParam.path.startsWith(item.path))
23416
23368
  return true;
23369
+ // Keep if parent is an expanded item
23370
+ if (((_b = item.parent) === null || _b === void 0 ? void 0 : _b.action) === 'expand' && item.parent.expanded)
23371
+ return true;
23417
23372
  // Not visible
23418
23373
  return false;
23419
23374
  }
@@ -23447,19 +23402,89 @@ class MenuItems {
23447
23402
  }
23448
23403
  _a = MenuItems;
23449
23404
  MenuItems.sortByTabOrId = _a.sortByParamOrIdComparator('tab');
23405
+ /**
23406
+ * Options to configure specific behavior on projects
23407
+ */
23450
23408
  class MenuOptions {
23451
23409
  }
23452
23410
 
23411
+ const APP_ABOUT_DEVELOPERS = new InjectionToken('aboutDevelopers');
23412
+ const APP_ABOUT_PARTNERS = new InjectionToken('aboutPartners');
23413
+ class AboutModal {
23414
+ constructor(translate, modalController, configService, environment, developers, partners) {
23415
+ this.translate = translate;
23416
+ this.modalController = modalController;
23417
+ this.configService = configService;
23418
+ this.environment = environment;
23419
+ this.developers = developers;
23420
+ this.partners = partners;
23421
+ this._subscription = new Subscription();
23422
+ this.name = translate.instant('APP_NAME');
23423
+ if (this.name === 'APP_NAME') {
23424
+ // Not translated: use name from the environnement
23425
+ this.name = environment && environment.name && capitalizeFirstLetter(environment.name) || undefined;
23426
+ }
23427
+ this.version = environment && environment.version;
23428
+ this.sourceUrl = environment && environment.sourceUrl;
23429
+ this.reportIssueUrl = environment && environment.reportIssueUrl;
23430
+ this.forumUrl = environment && environment.forumUrl;
23431
+ this.helpUrl = environment && environment.helpUrl;
23432
+ this.config$ = configService.config;
23433
+ this._subscription.add(this.config$.subscribe(config => this.setConfig(config)));
23434
+ }
23435
+ ngOnDestroy() {
23436
+ this._subscription.unsubscribe();
23437
+ }
23438
+ close() {
23439
+ return __awaiter(this, void 0, void 0, function* () {
23440
+ yield this.modalController.dismiss();
23441
+ });
23442
+ }
23443
+ /* -- protected functions -- */
23444
+ setConfig(config) {
23445
+ this.forumUrl = (config === null || config === void 0 ? void 0 : config.getProperty(CORE_CONFIG_OPTIONS.FORUM_URL)) || this.environment.forumUrl;
23446
+ this.helpUrl = (config === null || config === void 0 ? void 0 : config.getProperty(CORE_CONFIG_OPTIONS.HELP_URL)) || this.environment.helpUrl;
23447
+ }
23448
+ }
23449
+ AboutModal.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: AboutModal, deps: [{ token: i1$1.TranslateService }, { token: i2.ModalController }, { token: ConfigService }, { token: ENVIRONMENT, optional: true }, { token: APP_ABOUT_DEVELOPERS, optional: true }, { token: APP_ABOUT_PARTNERS, optional: true }], target: i0.ɵɵFactoryTarget.Component });
23450
+ AboutModal.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: AboutModal, selector: "app-about-modal", ngImport: i0, template: "<ion-header>\n <ion-toolbar color=\"light\">\n\n <ion-title>\n {{'ABOUT.TITLE'|translate}}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <ion-button (click)=\"close()\" visible-xs visible-sm visible-mobile>\n {{'COMMON.BTN_CLOSE'|translate}}\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n\n <ion-list lines=\"none\">\n\n <!-- Peer info -->\n <ion-item *ngIf=\"config$ | async; let config\">\n <span slot=\"start\">&nbsp;</span>\n <ion-label class=\"ion-text-wrap\" >\n <h2>\n <b>{{config.label}}</b> - <span [innerHTML]=\"config.name\"></span>\n </h2>\n <p *ngIf=\"config.description\">\n <markdown [data]=\"config.description\"\n emoji>\n </markdown>\n </p>\n </ion-label>\n </ion-item>\n\n <!-- Version info -->\n <ion-item>\n <span slot=\"start\">&nbsp;</span>\n <ion-label class=\"ion-text-wrap\">\n <h2>\n <small *ngIf=\"name\">{{'ABOUT.POWERED_BY'|translate}}</small> {{name}}\n <span *ngIf=\"version\" [innerHTML]=\"'ABOUT.VERSION'| translate: {version: version}\"></span>\n </h2>\n <p [innerHTML]=\"'ABOUT.LICENSE'| translate\"></p>\n </ion-label>\n </ion-item>\n\n <!-- Help -->\n <ion-item *ngIf=\"forumUrl || helpUrl\">\n <mat-icon slot=\"start\">help_outline</mat-icon>\n <ion-label>\n <p>\n {{'ABOUT.HELP'|translate}}\n </p>\n <p>\n <ion-button fill=\"solid\" color=\"accent\" href=\"{{helpUrl}}\" target=\"help\" size=\"default\">\n <mat-icon slot=\"start\">description</mat-icon>\n <ion-text translate>ABOUT.USER_MANUAL</ion-text>\n </ion-button>\n <ion-button fill=\"solid\" color=\"secondary\" href=\"{{forumUrl}}\" target=\"forum\" size=\"default\">\n <ion-icon slot=\"start\" name=\"chatbubbles\"></ion-icon>\n <ion-text translate>ABOUT.FORUM</ion-text>\n </ion-button>\n </p>\n </ion-label>\n </ion-item>\n\n <!-- Report issue -->\n <ion-item *ngIf=\"reportIssueUrl\">\n <ion-icon slot=\"start\" name=\"bug\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{'ABOUT.REPORT_ISSUE'|translate}}\n </p>\n <p>\n <a color=\"primary\" href=\"{{reportIssueUrl}}\" target=\"_system\" translate>ABOUT.BTN_REPORT_ISSUE</a>\n </p>\n </ion-label>\n </ion-item>\n\n\n <!-- Source code -->\n <ion-item *ngIf=\"sourceUrl\">\n <ion-icon slot=\"start\" name=\"code\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{'ABOUT.SOURCE_CODE'|translate}}\n </p>\n <p>\n <a color=\"primary\" href=\"{{sourceUrl}}\" target=\"_system\">{{sourceUrl}}</a>\n </p>\n </ion-label>\n </ion-item>\n\n <!-- Developers -->\n <ion-item *ngIf=\"developers | isNotEmptyArray\">\n <ion-icon slot=\"start\" name=\"people\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{'ABOUT.DEVELOPED_BY'|translate}}\n </p>\n <ion-list lines=\"none\">\n <ion-item *ngFor=\"let item of developers\"\n class=\"ion-no-padding\">\n <img slot=\"end\" src=\"{{item.logo}}\">\n <ion-label>\n <p>\n <a color=\"primary\" href=\"{{item.siteUrl}}\" target=\"_system\">{{item.name||item.label}}</a>\n </p>\n </ion-label>\n </ion-item>\n </ion-list>\n </ion-label>\n </ion-item>\n\n <!-- Partners -->\n <ion-item class=\"item-partners\" *ngIf=\"partners | isNotEmptyArray\">\n <ion-icon slot=\"start\" name=\"megaphone\" color=\"dark\"></ion-icon>\n <ion-text>\n <p>{{'ABOUT.PARTNERS'|translate}}</p>\n <p>\n <a *ngFor=\"let item of partners\" href=\"{{item.siteUrl}}\" class=\"partners\" target=\"_system\">\n <img src=\"{{item.logo}}\">\n </a>\n </p>\n </ion-text>\n </ion-item>\n </ion-list>\n\n</ion-content>\n\n<ion-footer hidden-xs hidden-sm hidden-mobile>\n <ion-toolbar>\n <ion-row class=\"ion-no-padding\" nowrap>\n <ion-col></ion-col>\n <ion-col size=\"auto\">\n <ion-button fill=\"solid\" color=\"tertiary\" (click)=\"close()\">{{'COMMON.BTN_CLOSE'|translate}}</ion-button>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n</ion-footer>\n", styles: [".item-partners ion-text{height:auto}.item-partners ion-text p:last-child{display:flex;flex-wrap:wrap;flex-direction:row;justify-content:space-between}.item-partners ion-text p:last-child a{padding:2px}.item-partners ion-text p:last-child a img{text-align:center;display:inline-block;max-height:40px}\n"], dependencies: [{ kind: "directive", type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i2.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.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2.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.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2.IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2.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.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2.IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: i2.IonRow, selector: "ion-row" }, { kind: "component", type: i2.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: i2.IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: i2.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: i6$4.MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "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: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "pipe", type: i3.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }, { kind: "pipe", type: NotEmptyArrayPipe, name: "isNotEmptyArray" }] });
23451
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: AboutModal, decorators: [{
23452
+ type: Component,
23453
+ args: [{ selector: 'app-about-modal', template: "<ion-header>\n <ion-toolbar color=\"light\">\n\n <ion-title>\n {{'ABOUT.TITLE'|translate}}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <ion-button (click)=\"close()\" visible-xs visible-sm visible-mobile>\n {{'COMMON.BTN_CLOSE'|translate}}\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n\n <ion-list lines=\"none\">\n\n <!-- Peer info -->\n <ion-item *ngIf=\"config$ | async; let config\">\n <span slot=\"start\">&nbsp;</span>\n <ion-label class=\"ion-text-wrap\" >\n <h2>\n <b>{{config.label}}</b> - <span [innerHTML]=\"config.name\"></span>\n </h2>\n <p *ngIf=\"config.description\">\n <markdown [data]=\"config.description\"\n emoji>\n </markdown>\n </p>\n </ion-label>\n </ion-item>\n\n <!-- Version info -->\n <ion-item>\n <span slot=\"start\">&nbsp;</span>\n <ion-label class=\"ion-text-wrap\">\n <h2>\n <small *ngIf=\"name\">{{'ABOUT.POWERED_BY'|translate}}</small> {{name}}\n <span *ngIf=\"version\" [innerHTML]=\"'ABOUT.VERSION'| translate: {version: version}\"></span>\n </h2>\n <p [innerHTML]=\"'ABOUT.LICENSE'| translate\"></p>\n </ion-label>\n </ion-item>\n\n <!-- Help -->\n <ion-item *ngIf=\"forumUrl || helpUrl\">\n <mat-icon slot=\"start\">help_outline</mat-icon>\n <ion-label>\n <p>\n {{'ABOUT.HELP'|translate}}\n </p>\n <p>\n <ion-button fill=\"solid\" color=\"accent\" href=\"{{helpUrl}}\" target=\"help\" size=\"default\">\n <mat-icon slot=\"start\">description</mat-icon>\n <ion-text translate>ABOUT.USER_MANUAL</ion-text>\n </ion-button>\n <ion-button fill=\"solid\" color=\"secondary\" href=\"{{forumUrl}}\" target=\"forum\" size=\"default\">\n <ion-icon slot=\"start\" name=\"chatbubbles\"></ion-icon>\n <ion-text translate>ABOUT.FORUM</ion-text>\n </ion-button>\n </p>\n </ion-label>\n </ion-item>\n\n <!-- Report issue -->\n <ion-item *ngIf=\"reportIssueUrl\">\n <ion-icon slot=\"start\" name=\"bug\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{'ABOUT.REPORT_ISSUE'|translate}}\n </p>\n <p>\n <a color=\"primary\" href=\"{{reportIssueUrl}}\" target=\"_system\" translate>ABOUT.BTN_REPORT_ISSUE</a>\n </p>\n </ion-label>\n </ion-item>\n\n\n <!-- Source code -->\n <ion-item *ngIf=\"sourceUrl\">\n <ion-icon slot=\"start\" name=\"code\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{'ABOUT.SOURCE_CODE'|translate}}\n </p>\n <p>\n <a color=\"primary\" href=\"{{sourceUrl}}\" target=\"_system\">{{sourceUrl}}</a>\n </p>\n </ion-label>\n </ion-item>\n\n <!-- Developers -->\n <ion-item *ngIf=\"developers | isNotEmptyArray\">\n <ion-icon slot=\"start\" name=\"people\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{'ABOUT.DEVELOPED_BY'|translate}}\n </p>\n <ion-list lines=\"none\">\n <ion-item *ngFor=\"let item of developers\"\n class=\"ion-no-padding\">\n <img slot=\"end\" src=\"{{item.logo}}\">\n <ion-label>\n <p>\n <a color=\"primary\" href=\"{{item.siteUrl}}\" target=\"_system\">{{item.name||item.label}}</a>\n </p>\n </ion-label>\n </ion-item>\n </ion-list>\n </ion-label>\n </ion-item>\n\n <!-- Partners -->\n <ion-item class=\"item-partners\" *ngIf=\"partners | isNotEmptyArray\">\n <ion-icon slot=\"start\" name=\"megaphone\" color=\"dark\"></ion-icon>\n <ion-text>\n <p>{{'ABOUT.PARTNERS'|translate}}</p>\n <p>\n <a *ngFor=\"let item of partners\" href=\"{{item.siteUrl}}\" class=\"partners\" target=\"_system\">\n <img src=\"{{item.logo}}\">\n </a>\n </p>\n </ion-text>\n </ion-item>\n </ion-list>\n\n</ion-content>\n\n<ion-footer hidden-xs hidden-sm hidden-mobile>\n <ion-toolbar>\n <ion-row class=\"ion-no-padding\" nowrap>\n <ion-col></ion-col>\n <ion-col size=\"auto\">\n <ion-button fill=\"solid\" color=\"tertiary\" (click)=\"close()\">{{'COMMON.BTN_CLOSE'|translate}}</ion-button>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n</ion-footer>\n", styles: [".item-partners ion-text{height:auto}.item-partners ion-text p:last-child{display:flex;flex-wrap:wrap;flex-direction:row;justify-content:space-between}.item-partners ion-text p:last-child a{padding:2px}.item-partners ion-text p:last-child a img{text-align:center;display:inline-block;max-height:40px}\n"] }]
23454
+ }], ctorParameters: function () {
23455
+ return [{ type: i1$1.TranslateService }, { type: i2.ModalController }, { type: ConfigService }, { type: undefined, decorators: [{
23456
+ type: Optional
23457
+ }, {
23458
+ type: Inject,
23459
+ args: [ENVIRONMENT]
23460
+ }] }, { type: undefined, decorators: [{
23461
+ type: Optional
23462
+ }, {
23463
+ type: Inject,
23464
+ args: [APP_ABOUT_DEVELOPERS]
23465
+ }] }, { type: undefined, decorators: [{
23466
+ type: Optional
23467
+ }, {
23468
+ type: Inject,
23469
+ args: [APP_ABOUT_PARTNERS]
23470
+ }] }];
23471
+ } });
23472
+
23453
23473
  const DEFAULT_MENU_SHOW_WHEN = 'lg';
23454
23474
  const APP_MENU_ITEMS = new InjectionToken('menuItems');
23455
23475
  const APP_MENU_OPTIONS = new InjectionToken('menuOptions');
23456
23476
  class MenuService extends StartableObservableService {
23457
- constructor(settings, configService, accountService, router, environment, options, staticItems) {
23477
+ constructor(settings, configService, accountService, router, modalCtrl, alertController, navController, translate, environment, options, staticItems) {
23478
+ var _a;
23458
23479
  super(settings);
23459
23480
  this.settings = settings;
23460
23481
  this.configService = configService;
23461
23482
  this.accountService = accountService;
23462
23483
  this.router = router;
23484
+ this.modalCtrl = modalCtrl;
23485
+ this.alertController = alertController;
23486
+ this.navController = navController;
23487
+ this.translate = translate;
23463
23488
  this.environment = environment;
23464
23489
  this.accountChanges = new BehaviorSubject(undefined);
23465
23490
  this.onPinned = new EventEmitter(true);
@@ -23470,11 +23495,22 @@ class MenuService extends StartableObservableService {
23470
23495
  this._$splitPaneWhen = new BehaviorSubject(DEFAULT_MENU_SHOW_WHEN);
23471
23496
  this._$enabled = new BehaviorSubject(true);
23472
23497
  this._$listenRoute = new BehaviorSubject(false);
23473
- this._options = Object.assign(Object.assign({ enableSubMenuIcon: true }, options), { enableSubMenus: (options === null || options === void 0 ? void 0 : options.enableSubMenus) !== false && !this.settings.mobile });
23474
- this._$subMenuEnabled = new BehaviorSubject(this._options.enableSubMenus);
23498
+ this._options = Object.assign(Object.assign({}, options), { subMenu: Object.assign(Object.assign({ disableIcon: false, savePinned: true }, ((options === null || options === void 0 ? void 0 : options.subMenu) || {})), {
23499
+ // Force disable on mobile
23500
+ enable: ((_a = options === null || options === void 0 ? void 0 : options.subMenu) === null || _a === void 0 ? void 0 : _a.enable) !== false && !this.settings.mobile }) });
23501
+ this._$enableSubMenu = new BehaviorSubject(this._options.subMenu.enable);
23475
23502
  this._debug = !environment.production;
23476
- this._staticItems = (staticItems || []).map(source => this.fromObject(source, { computeId: true, fillParent: true }));
23477
23503
  this._detachedSubItems = [];
23504
+ this._staticItems = (staticItems || []).map(source => {
23505
+ const target = this.fromObject(Object.assign({ static: true }, source), { computeId: true, fillParent: true, fillDefaultFromParent: true });
23506
+ const detachedItems = TreeItemEntityUtils.deleteRecursively(target, (item) => item.static !== true);
23507
+ if (detachedItems === null || detachedItems === void 0 ? void 0 : detachedItems.length) {
23508
+ console.debug(this._logPrefix + 'Detached init items', detachedItems);
23509
+ detachedItems.forEach(item => this._detachToParent(item));
23510
+ this._detachedSubItems.push(...detachedItems);
23511
+ }
23512
+ return target;
23513
+ });
23478
23514
  }
23479
23515
  get opened() {
23480
23516
  return this._$opened.asObservable();
@@ -23486,10 +23522,7 @@ class MenuService extends StartableObservableService {
23486
23522
  return this._$enabled.asObservable();
23487
23523
  }
23488
23524
  get subMenuEnabled() {
23489
- return this._$subMenuEnabled.value;
23490
- }
23491
- get subMenuIconEnabled() {
23492
- return this._options.enableSubMenuIcon;
23525
+ return this._$enableSubMenu.value;
23493
23526
  }
23494
23527
  get subItems() {
23495
23528
  return (this.data || []).reduce((res, rootElement) => res.concat(rootElement.children), []);
@@ -23501,7 +23534,7 @@ class MenuService extends StartableObservableService {
23501
23534
  this._$enabled.next(value);
23502
23535
  }
23503
23536
  enableSubMenu(value) {
23504
- this._$subMenuEnabled.next(value);
23537
+ this._$enableSubMenu.next(value);
23505
23538
  }
23506
23539
  setSplitPaneShowWhen(value) {
23507
23540
  if (this._$splitPaneWhen.value !== value) {
@@ -23521,8 +23554,8 @@ class MenuService extends StartableObservableService {
23521
23554
  // Compute id if need
23522
23555
  if ((opts === null || opts === void 0 ? void 0 : opts.computeId) && isNil(target.id))
23523
23556
  target.id = this._computeNewId();
23524
- // Remove icon if not enabled
23525
- if (!this.subMenuIconEnabled) {
23557
+ // Remove icon if disabled
23558
+ if (this._options.subMenu.disableIcon) {
23526
23559
  Object.assign(target, { icon: undefined, matIcon: undefined, matSvgIcon: undefined });
23527
23560
  }
23528
23561
  // Recursive
@@ -23530,6 +23563,21 @@ class MenuService extends StartableObservableService {
23530
23563
  // Fill parent (once, when the full tree is ready)
23531
23564
  if (opts === null || opts === void 0 ? void 0 : opts.fillParent) {
23532
23565
  TreeItemEntityUtils.treeFillParent(target);
23566
+ // Fill defaults from the parent (once filled)
23567
+ if (opts === null || opts === void 0 ? void 0 : opts.fillDefaultFromParent) {
23568
+ TreeItemEntityUtils.visit(target, (item) => {
23569
+ if (!item.parent)
23570
+ return; // Skip if no parent
23571
+ item.parentPath = item.parentPath || item.parent.path;
23572
+ item.parentPathParams = item.parentPathParams || item.parent.pathParams;
23573
+ item.static = toBoolean(item.static, item.parent.static);
23574
+ // Compute path, from parent path + action
23575
+ if (!item.path && !!item.action) {
23576
+ item.path = item.parent.path;
23577
+ item.pathParams = (typeof item.action === 'string') ? { action: item.action } : { actionId: item.id.toString(), actionTitle: item.title };
23578
+ }
23579
+ });
23580
+ }
23533
23581
  }
23534
23582
  return target;
23535
23583
  }
@@ -23588,6 +23636,9 @@ class MenuService extends StartableObservableService {
23588
23636
  console.warn(this._logPrefix + `Sub menu {${source.path}} cannot be found - unable to toggle pinned!`);
23589
23637
  return;
23590
23638
  }
23639
+ // Skip if static
23640
+ if (item.static)
23641
+ return;
23591
23642
  // Unpinned
23592
23643
  if (item.pinned) {
23593
23644
  item.pinned = false;
@@ -23604,6 +23655,101 @@ class MenuService extends StartableObservableService {
23604
23655
  }
23605
23656
  this.onPinned.emit(item);
23606
23657
  }
23658
+ executeAction(event, item) {
23659
+ return __awaiter(this, void 0, void 0, function* () {
23660
+ const action = item === null || item === void 0 ? void 0 : item.action;
23661
+ if (!action)
23662
+ return; // skip
23663
+ if (typeof action === 'string') {
23664
+ switch (action) {
23665
+ case 'logout':
23666
+ yield this.logout();
23667
+ break;
23668
+ case 'about':
23669
+ yield this.openAboutModal();
23670
+ break;
23671
+ case 'expand':
23672
+ yield this.toggleExpandableItem(item);
23673
+ if (item.path) {
23674
+ yield this.navController.navigateRoot(item.path, {
23675
+ queryParams: item.pathParams
23676
+ });
23677
+ }
23678
+ break;
23679
+ default: {
23680
+ if (item.path) {
23681
+ yield this.navController.navigateRoot(item.path, {
23682
+ queryParams: item.pathParams
23683
+ });
23684
+ }
23685
+ else {
23686
+ throw new Error('Unknown action: ' + action);
23687
+ }
23688
+ }
23689
+ }
23690
+ }
23691
+ else if (typeof action === 'function') {
23692
+ action(event);
23693
+ }
23694
+ });
23695
+ }
23696
+ logout() {
23697
+ return __awaiter(this, void 0, void 0, function* () {
23698
+ const translations = yield this.translate.instant([
23699
+ 'AUTH.LOGOUT.CONFIRM_TITLE',
23700
+ 'AUTH.LOGOUT.CONFIRM_MESSAGE',
23701
+ 'COMMON.BTN_CANCEL',
23702
+ 'AUTH.LOGOUT.BTN_CONFIRM',
23703
+ ]);
23704
+ const alert = yield this.alertController.create({
23705
+ header: translations['AUTH.LOGOUT.CONFIRM_TITLE'],
23706
+ message: translations['AUTH.LOGOUT.CONFIRM_MESSAGE'],
23707
+ buttons: [
23708
+ {
23709
+ text: translations['COMMON.BTN_CANCEL'],
23710
+ role: 'cancel',
23711
+ cssClass: 'secondary',
23712
+ },
23713
+ {
23714
+ text: translations['AUTH.LOGOUT.BTN_CONFIRM'],
23715
+ cssClass: 'ion-color-primary',
23716
+ handler: () => {
23717
+ this.accountService.logout();
23718
+ // Back to home
23719
+ setTimeout(() => this.navController.navigateRoot('/'), 100);
23720
+ },
23721
+ },
23722
+ ],
23723
+ });
23724
+ yield alert.present();
23725
+ });
23726
+ }
23727
+ openAboutModal() {
23728
+ return __awaiter(this, void 0, void 0, function* () {
23729
+ const modal = yield this.modalCtrl.create({ component: AboutModal });
23730
+ return modal.present();
23731
+ });
23732
+ }
23733
+ toggleExpandableItem(item) {
23734
+ return __awaiter(this, void 0, void 0, function* () {
23735
+ if (!item.expanded) {
23736
+ if (this._debug)
23737
+ console.debug(this._logPrefix + 'Expand item', item);
23738
+ item.matIcon = 'expand_less';
23739
+ // Try to attach some item
23740
+ this._detachedSubItems
23741
+ .filter(childItem => !!this._findParent(childItem, [item]))
23742
+ .forEach(childItem => this._attachMenuToParent(item, childItem));
23743
+ }
23744
+ else {
23745
+ item.matIcon = 'expand_more';
23746
+ this._detachedSubItems = [
23747
+ ...this._detachedSubItems,
23748
+ ...this._detachSubMenuItems({ path: '/', params: {} }, item.children)
23749
+ ];
23750
+ }
23751
+ });
23752
+ }
23607
23753
  _markAsOpened() {
23608
23754
  this._$opened.next(true);
23609
23755
  }
@@ -23636,7 +23782,7 @@ class MenuService extends StartableObservableService {
23636
23782
  // Emit account changes event
23637
23783
  this.accountChanges.next(account);
23638
23784
  }));
23639
- this.registerSubscription(merge(this.accountChanges.pipe(map(() => MenuItems.parsePathWithQuery(this.router.routerState.snapshot.url))), this._$listenRoute.pipe(filter(listen => listen === true), mergeMap$1(_ => this.router.events), filter(event => event instanceof NavigationEnd), map((event) => MenuItems.parsePathWithQuery(event.url)))).pipe(throttleTime(250), filter(() => isNotEmptyArray(this.data)))
23785
+ this.registerSubscription(merge(this.accountChanges.pipe(map(() => MenuItems.parsePathWithQuery(this.router.routerState.snapshot.url))), this._$listenRoute.pipe(filter(listen => listen === true && this.started), mergeMap$1(_ => this.router.events), filter(event => event instanceof NavigationEnd), map((event) => MenuItems.parsePathWithQuery(event.url)))).pipe(throttleTime(250))
23640
23786
  .subscribe(pathParam => {
23641
23787
  const detachedSubItems = this._detachSubMenuItems(pathParam);
23642
23788
  if (detachedSubItems === null || detachedSubItems === void 0 ? void 0 : detachedSubItems.length) {
@@ -23665,7 +23811,7 @@ class MenuService extends StartableObservableService {
23665
23811
  if (this._debug)
23666
23812
  console.debug(`${this._logPrefix}Loading menu items...`);
23667
23813
  // Save previous children of root items
23668
- const detachedSubItems = (this.subItems || [])
23814
+ const detachedSubItems = (this.subItems || []).concat(this._staticSubItems || [])
23669
23815
  .reduce((res, rootItem) => {
23670
23816
  const children = rootItem.children;
23671
23817
  if (isEmptyArray(children))
@@ -23746,18 +23892,19 @@ class MenuService extends StartableObservableService {
23746
23892
  return items;
23747
23893
  }
23748
23894
  _addSubMenuItems(sources, opts) {
23749
- return sources.map(source => this._addSubMenuItem(this.fromObject(source), opts));
23895
+ return sources.map(source => this._addSubMenuItem(source, opts));
23750
23896
  }
23751
23897
  _addSubMenuItem(newItem, opts) {
23752
23898
  opts = Object.assign({ skipIfExists: true }, opts);
23899
+ const pathParam = MenuItems.parsePathWithQuery(this.router.routerState.snapshot.url);
23753
23900
  // Find if item already exists
23754
23901
  const existingItem = this._findExistingItem(newItem, opts === null || opts === void 0 ? void 0 : opts.availableParents);
23755
23902
  if (existingItem) {
23756
23903
  // No changes
23757
- if (opts.skipIfExists && MenuItems.isSame(existingItem, newItem) && !existingItem.detached) {
23904
+ if (opts.skipIfExists && MenuItems.isSame(existingItem, newItem) && existingItem.visible) {
23758
23905
  // DEBUG
23759
23906
  if (this._debug)
23760
- console.debug(`${this._logPrefix}Skipping sub item (same item already exist)`, newItem);
23907
+ console.debug(`${this._logPrefix}Skipping sub item (same visible item already exist)`, newItem);
23761
23908
  return existingItem;
23762
23909
  }
23763
23910
  if (this._debug)
@@ -23767,52 +23914,69 @@ class MenuService extends StartableObservableService {
23767
23914
  newItem.id = existingItem.id;
23768
23915
  newItem.$children = existingItem.$children;
23769
23916
  newItem.pinned = existingItem.pinned;
23770
- if (newItem.pinned) {
23917
+ newItem.static = existingItem.static;
23918
+ if (newItem.pinned || newItem.static || existingItem.expanded) {
23771
23919
  // If loading, keep existing title if pinned (avoid to show skeleton)
23772
23920
  newItem.title = newItem.title || existingItem.title;
23773
23921
  }
23774
23922
  else {
23775
23923
  newItem.title = isNotNil(newItem.title) ? newItem.title : existingItem.title;
23776
23924
  }
23925
+ // Auto set icon, for expand action
23926
+ if (existingItem.action === 'expand') {
23927
+ newItem.icon = null;
23928
+ newItem.matIcon = existingItem.expanded ? 'expand_less' : 'expand_more';
23929
+ }
23777
23930
  }
23778
23931
  }
23779
23932
  // Generate item's id (if need)
23780
23933
  else if (isNil(newItem.id)) {
23781
23934
  newItem.id = this._computeNewId();
23782
23935
  }
23783
- // Get the parent where to attach the item
23936
+ // Default icon for expand action
23937
+ if (newItem.action === 'expand' && !newItem.matIcon) {
23938
+ newItem.matIcon = 'expand_more';
23939
+ }
23940
+ // If visible, get the parent where to attach the item
23784
23941
  const parent = this._findParent(newItem, opts === null || opts === void 0 ? void 0 : opts.availableParents);
23785
- if (parent) {
23942
+ if (parent && parent.expanded) {
23786
23943
  // Attach item to parent
23787
23944
  this._attachMenuToParent(parent, newItem);
23788
- const pathParam = MenuItems.parsePathWithQuery(this.router.routerState.snapshot.url);
23789
23945
  // Update detached items :
23790
23946
  // - excluded new item
23791
23947
  // - attach and exclude those than can be attached to the new item
23792
23948
  // Search in detached items one have newItem as parent
23793
- const ancestor = newItem.ancestor;
23794
- this._detachedSubItems = this._detachedSubItems.concat(...newItem.children)
23795
- .reduce((res, item) => {
23796
- // Excluded newly add item
23797
- if (item.id === newItem.id)
23798
- return res;
23799
- // If some detached can be attached to the new item: do it
23800
- if (MenuItems.checkIfSubMenuVisible(item, pathParam)) {
23801
- const parent = this._findParent(item, [ancestor]);
23802
- if (parent) {
23803
- // Update item's children
23804
- const detachedChildren = this._detachSubMenuItems(pathParam, item.children || []);
23805
- // Attach item
23806
- this._attachMenuToParent(parent, item);
23807
- // Remember detached children
23808
- return res.concat(...detachedChildren);
23949
+ if (newItem.expanded) {
23950
+ const ancestor = newItem.ancestor;
23951
+ this._detachedSubItems = this._detachedSubItems.concat(...newItem.children)
23952
+ .reduce((res, item) => {
23953
+ // Excluded newly add item
23954
+ if (item.id === newItem.id)
23955
+ return res;
23956
+ // If some detached can be attached to the new item: do it
23957
+ if (MenuItems.checkIfSubMenuVisible(item, pathParam)) {
23958
+ const parent = this._findParent(item, [ancestor]);
23959
+ if (parent) {
23960
+ // Update item's children
23961
+ const detachedChildren = this._detachSubMenuItems(pathParam, item.children || []);
23962
+ // Attach item
23963
+ this._attachMenuToParent(parent, item);
23964
+ // Remember detached children
23965
+ return res.concat(...detachedChildren);
23966
+ }
23809
23967
  }
23810
- }
23811
- // Detach (if need)
23812
- this._detachToParent(item);
23813
- // Add to detached items
23814
- return res.concat(item);
23815
- }, []);
23968
+ // Detach (if need)
23969
+ this._detachToParent(item);
23970
+ // Add to detached items
23971
+ return res.concat(item);
23972
+ }, []);
23973
+ }
23974
+ else {
23975
+ // Remove from detached elements
23976
+ this._detachedSubItems = this._detachedSubItems.filter(item => item.id !== newItem.id);
23977
+ // Detach children
23978
+ newItem.children.forEach(item => this._detachToParent(item));
23979
+ }
23816
23980
  }
23817
23981
  else {
23818
23982
  // Add or update detached item
@@ -23927,7 +24091,7 @@ class MenuService extends StartableObservableService {
23927
24091
  }
23928
24092
  }
23929
24093
  }
23930
- MenuService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: MenuService, deps: [{ token: LocalSettingsService }, { token: ConfigService }, { token: AccountService }, { token: i1$5.Router }, { token: ENVIRONMENT }, { token: APP_MENU_OPTIONS, optional: true }, { token: APP_MENU_ITEMS, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
24094
+ MenuService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: MenuService, deps: [{ token: LocalSettingsService }, { token: ConfigService }, { token: AccountService }, { token: i1$5.Router }, { token: i2.ModalController }, { token: i2.AlertController }, { token: i2.NavController }, { token: i1$1.TranslateService }, { token: ENVIRONMENT }, { token: APP_MENU_OPTIONS, optional: true }, { token: APP_MENU_ITEMS, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
23931
24095
  MenuService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: MenuService, providedIn: 'root' });
23932
24096
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: MenuService, decorators: [{
23933
24097
  type: Injectable,
@@ -23935,7 +24099,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImpor
23935
24099
  providedIn: 'root', deps: [ENVIRONMENT]
23936
24100
  }]
23937
24101
  }], ctorParameters: function () {
23938
- return [{ type: LocalSettingsService }, { type: ConfigService }, { type: AccountService }, { type: i1$5.Router }, { type: Environment, decorators: [{
24102
+ return [{ type: LocalSettingsService }, { type: ConfigService }, { type: AccountService }, { type: i1$5.Router }, { type: i2.ModalController }, { type: i2.AlertController }, { type: i2.NavController }, { type: i1$1.TranslateService }, { type: Environment, decorators: [{
23939
24103
  type: Inject,
23940
24104
  args: [ENVIRONMENT]
23941
24105
  }] }, { type: MenuOptions, decorators: [{
@@ -24036,37 +24200,6 @@ class MenuComponent {
24036
24200
  this.markForCheck();
24037
24201
  });
24038
24202
  }
24039
- logout() {
24040
- return __awaiter(this, void 0, void 0, function* () {
24041
- const translations = yield this.translate.instant([
24042
- 'AUTH.LOGOUT.CONFIRM_TITLE',
24043
- 'AUTH.LOGOUT.CONFIRM_MESSAGE',
24044
- 'COMMON.BTN_CANCEL',
24045
- 'AUTH.LOGOUT.BTN_CONFIRM',
24046
- ]);
24047
- const alert = yield this.alertController.create({
24048
- header: translations['AUTH.LOGOUT.CONFIRM_TITLE'],
24049
- message: translations['AUTH.LOGOUT.CONFIRM_MESSAGE'],
24050
- buttons: [
24051
- {
24052
- text: translations['COMMON.BTN_CANCEL'],
24053
- role: 'cancel',
24054
- cssClass: 'secondary',
24055
- },
24056
- {
24057
- text: translations['AUTH.LOGOUT.BTN_CONFIRM'],
24058
- cssClass: 'ion-color-primary',
24059
- handler: () => {
24060
- this.accountService.logout();
24061
- // Back to home
24062
- setTimeout(() => this.navController.navigateRoot('/'), 100);
24063
- },
24064
- },
24065
- ],
24066
- });
24067
- yield alert.present();
24068
- });
24069
- }
24070
24203
  trackByFn(index, item) {
24071
24204
  return item.id.toString();
24072
24205
  }
@@ -24100,18 +24233,12 @@ class MenuComponent {
24100
24233
  if (opened) {
24101
24234
  console.debug('[menu] Closing menu');
24102
24235
  yield this.menu.close(this.menuId);
24103
- // Propage to service
24236
+ // Propagate to service
24104
24237
  this.menuService._markAsClosed();
24105
24238
  }
24106
24239
  return true;
24107
24240
  });
24108
24241
  }
24109
- openAboutModal(event) {
24110
- return __awaiter(this, void 0, void 0, function* () {
24111
- const modal = yield this.modalCtrl.create({ component: AboutModal });
24112
- return modal.present();
24113
- });
24114
- }
24115
24242
  setSplitPaneWhen(value) {
24116
24243
  return __awaiter(this, void 0, void 0, function* () {
24117
24244
  console.debug('[menu] Checking splitPane when...');
@@ -24136,26 +24263,10 @@ class MenuComponent {
24136
24263
  return this.setSplitPaneWhen(false);
24137
24264
  }
24138
24265
  }
24139
- doAction(action, event) {
24140
- return __awaiter(this, void 0, void 0, function* () {
24141
- this.close();
24142
- console.log(typeof action);
24143
- if (typeof action === 'string') {
24144
- switch (action) {
24145
- case 'logout':
24146
- yield this.logout();
24147
- break;
24148
- case 'about':
24149
- yield this.openAboutModal(event);
24150
- break;
24151
- default:
24152
- throw new Error('Unknown action: ' + action);
24153
- }
24154
- }
24155
- else if (typeof action === 'function') {
24156
- action(event);
24157
- }
24158
- });
24266
+ executeAction(event, item) {
24267
+ this.close();
24268
+ // Delegate execution to service
24269
+ return this.menuService.executeAction(event, item);
24159
24270
  }
24160
24271
  onSwipeRight(event) {
24161
24272
  // Skip, if not a valid swipe event
@@ -24204,10 +24315,10 @@ class MenuComponent {
24204
24315
  }
24205
24316
  }
24206
24317
  MenuComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: MenuComponent, deps: [{ token: PlatformService }, { token: AccountService }, { token: i2.NavController }, { token: i2.MenuController }, { token: i2.ModalController }, { token: i2.AlertController }, { token: i1$1.TranslateService }, { token: i0.ChangeDetectorRef }, { token: MenuService }, { token: i1$5.Router }, { token: ENVIRONMENT }, { token: i1$5.ActivatedRoute, optional: true }], target: i0.ɵɵFactoryTarget.Component });
24207
- MenuComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: MenuComponent, selector: "app-menu", inputs: { id: "id", menuId: "menuId", side: "side", contentId: "contentId", logo: "logo", appName: "appName", appVersion: "appVersion", rxStrategy: "rxStrategy" }, viewQueries: [{ propertyName: "splitPane", first: true, predicate: ["splitPane"], descendants: true, static: true }], ngImport: i0, template: "<ion-split-pane #splitPane [contentId]=\"contentId\" (swiperight)=\"onSwipeRight($event)\">\n\n <ion-menu [id]=\"id\" [menuId]=\"menuId\" [contentId]=\"contentId\">\n <ion-header>\n\n <ion-toolbar *ngIf=\"!loading && isLogin; else notLogin\"\n @fadeInSlowAnimation\n class=\"user-toolbar\">\n <ion-grid>\n <ion-row>\n <ion-col size=\"4\">\n <button type=\"button\" mat-flat-button\n class=\"user-avatar\" [class.primary]=\"!accountAvatar\"\n [style.background-image]=\"'url('+(accountAvatar||'./assets/img/person.png')+')'\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"ion-color-primary\"\n (click)=\"close()\"\n [title]=\"'MENU.BTN_MY_ACCOUNT'|translate\">\n </button>\n </ion-col>\n <ion-col size=\"8\" class=\"user-logo\">\n <img *ngIf=\"logo; else noLogo\" src=\"{{logo}}\" [title]=\"'APP_NAME'|translate: {appName: appName}\" width=\"108px\"/>\n <ng-template #noLogo>\n <span style=\"width: 108px;\">{{appName}}</span>\n </ng-template>\n </ion-col>\n </ion-row>\n <ion-row class=\"ion-no-padding\">\n <ion-col>\n <button mat-button type=\"button\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"ion-color-primary\"\n (click)=\"close()\"\n [title]=\"'MENU.BTN_MY_ACCOUNT'|translate\">\n <ion-label color=\"primary\" class=\"ion-text-wrap ion-text-start\">\n <h3 class=\"no-margin username\">\n <b>{{accountName}}</b>\n </h3>\n <h4>{{accountEmail}}</h4>\n </ion-label>\n </button>\n </ion-col>\n\n <!-- Insertion headerBottomRight -->\n <ion-col size=\"auto\">\n <ng-container *ngTemplateOutlet=\"headerBottomRight\"></ng-container>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n </ion-toolbar>\n\n <!-- User not logged -->\n <ng-template #notLogin>\n <mat-toolbar class=\"ion-padding\"\n *ngIf=\"!loading\"\n style=\"height: unset; display: block; margin: auto; text-align: center;\">\n <img *ngIf=\"logo\" src=\"{{logo}}\" [title]=\"'APP_NAME'|translate: {appName: appName}\" width=\"150px;\" alt=\"logo\">\n <span *ngIf=\"!logo\" style=\"width: 150px\">{{appName}}</span>\n </mat-toolbar>\n </ng-template>\n </ion-header>\n\n <ion-content [class.has-user-header]=\"isLogin\">\n <!-- will close the menu, after a click, in mobile -->\n <ion-menu-toggle auto-hide=\"false\">\n <ion-list lines=\"none\" *ngIf=\"!loading\">\n <ng-container *rxFor=\"let item of menuService.dataSubject; strategy: rxStrategy; trackBy: trackByFn\">\n <ng-container *ngTemplateOutlet=\"menuItem; context: {$implicit: item, level: 0}\"></ng-container>\n </ng-container>\n </ion-list>\n </ion-menu-toggle>\n </ion-content>\n\n <ion-footer class=\"hidden-xs hidden-sm\">\n <ion-toolbar>\n\n <ion-buttons slot=\"start\">\n <ion-button mat-icon-button color=\"accent\" (click)=\"openAboutModal($event)\">\n <mat-icon slot=\"icon-only\">help_outline</mat-icon>\n </ion-button>\n </ion-buttons>\n\n <ion-title (click)=\"openAboutModal($event)\" color=\"medium\">\n {{'MENU.FOOTER_VERSION_ABOUT'| translate: {version: appVersion} }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <button mat-icon-button color=\"accent\" (click)=\"toggleSplitPaneShow($event)\"\n class=\"hidden-xs hidden-sm hidden-md\"\n [title]=\"(splitPane.when ? 'COMMON.BTN_HIDE_MENU' : 'COMMON.BTN_SHOW_MENU') |translate\">\n <mat-icon><span>{{splitPane.when ? '&#xab;' : '&#xbb;'}}</span></mat-icon>\n </button>\n </ion-buttons>\n </ion-toolbar>\n </ion-footer>\n\n </ion-menu>\n\n <ng-content></ng-content>\n\n</ion-split-pane>\n\n<ng-template #headerBottomRight>\n <ng-content select=\"[headerBottomRight]\"></ng-content>\n</ng-template>\n\n<ng-template #menuItem let-item let-level=\"level\">\n <!-- link -->\n <ion-item *ngIf=\"item.path && !item.action\"\n @fadeInSlowAnimation\n class=\"{{item.cssClass}} {{item.color}}\"\n [class.menu-item-sub]=\"level!==0\"\n tappable\n [routerLink]=\"item.path\"\n [queryParams]=\"item.pathParams\"\n routerDirection=\"root\"\n routerLinkActive=\"selected\"\n [routerLinkActiveOptions]=\"{exact: (item.path === '/' || level !== 0)}\">\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-label *rxLet=\"item.$title; let title\">\n <span *ngIf=\"title; else skeletonText\" [innerHTML]=\"title|translate\"></span>\n <!--<span *ngIf=\"_debug\"> {{item.id}}</span>-->\n </ion-label>\n\n <!-- pin button -->\n <ng-container *ngTemplateOutlet=\"pinButton; context: {$implicit: item, level: level}\"></ng-container>\n\n </ion-item>\n\n <!-- action -->\n <ion-item *ngIf=\"item.action\"\n @fadeInSlowAnimation\n class=\"{{item.cssClass}} {{item.color}} text-1x\"\n [class.menu-item-sub]=\"level!==0\"\n tappable\n (click)=\"doAction(item.action, $event)\">\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-label>\n <span *rxIf=\"item.$title; let title; else skeletonText\" [innerHTML]=\"title|translate\"></span>\n <!--<span *ngIf=\"_debug\"> {{item.id}}</span>-->\n </ion-label>\n\n <!-- pin button -->\n <ng-container *ngTemplateOutlet=\"pinButton; context: {$implicit: item, level: level}\"></ng-container>\n </ion-item>\n\n <!-- divider -->\n <ion-item-divider @fadeInSlowAnimation\n class=\"{{item.cssClass}} {{item.color}}\"\n *ngIf=\"!item.path && !item.action\">\n <ion-label>\n <span *rxIf=\"item.$title; let title; else skeletonText\" [innerHTML]=\"title|translate\"></span>\n </ion-label>\n </ion-item-divider>\n\n <!-- children -->\n <div *ngIf=\"item?.$children\" class=\"sub-menu-container\">\n <ng-container *rxFor=\"let child of item.$children ; strategy: rxStrategy; trackBy: trackByFn\">\n <ng-container *ngTemplateOutlet=\"menuItem; context: {$implicit: child, level: level+1}\"></ng-container>\n </ng-container>\n </div>\n</ng-template>\n\n<ng-template #pinButton let-item let-level=\"level\">\n <!-- pin button -->\n <ion-button *ngIf=\"level > 0\"\n slot=\"end\"\n shape=\"round\" fill=\"clear\" size=\"small\"\n [class.visible-hover]=\"!item.pinned\"\n (click)=\"togglePinned($event, item)\">\n <ion-icon slot=\"icon-only\" [name]=\"item.pinned ? 'pin' : 'pin-outline'\"></ion-icon>\n </ion-button>\n</ng-template>\n\n<ng-template #skeletonText let-width let-animated>\n <ion-skeleton-text [animated]=\"animated\" [style.width.%]=\"width || 60\"></ion-skeleton-text>\n</ng-template>\n", styles: ["@keyframes fadeinout{0%{opacity:0;display:none}75%{opacity:0;display:none}to{opacity:1;display:flex}}ion-menu{--menu-item-margin: 0;--ion-item-background: transparent;--ion-item-divider-background: transparent;--ion-item-icon-color: var(--ion-color-primary-tint);--ion-item-text-color: var(--ion-color-primary-tint);--ion-item-background-selected: var(--ion-color-secondary100);--ion-item-text-color-selected: var(--ion-color-primary);--ion-item-icon-color-selected: var(--ion-color-primary);--ion-item-text-color-disable: var(--ion-color-medium);--ion-item-icon-color-disable: var(--ion-color-medium);--menu-item-background-selected-sub: var(--ion-color-secondary50);--menu-item-border-width-sub: 0 0 1px 0;--menu-item-margin-start-sub: 16px}ion-menu ion-header ion-text{color:var(--ion-color-primary)}ion-menu ion-header ion-toolbar.user-toolbar{--ion-toolbar-height: 128px}ion-menu ion-header .user-avatar{background-size:cover;background-repeat:no-repeat;background-position:center;background-color:var(--ion-color-secondary);border:solid 1px rgba(var(--ion-color-secondary-rgb),.5);overflow:hidden!important;font-size:var(--avatar-size, 60px)!important;line-height:var(--avatar-size, 60px);height:var(--avatar-size, 60px)!important;width:var(--avatar-size, 60px)!important;border-radius:50%;display:inline-block}ion-menu ion-header .user-avatar:hover{background-color:var(--ion-color-secondary-shade);border:solid 2px var(--ion-color-secondary-shade)}ion-menu ion-header .user-logo{text-align:right}ion-menu ion-header .user-logo img{max-width:120px;max-height:var(--avatar-size, 60px);width:auto}ion-menu ion-header .username{padding-top:0;margin-top:0;margin-bottom:0;width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}ion-menu ion-header button[mat-button]{padding:0;text-align:start!important;width:100%}ion-menu ion-content ion-list{min-height:100%;display:flex;flex-direction:column;justify-content:flex-start}ion-menu ion-content ion-list ion-menu-toggle.flex-spacer{flex:1 1 auto;display:flex;flex-direction:column;justify-content:flex-end}ion-menu ion-content ion-list ion-item.primary{--ion-item-icon-color: var(--ion-color-primary-tint);--ion-item-text-color: var(--ion-color-primary-tint)}ion-menu ion-content ion-list ion-item.secondary{--ion-item-icon-color: var(--ion-color-secondary-tint);--ion-item-text-color: var(--ion-color-secondary-tint)}ion-menu ion-content ion-list ion-item.tertiary{--ion-item-icon-color: var(--ion-color-tertiary-tint);--ion-item-text-color: var(--ion-color-tertiary-tint)}ion-menu ion-content ion-list ion-item.danger{--ion-item-icon-color: var(--ion-color-danger-tint);--ion-item-text-color: var(--ion-color-danger-tint)}ion-menu ion-content ion-list ion-item.medium{--ion-item-icon-color: var(--ion-color-medium-shade);--ion-item-text-color: var(--ion-color-medium-shade)}ion-menu ion-content ion-list ion-item.dark{--ion-item-icon-color: var(--ion-color-dark-tint);--ion-item-text-color: var(--ion-color-dark-tint)}ion-menu ion-content ion-list ion-item mat-icon,ion-menu ion-content ion-list ion-item ion-icon{color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor}ion-menu ion-content ion-list ion-item ion-text,ion-menu ion-content ion-list ion-item ion-label{color:var(--ion-item-text-color)!important}ion-menu ion-content ion-list ion-item span{margin-left:var(--menu-item-margin)}ion-menu ion-content ion-list ion-item.selected{background-color:var(--ion-item-background-selected)!important;--color-hover: var(--ion-item-background-selected) !important}ion-menu ion-content ion-list ion-item.selected mat-icon,ion-menu ion-content ion-list ion-item.selected ion-icon{color:var(--ion-item-icon-color-selected)!important;fill:currentColor;stroke:currentColor}ion-menu ion-content ion-list ion-item.selected ion-text,ion-menu ion-content ion-list ion-item.selected ion-label{color:var(--ion-item-text-color-selected)!important}ion-menu ion-content ion-list ion-item.selected:hover{--color-hover: var(--ion-item-background-selected) !important;--ion-item-icon-color-selected: var(--ion-item-icon-color) !important;--ion-item-text-color-selected: var(--ion-item-text-color) !important}ion-menu ion-content ion-list ion-item.selected.menu-item-sub{background-color:var(--menu-item-background-selected-sub)!important}ion-menu ion-content ion-list ion-item.menu-item-sub{--min-height: 34px;--padding-vertical: 8px;--padding-start: 8px;--padding-horizontal: 8px;--inner-border-width: var(--menu-item-border-width-sub);font-size:.8em;overflow:unset}ion-menu ion-content ion-list ion-item.menu-item-sub ion-icon[slot],ion-menu ion-content ion-list ion-item.menu-item-sub mat-icon[slot]{font-size:calc(var(--min-height) / 2 - 1px);height:calc(var(--min-height) / 2 - 1px);margin-top:calc(var(--padding-vertical) + 2px);margin-inline-end:calc(var(--padding-horizontal) + 2px);margin-bottom:calc(var(--padding-vertical) + 2px)}ion-menu ion-content ion-list ion-item.menu-item-sub ion-label{margin-top:var(--padding-vertical);margin-bottom:var(--padding-vertical)}ion-menu ion-content ion-list ion-item.menu-item-sub .item-inner{border-width:var(--inner-border-width);border-style:var(--border-style);border-color:var(--border-color);box-shadow:var(--inner-box-shadow)}ion-menu ion-content ion-list ion-item.menu-item-sub ion-button[slot=end] ion-icon[slot=icon-only]{margin:0}ion-menu ion-content ion-list ion-item.menu-item-sub ion-button[slot=end].visible-hover{opacity:0;display:none;animation:fadeinout 1s linear 1 backwards}ion-menu ion-content ion-list ion-item.menu-item-sub:hover ion-button.visible-hover{opacity:1;display:flex}ion-menu ion-content ion-list .sub-menu-container{margin-inline-start:var(--menu-item-margin-start-sub, 16px)}ion-menu ion-footer{display:block!important}ion-menu ion-footer ion-toolbar ion-title{cursor:pointer;font-size:12pt;font-weight:400;text-align:center;padding:0 8px}ion-menu ion-footer ion-toolbar ion-button{--width: 40px}@media screen and (max-width: 767px){ion-menu ion-footer{display:none!important;visibility:hidden!important}}@media screen and (min-width: 768px){ion-menu ion-scroll{overflow-y:auto!important}ion-menu .user-avatar{font-size:var(--avatar-size, 80px)!important;line-height:var(--avatar-size, 80px);height:var(--avatar-size, 80px)!important;width:var(--avatar-size, 80px)!important}ion-menu .user-logo img{max-height:var(--avatar-size, 80px)}}\n"], dependencies: [{ kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i2.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.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2.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.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i2.IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2.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.IonItemDivider, selector: "ion-item-divider", inputs: ["color", "mode", "sticky"] }, { kind: "component", type: i2.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2.IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: i2.IonMenu, selector: "ion-menu", inputs: ["contentId", "disabled", "maxEdgeStart", "menuId", "side", "swipeGesture", "type"] }, { kind: "component", type: i2.IonMenuToggle, selector: "ion-menu-toggle", inputs: ["autoHide", "menu"] }, { kind: "component", type: i2.IonRow, selector: "ion-row" }, { kind: "component", type: i2.IonSkeletonText, selector: "ion-skeleton-text", inputs: ["animated"] }, { kind: "component", type: i2.IonSplitPane, selector: "ion-split-pane", inputs: ["contentId", "disabled", "when"] }, { kind: "component", type: i2.IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: i2.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "directive", type: i2.RouterLinkDelegate, selector: ":not(a):not(area)[routerLink]", inputs: ["routerDirection", "routerAnimation"] }, { kind: "component", type: i8$1.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatButton, selector: "button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }, { kind: "directive", type: i1$5.RouterLink, selector: ":not(a):not(area)[routerLink]", inputs: ["queryParams", "fragment", "queryParamsHandling", "state", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i1$5.RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "directive", type: i5$2.RxFor, selector: "[rxFor][rxForOf]", inputs: ["rxForOf", "rxForTemplate", "rxForStrategy", "rxForParent", "rxForPatchZone", "rxForTrackBy", "rxForRenderCallback"] }, { kind: "directive", type: i12$3.RxIf, selector: "[rxIf]", inputs: ["rxIf", "rxIfStrategy", "rxIfElse", "rxIfThen", "rxIfSuspense", "rxIfComplete", "rxIfError", "rxIfContextTrigger", "rxIfNextTrigger", "rxIfSuspenseTrigger", "rxIfErrorTrigger", "rxIfCompleteTrigger", "rxIfParent", "rxIfPatchZone", "rxIfRenderCallback"] }, { kind: "directive", type: i13.LetDirective, selector: "[rxLet]", inputs: ["rxLet", "rxLetStrategy", "rxLetComplete", "rxLetError", "rxLetSuspense", "rxLetContextTrigger", "rxLetCompleteTrigger", "rxLetErrorTrigger", "rxLetSuspenseTrigger", "rxLetNextTrigger", "rxLetRenderCallback", "rxLetParent", "rxLetPatchZone"], outputs: ["rendered"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], animations: [fadeInSlowAnimation], changeDetection: i0.ChangeDetectionStrategy.OnPush });
24318
+ MenuComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: MenuComponent, selector: "app-menu", inputs: { id: "id", menuId: "menuId", side: "side", contentId: "contentId", logo: "logo", appName: "appName", appVersion: "appVersion", rxStrategy: "rxStrategy" }, viewQueries: [{ propertyName: "splitPane", first: true, predicate: ["splitPane"], descendants: true, static: true }], ngImport: i0, template: "<ion-split-pane #splitPane [contentId]=\"contentId\" (swiperight)=\"onSwipeRight($event)\">\n\n <ion-menu [id]=\"id\" [menuId]=\"menuId\" [contentId]=\"contentId\">\n <ion-header>\n\n <ion-toolbar *ngIf=\"!loading && isLogin; else notLogin\"\n @fadeInSlowAnimation\n class=\"user-toolbar\">\n <ion-grid>\n <ion-row>\n <ion-col size=\"4\">\n <button type=\"button\" mat-flat-button\n class=\"user-avatar\" [class.primary]=\"!accountAvatar\"\n [style.background-image]=\"'url('+(accountAvatar||'./assets/img/person.png')+')'\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"ion-color-primary\"\n (click)=\"close()\"\n [title]=\"'MENU.BTN_MY_ACCOUNT'|translate\">\n </button>\n </ion-col>\n <ion-col size=\"8\" class=\"user-logo\">\n <img *ngIf=\"logo; else noLogo\" src=\"{{logo}}\" [title]=\"'APP_NAME'|translate: {appName: appName}\" width=\"108px\"/>\n <ng-template #noLogo>\n <span style=\"width: 108px;\">{{appName}}</span>\n </ng-template>\n </ion-col>\n </ion-row>\n <ion-row class=\"ion-no-padding\">\n <ion-col>\n <button mat-button type=\"button\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"ion-color-primary\"\n (click)=\"close()\"\n [title]=\"'MENU.BTN_MY_ACCOUNT'|translate\">\n <ion-label color=\"primary\" class=\"ion-text-wrap ion-text-start\">\n <h3 class=\"no-margin username\">\n <b>{{accountName}}</b>\n </h3>\n <h4>{{accountEmail}}</h4>\n </ion-label>\n </button>\n </ion-col>\n\n <!-- Insertion headerBottomRight -->\n <ion-col size=\"auto\">\n <ng-container *ngTemplateOutlet=\"headerBottomRight\"></ng-container>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n </ion-toolbar>\n\n <!-- User not logged -->\n <ng-template #notLogin>\n <mat-toolbar class=\"ion-padding\"\n *ngIf=\"!loading\"\n style=\"height: unset; display: block; margin: auto; text-align: center;\">\n <img *ngIf=\"logo\" src=\"{{logo}}\" [title]=\"'APP_NAME'|translate: {appName: appName}\" width=\"150px;\" alt=\"logo\">\n <span *ngIf=\"!logo\" style=\"width: 150px\">{{appName}}</span>\n </mat-toolbar>\n </ng-template>\n </ion-header>\n\n <ion-content [class.has-user-header]=\"isLogin\">\n <!-- will close the menu, after a click, in mobile -->\n <ion-menu-toggle auto-hide=\"false\">\n <ion-list lines=\"none\" *ngIf=\"!loading\">\n <ng-container *rxFor=\"let item of menuService.dataSubject; strategy: rxStrategy; trackBy: trackByFn\">\n <ng-container *ngTemplateOutlet=\"menuItem; context: {$implicit: item, level: 0}\"></ng-container>\n </ng-container>\n </ion-list>\n </ion-menu-toggle>\n </ion-content>\n\n <ion-footer class=\"hidden-xs hidden-sm\">\n <ion-toolbar>\n\n <ion-buttons slot=\"start\">\n <ion-button mat-icon-button color=\"accent\" (click)=\"menuService.openAboutModal()\">\n <mat-icon slot=\"icon-only\">help_outline</mat-icon>\n </ion-button>\n </ion-buttons>\n\n <ion-title (click)=\"menuService.openAboutModal()\" color=\"medium\">\n {{'MENU.FOOTER_VERSION_ABOUT'| translate: {version: appVersion} }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <button mat-icon-button color=\"accent\" (click)=\"toggleSplitPaneShow($event)\"\n class=\"hidden-xs hidden-sm hidden-md\"\n [title]=\"(splitPane.when ? 'COMMON.BTN_HIDE_MENU' : 'COMMON.BTN_SHOW_MENU') |translate\">\n <mat-icon><span>{{splitPane.when ? '&#xab;' : '&#xbb;'}}</span></mat-icon>\n </button>\n </ion-buttons>\n </ion-toolbar>\n </ion-footer>\n\n </ion-menu>\n\n <ng-content></ng-content>\n\n</ion-split-pane>\n\n<ng-template #headerBottomRight>\n <ng-content select=\"[headerBottomRight]\"></ng-content>\n</ng-template>\n\n<ng-template #menuItem let-item let-level=\"level\">\n\n <!-- divider -->\n <ion-item-divider *ngIf=\"item.divider; else tappable\"\n class=\"{{item.cssClass}} {{item.color}}\"\n @fadeInSlowAnimation>\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-label>\n <span *rxIf=\"item.$title; let title; else skeletonText\" [innerHTML]=\"title|translate\"></span>\n </ion-label>\n </ion-item-divider>\n\n <ng-template #tappable>\n\n <!-- link -->\n <ion-item *ngIf=\"item.path && !item.action\"\n @fadeInSlowAnimation\n class=\"{{item.cssClass}} {{item.color}}\"\n [class.menu-item-sub]=\"level!==0\"\n tappable\n [routerLink]=\"item.path\"\n [queryParams]=\"item.pathParams\"\n routerDirection=\"root\"\n routerLinkActive=\"selected\"\n [routerLinkActiveOptions]=\"{exact: (item.path === '/' || level !== 0)}\">\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-label *rxLet=\"item.$title; let title\">\n <span *ngIf=\"title; else skeletonText\" [innerHTML]=\"title|translate\"></span>\n <!--<span *ngIf=\"_debug\"> {{item.id}}</span>-->\n </ion-label>\n\n <!-- pin button -->\n <ng-container *ngTemplateOutlet=\"pinButton; context: {$implicit: item, level: level}\"></ng-container>\n\n </ion-item>\n\n <!-- action -->\n <ion-item *ngIf=\"item.action\"\n @fadeInSlowAnimation\n class=\"{{item.cssClass}} {{item.color}} text-1x\"\n [class.menu-item-sub]=\"level!==0\"\n tappable\n (click)=\"executeAction($event, item)\">\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-label>\n <span *rxIf=\"item.$title; let title; else skeletonText\" [innerHTML]=\"title|translate\"></span>\n <!--<span *ngIf=\"_debug\"> {{item.id}}</span>-->\n </ion-label>\n\n <!-- pin button -->\n <ng-container *ngTemplateOutlet=\"pinButton; context: {$implicit: item, level: level}\"></ng-container>\n </ion-item>\n </ng-template>\n\n\n\n <!-- children -->\n <div *ngIf=\"item?.$children\" class=\"sub-menu-container\">\n <ng-container *rxFor=\"let child of item.$children ; strategy: rxStrategy; trackBy: trackByFn\">\n <ng-container *ngTemplateOutlet=\"menuItem; context: {$implicit: child, level: level+1}\"></ng-container>\n </ng-container>\n </div>\n</ng-template>\n\n<ng-template #pinButton let-item>\n <!-- pin button -->\n <ion-button *ngIf=\"item.pinnable\"\n slot=\"end\"\n shape=\"round\" fill=\"clear\" size=\"small\"\n [class.visible-hover]=\"!item.pinned\"\n (click)=\"togglePinned($event, item)\">\n <ion-icon slot=\"icon-only\" [name]=\"item.pinned ? 'pin' : 'pin-outline'\"></ion-icon>\n </ion-button>\n</ng-template>\n\n<ng-template #skeletonText let-width let-animated>\n <ion-skeleton-text [animated]=\"animated\" [style.width.%]=\"width || 60\"></ion-skeleton-text>\n</ng-template>\n", styles: ["@keyframes fadeinout{0%{opacity:0;display:none}75%{opacity:0;display:none}to{opacity:1;display:flex}}ion-menu{--menu-item-margin: 0;--ion-item-background: transparent;--ion-item-divider-background: transparent;--ion-item-icon-color: var(--ion-color-primary-tint);--ion-item-text-color: var(--ion-color-primary-tint);--ion-item-background-selected: var(--ion-color-secondary100);--ion-item-text-color-selected: var(--ion-color-primary);--ion-item-icon-color-selected: var(--ion-color-primary);--ion-item-text-color-disable: var(--ion-color-medium);--ion-item-icon-color-disable: var(--ion-color-medium);--menu-item-background-selected-sub: var(--ion-color-secondary50);--menu-item-border-width-sub: 0 0 1px 0;--menu-item-margin-start-sub: 16px}ion-menu ion-header ion-text{color:var(--ion-color-primary)}ion-menu ion-header ion-toolbar.user-toolbar{--ion-toolbar-height: 128px}ion-menu ion-header .user-avatar{background-size:cover;background-repeat:no-repeat;background-position:center;background-color:var(--ion-color-secondary);border:solid 1px rgba(var(--ion-color-secondary-rgb),.5);overflow:hidden!important;font-size:var(--avatar-size, 60px)!important;line-height:var(--avatar-size, 60px);height:var(--avatar-size, 60px)!important;width:var(--avatar-size, 60px)!important;border-radius:50%;display:inline-block}ion-menu ion-header .user-avatar:hover{background-color:var(--ion-color-secondary-shade);border:solid 2px var(--ion-color-secondary-shade)}ion-menu ion-header .user-logo{text-align:right}ion-menu ion-header .user-logo img{max-width:120px;max-height:var(--avatar-size, 60px);width:auto}ion-menu ion-header .username{padding-top:0;margin-top:0;margin-bottom:0;width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}ion-menu ion-header button[mat-button]{padding:0;text-align:start!important;width:100%}ion-menu ion-content ion-list{min-height:100%;display:flex;flex-direction:column;justify-content:flex-start}ion-menu ion-content ion-list ion-menu-toggle.flex-spacer{flex:1 1 auto;display:flex;flex-direction:column;justify-content:flex-end}ion-menu ion-content ion-list ion-item.primary{--ion-item-icon-color: var(--ion-color-primary-tint);--ion-item-text-color: var(--ion-color-primary-tint)}ion-menu ion-content ion-list ion-item.secondary{--ion-item-icon-color: var(--ion-color-secondary-tint);--ion-item-text-color: var(--ion-color-secondary-tint)}ion-menu ion-content ion-list ion-item.tertiary{--ion-item-icon-color: var(--ion-color-tertiary-tint);--ion-item-text-color: var(--ion-color-tertiary-tint)}ion-menu ion-content ion-list ion-item.danger{--ion-item-icon-color: var(--ion-color-danger-tint);--ion-item-text-color: var(--ion-color-danger-tint)}ion-menu ion-content ion-list ion-item.medium{--ion-item-icon-color: var(--ion-color-medium-shade);--ion-item-text-color: var(--ion-color-medium-shade)}ion-menu ion-content ion-list ion-item.dark{--ion-item-icon-color: var(--ion-color-dark-tint);--ion-item-text-color: var(--ion-color-dark-tint)}ion-menu ion-content ion-list ion-item mat-icon,ion-menu ion-content ion-list ion-item ion-icon{color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor}ion-menu ion-content ion-list ion-item ion-text,ion-menu ion-content ion-list ion-item ion-label{color:var(--ion-item-text-color)!important}ion-menu ion-content ion-list ion-item span{margin-left:var(--menu-item-margin)}ion-menu ion-content ion-list ion-item.selected{background-color:var(--ion-item-background-selected)!important;--color-hover: var(--ion-item-background-selected) !important}ion-menu ion-content ion-list ion-item.selected mat-icon,ion-menu ion-content ion-list ion-item.selected ion-icon{color:var(--ion-item-icon-color-selected)!important;fill:currentColor;stroke:currentColor}ion-menu ion-content ion-list ion-item.selected ion-text,ion-menu ion-content ion-list ion-item.selected ion-label{color:var(--ion-item-text-color-selected)!important}ion-menu ion-content ion-list ion-item.selected:hover{--color-hover: var(--ion-item-background-selected) !important;--ion-item-icon-color-selected: var(--ion-item-icon-color) !important;--ion-item-text-color-selected: var(--ion-item-text-color) !important}ion-menu ion-content ion-list ion-item.selected.menu-item-sub{background-color:var(--menu-item-background-selected-sub)!important}ion-menu ion-content ion-list ion-item.menu-item-sub{--min-height: 34px;--padding-vertical: 8px;--padding-start: 8px;--padding-horizontal: 8px;--inner-border-width: var(--menu-item-border-width-sub);font-size:.8em;overflow:unset}ion-menu ion-content ion-list ion-item.menu-item-sub ion-icon[slot],ion-menu ion-content ion-list ion-item.menu-item-sub mat-icon[slot]{font-size:calc(var(--min-height) / 2 - 1px);height:calc(var(--min-height) / 2 - 1px);margin-top:calc(var(--padding-vertical) + 2px);margin-inline-end:calc(var(--padding-horizontal) + 2px);margin-bottom:calc(var(--padding-vertical) + 2px)}ion-menu ion-content ion-list ion-item.menu-item-sub ion-label{margin-top:var(--padding-vertical);margin-bottom:var(--padding-vertical)}ion-menu ion-content ion-list ion-item.menu-item-sub .item-inner{border-width:var(--inner-border-width);border-style:var(--border-style);border-color:var(--border-color);box-shadow:var(--inner-box-shadow)}ion-menu ion-content ion-list ion-item.menu-item-sub ion-button[slot=end] ion-icon[slot=icon-only]{margin:0}ion-menu ion-content ion-list ion-item.menu-item-sub ion-button[slot=end].visible-hover{opacity:0;display:none;animation:fadeinout 1s linear 1 backwards}ion-menu ion-content ion-list ion-item.menu-item-sub:hover ion-button.visible-hover{opacity:1;display:flex}ion-menu ion-content ion-list .sub-menu-container{margin-inline-start:var(--menu-item-margin-start-sub, 16px)}ion-menu ion-footer{display:block!important}ion-menu ion-footer ion-toolbar ion-title{cursor:pointer;font-size:12pt;font-weight:400;text-align:center;padding:0 8px}ion-menu ion-footer ion-toolbar ion-button{--width: 40px}@media screen and (max-width: 767px){ion-menu ion-footer{display:none!important;visibility:hidden!important}}@media screen and (min-width: 768px){ion-menu ion-scroll{overflow-y:auto!important}ion-menu .user-avatar{font-size:var(--avatar-size, 80px)!important;line-height:var(--avatar-size, 80px);height:var(--avatar-size, 80px)!important;width:var(--avatar-size, 80px)!important}ion-menu .user-logo img{max-height:var(--avatar-size, 80px)}}\n"], dependencies: [{ kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i2.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.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2.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.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i2.IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2.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.IonItemDivider, selector: "ion-item-divider", inputs: ["color", "mode", "sticky"] }, { kind: "component", type: i2.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2.IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: i2.IonMenu, selector: "ion-menu", inputs: ["contentId", "disabled", "maxEdgeStart", "menuId", "side", "swipeGesture", "type"] }, { kind: "component", type: i2.IonMenuToggle, selector: "ion-menu-toggle", inputs: ["autoHide", "menu"] }, { kind: "component", type: i2.IonRow, selector: "ion-row" }, { kind: "component", type: i2.IonSkeletonText, selector: "ion-skeleton-text", inputs: ["animated"] }, { kind: "component", type: i2.IonSplitPane, selector: "ion-split-pane", inputs: ["contentId", "disabled", "when"] }, { kind: "component", type: i2.IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: i2.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "directive", type: i2.RouterLinkDelegate, selector: ":not(a):not(area)[routerLink]", inputs: ["routerDirection", "routerAnimation"] }, { kind: "component", type: i8$1.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatButton, selector: "button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }, { kind: "directive", type: i1$5.RouterLink, selector: ":not(a):not(area)[routerLink]", inputs: ["queryParams", "fragment", "queryParamsHandling", "state", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i1$5.RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "directive", type: i5$2.RxFor, selector: "[rxFor][rxForOf]", inputs: ["rxForOf", "rxForTemplate", "rxForStrategy", "rxForParent", "rxForPatchZone", "rxForTrackBy", "rxForRenderCallback"] }, { kind: "directive", type: i12$3.RxIf, selector: "[rxIf]", inputs: ["rxIf", "rxIfStrategy", "rxIfElse", "rxIfThen", "rxIfSuspense", "rxIfComplete", "rxIfError", "rxIfContextTrigger", "rxIfNextTrigger", "rxIfSuspenseTrigger", "rxIfErrorTrigger", "rxIfCompleteTrigger", "rxIfParent", "rxIfPatchZone", "rxIfRenderCallback"] }, { kind: "directive", type: i13.LetDirective, selector: "[rxLet]", inputs: ["rxLet", "rxLetStrategy", "rxLetComplete", "rxLetError", "rxLetSuspense", "rxLetContextTrigger", "rxLetCompleteTrigger", "rxLetErrorTrigger", "rxLetSuspenseTrigger", "rxLetNextTrigger", "rxLetRenderCallback", "rxLetParent", "rxLetPatchZone"], outputs: ["rendered"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], animations: [fadeInSlowAnimation], changeDetection: i0.ChangeDetectionStrategy.OnPush });
24208
24319
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: MenuComponent, decorators: [{
24209
24320
  type: Component,
24210
- args: [{ selector: 'app-menu', animations: [fadeInSlowAnimation], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ion-split-pane #splitPane [contentId]=\"contentId\" (swiperight)=\"onSwipeRight($event)\">\n\n <ion-menu [id]=\"id\" [menuId]=\"menuId\" [contentId]=\"contentId\">\n <ion-header>\n\n <ion-toolbar *ngIf=\"!loading && isLogin; else notLogin\"\n @fadeInSlowAnimation\n class=\"user-toolbar\">\n <ion-grid>\n <ion-row>\n <ion-col size=\"4\">\n <button type=\"button\" mat-flat-button\n class=\"user-avatar\" [class.primary]=\"!accountAvatar\"\n [style.background-image]=\"'url('+(accountAvatar||'./assets/img/person.png')+')'\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"ion-color-primary\"\n (click)=\"close()\"\n [title]=\"'MENU.BTN_MY_ACCOUNT'|translate\">\n </button>\n </ion-col>\n <ion-col size=\"8\" class=\"user-logo\">\n <img *ngIf=\"logo; else noLogo\" src=\"{{logo}}\" [title]=\"'APP_NAME'|translate: {appName: appName}\" width=\"108px\"/>\n <ng-template #noLogo>\n <span style=\"width: 108px;\">{{appName}}</span>\n </ng-template>\n </ion-col>\n </ion-row>\n <ion-row class=\"ion-no-padding\">\n <ion-col>\n <button mat-button type=\"button\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"ion-color-primary\"\n (click)=\"close()\"\n [title]=\"'MENU.BTN_MY_ACCOUNT'|translate\">\n <ion-label color=\"primary\" class=\"ion-text-wrap ion-text-start\">\n <h3 class=\"no-margin username\">\n <b>{{accountName}}</b>\n </h3>\n <h4>{{accountEmail}}</h4>\n </ion-label>\n </button>\n </ion-col>\n\n <!-- Insertion headerBottomRight -->\n <ion-col size=\"auto\">\n <ng-container *ngTemplateOutlet=\"headerBottomRight\"></ng-container>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n </ion-toolbar>\n\n <!-- User not logged -->\n <ng-template #notLogin>\n <mat-toolbar class=\"ion-padding\"\n *ngIf=\"!loading\"\n style=\"height: unset; display: block; margin: auto; text-align: center;\">\n <img *ngIf=\"logo\" src=\"{{logo}}\" [title]=\"'APP_NAME'|translate: {appName: appName}\" width=\"150px;\" alt=\"logo\">\n <span *ngIf=\"!logo\" style=\"width: 150px\">{{appName}}</span>\n </mat-toolbar>\n </ng-template>\n </ion-header>\n\n <ion-content [class.has-user-header]=\"isLogin\">\n <!-- will close the menu, after a click, in mobile -->\n <ion-menu-toggle auto-hide=\"false\">\n <ion-list lines=\"none\" *ngIf=\"!loading\">\n <ng-container *rxFor=\"let item of menuService.dataSubject; strategy: rxStrategy; trackBy: trackByFn\">\n <ng-container *ngTemplateOutlet=\"menuItem; context: {$implicit: item, level: 0}\"></ng-container>\n </ng-container>\n </ion-list>\n </ion-menu-toggle>\n </ion-content>\n\n <ion-footer class=\"hidden-xs hidden-sm\">\n <ion-toolbar>\n\n <ion-buttons slot=\"start\">\n <ion-button mat-icon-button color=\"accent\" (click)=\"openAboutModal($event)\">\n <mat-icon slot=\"icon-only\">help_outline</mat-icon>\n </ion-button>\n </ion-buttons>\n\n <ion-title (click)=\"openAboutModal($event)\" color=\"medium\">\n {{'MENU.FOOTER_VERSION_ABOUT'| translate: {version: appVersion} }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <button mat-icon-button color=\"accent\" (click)=\"toggleSplitPaneShow($event)\"\n class=\"hidden-xs hidden-sm hidden-md\"\n [title]=\"(splitPane.when ? 'COMMON.BTN_HIDE_MENU' : 'COMMON.BTN_SHOW_MENU') |translate\">\n <mat-icon><span>{{splitPane.when ? '&#xab;' : '&#xbb;'}}</span></mat-icon>\n </button>\n </ion-buttons>\n </ion-toolbar>\n </ion-footer>\n\n </ion-menu>\n\n <ng-content></ng-content>\n\n</ion-split-pane>\n\n<ng-template #headerBottomRight>\n <ng-content select=\"[headerBottomRight]\"></ng-content>\n</ng-template>\n\n<ng-template #menuItem let-item let-level=\"level\">\n <!-- link -->\n <ion-item *ngIf=\"item.path && !item.action\"\n @fadeInSlowAnimation\n class=\"{{item.cssClass}} {{item.color}}\"\n [class.menu-item-sub]=\"level!==0\"\n tappable\n [routerLink]=\"item.path\"\n [queryParams]=\"item.pathParams\"\n routerDirection=\"root\"\n routerLinkActive=\"selected\"\n [routerLinkActiveOptions]=\"{exact: (item.path === '/' || level !== 0)}\">\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-label *rxLet=\"item.$title; let title\">\n <span *ngIf=\"title; else skeletonText\" [innerHTML]=\"title|translate\"></span>\n <!--<span *ngIf=\"_debug\"> {{item.id}}</span>-->\n </ion-label>\n\n <!-- pin button -->\n <ng-container *ngTemplateOutlet=\"pinButton; context: {$implicit: item, level: level}\"></ng-container>\n\n </ion-item>\n\n <!-- action -->\n <ion-item *ngIf=\"item.action\"\n @fadeInSlowAnimation\n class=\"{{item.cssClass}} {{item.color}} text-1x\"\n [class.menu-item-sub]=\"level!==0\"\n tappable\n (click)=\"doAction(item.action, $event)\">\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-label>\n <span *rxIf=\"item.$title; let title; else skeletonText\" [innerHTML]=\"title|translate\"></span>\n <!--<span *ngIf=\"_debug\"> {{item.id}}</span>-->\n </ion-label>\n\n <!-- pin button -->\n <ng-container *ngTemplateOutlet=\"pinButton; context: {$implicit: item, level: level}\"></ng-container>\n </ion-item>\n\n <!-- divider -->\n <ion-item-divider @fadeInSlowAnimation\n class=\"{{item.cssClass}} {{item.color}}\"\n *ngIf=\"!item.path && !item.action\">\n <ion-label>\n <span *rxIf=\"item.$title; let title; else skeletonText\" [innerHTML]=\"title|translate\"></span>\n </ion-label>\n </ion-item-divider>\n\n <!-- children -->\n <div *ngIf=\"item?.$children\" class=\"sub-menu-container\">\n <ng-container *rxFor=\"let child of item.$children ; strategy: rxStrategy; trackBy: trackByFn\">\n <ng-container *ngTemplateOutlet=\"menuItem; context: {$implicit: child, level: level+1}\"></ng-container>\n </ng-container>\n </div>\n</ng-template>\n\n<ng-template #pinButton let-item let-level=\"level\">\n <!-- pin button -->\n <ion-button *ngIf=\"level > 0\"\n slot=\"end\"\n shape=\"round\" fill=\"clear\" size=\"small\"\n [class.visible-hover]=\"!item.pinned\"\n (click)=\"togglePinned($event, item)\">\n <ion-icon slot=\"icon-only\" [name]=\"item.pinned ? 'pin' : 'pin-outline'\"></ion-icon>\n </ion-button>\n</ng-template>\n\n<ng-template #skeletonText let-width let-animated>\n <ion-skeleton-text [animated]=\"animated\" [style.width.%]=\"width || 60\"></ion-skeleton-text>\n</ng-template>\n", styles: ["@keyframes fadeinout{0%{opacity:0;display:none}75%{opacity:0;display:none}to{opacity:1;display:flex}}ion-menu{--menu-item-margin: 0;--ion-item-background: transparent;--ion-item-divider-background: transparent;--ion-item-icon-color: var(--ion-color-primary-tint);--ion-item-text-color: var(--ion-color-primary-tint);--ion-item-background-selected: var(--ion-color-secondary100);--ion-item-text-color-selected: var(--ion-color-primary);--ion-item-icon-color-selected: var(--ion-color-primary);--ion-item-text-color-disable: var(--ion-color-medium);--ion-item-icon-color-disable: var(--ion-color-medium);--menu-item-background-selected-sub: var(--ion-color-secondary50);--menu-item-border-width-sub: 0 0 1px 0;--menu-item-margin-start-sub: 16px}ion-menu ion-header ion-text{color:var(--ion-color-primary)}ion-menu ion-header ion-toolbar.user-toolbar{--ion-toolbar-height: 128px}ion-menu ion-header .user-avatar{background-size:cover;background-repeat:no-repeat;background-position:center;background-color:var(--ion-color-secondary);border:solid 1px rgba(var(--ion-color-secondary-rgb),.5);overflow:hidden!important;font-size:var(--avatar-size, 60px)!important;line-height:var(--avatar-size, 60px);height:var(--avatar-size, 60px)!important;width:var(--avatar-size, 60px)!important;border-radius:50%;display:inline-block}ion-menu ion-header .user-avatar:hover{background-color:var(--ion-color-secondary-shade);border:solid 2px var(--ion-color-secondary-shade)}ion-menu ion-header .user-logo{text-align:right}ion-menu ion-header .user-logo img{max-width:120px;max-height:var(--avatar-size, 60px);width:auto}ion-menu ion-header .username{padding-top:0;margin-top:0;margin-bottom:0;width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}ion-menu ion-header button[mat-button]{padding:0;text-align:start!important;width:100%}ion-menu ion-content ion-list{min-height:100%;display:flex;flex-direction:column;justify-content:flex-start}ion-menu ion-content ion-list ion-menu-toggle.flex-spacer{flex:1 1 auto;display:flex;flex-direction:column;justify-content:flex-end}ion-menu ion-content ion-list ion-item.primary{--ion-item-icon-color: var(--ion-color-primary-tint);--ion-item-text-color: var(--ion-color-primary-tint)}ion-menu ion-content ion-list ion-item.secondary{--ion-item-icon-color: var(--ion-color-secondary-tint);--ion-item-text-color: var(--ion-color-secondary-tint)}ion-menu ion-content ion-list ion-item.tertiary{--ion-item-icon-color: var(--ion-color-tertiary-tint);--ion-item-text-color: var(--ion-color-tertiary-tint)}ion-menu ion-content ion-list ion-item.danger{--ion-item-icon-color: var(--ion-color-danger-tint);--ion-item-text-color: var(--ion-color-danger-tint)}ion-menu ion-content ion-list ion-item.medium{--ion-item-icon-color: var(--ion-color-medium-shade);--ion-item-text-color: var(--ion-color-medium-shade)}ion-menu ion-content ion-list ion-item.dark{--ion-item-icon-color: var(--ion-color-dark-tint);--ion-item-text-color: var(--ion-color-dark-tint)}ion-menu ion-content ion-list ion-item mat-icon,ion-menu ion-content ion-list ion-item ion-icon{color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor}ion-menu ion-content ion-list ion-item ion-text,ion-menu ion-content ion-list ion-item ion-label{color:var(--ion-item-text-color)!important}ion-menu ion-content ion-list ion-item span{margin-left:var(--menu-item-margin)}ion-menu ion-content ion-list ion-item.selected{background-color:var(--ion-item-background-selected)!important;--color-hover: var(--ion-item-background-selected) !important}ion-menu ion-content ion-list ion-item.selected mat-icon,ion-menu ion-content ion-list ion-item.selected ion-icon{color:var(--ion-item-icon-color-selected)!important;fill:currentColor;stroke:currentColor}ion-menu ion-content ion-list ion-item.selected ion-text,ion-menu ion-content ion-list ion-item.selected ion-label{color:var(--ion-item-text-color-selected)!important}ion-menu ion-content ion-list ion-item.selected:hover{--color-hover: var(--ion-item-background-selected) !important;--ion-item-icon-color-selected: var(--ion-item-icon-color) !important;--ion-item-text-color-selected: var(--ion-item-text-color) !important}ion-menu ion-content ion-list ion-item.selected.menu-item-sub{background-color:var(--menu-item-background-selected-sub)!important}ion-menu ion-content ion-list ion-item.menu-item-sub{--min-height: 34px;--padding-vertical: 8px;--padding-start: 8px;--padding-horizontal: 8px;--inner-border-width: var(--menu-item-border-width-sub);font-size:.8em;overflow:unset}ion-menu ion-content ion-list ion-item.menu-item-sub ion-icon[slot],ion-menu ion-content ion-list ion-item.menu-item-sub mat-icon[slot]{font-size:calc(var(--min-height) / 2 - 1px);height:calc(var(--min-height) / 2 - 1px);margin-top:calc(var(--padding-vertical) + 2px);margin-inline-end:calc(var(--padding-horizontal) + 2px);margin-bottom:calc(var(--padding-vertical) + 2px)}ion-menu ion-content ion-list ion-item.menu-item-sub ion-label{margin-top:var(--padding-vertical);margin-bottom:var(--padding-vertical)}ion-menu ion-content ion-list ion-item.menu-item-sub .item-inner{border-width:var(--inner-border-width);border-style:var(--border-style);border-color:var(--border-color);box-shadow:var(--inner-box-shadow)}ion-menu ion-content ion-list ion-item.menu-item-sub ion-button[slot=end] ion-icon[slot=icon-only]{margin:0}ion-menu ion-content ion-list ion-item.menu-item-sub ion-button[slot=end].visible-hover{opacity:0;display:none;animation:fadeinout 1s linear 1 backwards}ion-menu ion-content ion-list ion-item.menu-item-sub:hover ion-button.visible-hover{opacity:1;display:flex}ion-menu ion-content ion-list .sub-menu-container{margin-inline-start:var(--menu-item-margin-start-sub, 16px)}ion-menu ion-footer{display:block!important}ion-menu ion-footer ion-toolbar ion-title{cursor:pointer;font-size:12pt;font-weight:400;text-align:center;padding:0 8px}ion-menu ion-footer ion-toolbar ion-button{--width: 40px}@media screen and (max-width: 767px){ion-menu ion-footer{display:none!important;visibility:hidden!important}}@media screen and (min-width: 768px){ion-menu ion-scroll{overflow-y:auto!important}ion-menu .user-avatar{font-size:var(--avatar-size, 80px)!important;line-height:var(--avatar-size, 80px);height:var(--avatar-size, 80px)!important;width:var(--avatar-size, 80px)!important}ion-menu .user-logo img{max-height:var(--avatar-size, 80px)}}\n"] }]
24321
+ args: [{ selector: 'app-menu', animations: [fadeInSlowAnimation], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ion-split-pane #splitPane [contentId]=\"contentId\" (swiperight)=\"onSwipeRight($event)\">\n\n <ion-menu [id]=\"id\" [menuId]=\"menuId\" [contentId]=\"contentId\">\n <ion-header>\n\n <ion-toolbar *ngIf=\"!loading && isLogin; else notLogin\"\n @fadeInSlowAnimation\n class=\"user-toolbar\">\n <ion-grid>\n <ion-row>\n <ion-col size=\"4\">\n <button type=\"button\" mat-flat-button\n class=\"user-avatar\" [class.primary]=\"!accountAvatar\"\n [style.background-image]=\"'url('+(accountAvatar||'./assets/img/person.png')+')'\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"ion-color-primary\"\n (click)=\"close()\"\n [title]=\"'MENU.BTN_MY_ACCOUNT'|translate\">\n </button>\n </ion-col>\n <ion-col size=\"8\" class=\"user-logo\">\n <img *ngIf=\"logo; else noLogo\" src=\"{{logo}}\" [title]=\"'APP_NAME'|translate: {appName: appName}\" width=\"108px\"/>\n <ng-template #noLogo>\n <span style=\"width: 108px;\">{{appName}}</span>\n </ng-template>\n </ion-col>\n </ion-row>\n <ion-row class=\"ion-no-padding\">\n <ion-col>\n <button mat-button type=\"button\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"ion-color-primary\"\n (click)=\"close()\"\n [title]=\"'MENU.BTN_MY_ACCOUNT'|translate\">\n <ion-label color=\"primary\" class=\"ion-text-wrap ion-text-start\">\n <h3 class=\"no-margin username\">\n <b>{{accountName}}</b>\n </h3>\n <h4>{{accountEmail}}</h4>\n </ion-label>\n </button>\n </ion-col>\n\n <!-- Insertion headerBottomRight -->\n <ion-col size=\"auto\">\n <ng-container *ngTemplateOutlet=\"headerBottomRight\"></ng-container>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n </ion-toolbar>\n\n <!-- User not logged -->\n <ng-template #notLogin>\n <mat-toolbar class=\"ion-padding\"\n *ngIf=\"!loading\"\n style=\"height: unset; display: block; margin: auto; text-align: center;\">\n <img *ngIf=\"logo\" src=\"{{logo}}\" [title]=\"'APP_NAME'|translate: {appName: appName}\" width=\"150px;\" alt=\"logo\">\n <span *ngIf=\"!logo\" style=\"width: 150px\">{{appName}}</span>\n </mat-toolbar>\n </ng-template>\n </ion-header>\n\n <ion-content [class.has-user-header]=\"isLogin\">\n <!-- will close the menu, after a click, in mobile -->\n <ion-menu-toggle auto-hide=\"false\">\n <ion-list lines=\"none\" *ngIf=\"!loading\">\n <ng-container *rxFor=\"let item of menuService.dataSubject; strategy: rxStrategy; trackBy: trackByFn\">\n <ng-container *ngTemplateOutlet=\"menuItem; context: {$implicit: item, level: 0}\"></ng-container>\n </ng-container>\n </ion-list>\n </ion-menu-toggle>\n </ion-content>\n\n <ion-footer class=\"hidden-xs hidden-sm\">\n <ion-toolbar>\n\n <ion-buttons slot=\"start\">\n <ion-button mat-icon-button color=\"accent\" (click)=\"menuService.openAboutModal()\">\n <mat-icon slot=\"icon-only\">help_outline</mat-icon>\n </ion-button>\n </ion-buttons>\n\n <ion-title (click)=\"menuService.openAboutModal()\" color=\"medium\">\n {{'MENU.FOOTER_VERSION_ABOUT'| translate: {version: appVersion} }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <button mat-icon-button color=\"accent\" (click)=\"toggleSplitPaneShow($event)\"\n class=\"hidden-xs hidden-sm hidden-md\"\n [title]=\"(splitPane.when ? 'COMMON.BTN_HIDE_MENU' : 'COMMON.BTN_SHOW_MENU') |translate\">\n <mat-icon><span>{{splitPane.when ? '&#xab;' : '&#xbb;'}}</span></mat-icon>\n </button>\n </ion-buttons>\n </ion-toolbar>\n </ion-footer>\n\n </ion-menu>\n\n <ng-content></ng-content>\n\n</ion-split-pane>\n\n<ng-template #headerBottomRight>\n <ng-content select=\"[headerBottomRight]\"></ng-content>\n</ng-template>\n\n<ng-template #menuItem let-item let-level=\"level\">\n\n <!-- divider -->\n <ion-item-divider *ngIf=\"item.divider; else tappable\"\n class=\"{{item.cssClass}} {{item.color}}\"\n @fadeInSlowAnimation>\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-label>\n <span *rxIf=\"item.$title; let title; else skeletonText\" [innerHTML]=\"title|translate\"></span>\n </ion-label>\n </ion-item-divider>\n\n <ng-template #tappable>\n\n <!-- link -->\n <ion-item *ngIf=\"item.path && !item.action\"\n @fadeInSlowAnimation\n class=\"{{item.cssClass}} {{item.color}}\"\n [class.menu-item-sub]=\"level!==0\"\n tappable\n [routerLink]=\"item.path\"\n [queryParams]=\"item.pathParams\"\n routerDirection=\"root\"\n routerLinkActive=\"selected\"\n [routerLinkActiveOptions]=\"{exact: (item.path === '/' || level !== 0)}\">\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-label *rxLet=\"item.$title; let title\">\n <span *ngIf=\"title; else skeletonText\" [innerHTML]=\"title|translate\"></span>\n <!--<span *ngIf=\"_debug\"> {{item.id}}</span>-->\n </ion-label>\n\n <!-- pin button -->\n <ng-container *ngTemplateOutlet=\"pinButton; context: {$implicit: item, level: level}\"></ng-container>\n\n </ion-item>\n\n <!-- action -->\n <ion-item *ngIf=\"item.action\"\n @fadeInSlowAnimation\n class=\"{{item.cssClass}} {{item.color}} text-1x\"\n [class.menu-item-sub]=\"level!==0\"\n tappable\n (click)=\"executeAction($event, item)\">\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-label>\n <span *rxIf=\"item.$title; let title; else skeletonText\" [innerHTML]=\"title|translate\"></span>\n <!--<span *ngIf=\"_debug\"> {{item.id}}</span>-->\n </ion-label>\n\n <!-- pin button -->\n <ng-container *ngTemplateOutlet=\"pinButton; context: {$implicit: item, level: level}\"></ng-container>\n </ion-item>\n </ng-template>\n\n\n\n <!-- children -->\n <div *ngIf=\"item?.$children\" class=\"sub-menu-container\">\n <ng-container *rxFor=\"let child of item.$children ; strategy: rxStrategy; trackBy: trackByFn\">\n <ng-container *ngTemplateOutlet=\"menuItem; context: {$implicit: child, level: level+1}\"></ng-container>\n </ng-container>\n </div>\n</ng-template>\n\n<ng-template #pinButton let-item>\n <!-- pin button -->\n <ion-button *ngIf=\"item.pinnable\"\n slot=\"end\"\n shape=\"round\" fill=\"clear\" size=\"small\"\n [class.visible-hover]=\"!item.pinned\"\n (click)=\"togglePinned($event, item)\">\n <ion-icon slot=\"icon-only\" [name]=\"item.pinned ? 'pin' : 'pin-outline'\"></ion-icon>\n </ion-button>\n</ng-template>\n\n<ng-template #skeletonText let-width let-animated>\n <ion-skeleton-text [animated]=\"animated\" [style.width.%]=\"width || 60\"></ion-skeleton-text>\n</ng-template>\n", styles: ["@keyframes fadeinout{0%{opacity:0;display:none}75%{opacity:0;display:none}to{opacity:1;display:flex}}ion-menu{--menu-item-margin: 0;--ion-item-background: transparent;--ion-item-divider-background: transparent;--ion-item-icon-color: var(--ion-color-primary-tint);--ion-item-text-color: var(--ion-color-primary-tint);--ion-item-background-selected: var(--ion-color-secondary100);--ion-item-text-color-selected: var(--ion-color-primary);--ion-item-icon-color-selected: var(--ion-color-primary);--ion-item-text-color-disable: var(--ion-color-medium);--ion-item-icon-color-disable: var(--ion-color-medium);--menu-item-background-selected-sub: var(--ion-color-secondary50);--menu-item-border-width-sub: 0 0 1px 0;--menu-item-margin-start-sub: 16px}ion-menu ion-header ion-text{color:var(--ion-color-primary)}ion-menu ion-header ion-toolbar.user-toolbar{--ion-toolbar-height: 128px}ion-menu ion-header .user-avatar{background-size:cover;background-repeat:no-repeat;background-position:center;background-color:var(--ion-color-secondary);border:solid 1px rgba(var(--ion-color-secondary-rgb),.5);overflow:hidden!important;font-size:var(--avatar-size, 60px)!important;line-height:var(--avatar-size, 60px);height:var(--avatar-size, 60px)!important;width:var(--avatar-size, 60px)!important;border-radius:50%;display:inline-block}ion-menu ion-header .user-avatar:hover{background-color:var(--ion-color-secondary-shade);border:solid 2px var(--ion-color-secondary-shade)}ion-menu ion-header .user-logo{text-align:right}ion-menu ion-header .user-logo img{max-width:120px;max-height:var(--avatar-size, 60px);width:auto}ion-menu ion-header .username{padding-top:0;margin-top:0;margin-bottom:0;width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}ion-menu ion-header button[mat-button]{padding:0;text-align:start!important;width:100%}ion-menu ion-content ion-list{min-height:100%;display:flex;flex-direction:column;justify-content:flex-start}ion-menu ion-content ion-list ion-menu-toggle.flex-spacer{flex:1 1 auto;display:flex;flex-direction:column;justify-content:flex-end}ion-menu ion-content ion-list ion-item.primary{--ion-item-icon-color: var(--ion-color-primary-tint);--ion-item-text-color: var(--ion-color-primary-tint)}ion-menu ion-content ion-list ion-item.secondary{--ion-item-icon-color: var(--ion-color-secondary-tint);--ion-item-text-color: var(--ion-color-secondary-tint)}ion-menu ion-content ion-list ion-item.tertiary{--ion-item-icon-color: var(--ion-color-tertiary-tint);--ion-item-text-color: var(--ion-color-tertiary-tint)}ion-menu ion-content ion-list ion-item.danger{--ion-item-icon-color: var(--ion-color-danger-tint);--ion-item-text-color: var(--ion-color-danger-tint)}ion-menu ion-content ion-list ion-item.medium{--ion-item-icon-color: var(--ion-color-medium-shade);--ion-item-text-color: var(--ion-color-medium-shade)}ion-menu ion-content ion-list ion-item.dark{--ion-item-icon-color: var(--ion-color-dark-tint);--ion-item-text-color: var(--ion-color-dark-tint)}ion-menu ion-content ion-list ion-item mat-icon,ion-menu ion-content ion-list ion-item ion-icon{color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor}ion-menu ion-content ion-list ion-item ion-text,ion-menu ion-content ion-list ion-item ion-label{color:var(--ion-item-text-color)!important}ion-menu ion-content ion-list ion-item span{margin-left:var(--menu-item-margin)}ion-menu ion-content ion-list ion-item.selected{background-color:var(--ion-item-background-selected)!important;--color-hover: var(--ion-item-background-selected) !important}ion-menu ion-content ion-list ion-item.selected mat-icon,ion-menu ion-content ion-list ion-item.selected ion-icon{color:var(--ion-item-icon-color-selected)!important;fill:currentColor;stroke:currentColor}ion-menu ion-content ion-list ion-item.selected ion-text,ion-menu ion-content ion-list ion-item.selected ion-label{color:var(--ion-item-text-color-selected)!important}ion-menu ion-content ion-list ion-item.selected:hover{--color-hover: var(--ion-item-background-selected) !important;--ion-item-icon-color-selected: var(--ion-item-icon-color) !important;--ion-item-text-color-selected: var(--ion-item-text-color) !important}ion-menu ion-content ion-list ion-item.selected.menu-item-sub{background-color:var(--menu-item-background-selected-sub)!important}ion-menu ion-content ion-list ion-item.menu-item-sub{--min-height: 34px;--padding-vertical: 8px;--padding-start: 8px;--padding-horizontal: 8px;--inner-border-width: var(--menu-item-border-width-sub);font-size:.8em;overflow:unset}ion-menu ion-content ion-list ion-item.menu-item-sub ion-icon[slot],ion-menu ion-content ion-list ion-item.menu-item-sub mat-icon[slot]{font-size:calc(var(--min-height) / 2 - 1px);height:calc(var(--min-height) / 2 - 1px);margin-top:calc(var(--padding-vertical) + 2px);margin-inline-end:calc(var(--padding-horizontal) + 2px);margin-bottom:calc(var(--padding-vertical) + 2px)}ion-menu ion-content ion-list ion-item.menu-item-sub ion-label{margin-top:var(--padding-vertical);margin-bottom:var(--padding-vertical)}ion-menu ion-content ion-list ion-item.menu-item-sub .item-inner{border-width:var(--inner-border-width);border-style:var(--border-style);border-color:var(--border-color);box-shadow:var(--inner-box-shadow)}ion-menu ion-content ion-list ion-item.menu-item-sub ion-button[slot=end] ion-icon[slot=icon-only]{margin:0}ion-menu ion-content ion-list ion-item.menu-item-sub ion-button[slot=end].visible-hover{opacity:0;display:none;animation:fadeinout 1s linear 1 backwards}ion-menu ion-content ion-list ion-item.menu-item-sub:hover ion-button.visible-hover{opacity:1;display:flex}ion-menu ion-content ion-list .sub-menu-container{margin-inline-start:var(--menu-item-margin-start-sub, 16px)}ion-menu ion-footer{display:block!important}ion-menu ion-footer ion-toolbar ion-title{cursor:pointer;font-size:12pt;font-weight:400;text-align:center;padding:0 8px}ion-menu ion-footer ion-toolbar ion-button{--width: 40px}@media screen and (max-width: 767px){ion-menu ion-footer{display:none!important;visibility:hidden!important}}@media screen and (min-width: 768px){ion-menu ion-scroll{overflow-y:auto!important}ion-menu .user-avatar{font-size:var(--avatar-size, 80px)!important;line-height:var(--avatar-size, 80px);height:var(--avatar-size, 80px)!important;width:var(--avatar-size, 80px)!important}ion-menu .user-logo img{max-height:var(--avatar-size, 80px)}}\n"] }]
24211
24322
  }], ctorParameters: function () {
24212
24323
  return [{ type: PlatformService }, { type: AccountService }, { type: i2.NavController }, { type: i2.MenuController }, { type: i2.ModalController }, { type: i2.AlertController }, { type: i1$1.TranslateService }, { type: i0.ChangeDetectorRef }, { type: MenuService }, { type: i1$5.Router }, { type: undefined, decorators: [{
24213
24324
  type: Inject,
@@ -36025,33 +36136,23 @@ class UsersPage extends AppTable {
36025
36136
  debounceTime(500), tap((json) => this.settings.savePageSetting(this.settingsId, json, SETTINGS_FILTER)))
36026
36137
  .subscribe());
36027
36138
  if (this.canSendMessage && this.debug) {
36028
- let subActionsItems;
36029
- let actionIcon = yield this.menuService.addSubMenuItem({
36030
- parentPath: '/admin/users',
36031
- path: '/admin/users',
36032
- pathParams: { action: 'actions' },
36033
- matIcon: 'expand_more',
36034
- title: 'Actions',
36035
- //pinned: true,
36036
- action: () => __awaiter(this, void 0, void 0, function* () {
36037
- if (actionIcon.matIcon === 'expand_more') {
36038
- actionIcon.matIcon = 'expand_less';
36039
- subActionsItems = yield this.menuService.addSubMenuItems([
36040
- {
36041
- parentPath: '/admin/users?action=actions',
36042
- path: '/admin/users?action=send',
36043
- icon: 'mail',
36044
- title: 'USER.LIST.BTN_SEND_MESSAGE',
36045
- action: () => this.openComposeMessageModal(),
36046
- },
36047
- ]);
36048
- }
36049
- else {
36050
- actionIcon.matIcon = 'expand_more';
36051
- yield this.menuService.removeSubMenuItems(subActionsItems);
36052
- }
36053
- }),
36054
- });
36139
+ yield this.menuService.addSubMenuItems([
36140
+ {
36141
+ parentPath: '/admin/users',
36142
+ path: '/admin/users',
36143
+ pathParams: { action: 'expand' },
36144
+ matIcon: 'expand_more',
36145
+ title: 'USER.LIST.BTN_ACTIONS',
36146
+ action: 'expand',
36147
+ },
36148
+ {
36149
+ parentPath: '/admin/users?action=expand',
36150
+ path: '/admin/users?action=send',
36151
+ icon: 'mail',
36152
+ title: 'USER.LIST.BTN_SEND_MESSAGE',
36153
+ action: () => this.openComposeMessageModal(),
36154
+ },
36155
+ ], { skipIfExists: false });
36055
36156
  }
36056
36157
  // Restore filter from settings, or load all
36057
36158
  this.restoreFilterOrLoad();