@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.
@@ -11546,10 +11546,12 @@ class TreeItemEntityUtils {
11546
11546
  if (isEmptyArray(node.children))
11547
11547
  return []; // Skip
11548
11548
  // Delete children
11549
- const deletedBatches = node.children.filter(filterFn);
11550
- node.children = node.children.filter((c) => !deletedBatches.includes(c));
11549
+ const deletedChildren = node.children.filter(filterFn);
11550
+ node.children = node.children.filter((c) => !deletedChildren.includes(c));
11551
+ // TODO: add an options to clean the parent
11552
+ //deletedChildren.forEach(child => child.parent = null);
11551
11553
  // Recursive call, in still existing children
11552
- return node.children.reduce((res, c) => res.concat(...this.deleteRecursively(c, filterFn)), deletedBatches);
11554
+ return node.children.reduce((res, c) => res.concat(...this.deleteRecursively(c, filterFn)), deletedChildren);
11553
11555
  }
11554
11556
  /**
11555
11557
  * Visit each node, from root to leaf
@@ -11920,10 +11922,10 @@ const environment = Object.freeze({
11920
11922
  helpUrl: 'https://gitlab.ifremer.fr/sih-public/sumaris/sumaris-doc/-/blob/master/user-manual/index_fr.md',
11921
11923
  // Development
11922
11924
  defaultAuthValues: {
11923
- // Basic auth (using Person.username)
11924
- // username: 'admq2', password: 'q22006'
11925
- // Token auth (using Person.pubkey)
11926
- username: 'admin@sumaris.net', password: 'admin',
11925
+ // Basic auth (using Person.username)
11926
+ // username: 'admq2', password: 'q22006'
11927
+ // Token auth (using Person.pubkey)
11928
+ //username: 'admin@sumaris.net', password: 'admin',
11927
11929
  },
11928
11930
  account: {
11929
11931
  enableListenChanges: true,
@@ -22800,80 +22802,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImpor
22800
22802
  args: [{ providedIn: 'root' }]
22801
22803
  }] });
22802
22804
 
22803
- const APP_ABOUT_DEVELOPERS = new InjectionToken('aboutDevelopers');
22804
- const APP_ABOUT_PARTNERS = new InjectionToken('aboutPartners');
22805
- class AboutModal {
22806
- constructor(translate, modalController, configService, environment, developers, partners) {
22807
- this.translate = translate;
22808
- this.modalController = modalController;
22809
- this.configService = configService;
22810
- this.environment = environment;
22811
- this.developers = developers;
22812
- this.partners = partners;
22813
- this._subscription = new Subscription();
22814
- this.name = translate.instant('APP_NAME');
22815
- if (this.name === 'APP_NAME') {
22816
- // Not translated: use name from the environnement
22817
- this.name = environment && environment.name && capitalizeFirstLetter(environment.name) || undefined;
22818
- }
22819
- this.version = environment && environment.version;
22820
- this.sourceUrl = environment && environment.sourceUrl;
22821
- this.reportIssueUrl = environment && environment.reportIssueUrl;
22822
- this.forumUrl = environment && environment.forumUrl;
22823
- this.helpUrl = environment && environment.helpUrl;
22824
- this.config$ = configService.config;
22825
- this._subscription.add(this.config$.subscribe(config => this.setConfig(config)));
22826
- }
22827
- ngOnDestroy() {
22828
- this._subscription.unsubscribe();
22829
- }
22830
- async close() {
22831
- await this.modalController.dismiss();
22832
- }
22833
- /* -- protected functions -- */
22834
- setConfig(config) {
22835
- this.forumUrl = config?.getProperty(CORE_CONFIG_OPTIONS.FORUM_URL) || this.environment.forumUrl;
22836
- this.helpUrl = config?.getProperty(CORE_CONFIG_OPTIONS.HELP_URL) || this.environment.helpUrl;
22837
- }
22838
- }
22839
- 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 });
22840
- 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" }] });
22841
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: AboutModal, decorators: [{
22842
- type: Component,
22843
- 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"] }]
22844
- }], ctorParameters: function () { return [{ type: i1$1.TranslateService }, { type: i2.ModalController }, { type: ConfigService }, { type: undefined, decorators: [{
22845
- type: Optional
22846
- }, {
22847
- type: Inject,
22848
- args: [ENVIRONMENT]
22849
- }] }, { type: undefined, decorators: [{
22850
- type: Optional
22851
- }, {
22852
- type: Inject,
22853
- args: [APP_ABOUT_DEVELOPERS]
22854
- }] }, { type: undefined, decorators: [{
22855
- type: Optional
22856
- }, {
22857
- type: Inject,
22858
- args: [APP_ABOUT_PARTNERS]
22859
- }] }]; } });
22860
-
22861
22805
  var _a;
