Skip to content

Commit 1e009c4

Browse files
authored
Merge pull request #4595 from crazyserver/MOBILE-4910
MOBILE-4910 mainmenu: Show custom menu items on user menu
2 parents 8617ba6 + 6ab8d36 commit 1e009c4

File tree

14 files changed

+474
-242
lines changed

14 files changed

+474
-242
lines changed

src/core/constants.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,13 @@ export const enum CoreSyncIcon {
169169
SYNC = 'fas-rotate',
170170
};
171171

172+
export enum CoreLinkOpenMethod {
173+
APP = 'app',
174+
INAPPBROWSER = 'inappbrowser',
175+
BROWSER = 'browser',
176+
EMBEDDED = 'embedded',
177+
};
178+
172179
/**
173180
* Static class to contain all the core constants.
174181
*/

src/core/directives/external-content.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import { CoreLogger } from '@singletons/logger';
3232
import { CoreError } from '@classes/errors/error';
3333
import { CoreSite } from '@classes/sites/site';
3434
import { CoreEventObserver, CoreEvents } from '@singletons/events';
35-
import { DownloadStatus } from '../constants';
35+
import { CoreLinkOpenMethod, DownloadStatus } from '../constants';
3636
import { CoreNetwork } from '@services/network';
3737
import { Translate } from '@singletons';
3838
import type { AsyncDirective } from '@coretypes/async-directive';
@@ -370,7 +370,7 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges, O
370370
const tagName = this.element.tagName;
371371
const openIn = tagName === 'A' && this.element.getAttribute('data-open-in');
372372

