MOBILE-3565 mainmenu: Initial implementation of main menu
parent
df4dfa6eb5
commit
a332faa81e
|
@ -29,6 +29,10 @@ const routes: Routes = [
|
||||||
path: 'settings',
|
path: 'settings',
|
||||||
loadChildren: () => import('./core/settings/settings.module').then( m => m.CoreAppSettingsPageModule),
|
loadChildren: () => import('./core/settings/settings.module').then( m => m.CoreAppSettingsPageModule),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'mainmenu',
|
||||||
|
loadChildren: () => import('./core/mainmenu/mainmenu.module').then( m => m.CoreMainMenuModule),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { NavController } from '@ionic/angular';
|
||||||
|
|
||||||
import { CoreLangProvider } from '@services/lang';
|
import { CoreLangProvider } from '@services/lang';
|
||||||
import { CoreEvents } from '@singletons/events';
|
import { CoreEvents } from '@singletons/events';
|
||||||
|
|
||||||
|
@ -24,7 +26,8 @@ import { CoreEvents } from '@singletons/events';
|
||||||
export class AppComponent implements OnInit {
|
export class AppComponent implements OnInit {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private langProvider: CoreLangProvider,
|
protected langProvider: CoreLangProvider,
|
||||||
|
protected navCtrl: NavController,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,16 +37,13 @@ export class AppComponent implements OnInit {
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
CoreEvents.on(CoreEvents.LOGOUT, () => {
|
CoreEvents.on(CoreEvents.LOGOUT, () => {
|
||||||
// Go to sites page when user is logged out.
|
// Go to sites page when user is logged out.
|
||||||
// Due to DeepLinker, we need to use the ViewCtrl instead of name.
|
this.navCtrl.navigateRoot('/login/sites');
|
||||||
// Otherwise some pages are re-created when they shouldn't.
|
|
||||||
// TODO
|
|
||||||
// CoreApp.instance.getRootNavController().setRoot(CoreLoginSitesPage);
|
|
||||||
|
|
||||||
// Unload lang custom strings.
|
// Unload lang custom strings.
|
||||||
this.langProvider.clearCustomStrings();
|
this.langProvider.clearCustomStrings();
|
||||||
|
|
||||||
// Remove version classes from body.
|
// Remove version classes from body.
|
||||||
// TODO
|
// @todo
|
||||||
// this.removeVersionClass();
|
// this.removeVersionClass();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,11 @@ import { CoreLoginSitesPage } from './pages/sites/sites.page';
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
|
redirectTo: 'init',
|
||||||
|
pathMatch: 'full',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'init',
|
||||||
component: CoreLoginInitPage,
|
component: CoreLoginInitPage,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -27,7 +27,6 @@ import { CoreLoginCredentialsPage } from './pages/credentials/credentials.page';
|
||||||
import { CoreLoginInitPage } from './pages/init/init.page';
|
import { CoreLoginInitPage } from './pages/init/init.page';
|
||||||
import { CoreLoginSitePage } from './pages/site/site.page';
|
import { CoreLoginSitePage } from './pages/site/site.page';
|
||||||
import { CoreLoginSitesPage } from './pages/sites/sites.page';
|
import { CoreLoginSitesPage } from './pages/sites/sites.page';
|
||||||
import { CoreLoginHelperProvider } from './services/helper';
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -47,8 +46,5 @@ import { CoreLoginHelperProvider } from './services/helper';
|
||||||
CoreLoginSitePage,
|
CoreLoginSitePage,
|
||||||
CoreLoginSitesPage,
|
CoreLoginSitesPage,
|
||||||
],
|
],
|
||||||
providers: [
|
|
||||||
CoreLoginHelperProvider,
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
export class CoreLoginModule {}
|
export class CoreLoginModule {}
|
||||||
|
|
|
@ -243,7 +243,7 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
this.siteId = id;
|
this.siteId = id;
|
||||||
|
|
||||||
await CoreLoginHelper.instance.goToSiteInitialPage(undefined, undefined, undefined, undefined, this.urlToOpen);
|
await CoreLoginHelper.instance.goToSiteInitialPage({ urlToOpen: this.urlToOpen });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
CoreLoginHelper.instance.treatUserTokenError(siteUrl, error, username, password);
|
CoreLoginHelper.instance.treatUserTokenError(siteUrl, error, username, password);
|
||||||
|
|
||||||
|
|
|
@ -15,9 +15,13 @@
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { NavController } from '@ionic/angular';
|
import { NavController } from '@ionic/angular';
|
||||||
|
|
||||||
import { CoreApp } from '@services/app';
|
import { CoreApp, CoreRedirectData } from '@services/app';
|
||||||
import { CoreInit } from '@services/init';
|
import { CoreInit } from '@services/init';
|
||||||
import { SplashScreen } from '@singletons/core.singletons';
|
import { SplashScreen } from '@singletons/core.singletons';
|
||||||
|
import { CoreConstants } from '@core/constants';
|
||||||
|
import { CoreSite } from '@/app/classes/site';
|
||||||
|
import { CoreSites } from '@/app/services/sites';
|
||||||
|
import { CoreLoginHelper, CoreLoginHelperProvider } from '../../services/helper';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays a "splash screen" while the app is being initialized.
|
* Page that displays a "splash screen" while the app is being initialized.
|
||||||
|
@ -40,55 +44,75 @@ export class CoreLoginInitPage implements OnInit {
|
||||||
|
|
||||||
// Check if there was a pending redirect.
|
// Check if there was a pending redirect.
|
||||||
const redirectData = CoreApp.instance.getRedirect();
|
const redirectData = CoreApp.instance.getRedirect();
|
||||||
|
|
||||||
if (redirectData.siteId) {
|
if (redirectData.siteId) {
|
||||||
// Unset redirect data.
|
await this.handleRedirect(redirectData);
|
||||||
CoreApp.instance.storeRedirect('', '', {});
|
} else {
|
||||||
|
await this.loadPage();
|
||||||
// Only accept the redirect if it was stored less than 20 seconds ago.
|
|
||||||
if (redirectData.timemodified && Date.now() - redirectData.timemodified < 20000) {
|
|
||||||
// if (redirectData.siteId != CoreConstants.NO_SITE_ID) {
|
|
||||||
// // The redirect is pointing to a site, load it.
|
|
||||||
// return this.sitesProvider.loadSite(redirectData.siteId, redirectData.page, redirectData.params)
|
|
||||||
// .then((loggedIn) => {
|
|
||||||
|
|
||||||
// if (loggedIn) {
|
|
||||||
// return this.loginHelper.goToSiteInitialPage(this.navCtrl, redirectData.page, redirectData.params,
|
|
||||||
// { animate: false });
|
|
||||||
// }
|
|
||||||
// }).catch(() => {
|
|
||||||
// // Site doesn't exist.
|
|
||||||
// return this.loadPage();
|
|
||||||
// });
|
|
||||||
// } else {
|
|
||||||
// // No site to load, open the page.
|
|
||||||
// return this.loginHelper.goToNoSitePage(this.navCtrl, redirectData.page, redirectData.params);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.loadPage();
|
|
||||||
|
|
||||||
// If we hide the splash screen now, the init view is still seen for an instant. Wait a bit to make sure it isn't seen.
|
// If we hide the splash screen now, the init view is still seen for an instant. Wait a bit to make sure it isn't seen.
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
SplashScreen.instance.hide();
|
SplashScreen.instance.hide();
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Treat redirect data.
|
||||||
|
*
|
||||||
|
* @param redirectData Redirect data.
|
||||||
|
*/
|
||||||
|
protected async handleRedirect(redirectData: CoreRedirectData): Promise<void> {
|
||||||
|
// Unset redirect data.
|
||||||
|
CoreApp.instance.storeRedirect('', '', {});
|
||||||
|
|
||||||
|
// Only accept the redirect if it was stored less than 20 seconds ago.
|
||||||
|
if (redirectData.timemodified && Date.now() - redirectData.timemodified < 20000) {
|
||||||
|
if (redirectData.siteId != CoreConstants.NO_SITE_ID) {
|
||||||
|
// The redirect is pointing to a site, load it.
|
||||||
|
try {
|
||||||
|
const loggedIn = await CoreSites.instance.loadSite(
|
||||||
|
redirectData.siteId!,
|
||||||
|
redirectData.page,
|
||||||
|
redirectData.params,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!loggedIn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CoreLoginHelper.instance.goToSiteInitialPage({
|
||||||
|
redirectPage: redirectData.page,
|
||||||
|
redirectParams: redirectData.params,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// Site doesn't exist.
|
||||||
|
return this.loadPage();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No site to load, open the page.
|
||||||
|
return CoreLoginHelper.instance.goToNoSitePage(redirectData.page, redirectData.params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.loadPage();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the right page.
|
* Load the right page.
|
||||||
*
|
*
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
protected async loadPage(): Promise<void> {
|
protected async loadPage(): Promise<void> {
|
||||||
// if (this.sitesProvider.isLoggedIn()) {
|
if (CoreSites.instance.isLoggedIn()) {
|
||||||
// if (this.loginHelper.isSiteLoggedOut()) {
|
if (CoreLoginHelper.instance.isSiteLoggedOut()) {
|
||||||
// return this.sitesProvider.logout().then(() => {
|
await CoreSites.instance.logout();
|
||||||
// return this.loadPage();
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return this.loginHelper.goToSiteInitialPage();
|
return this.loadPage();
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
return CoreLoginHelper.instance.goToSiteInitialPage();
|
||||||
|
}
|
||||||
|
|
||||||
await this.navCtrl.navigateRoot('/login/sites');
|
await this.navCtrl.navigateRoot('/login/sites');
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,11 +34,14 @@ import { CoreWSError } from '@classes/errors/wserror';
|
||||||
import { makeSingleton, Translate } from '@singletons/core.singletons';
|
import { makeSingleton, Translate } from '@singletons/core.singletons';
|
||||||
import { CoreLogger } from '@singletons/logger';
|
import { CoreLogger } from '@singletons/logger';
|
||||||
import { CoreUrl } from '@singletons/url';
|
import { CoreUrl } from '@singletons/url';
|
||||||
|
import { NavigationOptions } from '@ionic/angular/providers/nav-controller';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper provider that provides some common features regarding authentication.
|
* Helper provider that provides some common features regarding authentication.
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
export class CoreLoginHelperProvider {
|
export class CoreLoginHelperProvider {
|
||||||
|
|
||||||
static readonly OPEN_COURSE = 'open_course';
|
static readonly OPEN_COURSE = 'open_course';
|
||||||
|
@ -448,7 +451,7 @@ export class CoreLoginHelperProvider {
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
goToNoSitePage(navCtrl: NavController, page: string, params?: Params): Promise<any> {
|
goToNoSitePage(page?: string, params?: Params): Promise<any> {
|
||||||
// @todo
|
// @todo
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
@ -456,17 +459,11 @@ export class CoreLoginHelperProvider {
|
||||||
/**
|
/**
|
||||||
* Go to the initial page of a site depending on 'userhomepage' setting.
|
* Go to the initial page of a site depending on 'userhomepage' setting.
|
||||||
*
|
*
|
||||||
* @param navCtrl NavController to use. Defaults to app root NavController.
|
* @param options Options.
|
||||||
* @param page Name of the page to load after loading the main page.
|
|
||||||
* @param params Params to pass to the page.
|
|
||||||
* @param options Navigation options.
|
|
||||||
* @param url URL to open once the main menu is loaded.
|
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
goToSiteInitialPage(options?: OpenMainMenuOptions): Promise<void> {
|
||||||
goToSiteInitialPage(navCtrl?: NavController, page?: string, params?: Params, options?: any, url?: string): Promise<any> {
|
return this.openMainMenu(options);
|
||||||
// @todo
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -664,17 +661,32 @@ export class CoreLoginHelperProvider {
|
||||||
/**
|
/**
|
||||||
* Open the main menu, loading a certain page.
|
* Open the main menu, loading a certain page.
|
||||||
*
|
*
|
||||||
* @param navCtrl NavController.
|
* @param options Options.
|
||||||
* @param page Name of the page to load.
|
|
||||||
* @param params Params to pass to the page.
|
|
||||||
* @param options Navigation options.
|
|
||||||
* @param url URL to open once the main menu is loaded.
|
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
protected async openMainMenu(options?: OpenMainMenuOptions): Promise<void> {
|
||||||
protected openMainMenu(navCtrl: NavController, page: string, params: Params, options?: any, url?: string): Promise<any> {
|
|
||||||
// @todo
|
// Due to DeepLinker, we need to remove the path from the URL before going to main menu.
|
||||||
return Promise.resolve();
|
// IonTabs checks the URL to determine which path to load for deep linking, so we clear the URL.
|
||||||
|
// @todo this.location.replaceState('');
|
||||||
|
|
||||||
|
if (options?.redirectPage == CoreLoginHelperProvider.OPEN_COURSE) {
|
||||||
|
// Load the main menu first, and then open the course.
|
||||||
|
try {
|
||||||
|
await this.navCtrl.navigateRoot('/mainmenu');
|
||||||
|
} finally {
|
||||||
|
// @todo: Open course.
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Open the main menu.
|
||||||
|
const queryParams: Params = Object.assign({}, options);
|
||||||
|
delete queryParams.navigationOptions;
|
||||||
|
|
||||||
|
await this.navCtrl.navigateRoot('/mainmenu', {
|
||||||
|
queryParams,
|
||||||
|
...options?.navigationOptions,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1375,3 +1387,10 @@ type StoredLoginLaunchData = {
|
||||||
pageParams: Params;
|
pageParams: Params;
|
||||||
ssoUrlParams: CoreUrlParams;
|
ssoUrlParams: CoreUrlParams;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type OpenMainMenuOptions = {
|
||||||
|
redirectPage?: string; // Route of the page to open in main menu. If not defined, default tab will be selected.
|
||||||
|
redirectParams?: Params; // Params to pass to the selected tab if any.
|
||||||
|
urlToOpen?: string; // URL to open once the main menu is loaded.
|
||||||
|
navigationOptions?: NavigationOptions; // Navigation options.
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"changesite": "Change site",
|
||||||
|
"help": "Help",
|
||||||
|
"logout": "Log out",
|
||||||
|
"website": "Website"
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
|
||||||
|
import { CoreMainMenuPage } from './pages/menu/menu.page';
|
||||||
|
import { CoreMainMenuMorePage } from './pages/more/more.page';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: CoreMainMenuPage,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'more',
|
||||||
|
component: CoreMainMenuMorePage,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule],
|
||||||
|
})
|
||||||
|
export class CoreMainMenuRoutingModule {}
|
|
@ -0,0 +1,42 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
|
import { IonicModule } from '@ionic/angular';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { CoreComponentsModule } from '@/app/components/components.module';
|
||||||
|
import { CoreDirectivesModule } from '@/app/directives/directives.module';
|
||||||
|
|
||||||
|
import { CoreMainMenuRoutingModule } from './mainmenu-routing.module';
|
||||||
|
import { CoreMainMenuPage } from './pages/menu/menu.page';
|
||||||
|
import { CoreMainMenuMorePage } from './pages/more/more.page';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
IonicModule,
|
||||||
|
CoreMainMenuRoutingModule,
|
||||||
|
TranslateModule.forChild(),
|
||||||
|
CoreComponentsModule,
|
||||||
|
CoreDirectivesModule,
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
CoreMainMenuPage,
|
||||||
|
CoreMainMenuMorePage,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class CoreMainMenuModule {}
|
|
@ -0,0 +1,23 @@
|
||||||
|
<ion-tabs #mainTabs [hidden]="!showTabs"> <!-- [loaded]="loaded" -->
|
||||||
|
<ion-tab-bar slot="bottom">
|
||||||
|
<ion-tab-button tab="redirect" [disabled]="true" [hidden]="true"></ion-tab-button> <!-- [show]="false" [root]="redirectPage" [rootParams]="redirectParams" -->
|
||||||
|
|
||||||
|
<ion-tab-button *ngFor="let tab of tabs" [tab]="tab.page" [disabled]="tab.hide" layout="label-hide"> <!-- [rootParams]="tab.pageParams" [tabBadge]="tab.badge" class="{{tab.class}}" [enabled]="!tab.hide" [show]="!tab.hide" -->
|
||||||
|
<core-icon [name]="tab.icon"></core-icon>
|
||||||
|
<ion-label>{{ tab.title | translate }}</ion-label>
|
||||||
|
</ion-tab-button>
|
||||||
|
|
||||||
|
<ion-tab-button tab="more" layout="label-hide">
|
||||||
|
<core-icon name="fa-bars"></core-icon>
|
||||||
|
<ion-label>{{ 'core.more' | translate }}</ion-label>
|
||||||
|
</ion-tab-button>
|
||||||
|
</ion-tab-bar>
|
||||||
|
</ion-tabs>
|
||||||
|
<div class="core-network-message" [hidden]="!showTabs">
|
||||||
|
<div class="core-online-message">
|
||||||
|
{{ "core.youreonline" | translate }}
|
||||||
|
</div>
|
||||||
|
<div class="core-offline-message">
|
||||||
|
{{ "core.youreoffline" | translate }}
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,225 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Component, OnInit, OnDestroy, ViewChild, ChangeDetectorRef } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Params } from '@angular/router';
|
||||||
|
import { NavController } from '@ionic/angular';
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
|
||||||
|
import { CoreApp } from '@services/app';
|
||||||
|
import { CoreSites } from '@services/sites';
|
||||||
|
import { CoreEvents, CoreEventObserver, CoreEventLoadPageMainMenuData } from '@singletons/events';
|
||||||
|
import { CoreMainMenu } from '../../services/mainmenu';
|
||||||
|
import { CoreMainMenuDelegate, CoreMainMenuHandlerToDisplay } from '../../services/delegate';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page that displays the main menu of the app.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'page-core-mainmenu',
|
||||||
|
templateUrl: 'menu.html',
|
||||||
|
styleUrls: ['menu.scss'],
|
||||||
|
})
|
||||||
|
export class CoreMainMenuPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
tabs: CoreMainMenuHandlerToDisplay[] = [];
|
||||||
|
allHandlers?: CoreMainMenuHandlerToDisplay[];
|
||||||
|
loaded = false;
|
||||||
|
redirectPage?: string;
|
||||||
|
redirectParams?: Params;
|
||||||
|
showTabs = false;
|
||||||
|
tabsPlacement = 'bottom';
|
||||||
|
|
||||||
|
protected subscription?: Subscription;
|
||||||
|
protected redirectObs?: CoreEventObserver;
|
||||||
|
protected pendingRedirect?: CoreEventLoadPageMainMenuData;
|
||||||
|
protected urlToOpen?: string;
|
||||||
|
protected mainMenuId: number;
|
||||||
|
protected keyboardObserver?: CoreEventObserver;
|
||||||
|
|
||||||
|
@ViewChild('mainTabs') mainTabs?: any; // CoreIonTabsComponent;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected route: ActivatedRoute,
|
||||||
|
protected navCtrl: NavController,
|
||||||
|
protected menuDelegate: CoreMainMenuDelegate,
|
||||||
|
protected changeDetector: ChangeDetectorRef,
|
||||||
|
) {
|
||||||
|
this.mainMenuId = CoreApp.instance.getMainMenuId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the component.
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
if (!CoreSites.instance.isLoggedIn()) {
|
||||||
|
this.navCtrl.navigateRoot('/login/init');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.route.queryParams.subscribe(params => {
|
||||||
|
const redirectPage = params['redirectPage'];
|
||||||
|
if (redirectPage) {
|
||||||
|
this.pendingRedirect = {
|
||||||
|
redirectPage: redirectPage,
|
||||||
|
redirectParams: params['redirectParams'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.urlToOpen = params['urlToOpen'];
|
||||||
|
});
|
||||||
|
|
||||||
|
this.showTabs = true;
|
||||||
|
|
||||||
|
this.redirectObs = CoreEvents.on(CoreEvents.LOAD_PAGE_MAIN_MENU, (data: CoreEventLoadPageMainMenuData) => {
|
||||||
|
if (!this.loaded) {
|
||||||
|
// View isn't ready yet, wait for it to be ready.
|
||||||
|
this.pendingRedirect = data;
|
||||||
|
} else {
|
||||||
|
delete this.pendingRedirect;
|
||||||
|
this.handleRedirect(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.subscription = this.menuDelegate.getHandlers().subscribe((handlers) => {
|
||||||
|
// Remove the handlers that should only appear in the More menu.
|
||||||
|
this.allHandlers = handlers.filter((handler) => !handler.onlyInMore);
|
||||||
|
|
||||||
|
this.initHandlers();
|
||||||
|
|
||||||
|
if (this.loaded && this.pendingRedirect) {
|
||||||
|
// Wait for tabs to be initialized and then handle the redirect.
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.pendingRedirect) {
|
||||||
|
this.handleRedirect(this.pendingRedirect);
|
||||||
|
delete this.pendingRedirect;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('resize', this.initHandlers.bind(this));
|
||||||
|
|
||||||
|
if (CoreApp.instance.isIOS()) {
|
||||||
|
// In iOS, the resize event is triggered before the keyboard is opened/closed and not triggered again once done.
|
||||||
|
// Init handlers again once keyboard is closed since the resize event doesn't have the updated height.
|
||||||
|
this.keyboardObserver = CoreEvents.on(CoreEvents.KEYBOARD_CHANGE, (kbHeight: number) => {
|
||||||
|
if (kbHeight === 0) {
|
||||||
|
this.initHandlers();
|
||||||
|
|
||||||
|
// If the device is slow it can take a bit more to update the window height. Retry in a few ms.
|
||||||
|
setTimeout(() => {
|
||||||
|
this.initHandlers();
|
||||||
|
}, 250);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
CoreApp.instance.setMainMenuOpen(this.mainMenuId, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init handlers on change (size or handlers).
|
||||||
|
*/
|
||||||
|
initHandlers(): void {
|
||||||
|
if (this.allHandlers) {
|
||||||
|
this.tabsPlacement = CoreMainMenu.instance.getTabPlacement();
|
||||||
|
|
||||||
|
const handlers = this.allHandlers.slice(0, CoreMainMenu.instance.getNumItems()); // Get main handlers.
|
||||||
|
|
||||||
|
// Re-build the list of tabs. If a handler is already in the list, use existing object to prevent re-creating the tab.
|
||||||
|
const newTabs: CoreMainMenuHandlerToDisplay[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < handlers.length; i++) {
|
||||||
|
const handler = handlers[i];
|
||||||
|
|
||||||
|
// Check if the handler is already in the tabs list. If so, use it.
|
||||||
|
const tab = this.tabs.find((tab) => tab.title == handler.title && tab.icon == handler.icon);
|
||||||
|
|
||||||
|
tab ? tab.hide = false : null;
|
||||||
|
handler.hide = false;
|
||||||
|
|
||||||
|
newTabs.push(tab || handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maintain tab in phantom mode in case is not visible.
|
||||||
|
const selectedTab = this.mainTabs?.getSelected();
|
||||||
|
if (selectedTab) {
|
||||||
|
const oldTab = this.tabs.find((tab) => tab.page == selectedTab.root && tab.icon == selectedTab.tabIcon);
|
||||||
|
|
||||||
|
if (oldTab) {
|
||||||
|
// Check if the selected handler is visible.
|
||||||
|
const isVisible = newTabs.some((newTab) => oldTab.title == newTab.title && oldTab.icon == newTab.icon);
|
||||||
|
|
||||||
|
if (!isVisible) {
|
||||||
|
oldTab.hide = true;
|
||||||
|
newTabs.push(oldTab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tabs = newTabs;
|
||||||
|
|
||||||
|
// Sort them by priority so new handlers are in the right position.
|
||||||
|
this.tabs.sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
||||||
|
|
||||||
|
this.loaded = this.menuDelegate.areHandlersLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.urlToOpen) {
|
||||||
|
// There's a content link to open.
|
||||||
|
// @todo: Treat URL.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a redirect.
|
||||||
|
*
|
||||||
|
* @param data Data received.
|
||||||
|
*/
|
||||||
|
protected handleRedirect(data: CoreEventLoadPageMainMenuData): void {
|
||||||
|
// Check if the redirect page is the root page of any of the tabs.
|
||||||
|
const i = this.tabs.findIndex((tab) => tab.page == data.redirectPage);
|
||||||
|
|
||||||
|
if (i >= 0) {
|
||||||
|
// Tab found. Set the params.
|
||||||
|
this.tabs[i].pageParams = Object.assign({}, data.redirectParams);
|
||||||
|
} else {
|
||||||
|
// Tab not found, use a phantom tab.
|
||||||
|
this.redirectPage = data.redirectPage;
|
||||||
|
this.redirectParams = data.redirectParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force change detection, otherwise sometimes the tab was selected before the params were applied.
|
||||||
|
this.changeDetector.detectChanges();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
// Let the tab load the params before navigating.
|
||||||
|
this.mainTabs?.selectTabRootByIndex(i + 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page destroyed.
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.subscription?.unsubscribe();
|
||||||
|
this.redirectObs?.off();
|
||||||
|
window.removeEventListener('resize', this.initHandlers.bind(this));
|
||||||
|
CoreApp.instance.setMainMenuOpen(this.mainMenuId, false);
|
||||||
|
this.keyboardObserver?.off();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
ion-icon.tab-button-icon {
|
||||||
|
text-overflow: unset;
|
||||||
|
overflow: visible;
|
||||||
|
text-align: center;
|
||||||
|
transition: margin 500ms ease-in-out, transform 300ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ion-md-fa-graduation-cap,
|
||||||
|
.ion-ios-fa-graduation-cap,
|
||||||
|
.ion-ios-fa-graduation-cap-outline,
|
||||||
|
.ion-fa-graduation-cap {
|
||||||
|
// @todo @extend .fa-graduation-cap;
|
||||||
|
// @todo @extend .fa;
|
||||||
|
font-size: 21px;
|
||||||
|
height: 21px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.ion-ios-fa-graduation-cap-outline {
|
||||||
|
color: transparent;
|
||||||
|
-webkit-text-stroke-width: 0.8px;
|
||||||
|
// @todo -webkit-text-stroke-color: $tabs-tab-color-inactive;
|
||||||
|
font-size: 23px;
|
||||||
|
height: 23px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ion-md-fa-newspaper-o,
|
||||||
|
.ion-ios-fa-newspaper-o,
|
||||||
|
.ion-ios-fa-newspaper-o-outline,
|
||||||
|
.ion-fa-newspaper-o {
|
||||||
|
// @todo @extend .fa-newspaper-o;
|
||||||
|
// @todo @extend .fa;
|
||||||
|
font-size: 22px;
|
||||||
|
height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ion-ios-fa-newspaper-o-outline {
|
||||||
|
font-size: 23px;
|
||||||
|
height: 23px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.core-network-message {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
visibility: hidden;
|
||||||
|
height: 0;
|
||||||
|
transition: all 500ms ease-in-out;
|
||||||
|
opacity: .8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.core-online-message,
|
||||||
|
.core-offline-message {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
.core-online ion-app.app-root page-core-mainmenu,
|
||||||
|
.core-offline ion-app.app-root page-core-mainmenu {
|
||||||
|
|
||||||
|
core-ion-tabs[tabsplacement="bottom"] ion-icon.tab-button-icon {
|
||||||
|
margin-bottom: $core-network-message-height / 2;
|
||||||
|
|
||||||
|
&.icon-ios {
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.core-network-message {
|
||||||
|
visibility: visible;
|
||||||
|
height: $core-network-message-height;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.core-offline ion-app.app-root page-core-mainmenu .core-offline-message,
|
||||||
|
.core-online ion-app.app-root page-core-mainmenu .core-online-message {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.core-online ion-app.app-root page-core-mainmenu .core-network-message {
|
||||||
|
background: $green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.core-offline ion-app.app-root page-core-mainmenu .core-network-message {
|
||||||
|
background: $red;
|
||||||
|
}*/
|
|
@ -0,0 +1,84 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
|
||||||
|
</ion-buttons>
|
||||||
|
|
||||||
|
<ion-title><core-format-text [text]="siteName" contextLevel="system" [contextInstanceId]="0"></core-format-text></ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content>
|
||||||
|
<ion-list>
|
||||||
|
<ion-item *ngIf="siteInfo" class="ion-text-wrap"> <!-- @todo core-user-link [userId]="siteInfo.userid" -->
|
||||||
|
<ion-avatar slot="start"></ion-avatar> <!-- @todo core-user-avatar [user]="siteInfo" -->
|
||||||
|
<ion-label>
|
||||||
|
<h2>{{siteInfo.fullname}}</h2>
|
||||||
|
<p><core-format-text [text]="siteName" contextLevel="system" [contextInstanceId]="0" [wsNotFiltered]="true"></core-format-text></p>
|
||||||
|
<p>{{ siteUrl }}</p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item-divider></ion-item-divider>
|
||||||
|
<ion-item class="ion-text-center" *ngIf="(!handlers || !handlers.length) && !handlersLoaded">
|
||||||
|
<ion-spinner></ion-spinner>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item *ngFor="let handler of handlers" [ngClass]="['core-moremenu-handler', handler.class || '']" (click)="openHandler(handler)" title="{{ handler.title | translate }}" detail="true">
|
||||||
|
<core-icon [name]="handler.icon" slot="start"></core-icon>
|
||||||
|
<ion-label>
|
||||||
|
<h2>{{ handler.title | translate}}</h2>
|
||||||
|
</ion-label>
|
||||||
|
<ion-badge slot="end" *ngIf="handler.showBadge" [hidden]="handler.loading || !handler.badge">{{handler.badge}}</ion-badge>
|
||||||
|
<ion-spinner slot="end" *ngIf="handler.showBadge && handler.loading"></ion-spinner>
|
||||||
|
</ion-item>
|
||||||
|
<div *ngFor="let item of customItems" class="core-moremenu-customitem">
|
||||||
|
<ion-item *ngIf="item.type != 'embedded'" [href]="item.url" title="{{item.label}}"> <!-- @todo core-link [capture]="item.type == 'app'" [inApp]="item.type == 'inappbrowser'" -->
|
||||||
|
<core-icon [name]="item.icon" slot="start"></core-icon>
|
||||||
|
<ion-label>
|
||||||
|
<h2>{{item.label}}</h2>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item *ngIf="item.type == 'embedded'" (click)="openItem(item)" title="{{item.label}}">
|
||||||
|
<core-icon [name]="item.icon" slot="start"></core-icon>
|
||||||
|
<ion-label>
|
||||||
|
<h2>{{item.label}}</h2>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</div>
|
||||||
|
<ion-item *ngIf="showScanQR" (click)="scanQR()">
|
||||||
|
<core-icon name="fa-qrcode" slot="start" aria-hidden="true"></core-icon>
|
||||||
|
<ion-label>
|
||||||
|
<h2>{{ 'core.scanqr' | translate }}</h2>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item *ngIf="showWeb" [href]="siteInfo.siteurl" core-link autoLogin="yes" title="{{ 'core.mainmenu.website' | translate }}">
|
||||||
|
<ion-icon name="globe" slot="start" aria-hidden="true"></ion-icon>
|
||||||
|
<ion-label>
|
||||||
|
<h2>{{ 'core.mainmenu.website' | translate }}</h2>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item *ngIf="showHelp" [href]="docsUrl" core-link autoLogin="no" title="{{ 'core.mainmenu.help' | translate }}">
|
||||||
|
<ion-icon name="help-buoy" slot="start" aria-hidden="true"></ion-icon>
|
||||||
|
<ion-label>
|
||||||
|
<h2>{{ 'core.mainmenu.help' | translate }}</h2>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item (click)="openSitePreferences()" title="{{ 'core.settings.preferences' | translate }}">
|
||||||
|
<core-icon name="fa-wrench" slot="start"></core-icon>
|
||||||
|
<ion-label>
|
||||||
|
<h2>{{ 'core.settings.preferences' | translate }}</h2>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item (click)="logout()" title="{{ logoutLabel | translate }}">
|
||||||
|
<ion-icon name="log-out" slot="start" aria-hidden="true"></ion-icon>
|
||||||
|
<ion-label>
|
||||||
|
<h2>{{ logoutLabel | translate }}</h2>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item-divider></ion-item-divider>
|
||||||
|
<ion-item (click)="openAppSettings()" title="{{ 'core.settings.appsettings' | translate }}">
|
||||||
|
<core-icon name="fa-cogs" slot="start"></core-icon>
|
||||||
|
<ion-label>
|
||||||
|
<h2>{{ 'core.settings.appsettings' | translate }}</h2>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,182 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
|
||||||
|
import { CoreSites } from '@services/sites';
|
||||||
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
import { CoreSiteInfo } from '@classes/site';
|
||||||
|
import { CoreLoginHelper } from '@core/login/services/helper';
|
||||||
|
import { CoreMainMenuDelegate, CoreMainMenuHandlerData } from '../../services/delegate';
|
||||||
|
import { CoreMainMenu, CoreMainMenuCustomItem } from '../../services/mainmenu';
|
||||||
|
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page that displays the main menu of the app.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'page-core-mainmenu-more',
|
||||||
|
templateUrl: 'more.html',
|
||||||
|
styleUrls: ['more.scss'],
|
||||||
|
})
|
||||||
|
export class CoreMainMenuMorePage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
handlers?: CoreMainMenuHandlerData[];
|
||||||
|
allHandlers?: CoreMainMenuHandlerData[];
|
||||||
|
handlersLoaded = false;
|
||||||
|
siteInfo?: CoreSiteInfo;
|
||||||
|
siteName?: string;
|
||||||
|
logoutLabel?: string;
|
||||||
|
showScanQR: boolean;
|
||||||
|
showWeb?: boolean;
|
||||||
|
showHelp?: boolean;
|
||||||
|
docsUrl?: string;
|
||||||
|
customItems?: CoreMainMenuCustomItem[];
|
||||||
|
siteUrl?: string;
|
||||||
|
|
||||||
|
protected subscription!: Subscription;
|
||||||
|
protected langObserver: CoreEventObserver;
|
||||||
|
protected updateSiteObserver: CoreEventObserver;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected menuDelegate: CoreMainMenuDelegate,
|
||||||
|
) {
|
||||||
|
|
||||||
|
this.langObserver = CoreEvents.on(CoreEvents.LANGUAGE_CHANGED, this.loadSiteInfo.bind(this));
|
||||||
|
this.updateSiteObserver = CoreEvents.on(
|
||||||
|
CoreEvents.SITE_UPDATED,
|
||||||
|
this.loadSiteInfo.bind(this),
|
||||||
|
CoreSites.instance.getCurrentSiteId(),
|
||||||
|
);
|
||||||
|
this.loadSiteInfo();
|
||||||
|
this.showScanQR = CoreUtils.instance.canScanQR() &&
|
||||||
|
!CoreSites.instance.getCurrentSite()?.isFeatureDisabled('CoreMainMenuDelegate_QrReader');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize component.
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
// Load the handlers.
|
||||||
|
this.subscription = this.menuDelegate.getHandlers().subscribe((handlers) => {
|
||||||
|
this.allHandlers = handlers;
|
||||||
|
|
||||||
|
this.initHandlers();
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('resize', this.initHandlers.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page destroyed.
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
window.removeEventListener('resize', this.initHandlers.bind(this));
|
||||||
|
this.langObserver?.off();
|
||||||
|
this.updateSiteObserver?.off();
|
||||||
|
this.subscription?.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init handlers on change (size or handlers).
|
||||||
|
*/
|
||||||
|
initHandlers(): void {
|
||||||
|
if (!this.allHandlers) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the main handlers not to display them in this view.
|
||||||
|
const mainHandlers = this.allHandlers
|
||||||
|
.filter((handler) => !handler.onlyInMore)
|
||||||
|
.slice(0, CoreMainMenu.instance.getNumItems());
|
||||||
|
|
||||||
|
// Get only the handlers that don't appear in the main view.
|
||||||
|
this.handlers = this.allHandlers.filter((handler) => mainHandlers.indexOf(handler) == -1);
|
||||||
|
|
||||||
|
this.handlersLoaded = this.menuDelegate.areHandlersLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the site info required by the view.
|
||||||
|
*/
|
||||||
|
protected async loadSiteInfo(): Promise<void> {
|
||||||
|
const currentSite = CoreSites.instance.getCurrentSite();
|
||||||
|
|
||||||
|
if (!currentSite) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.siteInfo = currentSite.getInfo();
|
||||||
|
this.siteName = currentSite.getSiteName();
|
||||||
|
this.siteUrl = currentSite.getURL();
|
||||||
|
this.logoutLabel = CoreLoginHelper.instance.getLogoutLabel(currentSite);
|
||||||
|
this.showWeb = !currentSite.isFeatureDisabled('CoreMainMenuDelegate_website');
|
||||||
|
this.showHelp = !currentSite.isFeatureDisabled('CoreMainMenuDelegate_help');
|
||||||
|
|
||||||
|
this.docsUrl = await currentSite.getDocsUrl();
|
||||||
|
|
||||||
|
this.customItems = await CoreMainMenu.instance.getCustomMenuItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a handler.
|
||||||
|
*
|
||||||
|
* @param handler Handler to open.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
openHandler(handler: CoreMainMenuHandlerData): void {
|
||||||
|
// @todo
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open an embedded custom item.
|
||||||
|
*
|
||||||
|
* @param item Item to open.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
openItem(item: CoreMainMenuCustomItem): void {
|
||||||
|
// @todo
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open app settings page.
|
||||||
|
*/
|
||||||
|
openAppSettings(): void {
|
||||||
|
// @todo
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open site settings page.
|
||||||
|
*/
|
||||||
|
openSitePreferences(): void {
|
||||||
|
// @todo
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan and treat a QR code.
|
||||||
|
*/
|
||||||
|
async scanQR(): Promise<void> {
|
||||||
|
// Scan for a QR code.
|
||||||
|
// @todo
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logout the user.
|
||||||
|
*/
|
||||||
|
logout(): void {
|
||||||
|
CoreSites.instance.logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
/*
|
||||||
|
$core-more-icon: $gray-darker !default;
|
||||||
|
$core-more-background-ios: $list-ios-background-color !default;
|
||||||
|
$core-more-background-md: $list-md-background-color !default;
|
||||||
|
$core-more-activated-background-ios: color-shade($core-more-background-ios) !default;
|
||||||
|
$core-more-activated-background-md: color-shade($core-more-background-md) !default;
|
||||||
|
$core-more-divider-ios: $item-ios-divider-background !default;
|
||||||
|
$core-more-divider-md: $item-md-divider-background !default;
|
||||||
|
$core-more-border-ios: $list-ios-border-color !default;
|
||||||
|
$core-more-border-md: $list-md-border-color !default;
|
||||||
|
$core-more-color-ios: $list-ios-text-color!default;
|
||||||
|
$core-more-color-md: $list-md-text-color !default;
|
||||||
|
|
||||||
|
.item-block {
|
||||||
|
&.item-ios {
|
||||||
|
background-color: $core-more-background-ios;
|
||||||
|
color: $core-more-color-ios;
|
||||||
|
p {
|
||||||
|
color: $core-more-color-ios;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-inner {
|
||||||
|
border-bottom: $hairlines-width solid $core-more-border-ios;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.item-md {
|
||||||
|
background-color: $core-more-background-md;
|
||||||
|
color: $core-more-color-md;
|
||||||
|
p {
|
||||||
|
color: $core-more-color-md;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-inner {
|
||||||
|
border-bottom: 1px solid $core-more-border-md;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.activated {
|
||||||
|
&.item-ios {
|
||||||
|
background-color: $core-more-activated-background-ios;
|
||||||
|
}
|
||||||
|
&.item-md {
|
||||||
|
background-color: $core-more-activated-background-md;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-icon {
|
||||||
|
color: $core-more-icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-divider {
|
||||||
|
&.item-ios {
|
||||||
|
background-color: $core-more-divider-ios;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.item-md {
|
||||||
|
background-color: $core-more-divider-md;
|
||||||
|
border-bottom: $core-more-border-md;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include darkmode() {
|
||||||
|
ion-icon {
|
||||||
|
color: $core-dark-text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-divider {
|
||||||
|
&.item-ios,
|
||||||
|
&.item-md {
|
||||||
|
color: $core-dark-text-color;
|
||||||
|
background-color: $core-dark-item-divider-bg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-block {
|
||||||
|
&.item-ios,
|
||||||
|
&.item-md {
|
||||||
|
color: $core-dark-text-color;
|
||||||
|
background-color: $core-dark-item-bg-color;
|
||||||
|
p {
|
||||||
|
color: $core-dark-text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&.activated {
|
||||||
|
&.item-ios {
|
||||||
|
background-color: $core-more-activated-background-ios;
|
||||||
|
}
|
||||||
|
&.item-md {
|
||||||
|
background-color: $core-more-activated-background-md;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
|
@ -0,0 +1,175 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Params } from '@angular/router';
|
||||||
|
import { Subject, BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
|
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
|
||||||
|
import { CoreEvents } from '@singletons/events';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface that all main menu handlers must implement.
|
||||||
|
*/
|
||||||
|
export interface CoreMainMenuHandler extends CoreDelegateHandler {
|
||||||
|
/**
|
||||||
|
* The highest priority is displayed first.
|
||||||
|
*/
|
||||||
|
priority?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the data needed to render the handler.
|
||||||
|
*
|
||||||
|
* @return Data.
|
||||||
|
*/
|
||||||
|
getDisplayData(): CoreMainMenuHandlerData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data needed to render a main menu handler. It's returned by the handler.
|
||||||
|
*/
|
||||||
|
export interface CoreMainMenuHandlerData {
|
||||||
|
/**
|
||||||
|
* Name of the page to load for the handler.
|
||||||
|
*/
|
||||||
|
page: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Title to display for the handler.
|
||||||
|
*/
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the icon to display for the handler.
|
||||||
|
*/
|
||||||
|
icon: string; // Name of the icon to display in the tab.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to add to the displayed handler.
|
||||||
|
*/
|
||||||
|
class?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the handler has badge to show or not.
|
||||||
|
*/
|
||||||
|
showBadge?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text to display on the badge. Only used if showBadge is true.
|
||||||
|
*/
|
||||||
|
badge?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, the badge number is being loaded. Only used if showBadge is true.
|
||||||
|
*/
|
||||||
|
loading?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params to pass to the page.
|
||||||
|
*/
|
||||||
|
pageParams?: Params;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the handler should only appear in More menu.
|
||||||
|
*/
|
||||||
|
onlyInMore?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data returned by the delegate for each handler.
|
||||||
|
*/
|
||||||
|
export interface CoreMainMenuHandlerToDisplay extends CoreMainMenuHandlerData {
|
||||||
|
/**
|
||||||
|
* Name of the handler.
|
||||||
|
*/
|
||||||
|
name?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Priority of the handler.
|
||||||
|
*/
|
||||||
|
priority?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide tab. Used then resizing.
|
||||||
|
*/
|
||||||
|
hide?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service to interact with plugins to be shown in the main menu. Provides functions to register a plugin
|
||||||
|
* and notify an update in the data.
|
||||||
|
*/
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class CoreMainMenuDelegate extends CoreDelegate {
|
||||||
|
|
||||||
|
protected loaded = false;
|
||||||
|
protected siteHandlers: Subject<CoreMainMenuHandlerToDisplay[]> = new BehaviorSubject<CoreMainMenuHandlerToDisplay[]>([]);
|
||||||
|
protected featurePrefix = 'CoreMainMenuDelegate_';
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super('CoreMainMenuDelegate', true);
|
||||||
|
|
||||||
|
CoreEvents.on(CoreEvents.LOGOUT, this.clearSiteHandlers.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if handlers are loaded.
|
||||||
|
*
|
||||||
|
* @return True if handlers are loaded, false otherwise.
|
||||||
|
*/
|
||||||
|
areHandlersLoaded(): boolean {
|
||||||
|
return this.loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear current site handlers. Reserved for core use.
|
||||||
|
*/
|
||||||
|
protected clearSiteHandlers(): void {
|
||||||
|
this.loaded = false;
|
||||||
|
this.siteHandlers.next([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the handlers for the current site.
|
||||||
|
*
|
||||||
|
* @return An observable that will receive the handlers.
|
||||||
|
*/
|
||||||
|
getHandlers(): Subject<CoreMainMenuHandlerToDisplay[]> {
|
||||||
|
return this.siteHandlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update handlers Data.
|
||||||
|
*/
|
||||||
|
updateData(): void {
|
||||||
|
const displayData: CoreMainMenuHandlerToDisplay[] = [];
|
||||||
|
|
||||||
|
for (const name in this.enabledHandlers) {
|
||||||
|
const handler = <CoreMainMenuHandler> this.enabledHandlers[name];
|
||||||
|
const data = <CoreMainMenuHandlerToDisplay> handler.getDisplayData();
|
||||||
|
|
||||||
|
data.name = name;
|
||||||
|
data.priority = handler.priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort them by priority.
|
||||||
|
displayData.sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
||||||
|
|
||||||
|
this.loaded = true;
|
||||||
|
this.siteHandlers.next(displayData);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,275 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
import { CoreApp } from '@services/app';
|
||||||
|
import { CoreLang } from '@services/lang';
|
||||||
|
import { CoreSites } from '@services/sites';
|
||||||
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
import { CoreConstants } from '@core/constants';
|
||||||
|
import { CoreMainMenuDelegate, CoreMainMenuHandlerToDisplay } from './delegate';
|
||||||
|
import { makeSingleton } from '@singletons/core.singletons';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service that provides some features regarding Main Menu.
|
||||||
|
*/
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class CoreMainMenuProvider {
|
||||||
|
|
||||||
|
static readonly NUM_MAIN_HANDLERS = 4;
|
||||||
|
static readonly ITEM_MIN_WIDTH = 72; // Min with of every item, based on 5 items on a 360 pixel wide screen.
|
||||||
|
|
||||||
|
protected tablet = false;
|
||||||
|
|
||||||
|
constructor(protected menuDelegate: CoreMainMenuDelegate) {
|
||||||
|
this.tablet = !!(window?.innerWidth && window.innerWidth >= 576 && window.innerHeight >= 576);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current main menu handlers.
|
||||||
|
*
|
||||||
|
* @return Promise resolved with the current main menu handlers.
|
||||||
|
*/
|
||||||
|
getCurrentMainMenuHandlers(): Promise<CoreMainMenuHandlerToDisplay[]> {
|
||||||
|
const deferred = CoreUtils.instance.promiseDefer<CoreMainMenuHandlerToDisplay[]>();
|
||||||
|
|
||||||
|
const subscription = this.menuDelegate.getHandlers().subscribe((handlers) => {
|
||||||
|
subscription?.unsubscribe();
|
||||||
|
|
||||||
|
// Remove the handlers that should only appear in the More menu.
|
||||||
|
handlers = handlers.filter(handler => !handler.onlyInMore);
|
||||||
|
|
||||||
|
// Return main handlers.
|
||||||
|
deferred.resolve(handlers.slice(0, this.getNumItems()));
|
||||||
|
});
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of custom menu items for a certain site.
|
||||||
|
*
|
||||||
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @return List of custom menu items.
|
||||||
|
*/
|
||||||
|
async getCustomMenuItems(siteId?: string): Promise<CoreMainMenuCustomItem[]> {
|
||||||
|
const site = await CoreSites.instance.getSite(siteId);
|
||||||
|
|
||||||
|
const itemsString = site.getStoredConfig('tool_mobile_custommenuitems');
|
||||||
|
const map: CustomMenuItemsMap = {};
|
||||||
|
const result: CoreMainMenuCustomItem[] = [];
|
||||||
|
|
||||||
|
let position = 0; // Position of each item, to keep the same order as it's configured.
|
||||||
|
|
||||||
|
if (!itemsString || typeof itemsString != 'string') {
|
||||||
|
// Setting not valid.
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add items to the map.
|
||||||
|
const items = itemsString.split(/(?:\r\n|\r|\n)/);
|
||||||
|
items.forEach((item) => {
|
||||||
|
const values = item.split('|');
|
||||||
|
const label = values[0] ? values[0].trim() : values[0];
|
||||||
|
const url = values[1] ? values[1].trim() : values[1];
|
||||||
|
const type = values[2] ? values[2].trim() : values[2];
|
||||||
|
const lang = (values[3] ? values[3].trim() : values[3]) || 'none';
|
||||||
|
let icon = values[4] ? values[4].trim() : values[4];
|
||||||
|
|
||||||
|
if (!label || !url || !type) {
|
||||||
|
// Invalid item, ignore it.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = url + '#' + type;
|
||||||
|
if (!icon) {
|
||||||
|
// Icon not defined, use default one.
|
||||||
|
icon = type == 'embedded' ? 'fa-square-o' : 'fa-link'; // @todo: Find a better icon for embedded.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!map[id]) {
|
||||||
|
// New entry, add it to the map.
|
||||||
|
map[id] = {
|
||||||
|
url: url,
|
||||||
|
type: type,
|
||||||
|
position: position,
|
||||||
|
labels: {},
|
||||||
|
};
|
||||||
|
position++;
|
||||||
|
}
|
||||||
|
|
||||||
|
map[id].labels[lang.toLowerCase()] = {
|
||||||
|
label: label,
|
||||||
|
icon: icon,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!position) {
|
||||||
|
// No valid items found, stop.
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentLang = await CoreLang.instance.getCurrentLanguage();
|
||||||
|
|
||||||
|
const fallbackLang = CoreConstants.CONFIG.default_lang || 'en';
|
||||||
|
|
||||||
|
// Get the right label for each entry and add it to the result.
|
||||||
|
for (const id in map) {
|
||||||
|
const entry = map[id];
|
||||||
|
let data = entry.labels[currentLang] || entry.labels[currentLang + '_only'] ||
|
||||||
|
entry.labels.none || entry.labels[fallbackLang];
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
// No valid label found, get the first one that is not "_only".
|
||||||
|
for (const lang in entry.labels) {
|
||||||
|
if (lang.indexOf('_only') == -1) {
|
||||||
|
data = entry.labels[lang];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
// No valid label, ignore this entry.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result[entry.position] = {
|
||||||
|
url: entry.url,
|
||||||
|
type: entry.type,
|
||||||
|
label: data.label,
|
||||||
|
icon: data.icon,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove undefined values.
|
||||||
|
return result.filter((entry) => typeof entry != 'undefined');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of items to be shown on the main menu bar.
|
||||||
|
*
|
||||||
|
* @return Number of items depending on the device width.
|
||||||
|
*/
|
||||||
|
getNumItems(): number {
|
||||||
|
if (!this.isResponsiveMainMenuItemsDisabledInCurrentSite() && window && window.innerWidth) {
|
||||||
|
let numElements: number;
|
||||||
|
|
||||||
|
if (this.tablet) {
|
||||||
|
// Tablet, menu will be displayed vertically.
|
||||||
|
numElements = Math.floor(window.innerHeight / CoreMainMenuProvider.ITEM_MIN_WIDTH);
|
||||||
|
} else {
|
||||||
|
numElements = Math.floor(window.innerWidth / CoreMainMenuProvider.ITEM_MIN_WIDTH);
|
||||||
|
|
||||||
|
// Set a maximum elements to show and skip more button.
|
||||||
|
numElements = numElements >= 5 ? 5 : numElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a mínimum elements to show and skip more button.
|
||||||
|
return numElements > 1 ? numElements - 1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CoreMainMenuProvider.NUM_MAIN_HANDLERS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get tabs placement depending on the device size.
|
||||||
|
*
|
||||||
|
* @return Tabs placement including side value.
|
||||||
|
*/
|
||||||
|
getTabPlacement(): string {
|
||||||
|
const tablet = !!(window.innerWidth && window.innerWidth >= 576 && (window.innerHeight >= 576 ||
|
||||||
|
((CoreApp.instance.isKeyboardVisible() || CoreApp.instance.isKeyboardOpening()) && window.innerHeight >= 200)));
|
||||||
|
|
||||||
|
if (tablet != this.tablet) {
|
||||||
|
this.tablet = tablet;
|
||||||
|
|
||||||
|
// @todo Resize so content margins can be updated.
|
||||||
|
}
|
||||||
|
|
||||||
|
return tablet ? 'side' : 'bottom';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a certain page is the root of a main menu handler currently displayed.
|
||||||
|
*
|
||||||
|
* @param page Name of the page.
|
||||||
|
* @param pageParams Page params.
|
||||||
|
* @return Promise resolved with boolean: whether it's the root of a main menu handler.
|
||||||
|
*/
|
||||||
|
async isCurrentMainMenuHandler(pageName: string): Promise<boolean> {
|
||||||
|
const handlers = await this.getCurrentMainMenuHandlers();
|
||||||
|
|
||||||
|
const handler = handlers.find((handler) => handler.page == pageName);
|
||||||
|
|
||||||
|
return !!handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if responsive main menu items is disabled in the current site.
|
||||||
|
*
|
||||||
|
* @return Whether it's disabled.
|
||||||
|
*/
|
||||||
|
protected isResponsiveMainMenuItemsDisabledInCurrentSite(): boolean {
|
||||||
|
const site = CoreSites.instance.getCurrentSite();
|
||||||
|
|
||||||
|
return !!site?.isFeatureDisabled('NoDelegate_ResponsiveMainMenuItems');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CoreMainMenu extends makeSingleton(CoreMainMenuProvider) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom main menu item.
|
||||||
|
*/
|
||||||
|
export interface CoreMainMenuCustomItem {
|
||||||
|
/**
|
||||||
|
* Type of the item: app, inappbrowser, browser or embedded.
|
||||||
|
*/
|
||||||
|
type: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Url of the item.
|
||||||
|
*/
|
||||||
|
url: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label to display for the item.
|
||||||
|
*/
|
||||||
|
label: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the icon to display for the item.
|
||||||
|
*/
|
||||||
|
icon: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map of custom menu items.
|
||||||
|
*/
|
||||||
|
type CustomMenuItemsMap = Record<string, {
|
||||||
|
url: string;
|
||||||
|
type: string;
|
||||||
|
position: number;
|
||||||
|
labels: {
|
||||||
|
[lang: string]: {
|
||||||
|
label: string;
|
||||||
|
icon: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}>;
|
|
@ -1312,7 +1312,7 @@ export class CoreSitesProvider {
|
||||||
async logout(): Promise<void> {
|
async logout(): Promise<void> {
|
||||||
await this.dbReady;
|
await this.dbReady;
|
||||||
|
|
||||||
let siteId;
|
let siteId: string | undefined;
|
||||||
const promises: Promise<unknown>[] = [];
|
const promises: Promise<unknown>[] = [];
|
||||||
|
|
||||||
if (this.currentSite) {
|
if (this.currentSite) {
|
||||||
|
|
|
@ -208,3 +208,11 @@ export type CoreEventLoadingChangedData = {
|
||||||
loaded: boolean;
|
loaded: boolean;
|
||||||
uniqueId: string;
|
uniqueId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data passed to LOAD_PAGE_MAIN_MENU event.
|
||||||
|
*/
|
||||||
|
export type CoreEventLoadPageMainMenuData = {
|
||||||
|
redirectPage: string;
|
||||||
|
redirectParams?: Params;
|
||||||
|
};
|
||||||
|
|
|
@ -304,6 +304,10 @@
|
||||||
"core.browser": "Browser",
|
"core.browser": "Browser",
|
||||||
"core.copiedtoclipboard": "Text copied to clipboard",
|
"core.copiedtoclipboard": "Text copied to clipboard",
|
||||||
"core.login.yourenteredsite": "Connect to your site",
|
"core.login.yourenteredsite": "Connect to your site",
|
||||||
|
"core.mainmenu.changesite": "Change site",
|
||||||
|
"core.mainmenu.help": "Help",
|
||||||
|
"core.mainmenu.logout": "Log out",
|
||||||
|
"core.mainmenu.website": "Website",
|
||||||
"core.no": "No",
|
"core.no": "No",
|
||||||
"core.offline": "Offline",
|
"core.offline": "Offline",
|
||||||
"core.ok": "OK",
|
"core.ok": "OK",
|
||||||
|
|
Loading…
Reference in New Issue