22862
- class MenuItem extends Entity {
22806
+ var MenuItem_1;
22807
+ // @dynamic
22808
+ let MenuItem = MenuItem_1 = class MenuItem extends Entity {
22863
22809
  constructor() {
22864
22810
  super(...arguments);
22865
22811
  this.$children = new BehaviorSubject([]);
22866
22812
  this.$title = new BehaviorSubject(undefined);
22867
22813
  }
22868
- static fromObject(source, opts) {
22869
- if (!source)
22870
- return null;
22871
- if (source instanceof MenuItem)
22872
- return source;
22873
- const target = new MenuItem();
22874
- target.fromObject(source, opts);
22875
- return target;
22876
- }
22877
22814
  fromObject(source, opts) {
22878
22815
  this.id = source.id;
22879
22816
  this.title = source.title;
@@ -22900,12 +22837,13 @@ class MenuItem extends Entity {
22900
22837
  this.cssClass = source.cssClass;
22901
22838
  this.ifProperty = source.ifProperty;
22902
22839
  this.pinned = toBoolean(source.pinned, false);
22840
+ this.static = isNotNil(source.static) ? source.static : undefined; // If undefined, will be set by service
22903
22841
  // Icon
22904
22842
  this.icon = source.icon;
22905
22843
  this.matIcon = source.matIcon;
22906
22844
  this.matSvgIcon = source.matSvgIcon;
22907
22845
  if (!opts || opts.withChildren === false) {
22908
- this.children = (source.children || []).map((json) => MenuItem.fromObject(json, opts)) || [];
22846
+ this.children = (source.children || []).map((json) => MenuItem_1.fromObject(json, opts)) || [];
22909
22847
  }
22910
22848
  }
22911
22849
  asObject(opts) {
@@ -22947,6 +22885,12 @@ class MenuItem extends Entity {
22947
22885
  get detached() {
22948
22886
  return !this.parentPath || !this.parent;
22949
22887
  }
22888
+ get visible() {
22889
+ return this.static || !this.detached;
22890
+ }
22891
+ get expanded() {
22892
+ return this.action !== 'expand' || this.matIcon === 'expand_less';
22893
+ }
22950
22894
  get ancestor() {
22951
22895
  if (!this.parent)
22952
22896
  return null; // root = not ancestor
@@ -22954,12 +22898,21 @@ class MenuItem extends Entity {
22954
22898
  return this; // first sub menu = ancestor
22955
22899
  return this.parent?.ancestor; // loop
22956
22900
  }
22901
+ get divider() {
22902
+ return !this.action && !this.path;
22903
+ }
22904
+ get pinnable() {
22905
+ return !this.static && (typeof this.action !== 'function');
22906
+ }
22957
22907
  isPinned() {
22958
22908
  if (this.pinned)
22959
22909
  return true;
22960
22910
  return (this.children || []).some(child => child.isPinned());
22961
22911
  }
22962
- }
22912
+ };
22913
+ MenuItem = MenuItem_1 = __decorate([
22914
+ EntityClass({ typename: 'MenuItemVO' })
22915
+ ], MenuItem);
22963
22916
  class MenuItems {
22964
22917
  /**
22965
22918
  * Compare, without checking the children
@@ -23041,11 +22994,16 @@ class MenuItems {
23041
22994
  static checkIfSubMenuVisible(item, currentPathParam) {
23042
22995
  if (!item.parentPath)
23043
22996
  return false; // Should have a parent
22997
+ if (item.static)
22998
+ return true; // Always keep static items
23044
22999
  if (item.isPinned())
23045
23000
  return true; // Always keep pinned items
23046
23001
  // Keep each menu items in the scope of the current url path
23047
23002
  if (currentPathParam?.path.startsWith(item.path))
23048
23003
  return true;
23004
+ // Keep if parent is an expanded item
23005
+ if (item.parent?.action === 'expand' && item.parent.expanded)
23006
+ return true;
23049
23007
  // Not visible
23050
23008
  return false;
23051
23009
  }
@@ -23078,19 +23036,84 @@ class MenuItems {
23078
23036
  }
23079
23037
  _a = MenuItems;
23080
23038
  MenuItems.sortByTabOrId = _a.sortByParamOrIdComparator('tab');
23039
+ /**
23040
+ * Options to configure specific behavior on projects
23041
+ */
23081
23042
  class MenuOptions {
23082
23043
  }
23083
23044
 
23045
+ const APP_ABOUT_DEVELOPERS = new InjectionToken('aboutDevelopers');
23046
+ const APP_ABOUT_PARTNERS = new InjectionToken('aboutPartners');
23047
+ class AboutModal {
23048
+ constructor(translate, modalController, configService, environment, developers, partners) {
23049
+ this.translate = translate;
23050
+ this.modalController = modalController;
23051
+ this.configService = configService;
23052
+ this.environment = environment;
23053
+ this.developers = developers;
23054
+ this.partners = partners;
23055
+ this._subscription = new Subscription();
23056
+ this.name = translate.instant('APP_NAME');
23057
+ if (this.name === 'APP_NAME') {
23058
+ // Not translated: use name from the environnement
23059
+ this.name = environment && environment.name && capitalizeFirstLetter(environment.name) || undefined;
23060
+ }
23061
+ this.version = environment && environment.version;
23062
+ this.sourceUrl = environment && environment.sourceUrl;
23063
+ this.reportIssueUrl = environment && environment.reportIssueUrl;
23064
+ this.forumUrl = environment && environment.forumUrl;
23065
+ this.helpUrl = environment && environment.helpUrl;
23066
+ this.config$ = configService.config;
23067
+ this._subscription.add(this.config$.subscribe(config => this.setConfig(config)));
23068
+ }
23069
+ ngOnDestroy() {
23070
+ this._subscription.unsubscribe();
23071
+ }
23072
+ async close() {
23073
+ await this.modalController.dismiss();
23074
+ }
23075
+ /* -- protected functions -- */
23076
+ setConfig(config) {
23077
+ this.forumUrl = config?.getProperty(CORE_CONFIG_OPTIONS.FORUM_URL) || this.environment.forumUrl;
23078
+ this.helpUrl = config?.getProperty(CORE_CONFIG_OPTIONS.HELP_URL) || this.environment.helpUrl;
23079
+ }
23080
+ }
23081
+ 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 });
23082
+ 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" }] });
23083
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: AboutModal, decorators: [{
23084
+ type: Component,
23085
+ 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"] }]
23086
+ }], ctorParameters: function () { return [{ type: i1$1.TranslateService }, { type: i2.ModalController }, { type: ConfigService }, { type: undefined, decorators: [{
23087
+ type: Optional
23088
+ }, {
23089
+ type: Inject,
23090
+ args: [ENVIRONMENT]
23091
+ }] }, { type: undefined, decorators: [{
23092
+ type: Optional
23093
+ }, {
23094
+ type: Inject,
23095
+ args: [APP_ABOUT_DEVELOPERS]
23096
+ }] }, { type: undefined, decorators: [{
23097
+ type: Optional
23098
+ }, {
23099
+ type: Inject,
23100
+ args: [APP_ABOUT_PARTNERS]
23101
+ }] }]; } });
23102
+
23084
23103
  const DEFAULT_MENU_SHOW_WHEN = 'lg';
23085
23104
  const APP_MENU_ITEMS = new InjectionToken('menuItems');
23086
23105
  const APP_MENU_OPTIONS = new InjectionToken('menuOptions');
23087
23106
  class MenuService extends StartableObservableService {
23088
- constructor(settings, configService, accountService, router, environment, options, staticItems) {
23107
+ constructor(settings, configService, accountService, router, modalCtrl, alertController, navController, translate, environment, options, staticItems) {
23089
23108
  super(settings);
23090
23109
  this.settings = settings;
23091
23110
  this.configService = configService;
23092
23111
  this.accountService = accountService;
23093
23112
  this.router = router;
23113
+ this.modalCtrl = modalCtrl;
23114
+ this.alertController = alertController;
23115
+ this.navController = navController;
23116
+ this.translate = translate;
23094
23117
  this.environment = environment;
23095
23118
  this.accountChanges = new BehaviorSubject(undefined);
23096
23119
  this.onPinned = new EventEmitter(true);
@@ -23102,14 +23125,28 @@ class MenuService extends StartableObservableService {
23102
23125
  this._$enabled = new BehaviorSubject(true);
23103
23126
  this._$listenRoute = new BehaviorSubject(false);
23104
23127
  this._options = {
23105
- enableSubMenuIcon: true,
23106
23128
  ...options,
23107
- enableSubMenus: options?.enableSubMenus !== false && !this.settings.mobile
23129
+ subMenu: {
23130
+ disableIcon: false,
23131
+ savePinned: true,
23132
+ ...(options?.subMenu || {}),
23133
+ // Force disable on mobile
23134
+ enable: options?.subMenu?.enable !== false && !this.settings.mobile,
23135
+ }
23108
23136
  };
23109
- this._$subMenuEnabled = new BehaviorSubject(this._options.enableSubMenus);
23137
+ this._$enableSubMenu = new BehaviorSubject(this._options.subMenu.enable);
23110
23138
  this._debug = !environment.production;
23111
- this._staticItems = (staticItems || []).map(source => this.fromObject(source, { computeId: true, fillParent: true }));
23112
23139
  this._detachedSubItems = [];
23140
+ this._staticItems = (staticItems || []).map(source => {
23141
+ const target = this.fromObject({ static: true, ...source }, { computeId: true, fillParent: true, fillDefaultFromParent: true });
23142
+ const detachedItems = TreeItemEntityUtils.deleteRecursively(target, (item) => item.static !== true);
23143
+ if (detachedItems?.length) {
23144
+ console.debug(this._logPrefix + 'Detached init items', detachedItems);
23145
+ detachedItems.forEach(item => this._detachToParent(item));
23146
+ this._detachedSubItems.push(...detachedItems);
23147
+ }
23148
+ return target;
23149
+ });
23113
23150
  }
23114
23151
  get opened() {
23115
23152
  return this._$opened.asObservable();
@@ -23121,10 +23158,7 @@ class MenuService extends StartableObservableService {
23121
23158
  return this._$enabled.asObservable();
23122
23159
  }
23123
23160
  get subMenuEnabled() {
23124
- return this._$subMenuEnabled.value;
23125
- }
23126
- get subMenuIconEnabled() {
23127
- return this._options.enableSubMenuIcon;
23161
+ return this._$enableSubMenu.value;
23128
23162
  }
23129
23163
  get subItems() {
23130
23164
  return (this.data || []).reduce((res, rootElement) => res.concat(rootElement.children), []);
@@ -23136,7 +23170,7 @@ class MenuService extends StartableObservableService {
23136
23170
  this._$enabled.next(value);
23137
23171
  }
23138
23172
  enableSubMenu(value) {
23139
- this._$subMenuEnabled.next(value);
23173
+ this._$enableSubMenu.next(value);
23140
23174
  }
23141
23175
  setSplitPaneShowWhen(value) {
23142
23176
  if (this._$splitPaneWhen.value !== value) {
@@ -23156,8 +23190,8 @@ class MenuService extends StartableObservableService {
23156
23190
  // Compute id if need
23157
23191
  if (opts?.computeId && isNil(target.id))
23158
23192
  target.id = this._computeNewId();
23159
- // Remove icon if not enabled
23160
- if (!this.subMenuIconEnabled) {
23193
+ // Remove icon if disabled
23194
+ if (this._options.subMenu.disableIcon) {
23161
23195
  Object.assign(target, { icon: undefined, matIcon: undefined, matSvgIcon: undefined });
23162
23196
  }
23163
23197
  // Recursive
@@ -23165,6 +23199,21 @@ class MenuService extends StartableObservableService {
23165
23199
  // Fill parent (once, when the full tree is ready)
23166
23200
  if (opts?.fillParent) {
23167
23201
  TreeItemEntityUtils.treeFillParent(target);
23202
+ // Fill defaults from the parent (once filled)
23203
+ if (opts?.fillDefaultFromParent) {
23204
+ TreeItemEntityUtils.visit(target, (item) => {
23205
+ if (!item.parent)
23206
+ return; // Skip if no parent
23207
+ item.parentPath = item.parentPath || item.parent.path;
23208
+ item.parentPathParams = item.parentPathParams || item.parent.pathParams;
23209
+ item.static = toBoolean(item.static, item.parent.static);
23210
+ // Compute path, from parent path + action
23211
+ if (!item.path && !!item.action) {
23212
+ item.path = item.parent.path;
23213
+ item.pathParams = (typeof item.action === 'string') ? { action: item.action } : { actionId: item.id.toString(), actionTitle: item.title };
23214
+ }
23215
+ });
23216
+ }
23168
23217
  }
23169
23218
  return target;
23170
23219
  }
@@ -23215,6 +23264,9 @@ class MenuService extends StartableObservableService {
23215
23264
  console.warn(this._logPrefix + `Sub menu {${source.path}} cannot be found - unable to toggle pinned!`);
23216
23265
  return;
23217
23266
  }
23267
+ // Skip if static
23268
+ if (item.static)
23269
+ return;
23218
23270
  // Unpinned
23219
23271
  if (item.pinned) {
23220
23272
  item.pinned = false;
@@ -23231,6 +23283,93 @@ class MenuService extends StartableObservableService {
23231
23283
  }
23232
23284
  this.onPinned.emit(item);
23233
23285
  }
23286
+ async executeAction(event, item) {
23287
+ const action = item?.action;
23288
+ if (!action)
23289
+ return; // skip
23290
+ if (typeof action === 'string') {
23291
+ switch (action) {
23292
+ case 'logout':
23293
+ await this.logout();
23294
+ break;
23295
+ case 'about':
23296
+ await this.openAboutModal();
23297
+ break;
23298
+ case 'expand':
23299
+ await this.toggleExpandableItem(item);
23300
+ if (item.path) {
23301
+ await this.navController.navigateRoot(item.path, {
23302
+ queryParams: item.pathParams
23303
+ });
23304
+ }
23305
+ break;
23306
+ default: {
23307
+ if (item.path) {
23308
+ await this.navController.navigateRoot(item.path, {
23309
+ queryParams: item.pathParams
23310
+ });
23311
+ }
23312
+ else {
23313
+ throw new Error('Unknown action: ' + action);
23314
+ }
23315
+ }
23316
+ }
23317
+ }
23318
+ else if (typeof action === 'function') {
23319
+ action(event);
23320
+ }
23321
+ }
23322
+ async logout() {
23323
+ const translations = await this.translate.instant([
23324
+ 'AUTH.LOGOUT.CONFIRM_TITLE',
23325
+ 'AUTH.LOGOUT.CONFIRM_MESSAGE',
23326
+ 'COMMON.BTN_CANCEL',
23327
+ 'AUTH.LOGOUT.BTN_CONFIRM',
23328
+ ]);
23329
+ const alert = await this.alertController.create({
23330
+ header: translations['AUTH.LOGOUT.CONFIRM_TITLE'],
23331
+ message: translations['AUTH.LOGOUT.CONFIRM_MESSAGE'],
23332
+ buttons: [
23333
+ {
23334
+ text: translations['COMMON.BTN_CANCEL'],
23335
+ role: 'cancel',
23336
+ cssClass: 'secondary',
23337
+ },
23338
+ {
23339
+ text: translations['AUTH.LOGOUT.BTN_CONFIRM'],
23340
+ cssClass: 'ion-color-primary',
23341
+ handler: () => {
23342
+ this.accountService.logout();
23343
+ // Back to home
23344
+ setTimeout(() => this.navController.navigateRoot('/'), 100);
23345
+ },
23346
+ },
23347
+ ],
23348
+ });
23349
+ await alert.present();
23350
+ }
23351
+ async openAboutModal() {
23352
+ const modal = await this.modalCtrl.create({ component: AboutModal });
23353
+ return modal.present();
23354
+ }
23355
+ async toggleExpandableItem(item) {
23356
+ if (!item.expanded) {
23357
+ if (this._debug)
23358
+ console.debug(this._logPrefix + 'Expand item', item);
23359
+ item.matIcon = 'expand_less';
23360
+ // Try to attach some item
23361
+ this._detachedSubItems
23362
+ .filter(childItem => !!this._findParent(childItem, [item]))
23363
+ .forEach(childItem => this._attachMenuToParent(item, childItem));
23364
+ }
23365
+ else {
23366
+ item.matIcon = 'expand_more';
23367
+ this._detachedSubItems = [
23368
+ ...this._detachedSubItems,
23369
+ ...this._detachSubMenuItems({ path: '/', params: {} }, item.children)
23370
+ ];
23371
+ }
23372
+ }
23234
23373
  _markAsOpened() {
23235
23374
  this._$opened.next(true);
23236
23375
  }
@@ -23262,7 +23401,7 @@ class MenuService extends StartableObservableService {
23262
23401
  // Emit account changes event
23263
23402
  this.accountChanges.next(account);
23264
23403
  }));
23265
- 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)))
23404
+ 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))
23266
23405
  .subscribe(pathParam => {
23267
23406
  const detachedSubItems = this._detachSubMenuItems(pathParam);
23268
23407
  if (detachedSubItems?.length) {
@@ -23290,7 +23429,7 @@ class MenuService extends StartableObservableService {
23290
23429
  if (this._debug)
23291
23430
  console.debug(`${this._logPrefix}Loading menu items...`);
23292
23431
  // Save previous children of root items
23293
- const detachedSubItems = (this.subItems || [])
23432
+ const detachedSubItems = (this.subItems || []).concat(this._staticSubItems || [])
23294
23433
  .reduce((res, rootItem) => {
23295
23434
  const children = rootItem.children;
23296
23435
  if (isEmptyArray(children))
@@ -23370,21 +23509,22 @@ class MenuService extends StartableObservableService {
23370
23509
  return items;
23371
23510
  }
23372
23511
  _addSubMenuItems(sources, opts) {
23373
- return sources.map(source => this._addSubMenuItem(this.fromObject(source), opts));
23512
+ return sources.map(source => this._addSubMenuItem(source, opts));
23374
23513
  }
23375
23514
  _addSubMenuItem(newItem, opts) {
23376
23515
  opts = {
23377
23516
  skipIfExists: true,
23378
23517
  ...opts,
23379
23518
  };
23519
+ const pathParam = MenuItems.parsePathWithQuery(this.router.routerState.snapshot.url);
23380
23520
  // Find if item already exists
23381
23521
  const existingItem = this._findExistingItem(newItem, opts?.availableParents);
23382
23522
  if (existingItem) {
23383
23523
  // No changes
23384
- if (opts.skipIfExists && MenuItems.isSame(existingItem, newItem) && !existingItem.detached) {
23524
+ if (opts.skipIfExists && MenuItems.isSame(existingItem, newItem) && existingItem.visible) {
23385
23525
  // DEBUG
23386
23526
  if (this._debug)
23387
- console.debug(`${this._logPrefix}Skipping sub item (same item already exist)`, newItem);
23527
+ console.debug(`${this._logPrefix}Skipping sub item (same visible item already exist)`, newItem);
23388
23528
  return existingItem;
23389
23529
  }
23390
23530
  if (this._debug)
@@ -23394,52 +23534,69 @@ class MenuService extends StartableObservableService {
23394
23534
  newItem.id = existingItem.id;
23395
23535
  newItem.$children = existingItem.$children;
23396
23536
  newItem.pinned = existingItem.pinned;
23397
- if (newItem.pinned) {
23537
+ newItem.static = existingItem.static;
23538
+ if (newItem.pinned || newItem.static || existingItem.expanded) {
23398
23539
  // If loading, keep existing title if pinned (avoid to show skeleton)
23399
23540
  newItem.title = newItem.title || existingItem.title;
23400
23541
  }
23401
23542
  else {
23402
23543
  newItem.title = isNotNil(newItem.title) ? newItem.title : existingItem.title;
23403
23544
  }
23545
+ // Auto set icon, for expand action
23546
+ if (existingItem.action === 'expand') {
23547
+ newItem.icon = null;
23548
+ newItem.matIcon = existingItem.expanded ? 'expand_less' : 'expand_more';
23549
+ }
23404
23550
  }
23405
23551
  }
23406
23552
  // Generate item's id (if need)
23407
23553
  else if (isNil(newItem.id)) {
23408
23554
  newItem.id = this._computeNewId();
23409
23555
  }
23410
- // Get the parent where to attach the item
23556
+ // Default icon for expand action
23557
+ if (newItem.action === 'expand' && !newItem.matIcon) {
23558
+ newItem.matIcon = 'expand_more';
23559
+ }
23560
+ // If visible, get the parent where to attach the item
23411
23561
  const parent = this._findParent(newItem, opts?.availableParents);
23412
- if (parent) {
23562
+ if (parent && parent.expanded) {
23413
23563
  // Attach item to parent
23414
23564
  this._attachMenuToParent(parent, newItem);
23415
- const pathParam = MenuItems.parsePathWithQuery(this.router.routerState.snapshot.url);
23416
23565
  // Update detached items :
23417
23566
  // - excluded new item
23418
23567
  // - attach and exclude those than can be attached to the new item
23419
23568
  // Search in detached items one have newItem as parent
23420
- const ancestor = newItem.ancestor;
23421
- this._detachedSubItems = this._detachedSubItems.concat(...newItem.children)
23422
- .reduce((res, item) => {
23423
- // Excluded newly add item
23424
- if (item.id === newItem.id)
23425
- return res;
23426
- // If some detached can be attached to the new item: do it
23427
- if (MenuItems.checkIfSubMenuVisible(item, pathParam)) {
23428
- const parent = this._findParent(item, [ancestor]);
23429
- if (parent) {
23430
- // Update item's children
23431
- const detachedChildren = this._detachSubMenuItems(pathParam, item.children || []);
23432
- // Attach item
23433
- this._attachMenuToParent(parent, item);
23434
- // Remember detached children
23435
- return res.concat(...detachedChildren);
23569
+ if (newItem.expanded) {
23570
+ const ancestor = newItem.ancestor;
23571
+ this._detachedSubItems = this._detachedSubItems.concat(...newItem.children)
23572
+ .reduce((res, item) => {
23573
+ // Excluded newly add item
23574
+ if (item.id === newItem.id)
23575
+ return res;
23576
+ // If some detached can be attached to the new item: do it
23577
+ if (MenuItems.checkIfSubMenuVisible(item, pathParam)) {
23578
+ const parent = this._findParent(item, [ancestor]);
23579
+ if (parent) {
23580
+ // Update item's children
23581
+ const detachedChildren = this._detachSubMenuItems(pathParam, item.children || []);
23582
+ // Attach item
23583
+ this._attachMenuToParent(parent, item);
23584
+ // Remember detached children
23585
+ return res.concat(...detachedChildren);
23586
+ }
23436
23587
  }
23437
- }
23438
- // Detach (if need)
23439
- this._detachToParent(item);
23440
- // Add to detached items
23441
- return res.concat(item);
23442
- }, []);
23588
+ // Detach (if need)
23589
+ this._detachToParent(item);
23590
+ // Add to detached items
23591
+ return res.concat(item);
23592
+ }, []);
23593
+ }
23594
+ else {
23595
+ // Remove from detached elements
23596
+ this._detachedSubItems = this._detachedSubItems.filter(item => item.id !== newItem.id);
23597
+ // Detach children
23598
+ newItem.children.forEach(item => this._detachToParent(item));
23599
+ }
23443
23600
  }
23444
23601
  else {
23445
23602
  // Add or update detached item
@@ -23553,14 +23710,14 @@ class MenuService extends StartableObservableService {
23553
23710
  }
23554
23711
  }
23555
23712
  }
23556
- 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 });
23713
+ 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 });
23557
23714
  MenuService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: MenuService, providedIn: 'root' });
23558
23715
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: MenuService, decorators: [{
23559
23716
  type: Injectable,
23560
23717
  args: [{
23561
23718
  providedIn: 'root', deps: [ENVIRONMENT]
23562
23719
  }]
23563
- }], ctorParameters: function () { return [{ type: LocalSettingsService }, { type: ConfigService }, { type: AccountService }, { type: i1$5.Router }, { type: Environment, decorators: [{
23720
+ }], ctorParameters: function () { 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: [{
23564
23721
  type: Inject,
23565
23722
  args: [ENVIRONMENT]
23566
23723
  }] }, { type: MenuOptions, decorators: [{
@@ -23654,35 +23811,6 @@ class MenuComponent {
23654
23811
  this.markAsLoaded({ emitEvent: false });
23655
23812
  this.markForCheck();
23656
23813
  }
23657
- async logout() {
23658
- const translations = await this.translate.instant([
23659
- 'AUTH.LOGOUT.CONFIRM_TITLE',
23660
- 'AUTH.LOGOUT.CONFIRM_MESSAGE',
23661
- 'COMMON.BTN_CANCEL',
23662
- 'AUTH.LOGOUT.BTN_CONFIRM',
23663
- ]);
23664
- const alert = await this.alertController.create({
23665
- header: translations['AUTH.LOGOUT.CONFIRM_TITLE'],
23666
- message: translations['AUTH.LOGOUT.CONFIRM_MESSAGE'],
23667
- buttons: [
23668
- {
23669
- text: translations['COMMON.BTN_CANCEL'],
23670
- role: 'cancel',
23671
- cssClass: 'secondary',
23672
- },
23673
- {
23674
- text: translations['AUTH.LOGOUT.BTN_CONFIRM'],
23675
- cssClass: 'ion-color-primary',
23676
- handler: () => {
23677
- this.accountService.logout();
23678
- // Back to home
23679
- setTimeout(() => this.navController.navigateRoot('/'), 100);
23680
- },
23681
- },
23682
- ],
23683
- });
23684
- await alert.present();
23685
- }
23686
23814
  trackByFn(index, item) {
23687
23815
  return item.id.toString();
23688
23816
  }
@@ -23711,15 +23839,11 @@ class MenuComponent {
23711
23839
  if (opened) {
23712
23840
  console.debug('[menu] Closing menu');
23713
23841
  await this.menu.close(this.menuId);
23714
- // Propage to service
23842
+ // Propagate to service
23715
23843
  this.menuService._markAsClosed();
23716
23844
  }
23717
23845
  return true;
23718
23846
  }
23719
- async openAboutModal(event) {
23720
- const modal = await this.modalCtrl.create({ component: AboutModal });
23721
- return modal.present();
23722
- }
23723
23847
  async setSplitPaneWhen(value) {
23724
23848
  console.debug('[menu] Checking splitPane when...');
23725
23849
  if (this.splitPane.when !== value) {
@@ -23742,24 +23866,10 @@ class MenuComponent {
23742
23866
  return this.setSplitPaneWhen(false);
23743
23867
  }
23744
23868
  }
23745
- async doAction(action, event) {
23869
+ executeAction(event, item) {
23746
23870
  this.close();
23747
- console.log(typeof action);
23748
- if (typeof action === 'string') {
23749
- switch (action) {
23750
- case 'logout':
23751
- await this.logout();
23752
- break;
23753
- case 'about':
23754
- await this.openAboutModal(event);
23755
- break;
23756
- default:
23757
- throw new Error('Unknown action: ' + action);
23758
- }
23759
- }
23760
- else if (typeof action === 'function') {
23761
- action(event);
23762
- }
23871
+ // Delegate execution to service
23872
+ return this.menuService.executeAction(event, item);
23763
23873
  }
23764
23874
  onSwipeRight(event) {
23765
23875
  // Skip, if not a valid swipe event
@@ -23808,10 +23918,10 @@ class MenuComponent {
23808
23918
  }
23809
23919
  }
23810
23920
  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 });
23811
- 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 });
23921
+ 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 });
23812
23922
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: MenuComponent, decorators: [{
23813
23923
  type: Component,
23814
- 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"] }]
23924
+ 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"] }]
23815
23925
  }], ctorParameters: function () { 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: [{
23816
23926
  type: Inject,
23817
23927
  args: [ENVIRONMENT]
@@ -35179,33 +35289,23 @@ class UsersPage extends AppTable {
35179
35289
  debounceTime(500), tap((json) => this.settings.savePageSetting(this.settingsId, json, SETTINGS_FILTER)))
35180
35290
  .subscribe());
35181
35291
  if (this.canSendMessage && this.debug) {
35182
- let subActionsItems;
35183
- let actionIcon = await this.menuService.addSubMenuItem({
35184
- parentPath: '/admin/users',
35185
- path: '/admin/users',
35186
- pathParams: { action: 'actions' },
35187
- matIcon: 'expand_more',
35188
- title: 'Actions',
35189
- //pinned: true,
35190
- action: async () => {
35191
- if (actionIcon.matIcon === 'expand_more') {
35192
- actionIcon.matIcon = 'expand_less';
35193
- subActionsItems = await this.menuService.addSubMenuItems([
35194
- {
35195
- parentPath: '/admin/users?action=actions',
35196
- path: '/admin/users?action=send',
35197
- icon: 'mail',
35198
- title: 'USER.LIST.BTN_SEND_MESSAGE',
35199
- action: () => this.openComposeMessageModal(),
35200
- },
35201
- ]);
35202
- }
35203
- else {
35204
- actionIcon.matIcon = 'expand_more';
35205
- await this.menuService.removeSubMenuItems(subActionsItems);
35206
- }
35292
+ await this.menuService.addSubMenuItems([
35293
+ {
35294
+ parentPath: '/admin/users',
35295
+ path: '/admin/users',
35296
+ pathParams: { action: 'expand' },
35297
+ matIcon: 'expand_more',
35298
+ title: 'USER.LIST.BTN_ACTIONS',
35299
+ action: 'expand',
35207
35300
  },
35208
- });
35301
+ {
35302
+ parentPath: '/admin/users?action=expand',
35303
+ path: '/admin/users?action=send',
35304
+ icon: 'mail',
35305
+ title: 'USER.LIST.BTN_SEND_MESSAGE',
35306
+ action: () => this.openComposeMessageModal(),
35307
+ },
35308
+ ], { skipIfExists: false });
35209
35309
  }
35210
35310
  // Restore filter from settings, or load all
35211
35311
  this.restoreFilterOrLoad();