373-
if (openIn === 'app' || openIn === 'browser') {
373+
if (openIn === CoreLinkOpenMethod.APP || openIn === CoreLinkOpenMethod.BROWSER) {
374374
// The file is meant to be opened in browser or InAppBrowser, don't use the downloaded URL because it won't work.
375375
if (!site.isSitePluginFileUrl(url)) {
376376
return url;

src/core/directives/format-text.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ import { MediaElementController } from '@classes/element-controllers/MediaElemen
5353
import { FrameElement, FrameElementController } from '@classes/element-controllers/FrameElementController';
5454
import { CoreUrl } from '@singletons/url';
5555
import { CoreIcons } from '@singletons/icons';
56-
import { ContextLevel } from '../constants';
56+
import { ContextLevel, CoreLinkOpenMethod } from '../constants';
5757
import { CoreWait } from '@singletons/wait';
5858
import { toBoolean } from '../transforms/boolean';
5959
import { CoreViewer } from '@features/viewer/services/viewer';
@@ -839,7 +839,7 @@ export class CoreFormatTextDirective implements OnDestroy, AsyncDirective {
839839
// Try to convert the URL to absolute if needed.
840840
url = CoreUrl.toAbsoluteURL(site.getURL(), url);
841841
const confirmMessage = element.dataset.appUrlConfirm;
842-
const openInApp = element.dataset.openIn === 'app';
842+
const openInApp = element.dataset.openIn === CoreLinkOpenMethod.APP;
843843
const refreshOnResume = element.dataset.appUrlResumeAction === 'refresh';
844844

845845
if (confirmMessage) {

src/core/directives/link.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { CoreFileHelper } from '@services/file-helper';
1919
import { CoreSites } from '@services/sites';
2020
import { CoreUrl } from '@singletons/url';
2121
import { CoreOpener } from '@singletons/opener';
22-
import { CoreConstants } from '@/core/constants';
22+
import { CoreConstants, CoreLinkOpenMethod } from '@/core/constants';
2323
import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper';
2424
import { CoreCustomURLSchemes } from '@services/urlschemes';
2525
import { DomSanitizer } from '@singletons';
@@ -187,8 +187,8 @@ export class CoreLinkDirective implements OnInit {
187187
protected async openExternalLink(href: string, openIn?: string | null): Promise<void> {
188188
// Priority order is: core-link inApp attribute > forceOpenLinksIn setting > data-open-in HTML attribute.
189189
const openInApp = this.inApp ??
190-
(CoreConstants.CONFIG.forceOpenLinksIn !== 'browser' &&
191-
(CoreConstants.CONFIG.forceOpenLinksIn === 'app' || openIn === 'app'));
190+
(CoreConstants.CONFIG.forceOpenLinksIn !== CoreLinkOpenMethod.BROWSER &&
191+
(CoreConstants.CONFIG.forceOpenLinksIn === CoreLinkOpenMethod.APP || openIn === CoreLinkOpenMethod.APP));
192192

193193
// Check if we need to auto-login.
194194
if (!CoreSites.isLoggedIn()) {
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
@if (type() !== 'embedded') {
2+
<ion-item button [href]="url()" [attr.aria-label]="label()" core-link [capture]="type() === 'app'"
3+
[inApp]="type() === 'inappbrowser'" class="core-custom-menu-item" [detail]="true"
4+
[detailIcon]="type() === 'browser' ? 'open-outline' : 'chevron-forward'">
5+
<ion-icon [name]="icon()" slot="start" aria-hidden="true" />
6+
<ion-label>
7+
<p class="item-heading">{{label()}}</p>
8+
</ion-label>
9+
</ion-item>
10+
} @else {
11+
<ion-item button (click)="openItem()" [attr.aria-label]="label()" class="core-custom-menu-item" [detail]="true">
12+
<ion-icon [name]="icon()" slot="start" aria-hidden="true" />
13+
<ion-label>
14+
<p class="item-heading">{{label()}}</p>
15+
</ion-label>
16+
</ion-item>
17+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// (C) Copyright 2015 Moodle Pty Ltd.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import { CoreLinkOpenMethod } from '@/core/constants';
16+
import { CoreSharedModule } from '@/core/shared.module';
17+
import { Component, input } from '@angular/core';
18+
import { CoreViewer } from '@features/viewer/services/viewer';
19+
20+
/**
21+
* Component to display a custom menu item.
22+
*/
23+
@Component({
24+
selector: 'core-custom-menu-item',
25+
templateUrl: 'custom-menu-item.html',
26+
imports: [
27+
CoreSharedModule,
28+
],
29+
})
30+
export class CoreCustomMenuItemComponent {
31+
32+
/**
33+
* Type of the item: app, inappbrowser, browser or embedded.
34+
*/
35+
readonly type = input.required<CoreLinkOpenMethod>();
36+
37+
/**
38+
* Url of the item.
39+
*/
40+
readonly url = input.required<string>();
41+
42+
/**
43+
* Label to display for the item.
44+
*/
45+
readonly label = input.required<string>();
46+
47+
/**
48+
* Name of the icon to display for the item.
49+
*/
50+
readonly icon = input.required<string>();
51+
52+
/**
53+
* Open an embedded custom item.
54+
*/
55+
openItem(): void {
56+
CoreViewer.openIframeViewer(this.label(), this.url());
57+
}
58+
59+
}

src/core/features/mainmenu/components/user-menu/user-menu.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ <h1>
5050
<ng-container *ngTemplateOutlet="menuHandler; context: { handler, class: 'core-user-menu-handler', addSeparator: false }" />
5151
}
5252

53+
@for (item of customItems; track item) {
54+
<core-custom-menu-item [type]="item.type" [label]="item.label" [icon]="item.icon" [url]="item.url" />
55+
}
56+
5357
@for (handler of accountHandlers; track handler.name; let first = $first) {
5458
<ng-container
5559
*ngTemplateOutlet="menuHandler; context: { handler, class: 'core-user-account-menu-handler', addSeparator: first }" />

src/core/features/mainmenu/components/user-menu/user-menu.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ import { CoreAlerts } from '@services/overlays/alerts';
3838
import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-component';
3939
import { CorePromiseUtils } from '@singletons/promise-utils';
4040
import type { ReloadableComponent } from '@coretypes/reloadable-component';
41+
import { CoreCustomMenu, CoreCustomMenuItem } from '@features/mainmenu/services/custommenu';
42+
import { CoreCustomMenuItemComponent } from '../custom-menu-item/custom-menu-item';
4143

4244
/**
4345
* Component to display a user menu.
@@ -49,6 +51,7 @@ import type { ReloadableComponent } from '@coretypes/reloadable-component';
4951
imports: [
5052
CoreSharedModule,
5153
CoreSiteLogoComponent,
54+
CoreCustomMenuItemComponent,
5255
],
5356
})
5457
export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy {
@@ -59,6 +62,7 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy {
5962
siteUrl?: string;
6063
displaySiteUrl = false;
6164
handlers: HandlerData[] = [];
65+
customItems?: CoreCustomMenuItem[];
6266
accountHandlers: HandlerData[] = [];
6367
handlersLoaded = false;
6468
user?: CoreUserProfile;
@@ -84,6 +88,8 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy {
8488
this.removeAccountOnLogout = !!CoreConstants.CONFIG.removeaccountonlogout;
8589
this.displaySiteUrl = currentSite.shouldDisplayInformativeLinks();
8690

91+
await this.loadCustomMenuItems();
92+
8793
await this.loadData();
8894
}
8995

@@ -177,6 +183,13 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy {
177183
event?.complete();
178184
}
179185

186+
/**
187+
* Load custom menu items.
188+
*/
189+
protected async loadCustomMenuItems(): Promise<void> {
190+
this.customItems = await CoreCustomMenu.getUserCustomMenuItems();
191+
}
192+
180193
/**
181194
* Opens User profile page.
182195
*

src/core/features/mainmenu/pages/more/more.html

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -52,25 +52,11 @@ <h1>{{ 'core.more' | translate }}</h1>
5252
</ion-item>
5353
}
5454
}
55-
<ng-container *ngFor="let item of customItems">
56-
@if (item.type !== 'embedded') {
57-
<ion-item button [href]="item.url" [attr.aria-label]="item.label" core-link [capture]="item.type === 'app'"
58-
[inApp]="item.type === 'inappbrowser'" class="core-moremenu-customitem" [detail]="true"
59-
[detailIcon]="item.type === 'browser' ? 'open-outline' : 'chevron-forward'">
60-
<ion-icon [name]="item.icon" slot="start" aria-hidden="true" />
61-
<ion-label>
62-
<p class="item-heading">{{item.label}}</p>
63-
</ion-label>
64-
</ion-item>
65-
} @else {
66-
<ion-item button (click)="openItem(item)" [attr.aria-label]="item.label" class="core-moremenu-customitem" [detail]="true">
67-
<ion-icon [name]="item.icon" slot="start" aria-hidden="true" />
68-
<ion-label>
69-
<p class="item-heading">{{item.label}}</p>
70-
</ion-label>
71-
</ion-item>
72-
}
73-
</ng-container>
55+
56+
@for (item of customItems; track item) {
57+
<core-custom-menu-item [type]="item.type" [label]="item.label" [icon]="item.icon" [url]="item.url" />
58+
}
59+
7460
@if (showScanQR) {
7561
<ion-item button (click)="scanQR()" [detail]="true">
7662
<ion-icon name="fas-qrcode" slot="start" aria-hidden="true" />

src/core/features/mainmenu/pages/more/more.ts

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
CoreMainMenuHandlerToDisplay,
2323
CoreMainMenuPageNavHandlerToDisplay,
2424
} from '../../services/mainmenu-delegate';
25-
import { CoreMainMenu, CoreMainMenuCustomItem } from '../../services/mainmenu';
25+
import { CoreMainMenu } from '../../services/mainmenu';
2626
import { CoreEventObserver, CoreEvents } from '@singletons/events';
2727
import { CoreNavigator } from '@services/navigator';
2828
import { Translate } from '@singletons';
@@ -35,6 +35,8 @@ import { CoreUrl } from '@singletons/url';
3535
import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-component';
3636
import { ReloadableComponent } from '@coretypes/reloadable-component';
3737
import { CorePromiseUtils } from '@singletons/promise-utils';
38+
import { CoreCustomMenu, CoreCustomMenuItem } from '@features/mainmenu/services/custommenu';
39+
import { CoreCustomMenuItemComponent } from '@features/mainmenu/components/custom-menu-item/custom-menu-item';
3840

3941
/**
4042
* Page that displays the more page of the app.
@@ -46,6 +48,7 @@ import { CorePromiseUtils } from '@singletons/promise-utils';
4648
imports: [
4749
CoreSharedModule,
4850
CoreMainMenuUserButtonComponent,
51+
CoreCustomMenuItemComponent,
4952
],
5053
})
5154
export default class CoreMainMenuMorePage implements OnInit, OnDestroy {
@@ -55,7 +58,7 @@ export default class CoreMainMenuMorePage implements OnInit, OnDestroy {
5558
handlers?: CoreMainMenuHandlerToDisplay[];
5659
handlersLoaded = false;
5760
showScanQR: boolean;
58-
customItems?: CoreMainMenuCustomItem[];
61+
customItems?: CoreCustomMenuItem[];
5962
hasComponentHandlers = false;
6063

6164
protected allHandlers?: CoreMainMenuHandlerToDisplay[];
@@ -68,7 +71,7 @@ export default class CoreMainMenuMorePage implements OnInit, OnDestroy {
6871
this.langObserver = CoreEvents.on(CoreEvents.LANGUAGE_CHANGED, () => this.loadCustomMenuItems());
6972

7073
this.updateSiteObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, async () => {
71-
this.customItems = await CoreMainMenu.getCustomMenuItems();
74+
this.customItems = await CoreCustomMenu.getCustomMainMenuItems();
7275
}, CoreSites.getCurrentSiteId());
7376

7477
this.loadCustomMenuItems();
@@ -128,7 +131,7 @@ export default class CoreMainMenuMorePage implements OnInit, OnDestroy {
128131
* Load custom menu items.
129132
*/
130133
protected async loadCustomMenuItems(): Promise<void> {
131-
this.customItems = await CoreMainMenu.getCustomMenuItems();
134+
this.customItems = await CoreCustomMenu.getCustomMainMenuItems();
132135
}
133136

134137
/**
@@ -142,15 +145,6 @@ export default class CoreMainMenuMorePage implements OnInit, OnDestroy {
142145
CoreNavigator.navigateToSitePath(handler.page, { params });
143146
}
144147

145-
/**
146-
* Open an embedded custom item.
147-
*
148-
* @param item Item to open.
149-
*/
150-
openItem(item: CoreMainMenuCustomItem): void {
151-
CoreViewer.openIframeViewer(item.label, item.url);
152-
}
153-
154148
/**
155149
* Open settings.
156150
*/

0 commit comments

Comments
 (0)